blob: 8bfe0732d36dd94dea6e150a69b43f0d34aefd06 [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.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompilationUnitBuilder;
import com.google.gwt.dev.javac.CompilationUnitTypeOracleUpdater;
import com.google.gwt.dev.javac.JdtCompiler;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.arg.SourceLevel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* {@link ApiContainer} Encapsulates an API.
*
*/
public final class ApiContainer {
private final Map<JClassType, Boolean> apiClassCache = new HashMap<JClassType, Boolean>();
private final Map<String, ApiPackage> apiPackages = new HashMap<String, ApiPackage>();
private final CompilerContext compilerContext = new CompilerContext();
private final Set<String> excludedPackages;
private final TreeLogger logger;
private final String name;
private final TypeOracle typeOracle;
/**
* A public constructor used for programmatic invocation and testing.
*
* @param name Api name
* @param resources a set of Resources
* @param excludedPackages a set of excludedPackages
* @param logger TreeLogger for logging messages
* @throws UnableToCompleteException if there is a TypeOracle exception
*/
ApiContainer(String name, Set<Resource> resources, Set<String> excludedPackages, TreeLogger logger)
throws UnableToCompleteException {
this(name, resources, excludedPackages, logger, null);
}
/**
* A public constructor used for programmatic invocation and testing.
*
* @param name Api name
* @param resources a set of Resources
* @param excludedPackages a set of excludedPackages
* @param logger TreeLogger for logging messages
* @param sourceLevel Java source compatibility level
* @throws UnableToCompleteException if there is a TypeOracle exception
*/
ApiContainer(String name, Set<Resource> resources, Set<String> excludedPackages, TreeLogger logger,
SourceLevel sourceLevel)
throws UnableToCompleteException {
this.name = name;
this.logger = logger;
logger.log(TreeLogger.INFO, "name = " + name + ", builders.size = " + resources.size(), null);
compilerContext.getOptions().setSourceLevel(
sourceLevel == null ? SourceLevel.DEFAULT_SOURCE_LEVEL : sourceLevel);
this.typeOracle = createTypeOracle(resources);
this.excludedPackages = excludedPackages;
initializeApiPackages();
}
/**
* Get all the API members as String.
*
* @return the string value
*/
public String getApiAsString() {
StringBuffer sb = new StringBuffer();
sb.append("Api: " + name + ", size = " + apiPackages.size() + "\n\n");
List<ApiPackage> sortedApiPackages = new ArrayList<ApiPackage>(apiPackages.values());
Collections.sort(sortedApiPackages);
for (ApiPackage apiPackage : sortedApiPackages) {
sb.append(apiPackage.getApiAsString());
}
return sb.toString();
}
ApiPackage getApiPackage(String packageName) {
return apiPackages.get(packageName);
}
HashSet<String> getApiPackageNames() {
return new HashSet<String>(apiPackages.keySet());
}
Set<ApiPackage> getApiPackagesBySet(Set<String> names) {
Set<ApiPackage> ret = new HashSet<ApiPackage>();
for (String packageName : names) {
ret.add(apiPackages.get(packageName));
}
return ret;
}
TreeLogger getLogger() {
return logger;
}
String getName() {
return name;
}
boolean isApiClass(JClassType classType) {
Boolean ret = apiClassCache.get(classType);
if (ret != null) {
return ret.booleanValue();
}
// to avoid infinite recursion for isApiClass("BAR", ..) when:
// class FOO {
// public class BAR extends FOO {
// }
// }
apiClassCache.put(classType, Boolean.FALSE);
boolean bool = computeIsApiClass(classType);
if (bool) {
apiClassCache.put(classType, Boolean.TRUE);
} else {
apiClassCache.put(classType, Boolean.FALSE);
}
// container.getLogger().log(TreeLogger.SPAM, "computed isApiClass for " +
// classType + " as " + bool, null);
return bool;
}
boolean isInstantiableApiClass(JClassType classType) {
return !classType.isAbstract() && isApiClass(classType)
&& hasPublicOrProtectedConstructor(classType);
}
boolean isNotsubclassableApiClass(JClassType classType) {
return isApiClass(classType) && !isSubclassable(classType);
}
boolean isSubclassableApiClass(JClassType classType) {
return isApiClass(classType) && isSubclassable(classType);
}
/**
* Assumption: Clients may subclass an API class, but they will not add their
* class to the package.
*
* Notes: -- A class with only private constructors can be an API class.
*/
private boolean computeIsApiClass(JClassType classType) {
if (excludedPackages.contains(classType.getPackage().getName())) {
return false;
}
// check for outer classes
if (isPublicOuterClass(classType)) {
return true;
}
// if classType is not a member type, return false
if (!classType.isMemberType()) {
return false;
}
JClassType enclosingType = classType.getEnclosingType();
if (classType.isPublic()) {
return isApiClass(enclosingType) || isAnySubtypeAnApiClass(enclosingType);
}
if (classType.isProtected()) {
return isSubclassableApiClass(enclosingType)
|| isAnySubtypeASubclassableApiClass(enclosingType);
}
return false;
}
private TypeOracle createTypeOracle(Set<Resource> resources) throws UnableToCompleteException {
List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>();
for (Resource resource : resources) {
CompilationUnitBuilder builder = CompilationUnitBuilder.create(resource);
builders.add(builder);
}
List<CompilationUnit> units = JdtCompiler.compile(logger, compilerContext, builders);
boolean anyError = false;
TreeLogger branch = logger.branch(TreeLogger.TRACE, "Checking for compile errors");
for (CompilationUnit unit : units) {
CompilationProblemReporter.reportErrors(branch, unit, false);
anyError |= unit.isError();
}
if (anyError) {
logger.log(TreeLogger.ERROR, "Unable to build typeOracle for " + getName());
throw new UnableToCompleteException();
}
CompilationUnitTypeOracleUpdater typeOracleBuilder =
new CompilationUnitTypeOracleUpdater(
new com.google.gwt.dev.javac.typemodel.TypeOracle());
typeOracleBuilder.addNewUnits(logger, units);
logger.log(TreeLogger.INFO, "API " + name + ", Finished with building typeOracle, added "
+ units.size() + " files", null);
return typeOracleBuilder.getTypeOracle();
}
private boolean hasPublicOrProtectedConstructor(JClassType classType) {
JConstructor[] constructors = classType.getConstructors();
for (JConstructor constructor : constructors) {
if (constructor.isPublic() || constructor.isProtected()) {
return true;
}
}
return false;
}
/**
* Purge non API packages.
*/
private void initializeApiPackages() {
Set<JPackage> allPackages = new HashSet<JPackage>(Arrays.asList(typeOracle.getPackages()));
Set<String> packagesNotAdded = new HashSet<String>();
for (JPackage packageObject : allPackages) {
if (isApiPackage(packageObject)) {
ApiPackage apiPackageObj = new ApiPackage(packageObject, this);
apiPackages.put(apiPackageObj.getName(), apiPackageObj);
} else {
packagesNotAdded.add(packageObject.toString());
}
}
if (packagesNotAdded.size() > 0) {
logger.log(TreeLogger.DEBUG, "API " + name + ": not added " + packagesNotAdded.size()
+ " packages: " + packagesNotAdded, null);
}
if (apiPackages.size() > 0) {
logger.log(TreeLogger.INFO, "API " + name + " " + apiPackages.size() + " Api packages: "
+ apiPackages.keySet(), null);
}
}
private boolean isAnySubtypeAnApiClass(JClassType classType) {
JClassType subTypes[] = classType.getSubtypes();
for (JClassType tempType : subTypes) {
if (isApiClass(tempType)) {
return true;
}
}
return false;
}
private boolean isAnySubtypeASubclassableApiClass(JClassType classType) {
JClassType subTypes[] = classType.getSubtypes();
for (JClassType tempType : subTypes) {
if (isSubclassableApiClass(tempType)) {
return true;
}
}
return false;
}
/**
* A package is an API package if it contains at least one API class. Refer
* http://wiki.eclipse.org/index.php/Evolving_Java-based_APIs This definition
* boils down to "a package is an API package iff it contains at least one API
* class that is not enclosed in any other class."
*
* @return return true if and only if the packageObject is an apiPackage
*/
private boolean isApiPackage(JPackage packageObject) {
if (excludedPackages.contains(packageObject.getName())) {
return false;
}
JClassType classTypes[] = packageObject.getTypes();
for (JClassType classType : classTypes) {
if (isPublicOuterClass(classType)) {
return true;
}
}
return false;
}
/**
* @return returns true if classType is public AND an outer class
*/
private boolean isPublicOuterClass(JClassType classType) {
return classType.isPublic() && !classType.isMemberType();
}
private boolean isSubclassable(JClassType classType) {
return !classType.isFinal() && hasPublicOrProtectedConstructor(classType);
}
}