/* * 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.MoreElements; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import dagger.MembersInjector; import dagger.producers.Producer; import java.util.List; import java.util.Set; import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.WildcardType; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; import static javax.lang.model.element.Modifier.PUBLIC; /** * An abstract type for classes representing a Dagger binding. Particularly, contains the * {@link Element} that generated the binding and the {@link DependencyRequest} instances that are * required to satisfy the binding, but leaves the specifics of the mechanism of the binding * to the subtypes. * * @author Gregory Kick * @since 2.0 */ abstract class Binding { /** * The subtype of this binding. */ enum Type implements Predicate { /** A binding with this type is a {@link ProvisionBinding}. */ PROVISION(Provider.class), /** A binding with this type is a {@link MembersInjectionBinding}. */ MEMBERS_INJECTION(MembersInjector.class), /** A binding with this type is a {@link ProductionBinding}. */ PRODUCTION(Producer.class), ; private final Class frameworkClass; private Type(Class frameworkClass) { this.frameworkClass = frameworkClass; } /** * Returns the framework class associated with bindings of this type. */ Class frameworkClass() { return frameworkClass; } BindingKey.Kind bindingKeyKind() { switch (this) { case MEMBERS_INJECTION: return BindingKey.Kind.MEMBERS_INJECTION; case PROVISION: case PRODUCTION: return BindingKey.Kind.CONTRIBUTION; default: throw new AssertionError(); } } @Override public boolean apply(Binding binding) { return this.equals(binding.bindingType()); } } abstract Binding.Type bindingType(); /** * Returns the framework class associated with this binding. */ Class frameworkClass() { return bindingType().frameworkClass(); } static Optional bindingPackageFor(Iterable bindings) { ImmutableSet.Builder bindingPackagesBuilder = ImmutableSet.builder(); for (Binding binding : bindings) { bindingPackagesBuilder.addAll(binding.bindingPackage().asSet()); } ImmutableSet bindingPackages = bindingPackagesBuilder.build(); switch (bindingPackages.size()) { case 0: return Optional.absent(); case 1: return Optional.of(bindingPackages.iterator().next()); default: throw new IllegalArgumentException(); } } /** The {@link Key} that is provided by this binding. */ protected abstract Key key(); BindingKey bindingKey() { return BindingKey.create(bindingType().bindingKeyKind(), key()); } /** Returns the {@link Element} instance that is responsible for declaring the binding. */ abstract Element bindingElement(); /** The type enclosing the binding {@link #bindingElement()}. */ TypeElement bindingTypeElement() { return BINDING_TYPE_ELEMENT.visit(bindingElement()); } private static final ElementVisitor BINDING_TYPE_ELEMENT = new SimpleElementVisitor6() { @Override protected TypeElement defaultAction(Element e, Void p) { return visit(e.getEnclosingElement()); } @Override public TypeElement visitType(TypeElement e, Void p) { return e; } }; /** * The explicit set of {@link DependencyRequest dependencies} required to satisfy this binding. */ abstract ImmutableSet dependencies(); /** * The set of {@link DependencyRequest dependencies} required to satisfy this binding. This is a * superset of {@link #dependencies()}. This returns an unmodifiable set. */ abstract Set implicitDependencies(); /** * Returns the name of the package in which this binding must be managed. E.g.: a binding * may reference non-public types. */ abstract Optional bindingPackage(); protected static Optional findBindingPackage(Key bindingKey) { Set packages = nonPublicPackageUse(bindingKey.type()); switch (packages.size()) { case 0: return Optional.absent(); case 1: return Optional.of(packages.iterator().next()); default: throw new IllegalStateException(); } } private static Set nonPublicPackageUse(TypeMirror typeMirror) { ImmutableSet.Builder packages = ImmutableSet.builder(); typeMirror.accept(new SimpleTypeVisitor6>() { @Override public Void visitArray(ArrayType t, ImmutableSet.Builder p) { return t.getComponentType().accept(this, p); } @Override public Void visitDeclared(DeclaredType t, ImmutableSet.Builder p) { for (TypeMirror typeArgument : t.getTypeArguments()) { typeArgument.accept(this, p); } // TODO(gak): address public nested types in non-public types TypeElement typeElement = MoreElements.asType(t.asElement()); if (!typeElement.getModifiers().contains(PUBLIC)) { PackageElement elementPackage = MoreElements.getPackage(typeElement); Name qualifiedName = elementPackage.getQualifiedName(); p.add(qualifiedName.toString()); } // Also make sure enclosing types are visible, otherwise we're fooled by // class Foo { public class Bar } // (Note: we can't use t.getEnclosingType() because it doesn't work!) typeElement.getEnclosingElement().asType().accept(this, p); return null; } @Override public Void visitWildcard(WildcardType t, ImmutableSet.Builder p) { if (t.getExtendsBound() != null) { t.getExtendsBound().accept(this, p); } if (t.getSuperBound() != null) { t.getSuperBound().accept(this, p); } return null; } }, packages); return packages.build(); } /** * Returns true if this is a binding for a key that has a different type parameter list than the * element it's providing. */ abstract boolean hasNonDefaultTypeParameters(); /** * The scope of this binding. */ Scope scope() { return Scope.unscoped(); } // TODO(sameb): Remove the TypeElement parameter and pull it from the TypeMirror. static boolean hasNonDefaultTypeParameters(TypeElement element, TypeMirror type, Types types) { // If the element has no type parameters, nothing can be wrong. if (element.getTypeParameters().isEmpty()) { return false; } List defaultTypes = Lists.newArrayList(); for (TypeParameterElement parameter : element.getTypeParameters()) { defaultTypes.add(parameter.asType()); } List actualTypes = type.accept( new SimpleTypeVisitor6, Void>() { @Override protected List defaultAction(TypeMirror e, Void p) { return ImmutableList.of(); } @Override public List visitDeclared(DeclaredType t, Void p) { return ImmutableList.copyOf(t.getTypeArguments()); } }, null); // The actual type parameter size can be different if the user is using a raw type. if (defaultTypes.size() != actualTypes.size()) { return true; } for (int i = 0; i < defaultTypes.size(); i++) { if (!types.isSameType(defaultTypes.get(i), actualTypes.get(i))) { return true; } } return false; } }