blob: b8ad418ce5c2547b4b0be6ed9ba5d8a4fade8a30 [file] [log] [blame]
Ben Dodson920dbbb2010-08-04 15:21:06 -07001/*
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;
Jesse Wilson7ab64e62011-09-28 17:02:22 -040023import java.io.PrintWriter;
24import java.io.StringWriter;
Ben Dodson920dbbb2010-08-04 15:21:06 -070025import java.util.ArrayList;
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -070026import java.util.Collections;
Ben Dodson920dbbb2010-08-04 15:21:06 -070027import java.util.LinkedHashMap;
28import java.util.List;
29import java.util.Map;
Ben Dodson920dbbb2010-08-04 15:21:06 -070030
31
32/**
Dirk Dougherty1b45d1d2010-08-17 17:25:36 -070033 * Applies version information to the Doclava class model from apicheck XML files. Sample usage:
Hui Shu5118ffe2014-02-18 14:06:42 -080034 *
Ben Dodson920dbbb2010-08-04 15:21:06 -070035 * <pre>
36 * ClassInfo[] classInfos = ...
37 *
38 * SinceTagger sinceTagger = new SinceTagger()
Dirk Dougherty41d86562010-08-20 15:21:11 -070039 * sinceTagger.addVersion("frameworks/base/api/1.xml", "product 1.0")
40 * sinceTagger.addVersion("frameworks/base/api/2.xml", "product 1.5")
Ben Dodson920dbbb2010-08-04 15:21:06 -070041 * 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();
Hui Shu5118ffe2014-02-18 14:06:42 -080062
Ben Dodson920dbbb2010-08-04 15:21:06 -070063 ApiInfo specApi;
64 try {
65 specApi = new ApiCheck().parseApi(xmlFile);
66 } catch (ApiParseException e) {
Jesse Wilson7ab64e62011-09-28 17:02:22 -040067 StringWriter stackTraceWriter = new StringWriter();
68 e.printStackTrace(new PrintWriter(stackTraceWriter));
69 Errors.error(Errors.BROKEN_SINCE_FILE, null, "Failed to parse " + xmlFile
70 + " for " + versionName + " since data.\n" + stackTraceWriter.toString());
Ben Dodson920dbbb2010-08-04 15:21:06 -070071 continue;
72 }
73
74 applyVersionsFromSpec(versionName, specApi, classDocs);
75 }
76
77 if (!xmlToName.isEmpty()) {
78 warnForMissingVersions(classDocs);
79 }
80 }
81
82 public boolean hasVersions() {
83 return !xmlToName.isEmpty();
84 }
85
86 /**
87 * Writes an index of the version names to {@code data}.
88 */
89 public void writeVersionNames(Data data) {
90 int index = 1;
91 for (String version : xmlToName.values()) {
92 data.setValue("since." + index + ".name", version);
93 index++;
94 }
95 }
96
97 /**
98 * Applies the version information to {@code classDocs} where not already present.
Hui Shu5118ffe2014-02-18 14:06:42 -080099 *
Ben Dodson920dbbb2010-08-04 15:21:06 -0700100 * @param versionName the version name
101 * @param specApi the spec for this version. If a symbol is in this spec, it was present in the
102 * named version
103 * @param classDocs the doc model to update
104 */
105 private void applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs) {
106 for (ClassInfo classDoc : classDocs) {
107 PackageInfo packageSpec
108 = specApi.getPackages().get(classDoc.containingPackage().name());
109
110 if (packageSpec == null) {
111 continue;
112 }
113
114 ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
115
116 if (classSpec == null) {
117 continue;
118 }
119
120 versionPackage(versionName, classDoc.containingPackage());
Scott Main40ad1472012-10-24 18:19:17 -0700121 versionClass(versionName, classSpec, classDoc);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700122 versionConstructors(versionName, classSpec, classDoc);
123 versionFields(versionName, classSpec, classDoc);
124 versionMethods(versionName, classSpec, classDoc);
125 }
126 }
127
128 /**
129 * Applies version information to {@code doc} where not already present.
130 */
131 private void versionPackage(String versionName, PackageInfo doc) {
132 if (doc.getSince() == null) {
133 doc.setSince(versionName);
134 }
135 }
136
137 /**
138 * Applies version information to {@code doc} where not already present.
139 */
Scott Main40ad1472012-10-24 18:19:17 -0700140 private void versionClass(String versionName, ClassInfo spec, ClassInfo doc) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700141 if (doc.getSince() == null) {
142 doc.setSince(versionName);
143 }
Scott Main40ad1472012-10-24 18:19:17 -0700144
145 // Set deprecated version
146 if (doc.isDeprecated() && doc.getDeprecatedSince() == null) {
147 if (spec.isDeprecated()) {
148 doc.setDeprecatedSince(versionName);
149 }
150 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700151 }
152
153 /**
154 * Applies version information from {@code spec} to {@code doc} where not already present.
155 */
156 private void versionConstructors(String versionName, ClassInfo spec, ClassInfo doc) {
157 for (MethodInfo constructor : doc.constructors()) {
158 if (constructor.getSince() == null
159 && spec.hasConstructor(constructor)) {
160 constructor.setSince(versionName);
161 }
Scott Main40ad1472012-10-24 18:19:17 -0700162
163 // Set deprecated version
164 if (constructor.isDeprecated() && constructor.getDeprecatedSince() == null) {
165 // Find matching field from API spec
166 if (spec.allConstructorsMap().containsKey(constructor.getHashableName())) {
167 MethodInfo specConstructor = spec.allConstructorsMap().get(constructor.getHashableName());
168 if (specConstructor.isDeprecated()) {
169 constructor.setDeprecatedSince(versionName);
170 }
171 }
172 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700173 }
174 }
175
176 /**
177 * Applies version information from {@code spec} to {@code doc} where not already present.
178 */
179 private void versionFields(String versionName, ClassInfo spec, ClassInfo doc) {
180 for (FieldInfo field : doc.fields()) {
Scott Main18904802013-08-07 13:53:14 -0700181 if (field.getSince() == null && (spec.allFields().containsKey(field.name()) ||
182 spec.allEnums().containsKey(field.name()))) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700183 field.setSince(versionName);
184 }
Scott Main40ad1472012-10-24 18:19:17 -0700185
186 // Set deprecated version
187 if (field.isDeprecated() && field.getDeprecatedSince() == null) {
188 // Find matching field from API spec
189 if (spec.allFields().containsKey(field.name())) {
190 FieldInfo specField = spec.allFields().get(field.name());
191 if (specField.isDeprecated()) {
192 field.setDeprecatedSince(versionName);
193 }
194 }
195 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700196 }
197 }
198
199 /**
200 * Applies version information from {@code spec} to {@code doc} where not already present.
201 */
202 private void versionMethods(String versionName, ClassInfo spec, ClassInfo doc) {
203 for (MethodInfo method : doc.methods()) {
Scott Main40ad1472012-10-24 18:19:17 -0700204
205 // Set deprecated version
206 if (method.isDeprecated() && method.getDeprecatedSince() == null) {
207 // Find matching method from API spec
208 if (spec.allMethods().containsKey(method.getHashableName())) {
209 MethodInfo specMethod = spec.allMethods().get(method.getHashableName());
210 if (specMethod.isDeprecated()) {
211 method.setDeprecatedSince(versionName);
212 }
213 }
214 }
215
Ben Dodson920dbbb2010-08-04 15:21:06 -0700216 if (method.getSince() != null) {
217 continue;
218 }
219
220 for (ClassInfo superclass : spec.hierarchy()) {
221 if (superclass.allMethods().containsKey(method.getHashableName())) {
222 method.setSince(versionName);
223 break;
224 }
225 }
226 }
227 }
228
229 /**
230 * Warns if any symbols are missing version information. When configured properly, this will yield
231 * zero warnings because {@code apicheck} guarantees that all symbols are present in the most
232 * recent API.
233 */
234 private void warnForMissingVersions(ClassInfo[] classDocs) {
235 for (ClassInfo claz : classDocs) {
236 if (!checkLevelRecursive(claz)) {
237 continue;
238 }
239
240 if (claz.getSince() == null) {
241 Errors.error(Errors.NO_SINCE_DATA, claz.position(), "XML missing class "
242 + claz.qualifiedName());
243 }
244
245 for (FieldInfo field : missingVersions(claz.fields())) {
246 Errors.error(Errors.NO_SINCE_DATA, field.position(), "XML missing field "
247 + claz.qualifiedName() + "#" + field.name());
248 }
249
250 for (MethodInfo constructor : missingVersions(claz.constructors())) {
251 Errors.error(Errors.NO_SINCE_DATA, constructor.position(), "XML missing constructor "
252 + claz.qualifiedName() + "#" + constructor.getHashableName());
253 }
254
255 for (MethodInfo method : missingVersions(claz.methods())) {
256 Errors.error(Errors.NO_SINCE_DATA, method.position(), "XML missing method "
257 + claz.qualifiedName() + "#" + method.getHashableName());
258 }
259 }
260 }
261
262 /**
263 * Returns the DocInfos in {@code all} that are documented but do not have since tags.
264 */
Andrew Sappersteind6eaacb2011-05-20 13:14:56 -0700265 private <T extends MemberInfo> Iterable<T> missingVersions(ArrayList<T> all) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700266 List<T> result = Collections.emptyList();
267 for (T t : all) {
268 // if this member has version info or isn't documented, skip it
Hui Shu5118ffe2014-02-18 14:06:42 -0800269 if (t.getSince() != null || t.isHiddenOrRemoved() ||
270 !checkLevelRecursive(t.realContainingClass())) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700271 continue;
272 }
273
274 if (result.isEmpty()) {
275 result = new ArrayList<T>(); // lazily construct a mutable list
276 }
277 result.add(t);
278 }
279 return result;
280 }
281
282 /**
283 * Returns true if {@code claz} and all containing classes are documented. The result may be used
284 * to filter out members that exist in the API data structure but aren't a part of the API.
285 */
286 private boolean checkLevelRecursive(ClassInfo claz) {
287 for (ClassInfo c = claz; c != null; c = c.containingClass()) {
288 if (!c.checkLevel()) {
289 return false;
290 }
291 }
292 return true;
293 }
294}