
xapi.dev.inject.MagicMethods Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xapi-gwt-inject Show documentation
Show all versions of xapi-gwt-inject Show documentation
In order to implement the core XApi dependency injection in gwt,
we had to swap out the injection methods themselves with magic methods
that call into generated-on-the-fly provider classes.
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 extends Generator> generator = AsyncInjectionGenerator.class;
// (Class extends Generator>) 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 extends Generator> 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 extends Generator> 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 - 2025 Weber Informatics LLC | Privacy Policy