
com.google.gwt.inject.rebind.util.MethodCallUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gin Show documentation
Show all versions of gin Show documentation
GIN (GWT INjection) brings automatic dependency injection to Google Web Toolkit client-side code. GIN is built on top of Guice and uses (a subset of) Guice's binding language.
/*
* 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.util;
import static com.google.gwt.inject.rebind.util.SourceWriteUtil.join;
import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.gwt.inject.rebind.reflect.NoSourceNameException;
import com.google.gwt.inject.rebind.reflect.ReflectUtil;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Utility code to create method and constructor calls.
*/
public class MethodCallUtil {
/**
* Creates a constructor injecting method and returns a string that invokes
* the new method. The new method returns the constructed object.
*
* @param constructor constructor to call
* @param nameGenerator NameGenerator to be used for ensuring method name uniqueness
* @param methodsOutput a list where all new methods created by this
* call are added
* @return source snippet calling the generated method
*/
public SourceSnippet createConstructorInjection(
MethodLiteral, Constructor>> constructor, NameGenerator nameGenerator,
List methodsOutput) throws NoSourceNameException {
return createMethodCallWithInjection(constructor, null, nameGenerator, methodsOutput);
}
/**
* Creates a method that calls the passed method, injecting its parameters
* using getters, and returns a string that invokes the new method. The new
* method returns the passed method's return value, if any. If a method
* without parameters is provided, that method will be called and no
* parameters will be passed.
*
* @param method method to call (can be constructor)
* @param invokeeName expression that evaluates to the object on which
* the method is to be called. If null the method will be called
* in the current scope.
* @param nameGenerator NameGenerator to be used for ensuring method name uniqueness
* @param methodsOutput a list where all new methods created by this
* call are added
* @return source snippet calling the generated method
*/
public SourceSnippet createMethodCallWithInjection(MethodLiteral, ?> method,
String invokeeName, NameGenerator nameGenerator, List methodsOutput)
throws NoSourceNameException {
String[] params = new String[method.getParameterTypes().size()];
return createMethodCallWithInjection(method, invokeeName, params, nameGenerator,
methodsOutput);
}
/**
* Creates a method that calls the passed method, injecting its parameters
* using getters as necessary, and returns a string that invokes the new
* method. The new method returns the passed method's return value, if any.
* If a method without parameters is provided, that method will be called and
* no parameters will be passed. If the passed method declared any checked
* exceptions, the generated method will catch and rethrow those as
* {@link com.google.gwt.inject.client.CreationException}.
*
* @param method method to call (can be constructor)
* @param invokeeName expression that evaluates to the object on which
* the method is to be called. If null the method will be called
* in the current scope.
* @param parameterNames array with parameter names that can replace getter
* methods (usually used to fetch injected values) in the returned
* string. The array length must match the number of method
* parameters. A {@code null} value denotes that the getter method
* should be used.
* @param nameGenerator NameGenerator to use for ensuring method name uniqueness
* @param methodsOutput a list where all new methods created by this
* call are added
* @return string calling the generated method
*/
public SourceSnippet createMethodCallWithInjection(MethodLiteral, ?> method,
String invokeeName, String[] parameterNames, NameGenerator nameGenerator,
List methodsOutput) throws NoSourceNameException {
boolean hasInvokee = invokeeName != null;
boolean useNativeMethod = method.isPrivate() ||
ReflectUtil.isPrivate(method.getDeclaringType());
boolean isThrowing = hasCheckedExceptions(method);
// Determine method signature parts.
String invokeeTypeName = ReflectUtil.getSourceName(method.getRawDeclaringType());
int invokerParamCount = method.getParameterTypes().size() + (hasInvokee ? 1 : 0);
TypeLiteral> returnType = method.getReturnType();
String returnTypeString = ReflectUtil.getSourceName(returnType);
boolean returning = !returnType.getRawType().equals(Void.TYPE);
String invokerMethodName = getInvokerMethodName(method, nameGenerator);
// The invoker method is placed in the fragment of the package that declares
// the method, so it has access to the same package-private types as the
// method declaration.
String invokerPackageName;
if (useNativeMethod && !hasInvokee) {
// In this case, the type of the invokee is not mentioned by the type
// signature of the invoker, and since we're using native code, we can
// write a call to the target method even if the invokee is fully private.
//
// This handles the case of a user statically injecting a private inner
// class.
//
// Pick a package somewhat arbitrarily; since we're using native code, it
// doesn't really matter where it goes. The declaring type's package is
// easy to get to:
invokerPackageName = method.getDeclaringType().getRawType().getPackage().getName();
} else {
invokerPackageName = ReflectUtil.getUserPackageName(method.getDeclaringType());
// TODO(dburrows): won't this silently fail if some *parameters* to the
// invokee have limited visibility? Currently I believe that we'll just
// generate noncompiling code.
}
methodsOutput.add(createInvoker(invokeeName, invokeeTypeName, hasInvokee, useNativeMethod,
isThrowing, invokerMethodName, invokerPackageName, invokerParamCount, method,
returnTypeString, returning, isLongAccess(method)));
return new InvokerCall(hasInvokee, invokeeName, invokerMethodName, invokerPackageName,
invokerParamCount, method, parameterNames);
}
/**
* Check whether a method needs to have Long access.
*/
private boolean isLongAccess(MethodLiteral, ?> method) {
boolean result = method.getReturnType().getRawType().equals(Long.TYPE);
for (TypeLiteral> paramLiteral : method.getParameterTypes()) {
result |= paramLiteral.getRawType().equals(Long.TYPE);
}
return result;
}
private static final class InvokerCall implements SourceSnippet {
private final boolean hasInvokee;
private final String invokeeName;
private final String invokerMethodName;
private final String invokerPackageName;
private final int invokerParamCount;
private final MethodLiteral, ?> method;
private final String[] parameterNames;
public InvokerCall(boolean hasInvokee, String invokeeName, String invokerMethodName,
String invokerPackageName, int invokerParamCount, MethodLiteral, ?> method,
String[] parameterNames) {
this.hasInvokee = hasInvokee;
this.invokeeName = invokeeName;
this.invokerMethodName = invokerMethodName;
this.invokerPackageName = invokerPackageName;
this.invokerParamCount = invokerParamCount;
this.method = method;
this.parameterNames = parameterNames;
}
@Override
public String getSource(InjectorWriteContext writeContext) {
// Collect method parameters to be passed to the actual method.
List invokerCallParams = new ArrayList(invokerParamCount);
if (hasInvokee) {
invokerCallParams.add(invokeeName);
}
int paramCount = 0;
for (Key> paramKey : method.getParameterKeys()) {
String paramName = ReflectUtil.formatParameterName(paramCount);
if (parameterNames[paramCount] != null) {
invokerCallParams.add(parameterNames[paramCount]);
} else {
invokerCallParams.add(writeContext.callGetter(paramKey));
}
paramCount++;
}
return writeContext.callMethod(invokerMethodName, invokerPackageName, invokerCallParams)
+ ";";
}
}
/**
* Create an invoker method. See {@link #createMethodCallWithInjection}.
*/
private InjectorMethod createInvoker(String invokeeName, String invokeeTypeName,
boolean hasInvokee, boolean isNative, boolean isThrowing, String invokerMethodName,
String invokerPackageName, int invokerParamCount, MethodLiteral, ?> method,
String returnTypeString, boolean returning, boolean isLongAccess)
throws NoSourceNameException {
List invokerSignatureParams = new ArrayList(invokerParamCount);
if (hasInvokee) {
invokerSignatureParams.add(invokeeTypeName + " invokee");
}
List invokeeCallParams = new ArrayList(method.getParameterTypes().size());
int paramCount = 0;
for (Key> paramKey : method.getParameterKeys()) {
String paramName = ReflectUtil.formatParameterName(paramCount);
// We cannot use the type literal of the key here: It is canonicalized
// during key creation, destroying some information, for example
// auto-boxing any primitives. This leads to type-mismatches when calling
// into JSNI. Instead we'll access the parameter's original type.
TypeLiteral> paramLiteral = method.getParameterTypes().get(paramCount);
invokerSignatureParams.add(ReflectUtil.getSourceName(paramLiteral) + " " + paramName);
invokeeCallParams.add(paramName);
paramCount++;
}
String annotation = isLongAccess ? "@com.google.gwt.core.client.UnsafeNativeLong " : "";
String invokerSignature = annotation + "public " + (isNative ? "native " : "")
+ returnTypeString + " " + invokerMethodName + "(" + join(", ", invokerSignatureParams)
+ ")";
return new InvokerMethod(hasInvokee, invokeeCallParams, invokeeTypeName, invokerPackageName,
invokerSignature, isNative, isThrowing, method, returning, returnTypeString);
}
private static final class InvokerMethod extends AbstractInjectorMethod {
private final boolean hasInvokee;
private final List invokeeCallParams;
private final String invokeeTypeName;
private final boolean isThrowing;
private final MethodLiteral, ?> method;
private final boolean returning;
private final String returnTypeString;
public InvokerMethod(boolean hasInvokee, List invokeeCallParams, String invokeeTypeName,
String invokerPackageName, String invokerSignature, boolean isNative, boolean isThrowing,
MethodLiteral, ?> method, boolean returning, String returnTypeString) {
super(isNative, invokerSignature, invokerPackageName);
this.hasInvokee = hasInvokee;
this.invokeeCallParams = invokeeCallParams;
this.invokeeTypeName = invokeeTypeName;
this.isThrowing = isThrowing;
this.method = method;
this.returning = returning;
this.returnTypeString = returnTypeString;
}
public String getMethodBody(InjectorWriteContext writeContext) throws NoSourceNameException {
StringBuilder result = new StringBuilder();
if (isThrowing) {
result.append("try {\n ");
}
if (returning) {
result.append("return ");
}
if (!isNative()) {
if (hasInvokee) {
result.append("invokee.").append(method.getName());
} else if (method.isConstructor()) {
result.append("new ").append(invokeeTypeName);
} else {
result.append(invokeeTypeName).append(".").append(method.getName());
}
} else {
if (hasInvokee) {
result.append("invokee.");
}
result.append(getJsniSignature(method));
}
result.append("(").append(join(", ", invokeeCallParams)).append(");");
if (isThrowing) {
if (!isNative()) {
result.append("\n} catch (Exception e) {\n")
.append(" throw new com.google.gwt.inject.client.CreationException(e);\n")
.append("}");
} else {
result.append("\n} catch (e) {\n")
.append(" throw @com.google.gwt.inject.client.CreationException")
.append("::new(Ljava/lang/Throwable;)(e);\n")
.append("}");
}
}
return result.toString();
}
}
/**
* Get the name of an invoker method. This has a side-effect: it returns a
* newly unique value each time it is called, so be sure not to call it twice
* for the same method call (you'll get two different names).
*/
private String getInvokerMethodName(MethodLiteral, ?> method,
NameGenerator nameGenerator) throws NoSourceNameException {
Class> methodDeclaringType = method.getRawDeclaringType();
String invokeeTypeName = ReflectUtil.getSourceName(methodDeclaringType);
String methodBaseName = nameGenerator.convertToValidMemberName(
String.format("%s_%s_methodInjection", invokeeTypeName, method.getName()));
return nameGenerator.createMethodName(methodBaseName);
}
private boolean hasCheckedExceptions(MethodLiteral, ?> method) {
return method.getExceptionTypes().size() > 0;
}
private static String getJsniSignature(MethodLiteral, ?> method) throws NoSourceNameException {
StringBuilder signature = new StringBuilder();
signature.append("@");
signature.append(ReflectUtil.getSourceName(method.getRawDeclaringType()));
String name = method.isConstructor() ? "new" : method.getName();
signature.append("::").append(name).append("(");
// Using raw parameter types here since JNI doesn't know about
// parametrization at lookup time.
for (Type param : method.getRawParameterTypes()) {
signature.append(getJniSignature(param));
}
signature.append(")");
return signature.toString();
}
private static String getJniSignature(Type type) throws NoSourceNameException {
if (type instanceof Class>) {
if (((Class) type).isPrimitive()) {
if (type.equals(Boolean.TYPE)) {
return "Z";
} else if (type.equals(Byte.TYPE)) {
return "B";
} else if (type.equals(Character.TYPE)) {
return "C";
} else if (type.equals(Double.TYPE)) {
return "D";
} else if (type.equals(Float.TYPE)) {
return "F";
} else if (type.equals(Integer.TYPE)) {
return "I";
} else if (type.equals(Long.TYPE)) {
return "J";
} else if (type.equals(Short.TYPE)) {
return "S";
}
}
return "L" + getBinaryName((Class) type) + ";";
}
if (type instanceof GenericArrayType) {
return "[" + getJniSignature(((GenericArrayType) type).getGenericComponentType());
}
if (type instanceof ParameterizedType) {
return getJniSignature(((ParameterizedType) type).getRawType());
}
if (type instanceof WildcardType) {
// TODO(schmitt): This is likely incorrect in some cases.
return getJniSignature(((WildcardType) type).getUpperBounds()[0]);
}
if (type instanceof TypeVariable) {
// TODO(schmitt): This is likely incorrect in some cases.
return getJniSignature(((TypeVariable) type).getBounds()[0]);
}
throw new NoSourceNameException(type);
}
private static String getBinaryName(Class> type) {
List classes = new ArrayList();
for (Class> clazz = type; clazz != null; clazz = clazz.getEnclosingClass()) {
classes.add(clazz.getSimpleName());
}
Collections.reverse(classes);
String packageName = type.getPackage().getName().replace('.', '/');
if (packageName.length() > 0) {
packageName += "/";
}
return packageName + join("$", classes);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy