/* * 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; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.inject.rebind.binding.BindingFactory; import com.google.gwt.inject.rebind.binding.Context; import com.google.gwt.inject.rebind.binding.ExposedChildBinding; import com.google.gwt.inject.rebind.util.PrettyPrinter; import com.google.inject.Inject; import com.google.inject.Key; import com.google.inject.assistedinject.Assisted; import com.google.inject.spi.DefaultElementVisitor; import com.google.inject.spi.Element; import com.google.inject.spi.Message; import com.google.inject.spi.PrivateElements; import com.google.inject.spi.ProviderLookup; import com.google.inject.spi.StaticInjectionRequest; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Gathers elements and adds them to a {@link GinjectorBindings}. */ public class GuiceElementVisitor extends DefaultElementVisitor<Void> { /** * Interface for use with Assisted Injection for creating {@link GuiceElementVisitor} */ public interface GuiceElementVisitorFactory { GuiceElementVisitor create(GinjectorBindings bindingsCollection); } private final List<Message> messages = new ArrayList<Message>(); private final TreeLogger logger; private final GuiceElementVisitor.GuiceElementVisitorFactory guiceElementVisitorFactory; private final GinjectorBindings bindings; private final ErrorManager errorManager; private final BindingFactory bindingFactory; private Iterator<GinjectorBindings> children; private GuiceBindingVisitorFactory bindingVisitorFactory; @Inject public GuiceElementVisitor(TreeLogger logger, GuiceElementVisitorFactory guiceElementVisitorFactory, GuiceBindingVisitorFactory bindingVisitorFactory, ErrorManager errorManager, @Assisted GinjectorBindings bindings, BindingFactory bindingFactory) { this.logger = logger; this.guiceElementVisitorFactory = guiceElementVisitorFactory; this.bindingVisitorFactory = bindingVisitorFactory; this.errorManager = errorManager; this.bindings = bindings; this.bindingFactory = bindingFactory; } public void visitElementsAndReportErrors(List<Element> elements) { visitElements(elements); // Capture any binding errors, any of which we treat as fatal. if (!messages.isEmpty()) { for (Message message : messages) { // tostring has both source and message so use that errorManager.logError(message.toString(), message.getCause()); } } } private void visitElements(List<Element> elements) { // We take advantage of the fact that iterating over the PrivateElements should // happen in the same order that the modules were installed. We match each PrivateElements // up with the {@link GinjectorBindings} that were created in the adapter. children = bindings.getChildren().iterator(); for (Element element : elements) { element.acceptVisitor(this); } } public <T> Void visit(com.google.inject.Binding<T> command) { GuiceBindingVisitor<T> bindingVisitor = bindingVisitorFactory.create( command.getKey(), messages, bindings); PrettyPrinter.log(logger, TreeLogger.DEBUG, "Adding pin for %s in %s because %s", command.getKey(), bindings, command); // If we visit a binding for a key, we pin it to the current ginjector, // since it indicates that the user explicitly asked for it to be placed // there. bindings.addPin(command.getKey()); command.acceptTargetVisitor(bindingVisitor); command.acceptScopingVisitor(bindingVisitor); return null; } public Void visit(Message message) { messages.add(message); return null; } public <T> Void visit(ProviderLookup<T> providerLookup) { // Ignore provider lookups for now // TODO(bstoler): I guess we should error if you try to lookup a provider // that is not bound? return null; } protected Void visitOther(Element element) { visit(new Message(element.getSource(), "Ignoring unsupported Module element: " + element)); return null; } public Void visit(StaticInjectionRequest staticInjectionRequest) { bindings.addStaticInjectionRequest( staticInjectionRequest.getType(), staticInjectionRequest.getSource()); return null; } public Void visit(PrivateElements privateElements) { GinjectorBindings childCollection = children.next(); // Add information about the elements in the child ginjector to the child bindings // TODO(bchambers): Use the tree loggers more intelligently -- when visiting // a child bindings collection, we should create a new branch. This is slightly // complicated because we process in two stages -- once here where we // add explicit bindings (and record implicit dependencies), and again later // to resolve the implicit dependencies. GuiceElementVisitor childVisitor = guiceElementVisitorFactory.create(childCollection); childVisitor.visitElements(privateElements.getElements()); messages.addAll(childVisitor.getMessages()); // Add information about the exposed elements in child to the current binding collection for (Key<?> key : privateElements.getExposedKeys()) { ExposedChildBinding childBinding = bindingFactory.getExposedChildBinding(key, childCollection, Context.forElement(privateElements)); // The child must have an explicit binding or pin for anything it exposes. // // Note that this is correct only because the // GuiceElementVisitor runs before any synthetic bindings are // inserted into the GinjectorBindings (more specifically, // before implicit bindings are generated). Otherwise, // isBound() might return true for keys that weren't explicitly bound. // // If the above invariant is violated, the effect will be to // bypass this error in some cases. The offending key should // still generate an error, but it will not be as clearly // described. if (!(childCollection.isBound(key) || childCollection.isPinned(key))) { errorManager.logError( "Key %s was exposed from but not bound in %s. Did you forget to call bind()?", key, childCollection); } PrettyPrinter.log(logger, TreeLogger.TRACE, "Child binding for %s in %s: %s", key, bindings, childBinding); bindings.addBinding(key, childBinding); } return null; } public List<Message> getMessages() { return messages; } }