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