All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.inject.rebind.GinjectorGenerator Maven / Gradle / Ivy

/*
 * 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.BadPropertyValueException;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.inject.client.GinModule;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;
import com.google.gwt.inject.client.NoGinModules;
import com.google.inject.Guice;
import com.google.inject.Module;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Generator for implementations of {@link com.google.gwt.inject.client.Ginjector}.
 */
public class GinjectorGenerator extends Generator {

  // Visible for testing.
  ClassLoader classLoader;

  private PropertyOracle propertyOracle;

  private TreeLogger logger;

  @Override
  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
      throws UnableToCompleteException {

    propertyOracle = context.getPropertyOracle();
    this.logger = logger;
    
    classLoader = createGinClassLoader(logger, context);

    Class ginjectorInterface;
    try {
      ginjectorInterface = getGinjectorType(typeName);
    } catch (ClassNotFoundException e) {
      this.logger.log(TreeLogger.ERROR, String.format("Unable to load ginjector type [%s], "
          + "maybe you haven't compiled your client java sources?", typeName), e);
      throw new UnableToCompleteException();
    } catch (IllegalArgumentException e) {
      this.logger.log(TreeLogger.Type.ERROR, e.getMessage(), e);
      throw new UnableToCompleteException();
    }

    // This is the Injector we use for the Generator internally,
    // it has nothing to do with user code.
    Module module = new GinjectorGeneratorModule(logger, context, ginjectorInterface,
        getModuleClasses(ginjectorInterface));
    return Guice.createInjector(module).getInstance(GinjectorGeneratorImpl.class).generate();
  }

  /**
   * Creates a new gin-specific class loader that will load GWT and non-GWT types such that there is
   * never a conflict, especially with super source.
   *
   * @param logger logger for errors that occur during class loading
   * @param context generator context in which classes are loaded
   * @return new gin class loader
   * @see GinBridgeClassLoader
   */
  private ClassLoader createGinClassLoader(TreeLogger logger, GeneratorContext context) {
    Set exceptions = new LinkedHashSet();
    exceptions.add("com.google.inject"); // Need the non-super-source version during generation.
    exceptions.add("javax.inject"); // Need the non-super-source version during generation.
    exceptions.add("com.google.gwt.inject.client"); // Excluded to allow class-literal comparison.

    // Add any excepted packages or classes registered by other developers.
    exceptions.addAll(getValuesForProperty("gin.classloading.exceptedPackages"));
    return new GinBridgeClassLoader(context, logger, exceptions);
  }

  @SuppressWarnings("unchecked")
  // Due to deferred binding we assume that the requested class has to be a ginjector.
  private Class getGinjectorType(String requestedClass)
      throws ClassNotFoundException {

    // We choose not to initialize ginjectors since we do not require it for reflective analysis and
    // some people statically call GWT.create in them (which is illegal during Gin generator runs).
    Class type = loadClass(requestedClass, false);
    if (!Ginjector.class.isAssignableFrom(type)) {
      throw new IllegalArgumentException("The type passed does not inherit from Ginjector - "
          + "please check the deferred binding rules.");
    }

    return (Class) type;
  }

  private Set> getModuleClasses(Class ginjectorType)
      throws UnableToCompleteException {
    Set> ginModules = new LinkedHashSet>();
    getPropertyModuleClasses(ginjectorType, ginModules);
    getModuleClassesFromInjectorInterface(ginjectorType, ginModules);

    if (ginModules.isEmpty() && !ginjectorType.isAnnotationPresent(NoGinModules.class)) {
      logger.log(TreeLogger.Type.WARN,
          String.format("No gin modules are annotated on Ginjector %s, "
              + "did you forget the @GinModules annotation?", ginjectorType));
    }

    return ginModules;
  }

  @SuppressWarnings("unchecked") // We check that the class is a GinModule before casting it.
  private void getPropertyModuleClasses(Class ginjectorType,
      Set> ginModules) throws UnableToCompleteException {
    Set propertyModuleNames = getPropertyModuleNames(ginjectorType);
    for (String moduleName : propertyModuleNames) {
      try {

        // Gin modules must be initialized when loading since we will instantiate it. It is
        // officially illegal to call any GWT-client code in a Gin module.
        Class ginModule = loadClass(moduleName, true);
        if (!GinModule.class.isAssignableFrom(ginModule)) {
          logger.log(TreeLogger.Type.ERROR, String.format("The gin module type [%s] does not "
              + "inherit from GinModule.", moduleName));
          throw new UnableToCompleteException();
        }
        ginModules.add((Class) ginModule);
      } catch (ClassNotFoundException e) {
        logger.log(TreeLogger.ERROR, String.format("Unable to load gin module type [%s], "
            + "maybe you haven't compiled your client java sources?", moduleName), e);
        throw new UnableToCompleteException();
      }
    }
  }

  private Set getPropertyModuleNames(Class ginjectorType)
      throws UnableToCompleteException {
    Set propertyNames = new LinkedHashSet();
    getPropertyNamesFromInjectorInterface(ginjectorType, propertyNames);

    Set configurationModuleNames = new LinkedHashSet();
    for (String propertyName : propertyNames) {
      Set moduleNames = getValuesForProperty(propertyName);
      if (moduleNames.isEmpty()) {
        logger.log(TreeLogger.Type.ERROR, String.format("The GinModules annotation requests "
            + "property %s, but this property cannot be found in the GWT module.", propertyName));
        throw new UnableToCompleteException();
      }
      configurationModuleNames.addAll(moduleNames);
    }
    return configurationModuleNames;
  }

  private Set getValuesForProperty(String propertyName) {
    try {
      // Result of getConfigurationProperty can never be null.
      return new LinkedHashSet(
          propertyOracle.getConfigurationProperty(propertyName).getValues());
    } catch (BadPropertyValueException e) {
      // Thrown when the configuration property is not defined.
      return Collections.emptySet();
    }
  }

  private void getPropertyNamesFromInjectorInterface(Class ginjectorType,
      Set propertyNames) {
    GinModules ginModulesAnnotation = ginjectorType.getAnnotation(GinModules.class);
    if (ginModulesAnnotation != null) {
      propertyNames.addAll(Arrays.asList(ginModulesAnnotation.properties()));
    }

    for (Class ancestor : ginjectorType.getInterfaces()) {
      getPropertyNamesFromInjectorInterface(ancestor, propertyNames);
    }
  }

  private void getModuleClassesFromInjectorInterface(Class ginjectorType,
      Set> moduleClasses) {
    for (Class ancestor : ginjectorType.getInterfaces()) {
      getModuleClassesFromInjectorInterface(ancestor, moduleClasses);
    }

    GinModules ginModulesAnnotation = ginjectorType.getAnnotation(GinModules.class);
    if (ginModulesAnnotation != null) {
      moduleClasses.addAll(Arrays.asList(ginModulesAnnotation.value()));
    }
  }

  /**
   * Loads the class with the given name. If the provided name is a source name
   * this method tries to find the appropriate class.
   *
   * 

For example, given a string {@code A.B.C}, this method will try to load * (in this order, returning upon found class): {@code A.B.C}, {@code A.B$C} * and {@code A$B$C}. * * @param requestedClass binary or source name of class to be loaded * @param initialize whether to initialize the loaded class or not * @return loaded class * @throws ClassNotFoundException if no class could be found for the provided name */ // Package accessible for testing. Class loadClass(String requestedClass, boolean initialize) throws ClassNotFoundException { String binaryName = requestedClass; while (true) { try { return Class.forName(binaryName, initialize, classLoader); } catch (ClassNotFoundException e) { if (!binaryName.contains(".")) { throw e; } else { binaryName = replaceLastPeriodWithDollar(binaryName); } } } } private String replaceLastPeriodWithDollar(String input) { int where = input.lastIndexOf('.'); if (where != -1) { return input.substring(0, where) + '$' + input.substring(where + 1); } else { return input; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy