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("""); //$NON-NLS-1$ 213 } else if (c == '<') { 214 sb.append("<"); //$NON-NLS-1$ 215 } else if (c == '\'') { 216 sb.append("'"); //$NON-NLS-1$ 217 } else if (c == '&') { 218 sb.append("&"); //$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