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 
26 /**
27  * Represents an API element, e.g. class, method or field.
28  */
29 public class ApiElement implements Comparable<ApiElement> {
30     private final String mName;
31     private int mSince;
32     private int mDeprecatedIn;
33     private int mLastPresentIn;
34 
35     /**
36      * @param name       the name of the API element
37      * @param version    an API version for which the API element existed
38      * @param deprecated whether the API element was deprecated in the API version in question
39      */
ApiElement(String name, int version, boolean deprecated)40     public ApiElement(String name, int version, boolean deprecated) {
41         assert name != null;
42         assert version > 0;
43         mName = name;
44         mSince = version;
45         mLastPresentIn = version;
46         if (deprecated) {
47             mDeprecatedIn = version;
48         }
49     }
50 
51     /**
52      * @param name    the name of the API element
53      * @param version an API version for which the API element existed
54      */
ApiElement(String name, int version)55     public ApiElement(String name, int version) {
56         this(name, version, false);
57     }
58 
ApiElement(String name)59     protected ApiElement(String name) {
60         assert name != null;
61         mName = name;
62     }
63 
64     /**
65      * Returns the name of the API element.
66      */
getName()67     public final String getName() {
68         return mName;
69     }
70 
71     /**
72      * Checks if this API element was introduced not later than another API element.
73      *
74      * @param other the API element to compare to
75      * @return true if this API element was introduced not later than {@code other}
76      */
introducedNotLaterThan(ApiElement other)77     public final boolean introducedNotLaterThan(ApiElement other) {
78         return mSince <= other.mSince;
79     }
80 
81     /**
82      * Updates the API element with information for a specific API version.
83      *
84      * @param version    an API version for which the API element existed
85      * @param deprecated whether the API element was deprecated in the API version in question
86      */
update(int version, boolean deprecated)87     public void update(int version, boolean deprecated) {
88         assert version > 0;
89         if (mSince > version) {
90             mSince = version;
91         }
92         if (mLastPresentIn < version) {
93             mLastPresentIn = version;
94         }
95         if (deprecated) {
96             if (mDeprecatedIn == 0 || mDeprecatedIn > version) {
97                 mDeprecatedIn = version;
98             }
99         }
100     }
101 
102     /**
103      * Updates the API element with information for a specific API version.
104      *
105      * @param version an API version for which the API element existed
106      */
update(int version)107     public void update(int version) {
108         update(version, false);
109     }
110 
111     /**
112      * Checks whether the API element is deprecated or not.
113      */
isDeprecated()114     public final boolean isDeprecated() {
115         return mDeprecatedIn != 0;
116     }
117 
118     /**
119      * Prints an XML representation of the element to a stream terminated by a line break.
120      * Attributes with values matching the parent API element are omitted.
121      *
122      * @param tag           the tag of the XML element
123      * @param parentElement the parent API element
124      * @param indent        the whitespace prefix to insert before the XML element
125      * @param stream        the stream to print the XML element to
126      */
print(String tag, ApiElement parentElement, String indent, PrintStream stream)127     public void print(String tag, ApiElement parentElement, String indent, PrintStream stream) {
128         print(tag, true, parentElement, indent, stream);
129     }
130 
131     /**
132      * Prints an XML representation of the element to a stream terminated by a line break.
133      * Attributes with values matching the parent API element are omitted.
134      *
135      * @param tag           the tag of the XML element
136      * @param closeTag      if true the XML element is terminated by "/>", otherwise the closing
137      *                      tag of the element is not printed
138      * @param parentElement the parent API element
139      * @param indent        the whitespace prefix to insert before the XML element
140      * @param stream        the stream to print the XML element to
141      * @see #printClosingTag(String, String, PrintStream)
142      */
print(String tag, boolean closeTag, ApiElement parentElement, String indent, PrintStream stream)143     protected void print(String tag, boolean closeTag, ApiElement parentElement, String indent,
144                          PrintStream stream) {
145         stream.print(indent);
146         stream.print('<');
147         stream.print(tag);
148         stream.print(" name=\"");
149         stream.print(encodeAttribute(mName));
150         if (mSince > parentElement.mSince) {
151             stream.print("\" since=\"");
152             stream.print(mSince);
153         }
154         if (mDeprecatedIn != 0) {
155             stream.print("\" deprecated=\"");
156             stream.print(mDeprecatedIn);
157         }
158         if (mLastPresentIn < parentElement.mLastPresentIn) {
159             stream.print("\" removed=\"");
160             stream.print(mLastPresentIn + 1);
161         }
162         stream.print('"');
163         if (closeTag) {
164             stream.print('/');
165         }
166         stream.println('>');
167     }
168 
169     /**
170      * Prints homogeneous XML elements to a stream. Each element is printed on a separate line.
171      * Attributes with values matching the parent API element are omitted.
172      *
173      * @param elements the elements to print
174      * @param tag      the tag of the XML elements
175      * @param indent   the whitespace prefix to insert before each XML element
176      * @param stream   the stream to print the XML elements to
177      */
print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream)178     protected void print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream) {
179         for (ApiElement element : sortedList(elements)) {
180             element.print(tag, this, indent, stream);
181         }
182     }
183 
sortedList(Collection<T> elements)184     private <T extends ApiElement> List<T> sortedList(Collection<T> elements) {
185         List<T> list = new ArrayList<>(elements);
186         Collections.sort(list);
187         return list;
188     }
189 
190     /**
191      * Prints a closing tag of an XML element terminated by a line break.
192      *
193      * @param tag    the tag of the element
194      * @param indent the whitespace prefix to insert before the closing tag
195      * @param stream the stream to print the XML element to
196      */
printClosingTag(String tag, String indent, PrintStream stream)197     protected static void printClosingTag(String tag, String indent, PrintStream stream) {
198         stream.print(indent);
199         stream.print("</");
200         stream.print(tag);
201         stream.println('>');
202     }
203 
encodeAttribute(String attribute)204     protected static String encodeAttribute(String attribute) {
205         StringBuilder sb = new StringBuilder();
206         int n = attribute.length();
207         // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
208         // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe side).
209         for (int i = 0; i < n; i++) {
210             char c = attribute.charAt(i);
211             if (c == '"') {
212                 sb.append("&quot;"); //$NON-NLS-1$
213             } else if (c == '<') {
214                 sb.append("&lt;"); //$NON-NLS-1$
215             } else if (c == '\'') {
216                 sb.append("&apos;"); //$NON-NLS-1$
217             } else if (c == '&') {
218                 sb.append("&amp;"); //$NON-NLS-1$
219             } else {
220                 sb.append(c);
221             }
222         }
223 
224         return sb.toString();
225     }
226 
227     @Override
compareTo(@otNull ApiElement other)228     public int compareTo(@NotNull ApiElement other) {
229         return mName.compareTo(other.mName);
230     }
231 }
232