blob: 47786a4544ae87e203db2b6b60a24d475acaa1b9 [file] [log] [blame]
/*
* Copyright 2010 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.javac;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.dev.util.collect.Lists;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* Tracks dependencies from a {@link CompilationUnit} to {@link CompiledClass
* CompiledClasses}.
*/
class Dependencies {
Map<String, CompiledClass> qualified = new HashMap<String, CompiledClass>();
Map<String, CompiledClass> simple = new HashMap<String, CompiledClass>();
private final List<String> apiRefs;
private final String myPackage;
private List<String> unresolvedQualified;
private List<String> unresolvedSimple;
Dependencies() {
this.myPackage = "";
this.apiRefs = Lists.create();
}
/**
* Initializes the set of simple and qualified dependency names, but does not
* resolve them.
*/
Dependencies(String myPackage, List<String> unresolvedQualified,
List<String> unresolvedSimple, List<String> apiRefs) {
this.myPackage = StringInterner.get().intern(
(myPackage.length() == 0) ? "" : (myPackage + '.'));
this.unresolvedQualified = unresolvedQualified;
this.unresolvedSimple = unresolvedSimple;
this.apiRefs = apiRefs;
}
/**
* Returns the list of deps that cannot be resolved at all.
*/
List<String> findMissingApiRefs(Set<String> allValidClasses) {
List<String> result = Lists.create();
for (String apiRef : apiRefs) {
if (!allValidClasses.contains(apiRef)) {
result = Lists.add(result, apiRef);
}
}
return result;
}
/**
* Resolves unqualified dependencies against the global list of all valid
* classes. Must be called before {@link #validate(String, Map, Map)}.
*/
void resolve(Map<String, CompiledClass> allValidClasses) {
for (String ref : unresolvedQualified) {
CompiledClass cc = allValidClasses.get(ref);
qualified.put(ref, cc);
}
for (String ref : unresolvedSimple) {
CompiledClass cc = findBySimpleName(ref, allValidClasses);
allValidClasses.get(ref);
simple.put(ref, cc);
}
unresolvedQualified = unresolvedSimple = null;
}
/**
* Validate that all of my existing dependencies can be found in the global
* set of valid classes, and resolve to structurally identical APIs.
*
* @return <code>true</code> if all of my dependencies are valid
*/
boolean validate(Map<String, CompiledClass> allValidClasses,
Map<CompiledClass, CompiledClass> cachedStructurallySame) {
for (Entry<String, CompiledClass> entry : qualified.entrySet()) {
CompiledClass theirs = allValidClasses.get(entry.getKey());
if (!validateClass(cachedStructurallySame, entry, theirs)) {
return false;
}
}
for (Entry<String, CompiledClass> entry : simple.entrySet()) {
CompiledClass theirs = findBySimpleName(entry.getKey(), allValidClasses);
if (!validateClass(cachedStructurallySame, entry, theirs)) {
return false;
}
}
return true;
}
/**
* Tries to resolve a simple name using Java lookup rules, first checking the
* current package, then java.lang.
*/
private CompiledClass findBySimpleName(String ref,
Map<String, CompiledClass> allValidClasses) {
CompiledClass cc = allValidClasses.get(myPackage + ref);
if (cc != null) {
return cc;
}
return allValidClasses.get("java.lang." + ref);
}
private boolean hasStructuralChanges(CompiledClass mine, CompiledClass theirs) {
try {
ClassFileReader cfr = new ClassFileReader(theirs.getBytes(), null);
return cfr.hasStructuralChanges(mine.getBytes());
} catch (ClassFormatException e) {
throw new RuntimeException("Unexpected error reading compiled class", e);
}
}
private boolean structurallySame(CompiledClass mine, CompiledClass theirs,
Map<CompiledClass, CompiledClass> cachedStructurallySame) {
if (cachedStructurallySame.get(mine) == theirs) {
return true;
}
if (cachedStructurallySame.containsKey(mine)) {
return false;
}
boolean isSame = !hasStructuralChanges(mine, theirs);
if (isSame) {
cachedStructurallySame.put(mine, theirs);
} else {
cachedStructurallySame.put(mine, null);
}
return isSame;
}
/**
* Returns true if my class is the same as their class. Uses caching to avoid
* recomputing diffs. Updates the my entry to 'their' class if non-identical
* objects have the same structure.
*/
private boolean validateClass(
Map<CompiledClass, CompiledClass> cachedStructurallySame,
Entry<String, CompiledClass> entry, CompiledClass theirs) {
CompiledClass mine = entry.getValue();
boolean result;
if (mine == theirs) {
// Identical.
result = true;
} else if ((mine == null) != (theirs == null)) {
result = false;
} else if (structurallySame(mine, theirs, cachedStructurallySame)) {
// Update our entry for identity.
entry.setValue(theirs);
result = true;
} else {
result = false;
}
return result;
}
}