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

xapi.dev.inject.MagicMethods Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

The newest version!
/*
 * Copyright 2012, We The Internet Ltd.
 *
 * All rights reserved.
 *
 * Distributed under a modified BSD License as follow:
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution, unless otherwise
 * agreed to in a written document signed by a director of We The Internet Ltd.
 *
 * Neither the name of We The Internet nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package xapi.dev.inject;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.javac.StandardGeneratorContext;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.MagicMethodGenerator;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.UnifyAstView;
import com.google.gwt.dev.jjs.ast.AccessModifier;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JPermutationDependentValue;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.impl.UnifyAst;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.thirdparty.guava.common.base.Optional;
import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import xapi.dev.generators.AsyncInjectionGenerator;
import xapi.dev.generators.AsyncProxyGenerator;
import xapi.dev.generators.InstanceInjectionGenerator;
import xapi.dev.generators.SyncInjectionGenerator;
import xapi.dev.util.InjectionCallbackArtifact;
import xapi.inject.AsyncProxy;
import xapi.inject.X_Inject;
import xapi.inject.impl.SingletonProvider;

/**
 * A collection of magic method providers used for gwt production mode. These methods are mapped over top of
 * existing static methods, to allow modification / extension of GWT.create() (the original implementation is
 * jacked directly from GWT.create() in {@link UnifyAst}). Because these methods are only used in production
 * mode, the methods they override must implement the same functionality for gwt dev mode / pure java. It is
 * recommended to direct your magic methods outside of the class from which they are called; the contents of
 * the class they are sourced from will be included in production gwt compiles, so simply delegate your static
 * methods from X_Impl.someMethod -> Jre_Impl.someMethod, and do your work there. In order to inject your own
 * magic methods, simply include xapi.X_Inject in your gwt module, then add the following (assuming
 * your method is:  This value string is a little monsterous, but it simple maps the type signature of the method you want
 * to replace, with the method you will use to build the AST. The type signature of your static injection
 * methods must match the supplied signature of
 * {@link MagicMethodGenerator#injectMagic(TreeLogger, JMethodCall, JMethod, Context, UnifyAstView)}, though you may
 * name your methods anything you please, and must return a JExpression or throw an exception. Returning null
 * will allow your code to compile, but you will hit mysterious null.nullMethod() errors. (in other words,
 * throw an exception instead of return null; unless you love hours of debugging.)
 *
 * @author James X. Nelson ([email protected], @james)
 */
public class MagicMethods {

  /**
   * Replaces a call from {@link X_Inject#singletonAsync(Class, xapi.util.api.ReceivesValue)} by first a)
   * generating an async provider, and then b) sending the value receiver into the async provider as a
   * callback. See the {@link AsyncProxy} class and {@link AsyncInjectionGenerator} for implementation.
   *
   * @param logger - The logger to log to.
   * @param methodCall - The method call we are overwriting
   * @param currentMethod - The encapsulated method itself
   * @param context - The method call context, so you can insert clinits / whatnot
   * @param ast - A view over UnifyAst, exposing our basic needs
   * @return - A JExpression to replace this method call with
   * @throws UnableToCompleteException
   */
  public static JExpression rebindSingletonAsync(final TreeLogger logger, final JMethodCall methodCall,
    final JMethod currentMethod, final Context context, final UnifyAstView ast) throws UnableToCompleteException {
    assert (methodCall.getArgs().size() == 2);
    final JExpression classParam = methodCall.getArgs().get(0);
    final JExpression receiverParam = methodCall.getArgs().get(1);
    if (!(classParam instanceof JClassLiteral)) {
      ast.error(
        methodCall,
        "Only class literals may be used as arguments to X_Inject.singletonAsync; you sent " +
          classParam.getClass() + " - " + classParam);
      return null;
    }

    logger.log(logLevel(),
      receiverParam.toString() + " : " + receiverParam.getClass() + " : " + receiverParam.toSource());
    final JClassLiteral classLiteral = (JClassLiteral)classParam;
    final JDeclaredType answerType = injectSingletonAsync(logger, classLiteral, methodCall, ast);

    final JDeclaredType receiverType = ast.searchForTypeBySource("xapi.util.api.ReceivesValue");
    for (final JMethod method : receiverType.getMethods()) {
      if (method.getName().equals("set")) {

        final SourceInfo info = methodCall.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
        final Optional result = newInstance(logger, info, ast, answerType);
        if (result.isPresent()) {
          final JMethodCall call = new JMethodCall(info, result.get(), method);
          call.addArg(receiverParam);
          if (logger.isLoggable(logLevel())) {
            final TreeLogger branch = logger.branch(logLevel(), "Generated asynchronous magic singleton: ");
            for (final String str : call.toSource().split("\n")) {
              branch.log(logLevel(), str);
            }
          }
          return call;
        } else {
          ast.error(methodCall, "Rebind result '" + answerType +
            "' has no default (zero argument) constructors");
          return null;
        }
      }
    }
    throw new InternalCompilerException("Unable to generate asynchronous class injector");
  }

  private static Type logLevel() {
    return Type.DEBUG;
  }

  private static JDeclaredType injectSingletonAsync(final TreeLogger logger, final JClassLiteral classLiteral,
    final JMethodCall methodCall, final UnifyAstView ast) throws UnableToCompleteException {

    final JDeclaredType type = (JDeclaredType)classLiteral.getRefType();
    final String[] names = type.getShortName().split("[$]");
    // TODO: stop stripping the enclosing class name (need to update generators)
    // String reqType = JGwtCreate.nameOf(type);

    String answer = classLiteral.getRefType().getName();
    answer = answer.substring(0, answer.lastIndexOf('.') + 1) + "impl.AsyncFor_" + names[names.length - 1];
    JDeclaredType answerType = null;
    final JDeclaredType knownType = ast.getProgram().getFromTypeMap(answer);

    if (knownType != null) {
      return ast.searchForTypeBySource(answer);
    } else {// we need to generate the singleton on the fly, without updating rebind cache
      final StandardGeneratorContext ctx = ast.getRebindPermutationOracle().getGeneratorContext();
      // make sure the requested interface is compiled for the generator
      ast.searchForTypeBySource(type.getName());
      try {
        // our hardcoded class is definitely a generator ;-}
        final Class generator = AsyncInjectionGenerator.class;
        // (Class) Class
        // .forName("xapi.dev.generators.AsyncInjectionGenerator");
        // creates the singleton and provider
        final RebindResult rebindResult = ctx.runGeneratorIncrementally(logger, generator, type.getName());
        // commit the generator result, w/out updating rebind cache (to allow GWT.create() rebinds)
        ctx.finish(logger);
        // pull back the LazySingeton provider
        answerType = ast.searchForTypeBySource(rebindResult.getResultTypeName());
        // sanity check
        if (answerType == null) {
          ast.error(methodCall, "Rebind result '" + answer + "' could not be found.  Please be sure that " +
            type.getName() +
            " has a subclass on the classpath which contains @SingletonOverride or @SingletonDefault annotations.");
          throw new UnableToCompleteException();
        }

      } catch (final UnableToCompleteException e) {
        logger.log(Type.ERROR, "Error trying to generator provider for " + type.getName() + ". " +
          "\nPlease make sure this class is non-abstract, or that a concrete class on the classpath " +
          "is annotated with @SingletonOverride or @SingletonDefault", e);
        ast.error(methodCall, "Rebind result '" + answer + "' could not be found");
        throw new UnableToCompleteException();
      }
    }
    if (!(answerType instanceof JClassType)) {
      ast.error(methodCall, "Rebind result '" + answer + "' must be a class");
      throw new UnableToCompleteException();
    }
    if (answerType.isAbstract()) {
      ast.error(methodCall, "Rebind result '" + answer + "' cannot be abstract");
      throw new UnableToCompleteException();
    }
    logger.log(logLevel(), "Injecting asynchronous singleton for " + type.getName() + " -> " + answerType);
    return answerType;
  }

  /**
   * Replaces a call from {@link X_Inject#singletonAsync(Class, xapi.util.api.ReceivesValue)} by first a)
   * generating an async provider, and then b) sending the value receiver into the async provider as a
   * callback. See the {@link AsyncProxy} class and {@link AsyncInjectionGenerator} for implementation.
   *
   * @param logger - The logger to log to.
   * @param methodCall - The method call we are overwriting
   * @param currentMethod - The encapsulated method itself
   * @param context - The method call context, so you can insert clinits / whatnot
   * @param ast - A view over UnifyAst, exposing our basic needs
   * @return - A JExpression to replace this method call with
   * @throws UnableToCompleteException
   */
  public static JExpression rebindSingletonAndCallback(final TreeLogger logger, final JMethodCall methodCall,
    final JMethod currentMethod, final Context context, final UnifyAstView ast) throws UnableToCompleteException {
    assert (methodCall.getArgs().size() == 2);
    final JExpression classParam = methodCall.getArgs().get(0);
    final JExpression receiveParam = methodCall.getArgs().get(1);
    if (!(classParam instanceof JClassLiteral)) {
      ast.error(
        methodCall,
        "Only class literals may be used as arguments to X_Inject.singletonAsync; you sent " +
          classParam.getClass() + " - " + classParam);
      return null;
    }
    logger.log(logLevel(),
      receiveParam.toString() + " : " + receiveParam.getClass() + " : " + receiveParam.toSource());
    final JClassLiteral classLiteral = (JClassLiteral)classParam;
    final JClassLiteral receiverLiteral = (JClassLiteral)receiveParam;

    final JDeclaredType type = (JDeclaredType)classLiteral.getRefType();
    final String[] names = type.getShortName().split("[$]");
    // TODO: stop stripping the enclosing class name (need to update generators)
    // String reqType = JGwtCreate.nameOf(type);

    String answer = receiverLiteral.getRefType().getName();
    answer = answer.substring(0, answer.lastIndexOf('.') + 1) + "impl.AsyncProxy_" + names[names.length - 1];
    JDeclaredType answerType = null;
    final JDeclaredType knownType = ast.getProgram().getFromTypeMap(answer);

    // ensure we have a service provider
    final JDeclaredType provider = injectSingletonAsync(logger, classLiteral, methodCall, ast);
    final StandardGeneratorContext ctx = ast.getRebindPermutationOracle().getGeneratorContext();
    // ctx.finish(logger);

    if (knownType != null) {// if the singleton already exists, just use it
      answerType = ast.searchForTypeBySource(answer);
      // result =
      // JGwtCreate.createInstantiationExpression(methodCall.getSourceInfo(), (JClassType) answerType,
      // currentMethod.getEnclosingType());
    } else {// we need to generate the singleton on the fly, without updating rebind cache
      // make sure the requested interface is compiled for the generator
      logger.log(logLevel(), "Rebinding singleton w/ callback: " + type + " -> " + provider.getName());
      ast.searchForTypeBySource(type.getName());
      ast.searchForTypeBySource(BinaryName.toSourceName(provider.getName()));
      try {
        InjectionCallbackArtifact rebindResult;
        try {
          logger.log(logLevel(), "Loading injected result: " + provider.getName());
          rebindResult = AsyncProxyGenerator.setupAsyncCallback(logger, ctx,
            ctx.getTypeOracle().findType(BinaryName.toSourceName(type.getName())),
            ((JDeclaredType)receiverLiteral.getRefType()));
        } catch (final ClassNotFoundException e) {
          e.printStackTrace();
          throw new UnableToCompleteException();
        }
        // creates the singleton and provider
        // RebindResult rebindResult =
        // ctx.runGeneratorIncrementally(logger, generator, type.getName());
        // commit the generator result, w/out updating rebind cache (to allow GWT.create() rebinds)
        ctx.finish(logger);
        // pull back the LazySingeton provider
        answerType = ast.searchForTypeBySource(rebindResult.getAsyncInjectionName());
        // sanity check
        if (answerType == null) {
          ast.error(methodCall, "Rebind result '" + answer + "' could not be found.  Please be sure that " +
            type.getName() +
            " has a subclass on the classpath which contains @SingletonOverride or @SingletonDefault annotations.");
          return null;
        }
      } catch (final UnableToCompleteException e) {
        logger.log(Type.ERROR, "Error trying to generator provider for " + type.getName() + ". " +
          "\nPlease make sure this class is non-abstract, or that a concrete class on the classpath " +
          "is annotated with @SingletonOverride or @SingletonDefault", e);
        ast.error(methodCall, "Rebind result '" + answer + "' could not be found");
        return null;
      }
    }

    for (final JMethod method : answerType.getMethods()) {
      if (method.getName().equals("go")) {
        // JExpression inst = JGwtCreate.createInstantiationExpression(answerType.getSourceInfo(),
        // (JClassType)answerType, answerType.getEnclosingType());
        return new JMethodCall(method.getSourceInfo(), null, method);
      }
    }
    throw new InternalCompilerException("Did not generate async proxy for " + answerType);
  }

  /**
   * Replaces a call from {@link X_Inject#singletonLazy(Class)} by first a-0) generating a provider which will
   * be synchronous if an async call hasn't already been made, or a-1) generating a provider which will route
   * through the async provider, and return null before inited. then b) returning a simple lazy provider which
   * will poll the actual provider until it is set. If you use the
   * {@link X_Inject#singletonAsync(Class, xapi.util.api.ReceivesValue)} once, you should not use the
   * other two synchronous provider methods, as they may return null if you happen to request them before the
   * code split containing the service is downloaded.
   *
   * @param logger - The logger to log to.
   * @param methodCall - The method call we are overwriting
   * @param currentMethod - The encapsulated method itself
   * @param context - The method call context, so you can insert clinits / whatnot
   * @param ast - A view over UnifyAst, exposing our basic needs
   * @return - A JExpression to replace this method call with
   * @throws UnableToCompleteException
   */

  public static JExpression rebindSingletonLazy(final TreeLogger logger, final JMethodCall x, final JMethod currentMethod,
    final Context context, final UnifyAstView ast) throws UnableToCompleteException {
    assert (x.getArgs().size() == 1);
    final JExpression arg = x.getArgs().get(0);
    if (!(arg instanceof JClassLiteral)) {
      ast.error(x,
        "Only class literals may be used as arguments to X_Inject.lazySingleton; you sent " + arg.getClass() +
          " - " + arg);
      return null;
    }
    final JClassLiteral classLiteral = (JClassLiteral)arg;
    if (!(classLiteral.getRefType() instanceof JDeclaredType)) {
      ast.error(x, "Only classes and interfaces may be used as arguments to X_Inject.singletonLazy()");
      return null;
    }
    return injectLazySingleton(logger, classLiteral, x, currentMethod.getEnclosingType(), ast);
  }

  private static JExpression injectLazySingleton(final TreeLogger logger, final JClassLiteral classLiteral, final JNode x,
    final JDeclaredType enclosingType, final UnifyAstView ast) throws UnableToCompleteException {
    final JDeclaredType type = (JDeclaredType)classLiteral.getRefType();
    final String[] names = type.getShortName().split("[$]");
    // TODO: stop stripping the enclosing class name (need to update generators)
    // String reqType = JGwtCreate.nameOf(type);

    String answer = classLiteral.getRefType().getName();
    answer = answer.substring(0, answer.lastIndexOf('.') + 1) + "impl.SingletonFor_" +
      names[names.length - 1];
    JDeclaredType answerType = null;
    final JDeclaredType knownType = ast.getProgram().getFromTypeMap(answer);
    if (knownType != null) {// if the singleton already exists, just use it
      answerType = ast.searchForTypeBySource(answer);
    } else {// we need to generate the singleton on the fly, without updating rebind cache
      final StandardGeneratorContext ctx = ast.getRebindPermutationOracle().getGeneratorContext();
      // make sure the requested interface is compiled for the generator
      ast.searchForTypeBySource(type.getName());
      try {
        // our hardcoded class is definitely a generator ;-}
        final Class generator = SyncInjectionGenerator.class;
        // creates the singleton and provider
        final RebindResult result = ctx.runGeneratorIncrementally(logger, generator, type.getName());
        // commit the generator result, w/out updating rebind cache (to allow GWT.create() rebinds)
        ctx.finish(logger);
        // pull back the LazySingeton provider
        logger.log(logLevel(), "Loading injected result: " + result.getResultTypeName());
        answerType = ast.searchForTypeBySource(result.getResultTypeName());
        // sanity check
        if (answerType == null) {
          ast.error(x, "Rebind result '" + answer + "' could not be found");
          return null;
        }
      } catch (final UnableToCompleteException e) {
        logger.log(Type.ERROR, "Error trying to generator provider for " + type.getName() + ". " +
          "\nPlease make sure this class is non-abstract, or that a concrete class on the classpath " +
          "is annotated with @SingletonOverride or @SingletonDefault", e);
        ast.error(x, "Rebind result '" + answer + "' could not be found");
        return null;
      }
    }
    if (!(answerType instanceof JClassType)) {
      ast.error(x, "Rebind result '" + answer + "' must be a class");
      return null;
    }
    if (answerType.isAbstract()) {
      ast.error(x, "Rebind result '" + answer + "' cannot be abstract");
      return null;
    }
    logger.log(logLevel(), "Injecting lazy singleton for " + type.getName() + " -> " + answerType);
    final Optional result = newInstance(logger, x.getSourceInfo(), ast, answerType);
    if (result.isPresent()) {
      return result.get();
    } else {
      ast.error(x, "Rebind result '" + answer + "' has no default (zero argument) constructors");
      return null;
    }
  }

  /**
   * Replaces a call from {@link X_Inject#singleton(Class)} by first a-0) generating a provider which will be
   * synchronous if an async call hasn't already been made, or a-1) generating a provider which will route
   * through the async provider, and return null before inited. then b) creates a lazy provider to call into
   * the synchronous provider finally c) calls .get() on the provider and return the value. If you use the
   * {@link X_Inject#singletonAsync(Class, xapi.util.api.ReceivesValue)} once, you should not use the
   * other two synchronous provider methods, as they may return null if you happen to request them before the
   * code split containing the service is downloaded.
   *
   * @param logger - The logger to log to.
   * @param methodCall - The method call we are overwriting
   * @param currentMethod - The encapsulated method itself
   * @param context - The method call context, so you can insert clinits / whatnot
   * @param ast - A view over UnifyAst, exposing our basic needs
   * @return - A JExpression to replace this method call with
   * @throws UnableToCompleteException
   */
  public static JExpression rebindSingleton(final TreeLogger logger, final JMethodCall x, final JMethod currentMethod,
    final Context context, final UnifyAstView ast) throws UnableToCompleteException {
    assert (x.getArgs().size() == 1);
    final JExpression arg = x.getArgs().get(0);
    if (!(arg instanceof JClassLiteral)) {
      ast.error(x,
        "Only class literals may be used as arguments to X_Inject.lazySingleton; you sent " + arg.getClass() +
          " - " + arg);
      return null;
    }
    final JClassLiteral classLiteral = (JClassLiteral)arg;
    return injectSingleton(logger, classLiteral, x, ast);
  }

  private static final Map cachedProviders = new IdentityHashMap();

  private static JExpression injectSingleton(TreeLogger logger, final JClassLiteral classLiteral, final JNode x,
    final UnifyAstView ast) throws UnableToCompleteException {
    // check for cached result.

    // inject our provider class
    final JDeclaredType type = (JDeclaredType)classLiteral.getRefType();
    if (cachedProviders.containsKey(type)) {
      return cachedProviders.get(type);
    }
    JExpression expr = injectLazySingleton(logger, classLiteral, x, type, ast);
    final String[] names = type.getShortName().split("[$]");

    String answer = classLiteral.getRefType().getName();
    answer = answer.substring(0, answer.lastIndexOf('.') + 1) + "impl.SingletonFor_" +
      names[names.length - 1];

    final JDeclaredType enclosing = ast.searchForTypeBySource(answer);
    final JDeclaredType lazyProvider = ast.searchForTypeBySource(SingletonProvider.class.getName());
    for (final JMethod method : lazyProvider.getMethods()) {
      if (method.getName().equals("get")) {
        // Create a new method for each singleton to access the desired provider
        SourceInfo info = null;
        JMethod getSingleton = null;
        final String targetName = "singleton" + type.getShortName().replaceAll("[$]", "_");
        for (final JMethod existingMethod : enclosing.getMethods()) {
          if (existingMethod.getName().equals(existingMethod)) {
            getSingleton = existingMethod;
            info = getSingleton.getSourceInfo();
            logger.log(logLevel(), "Reusing generated method " + getSingleton.toSource());
            break;
          }
        }
        if (getSingleton == null) {

          info = expr.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
          final JMethodBody body = new JMethodBody(info);
          getSingleton = new JMethod(info, targetName, enclosing, type, false, true, true,
            AccessModifier.PRIVATE);
          // insert our generated method into the enclosing type; needed for super dev mode
          enclosing.addMethod(getSingleton);

          // freeze this method
          getSingleton.setBody(body);
          getSingleton.freezeParamTypes();
          getSingleton.setSynthetic();
          info.addCorrelation(info.getCorrelator().by(getSingleton));

          final JMethodCall call = new JMethodCall(info, expr, method);
          final JReturnStatement value = new JReturnStatement(x.getSourceInfo(), call);
          if (enclosing.getClinitTarget() != null) {
            final JDeclaredType clinit = enclosing.getClinitTarget();
            final JMethod clinitMethod = clinit.getMethods().get(0);
            assert (JProgram.isClinit(clinitMethod));
            final JMethodCall doClinit = new JMethodCall(clinit.getSourceInfo(), null, clinitMethod);
            body.getBlock().addStmt(doClinit.makeStatement());
          }
          body.getBlock().addStmt(value);
          if (logger.isLoggable(Type.DEBUG)) {
            logger = logger.branch(Type.DEBUG, "Generated magic singleton: ");
            for (final String str : getSingleton.toSource().split("\n")) {
              logger.branch(Type.DEBUG, str);
            }
          }
        }
        expr = new JMethodCall(info, null, getSingleton);
        cachedProviders.put(type, expr);
        return expr;
      }
    }
    throw new InternalCompilerException("Unable to generate synchronous injected class access");
  }

  public static  JExpression rebindNewInstance(final TreeLogger logger, final JMethodCall x, final JMethod currentMethod,
    final Context context, final UnifyAstView ast) {
//    JExpression instance = x.getInstance();
//    JMethod target = x.getTarget();
//    JExpressionStatement type = instance.makeStatement();
//    String source = type.toSource();
//    JExpression expr = type.getExpr();
//    JType exprType = expr.getType();
//    JExpressionStatement statement = x.makeStatement();
    return x;
  }

  /**
   * Replaces a call from {@link X_Inject#singleton(Class)} by first a-0) generating a provider which will be
   * synchronous if an async call hasn't already been made, or a-1) generating a provider which will route
   * through the async provider, and return null before inited. then b) creates a lazy provider to call into
   * the synchronous provider finally c) calls .get() on the provider and return the value. If you use the
   * {@link X_Inject#singletonAsync(Class, xapi.util.api.ReceivesValue)} once, you should not use the
   * other two synchronous provider methods, as they may return null if you happen to request them before the
   * code split containing the service is downloaded.
   *
   * @param logger - The logger to log to.
   * @param methodCall - The method call we are overwriting
   * @param currentMethod - The encapsulated method itself
   * @param context - The method call context, so you can insert clinits / whatnot
   * @param ast - A view over UnifyAst, exposing our basic needs
   * @return - A JExpression to replace this method call with
   * @throws UnableToCompleteException
   */

  public static JExpression rebindInstance(final TreeLogger logger, final JMethodCall x,
      final JMethod currentMethod, final Context context, final UnifyAstView ast) throws UnableToCompleteException {
    assert (x.getArgs().size() == 1);
    JExpression arg = x.getArgs().get(0);

    if (!(arg instanceof JClassLiteral)) {
      //uh-oh; our class argument isn't actually a literal.
      //it may be a reference to a magic class,
      //in which case it will have java.lang.Class as a supertype.

      //our search semantics (for methods) are as follows:
      //first check the first arguments of methods for magic class or class lit.
      //if one is found, emit log and use it
      //if not, throw UnableToComplete (TODO a xinject.strict flag to disallow type search)

      if (arg instanceof JVariableRef) {
        final JVariableRef local = (JVariableRef)arg;
        final JExpression init = local.getTarget().getDeclarationStatement().initializer;
        if (init instanceof JVariableRef) {
          final JVariableRef ref = (JVariableRef)init;
          final String fromSourceInfo = ref.getSourceInfo().getFileName()
            .replace("gen/","").replace("_MC.java", "")
            .replaceAll("/",".");

          JDeclaredType type;
          //TODO error handling
          type = ast.searchForTypeBySource(fromSourceInfo);
          arg = new JClassLiteral(type.getSourceInfo(), type);
        }
      } else if (arg instanceof JMethodCall) {
        final JMethodCall call = (JMethodCall)arg;
        System.out.println(call.getType());
        arg = call.getArgs().get(0);
      }
      if (!(arg instanceof JClassLiteral)) {
        logger.log(Type.ERROR, "Could not generate X_Inject.instance for "+arg.getType().getName());
      }
    }
    final JClassLiteral classLiteral = (JClassLiteral) arg;
    return injectInstance(logger, classLiteral, x, currentMethod, ast);
  }

  private static JExpression injectInstance(final TreeLogger logger, final JClassLiteral classLiteral,
    final JMethodCall x, final JMethod method, final UnifyAstView ast) throws UnableToCompleteException {
    final JDeclaredType type = (JDeclaredType)classLiteral.getRefType();
    // JExpression expr = injectLazySingleton(logger, classLiteral, x, type, ast);
    // String[] names = type.getShortName().split("[$]");

    final String instanceType = classLiteral.getRefType().getName();
    final StandardGeneratorContext ctx = ast.getRebindPermutationOracle().getGeneratorContext();
    // make sure the requested interface is compiled for the generator
    ast.searchForTypeBySource(type.getName());
    JDeclaredType injectedInstance;
    try {
      // our hardcoded class is definitely a generator ;-}
      final Class generator = InstanceInjectionGenerator.class;
      // creates the singleton and provider
      final RebindResult result = ctx.runGeneratorIncrementally(logger, generator, type.getName());
      // commit the generator result, w/out updating rebind cache (to allow GWT.create() rebinds)
      ctx.finish(logger);
      // pull back the LazySingeton provider
      logger.log(logLevel(), "Loading injected result: " + result.getResultTypeName());
      injectedInstance = ast.searchForTypeBySource(result.getResultTypeName());
      // sanity check
      if (injectedInstance == null) {
        ast.error(x, "Rebind result '" + instanceType + "' could not be found");
        throw new InternalCompilerException("Unable to generate instance provider");
      }
    } catch (final UnableToCompleteException e) {
      logger.log(Type.ERROR, "Error trying to generate provider for " + type.getName() + ". " +
        "\nPlease make sure this class is non-abstract, or that a concrete class on the classpath " +
        "is annotated with @SingletonOverride or @SingletonDefault", e);
      ast.error(x, "Rebind result '" + instanceType + "' could not be found");
      throw new InternalCompilerException("Unable to generate instance provider");
    }
    if (!(injectedInstance instanceof JClassType)) {
      ast.error(x, "Rebind result '" + instanceType + "' must be a class");
      throw new InternalCompilerException("Unable to generate instance provider");
    }
    if (injectedInstance.isAbstract()) {
      ast.error(x, "Rebind result '" + instanceType + "' cannot be abstract");
      throw new InternalCompilerException("Unable to generate instance provider");
    }
    // now that we have our injected answer,
    // let's run it through normal gwt deferred binding as well

    // copied from UnifyAst#handleGwtCreate
    final String reqType = BinaryName.toSourceName(injectedInstance.getName());
    List answers;
    try {
      answers = Lists.create(ast.getRebindPermutationOracle().getAllPossibleRebindAnswers(logger, reqType));
      ctx.finish(logger);
    } catch (final UnableToCompleteException e) {
      ast.error(x, "Failed to resolve '" + reqType + "' via deferred binding");
      return null;
    }
    final ArrayList instantiationExpressions = new ArrayList(answers.size());
    for (final String answer : answers) {
      final JDeclaredType answerType = ast.searchForTypeBySource(answer);
      if (answerType == null) {
        ast.error(x, "Rebind result '" + answer + "' could not be found");
        return null;
      }
      if (!(answerType instanceof JClassType)) {
        ast.error(x, "Rebind result '" + answer + "' must be a class");
        return null;
      }
      if (answerType.isAbstract()) {
        ast.error(x, "Rebind result '" + answer + "' cannot be abstract");
        return null;
      }
      JDeclaredType enclosing = injectedInstance.getEnclosingType();
      if (enclosing == null) {
        enclosing = method.getEnclosingType();
      }
      final Optional ctor = newInstance(logger, x.getSourceInfo(), ast, answerType);
      if (ctor.isPresent()) {
        instantiationExpressions.add(ctor.get());
      } else {
        ast.error(x, "Rebind result '" + answer + "' has no default (zero argument) constructors");
        return null;
      }
    }
    assert answers.size() == instantiationExpressions.size();
    if (answers.size() == 1) {
      return instantiationExpressions.get(0);
    } else {
      return JPermutationDependentValue.createTypeRebind(ast.getProgram(),
          x.getSourceInfo(), reqType, answers, instantiationExpressions);

    }

    // TODO: cache each injection to detect the first time a class is injected,
    // then see if the given injection target is Preloadable,
    // so we can call it's clinit before it is ever accessed,
    // to reduce the bloat of clinits by visiting preloadable methods before
    // any client code can possibly access them (less clinit in runtime code)

  }

  private static Optional newInstance(final TreeLogger logger, final SourceInfo sourceInfo, final UnifyAstView ast,
      final JDeclaredType answerType) {
    final Optional ctor = FluentIterable.from(answerType.getMethods()).firstMatch((m)->m.isConstructor() && m.getParams().size() == 0);
    if (ctor.isPresent()) {
      final JNewInstance result = new JNewInstance(sourceInfo, (JConstructor)ctor.get());
      return Optional.of(result);
    } else {
      return Optional.absent();
    }
  }

  public static String toSourceName(final JDeclaredType type) {
    final JDeclaredType enclosing = type.getEnclosingType();
    if (enclosing == null) {
      return type.getName();
    }
    return toSourceName(enclosing) + "." + type.getShortName();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy