1 /* 2 * Copyright (C) 2014 Google, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package dagger.internal.codegen; 17 18 import com.google.auto.common.MoreElements; 19 import com.google.auto.common.MoreTypes; 20 import com.google.common.base.Optional; 21 import com.google.common.base.Predicate; 22 import com.google.common.collect.FluentIterable; 23 import com.google.common.collect.ImmutableSet; 24 import com.google.common.collect.Iterables; 25 import com.google.common.collect.Maps; 26 import com.google.common.collect.Sets; 27 import dagger.Component; 28 import dagger.Provides; 29 import dagger.internal.codegen.writer.ClassName; 30 import java.util.ArrayDeque; 31 import java.util.Deque; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import javax.annotation.processing.Messager; 36 import javax.inject.Inject; 37 import javax.lang.model.element.ExecutableElement; 38 import javax.lang.model.element.TypeElement; 39 import javax.lang.model.util.ElementFilter; 40 import javax.lang.model.util.Elements; 41 import javax.lang.model.util.Types; 42 import javax.tools.Diagnostic.Kind; 43 44 import static com.google.auto.common.MoreElements.isAnnotationPresent; 45 import static com.google.common.base.Preconditions.checkArgument; 46 import static com.google.common.base.Preconditions.checkNotNull; 47 import static com.google.common.base.Preconditions.checkState; 48 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; 49 50 /** 51 * Maintains the collection of provision bindings from {@link Inject} constructors and members 52 * injection bindings from {@link Inject} fields and methods known to the annotation processor. 53 * Note that this registry <b>does not</b> handle any explicit bindings (those from {@link Provides} 54 * methods, {@link Component} dependencies, etc.). 55 * 56 * @author Gregory Kick 57 */ 58 final class InjectBindingRegistry { 59 private final Elements elements; 60 private final Types types; 61 private final Messager messager; 62 private final ProvisionBinding.Factory provisionBindingFactory; 63 private final MembersInjectionBinding.Factory membersInjectionBindingFactory; 64 65 final class BindingsCollection<B extends Binding> { 66 private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap(); 67 private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>(); 68 private final Set<Key> materializedBindingKeys = Sets.newLinkedHashSet(); 69 70 void generateBindings(SourceFileGenerator<B> generator) throws SourceFileGenerationException { 71 for (B binding = bindingsRequiringGeneration.poll(); 72 binding != null; 73 binding = bindingsRequiringGeneration.poll()) { 74 checkState(!binding.hasNonDefaultTypeParameters()); 75 generator.generate(binding); 76 materializedBindingKeys.add(binding.key()); 77 } 78 // Because Elements instantiated across processing rounds are not guaranteed to be equals() to 79 // the logically same element, clear the cache after generating 80 bindingsByKey.clear(); 81 } 82 83 /** Returns a previously cached binding. */ 84 B getBinding(Key key) { 85 return bindingsByKey.get(key); 86 } 87 88 /** Caches the binding and generates it if it needs generation. */ 89 void tryRegisterBinding(B binding, ClassName factoryName, boolean explicit) { 90 tryToCacheBinding(binding); 91 tryToGenerateBinding(binding, factoryName, explicit); 92 } 93 94 /** 95 * Tries to generate a binding, not generating if it already is generated. For resolved 96 * bindings, this will try to generate the unresolved version of the binding. 97 */ 98 void tryToGenerateBinding(B binding, ClassName factoryName, boolean explicit) { 99 if (shouldGenerateBinding(binding, factoryName)) { 100 bindingsRequiringGeneration.offer(binding); 101 if (!explicit) { 102 messager.printMessage(Kind.NOTE, String.format( 103 "Generating a MembersInjector or Factory for %s. " 104 + "Prefer to run the dagger processor over that class instead.", 105 types.erasure(binding.key().type()))); // erasure to strip <T> from msgs. 106 } 107 } 108 } 109 110 /** Returns true if the binding needs to be generated. */ 111 private boolean shouldGenerateBinding(B binding, ClassName factoryName) { 112 return !binding.hasNonDefaultTypeParameters() 113 && elements.getTypeElement(factoryName.canonicalName()) == null 114 && !materializedBindingKeys.contains(binding.key()) 115 && !bindingsRequiringGeneration.contains(binding); 116 117 } 118 119 /** Caches the binding for future lookups by key. */ 120 private void tryToCacheBinding(B binding) { 121 // We only cache resolved bindings or unresolved bindings w/o type arguments. 122 // Unresolved bindings w/ type arguments aren't valid for the object graph. 123 if (binding.hasNonDefaultTypeParameters() 124 || binding.bindingTypeElement().getTypeParameters().isEmpty()) { 125 Key key = binding.key(); 126 Binding previousValue = bindingsByKey.put(key, binding); 127 checkState(previousValue == null || binding.equals(previousValue), 128 "couldn't register %s. %s was already registered for %s", 129 binding, previousValue, key); 130 } 131 } 132 } 133 134 private final BindingsCollection<ProvisionBinding> provisionBindings = new BindingsCollection<>(); 135 private final BindingsCollection<MembersInjectionBinding> membersInjectionBindings = 136 new BindingsCollection<>(); 137 138 InjectBindingRegistry(Elements elements, 139 Types types, 140 Messager messager, 141 ProvisionBinding.Factory provisionBindingFactory, 142 MembersInjectionBinding.Factory membersInjectionBindingFactory) { 143 this.elements = elements; 144 this.types = types; 145 this.messager = messager; 146 this.provisionBindingFactory = provisionBindingFactory; 147 this.membersInjectionBindingFactory = membersInjectionBindingFactory; 148 } 149 150 /** 151 * This method ensures that sources for all registered {@link Binding bindings} (either 152 * {@linkplain #registerBinding explicitly} or implicitly via 153 * {@link #getOrFindMembersInjectionBinding} or {@link #getOrFindProvisionBinding}) are generated. 154 */ 155 void generateSourcesForRequiredBindings(FactoryGenerator factoryGenerator, 156 MembersInjectorGenerator membersInjectorGenerator) throws SourceFileGenerationException { 157 provisionBindings.generateBindings(factoryGenerator); 158 membersInjectionBindings.generateBindings(membersInjectorGenerator); 159 } 160 161 ProvisionBinding registerBinding(ProvisionBinding binding) { 162 return registerBinding(binding, true); 163 } 164 165 MembersInjectionBinding registerBinding(MembersInjectionBinding binding) { 166 return registerBinding(binding, true); 167 } 168 169 /** 170 * Registers the binding for generation & later lookup. If the binding is resolved, we also 171 * attempt to register an unresolved version of it. 172 */ 173 private ProvisionBinding registerBinding(ProvisionBinding binding, boolean explicit) { 174 ClassName factoryName = generatedClassNameForBinding(binding); 175 provisionBindings.tryRegisterBinding(binding, factoryName, explicit); 176 if (binding.hasNonDefaultTypeParameters()) { 177 provisionBindings.tryToGenerateBinding(provisionBindingFactory.unresolve(binding), 178 factoryName, explicit); 179 } 180 return binding; 181 } 182 183 /** 184 * Registers the binding for generation & later lookup. If the binding is resolved, we also 185 * attempt to register an unresolved version of it. 186 */ 187 private MembersInjectionBinding registerBinding( 188 MembersInjectionBinding binding, boolean explicit) { 189 ClassName membersInjectorName = generatedClassNameForBinding(binding); 190 membersInjectionBindings.tryRegisterBinding(binding, membersInjectorName, explicit); 191 if (binding.hasNonDefaultTypeParameters()) { 192 membersInjectionBindings.tryToGenerateBinding( 193 membersInjectionBindingFactory.unresolve(binding), membersInjectorName, explicit); 194 } 195 return binding; 196 } 197 198 Optional<ProvisionBinding> getOrFindProvisionBinding(Key key) { 199 checkNotNull(key); 200 if (!key.isValidImplicitProvisionKey(types)) { 201 return Optional.absent(); 202 } 203 ProvisionBinding binding = provisionBindings.getBinding(key); 204 if (binding != null) { 205 return Optional.of(binding); 206 } 207 208 // ok, let's see if we can find an @Inject constructor 209 TypeElement element = MoreElements.asType(types.asElement(key.type())); 210 List<ExecutableElement> constructors = 211 ElementFilter.constructorsIn(element.getEnclosedElements()); 212 ImmutableSet<ExecutableElement> injectConstructors = FluentIterable.from(constructors) 213 .filter(new Predicate<ExecutableElement>() { 214 @Override public boolean apply(ExecutableElement input) { 215 return isAnnotationPresent(input, Inject.class); 216 } 217 }).toSet(); 218 switch (injectConstructors.size()) { 219 case 0: 220 // No constructor found. 221 return Optional.absent(); 222 case 1: 223 ProvisionBinding constructorBinding = provisionBindingFactory.forInjectConstructor( 224 Iterables.getOnlyElement(injectConstructors), Optional.of(key.type())); 225 return Optional.of(registerBinding(constructorBinding, false)); 226 default: 227 throw new IllegalStateException("Found multiple @Inject constructors: " 228 + injectConstructors); 229 } 230 } 231 232 MembersInjectionBinding getOrFindMembersInjectionBinding(Key key) { 233 checkNotNull(key); 234 // TODO(gak): is checking the kind enough? 235 checkArgument(key.isValidMembersInjectionKey()); 236 MembersInjectionBinding binding = membersInjectionBindings.getBinding(key); 237 if (binding != null) { 238 return binding; 239 } 240 return registerBinding(membersInjectionBindingFactory.forInjectedType( 241 MoreTypes.asDeclared(key.type()), Optional.of(key.type())), false); 242 } 243 } 244