/* * 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.core.ext.TreeLogger.Type; import com.google.gwt.inject.rebind.binding.Binding; import com.google.gwt.inject.rebind.binding.ExposedChildBinding; import com.google.gwt.inject.rebind.binding.ParentBinding; import com.google.gwt.inject.rebind.util.Preconditions; import com.google.gwt.inject.rebind.util.PrettyPrinter; import com.google.inject.Inject; import com.google.inject.Key; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Validate that a Ginjector hierarchy doesn't contain any duplicate bindings. */ public class DoubleBindingChecker { private final ErrorManager errorManager; private final TreeLogger logger; @Inject public DoubleBindingChecker(ErrorManager errorManager, TreeLogger logger) { this.errorManager = errorManager; this.logger = logger; } public void checkBindings(GinjectorBindings ginjector) { Map<Key<?>, GinjectorBindings> bindingSources = new LinkedHashMap<Key<?>, GinjectorBindings>(); checkBindings(ginjector, bindingSources); Preconditions.checkState(bindingSources.isEmpty()); } public void checkBindings(GinjectorBindings ginjector, Map<Key<?>, GinjectorBindings> bindingSources) { // Add bindings from this ginjector, reporting errors if detected List<Key<?>> keysFromGinjector = new ArrayList<Key<?>>(); for (Key<?> key : ginjector.getBoundKeys()) { GinjectorBindings newSource = findSource(ginjector, key); GinjectorBindings oldSource = bindingSources.put(key, newSource); if (oldSource != null && oldSource != newSource) { // TODO(bchambers, dburrows): We should revisit where double-bindings // come from, and how they get reported more systematically, to make // sure that we produce the most useful error messages possible. errorManager.logDoubleBind(key, newSource.getBinding(key), newSource, oldSource.getBinding(key), oldSource); } else { // If there was already a matching value in the map, we shouldn't remove it // here. Instead, let the person who put the binding in clear it out. keysFromGinjector.add(key); } } // Visit each child ginjector in the context we've built up for (GinjectorBindings child : ginjector.getChildren()) { checkBindings(child, bindingSources); } // Before going back up, remove any state that was added at this level for (Key<?> key : keysFromGinjector) { bindingSources.remove(key); } } /** * Find the ginjector that we "really" get the binding for key from. That is, * if it is inherited from a child/parent, return that injector. */ private GinjectorBindings findSource(GinjectorBindings ginjector, Key<?> key) { Set<GinjectorBindings> visited = new LinkedHashSet<GinjectorBindings>(); GinjectorBindings lastGinjector = null; while (ginjector != null) { if (!visited.add(ginjector)) { logger.log(Type.ERROR, PrettyPrinter.format( "Cycle detected in bindings for %s", key)); for (GinjectorBindings visitedBindings : visited) { PrettyPrinter.log(logger, Type.ERROR, " %s", visitedBindings); } return ginjector; // at this point, just return *something* } lastGinjector = ginjector; ginjector = linkedGinjector(ginjector.getBinding(key)); } return lastGinjector; } private GinjectorBindings linkedGinjector(Binding binding) { GinjectorBindings nextGinjector = null; if (binding instanceof ExposedChildBinding) { ExposedChildBinding childBinding = (ExposedChildBinding) binding; nextGinjector = childBinding.getChildBindings(); } else if (binding instanceof ParentBinding) { ParentBinding parentBinding = (ParentBinding) binding; nextGinjector = parentBinding.getParentBindings(); } return nextGinjector; } }