com.google.inject.daggeradapter.DaggerMethodScanner Maven / Gradle / Ivy
/*
* Copyright (C) 2015 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 com.google.inject.daggeradapter;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.inject.daggeradapter.Annotations.getAnnotatedAnnotation;
import static com.google.inject.daggeradapter.Keys.parameterKey;
import static com.google.inject.daggeradapter.SupportedAnnotations.supportedBindingAnnotations;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.OptionalBinder;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.ModuleAnnotatedMethodScanner;
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.MapKey;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.IntoSet;
import dagger.multibindings.Multibinds;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.inject.Scope;
/**
* A scanner to process provider methods on Dagger modules.
*
* @author [email protected] (Christian Gruber)
*/
final class DaggerMethodScanner extends ModuleAnnotatedMethodScanner {
static DaggerMethodScanner create(Predicate predicate) {
return new DaggerMethodScanner(predicate);
}
private final Predicate predicate;
@Override
public ImmutableSet> annotationClasses() {
return supportedBindingAnnotations();
}
@Override
public Key prepareMethod(
Binder binder, Annotation annotation, Key key, InjectionPoint injectionPoint) {
Method method = (Method) injectionPoint.getMember();
if (!predicate.apply(method)) {
return null;
}
Class extends Annotation> annotationType = annotation.annotationType();
if (annotationType.equals(Provides.class)) {
return prepareProvidesKey(binder, method, key);
} else if (annotationType.equals(Binds.class)) {
configureBindsKey(binder, method, key);
return null;
} else if (annotationType.equals(Multibinds.class)) {
configureMultibindsKey(binder, method, key);
return null;
} else if (annotationType.equals(BindsOptionalOf.class)) {
OptionalBinder.newOptionalBinder(binder, key);
return null;
}
throw new UnsupportedOperationException(annotation.toString());
}
private Key prepareProvidesKey(Binder binder, Method method, Key key) {
key = processMultibindingAnnotations(binder, method, key);
return key;
}
private void configureBindsKey(Binder binder, Method method, Key key) {
// the Dagger processor already validates the assignability of these two keys. parameterKey()
// has no way to infer the correct type parameter, so we use rawtypes instead.
@SuppressWarnings({"unchecked", "rawtypes"})
ScopedBindingBuilder scopedBindingBuilder =
binder
.bind((Key) processMultibindingAnnotations(binder, method, key))
.to(parameterKey(method.getParameters()[0]));
getAnnotatedAnnotation(method, Scope.class)
.ifPresent(scope -> scopedBindingBuilder.in(scope.annotationType()));
}
private static Key processMultibindingAnnotations(
Binder binder, Method method, Key key) {
if (method.isAnnotationPresent(IntoSet.class)) {
return processSetBinding(binder, key);
} else if (method.isAnnotationPresent(IntoMap.class)) {
return processMapBinding(binder, key, method);
}
return key;
}
private static Key processSetBinding(Binder binder, Key key) {
Multibinder setBinder = newSetBinder(binder, key.getTypeLiteral(), key.getAnnotation());
Key contributionKey = key.withAnnotation(UniqueAnnotations.create());
setBinder.addBinding().to(contributionKey);
return contributionKey;
}
private static Key processMapBinding(Binder binder, Key key, Method method) {
MapKeyData mapKeyData = mapKeyData(method);
MapBinder mapBinder =
newMapBinder(binder, mapKeyData.typeLiteral, key.getTypeLiteral(), key.getAnnotation());
Key contributionKey = key.withAnnotation(UniqueAnnotations.create());
mapBinder.addBinding(mapKeyData.key).to(contributionKey);
return contributionKey;
}
private static MapKeyData mapKeyData(Method method) {
Optional mapKeyOpt = getAnnotatedAnnotation(method, MapKey.class);
checkState(
mapKeyOpt.isPresent(),
"Missing @MapKey annotation on method %s (make sure the annotation has RUNTIME rentention)",
method);
Annotation mapKey = mapKeyOpt.get();
MapKey mapKeyDefinition = mapKey.annotationType().getAnnotation(MapKey.class);
if (!mapKeyDefinition.unwrapValue()) {
return MapKeyData.create(TypeLiteral.get(mapKey.annotationType()), mapKey);
}
Method mapKeyValueMethod =
getOnlyElement(Arrays.asList(mapKey.annotationType().getDeclaredMethods()));
Object mapKeyValue;
try {
mapKeyValue = mapKeyValueMethod.invoke(mapKey);
} catch (ReflectiveOperationException e) {
throw new UnsupportedOperationException("Cannot extract map key value", e);
}
return MapKeyData.create(
TypeLiteral.get(mapKeyValueMethod.getGenericReturnType()), mapKeyValue);
}
private static class MapKeyData {
final TypeLiteral typeLiteral;
final K key;
MapKeyData(TypeLiteral typeLiteral, K key) {
this.typeLiteral = typeLiteral;
this.key = key;
}
// We can't verify the compatibility of the type arguments here, but by definition they must be
// aligned
@SuppressWarnings({"unchecked", "rawtypes"})
static MapKeyData create(TypeLiteral> typeLiteral, Object key) {
return new MapKeyData(typeLiteral, key);
}
}
private static Multibinder newSetBinder(
Binder binder, TypeLiteral typeLiteral, Annotation possibleAnnotation) {
return possibleAnnotation == null
? Multibinder.newSetBinder(binder, typeLiteral)
: Multibinder.newSetBinder(binder, typeLiteral, possibleAnnotation);
}
private static MapBinder newMapBinder(
Binder binder,
TypeLiteral keyType,
TypeLiteral valueType,
Annotation possibleAnnotation) {
return possibleAnnotation == null
? MapBinder.newMapBinder(binder, keyType, valueType)
: MapBinder.newMapBinder(binder, keyType, valueType, possibleAnnotation);
}
private void configureMultibindsKey(Binder binder, Method method, Key key) {
Class> rawReturnType = method.getReturnType();
ImmutableList extends TypeLiteral>> typeParameters =
Arrays.stream(((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments())
.map(TypeLiteral::get)
.collect(toImmutableList());
if (rawReturnType.equals(Set.class)) {
newSetBinder(binder, typeParameters.get(0), key.getAnnotation());
} else if (rawReturnType.equals(Map.class)) {
newMapBinder(binder, typeParameters.get(0), typeParameters.get(1), key.getAnnotation());
} else {
throw new AssertionError(
"@dagger.Multibinds can only be used with Sets or Map, found: "
+ method.getGenericReturnType());
}
}
@Override
public boolean equals(Object object) {
if (object instanceof DaggerMethodScanner) {
DaggerMethodScanner that = (DaggerMethodScanner) object;
return this.predicate.equals(that.predicate);
}
return false;
}
@Override
public int hashCode() {
return predicate.hashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("predicate", predicate).toString();
}
private DaggerMethodScanner(Predicate predicate) {
this.predicate = predicate;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy