/*
* Copyright 2011 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.inject.rebind;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.javac.StandardGeneratorContext;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Gin-internal class loader that allows us to load classes generated by other generators and
* super-source.
*
* <p>In most cases, Gin needs access to the GWT version of a class (whether it is unmodified,
* created by a generator or a super-source version) so this is the default version provided by this
* class loader. The exception are JRE classes (which cannot be loaded by a custom class loader),
* classes that are also used in the Gin Generator "rebind" code (otherwise class literal
* comparisons yield strange results) and classes that are defined as super-source by Gin but must
* be their "normal" self during the generator run. These exceptions are loaded with the system
* class loader.
*
* <p>If the class is not available to GWT, we attempt to load it through the system class loader.
*
* <p>Unfortunately, GWT does not like to expose internal details like the compilation state and its
* bytes. For now, we use reflection to access this internal state but in the long term we should
* switch to other strategies such as running javac on source (which we'd need to reverse-engineer
* from parsing the GWT AST).
*/
class GinBridgeClassLoader extends ClassLoader {
private final TreeLogger logger;
private final GeneratorContext context;
/**
* Packages that should not be loaded from GWT.
*/
private final Collection<String> exceptedPackages;
// Lazily load class files from compilation state.
private boolean loadedClassFiles = false;
private Map<String, CompiledClass> classFileMap;
GinBridgeClassLoader(GeneratorContext context, TreeLogger logger,
Collection<String> exceptedPackages) {
super(GinBridgeClassLoader.class.getClassLoader()); // Use own class loader.
this.context = context;
this.logger = logger;
this.exceptedPackages = getExceptedPackages(exceptedPackages);
}
private static Collection<String> getExceptedPackages(Collection<String> superSourceExceptions) {
Set<String> names = new LinkedHashSet<String>();
for (String name : superSourceExceptions) {
if (name.endsWith(".")) {
names.add(name);
} else {
names.add(name + ".");
}
}
// Make sure we're not loading JRE classes through a non-system class loader (which is not
// allowed).
names.add("java.");
// Annotation loading will require sun.reflect APIs, even when they're referenced from
// and present in client code.
names.add("sun.reflect.");
return names;
}
/**
* @inheritDoc
*
* Gin class loading implementation, making sure that classes are loaded consistently and can be
* GWT generated or super-source classes. See description {@link GinBridgeClassLoader above}.
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
if (inExceptedPackage(name)) {
clazz = super.loadClass(name, false);
} else {
try {
clazz = findClass(name);
} catch (ClassNotFoundException e) {
clazz = super.loadClass(name, false);
if (!clazz.isAnnotation()) { // Annotations are always safe to load
logger.log(Type.WARN, String.format(
"Class %s is used in Gin, but not available in GWT client code.", name));
}
}
}
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
private boolean inExceptedPackage(String name) {
for (String pkg : exceptedPackages) {
if (name.startsWith(pkg)) {
return true;
}
}
return false;
}
/**
* Looks up classes in GWT's compilation state.
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (!loadedClassFiles) {
classFileMap = extractClassFileMap();
loadedClassFiles = true;
}
if (classFileMap == null) {
throw new ClassNotFoundException(name);
}
String internalName = name.replace('.', '/');
CompiledClass compiledClass = classFileMap.get(internalName);
if (compiledClass == null) {
throw new ClassNotFoundException(name);
}
// Make sure the class's package is present.
String pkg = compiledClass.getPackageName();
if (getPackage(pkg) == null) {
definePackage(pkg, null, null, null, null, null, null, null);
}
byte[] bytes = compiledClass.getBytes();
return defineClass(name, bytes, 0, bytes.length);
}
/**
* Retrieves class definitions from a {@link GeneratorContext} by downcasting.
*/
private Map<String, CompiledClass> extractClassFileMap() {
if (context instanceof StandardGeneratorContext) {
StandardGeneratorContext standardContext = (StandardGeneratorContext) context;
return standardContext.getCompilationState().getClassFileMap();
} else {
logger.log(TreeLogger.Type.WARN,
String.format("Could not load generated classes from GWT context, "
+ "encountered unexpected generator type %s.", context.getClass()));
return null;
}
}
}