• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &lt;int&gt;:&lt;int&gt; values, where the first
49      * &lt;int&gt; is the integer ID of an SDK, and the second &lt;int&gt; 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("&quot;"); //$NON-NLS-1$
282             } else if (c == '<') {
283                 sb.append("&lt;"); //$NON-NLS-1$
284             } else if (c == '\'') {
285                 sb.append("&apos;"); //$NON-NLS-1$
286             } else if (c == '&') {
287                 sb.append("&amp;"); //$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