com.google.gwt.inject.rebind.output.GinjectorFragmentOutputter Maven / Gradle / Ivy
Show all versions of gin Show documentation
/*
* 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.output;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.inject.rebind.ErrorManager;
import com.google.gwt.inject.rebind.GinScope;
import com.google.gwt.inject.rebind.GinjectorBindings;
import com.google.gwt.inject.rebind.binding.Binding;
import com.google.gwt.inject.rebind.binding.Context;
import com.google.gwt.inject.rebind.reflect.NoSourceNameException;
import com.google.gwt.inject.rebind.reflect.ReflectUtil;
import com.google.gwt.inject.rebind.util.InjectorMethod;
import com.google.gwt.inject.rebind.util.InjectorWriteContext;
import com.google.gwt.inject.rebind.util.NameGenerator;
import com.google.gwt.inject.rebind.util.SourceSnippet;
import com.google.gwt.inject.rebind.util.SourceSnippetBuilder;
import com.google.gwt.inject.rebind.util.SourceSnippets;
import com.google.gwt.inject.rebind.util.SourceWriteUtil;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.assistedinject.Assisted;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Writes the definition of a single fragment of the Ginjector. A Ginjector
* fragment contains all the code to create objects bound in that Ginjector that
* belong to a particular package, exposing only methods to create objects that
* are not package-private.
*
* Visible for testing, so it can be returned from a mock factory.
*/
class GinjectorFragmentOutputter {
private final GeneratorContext ctx;
private final InjectorWriteContext injectorWriteContext;
private final ErrorManager errorManager;
private final TreeLogger logger;
private final NameGenerator nameGenerator;
private final SourceWriteUtil sourceWriteUtil;
private final String fragmentClassName;
private final FragmentPackageName fragmentPackageName;
private final String ginjectorClassName;
/**
* Collects the text of the body of initializeEagerSingletons().
*/
private final StringBuilder initializeEagerSingletonsBody = new StringBuilder();
/**
* Collects the text of the body of initializeStaticInjections().
*/
private final StringBuilder initializeStaticInjectionsBody = new StringBuilder();
/**
* The {@link SourceWriter} used to generate the source code.
*/
private final SourceWriter writer;
private boolean committed = false;
@Inject
GinjectorFragmentOutputter(
GeneratorContext ctx,
GinjectorFragmentContext.Factory ginjectorFragmentContextFactory,
ErrorManager errorManager,
TreeLogger logger,
SourceWriteUtil.Factory sourceWriteUtilFactory,
@Assisted GinjectorBindings bindings,
@Assisted FragmentPackageName fragmentPackageName,
@Assisted("ginjectorPackageName") String ginjectorPackageName,
@Assisted("ginjectorClassName") String ginjectorClassName) {
this.ctx = ctx;
this.errorManager = errorManager;
this.logger = logger;
this.sourceWriteUtil = sourceWriteUtilFactory.create(bindings);
this.fragmentPackageName = fragmentPackageName;
this.ginjectorClassName = ginjectorClassName;
this.nameGenerator = bindings.getNameGenerator();
fragmentClassName = nameGenerator.getFragmentClassName(ginjectorClassName, fragmentPackageName);
if (fragmentClassName.contains(".")) {
errorManager.logError("Internal error: the fragment class name \"%s\" contains a full stop.",
fragmentClassName);
}
PrintWriter printWriter =
ctx.tryCreate(logger, fragmentPackageName.toString(), fragmentClassName);
if (printWriter == null) {
// Something is very wrong! We already created this fragment, but the
// GinjectorBindingsOutputter should only create each fragment once.
// Something bad will probably happen later on if we continue, so just
// abort.
logger.log(TreeLogger.Type.ERROR, "The fragment " + fragmentPackageName + "." +
fragmentClassName + " already exists.");
throw new IllegalStateException("The fragment " + fragmentClassName + " already exists.");
}
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
fragmentPackageName.toString(), fragmentClassName);
composerFactory.addImport(GWT.class.getCanonicalName());
composerFactory.addImport(ginjectorPackageName + "." + ginjectorClassName);
writer = composerFactory.createSourceWriter(ctx, printWriter);
injectorWriteContext = ginjectorFragmentContextFactory.create(bindings, fragmentPackageName,
writer);
}
String getFragmentClassName() {
return fragmentClassName;
}
FragmentPackageName getFragmentPackageName() {
return fragmentPackageName;
}
/** Returns true if the eager singletons initializer is nonempty. */
boolean hasEagerSingletonInitialization() {
return initializeEagerSingletonsBody.length() > 0;
}
/** Returns true if the static injections initializer is nonempty. */
boolean hasStaticInjectionInitialization() {
return initializeStaticInjectionsBody.length() > 0;
}
/**
* Writes a method describing the getter for the given key, along with any
* other code necessary to support it. Produces a list of helper methods that
* still need to be written.
*/
void writeBindingGetter(Key> key, Binding binding, GinScope scope,
List helperMethodsOutput) {
Context bindingContext = binding.getContext();
SourceSnippetBuilder getterBuilder = new SourceSnippetBuilder();
SourceSnippet creationStatements;
String getter = nameGenerator.getGetterMethodName(key);
String typeName;
try {
typeName = ReflectUtil.getSourceName(key.getTypeLiteral());
creationStatements = binding.getCreationStatements(nameGenerator, helperMethodsOutput);
} catch (NoSourceNameException e) {
errorManager.logError("Error trying to write getter for [%s] -> [%s];"
+ " binding declaration: %s", e, key, binding, bindingContext);
return;
}
// Name of the field that we might need.
String field = nameGenerator.getSingletonFieldName(key);
switch (scope) {
case EAGER_SINGLETON:
initializeEagerSingletonsBody.append("// Eager singleton bound at:\n");
appendBindingContextCommentToMethod(bindingContext, initializeEagerSingletonsBody);
initializeEagerSingletonsBody.append(getter).append("();\n");
// $FALL-THROUGH$
case SINGLETON:
writer.println("private " + typeName + " " + field + " = null;");
writer.println();
getterBuilder.append(String.format("\nif (%s == null) {\n", field))
.append(creationStatements).append("\n")
.append(String.format(" %s = result;\n", field))
.append("}\n")
.append(String.format("return %s;\n", field));
break;
case NO_SCOPE:
sourceWriteUtil.writeBindingContextJavadoc(writer, bindingContext, key);
getterBuilder.append(creationStatements).append("\n").append("return result;\n");
break;
default:
throw new IllegalStateException();
}
outputMethod(SourceSnippets.asMethod(false, String.format("public %s %s()", typeName, getter),
fragmentPackageName.toString(), getterBuilder.build()));
}
void outputMethod(InjectorMethod method) {
try {
sourceWriteUtil.writeMethod(method, writer, injectorWriteContext);
} catch (NoSourceNameException e) {
errorManager.logError(e.getMessage(), e);
}
}
/**
* Add the given method name to the methods invoked in initializeStaticInjections().
*/
void invokeInInitializeStaticInjections(String methodName) {
initializeStaticInjectionsBody.append(methodName).append("();\n");
}
/**
* Outputs all the top-level methods and fields of the class, and commits the
* writer. Must be the last method invoked on this object.
*/
void commit() {
if (committed) {
errorManager.logError("Committed the fragment for %s twice.", fragmentPackageName);
return;
}
committed = true;
// Write the field where the enclosing injector is stored.
writer.beginJavaDocComment();
writer.print("Field for the enclosing injector.");
writer.endJavaDocComment();
writer.println("private final %s injector;", ginjectorClassName);
// Write the constructor, which takes the enclosing injector and does
// nothing but store it in a field. It's important that the constructor has
// no other side-effects; in particular, it must not call any injector
// methods, since the injector might not be fully constructed.
sourceWriteUtil.writeMethod(writer,
String.format("public %s(%s injector)", fragmentClassName, ginjectorClassName),
"this.injector = injector;");
if (hasEagerSingletonInitialization()) {
// Write a method to initialize eager singletons.
sourceWriteUtil.writeMethod(
writer,
"public void initializeEagerSingletons()",
initializeEagerSingletonsBody.toString());
}
if (hasStaticInjectionInitialization()) {
// Write a method to initialize static injection.
sourceWriteUtil.writeMethod(
writer,
"public void initializeStaticInjections()",
initializeStaticInjectionsBody.toString());
}
writer.commit(logger);
}
private void appendBindingContextCommentToMethod(Context bindingContext,
StringBuilder methodBody) {
for (String line : bindingContext.toString().split("\n")) {
methodBody.append("// ").append(line).append("\n");
}
}
interface Factory {
GinjectorFragmentOutputter create(
GinjectorBindings bindings,
FragmentPackageName fragmentPackageName,
@Assisted("ginjectorPackageName") String ginjectorPackageName,
@Assisted("ginjectorClassName") String ginjectorClassName);
}
}