1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tools.metalava.apilevels; 17 18 import org.jetbrains.annotations.NotNull; 19 20 import java.io.PrintStream; 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Objects; 26 27 /** 28 * Represents an API element, e.g. class, method or field. 29 */ 30 public class ApiElement implements Comparable<ApiElement> { 31 public static final int NEVER = Integer.MAX_VALUE; 32 33 private final String mName; 34 35 /** 36 * The Android platform SDK version this API was first introduced in. 37 */ 38 private int mSince; 39 40 /** 41 * The Android extension SDK version this API was first introduced in. 42 */ 43 private int mSinceExtension = NEVER; 44 45 /** 46 * The SDKs and their versions this API was first introduced in. 47 * 48 * The value is a comma-separated list of <int>:<int> values, where the first 49 * <int> is the integer ID of an SDK, and the second <int> the version of that SDK, 50 * in which this API first appeared. 51 * 52 * This field is a super-set of mSince, and if non-null/non-empty, should be preferred. 53 */ 54 private String mSdks; 55 56 private String mMainlineModule; 57 private int mDeprecatedIn; 58 private int mLastPresentIn; 59 60 /** 61 * @param name the name of the API element 62 * @param version an API version for which the API element existed, or -1 if the class does 63 * not yet exist in the Android SDK (only in extension SDKs) 64 * @param deprecated whether the API element was deprecated in the API version in question 65 */ ApiElement(String name, int version, boolean deprecated)66 ApiElement(String name, int version, boolean deprecated) { 67 assert name != null; 68 mName = name; 69 mSince = version; 70 mLastPresentIn = version; 71 if (deprecated) { 72 mDeprecatedIn = version; 73 } 74 } 75 76 /** 77 * @param name the name of the API element 78 * @param version an API version for which the API element existed 79 */ ApiElement(String name, int version)80 ApiElement(String name, int version) { 81 this(name, version, false); 82 } 83 ApiElement(String name)84 ApiElement(String name) { 85 assert name != null; 86 mName = name; 87 } 88 89 /** 90 * Returns the name of the API element. 91 */ getName()92 public final String getName() { 93 return mName; 94 } 95 96 /** 97 * The Android API level of this ApiElement. 98 */ getSince()99 public int getSince() { 100 return mSince; 101 } 102 103 /** 104 * The extension version of this ApiElement. 105 */ getSinceExtension()106 public int getSinceExtension() { 107 return mSinceExtension; 108 } 109 110 /** 111 * Checks if this API element was introduced not later than another API element. 112 * 113 * @param other the API element to compare to 114 * @return true if this API element was introduced not later than {@code other} 115 */ introducedNotLaterThan(ApiElement other)116 final boolean introducedNotLaterThan(ApiElement other) { 117 return mSince <= other.mSince; 118 } 119 120 /** 121 * Updates the API element with information for a specific API version. 122 * 123 * @param version an API version for which the API element existed 124 * @param deprecated whether the API element was deprecated in the API version in question 125 */ update(int version, boolean deprecated)126 void update(int version, boolean deprecated) { 127 assert version > 0; 128 if (mSince > version) { 129 mSince = version; 130 } 131 if (mLastPresentIn < version) { 132 mLastPresentIn = version; 133 } 134 if (deprecated) { 135 if (mDeprecatedIn == 0 || mDeprecatedIn > version) { 136 mDeprecatedIn = version; 137 } 138 } 139 } 140 141 /** 142 * Updates the API element with information for a specific API version. 143 * 144 * @param version an API version for which the API element existed 145 */ update(int version)146 public void update(int version) { 147 update(version, isDeprecated()); 148 } 149 150 /** 151 * Analoguous to update(), but for extensions sdk versions. 152 * 153 * @param version an extension SDK version for which the API element existed 154 */ updateExtension(int version)155 public void updateExtension(int version) { 156 assert version > 0; 157 if (mSinceExtension > version) { 158 mSinceExtension = version; 159 } 160 } 161 updateSdks(String sdks)162 public void updateSdks(String sdks) { mSdks = sdks; } 163 updateMainlineModule(String module)164 public void updateMainlineModule(String module) { mMainlineModule = module; } 165 getMainlineModule()166 public String getMainlineModule() { return mMainlineModule; } 167 168 /** 169 * Checks whether the API element is deprecated or not. 170 */ isDeprecated()171 public final boolean isDeprecated() { 172 return mDeprecatedIn != 0; 173 } 174 175 /** 176 * Prints an XML representation of the element to a stream terminated by a line break. 177 * Attributes with values matching the parent API element are omitted. 178 * 179 * @param tag the tag of the XML element 180 * @param parentElement the parent API element 181 * @param indent the whitespace prefix to insert before the XML element 182 * @param stream the stream to print the XML element to 183 */ print(String tag, ApiElement parentElement, String indent, PrintStream stream)184 void print(String tag, ApiElement parentElement, String indent, PrintStream stream) { 185 print(tag, true, parentElement, indent, stream); 186 } 187 188 /** 189 * Prints an XML representation of the element to a stream terminated by a line break. 190 * Attributes with values matching the parent API element are omitted. 191 * 192 * @param tag the tag of the XML element 193 * @param closeTag if true the XML element is terminated by "/>", otherwise the closing 194 * tag of the element is not printed 195 * @param parentElement the parent API element 196 * @param indent the whitespace prefix to insert before the XML element 197 * @param stream the stream to print the XML element to 198 * @see #printClosingTag(String, String, PrintStream) 199 */ print(String tag, boolean closeTag, ApiElement parentElement, String indent, PrintStream stream)200 void print(String tag, boolean closeTag, ApiElement parentElement, String indent, 201 PrintStream stream) { 202 stream.print(indent); 203 stream.print('<'); 204 stream.print(tag); 205 stream.print(" name=\""); 206 stream.print(encodeAttribute(mName)); 207 if (!isEmpty(mMainlineModule) && !isEmpty(mSdks)) { 208 stream.print("\" module=\""); 209 stream.print(encodeAttribute(mMainlineModule)); 210 } 211 if (mSince > parentElement.mSince) { 212 stream.print("\" since=\""); 213 stream.print(mSince); 214 } 215 if (!isEmpty(mSdks) && !Objects.equals(mSdks, parentElement.mSdks)) { 216 stream.print("\" sdks=\""); 217 stream.print(mSdks); 218 } 219 if (mDeprecatedIn != 0) { 220 stream.print("\" deprecated=\""); 221 stream.print(mDeprecatedIn); 222 } 223 if (mLastPresentIn < parentElement.mLastPresentIn) { 224 stream.print("\" removed=\""); 225 stream.print(mLastPresentIn + 1); 226 } 227 stream.print('"'); 228 if (closeTag) { 229 stream.print('/'); 230 } 231 stream.println('>'); 232 } 233 isEmpty(String s)234 private boolean isEmpty(String s) { 235 return s == null || s.isEmpty(); 236 } 237 238 /** 239 * Prints homogeneous XML elements to a stream. Each element is printed on a separate line. 240 * Attributes with values matching the parent API element are omitted. 241 * 242 * @param elements the elements to print 243 * @param tag the tag of the XML elements 244 * @param indent the whitespace prefix to insert before each XML element 245 * @param stream the stream to print the XML elements to 246 */ print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream)247 void print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream) { 248 for (ApiElement element : sortedList(elements)) { 249 element.print(tag, this, indent, stream); 250 } 251 } 252 sortedList(Collection<T> elements)253 private <T extends ApiElement> List<T> sortedList(Collection<T> elements) { 254 List<T> list = new ArrayList<>(elements); 255 Collections.sort(list); 256 return list; 257 } 258 259 /** 260 * Prints a closing tag of an XML element terminated by a line break. 261 * 262 * @param tag the tag of the element 263 * @param indent the whitespace prefix to insert before the closing tag 264 * @param stream the stream to print the XML element to 265 */ printClosingTag(String tag, String indent, PrintStream stream)266 static void printClosingTag(String tag, String indent, PrintStream stream) { 267 stream.print(indent); 268 stream.print("</"); 269 stream.print(tag); 270 stream.println('>'); 271 } 272 encodeAttribute(String attribute)273 private static String encodeAttribute(String attribute) { 274 StringBuilder sb = new StringBuilder(); 275 int n = attribute.length(); 276 // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue 277 // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe side). 278 for (int i = 0; i < n; i++) { 279 char c = attribute.charAt(i); 280 if (c == '"') { 281 sb.append("""); //$NON-NLS-1$ 282 } else if (c == '<') { 283 sb.append("<"); //$NON-NLS-1$ 284 } else if (c == '\'') { 285 sb.append("'"); //$NON-NLS-1$ 286 } else if (c == '&') { 287 sb.append("&"); //$NON-NLS-1$ 288 } else { 289 sb.append(c); 290 } 291 } 292 293 return sb.toString(); 294 } 295 296 @Override compareTo(@otNull ApiElement other)297 public int compareTo(@NotNull ApiElement other) { 298 return mName.compareTo(other.mName); 299 } 300 } 301