com.jyavu.jytsu.core.rebind.GinBridgeClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.jyavu.jytsu.core.rebind.GinBridgeClassLoader.java

Source

/*
 * 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.jyavu.jytsu.core.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.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;
        }
    }
}