Java程序  |  289行  |  9.99 KB

/*
 * Copyright (C) 2014 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 dagger.internal.codegen;

import com.google.auto.common.MoreTypes;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import dagger.Component;
import dagger.MapKey;
import dagger.Provides;
import dagger.producers.Produces;
import dagger.producers.ProductionComponent;
import java.util.EnumSet;
import java.util.Set;
import javax.inject.Inject;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.MapKeys.getMapKey;
import static dagger.internal.codegen.MapKeys.unwrapValue;
import static javax.lang.model.element.Modifier.STATIC;

/**
 * An abstract class for a value object representing the mechanism by which a {@link Key} can be
 * contributed to a dependency graph.
 *
 * @author Jesse Beder
 * @since 2.0
 */
abstract class ContributionBinding extends Binding {
  
  @Override
  Set<DependencyRequest> implicitDependencies() {
    // Optimization: If we don't need the memberInjectionRequest, don't create more objects.
    if (!membersInjectionRequest().isPresent()) {
      return dependencies();
    } else {
      // Optimization: Avoid creating an ImmutableSet+Builder just to union two things together.
      return Sets.union(membersInjectionRequest().asSet(), dependencies());
    }
  }

  static enum ContributionType {
    /** Represents map bindings. */
    MAP,
    /** Represents set bindings. */
    SET,
    /** Represents a valid non-collection binding. */
    UNIQUE;

    boolean isMultibinding() {
      return !this.equals(UNIQUE);
    }
  }

  ContributionType contributionType() {
    switch (provisionType()) {
      case SET:
      case SET_VALUES:
        return ContributionType.SET;
      case MAP:
        return ContributionType.MAP;
      case UNIQUE:
        return ContributionType.UNIQUE;
      default:
        throw new AssertionError("Unknown provision type: " + provisionType());
    }
  }
  
  /** Returns the type that specifies this' nullability, absent if not nullable. */
  abstract Optional<DeclaredType> nullableType();

  /**
   * If this is a provision request from an {@code @Provides} or {@code @Produces} method, this will
   * be the element that contributed it. In the case of subclassed modules, this may differ than the
   * binding's enclosed element, as this will return the subclass whereas the enclosed element will
   * be the superclass.
   */
  abstract Optional<TypeElement> contributedBy();

  /**
   * Returns whether this binding is synthetic, i.e., not explicitly tied to code, but generated
   * implicitly by the framework.
   */
  boolean isSyntheticBinding() {
    return bindingKind().equals(Kind.SYNTHETIC);
  }

  /** If this provision requires members injection, this will be the corresponding request. */
  abstract Optional<DependencyRequest> membersInjectionRequest();

  /**
   * The kind of contribution this binding represents. Defines which elements can specify this kind
   * of contribution.
   */
  enum Kind {
    /**
     * A binding that is not explicitly tied to an element, but generated implicitly by the
     * framework.
     */
    SYNTHETIC,

    // Provision kinds

    /** An {@link Inject}-annotated constructor. */
    INJECTION,

    /** A {@link Provides}-annotated method. */
    PROVISION,

    /** An implicit binding to a {@link Component @Component}-annotated type. */
    COMPONENT,

    /** A provision method on a component's {@linkplain Component#dependencies() dependency}. */
    COMPONENT_PROVISION,

    /**
     * A subcomponent builder method on a component or subcomponent.
     */
    SUBCOMPONENT_BUILDER,

    // Production kinds

    /** A {@link Produces}-annotated method that doesn't return a {@link ListenableFuture}. */
    IMMEDIATE,

    /** A {@link Produces}-annotated method that returns a {@link ListenableFuture}. */
    FUTURE_PRODUCTION,

    /**
     * A production method on a production component's
     * {@linkplain ProductionComponent#dependencies() dependency} that returns a
     * {@link ListenableFuture}. Methods on production component dependencies that don't return a
     * {@link ListenableFuture} are considered {@linkplain #PROVISION provision bindings}.
     */
    COMPONENT_PRODUCTION,
  }

  /**
   * The kind of this contribution binding.
   */
  protected abstract Kind bindingKind();
  
  /**
   * A predicate that passes for bindings of a given kind.
   */
  static Predicate<ContributionBinding> isOfKind(final Kind kind) {
    return new Predicate<ContributionBinding>() {
      @Override
      public boolean apply(ContributionBinding binding) {
        return binding.bindingKind().equals(kind);
      }};
  }

  /** The provision type that was used to bind the key. */
  abstract Provides.Type provisionType();

  /**
   * The strategy for getting an instance of a factory for a {@link ContributionBinding}.
   */
  enum FactoryCreationStrategy {
    /** The factory class is an enum with one value named {@code INSTANCE}. */
    ENUM_INSTANCE,
    /** The factory must be created by calling the constructor. */
    CLASS_CONSTRUCTOR,
  }

  /**
   * Returns {@link FactoryCreationStrategy#ENUM_INSTANCE} if the binding has no dependencies and
   * is a static provision binding or an {@link Inject @Inject} constructor binding. Otherwise
   * returns {@link FactoryCreationStrategy#CLASS_CONSTRUCTOR}.
   */
  FactoryCreationStrategy factoryCreationStrategy() {
    switch (bindingKind()) {
      case PROVISION:
        return implicitDependencies().isEmpty() && bindingElement().getModifiers().contains(STATIC)
            ? FactoryCreationStrategy.ENUM_INSTANCE
            : FactoryCreationStrategy.CLASS_CONSTRUCTOR;

      case INJECTION:
        return implicitDependencies().isEmpty()
            ? FactoryCreationStrategy.ENUM_INSTANCE
            : FactoryCreationStrategy.CLASS_CONSTRUCTOR;

      default:
        return FactoryCreationStrategy.CLASS_CONSTRUCTOR;
    }
  }

  /**
   * Returns the {@link ContributionType}s represented by a given {@link ContributionBinding}
   * collection.
   */
  static <B extends ContributionBinding>
      ImmutableListMultimap<ContributionType, B> contributionTypesFor(
          Iterable<? extends B> bindings) {
    ImmutableListMultimap.Builder<ContributionType, B> builder = ImmutableListMultimap.builder();
    builder.orderKeysBy(Ordering.<ContributionType>natural());
    for (B binding : bindings) {
      builder.put(binding.contributionType(), binding);
    }
    return builder.build();
  }

  /**
   * Returns a single {@link ContributionType} represented by a given collection of
   * {@link ContributionBinding}s.
   *
   * @throws IllegalArgumentException if the given bindings are not all of one type
   */
  static ContributionType contributionTypeFor(Iterable<ContributionBinding> bindings) {
    checkNotNull(bindings);
    checkArgument(!Iterables.isEmpty(bindings), "no bindings");
    Set<ContributionType> types = EnumSet.noneOf(ContributionType.class);
    for (ContributionBinding binding : bindings) {
      types.add(binding.contributionType());
    }
    if (types.size() > 1) {
      throw new IllegalArgumentException(
          String.format(ErrorMessages.MULTIPLE_CONTRIBUTION_TYPES_FORMAT, types));
    }
    return Iterables.getOnlyElement(types);
  }

  /**
   * Indexes map-multibindings by map key (the result of calling
   * {@link AnnotationValue#getValue()} on a single member or the whole {@link AnnotationMirror}
   * itself, depending on {@link MapKey#unwrapValue()}).
   */
  static ImmutableSetMultimap<Object, ContributionBinding> indexMapBindingsByMapKey(
      Set<ContributionBinding> mapBindings) {
    return ImmutableSetMultimap.copyOf(
        Multimaps.index(
            mapBindings,
            new Function<ContributionBinding, Object>() {
              @Override
              public Object apply(ContributionBinding mapBinding) {
                AnnotationMirror mapKey = getMapKey(mapBinding.bindingElement()).get();
                Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey);
                return unwrappedValue.isPresent() ? unwrappedValue.get().getValue() : mapKey;
              }
            }));
  }

  /**
   * Indexes map-multibindings by map key annotation type.
   */
  static ImmutableSetMultimap<Wrapper<DeclaredType>, ContributionBinding>
      indexMapBindingsByAnnotationType(Set<ContributionBinding> mapBindings) {
    return ImmutableSetMultimap.copyOf(
        Multimaps.index(
            mapBindings,
            new Function<ContributionBinding, Equivalence.Wrapper<DeclaredType>>() {
              @Override
              public Equivalence.Wrapper<DeclaredType> apply(ContributionBinding mapBinding) {
                return MoreTypes.equivalence()
                    .wrap(getMapKey(mapBinding.bindingElement()).get().getAnnotationType());
              }
            }));
  }
}