| /* |
| * 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); |
| } |
| |
| } |