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