SinceTagger.java revision 920dbbbaca6aa578f3b26d89e99d12754c26ed60
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 17package com.google.doclava; 18 19import com.google.clearsilver.jsilver.data.Data; 20import com.google.doclava.apicheck.ApiCheck; 21import com.google.doclava.apicheck.ApiInfo; 22import com.google.doclava.apicheck.ApiParseException; 23 24 25import java.util.ArrayList; 26import java.util.LinkedHashMap; 27import java.util.List; 28import java.util.Map; 29import java.util.Collections; 30 31 32/** 33 * Applies version information to the DroidDoc class model from apicheck XML files. Sample usage: 34 * 35 * <pre> 36 * ClassInfo[] classInfos = ... 37 * 38 * SinceTagger sinceTagger = new SinceTagger() 39 * sinceTagger.addVersion("frameworks/base/api/1.xml", "Android 1.0") 40 * sinceTagger.addVersion("frameworks/base/api/2.xml", "Android 1.5") 41 * sinceTagger.tagAll(...); 42 * </pre> 43 */ 44public 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(); 62 63 ApiInfo specApi; 64 try { 65 specApi = new ApiCheck().parseApi(xmlFile); 66 } catch (ApiParseException e) { 67 Errors.error(Errors.NO_SINCE_DATA, null, "Could not add since data for " + versionName); 68 continue; 69 } 70 71 applyVersionsFromSpec(versionName, specApi, classDocs); 72 } 73 74 if (!xmlToName.isEmpty()) { 75 warnForMissingVersions(classDocs); 76 } 77 } 78 79 public boolean hasVersions() { 80 return !xmlToName.isEmpty(); 81 } 82 83 /** 84 * Writes an index of the version names to {@code data}. 85 */ 86 public void writeVersionNames(Data data) { 87 int index = 1; 88 for (String version : xmlToName.values()) { 89 data.setValue("since." + index + ".name", version); 90 index++; 91 } 92 } 93 94 /** 95 * Applies the version information to {@code classDocs} where not already present. 96 * 97 * @param versionName the version name 98 * @param specApi the spec for this version. If a symbol is in this spec, it was present in the 99 * named version 100 * @param classDocs the doc model to update 101 */ 102 private void applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs) { 103 for (ClassInfo classDoc : classDocs) { 104 PackageInfo packageSpec 105 = specApi.getPackages().get(classDoc.containingPackage().name()); 106 107 if (packageSpec == null) { 108 continue; 109 } 110 111 ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name()); 112 113 if (classSpec == null) { 114 continue; 115 } 116 117 versionPackage(versionName, classDoc.containingPackage()); 118 versionClass(versionName, classDoc); 119 versionConstructors(versionName, classSpec, classDoc); 120 versionFields(versionName, classSpec, classDoc); 121 versionMethods(versionName, classSpec, classDoc); 122 } 123 } 124 125 /** 126 * Applies version information to {@code doc} where not already present. 127 */ 128 private void versionPackage(String versionName, PackageInfo doc) { 129 if (doc.getSince() == null) { 130 doc.setSince(versionName); 131 } 132 } 133 134 /** 135 * Applies version information to {@code doc} where not already present. 136 */ 137 private void versionClass(String versionName, ClassInfo doc) { 138 if (doc.getSince() == null) { 139 doc.setSince(versionName); 140 } 141 } 142 143 /** 144 * Applies version information from {@code spec} to {@code doc} where not already present. 145 */ 146 private void versionConstructors(String versionName, ClassInfo spec, ClassInfo doc) { 147 for (MethodInfo constructor : doc.constructors()) { 148 if (constructor.getSince() == null 149 && spec.hasConstructor(constructor)) { 150 constructor.setSince(versionName); 151 } 152 } 153 } 154 155 /** 156 * Applies version information from {@code spec} to {@code doc} where not already present. 157 */ 158 private void versionFields(String versionName, ClassInfo spec, ClassInfo doc) { 159 for (FieldInfo field : doc.fields()) { 160 if (field.getSince() == null && spec.allFields().containsKey(field.name())) { 161 field.setSince(versionName); 162 } 163 } 164 } 165 166 /** 167 * Applies version information from {@code spec} to {@code doc} where not already present. 168 */ 169 private void versionMethods(String versionName, ClassInfo spec, ClassInfo doc) { 170 for (MethodInfo method : doc.methods()) { 171 if (method.getSince() != null) { 172 continue; 173 } 174 175 for (ClassInfo superclass : spec.hierarchy()) { 176 if (superclass.allMethods().containsKey(method.getHashableName())) { 177 method.setSince(versionName); 178 break; 179 } 180 } 181 } 182 } 183 184 /** 185 * Warns if any symbols are missing version information. When configured properly, this will yield 186 * zero warnings because {@code apicheck} guarantees that all symbols are present in the most 187 * recent API. 188 */ 189 private void warnForMissingVersions(ClassInfo[] classDocs) { 190 for (ClassInfo claz : classDocs) { 191 if (!checkLevelRecursive(claz)) { 192 continue; 193 } 194 195 if (claz.getSince() == null) { 196 Errors.error(Errors.NO_SINCE_DATA, claz.position(), "XML missing class " 197 + claz.qualifiedName()); 198 } 199 200 for (FieldInfo field : missingVersions(claz.fields())) { 201 Errors.error(Errors.NO_SINCE_DATA, field.position(), "XML missing field " 202 + claz.qualifiedName() + "#" + field.name()); 203 } 204 205 for (MethodInfo constructor : missingVersions(claz.constructors())) { 206 Errors.error(Errors.NO_SINCE_DATA, constructor.position(), "XML missing constructor " 207 + claz.qualifiedName() + "#" + constructor.getHashableName()); 208 } 209 210 for (MethodInfo method : missingVersions(claz.methods())) { 211 Errors.error(Errors.NO_SINCE_DATA, method.position(), "XML missing method " 212 + claz.qualifiedName() + "#" + method.getHashableName()); 213 } 214 } 215 } 216 217 /** 218 * Returns the DocInfos in {@code all} that are documented but do not have since tags. 219 */ 220 private <T extends MemberInfo> Iterable<T> missingVersions(T[] all) { 221 List<T> result = Collections.emptyList(); 222 for (T t : all) { 223 // if this member has version info or isn't documented, skip it 224 if (t.getSince() != null || t.isHidden() || !checkLevelRecursive(t.realContainingClass())) { 225 continue; 226 } 227 228 if (result.isEmpty()) { 229 result = new ArrayList<T>(); // lazily construct a mutable list 230 } 231 result.add(t); 232 } 233 return result; 234 } 235 236 /** 237 * Returns true if {@code claz} and all containing classes are documented. The result may be used 238 * to filter out members that exist in the API data structure but aren't a part of the API. 239 */ 240 private boolean checkLevelRecursive(ClassInfo claz) { 241 for (ClassInfo c = claz; c != null; c = c.containingClass()) { 242 if (!c.checkLevel()) { 243 return false; 244 } 245 } 246 return true; 247 } 248} 249