• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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