1 // Copyright 2009 Google Inc. All Rights Reserved. 2 3 import com.android.apicheck.*; 4 5 import java.util.*; 6 7 import org.clearsilver.HDF; 8 9 /** 10 * Applies version information to the DroidDoc class model from apicheck XML 11 * files. Sample usage: 12 * <pre> 13 * ClassInfo[] classInfos = ... 14 * 15 * SinceTagger sinceTagger = new SinceTagger() 16 * sinceTagger.addVersion("frameworks/base/api/1.xml", "Android 1.0") 17 * sinceTagger.addVersion("frameworks/base/api/2.xml", "Android 1.5") 18 * sinceTagger.tagAll(...); 19 * </pre> 20 */ 21 public class SinceTagger { 22 23 private final Map<String, String> xmlToName 24 = new LinkedHashMap<String, String>(); 25 26 /** 27 * Specifies the apicheck XML file and the API version it holds. Calls to 28 * this method should be called in order from oldest version to newest. 29 */ addVersion(String file, String name)30 public void addVersion(String file, String name) { 31 xmlToName.put(file, name); 32 } 33 tagAll(ClassInfo[] classDocs)34 public void tagAll(ClassInfo[] classDocs) { 35 // read through the XML files in order, applying their since information 36 // to the Javadoc models 37 for (Map.Entry<String, String> versionSpec : xmlToName.entrySet()) { 38 String xmlFile = versionSpec.getKey(); 39 String versionName = versionSpec.getValue(); 40 ApiInfo specApi = new ApiCheck().parseApi(xmlFile); 41 42 applyVersionsFromSpec(versionName, specApi, classDocs); 43 } 44 45 if (!xmlToName.isEmpty()) { 46 warnForMissingVersions(classDocs); 47 } 48 } 49 50 /** 51 * Writes an index of the version names to {@code data}. 52 */ writeVersionNames(HDF data)53 public void writeVersionNames(HDF data) { 54 int index = 1; 55 for (String version : xmlToName.values()) { 56 data.setValue("since." + index + ".name", version); 57 index++; 58 } 59 } 60 61 /** 62 * Applies the version information to {@code classDocs} where not already 63 * present. 64 * 65 * @param versionName the version name 66 * @param specApi the spec for this version. If a symbol is in this spec, it 67 * was present in the named version 68 * @param classDocs the doc model to update 69 */ applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs)70 private void applyVersionsFromSpec(String versionName, 71 ApiInfo specApi, ClassInfo[] classDocs) { 72 for (ClassInfo classDoc : classDocs) { 73 com.android.apicheck.PackageInfo packageSpec 74 = specApi.getPackages().get(classDoc.containingPackage().name()); 75 76 if (packageSpec == null) { 77 continue; 78 } 79 80 com.android.apicheck.ClassInfo classSpec 81 = packageSpec.allClasses().get(classDoc.name()); 82 83 if (classSpec == null) { 84 continue; 85 } 86 87 versionPackage(versionName, classDoc.containingPackage()); 88 versionClass(versionName, classDoc); 89 versionConstructors(versionName, classSpec, classDoc); 90 versionFields(versionName, classSpec, classDoc); 91 versionMethods(versionName, classSpec, classDoc); 92 } 93 } 94 95 /** 96 * Applies version information to {@code doc} where not already present. 97 */ versionPackage(String versionName, PackageInfo doc)98 private void versionPackage(String versionName, PackageInfo doc) { 99 if (doc.getSince() == null) { 100 doc.setSince(versionName); 101 } 102 } 103 104 /** 105 * Applies version information to {@code doc} where not already present. 106 */ versionClass(String versionName, ClassInfo doc)107 private void versionClass(String versionName, ClassInfo doc) { 108 if (doc.getSince() == null) { 109 doc.setSince(versionName); 110 } 111 } 112 113 /** 114 * Applies version information from {@code spec} to {@code doc} where not 115 * already present. 116 */ versionConstructors(String versionName, com.android.apicheck.ClassInfo spec, ClassInfo doc)117 private void versionConstructors(String versionName, 118 com.android.apicheck.ClassInfo spec, ClassInfo doc) { 119 for (MethodInfo constructor : doc.constructors()) { 120 if (constructor.getSince() == null 121 && spec.allConstructors().containsKey(constructor.getHashableName())) { 122 constructor.setSince(versionName); 123 } 124 } 125 } 126 127 /** 128 * Applies version information from {@code spec} to {@code doc} where not 129 * already present. 130 */ versionFields(String versionName, com.android.apicheck.ClassInfo spec, ClassInfo doc)131 private void versionFields(String versionName, 132 com.android.apicheck.ClassInfo spec, ClassInfo doc) { 133 for (FieldInfo field : doc.fields()) { 134 if (field.getSince() == null 135 && spec.allFields().containsKey(field.name())) { 136 field.setSince(versionName); 137 } 138 } 139 } 140 141 /** 142 * Applies version information from {@code spec} to {@code doc} where not 143 * already present. 144 */ versionMethods(String versionName, com.android.apicheck.ClassInfo spec, ClassInfo doc)145 private void versionMethods(String versionName, 146 com.android.apicheck.ClassInfo spec, ClassInfo doc) { 147 for (MethodInfo method : doc.methods()) { 148 if (method.getSince() != null) { 149 continue; 150 } 151 152 for (com.android.apicheck.ClassInfo superclass : spec.hierarchy()) { 153 if (superclass.allMethods().containsKey(method.getHashableName())) { 154 method.setSince(versionName); 155 break; 156 } 157 } 158 } 159 } 160 161 /** 162 * Warns if any symbols are missing version information. When configured 163 * properly, this will yield zero warnings because {@code apicheck} 164 * guarantees that all symbols are present in the most recent API. 165 */ warnForMissingVersions(ClassInfo[] classDocs)166 private void warnForMissingVersions(ClassInfo[] classDocs) { 167 for (ClassInfo claz : classDocs) { 168 if (claz.getSince() == null) { 169 Errors.error(Errors.NO_SINCE_DATA, claz.position(), 170 "XML missing class " + claz.qualifiedName()); 171 } 172 for (FieldInfo field : claz.fields()) { 173 if (field.getSince() == null) { 174 Errors.error(Errors.NO_SINCE_DATA, field.position(), 175 "XML missing field " 176 + claz.qualifiedName() + "#" + field .name()); 177 } 178 } 179 for (MethodInfo constructor : claz.constructors()) { 180 if (constructor.getSince() == null) { 181 Errors.error(Errors.NO_SINCE_DATA, constructor.position(), 182 "XML missing constructor " 183 + claz.qualifiedName() + "#" + constructor.getHashableName()); 184 } 185 } 186 for (MethodInfo method : claz.methods()) { 187 if (method.getSince() == null) { 188 Errors.error(Errors.NO_SINCE_DATA, method.position(), 189 "XML missing method " 190 + claz.qualifiedName() + "#" + method .getHashableName()); 191 } 192 } 193 } 194 } 195 } 196