Home | History | Annotate | Download | only in codegen
      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
     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 
     78     boolean isMultibinding() {
     79       return !this.equals(UNIQUE);
     80     }
     81   }
     82 
     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. */
     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    */
    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    */
    112   boolean isSyntheticBinding() {
    113     return bindingKind().equals(Kind.SYNTHETIC);
    114   }
    115 
    116   /** If this provision requires members injection, this will be the corresponding request. */
    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    */
    169   protected abstract Kind bindingKind();
    170 
    171   /**
    172    * A predicate that passes for bindings of a given kind.
    173    */
    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