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