blob: bd00bc7b588024bf5ea7b6d50fab3d2e79100000 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.tools.apichecker;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.dev.javac.Shared;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.ToolBase;
import org.apache.tools.ant.types.ZipScanner;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* {@link ApiCompatibilityChecker} Main class to check if the new API is
* compatible with the existing API.
*
* <p>
* To compute API diffs, follow these 2 steps:
* <ol>
* <li>for each of the two repositories, construct an {@link ApiContainer}
* <li>call getApiDiff on the {@code ApiDiffGenerator}
* </ol>
* </p>
*
* <p>
* An {@code ApiContainer} object is a list of {@link ApiPackage} objects.
* {@code ApiPackage} objects themselves are list of {@link ApiClass} objects.
* {@code ApiClass} objects contain list of {@code ApiConstructor},
* {@code ApiMethod}, and {@code JField} objects.
* </p>
*
* <p>
* Each {@code ApiDiffGenerator} object computes the list of intersecting and
* missing {@link ApiPackageDiffGenerator} objects. Each
* {@code ApiPackageDiffGenerator} object in turn computes the list of
* intersecting and missing {@link ApiClassDiffGenerator} objects. Each
* {@code ApiClassDiffGenerator} object in turn computes the list of
* intersecting and missing API members. The members are represented by
* {@link ApiConstructor} for constructors, {@link ApiMethod} for methods, and
* {@link ApiField} for fields.
* </p>
*
* <p>
* For each intersecting API member, a list of {@link ApiChange} objects is
* stored. Each ApiChange object encodes a specific {@code ApiChange} like
* adding the 'final' keyword to the API member.
*
*/
public class ApiCompatibilityChecker extends ToolBase {
// TODO(amitmanjhi): check gwt's dev/core/src files. Would need the ability to
// build TypeOracle from class files
// TODO(amitmanjhi): better handling of exceptions and exception-chaining.
static class FileResource extends Resource {
private final File file;
private final String path;
public FileResource(File file, String path) {
this.file = file;
this.path = path;
assert path.endsWith(".java");
assert file.getAbsolutePath().endsWith(path);
}
@Override
public long getLastModified() {
return file.lastModified();
}
@Override
public String getLocation() {
return file.getAbsolutePath();
}
@Override
public String getPath() {
return path;
}
@Override
public InputStream openContents() {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException("Unable to open file '" + file.getAbsolutePath() + "'", e);
}
}
@Override
public boolean wasRerooted() {
return false;
}
}
static class StaticResource extends Resource {
private final long lastModified;
private final String source;
private final String typeName;
public StaticResource(String typeName, String source, long lastModified) {
this.typeName = typeName;
this.source = source;
this.lastModified = lastModified;
}
@Override
public long getLastModified() {
return lastModified;
}
@Override
public String getLocation() {
return "/mock/" + getPath();
}
@Override
public String getPath() {
return Shared.toPath(typeName);
}
@Override
public InputStream openContents() {
return new ByteArrayInputStream(Util.getBytes(source));
}
@Override
public boolean wasRerooted() {
return false;
}
}
/**
* Class that specifies a set of
* {@link com.google.gwt.dev.javac.CompilationUnit CompilationUnit} read from
* jar files.
*/
private static class JarFileResources extends Resources {
private static final String MOCK_PREFIX = "/mock/";
private static final int MOCK_PREFIX_LENGTH = MOCK_PREFIX.length();
private final ZipScanner excludeScanner;
private final Set<String> includedPaths;
private final JarFile jarFiles[];
private Set<Resource> resources = null;
JarFileResources(JarFile[] jarFiles, Set<String> includedPaths, Set<String> excludedPaths,
TreeLogger logger) {
super(logger);
this.jarFiles = jarFiles;
this.includedPaths = includedPaths;
// initialize the ant scanner
excludeScanner = new ZipScanner();
List<String> list = new ArrayList<String>(excludedPaths);
excludeScanner.setIncludes(list.toArray(new String[list.size()]));
excludeScanner.addDefaultExcludes();
excludeScanner.setCaseSensitive(true);
excludeScanner.init();
}
@Override
public Set<Resource> getResources() throws IOException {
if (resources != null) {
return resources;
}
resources = new HashSet<Resource>();
for (JarFile jarFile : jarFiles) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String fileName = jarEntry.toString();
if (fileName.startsWith(MOCK_PREFIX)) {
fileName = fileName.substring(MOCK_PREFIX_LENGTH);
}
if (fileName.endsWith(".java") && isIncluded(fileName)) {
// add this compilation unit
String fileContent = getFileContentsFromJar(jarFile, jarEntry);
String packageName = extractPackageName(new StringReader(fileContent));
if (packageName == null) {
logger.log(TreeLogger.WARN, "Not adding file = " + fileName
+ ", because packageName = null", null);
} else {
if (isValidPackage(packageName, fileName)) {
// Add if package and fileNames are okay
long lastModified = jarEntry.getTime();
if (lastModified < 0) {
lastModified = System.currentTimeMillis();
}
resources.add(new StaticResource(packageName + "." + getClassName(fileName),
fileContent, lastModified));
logger.log(TreeLogger.DEBUG, "adding pkgName = " + packageName + ", file = "
+ fileName, null);
} else {
logger.log(TreeLogger.SPAM, " not adding file " + fileName, null);
}
}
}
}
}
return resources;
}
String getFileContentsFromJar(JarFile jarFile, JarEntry jarEntry) throws IOException {
StringBuffer fileContent = new StringBuffer();
InputStream is = jarFile.getInputStream(jarEntry);
BufferedInputStream bis = new BufferedInputStream(is);
int length = 500;
byte buf[] = new byte[length];
int count = 0;
while ((count = bis.read(buf, 0, length)) != -1) {
fileContent.append(new String(buf, 0, count));
buf = new byte[length];
}
bis.close();
is.close();
return fileContent.toString();
}
private String getClassName(String fileName) {
int index = fileName.lastIndexOf("/");
int endOffset = 0;
if (fileName.endsWith(".java")) {
endOffset = 5;
}
return fileName.substring(index + 1, fileName.length() - endOffset);
}
private boolean isIncluded(String fileName) {
if (excludeScanner.match(fileName)) {
logger.log(TreeLogger.SPAM, fileName + " is excluded");
return false;
}
for (String includedPath : includedPaths) {
if (fileName.startsWith(includedPath)) {
logger.log(TreeLogger.SPAM, fileName + " is not excluded, and is included by "
+ includedPath);
return true;
}
}
return false;
}
}
/**
* Abstract internal class that specifies a set of
* {@link com.google.gwt.dev.javac.CompilationUnit CompilationUnit}.
*/
private abstract static class Resources {
protected final TreeLogger logger;
Resources(TreeLogger logger) {
this.logger = logger;
}
public abstract Set<Resource> getResources() throws NotFoundException, IOException,
UnableToCompleteException;
// TODO (amitmanjhi): remove this code. use TypeOracle functionality
// instead.
protected String extractPackageName(Reader reader) throws IOException {
BufferedReader br = new BufferedReader(reader);
String str = null;
while ((str = br.readLine()) != null) {
if (str.indexOf("package") != 0) {
continue;
}
String parts[] = str.split("[\b\t\n\r ]+");
if ((parts.length == 2) && parts[0].equals("package")) {
return parts[1].substring(0, parts[1].length() - 1); // the ; char
}
}
return null;
}
protected File getFileFromName(String tag, String pathName) throws FileNotFoundException {
File file = new File(pathName);
if (!file.exists()) {
throw new FileNotFoundException(tag + "file " + pathName + " not found");
}
return file;
}
// TODO (amitmanjhi): remove this code. use TypeOracle functionality
// instead.
protected boolean isValidPackage(String packageName, String filePath) {
logger
.log(TreeLogger.SPAM, "packageName = " + packageName + ", filePath = " + filePath, null);
if (packageName == null) {
return false;
}
int lastSlashPosition = filePath.lastIndexOf("/");
if (lastSlashPosition == -1) {
return false;
}
String dirPath = filePath.substring(0, lastSlashPosition);
String packageNameAsPath = packageName.replace('.', '/');
logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath + ", dirPath = "
+ dirPath, null);
return dirPath.endsWith(packageNameAsPath);
}
}
/**
* Class that specifies a set of
* {@link com.google.gwt.dev.javac.CompilationUnit CompilationUnit} read from
* the file-system.
*/
private static class SourceFileResources extends Resources {
private final ZipScanner excludeScanner;
private final Set<File> includedPaths;
private Set<Resource> units = null;
SourceFileResources(String dirRoot, Set<String> includedPathsAsString,
Set<String> excludedPathsAsString, TreeLogger logger) throws FileNotFoundException,
IOException {
super(logger);
if (dirRoot == null) {
dirRoot = "";
}
includedPaths = new HashSet<File>();
for (String includedPath : includedPathsAsString) {
includedPaths.add(getFileFromName("source file: ", dirRoot + includedPath));
}
String fullExcludedPaths[] = new String[excludedPathsAsString.size()];
int count = 0;
String dirRootAbsolutePath = getFileFromName("dirRoot: ", dirRoot).getAbsolutePath();
if (dirRootAbsolutePath.startsWith("/") || dirRootAbsolutePath.startsWith("\\")) {
// Remove initial File.Separator to work with ant versions above and below 1.8.1.
dirRootAbsolutePath = dirRootAbsolutePath.substring(1);
}
for (String excludedPath : excludedPathsAsString) {
fullExcludedPaths[count++] = dirRootAbsolutePath + "/" + excludedPath;
}
// initialize the ant scanner
excludeScanner = new ZipScanner();
if (fullExcludedPaths.length > 0) {
excludeScanner.setIncludes(fullExcludedPaths);
}
excludeScanner.addDefaultExcludes();
excludeScanner.setCaseSensitive(true);
excludeScanner.init();
}
@Override
public Set<Resource> getResources() throws NotFoundException, IOException,
UnableToCompleteException {
if (units != null) {
return units;
}
units = new HashSet<Resource>();
for (File sourceFile : includedPaths) {
updateCompilationUnitsInPath(sourceFile);
}
return units;
}
private boolean isExcludedFile(String fileName) {
String pattern = "file:";
if (fileName.indexOf(pattern) == 0) {
fileName = fileName.substring(pattern.length());
}
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
// Remove initial File.Separator to work with ant versions above and below 1.8.1.
fileName = fileName.substring(1);
}
return excludeScanner.match(fileName);
}
private void updateCompilationUnitsInPath(File sourcePathEntry) throws NotFoundException,
IOException, UnableToCompleteException {
logger.log(TreeLogger.SPAM, "entering addCompilationUnitsInPath, file = " + sourcePathEntry,
null);
File[] files = sourcePathEntry.listFiles();
if (files == null) { // No files found.
return;
}
for (int i = 0; i < files.length; i++) {
final File file = files[i];
logger.log(TreeLogger.SPAM, "deciding the fate of file " + file, null);
// Ignore files like .svn and .cvs
if (file.getName().startsWith(".") || file.getName().equals("CVS")) {
continue;
}
if (isExcludedFile(file.getAbsolutePath())) {
// do not process the subtree
logger.log(TreeLogger.DEBUG, "not traversing " + file.toURI().toURL(), null);
continue;
}
if (file.isFile()) {
String fileName = file.getName();
if (file.getName().endsWith("java")) {
String className = file.getName().substring(0, fileName.length() - 5);
String pkgName = extractPackageName(new FileReader(file));
if (pkgName == null) {
logger.log(TreeLogger.WARN, "Not adding file = " + file.toString()
+ ", because packageName = null", null);
} else {
if (isValidPackage(pkgName, sourcePathEntry.toURI().toURL().toString())) {
// Add if the package and fileNames are okay
String typeName = Shared.makeTypeName(pkgName, className);
units.add(new FileResource(file, Shared.toPath(typeName)));
logger.log(TreeLogger.DEBUG, "adding pkgName = " + pkgName + ", file = "
+ file.toString(), null);
} else {
logger.log(TreeLogger.SPAM, " not adding file " + file.toURI().toURL(), null);
}
}
}
} else {
// Recurse into subDirs
updateCompilationUnitsInPath(file);
}
}
}
}
// currently doing only source_compatibility. true by default.
public static final boolean API_SOURCE_COMPATIBILITY = true;
/**
* Prints which class the member was declared in plus the string message in
* ApiChange, false by default.
*/
public static boolean DEBUG = false;
public static final boolean DEBUG_DUPLICATE_REMOVAL = false;
/**
* Flag for filtering duplicates, true by default. Just for debugging in rare
* cases.
*/
public static final boolean FILTER_DUPLICATES = true;
/**
* Print APIs common in the two repositories. Should be false by default.
*/
public static final boolean PRINT_COMPATIBLE = false;
/**
* Print APIs common in the two repositories. Should be false by default.
*/
public static final boolean PRINT_COMPATIBLE_WITH = false;
/**
* Flag for debugging whether typeOracle builds.
*/
public static final boolean PROCESS_EXISTING_API = true;
/**
* Flag for debugging whether typeOracle builds.
*/
public static final boolean PROCESS_NEW_API = true;
// true by default
public static final boolean REMOVE_NON_SUBCLASSABLE_ABSTRACT_CLASS_FROM_API = true;
// remove duplicates by default
public static Collection<ApiChange> getApiDiff(ApiContainer newApi, ApiContainer existingApi,
Set<String> whiteList) throws NotFoundException {
ApiDiffGenerator apiDiff = new ApiDiffGenerator(newApi, existingApi);
return getApiDiff(apiDiff, whiteList, FILTER_DUPLICATES);
}
public static void main(String args[]) {
try {
ApiCompatibilityChecker checker = new ApiCompatibilityChecker();
if (!checker.processArgs(args)) {
// if we couldn't parse arguments, return non-zero so the build breaks
System.exit(1);
}
ApiContainer newApi = null, existingApi = null;
AbstractTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(checker.type);
logger.log(TreeLogger.INFO, "gwtDevJar = " + checker.gwtDevJar + ", userJar = "
+ checker.gwtUserJar + ", refjars = " + Arrays.toString(checker.refJars)
+ ", logLevel = " + checker.type + ", printAllApi = " + checker.printAllApi, null);
Set<String> excludedPackages = checker.getSetOfExcludedPackages(checker.configProperties);
if (PROCESS_NEW_API) {
Set<Resource> resources = new HashSet<Resource>();
resources.addAll(new SourceFileResources(checker.configProperties
.getProperty("dirRoot_new"), checker.getConfigPropertyAsSet("sourceFiles_new"), checker
.getConfigPropertyAsSet("excludedFiles_new"), logger).getResources());
resources.addAll(checker.getJavaxValidationCompilationUnits(logger));
resources.addAll(checker.getGwtCompilationUnits(logger));
SourceLevel newSourceLevel =
SourceLevel.fromString(checker.configProperties.getProperty("sourceLevel_new"));
newApi =
new ApiContainer(checker.configProperties.getProperty("name_new"), resources,
excludedPackages, logger, newSourceLevel);
if (checker.printAllApi) {
logger.log(TreeLogger.INFO, newApi.getApiAsString());
}
}
if (PROCESS_EXISTING_API) {
Set<Resource> resources = new HashSet<Resource>();
if (checker.refJars == null) {
resources.addAll(new SourceFileResources(checker.configProperties
.getProperty("dirRoot_old"), checker.getConfigPropertyAsSet("sourceFiles_old"),
checker.getConfigPropertyAsSet("excludedFiles_old"), logger).getResources());
} else {
resources.addAll(new JarFileResources(checker.refJars, checker
.getConfigPropertyAsSet("sourceFiles_old"), checker
.getConfigPropertyAsSet("excludedFiles_old"), logger).getResources());
}
resources.addAll(checker.getJavaxValidationCompilationUnits(logger));
resources.addAll(checker.getGwtCompilationUnits(logger));
SourceLevel oldSourceLevel =
SourceLevel.fromString(checker.configProperties.getProperty("sourceLevel_old"));
existingApi =
new ApiContainer(checker.configProperties.getProperty("name_old"), resources,
excludedPackages, logger, oldSourceLevel);
if (checker.printAllApi) {
logger.log(TreeLogger.INFO, existingApi.getApiAsString());
}
}
if (PROCESS_NEW_API && PROCESS_EXISTING_API) {
Collection<ApiChange> apiDifferences = getApiDiff(newApi, existingApi, checker.whiteList);
for (ApiChange apiChange : apiDifferences) {
System.out.println(apiChange);
}
if (apiDifferences.size() == 0) {
System.out.println("API compatibility check SUCCESSFUL");
} else {
System.out.println("API compatibility check FAILED");
}
System.exit(apiDifferences.size() == 0 ? 0 : 1);
}
} catch (Throwable t) {
// intercepting all exceptions in main, because I have to exit with -1 so
// that the build breaks.
try {
t.printStackTrace();
System.err
.println("To view the help for this tool, execute this tool without any arguments");
} finally {
System.exit(-1);
}
}
}
// interface for testing, since do not want to build ApiDiff frequently
static Collection<ApiChange> getApiDiff(ApiDiffGenerator apiDiff, Set<String> whiteList,
boolean removeDuplicates) throws NotFoundException {
Collection<ApiChange> collection = apiDiff.getApiDiff();
if (removeDuplicates) {
collection = apiDiff.removeDuplicates(collection);
}
Set<String> matchedWhiteList = new HashSet<String>();
Collection<ApiChange> prunedCollection = new ArrayList<ApiChange>();
for (ApiChange apiChange : collection) {
String apiChangeAsString = apiChange.getStringRepresentationWithoutMessage();
apiChangeAsString = apiChangeAsString.trim();
if (whiteList.contains(apiChangeAsString)) {
matchedWhiteList.add(apiChangeAsString);
continue;
}
// check for Status.Compatible and Status.Compatible_with
if (!PRINT_COMPATIBLE && apiChange.getStatus().equals(ApiChange.Status.COMPATIBLE)) {
continue;
}
if (!PRINT_COMPATIBLE_WITH && apiChange.getStatus().equals(ApiChange.Status.COMPATIBLE_WITH)) {
continue;
}
prunedCollection.add(apiChange);
}
whiteList.removeAll(matchedWhiteList);
if (whiteList.size() > 0) {
List<String> al = new ArrayList<String>(whiteList);
Collections.sort(al);
System.err.println("ApiChanges [");
for (String apiChange : al) {
System.err.println(apiChange);
}
System.err.println("], not found. Are you using a properly formatted configuration file?");
}
List<ApiChange> apiChangeList = new ArrayList<ApiChange>(prunedCollection);
Collections.sort(apiChangeList);
return apiChangeList;
}
private Properties configProperties;
private JarFile[] extraSourceJars;
private JarFile gwtDevJar;
private JarFile gwtUserJar;
// prints the API of the two containers, false by default.
private boolean printAllApi = false;
private JarFile refJars[];
// default log output
private TreeLogger.Type type = TreeLogger.WARN;
private Set<String> whiteList;
protected ApiCompatibilityChecker() {
/*
* register handlers for gwtDevJar, gwtUserJar, refJar, logLevel,
* printAllApi, configProperties
*/
// handler for gwtDevJar
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "Path of the gwt dev jar";
}
@Override
public String getTag() {
return "-gwtDevJar";
}
@Override
public String[] getTagArgs() {
return new String[] {"gwt_dev_jar_path"};
}
@Override
public boolean setString(String str) {
gwtDevJar = getJarFromString(str);
return gwtDevJar != null;
}
});
// handler for gwtUserJar
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "Path of the gwt user jar";
}
@Override
public String getTag() {
return "-gwtUserJar";
}
@Override
public String[] getTagArgs() {
return new String[] {"gwt_user_jar_path"};
}
@Override
public boolean setString(String str) {
gwtUserJar = getJarFromString(str);
return gwtUserJar != null;
}
});
// handler for refJar
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "Path of the reference jar";
}
@Override
public String getTag() {
return "-refJar";
}
@Override
public String[] getTagArgs() {
return new String[] {"reference_jar_path"};
}
@Override
public boolean setString(String str) {
String refJarStrings[] = str.split(System.getProperty("path.separator"));
refJars = new JarFile[refJarStrings.length];
int count = 0;
for (String refJarString : refJarStrings) {
refJars[count++] = getJarFromString(refJarString);
if (refJars[count - 1] == null) {
return false; // bail-out early
}
}
return refJars != null;
}
});
// handler for logLevel
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "Sets the log level of the TreeLogger";
}
@Override
public String getTag() {
return "-logLevel";
}
@Override
public String[] getTagArgs() {
String values[] = new String[TreeLogger.Type.values().length];
int count = 0;
for (TreeLogger.Type tempType : TreeLogger.Type.values()) {
values[count++] = tempType.name();
}
return values;
}
@Override
public boolean setString(String str) {
for (TreeLogger.Type tempType : TreeLogger.Type.values()) {
if (tempType.name().equals(str)) {
type = tempType;
return true;
}
}
return false;
}
});
// handler for printAllApi
registerHandler(new ArgHandlerFlag() {
@Override
public String getPurposeSnippet() {
return "Prints all api.";
}
@Override
public String getLabel() {
return "printAllApi";
}
@Override
public boolean setFlag(boolean enabled) {
printAllApi = enabled;
return true;
}
@Override
public boolean getDefaultValue() {
return printAllApi;
}
});
// handler for configFile
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "Path of the configuration file";
}
@Override
public String getTag() {
return "-configFile";
}
@Override
public String[] getTagArgs() {
return new String[] {"config_file_path"};
}
@Override
public boolean isRequired() {
return true;
}
@Override
public boolean setString(String str) {
setPropertiesAndWhitelist(str);
return configProperties != null && whiteList != null;
}
});
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "The location of the javax.validation sources";
}
@Override
public String getTag() {
return "-validationSourceJars";
}
@Override
public String[] getTagArgs() {
return new String[] {"jar1.jar:jar2.jar"};
}
@Override
public boolean setString(String str) {
boolean success = true;
String[] parts = str.split(System.getProperty("path.separator"));
extraSourceJars = new JarFile[parts.length];
for (int i = 0, j = parts.length; i < j; i++) {
extraSourceJars[i] = getJarFromString(parts[i]);
}
return success;
}
});
}
@Override
public void printHelp() {
super.printHelp();
StringBuffer sb = new StringBuffer();
sb.append("\n");
sb.append("The config file must specify two repositories of java source files: ");
sb.append("'_old' and '_new', which are to be compared for API source compatibility.\n");
sb.append("An optional whitelist is present at the end of ");
sb.append("the config file. The format of the whitelist is same as the output of ");
sb.append("the tool without the whitelist.\n");
sb.append("Each repository is specified by the following four properties:\n");
sb.append("name specifies how the api should be refered to in the output\n");
sb.append("dirRoot optional argument that specifies the base directory of all other file/directory names\n");
sb.append("sourceFiles a colon-separated list of files/directories that specify the roots of the filesystem trees to be included.\n");
sb.append("excludeFiles a colon-separated lists of ant patterns to exclude");
sb.append("sourceLevel Java source level compatibility");
sb.append("\n\n");
sb.append("Example api.conf file:\n");
sb.append("name_old gwtEmulator");
sb.append("\n");
sb.append("dirRoot_old ./");
sb.append("\n");
sb.append("sourceFiles_old dev/core/super:user/super:user/src");
sb.append("\n");
sb.append("excludeFiles_old user/super/com/google/gwt/junit/*.java");
sb.append("\n\n");
sb.append("name_new gwtEmulatorCopy");
sb.append("\n");
sb.append("dirRoot_new ../gwt-14/");
sb.append("\n");
sb.append("sourceFiles_new dev/core:user/super:user/src");
sb.append("\n");
sb.append("excludeFiles_new user/super/com/google/gwt/junit/*.java");
sb.append("\n\n");
System.err.println(sb.toString());
}
protected JarFile getJarFromString(String str) {
try {
return new JarFile(str);
} catch (IOException ex) {
System.err.println("exception in getting jar from fileName: " + str + ", message: "
+ ex.getMessage());
return null;
}
}
protected void setPropertiesAndWhitelist(String fileName) throws IllegalArgumentException {
try {
// load config properties
FileInputStream fis = new FileInputStream(fileName);
configProperties = new Properties();
configProperties.load(fis);
fis.close();
// load whitelist
FileReader fr = new FileReader(fileName);
whiteList = readWhiteListFromFile(fr);
fr.close();
} catch (IOException ex) {
System.err.println("Have you specified the path of the config file correctly?");
throw new IllegalArgumentException(ex);
}
}
private Set<String> getConfigPropertyAsSet(String key) {
Set<String> set = new HashSet<String>();
String propertyValue = configProperties.getProperty(key);
if (propertyValue == null) {
return set;
}
for (String element : propertyValue.split(":")) {
element = element.trim();
set.add(element);
}
return set;
}
private Set<Resource> getGwtCompilationUnits(TreeLogger logger) throws FileNotFoundException,
IOException, NotFoundException, UnableToCompleteException {
Set<Resource> resources = new HashSet<Resource>();
if (gwtDevJar == null || gwtUserJar == null) {
if (gwtDevJar != null) {
System.err.println("Argument gwtUserJar must be provided for gwtDevJar to be used");
}
if (gwtUserJar != null) {
System.err.println("Argument gwtDevJar must be provided for gwtUserJar to be used");
}
return resources;
}
// gwt-user.jar
Set<String> gwtIncludedPaths =
new HashSet<String>(Arrays.asList(new String[] {"com/google/gwt", "com/google/web"}));
Set<String> gwtExcludedPaths =
new HashSet<String>(
Arrays
.asList(new String[] {
"com/google/gwt/benchmarks",
"com/google/gwt/i18n/rebind",
"com/google/gwt/i18n/tools",
"com/google/gwt/json",
"com/google/gwt/junit",
"com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java",
"com/google/gwt/user/rebind", "com/google/gwt/user/server",
"com/google/gwt/user/tools",}));
Resources cu =
new JarFileResources(new JarFile[] {gwtUserJar}, gwtIncludedPaths, gwtExcludedPaths, logger);
resources.addAll(cu.getResources());
// gwt-dev-*.jar
gwtIncludedPaths =
new HashSet<String>(Arrays.asList(new String[] {
"com/google/gwt/core/client", "com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang",
"com/google/gwt/lang",}));
cu =
new JarFileResources(new JarFile[] {gwtDevJar}, gwtIncludedPaths, new HashSet<String>(),
logger);
resources.addAll(cu.getResources());
return resources;
}
/**
* This is a hack to make the ApiChecker able to find the javax.validation
* sources, which we include through an external jar file.
*/
private Set<Resource> getJavaxValidationCompilationUnits(TreeLogger logger)
throws UnableToCompleteException, NotFoundException, IOException {
Set<Resource> resources = new HashSet<Resource>();
if (extraSourceJars != null) {
Resources extra = new JarFileResources(
extraSourceJars,
Collections.singleton(""),
new HashSet<String>(Arrays.asList(
"javax/validation/Configuration.java",
"javax/validation/MessageInterpolator.java",
"javax/validation/Validation.java",
"javax/validation/ValidatorContext.java",
"javax/validation/ValidatorFactory.java",
"javax/validation/ValidationProviderResolver.java",
"javax/validation/bootstrap/GenericBootstrap.java",
"javax/validation/bootstrap/ProviderSpecificBootstrap.java",
"javax/validation/constraints/Pattern.java",
"javax/validation/spi/BootstrapState.java",
"javax/validation/spi/ConfigurationState.java",
"javax/validation/spi/ValidationProvider.java")),
logger);
Set<Resource> loaded = extra.getResources();
System.out.println("Found " + loaded.size() + " new resources");
resources.addAll(loaded);
}
return resources;
}
/*
* excludedPackages is used in only one place: to determine whether some class
* is an api class or not
*/
private Set<String> getSetOfExcludedPackages(Properties config) {
String allExcludedPackages = config.getProperty("excludedPackages");
if (allExcludedPackages == null) {
allExcludedPackages = "";
}
String excludedPackagesArray[] = allExcludedPackages.split(":");
return new HashSet<String>(Arrays.asList(excludedPackagesArray));
}
/**
* Each whiteList element is an {@link ApiElement} and
* {@link ApiChange.Status} separated by space. For example,
* "java.util.ArrayList::size() MISSING". The {@code ApiElement} is
* represented as the string obtained by invoking the getRelativeSignature()
* method on {@link ApiElement}.
*/
private Set<String> readWhiteListFromFile(FileReader fr) throws IOException {
Set<String> hashSet = new HashSet<String>();
BufferedReader br = new BufferedReader(fr);
String str = null;
while ((str = br.readLine()) != null) {
str = str.trim();
// ignore comments
if (str.startsWith("#")) {
continue;
}
String splits[] = str.split(" ");
if (splits.length > 1 && ApiChange.contains(splits[1])) {
String identifier = splits[0] + ApiDiffGenerator.DELIMITER + splits[1];
hashSet.add(identifier.trim());
}
}
return hashSet;
}
}