/*
* 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 extends Binding> 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;
}
}