blob: c9ec6d102c636c3ca005f998c50bd1759647deac [file] [log] [blame]
/*
* Copyright 2013 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.dev.cfg;
import com.google.gwt.dev.cfg.Libraries.IncompatibleLibraryVersionException;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.PermutationResult;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.PersistenceBackedObject;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A convenience wrapper around a set of libraries.<br />
*
* Indexes library contents to enable single step retrieval of resources by name.<br />
*
* Analyzes the library dependency tree to provide link ordering and property and rebound type
* change analysis for partial generator execution.<br />
*
* Combines library resource lists for easy iteration over the complete set.
*/
public class LibraryGroup {
/**
* An exception that indicates that some library was referenced as a dependency but was not
* provided to the compiler.
*/
public static class UnresolvedLibraryException extends InternalCompilerException {
public UnresolvedLibraryException(String message) {
super(message);
}
}
/**
* Factory function that constructs and returns a library group from a list of libraries.
*/
public static LibraryGroup fromLibraries(
List<? extends Library> libraries, boolean verifyLibraryReferences) {
LibraryGroup libraryGroup = new LibraryGroup();
for (Library library : libraries) {
libraryGroup.libraries.add(library);
}
libraryGroup.buildLibraryIndexes(verifyLibraryReferences);
return libraryGroup;
}
/**
* Factory function that constructs and returns a library group from a list of zip library paths.
*/
public static LibraryGroup fromZipPaths(List<String> zipLibraryPaths)
throws IncompatibleLibraryVersionException {
List<Library> zipLibraries = Lists.newArrayList();
for (String zipLibraryPath : zipLibraryPaths) {
zipLibraries.add(new ZipLibrary(zipLibraryPath));
}
return fromLibraries(zipLibraries, true);
}
private Set<String> compilationUnitTypeNames;
private List<Library> libraries = Lists.newArrayList();
private Map<String, Library> librariesByBuildResourcePath;
private Map<String, Library> librariesByClassFilePath;
private Map<String, Library> librariesByCompilationUnitTypeName;
private Map<String, Library> librariesByName;
private Map<String, Library> librariesByPublicResourcePath;
private List<PersistenceBackedObject<PermutationResult>> permutationResultHandles;
private Set<String> reboundTypeNames;
private List<Library> rootLibraries;
private Set<String> superSourceCompilationUnitTypeNames;
private LibraryGroup() {
// Private to force class construction via one of the public factory functions.
}
/**
* Returns whether a build resource is is available at the given path.
*/
public boolean containsBuildResource(String buildResourcePath) {
return getLibrariesByBuildResourcePath().containsKey(buildResourcePath);
}
/**
* Create and return a library group of just the given libraries referenced by name. Useful for
* taking an overly broad library group of all libraries in a dependency tree and trimming it down
* to a restricted set of strict top level dependencies.
*/
public LibraryGroup createSubgroup(List<String> libraryNames) {
return fromLibraries(getLibraries(libraryNames), false);
}
/**
* Walks the parts of the library dependency graph that have not run the given generator
* referenced by name and accumulates and returns a map from binding property name to newly legal
* values that were declared in those libraries.
*/
public Multimap<String, String> gatherNewBindingPropertyValuesForGenerator(String generatorName) {
Multimap<String, String> newBindingPropertyValuesByName = LinkedHashMultimap.create();
for (Library libraryPendingGeneratorRun : gatherLibrariesForGenerator(generatorName, true)) {
newBindingPropertyValuesByName.putAll(
libraryPendingGeneratorRun.getNewBindingPropertyValuesByName());
}
return newBindingPropertyValuesByName;
}
/**
* Walks the parts of the library dependency graph that have not run the given generator
* referenced by name and accumulates and returns a map from configuration property name to newly
* set values that were declared in those libraries.
*/
public Multimap<String, String> gatherNewConfigurationPropertyValuesForGenerator(
String generatorName) {
Multimap<String, String> newConfigurationPropertyValuesByName = LinkedHashMultimap.create();
for (Library libraryPendingGeneratorRun : gatherLibrariesForGenerator(generatorName, true)) {
newConfigurationPropertyValuesByName.putAll(
libraryPendingGeneratorRun.getNewConfigurationPropertyValuesByName());
}
return newConfigurationPropertyValuesByName;
}
/**
* Walks the parts of the library dependency graph that have not run the given generator
* referenced by name and accumulates and returns a set of newly rebound type names.
*/
public Set<String> gatherNewReboundTypeNamesForGenerator(String generatorName) {
Set<String> newReboundTypeNames = Sets.newHashSet();
for (Library libraryPendingGeneratorRun : gatherLibrariesForGenerator(generatorName, true)) {
newReboundTypeNames.addAll(libraryPendingGeneratorRun.getReboundTypeNames());
}
return newReboundTypeNames;
}
/**
* Walks the parts of the library dependency graph that have already run the given generator
* referenced by name and accumulates and returns the set of old rebound type names.
*/
public Set<String> gatherOldReboundTypeNamesForGenerator(String generatorName) {
Set<String> oldReboundTypeNames = Sets.newHashSet();
for (Library processedLibrary : gatherLibrariesForGenerator(generatorName, false)) {
oldReboundTypeNames.addAll(processedLibrary.getReboundTypeNames());
}
return oldReboundTypeNames;
}
/**
* Returns the resource referenced by name if present or null;
*/
public Resource getBuildResourceByPath(String buildResourcePath) {
if (!getLibrariesByBuildResourcePath().containsKey(buildResourcePath)) {
return null;
}
Library library = getLibrariesByBuildResourcePath().get(buildResourcePath);
return library.getBuildResourceByPath(buildResourcePath);
}
/**
* Returns the set of all build resource paths.
*/
public Set<String> getBuildResourcePaths() {
return getLibrariesByBuildResourcePath().keySet();
}
/**
* Opens and returns an input stream for the given class file path if present or null.
*/
public InputStream getClassFileStream(String classFilePath) {
Set<String> classFilePaths = getLibrariesByClassFilePath().keySet();
if (!classFilePaths.contains(Libraries.computeClassFileName(classFilePath))) {
return null;
}
Library library =
getLibrariesByClassFilePath().get(Libraries.computeClassFileName(classFilePath));
return library.getClassFileStream(classFilePath);
}
/**
* Returns the compilation unit for the given compilation unit type name if present or null.
*/
public CompilationUnit getCompilationUnitByTypeName(String typeName) {
if (!getLibrariesByCompilationUnitTypeName().containsKey(typeName)) {
return null;
}
Library library = getLibrariesByCompilationUnitTypeName().get(typeName);
return library.getCompilationUnitByTypeName(typeName);
}
/**
* Returns the set of all compilation unit type names.
*/
public Set<String> getCompilationUnitTypeNames() {
if (compilationUnitTypeNames == null) {
compilationUnitTypeNames = Sets.newLinkedHashSet();
for (Library library : libraries) {
compilationUnitTypeNames.addAll(library.getCompilationUnitTypeNames());
}
compilationUnitTypeNames = Collections.unmodifiableSet(compilationUnitTypeNames);
}
return compilationUnitTypeNames;
}
/**
* Returns the list of all permutation result handles (one per library) in library link order.
*/
public List<PersistenceBackedObject<PermutationResult>> getPermutationResultHandlesInLinkOrder() {
if (permutationResultHandles == null) {
permutationResultHandles = Lists.newArrayList();
for (Library library : libraries) {
permutationResultHandles.add(library.getPermutationResultHandle());
}
permutationResultHandles = Collections.unmodifiableList(permutationResultHandles);
}
return permutationResultHandles;
}
/**
* Returns the resource referenced by name if present or null;
*/
public Resource getPublicResourceByPath(String path) {
if (!getLibrariesByPublicResourcePath().containsKey(path)) {
return null;
}
Library library = getLibrariesByPublicResourcePath().get(path);
return library.getPublicResourceByPath(path);
}
/**
* Returns the set of all public resource paths.
*/
public Set<String> getPublicResourcePaths() {
return getLibrariesByPublicResourcePath().keySet();
}
/**
* Returns the set of names of types which are the subject of GWT.create() calls in source code in
* any of the contained libraries.
*/
public Set<String> getReboundTypeNames() {
if (reboundTypeNames == null) {
reboundTypeNames = Sets.newLinkedHashSet();
for (Library library : libraries) {
reboundTypeNames.addAll(library.getReboundTypeNames());
}
reboundTypeNames = Collections.unmodifiableSet(reboundTypeNames);
}
return reboundTypeNames;
}
/**
* Returns the set of compilation unit type names for all contained super source compilation
* units.
*/
public Set<String> getSuperSourceCompilationUnitTypeNames() {
if (superSourceCompilationUnitTypeNames == null) {
superSourceCompilationUnitTypeNames = Sets.newLinkedHashSet();
for (Library library : libraries) {
superSourceCompilationUnitTypeNames.addAll(
library.getSuperSourceCompilationUnitTypeNames());
}
superSourceCompilationUnitTypeNames =
Collections.unmodifiableSet(superSourceCompilationUnitTypeNames);
}
return superSourceCompilationUnitTypeNames;
}
// VisibleForTesting
List<Library> getLibraries() {
return libraries;
}
// VisibleForTesting
List<Library> getLibraries(Collection<String> libraryNames) {
return Lists.newArrayList(
Maps.filterKeys(librariesByName, Predicates.in(libraryNames)).values());
}
private Iterable<Library> asLibraries(Set<String> libraryNames) {
return Iterables.transform(libraryNames, new Function<String, Library>() {
@Override
public Library apply(String libraryName) {
return librariesByName.get(libraryName);
}
});
}
private void buildLibraryIndexes(boolean verifyLibraryReferences) {
librariesByName = Maps.newLinkedHashMap();
for (Library library : libraries) {
librariesByName.put(library.getLibraryName(), library);
}
Multimap<Library, Library> parentLibrariesByChildLibrary = HashMultimap.create();
Multimap<Library, Library> childLibrariesByParentLibrary = HashMultimap.create();
for (Library parentLibrary : libraries) {
for (String childLibraryName : parentLibrary.getDependencyLibraryNames()) {
Library childLibrary = librariesByName.get(childLibraryName);
boolean libraryIsMissing = childLibrary == null;
if (libraryIsMissing && verifyLibraryReferences) {
throw new UnresolvedLibraryException("Library " + parentLibrary.getLibraryName()
+ " references library " + childLibraryName + " but it is not available.");
}
parentLibrariesByChildLibrary.put(childLibrary, parentLibrary);
childLibrariesByParentLibrary.put(parentLibrary, childLibrary);
}
}
rootLibraries = Lists.newArrayList();
for (Library library : libraries) {
boolean libraryHasParents = parentLibrariesByChildLibrary.containsKey(library);
if (libraryHasParents) {
continue;
}
rootLibraries.add(library);
}
List<Library> librariesInLinkOrder = Lists.newArrayList();
Set<Library> maybeLeafLibraries = Sets.newLinkedHashSet(libraries);
while (!maybeLeafLibraries.isEmpty()) {
List<Library> leafLibraries = Lists.newArrayList();
for (Library maybeLeafLibrary : maybeLeafLibraries) {
if (childLibrariesByParentLibrary.get(maybeLeafLibrary).isEmpty()) {
leafLibraries.add(maybeLeafLibrary);
}
}
librariesInLinkOrder.addAll(leafLibraries);
maybeLeafLibraries.clear();
for (Library leafLibrary : leafLibraries) {
Collection<Library> parentLibraries = parentLibrariesByChildLibrary.removeAll(leafLibrary);
maybeLeafLibraries.addAll(parentLibraries);
for (Library parentLibrary : parentLibraries) {
childLibrariesByParentLibrary.remove(parentLibrary, leafLibrary);
}
}
}
libraries = librariesInLinkOrder;
}
/**
* Walks the library dependency graph and collects a list of libraries that either have or have
* not run the given generator depending on the given gatherNotProcessed boolean.
*/
private List<Library> gatherLibrariesForGenerator(String generatorName, boolean generatorWasRun) {
Set<Library> exploredLibraries = Sets.newHashSet();
LinkedList<Library> unexploredLibraries = Lists.newLinkedList();
List<Library> librariesForGenerator = Lists.newArrayList();
unexploredLibraries.addAll(rootLibraries);
while (!unexploredLibraries.isEmpty()) {
Library library = unexploredLibraries.removeFirst();
exploredLibraries.add(library);
boolean alreadyProcessed = library.getRanGeneratorNames().contains(generatorName);
if (generatorWasRun && alreadyProcessed) {
continue;
}
librariesForGenerator.add(library);
for (Library dependencyLibrary : asLibraries(library.getDependencyLibraryNames())) {
if (exploredLibraries.contains(dependencyLibrary)) {
continue;
}
unexploredLibraries.add(dependencyLibrary);
}
}
return librariesForGenerator;
}
private Map<String, Library> getLibrariesByBuildResourcePath() {
if (librariesByBuildResourcePath == null) {
librariesByBuildResourcePath = Maps.newLinkedHashMap();
for (Library library : libraries) {
Set<String> buildResourcePaths = library.getBuildResourcePaths();
for (String buildResourcePath : buildResourcePaths) {
librariesByBuildResourcePath.put(buildResourcePath, library);
}
}
librariesByBuildResourcePath = Collections.unmodifiableMap(librariesByBuildResourcePath);
}
return librariesByBuildResourcePath;
}
private Map<String, Library> getLibrariesByClassFilePath() {
if (librariesByClassFilePath == null) {
librariesByClassFilePath = Maps.newLinkedHashMap();
// Record regular class files first.
for (Library library : libraries) {
Set<String> classFilePaths = library.getClassFilePaths();
for (String classFilePath : classFilePaths) {
librariesByClassFilePath.put(classFilePath, library);
}
}
// Overwrite with superSource class files, so they have higher priority.
for (Library library : libraries) {
Set<String> superSourceClassFilePaths = library.getSuperSourceClassFilePaths();
for (String superSourceClassFilePath : superSourceClassFilePaths) {
librariesByClassFilePath.put(superSourceClassFilePath, library);
}
}
librariesByClassFilePath = Collections.unmodifiableMap(librariesByClassFilePath);
}
return librariesByClassFilePath;
}
private Map<String, Library> getLibrariesByCompilationUnitTypeName() {
if (librariesByCompilationUnitTypeName == null) {
librariesByCompilationUnitTypeName = Maps.newLinkedHashMap();
// Record regular compilation units first.
for (Library library : libraries) {
for (String compilationUnitTypeName : library.getCompilationUnitTypeNames()) {
librariesByCompilationUnitTypeName.put(compilationUnitTypeName, library);
}
}
// Overwrite with superSource compilation units, so they have higher priority.
for (Library library : libraries) {
for (String superSourceCompilationUnitTypeName :
library.getSuperSourceCompilationUnitTypeNames()) {
librariesByCompilationUnitTypeName.put(superSourceCompilationUnitTypeName, library);
}
}
librariesByCompilationUnitTypeName =
Collections.unmodifiableMap(librariesByCompilationUnitTypeName);
}
return librariesByCompilationUnitTypeName;
}
private Map<String, Library> getLibrariesByPublicResourcePath() {
if (librariesByPublicResourcePath == null) {
librariesByPublicResourcePath = Maps.newLinkedHashMap();
for (Library library : libraries) {
Set<String> publicResourcePaths = library.getPublicResourcePaths();
for (String publicResourcePath : publicResourcePaths) {
librariesByPublicResourcePath.put(publicResourcePath, library);
}
}
librariesByPublicResourcePath = Collections.unmodifiableMap(librariesByPublicResourcePath);
}
return librariesByPublicResourcePath;
}
}