com.google.gwt.inject.rebind.util.SourceWriteUtil Maven / Gradle / Ivy
Show all versions of gin Show documentation
/*
* Copyright 2008 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 com.google.gwt.inject.rebind.binding.BindingIndex;
import com.google.gwt.inject.rebind.binding.Context;
import com.google.gwt.inject.rebind.binding.Injectable;
import com.google.gwt.inject.rebind.reflect.FieldLiteral;
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.gwt.user.rebind.SourceWriter;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Helper object for source writing.
*/
public class SourceWriteUtil {
private final GuiceUtil guiceUtil;
private final MemberCollector memberCollector;
private final MethodCallUtil methodCallUtil;
private final BindingIndex bindingIndex;
@Inject
protected SourceWriteUtil(GuiceUtil guiceUtil, @Injectable MemberCollector memberCollector,
MethodCallUtil methodCallUtil, @Assisted BindingIndex bindingIndex) {
this.guiceUtil = guiceUtil;
this.memberCollector = memberCollector;
this.methodCallUtil = methodCallUtil;
this.bindingIndex = bindingIndex;
}
/**
* Appends a field injecting method for each passed field to the
* {@code sourceWriter} and returns a string that invokes all written
* methods.
*
* @param fields fields to be injected
* @param injecteeName variable that references the object into which values
* are injected, in the context of the returned call string
* @return string calling the generated method
*/
public SourceSnippet createFieldInjections(Iterable> fields, String injecteeName,
NameGenerator nameGenerator, List methodsOutput)
throws NoSourceNameException {
SourceSnippetBuilder methodInvocations = new SourceSnippetBuilder();
for (FieldLiteral> field : fields) {
methodInvocations
.append(createFieldInjection(field, injecteeName, nameGenerator, methodsOutput))
.append("\n");
}
return methodInvocations.build();
}
/**
* Creates a field injecting method and returns a string that invokes the
* written method.
*
* @param field field to be injected
* @param injecteeName variable that references the object into which values
* are injected, in the context of the returned call string
* @param nameGenerator NameGenerator to be used for ensuring method name uniqueness
* @return string calling the generated method
*/
public SourceSnippet createFieldInjection(final FieldLiteral> field, final String injecteeName,
NameGenerator nameGenerator, List methodsOutput)
throws NoSourceNameException {
final boolean hasInjectee = injecteeName != null;
final boolean useNativeMethod = field.isPrivate()
|| ReflectUtil.isPrivate(field.getDeclaringType())
|| field.isLegacyFinalField();
// Determine method signature parts.
final String injecteeTypeName = ReflectUtil.getSourceName(field.getRawDeclaringType());
String fieldTypeName = ReflectUtil.getSourceName(field.getFieldType());
String methodBaseName = nameGenerator.convertToValidMemberName(injecteeTypeName + "_"
+ field.getName() + "_fieldInjection");
final String methodName = nameGenerator.createMethodName(methodBaseName);
// Field injections are performed in the package of the class declaring the
// field. Any private types referenced by the injection must be visible
// from there.
final String packageName = ReflectUtil.getUserPackageName(field.getDeclaringType());
String signatureParams = fieldTypeName + " value";
boolean isLongAcccess = field.getFieldType().getRawType().equals(Long.TYPE);
if (hasInjectee) {
signatureParams = injecteeTypeName + " injectee, " + signatureParams;
}
// Compose method implementation and invocation.
String annotation;
if (isLongAcccess) {
annotation = "@com.google.gwt.core.client.UnsafeNativeLong ";
} else {
annotation = "";
}
String header = useNativeMethod ? "public native " : "public ";
String signature = annotation + header + "void " + methodName + "(" + signatureParams + ")";
InjectorMethod injectionMethod =
new AbstractInjectorMethod(useNativeMethod, signature, packageName) {
public String getMethodBody(InjectorWriteContext writeContext)
throws NoSourceNameException {
if (!useNativeMethod) {
return (hasInjectee
? "injectee." : injecteeTypeName + ".") + field.getName() + " = value;";
} else {
return (hasInjectee ? "injectee." : "") + getJsniSignature(field) + " = value;";
}
}
};
methodsOutput.add(injectionMethod);
return new SourceSnippet() {
public String getSource(InjectorWriteContext writeContext) {
List callParams = new ArrayList();
if (hasInjectee) {
callParams.add(injecteeName);
}
callParams.add(writeContext.callGetter(guiceUtil.getKey(field)));
return writeContext.callMethod(methodName, packageName, callParams) + ";\n";
}
};
}
/**
* Creates a method injecting method and returns a string that invokes the new
* method. The values for the passed method's parameters are retrieved
* through the {@link com.google.gwt.inject.client.Ginjector}.
*
* If the passed method collection contains only one actual method, the native
* method will pass on (i.e. return) the result of the actual method's
* invocation, if any.
*
* The passed method collection can contain constructors (they'll be treated
* correctly) if no {@code injecteeName} is passed. The same applies for
* static methods.
*
* If a method without parameters is provided, that method will be called and
* no parameters will be passed.
*
* @param methods methods to be called & injected
* @param injecteeName variable that references the object into which values
* are injected, in the context of the returned call string. If
* {@code null} all passed methods are called as static/constructors.
* @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 createMethodInjections(Iterable extends MethodLiteral, ?>> methods,
String injecteeName, NameGenerator nameGenerator, List methodsOutput)
throws NoSourceNameException {
SourceSnippetBuilder methodInvocations = new SourceSnippetBuilder();
for (MethodLiteral, ?> method : methods) {
methodInvocations
.append(methodCallUtil.createMethodCallWithInjection(method, injecteeName, nameGenerator,
methodsOutput))
.append("\n");
}
return methodInvocations.build();
}
/**
* Writes out a binding context, followed by a newline.
*
* Binding contexts may contain newlines; this routine translates those for
* the SourceWriter to ensure that indents, Javadoc comments, etc are handled
* properly.
*/
public void writeBindingContext(SourceWriter writer, Context context) {
// Avoid a trailing \n -- the GWT class source file composer will output an
// ugly extra newline if we do that.
String text = context.toString();
boolean first = true;
for (String line : text.split("\n")) {
if (first) {
first = false;
} else {
writer.println();
}
// Indent the line relative to its current location. writer.indent()
// won't work, since it does the wrong thing in Javadoc.
writer.print(" ");
writer.print(line);
}
}
/**
* Write a Javadoc comment for a binding, including its context.
*
* @param description The description of the binding printed before its
* location, such as "Foo bound at: "
* @param writer The writer to use in displaying the context.
* @param bindingContext The context of the binding.
*/
public void writeBindingContextJavadoc(SourceWriter writer, Context bindingContext,
String description) {
writer.beginJavaDocComment();
writer.println(description);
writeBindingContext(writer, bindingContext);
writer.endJavaDocComment();
}
/**
* Write the Javadoc for the binding of a particular key, showing the context
* of the binding.
*
* @param key The bound key.
* @param writer The writer to use to write this comment.
* @param bindingContext The context of the binding.
*/
public void writeBindingContextJavadoc(SourceWriter writer, Context bindingContext,
Key> key) {
writeBindingContextJavadoc(writer, bindingContext,
"Binding for " + key.getTypeLiteral() + " declared at:");
}
/**
* Writes a method with the given signature and body to the source writer.
*
* @param writer writer that the method is written to
* @param signature method's signature
* @param body method's body
*/
public void writeMethod(SourceWriter writer, String signature, String body) {
writer.println(signature + " {");
writer.indent();
writer.println(body);
writer.outdent();
writer.println("}");
writer.println();
}
/**
* Writes a native method with the given signature and body to the source
* writer.
*
* @param writer writer that the method is written to
* @param signature method's signature
* @param body method's body
*/
public void writeNativeMethod(SourceWriter writer, String signature, String body) {
writer.println(signature + " /*-{");
writer.indent();
writer.println(body);
writer.outdent();
writer.println("}-*/;");
writer.println();
}
/**
* Writes the given method to the given source writer.
*/
public void writeMethod(InjectorMethod method, SourceWriter writer,
InjectorWriteContext writeContext) throws NoSourceNameException {
if (method.isNative()) {
writeNativeMethod(writer, method.getMethodSignature(), method.getMethodBody(writeContext));
} else {
writeMethod(writer, method.getMethodSignature(), method.getMethodBody(writeContext));
}
}
/**
* Writes the given methods to the given source writer.
*
* @param methods the methods to write
* @param writer the source writer to which the methods should be written
* @param writeContext the context in which to write the methods
*/
public void writeMethods(Iterable methods, SourceWriter writer,
InjectorWriteContext writeContext) throws NoSourceNameException {
for (InjectorMethod method : methods) {
writeMethod(method, writer, writeContext);
}
}
/**
* Generates all the required injector methods to inject members of the given
* type, and a standard member-inject method that invokes them.
*
* @param type type for which the injection is performed
* @param nameGenerator the name generator used to create method names
* @param methodsOutput a list to which the new injection method and all its
* helpers are added
* @return name of the method created
*/
public String createMemberInjection(TypeLiteral> type, NameGenerator nameGenerator,
List methodsOutput) throws NoSourceNameException {
String memberInjectMethodName = nameGenerator.getMemberInjectMethodName(type);
String memberInjectMethodSignature = "public void " + memberInjectMethodName + "("
+ ReflectUtil.getSourceName(type) + " injectee)";
SourceSnippetBuilder sb = new SourceSnippetBuilder();
sb.append(createFieldInjections(getFieldsToInject(type), "injectee", nameGenerator,
methodsOutput));
sb.append(createMethodInjections(getMethodsToInject(type), "injectee", nameGenerator,
methodsOutput));
// Generate the top-level member inject method in the package containing the
// type we're injecting:
methodsOutput.add(SourceSnippets.asMethod(false, memberInjectMethodSignature,
ReflectUtil.getUserPackageName(type), sb.build()));
return memberInjectMethodName;
}
private String getJsniSignature(FieldLiteral> field) throws NoSourceNameException {
StringBuilder signature = new StringBuilder();
signature.append("@");
signature.append(ReflectUtil.getSourceName(field.getRawDeclaringType()));
signature.append("::").append(field.getName());
return signature.toString();
}
// TODO(schmitt): Move to a better location.
public static CharSequence join(CharSequence delimiter, Iterable extends CharSequence> list) {
Iterator extends CharSequence> it = list.iterator();
if (it.hasNext()) {
StringBuilder sb = new StringBuilder(it.next());
while (it.hasNext()) {
sb.append(delimiter);
sb.append(it.next());
}
return sb.toString();
}
return "";
}
private Set> getFieldsToInject(TypeLiteral> type) {
// Only inject fields that are non optional or where the key is bound.
Set> fields = new LinkedHashSet>();
for (FieldLiteral> field : memberCollector.getFields(type)) {
if (!guiceUtil.isOptional(field) || bindingIndex.isBound(guiceUtil.getKey(field))) {
fields.add(field);
}
}
return fields;
}
private Set> getMethodsToInject(TypeLiteral> type) {
Set> methods = new LinkedHashSet>();
for (MethodLiteral, Method> method : memberCollector.getMethods(type)) {
if (shouldInject(method)) {
methods.add(method);
}
}
return methods;
}
private boolean shouldInject(MethodLiteral, Method> method) {
// Only inject methods that are non optional or where all keys are bound.
if (guiceUtil.isOptional(method)) {
for (Key> paramKey : method.getParameterKeys()) {
if (!bindingIndex.isBound(paramKey)) {
return false;
}
}
}
return true;
}
/**
* Factory for {@link SourceWriteUtil}.
*/
public interface Factory {
SourceWriteUtil create(BindingIndex bindingIndex);
}
}