/* * 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.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.inject.rebind.ErrorManager; import com.google.gwt.inject.rebind.GinjectorBindings; import com.google.gwt.inject.rebind.GinjectorNameGenerator; import com.google.gwt.inject.rebind.binding.Binding; import com.google.gwt.inject.rebind.binding.GinjectorBinding; 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.inject.rebind.util.InjectorMethod; import com.google.gwt.inject.rebind.util.MethodCallUtil; 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.Singleton; import com.google.inject.TypeLiteral; import com.google.inject.spi.InjectionPoint; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Outputs the generated classes for one or more {@link GinjectorBindings}. */ @Singleton class GinjectorBindingsOutputter { private final GeneratorContext ctx; private final ErrorManager errorManager; private final GinjectorFragmentOutputter.Factory fragmentOutputterFactory; private final FragmentPackageName.Factory fragmentPackageNameFactory; private final GinjectorNameGenerator ginjectorNameGenerator; private final TreeLogger logger; private final MethodCallUtil methodCallUtil; private final ReachabilityAnalyzer reachabilityAnalyzer; private final SourceWriteUtil.Factory sourceWriteUtilFactory; @Inject GinjectorBindingsOutputter(GeneratorContext ctx, ErrorManager errorManager, GinjectorFragmentOutputter.Factory fragmentOutputterFactory, FragmentPackageName.Factory fragmentPackageNameFactory, GinjectorNameGenerator ginjectorNameGenerator, TreeLogger logger, MethodCallUtil methodCallUtil, ReachabilityAnalyzer reachabilityAnalyzer, SourceWriteUtil.Factory sourceWriteUtilFactory) { this.ctx = ctx; this.errorManager = errorManager; this.fragmentOutputterFactory = fragmentOutputterFactory; this.fragmentPackageNameFactory = fragmentPackageNameFactory; this.ginjectorNameGenerator = ginjectorNameGenerator; this.logger = logger; this.methodCallUtil = methodCallUtil; this.reachabilityAnalyzer = reachabilityAnalyzer; this.sourceWriteUtilFactory = sourceWriteUtilFactory; } /** * Writes the Ginjector class for the given bindings object, and all its * package-specific fragments. */ void write(GinjectorBindings bindings) throws UnableToCompleteException { TypeLiteral<?> ginjectorInterface = bindings.getGinjectorInterface(); String implClassName = ginjectorNameGenerator.getClassName(bindings); if (implClassName.contains(".")) { errorManager.logError("Internal error: the injector class name \"%s\" contains a full stop.", implClassName); } String packageName = ReflectUtil.getUserPackageName(TypeLiteral.get(bindings.getModule())); PrintWriter printWriter = ctx.tryCreate(logger, packageName, implClassName); if (printWriter == null) { // We already created this Ginjector. return; } ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, implClassName); SourceWriter writer = composerFactory.createSourceWriter(ctx, printWriter); FragmentMap fragments = new FragmentMap(bindings, packageName, implClassName, fragmentOutputterFactory); outputBindings(bindings, fragments, writer); errorManager.checkForError(); fragments.commitAll(); writer.commit(logger); } /** * Outputs the top-level injector for the given {@link GinjectorBindings}, * along with all of its fragments. * * <p>The top-level injector contains one field for each fragment of the * injector, which stores a reference to an instance of that fragment. In * addition, it contains a getter for every public type created by one of its * fragments, each of which forwards to a getter in the corresponding * fragment. In addition to being the injector's public interface, these * getters are used by each fragment of the injector to retrieve objects * created by other fragments. */ private void outputBindings(GinjectorBindings bindings, FragmentMap fragments, SourceWriter writer) { NameGenerator nameGenerator = bindings.getNameGenerator(); // The initialize*() methods contain code that needs to run before the root // injector is returned to the client, but after the injector hierarchy is // fully constructed. // Collects the text of the body of initializeEagerSingletons(). StringBuilder initializeEagerSingletonsBody = new StringBuilder(); // Collects the text of the body of initializeStaticInjections(). StringBuilder initializeStaticInjectionsBody = new StringBuilder(); SourceWriteUtil sourceWriteUtil = sourceWriteUtilFactory.create(bindings); // Output child modules. for (GinjectorBindings child : bindings.getChildren()) { String className = ginjectorNameGenerator.getClassName(child); String canonicalClassName = ginjectorNameGenerator.getCanonicalClassName(child); String fieldName = ginjectorNameGenerator.getFieldName(child); String getterName = nameGenerator.getChildInjectorGetterMethodName(className); writer.beginJavaDocComment(); writer.print("Child injector for %s", child.getModule()); writer.endJavaDocComment(); writer.println("private %s %s = null;", canonicalClassName, fieldName); writer.beginJavaDocComment(); writer.print("Getter for child injector for %s", child.getModule()); writer.endJavaDocComment(); sourceWriteUtil.writeMethod(writer, String.format("public %s %s()", canonicalClassName, getterName), String.format( "if (%2$s == null) {\n" + " %2$s = new %1$s(this);\n" + "}\n\n" + "return %2$s;", canonicalClassName, fieldName)); // Ensure that the initializer initializes this child, if necessary. outputSubInitialize(child, getterName, initializeEagerSingletonsBody, initializeStaticInjectionsBody); } initializeEagerSingletonsBody.append("\n"); initializeStaticInjectionsBody.append("\n"); outputInterfaceField(bindings, sourceWriteUtil, writer); outputMemberInjections(bindings, fragments, sourceWriteUtil); outputStaticInjections(bindings, fragments, sourceWriteUtil); // Output the bindings in the fragments. for (Map.Entry<Key<?>, Binding> entry : bindings.getBindings()) { Binding binding = entry.getValue(); if (!reachabilityAnalyzer.isReachable(binding)) { continue; } FragmentPackageName fragmentPackageName = fragmentPackageNameFactory.create(binding.getGetterMethodPackage()); Key<?> key = entry.getKey(); List<InjectorMethod> helperMethods = new ArrayList(); fragments.get(fragmentPackageName) .writeBindingGetter(key, binding, bindings.determineScope(key), helperMethods); outputMethods(helperMethods, fragments); } // Output the fragment members. outputFragments(bindings, fragments, initializeEagerSingletonsBody, initializeStaticInjectionsBody, sourceWriteUtil, writer); writeConstructor(bindings, sourceWriteUtil, writer); writeInitializers(bindings, initializeEagerSingletonsBody, initializeStaticInjectionsBody, sourceWriteUtil, writer); } /** * Writes code to store and retrieve the current injector interface, if one is * bound. */ private void outputInterfaceField(GinjectorBindings bindings, SourceWriteUtil sourceWriteUtil, SourceWriter writer) { // Only the root injector has an interface binding. if (bindings.getParent() != null) { return; } Class<?> boundGinjectorInterface = getBoundGinjector(bindings); if (boundGinjectorInterface == null) { // Sanity-check: if this fails, then we somehow didn't bind the injector // interface in the root module (the root module should always generate a // binding for the injector). errorManager.logError("Injector interface not bound in the root module."); return; } NameGenerator nameGenerator = bindings.getNameGenerator(); String fieldName = nameGenerator.getGinjectorInterfaceFieldName(); String getterName = nameGenerator.getGinjectorInterfaceGetterMethodName(); writer.beginJavaDocComment(); writer.print("The implementation of " + boundGinjectorInterface); writer.endJavaDocComment(); writer.println("private final %s %s;", boundGinjectorInterface.getCanonicalName(), fieldName); sourceWriteUtil.writeMethod(writer, String.format("public %s %s()", boundGinjectorInterface.getCanonicalName(), getterName), String.format("return %s;", fieldName)); } /** * For each fragment in the given {@link FragmentMap}, writes the field that * stores it and a getter for that field, and adds code to invoke the * fragment's initializers. */ private void outputFragments(GinjectorBindings bindings, FragmentMap fragments, StringBuilder initializeEagerSingletonsBody, StringBuilder initializeStaticInjectionsBody, SourceWriteUtil sourceWriteUtil, SourceWriter writer) { String implClassName = ginjectorNameGenerator.getClassName(bindings); NameGenerator nameGenerator = bindings.getNameGenerator(); for (FragmentPackageName fragmentPackageName : fragments.getFragmentPackages()) { String fragmentCanonicalClassName = nameGenerator.getFragmentCanonicalClassName(implClassName, fragmentPackageName); String fieldName = nameGenerator.getFragmentFieldName(fragmentPackageName); String getterName = nameGenerator.getFragmentGetterMethodName(fragmentPackageName); // Create the field. writer.beginJavaDocComment(); writer.print("Injector fragment for %s", fragmentPackageName); writer.endJavaDocComment(); writer.print("private %s %s = null;", fragmentCanonicalClassName, fieldName); // Write the getter. writer.beginJavaDocComment(); writer.print("Getter for injector fragment for %s", fragmentPackageName); writer.endJavaDocComment(); sourceWriteUtil.writeMethod(writer, "public " + fragmentCanonicalClassName + " " + getterName + "()", String.format( "if (%2$s == null) {\n" + " %2$s = new %1$s(this);\n" + "}\n\n" + "return %2$s;", fragmentCanonicalClassName, fieldName)); if (fragments.get(fragmentPackageName).hasEagerSingletonInitialization()) { initializeEagerSingletonsBody.append(getterName + "().initializeEagerSingletons();\n"); } if (fragments.get(fragmentPackageName).hasStaticInjectionInitialization()) { initializeStaticInjectionsBody.append(getterName + "().initializeStaticInjections();\n"); } } } /** * Adds member injections to each fragment. */ private void outputMemberInjections(GinjectorBindings bindings, FragmentMap fragments, SourceWriteUtil sourceWriteUtil) { NameGenerator nameGenerator = bindings.getNameGenerator(); for (TypeLiteral<?> type : bindings.getMemberInjectRequests()) { if (!reachabilityAnalyzer.isReachableMemberInject(bindings, type)) { continue; } List<InjectorMethod> memberInjectionHelpers = new ArrayList<InjectorMethod>(); try { sourceWriteUtil.createMemberInjection(type, nameGenerator, memberInjectionHelpers); outputMethods(memberInjectionHelpers, fragments); } catch (NoSourceNameException e) { errorManager.logError(e.getMessage(), e); } } } void outputStaticInjections(GinjectorBindings bindings, FragmentMap fragments, SourceWriteUtil sourceWriteUtil) { for (Class<?> type : bindings.getStaticInjectionRequests()) { outputStaticInjectionMethods(type, fragments, bindings.getNameGenerator(), sourceWriteUtil); } } /** * Outputs all the static injection methods for the given class. */ void outputStaticInjectionMethods(Class<?> type, FragmentMap fragments, NameGenerator nameGenerator, SourceWriteUtil sourceWriteUtil) { String methodName = nameGenerator.convertToValidMemberName("injectStatic_" + type.getName()); SourceSnippetBuilder body = new SourceSnippetBuilder(); for (InjectionPoint injectionPoint : InjectionPoint.forStaticMethodsAndFields(type)) { Member member = injectionPoint.getMember(); try { List<InjectorMethod> staticInjectionHelpers = new ArrayList<InjectorMethod>(); if (member instanceof Method) { MethodLiteral<?, Method> method = MethodLiteral.get((Method) member, TypeLiteral.get(member.getDeclaringClass())); body.append(methodCallUtil.createMethodCallWithInjection(method, null, nameGenerator, staticInjectionHelpers)); } else if (member instanceof Field) { FieldLiteral<?> field = FieldLiteral.get((Field) member, TypeLiteral.get(member.getDeclaringClass())); body.append(sourceWriteUtil.createFieldInjection(field, null, nameGenerator, staticInjectionHelpers)); } outputMethods(staticInjectionHelpers, fragments); } catch (NoSourceNameException e) { errorManager.logError(e.getMessage(), e); } } // Note that the top-level method that performs static injection will only // invoke a bunch of other injector methods. Therefore, it doesn't matter // which package it goes in, and we don't need to invoke getUserPackageName // (which is good, because in practice users statically inject types that // have no user package name because they're private inner classes!) String packageName = type.getPackage().getName(); InjectorMethod method = SourceSnippets.asMethod(false, "private void " + methodName + "()", packageName, body.build()); GinjectorFragmentOutputter fragment = fragments.get(fragmentPackageNameFactory.create(packageName)); fragment.outputMethod(method); fragment.invokeInInitializeStaticInjections(methodName); } /** * Outputs some methods to the fragments they belong to. */ void outputMethods(Iterable<InjectorMethod> methods, FragmentMap fragments) { for (InjectorMethod method : methods) { FragmentPackageName fragmentPackageName = fragmentPackageNameFactory.create(method.getPackageName()); GinjectorFragmentOutputter fragment = fragments.get(fragmentPackageName); fragment.outputMethod(method); } } /** * Outputs code to invoke the given child's initialize*() routines via its * member variable. */ private void outputSubInitialize(GinjectorBindings child, String childGetterName, StringBuilder initializeEagerSingletonsBody, StringBuilder initializeStaticInjectionsBody) { if (child.hasEagerSingletonBindingInSubtree()) { initializeEagerSingletonsBody .append(childGetterName) .append("().initializeEagerSingletons();\n"); } if (child.hasStaticInjectionRequestInSubtree()) { initializeStaticInjectionsBody .append(childGetterName) .append("().initializeStaticInjections();\n"); } } /** * Gets the Ginjector interface that is bound by the given bindings, if any. */ private static Class<?> getBoundGinjector(GinjectorBindings bindings) { if (bindings.getGinjectorInterface() == null) { return null; } TypeLiteral<?> ginjectorInterface = bindings.getGinjectorInterface(); Key<?> ginjectorKey = Key.get(ginjectorInterface); if (!bindings.isBound(ginjectorKey)) { return null; } if (!(bindings.getBinding(ginjectorKey) instanceof GinjectorBinding)) { return null; } return ginjectorInterface.getRawType(); } /** * Writes the class constructor. If there is a parent injector, also writes a * field that stores it and a getter (used by fragments in this injector and * its children). * * <p>The arguments to the constructor are: * * <p>For injectors other than the root, the parent injector. * * <p>For the root injector, the implementation of the ginjector interface. */ private void writeConstructor(GinjectorBindings bindings, SourceWriteUtil sourceWriteUtil, SourceWriter writer) { String implClassName = ginjectorNameGenerator.getClassName(bindings); if (bindings.getParent() == null) { // In outputInterfaceField, we verify that we have a bound injector if we // are the root module, so this should never be null: Class<?> boundGinjector = getBoundGinjector(bindings); String interfaceCanonicalClassName = boundGinjector.getCanonicalName(); String fieldName = bindings.getNameGenerator().getGinjectorInterfaceFieldName(); sourceWriteUtil.writeMethod(writer, String.format("public %s(%s %s)", implClassName, interfaceCanonicalClassName, fieldName), String.format("this.%1$s = %1$s;", fieldName)); } else { String parentImplCanonicalClassName = ginjectorNameGenerator.getCanonicalClassName( bindings.getParent()); writer.print(String.format("private final %s parent;\n", parentImplCanonicalClassName)); sourceWriteUtil.writeMethod(writer, String.format("public %s getParent()", parentImplCanonicalClassName), "return parent;"); sourceWriteUtil.writeMethod(writer, String.format("public %1$s(%2$s parent)", implClassName, parentImplCanonicalClassName), "this.parent = parent;"); } } // Setting up the injector works as follows: // // When the injectors are constructed, each injector creates its children and // fragments via field initializers. Then, if the injector is the top-level // injector, it initializes itself and its children. Initialization is // performed as a separate step to ensure that the entire injector hierarchy // is created before we try to invoke any injection method to, e.g., create // eager singletons. For more details, see // <http://code.google.com/p/google-gin/issues/detail?id=156>. private void writeInitializers( GinjectorBindings bindings, StringBuilder initializeEagerSingletonsBody, StringBuilder initializeStaticInjectionsBody, SourceWriteUtil sourceWriteUtil, SourceWriter writer) { if (bindings.hasEagerSingletonBindingInSubtree()) { sourceWriteUtil.writeMethod(writer, "public void initializeEagerSingletons()", initializeEagerSingletonsBody.toString()); } if (bindings.hasStaticInjectionRequestInSubtree()) { sourceWriteUtil.writeMethod(writer, "public void initializeStaticInjections()", initializeStaticInjectionsBody.toString()); } } /** * Creates and tracks the fragment outputter associated with each package * containing bindings. Visible for testing. */ static final class FragmentMap { private final GinjectorBindings bindings; private final GinjectorFragmentOutputter.Factory fragmentFactory; private final Map<FragmentPackageName, GinjectorFragmentOutputter> fragments = new LinkedHashMap<FragmentPackageName, GinjectorFragmentOutputter>(); private final String ginjectorPackageName; private final String ginjectorClassName; FragmentMap(GinjectorBindings bindings, String ginjectorPackageName, String ginjectorClassName, GinjectorFragmentOutputter.Factory fragmentFactory) { this.bindings = bindings; this.ginjectorPackageName = ginjectorPackageName; this.ginjectorClassName = ginjectorClassName; this.fragmentFactory = fragmentFactory; } /** * Gets the fragment outputter associated with the given package name, * creating one if there isn't one yet. */ GinjectorFragmentOutputter get(FragmentPackageName packageName) { if (fragments.containsKey(packageName)) { return fragments.get(packageName); } else { GinjectorFragmentOutputter result = fragmentFactory.create(bindings, packageName, ginjectorPackageName, ginjectorClassName); fragments.put(packageName, result); return result; } } /** * Gets the package names associated with fragments that were created by * this map. */ Iterable<FragmentPackageName> getFragmentPackages() { return fragments.keySet(); } /** * Commits all the fragments that were created by this map. */ void commitAll() { for (GinjectorFragmentOutputter fragment : fragments.values()) { fragment.commit(); } } } }