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.MoreTypes; 19 import com.google.common.base.Equivalence; 20 import com.google.common.base.Equivalence.Wrapper; 21 import com.google.common.base.Function; 22 import com.google.common.base.Optional; 23 import com.google.common.base.Predicate; 24 import com.google.common.collect.ImmutableListMultimap; 25 import com.google.common.collect.ImmutableSetMultimap; 26 import com.google.common.collect.Iterables; 27 import com.google.common.collect.Multimaps; 28 import com.google.common.collect.Ordering; 29 import com.google.common.collect.Sets; 30 import com.google.common.util.concurrent.ListenableFuture; 31 import dagger.Component; 32 import dagger.MapKey; 33 import dagger.Provides; 34 import dagger.producers.Produces; 35 import dagger.producers.ProductionComponent; 36 import java.util.EnumSet; 37 import java.util.Set; 38 import javax.inject.Inject; 39 import javax.lang.model.element.AnnotationMirror; 40 import javax.lang.model.element.AnnotationValue; 41 import javax.lang.model.element.TypeElement; 42 import javax.lang.model.type.DeclaredType; 43 44 import static com.google.common.base.Preconditions.checkArgument; 45 import static com.google.common.base.Preconditions.checkNotNull; 46 import static dagger.internal.codegen.MapKeys.getMapKey; 47 import static dagger.internal.codegen.MapKeys.unwrapValue; 48 import static javax.lang.model.element.Modifier.STATIC; 49 50 /** 51 * An abstract class for a value object representing the mechanism by which a {@link Key} can be 52 * contributed to a dependency graph. 53 * 54 * @author Jesse Beder 55 * @since 2.0 56 */ 57 abstract class ContributionBinding extends Binding { 58 59 @Override implicitDependencies()60 Set<DependencyRequest> implicitDependencies() { 61 // Optimization: If we don't need the memberInjectionRequest, don't create more objects. 62 if (!membersInjectionRequest().isPresent()) { 63 return dependencies(); 64 } else { 65 // Optimization: Avoid creating an ImmutableSet+Builder just to union two things together. 66 return Sets.union(membersInjectionRequest().asSet(), dependencies()); 67 } 68 } 69 70 static enum ContributionType { 71 /** Represents map bindings. */ 72 MAP, 73 /** Represents set bindings. */ 74 SET, 75 /** Represents a valid non-collection binding. */ 76 UNIQUE; 77 isMultibinding()78 boolean isMultibinding() { 79 return !this.equals(UNIQUE); 80 } 81 } 82 contributionType()83 ContributionType contributionType() { 84 switch (provisionType()) { 85 case SET: 86 case SET_VALUES: 87 return ContributionType.SET; 88 case MAP: 89 return ContributionType.MAP; 90 case UNIQUE: 91 return ContributionType.UNIQUE; 92 default: 93 throw new AssertionError("Unknown provision type: " + provisionType()); 94 } 95 } 96 97 /** Returns the type that specifies this' nullability, absent if not nullable. */ nullableType()98 abstract Optional<DeclaredType> nullableType(); 99 100 /** 101 * If this is a provision request from an {@code @Provides} or {@code @Produces} method, this will 102 * be the element that contributed it. In the case of subclassed modules, this may differ than the 103 * binding's enclosed element, as this will return the subclass whereas the enclosed element will 104 * be the superclass. 105 */ contributedBy()106 abstract Optional<TypeElement> contributedBy(); 107 108 /** 109 * Returns whether this binding is synthetic, i.e., not explicitly tied to code, but generated 110 * implicitly by the framework. 111 */ isSyntheticBinding()112 boolean isSyntheticBinding() { 113 return bindingKind().equals(Kind.SYNTHETIC); 114 } 115 116 /** If this provision requires members injection, this will be the corresponding request. */ membersInjectionRequest()117 abstract Optional<DependencyRequest> membersInjectionRequest(); 118 119 /** 120 * The kind of contribution this binding represents. Defines which elements can specify this kind 121 * of contribution. 122 */ 123 enum Kind { 124 /** 125 * A binding that is not explicitly tied to an element, but generated implicitly by the 126 * framework. 127 */ 128 SYNTHETIC, 129 130 // Provision kinds 131 132 /** An {@link Inject}-annotated constructor. */ 133 INJECTION, 134 135 /** A {@link Provides}-annotated method. */ 136 PROVISION, 137 138 /** An implicit binding to a {@link Component @Component}-annotated type. */ 139 COMPONENT, 140 141 /** A provision method on a component's {@linkplain Component#dependencies() dependency}. */ 142 COMPONENT_PROVISION, 143 144 /** 145 * A subcomponent builder method on a component or subcomponent. 146 */ 147 SUBCOMPONENT_BUILDER, 148 149 // Production kinds 150 151 /** A {@link Produces}-annotated method that doesn't return a {@link ListenableFuture}. */ 152 IMMEDIATE, 153 154 /** A {@link Produces}-annotated method that returns a {@link ListenableFuture}. */ 155 FUTURE_PRODUCTION, 156 157 /** 158 * A production method on a production component's 159 * {@linkplain ProductionComponent#dependencies() dependency} that returns a 160 * {@link ListenableFuture}. Methods on production component dependencies that don't return a 161 * {@link ListenableFuture} are considered {@linkplain #PROVISION provision bindings}. 162 */ 163 COMPONENT_PRODUCTION, 164 } 165 166 /** 167 * The kind of this contribution binding. 168 */ bindingKind()169 protected abstract Kind bindingKind(); 170 171 /** 172 * A predicate that passes for bindings of a given kind. 173 */ isOfKind(final Kind kind)174 static Predicate<ContributionBinding> isOfKind(final Kind kind) { 175 return new Predicate<ContributionBinding>() { 176 @Override 177 public boolean apply(ContributionBinding binding) { 178 return binding.bindingKind().equals(kind); 179 }}; 180 } 181 182 /** The provision type that was used to bind the key. */ 183 abstract Provides.Type provisionType(); 184 185 /** 186 * The strategy for getting an instance of a factory for a {@link ContributionBinding}. 187 */ 188 enum FactoryCreationStrategy { 189 /** The factory class is an enum with one value named {@code INSTANCE}. */ 190 ENUM_INSTANCE, 191 /** The factory must be created by calling the constructor. */ 192 CLASS_CONSTRUCTOR, 193 } 194 195 /** 196 * Returns {@link FactoryCreationStrategy#ENUM_INSTANCE} if the binding has no dependencies and 197 * is a static provision binding or an {@link Inject @Inject} constructor binding. Otherwise 198 * returns {@link FactoryCreationStrategy#CLASS_CONSTRUCTOR}. 199 */ 200 FactoryCreationStrategy factoryCreationStrategy() { 201 switch (bindingKind()) { 202 case PROVISION: 203 return implicitDependencies().isEmpty() && bindingElement().getModifiers().contains(STATIC) 204 ? FactoryCreationStrategy.ENUM_INSTANCE 205 : FactoryCreationStrategy.CLASS_CONSTRUCTOR; 206 207 case INJECTION: 208 return implicitDependencies().isEmpty() 209 ? FactoryCreationStrategy.ENUM_INSTANCE 210 : FactoryCreationStrategy.CLASS_CONSTRUCTOR; 211 212 default: 213 return FactoryCreationStrategy.CLASS_CONSTRUCTOR; 214 } 215 } 216 217 /** 218 * Returns the {@link ContributionType}s represented by a given {@link ContributionBinding} 219 * collection. 220 */ 221 static <B extends ContributionBinding> 222 ImmutableListMultimap<ContributionType, B> contributionTypesFor( 223 Iterable<? extends B> bindings) { 224 ImmutableListMultimap.Builder<ContributionType, B> builder = ImmutableListMultimap.builder(); 225 builder.orderKeysBy(Ordering.<ContributionType>natural()); 226 for (B binding : bindings) { 227 builder.put(binding.contributionType(), binding); 228 } 229 return builder.build(); 230 } 231 232 /** 233 * Returns a single {@link ContributionType} represented by a given collection of 234 * {@link ContributionBinding}s. 235 * 236 * @throws IllegalArgumentException if the given bindings are not all of one type 237 */ 238 static ContributionType contributionTypeFor(Iterable<ContributionBinding> bindings) { 239 checkNotNull(bindings); 240 checkArgument(!Iterables.isEmpty(bindings), "no bindings"); 241 Set<ContributionType> types = EnumSet.noneOf(ContributionType.class); 242 for (ContributionBinding binding : bindings) { 243 types.add(binding.contributionType()); 244 } 245 if (types.size() > 1) { 246 throw new IllegalArgumentException( 247 String.format(ErrorMessages.MULTIPLE_CONTRIBUTION_TYPES_FORMAT, types)); 248 } 249 return Iterables.getOnlyElement(types); 250 } 251 252 /** 253 * Indexes map-multibindings by map key (the result of calling 254 * {@link AnnotationValue#getValue()} on a single member or the whole {@link AnnotationMirror} 255 * itself, depending on {@link MapKey#unwrapValue()}). 256 */ 257 static ImmutableSetMultimap<Object, ContributionBinding> indexMapBindingsByMapKey( 258 Set<ContributionBinding> mapBindings) { 259 return ImmutableSetMultimap.copyOf( 260 Multimaps.index( 261 mapBindings, 262 new Function<ContributionBinding, Object>() { 263 @Override 264 public Object apply(ContributionBinding mapBinding) { 265 AnnotationMirror mapKey = getMapKey(mapBinding.bindingElement()).get(); 266 Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey); 267 return unwrappedValue.isPresent() ? unwrappedValue.get().getValue() : mapKey; 268 } 269 })); 270 } 271 272 /** 273 * Indexes map-multibindings by map key annotation type. 274 */ 275 static ImmutableSetMultimap<Wrapper<DeclaredType>, ContributionBinding> 276 indexMapBindingsByAnnotationType(Set<ContributionBinding> mapBindings) { 277 return ImmutableSetMultimap.copyOf( 278 Multimaps.index( 279 mapBindings, 280 new Function<ContributionBinding, Equivalence.Wrapper<DeclaredType>>() { 281 @Override 282 public Equivalence.Wrapper<DeclaredType> apply(ContributionBinding mapBinding) { 283 return MoreTypes.equivalence() 284 .wrap(getMapKey(mapBinding.bindingElement()).get().getAnnotationType()); 285 } 286 })); 287 } 288 } 289