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