Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 Google Inc. |
| 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 | |
| 17 | package com.google.doclava; |
| 18 | |
| 19 | import com.google.clearsilver.jsilver.data.Data; |
| 20 | import com.google.doclava.apicheck.ApiCheck; |
| 21 | import com.google.doclava.apicheck.ApiInfo; |
| 22 | import com.google.doclava.apicheck.ApiParseException; |
Jesse Wilson | 7ab64e6 | 2011-09-28 17:02:22 -0400 | [diff] [blame] | 23 | import java.io.PrintWriter; |
| 24 | import java.io.StringWriter; |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 25 | import java.util.ArrayList; |
Andrew Sapperstein | d6eaacb | 2011-05-20 13:14:56 -0700 | [diff] [blame] | 26 | import java.util.Collections; |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 27 | import java.util.LinkedHashMap; |
| 28 | import java.util.List; |
| 29 | import java.util.Map; |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 30 | |
| 31 | |
| 32 | /** |
Dirk Dougherty | 1b45d1d | 2010-08-17 17:25:36 -0700 | [diff] [blame] | 33 | * Applies version information to the Doclava class model from apicheck XML files. Sample usage: |
Hui Shu | 5118ffe | 2014-02-18 14:06:42 -0800 | [diff] [blame] | 34 | * |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 35 | * <pre> |
| 36 | * ClassInfo[] classInfos = ... |
| 37 | * |
| 38 | * SinceTagger sinceTagger = new SinceTagger() |
Dirk Dougherty | 41d8656 | 2010-08-20 15:21:11 -0700 | [diff] [blame] | 39 | * sinceTagger.addVersion("frameworks/base/api/1.xml", "product 1.0") |
| 40 | * sinceTagger.addVersion("frameworks/base/api/2.xml", "product 1.5") |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 41 | * sinceTagger.tagAll(...); |
| 42 | * </pre> |
| 43 | */ |
| 44 | public class SinceTagger { |
| 45 | |
| 46 | private final Map<String, String> xmlToName = new LinkedHashMap<String, String>(); |
| 47 | |
| 48 | /** |
| 49 | * Specifies the apicheck XML file and the API version it holds. Calls to this method should be |
| 50 | * called in order from oldest version to newest. |
| 51 | */ |
| 52 | public void addVersion(String file, String name) { |
| 53 | xmlToName.put(file, name); |
| 54 | } |
| 55 | |
| 56 | public void tagAll(ClassInfo[] classDocs) { |
| 57 | // read through the XML files in order, applying their since information |
| 58 | // to the Javadoc models |
| 59 | for (Map.Entry<String, String> versionSpec : xmlToName.entrySet()) { |
| 60 | String xmlFile = versionSpec.getKey(); |
| 61 | String versionName = versionSpec.getValue(); |
Hui Shu | 5118ffe | 2014-02-18 14:06:42 -0800 | [diff] [blame] | 62 | |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 63 | ApiInfo specApi; |
| 64 | try { |
| 65 | specApi = new ApiCheck().parseApi(xmlFile); |
| 66 | } catch (ApiParseException e) { |
Jesse Wilson | 7ab64e6 | 2011-09-28 17:02:22 -0400 | [diff] [blame] | 67 | StringWriter stackTraceWriter = new StringWriter(); |
| 68 | e.printStackTrace(new PrintWriter(stackTraceWriter)); |
| 69 | Errors.error(Errors.BROKEN_SINCE_FILE, null, "Failed to parse " + xmlFile |
| 70 | + " for " + versionName + " since data.\n" + stackTraceWriter.toString()); |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 71 | continue; |
| 72 | } |
| 73 | |
| 74 | applyVersionsFromSpec(versionName, specApi, classDocs); |
| 75 | } |
| 76 | |
| 77 | if (!xmlToName.isEmpty()) { |
| 78 | warnForMissingVersions(classDocs); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | public boolean hasVersions() { |
| 83 | return !xmlToName.isEmpty(); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Writes an index of the version names to {@code data}. |
| 88 | */ |
| 89 | public void writeVersionNames(Data data) { |
| 90 | int index = 1; |
| 91 | for (String version : xmlToName.values()) { |
| 92 | data.setValue("since." + index + ".name", version); |
| 93 | index++; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Applies the version information to {@code classDocs} where not already present. |
Hui Shu | 5118ffe | 2014-02-18 14:06:42 -0800 | [diff] [blame] | 99 | * |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 100 | * @param versionName the version name |
| 101 | * @param specApi the spec for this version. If a symbol is in this spec, it was present in the |
| 102 | * named version |
| 103 | * @param classDocs the doc model to update |
| 104 | */ |
| 105 | private void applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs) { |
| 106 | for (ClassInfo classDoc : classDocs) { |
| 107 | PackageInfo packageSpec |
| 108 | = specApi.getPackages().get(classDoc.containingPackage().name()); |
| 109 | |
| 110 | if (packageSpec == null) { |
| 111 | continue; |
| 112 | } |
| 113 | |
| 114 | ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name()); |
| 115 | |
| 116 | if (classSpec == null) { |
| 117 | continue; |
| 118 | } |
| 119 | |
| 120 | versionPackage(versionName, classDoc.containingPackage()); |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 121 | versionClass(versionName, classSpec, classDoc); |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 122 | versionConstructors(versionName, classSpec, classDoc); |
| 123 | versionFields(versionName, classSpec, classDoc); |
| 124 | versionMethods(versionName, classSpec, classDoc); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Applies version information to {@code doc} where not already present. |
| 130 | */ |
| 131 | private void versionPackage(String versionName, PackageInfo doc) { |
| 132 | if (doc.getSince() == null) { |
| 133 | doc.setSince(versionName); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Applies version information to {@code doc} where not already present. |
| 139 | */ |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 140 | private void versionClass(String versionName, ClassInfo spec, ClassInfo doc) { |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 141 | if (doc.getSince() == null) { |
| 142 | doc.setSince(versionName); |
| 143 | } |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 144 | |
| 145 | // Set deprecated version |
| 146 | if (doc.isDeprecated() && doc.getDeprecatedSince() == null) { |
| 147 | if (spec.isDeprecated()) { |
| 148 | doc.setDeprecatedSince(versionName); |
| 149 | } |
| 150 | } |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Applies version information from {@code spec} to {@code doc} where not already present. |
| 155 | */ |
| 156 | private void versionConstructors(String versionName, ClassInfo spec, ClassInfo doc) { |
| 157 | for (MethodInfo constructor : doc.constructors()) { |
| 158 | if (constructor.getSince() == null |
| 159 | && spec.hasConstructor(constructor)) { |
| 160 | constructor.setSince(versionName); |
| 161 | } |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 162 | |
| 163 | // Set deprecated version |
| 164 | if (constructor.isDeprecated() && constructor.getDeprecatedSince() == null) { |
| 165 | // Find matching field from API spec |
| 166 | if (spec.allConstructorsMap().containsKey(constructor.getHashableName())) { |
| 167 | MethodInfo specConstructor = spec.allConstructorsMap().get(constructor.getHashableName()); |
| 168 | if (specConstructor.isDeprecated()) { |
| 169 | constructor.setDeprecatedSince(versionName); |
| 170 | } |
| 171 | } |
| 172 | } |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 173 | } |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Applies version information from {@code spec} to {@code doc} where not already present. |
| 178 | */ |
| 179 | private void versionFields(String versionName, ClassInfo spec, ClassInfo doc) { |
| 180 | for (FieldInfo field : doc.fields()) { |
Scott Main | 1890480 | 2013-08-07 13:53:14 -0700 | [diff] [blame] | 181 | if (field.getSince() == null && (spec.allFields().containsKey(field.name()) || |
| 182 | spec.allEnums().containsKey(field.name()))) { |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 183 | field.setSince(versionName); |
| 184 | } |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 185 | |
| 186 | // Set deprecated version |
| 187 | if (field.isDeprecated() && field.getDeprecatedSince() == null) { |
| 188 | // Find matching field from API spec |
| 189 | if (spec.allFields().containsKey(field.name())) { |
| 190 | FieldInfo specField = spec.allFields().get(field.name()); |
| 191 | if (specField.isDeprecated()) { |
| 192 | field.setDeprecatedSince(versionName); |
| 193 | } |
| 194 | } |
| 195 | } |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 196 | } |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Applies version information from {@code spec} to {@code doc} where not already present. |
| 201 | */ |
| 202 | private void versionMethods(String versionName, ClassInfo spec, ClassInfo doc) { |
| 203 | for (MethodInfo method : doc.methods()) { |
Scott Main | 40ad147 | 2012-10-24 18:19:17 -0700 | [diff] [blame] | 204 | |
| 205 | // Set deprecated version |
| 206 | if (method.isDeprecated() && method.getDeprecatedSince() == null) { |
| 207 | // Find matching method from API spec |
| 208 | if (spec.allMethods().containsKey(method.getHashableName())) { |
| 209 | MethodInfo specMethod = spec.allMethods().get(method.getHashableName()); |
| 210 | if (specMethod.isDeprecated()) { |
| 211 | method.setDeprecatedSince(versionName); |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 216 | if (method.getSince() != null) { |
| 217 | continue; |
| 218 | } |
| 219 | |
| 220 | for (ClassInfo superclass : spec.hierarchy()) { |
| 221 | if (superclass.allMethods().containsKey(method.getHashableName())) { |
| 222 | method.setSince(versionName); |
| 223 | break; |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Warns if any symbols are missing version information. When configured properly, this will yield |
| 231 | * zero warnings because {@code apicheck} guarantees that all symbols are present in the most |
| 232 | * recent API. |
| 233 | */ |
| 234 | private void warnForMissingVersions(ClassInfo[] classDocs) { |
| 235 | for (ClassInfo claz : classDocs) { |
| 236 | if (!checkLevelRecursive(claz)) { |
| 237 | continue; |
| 238 | } |
| 239 | |
| 240 | if (claz.getSince() == null) { |
| 241 | Errors.error(Errors.NO_SINCE_DATA, claz.position(), "XML missing class " |
| 242 | + claz.qualifiedName()); |
| 243 | } |
| 244 | |
| 245 | for (FieldInfo field : missingVersions(claz.fields())) { |
| 246 | Errors.error(Errors.NO_SINCE_DATA, field.position(), "XML missing field " |
| 247 | + claz.qualifiedName() + "#" + field.name()); |
| 248 | } |
| 249 | |
| 250 | for (MethodInfo constructor : missingVersions(claz.constructors())) { |
| 251 | Errors.error(Errors.NO_SINCE_DATA, constructor.position(), "XML missing constructor " |
| 252 | + claz.qualifiedName() + "#" + constructor.getHashableName()); |
| 253 | } |
| 254 | |
| 255 | for (MethodInfo method : missingVersions(claz.methods())) { |
| 256 | Errors.error(Errors.NO_SINCE_DATA, method.position(), "XML missing method " |
| 257 | + claz.qualifiedName() + "#" + method.getHashableName()); |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Returns the DocInfos in {@code all} that are documented but do not have since tags. |
| 264 | */ |
Andrew Sapperstein | d6eaacb | 2011-05-20 13:14:56 -0700 | [diff] [blame] | 265 | private <T extends MemberInfo> Iterable<T> missingVersions(ArrayList<T> all) { |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 266 | List<T> result = Collections.emptyList(); |
| 267 | for (T t : all) { |
| 268 | // if this member has version info or isn't documented, skip it |
Hui Shu | 5118ffe | 2014-02-18 14:06:42 -0800 | [diff] [blame] | 269 | if (t.getSince() != null || t.isHiddenOrRemoved() || |
| 270 | !checkLevelRecursive(t.realContainingClass())) { |
Ben Dodson | 920dbbb | 2010-08-04 15:21:06 -0700 | [diff] [blame] | 271 | continue; |
| 272 | } |
| 273 | |
| 274 | if (result.isEmpty()) { |
| 275 | result = new ArrayList<T>(); // lazily construct a mutable list |
| 276 | } |
| 277 | result.add(t); |
| 278 | } |
| 279 | return result; |
| 280 | } |
| 281 | |
| 282 | /** |
| 283 | * Returns true if {@code claz} and all containing classes are documented. The result may be used |
| 284 | * to filter out members that exist in the API data structure but aren't a part of the API. |
| 285 | */ |
| 286 | private boolean checkLevelRecursive(ClassInfo claz) { |
| 287 | for (ClassInfo c = claz; c != null; c = c.containingClass()) { |
| 288 | if (!c.checkLevel()) { |
| 289 | return false; |
| 290 | } |
| 291 | } |
| 292 | return true; |
| 293 | } |
| 294 | } |