blob: d9e8a01fc31c9e004b8b0036d6effe2c94bcbdc8 [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.apicheck;
18
Ben Dodson920dbbb2010-08-04 15:21:06 -070019import java.io.FileInputStream;
Joe Onorato04099252011-03-09 13:34:18 -080020import java.io.FileNotFoundException;
Ben Dodson920dbbb2010-08-04 15:21:06 -070021import java.io.IOException;
22import java.io.InputStream;
Joe Onorato04099252011-03-09 13:34:18 -080023import java.io.PrintStream;
Ben Dodson920dbbb2010-08-04 15:21:06 -070024import java.net.URL;
25import java.util.ArrayList;
Guang Zhuf9b7c1b2015-04-24 16:45:42 -070026import java.util.List;
C. Sean Young91c627c2015-05-11 15:36:03 -050027import java.util.HashSet;
Ben Dodson920dbbb2010-08-04 15:21:06 -070028import java.util.Set;
29import java.util.Stack;
30
Joe Onorato0aff7e02011-02-28 16:42:39 -080031import com.google.doclava.Errors;
Guang Zhuf9b7c1b2015-04-24 16:45:42 -070032import com.google.doclava.PackageInfo;
Joe Onorato0aff7e02011-02-28 16:42:39 -080033import com.google.doclava.Errors.ErrorMessage;
Joe Onorato04099252011-03-09 13:34:18 -080034import com.google.doclava.Stubs;
Joe Onorato0aff7e02011-02-28 16:42:39 -080035
Ben Dodson920dbbb2010-08-04 15:21:06 -070036public class ApiCheck {
37 // parse out and consume the -whatever command line flags
38 private static ArrayList<String[]> parseFlags(ArrayList<String> allArgs) {
39 ArrayList<String[]> ret = new ArrayList<String[]>();
40
41 int i;
42 for (i = 0; i < allArgs.size(); i++) {
43 // flags with one value attached
44 String flag = allArgs.get(i);
C. Sean Young91c627c2015-05-11 15:36:03 -050045 if (flag.equals("-error") || flag.equals("-warning") || flag.equals("-hide")
46 || flag.equals("-ignoreClass") || flag.equals("-ignorePackage")) {
Ben Dodson920dbbb2010-08-04 15:21:06 -070047 String[] arg = new String[2];
48 arg[0] = flag;
49 arg[1] = allArgs.get(++i);
50 ret.add(arg);
51 } else {
52 // we've consumed all of the -whatever args, so we're done
53 break;
54 }
55 }
56
57 // i now points to the first non-flag arg; strip what came before
58 for (; i > 0; i--) {
59 allArgs.remove(0);
60 }
61 return ret;
62 }
63
64 public static void main(String[] originalArgs) {
Joe Onorato04099252011-03-09 13:34:18 -080065 if (originalArgs.length == 3 && "-convert".equals(originalArgs[0])) {
66 System.exit(convertToApi(originalArgs[1], originalArgs[2]));
67 } else if (originalArgs.length == 3 && "-convert2xml".equals(originalArgs[0])) {
68 System.exit(convertToXml(originalArgs[1], originalArgs[2]));
Guang Zhuf9b7c1b2015-04-24 16:45:42 -070069 } else if (originalArgs.length == 4 && "-new_api".equals(originalArgs[0])) {
70 // command syntax: -new_api oldapi.txt newapi.txt diff.xml
C. Sean Youngc47ef092015-05-20 12:50:00 -050071 // TODO: Support reading in other options for new_api, such as ignored classes/packages.
Guang Zhuf9b7c1b2015-04-24 16:45:42 -070072 System.exit(newApi(originalArgs[1], originalArgs[2], originalArgs[3]));
Joe Onorato04099252011-03-09 13:34:18 -080073 } else {
74 ApiCheck acheck = new ApiCheck();
75 Report report = acheck.checkApi(originalArgs);
76
77 Errors.printErrors(report.errors());
78 System.exit(report.code);
79 }
Ben Dodson920dbbb2010-08-04 15:21:06 -070080 }
81
82 /**
83 * Compares two api xml files for consistency.
84 */
85 public Report checkApi(String[] originalArgs) {
86 // translate to an ArrayList<String> for munging
87 ArrayList<String> args = new ArrayList<String>(originalArgs.length);
88 for (String a : originalArgs) {
89 args.add(a);
90 }
91
C. Sean Young91c627c2015-05-11 15:36:03 -050092 // Not having having any classes or packages ignored is the common case.
93 // Avoid a hashCode call in a common loop by not passing in a HashSet in this case.
94 Set<String> ignoredPackages = null;
95 Set<String> ignoredClasses = null;
96
Ben Dodson920dbbb2010-08-04 15:21:06 -070097 ArrayList<String[]> flags = ApiCheck.parseFlags(args);
98 for (String[] a : flags) {
99 if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
100 try {
101 int level = -1;
102 if (a[0].equals("-error")) {
103 level = Errors.ERROR;
104 } else if (a[0].equals("-warning")) {
105 level = Errors.WARNING;
106 } else if (a[0].equals("-hide")) {
107 level = Errors.HIDDEN;
108 }
109 Errors.setErrorLevel(Integer.parseInt(a[1]), level);
110 } catch (NumberFormatException e) {
111 System.err.println("Bad argument: " + a[0] + " " + a[1]);
112 return new Report(2, Errors.getErrors());
113 }
C. Sean Young91c627c2015-05-11 15:36:03 -0500114 } else if (a[0].equals("-ignoreClass")) {
115 if (ignoredClasses == null) {
116 ignoredClasses = new HashSet<String>();
117 }
118 ignoredClasses.add(a[1]);
119 } else if (a[0].equals("-ignorePackage")) {
120 if (ignoredPackages == null) {
121 ignoredPackages = new HashSet<String>();
122 }
123 ignoredPackages.add(a[1]);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700124 }
125 }
126
127 ApiInfo oldApi;
128 ApiInfo newApi;
Hui Shu5118ffe2014-02-18 14:06:42 -0800129 ApiInfo oldRemovedApi;
130 ApiInfo newRemovedApi;
131
132 // commandline options look like:
133 // [other optoins] old_api.txt new_api.txt old_removed_api.txt new_removed_api.txt
Ben Dodson920dbbb2010-08-04 15:21:06 -0700134 try {
135 oldApi = parseApi(args.get(0));
136 newApi = parseApi(args.get(1));
Hui Shu5118ffe2014-02-18 14:06:42 -0800137 oldRemovedApi = parseApi(args.get(2));
138 newRemovedApi = parseApi(args.get(3));
Ben Dodson920dbbb2010-08-04 15:21:06 -0700139 } catch (ApiParseException e) {
140 e.printStackTrace();
141 System.err.println("Error parsing API");
142 return new Report(1, Errors.getErrors());
143 }
144
145 // only run the consistency check if we haven't had XML parse errors
146 if (!Errors.hadError) {
C. Sean Youngc47ef092015-05-20 12:50:00 -0500147 oldApi.isConsistent(newApi, null, ignoredPackages, ignoredClasses);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700148 }
149
Hui Shu5118ffe2014-02-18 14:06:42 -0800150 if (!Errors.hadError) {
C. Sean Youngc47ef092015-05-20 12:50:00 -0500151 oldRemovedApi.isConsistent(newRemovedApi, null, ignoredPackages, ignoredClasses);
Hui Shu5118ffe2014-02-18 14:06:42 -0800152 }
153
Ben Dodson920dbbb2010-08-04 15:21:06 -0700154 return new Report(Errors.hadError ? 1 : 0, Errors.getErrors());
155 }
156
Joe Onorato04099252011-03-09 13:34:18 -0800157 public static ApiInfo parseApi(String filename) throws ApiParseException {
158 InputStream stream = null;
Daniel Sandlerde0f0292012-03-27 14:01:45 -0400159 Throwable textParsingError = null;
James Cook1aeb0392015-06-08 11:51:28 -0700160 Throwable xmlParsingError = null;
Joe Onorato04099252011-03-09 13:34:18 -0800161 // try it as our format
Ben Dodson920dbbb2010-08-04 15:21:06 -0700162 try {
Joe Onorato04099252011-03-09 13:34:18 -0800163 stream = new FileInputStream(filename);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700164 } catch (IOException e) {
Joe Onorato04099252011-03-09 13:34:18 -0800165 throw new ApiParseException("Could not open file for parsing: " + filename, e);
166 }
167 try {
168 return ApiFile.parseApi(filename, stream);
James Cook1aeb0392015-06-08 11:51:28 -0700169 } catch (ApiParseException exception) {
170 textParsingError = exception;
Joe Onorato04099252011-03-09 13:34:18 -0800171 } finally {
172 try {
173 stream.close();
174 } catch (IOException ignored) {}
175 }
176 // try it as xml
177 try {
178 stream = new FileInputStream(filename);
179 } catch (IOException e) {
180 throw new ApiParseException("Could not open file for parsing: " + filename, e);
181 }
182 try {
183 return XmlApiFile.parseApi(stream);
James Cook1aeb0392015-06-08 11:51:28 -0700184 } catch (ApiParseException exception) {
185 xmlParsingError = exception;
Joe Onorato04099252011-03-09 13:34:18 -0800186 } finally {
187 try {
188 stream.close();
189 } catch (IOException ignored) {}
Ben Dodson920dbbb2010-08-04 15:21:06 -0700190 }
James Cook1aeb0392015-06-08 11:51:28 -0700191 // The file has failed to parse both as XML and as text. Build the string in this order as
192 // the message is easier to read with that error at the end.
193 throw new ApiParseException(filename +
194 " failed to parse as xml: \"" + xmlParsingError.getMessage() +
195 "\" and as text: \"" + textParsingError.getMessage() + "\"");
Ben Dodson920dbbb2010-08-04 15:21:06 -0700196 }
Joe Onorato04099252011-03-09 13:34:18 -0800197
198 public ApiInfo parseApi(URL url) throws ApiParseException {
199 InputStream stream = null;
200 // try it as our format
Ben Dodson920dbbb2010-08-04 15:21:06 -0700201 try {
Joe Onorato04099252011-03-09 13:34:18 -0800202 stream = url.openStream();
Ben Dodson920dbbb2010-08-04 15:21:06 -0700203 } catch (IOException e) {
Joe Onorato04099252011-03-09 13:34:18 -0800204 throw new ApiParseException("Could not open stream for parsing: " + url, e);
205 }
206 try {
207 return ApiFile.parseApi(url.toString(), stream);
208 } catch (ApiParseException ignored) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700209 } finally {
Joe Onorato04099252011-03-09 13:34:18 -0800210 try {
211 stream.close();
212 } catch (IOException ignored) {}
213 }
214 // try it as xml
215 try {
216 stream = url.openStream();
217 } catch (IOException e) {
218 throw new ApiParseException("Could not open stream for parsing: " + url, e);
219 }
220 try {
221 return XmlApiFile.parseApi(stream);
222 } finally {
223 try {
224 stream.close();
225 } catch (IOException ignored) {}
Ben Dodson920dbbb2010-08-04 15:21:06 -0700226 }
227 }
Joe Onorato04099252011-03-09 13:34:18 -0800228
Ben Dodson920dbbb2010-08-04 15:21:06 -0700229 public class Report {
230 private int code;
231 private Set<ErrorMessage> errors;
232
233 private Report(int code, Set<ErrorMessage> errors) {
234 this.code = code;
235 this.errors = errors;
236 }
237
238 public int code() {
239 return code;
240 }
241
242 public Set<ErrorMessage> errors() {
243 return errors;
244 }
245 }
Joe Onorato04099252011-03-09 13:34:18 -0800246
247 static int convertToApi(String src, String dst) {
248 ApiInfo api;
249 try {
250 api = parseApi(src);
251 } catch (ApiParseException e) {
252 e.printStackTrace();
253 System.err.println("Error parsing API: " + src);
254 return 1;
255 }
256
257 PrintStream apiWriter = null;
258 try {
259 apiWriter = new PrintStream(dst);
260 } catch (FileNotFoundException ex) {
261 System.err.println("can't open file: " + dst);
262 }
263
264 Stubs.writeApi(apiWriter, api.getPackages().values());
265
266 return 0;
267 }
268
269 static int convertToXml(String src, String dst) {
270 ApiInfo api;
271 try {
272 api = parseApi(src);
273 } catch (ApiParseException e) {
274 e.printStackTrace();
275 System.err.println("Error parsing API: " + src);
276 return 1;
277 }
278
279 PrintStream apiWriter = null;
280 try {
281 apiWriter = new PrintStream(dst);
282 } catch (FileNotFoundException ex) {
283 System.err.println("can't open file: " + dst);
284 }
285
286 Stubs.writeXml(apiWriter, api.getPackages().values());
287
288 return 0;
289 }
290
Guang Zhuf9b7c1b2015-04-24 16:45:42 -0700291 /**
292 * Generates a "diff": where new API is trimmed down by removing existing methods found in old API
293 * @param origApiPath path to old API text file
294 * @param newApiPath path to new API text file
295 * @param outputPath output XML path for the generated diff
296 * @return
297 */
298 static int newApi(String origApiPath, String newApiPath, String outputPath) {
299 ApiInfo origApi, newApi;
300 try {
301 origApi = parseApi(origApiPath);
302 } catch (ApiParseException e) {
303 e.printStackTrace();
304 System.err.println("Error parsing API: " + origApiPath);
305 return 1;
306 }
307 try {
308 newApi = parseApi(newApiPath);
309 } catch (ApiParseException e) {
310 e.printStackTrace();
311 System.err.println("Error parsing API: " + newApiPath);
312 return 1;
313 }
314 List<PackageInfo> pkgInfoDiff = new ArrayList<>();
315 if (!origApi.isConsistent(newApi, pkgInfoDiff)) {
316 PrintStream apiWriter = null;
317 try {
318 apiWriter = new PrintStream(outputPath);
319 } catch (FileNotFoundException ex) {
320 System.err.println("can't open file: " + outputPath);
321 }
322 Stubs.writeXml(apiWriter, pkgInfoDiff);
323 } else {
324 System.err.println("No API change detected, not generating diff.");
325 }
326 return 0;
327 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700328}