package tc.oc.commons.core.inject;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.inject.Qualifier;
import com.google.common.cache.LoadingCache;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.TypeToken;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import tc.oc.commons.core.reflect.Types;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.CacheUtils;
import tc.oc.commons.core.util.Optionals;
public final class Keys {
private Keys() {}
public static Key<?> fieldType(TypeLiteral<?> owner, Field field, Errors errors) throws ErrorsException {
return Annotations.getKey(owner.getFieldType(field), field, field.getAnnotations(), errors);
}
public static <T> Key<T> returnType(Invokable<?, T> method, Errors errors) throws ErrorsException {
return (Key<T>) Annotations.getKey(Types.toLiteral(method.getReturnType()), method, method.getAnnotations(), errors);
}
public static <T> Key<T> returnType(TypeLiteral<?> decl, Method method, Errors errors) throws ErrorsException {
return (Key<T>) Annotations.getKey(decl.getReturnType(method), method, method.getAnnotations(), errors);
}
public static <T> Key<T> get(TypeLiteral<T> type, @Nullable Annotation annotation) {
return annotation == null ? Key.get(type) : Key.get(type, annotation);
}
public static <T> Key<T> get(Key<T> type, @Nullable Annotation annotation) {
return get(type.getTypeLiteral(), annotation);
}
public static Set<Key<?>> get(Class<?>... types) {
return Stream.of(types).map(Key::get).collect(Collectors.toImmutableSet());
}
public static <T> Key<T> get(TypeToken<T> type) {
return Key.get(Types.toLiteral(type));
}
public static <T> Key<T> forInstance(T instance) {
return forInstance((Class<T>) instance.getClass(), instance);
}
public static <T> Key<T> forInstance(Class<T> type, T instance) {
return forInstance(TypeLiteral.get(type), instance);
}
public static <T> Key<T> forInstance(TypeLiteral<T> type, T instance) {
return Key.get(type, new InstanceQualifierImpl<>(instance));
}
public static <T> Key<Set<T>> setOf(Key<T> key) {
return get(Types.setOf(key.getTypeLiteral()), key.getAnnotation());
}
public static <T> Key<List<T>> listOf(Key<T> key) {
return get(Types.listOf(key.getTypeLiteral()), key.getAnnotation());
}
private static final LoadingCache<TypeLiteral, Key> OPTIONAL_BY_TYPE_LITERAL = CacheUtils.newCache(
type -> Key.get(Optionals.optionalType(type))
);
private static final LoadingCache<Type, Key> OPTIONAL_BY_TYPE = CacheUtils.newCache(
type -> OPTIONAL_BY_TYPE_LITERAL.get(TypeLiteral.get(type))
);
private static final LoadingCache<Key, Key> OPTIONAL_BY_KEY = CacheUtils.newCache(
key -> OPTIONAL_BY_TYPE_LITERAL.get(key.getTypeLiteral())
);
public static <T> Key<Optional<T>> optional(TypeLiteral<T> type) {
return OPTIONAL_BY_TYPE_LITERAL.getUnchecked(type);
}
public static <T> Key<Optional<T>> optional(Class<T> type) {
return OPTIONAL_BY_TYPE.getUnchecked(type);
}
public static <T> Key<Optional<T>> optional(Key<T> key) {
return OPTIONAL_BY_KEY.getUnchecked(key);
}
public static Stream<Key<Optional<?>>> optional(Stream<Key<?>> keys) {
return keys.map(OPTIONAL_BY_KEY::getUnchecked);
}
public static Set<Key<Optional<?>>> optional(Class<?>... types) {
return (Set) Stream.of(types).map(Keys::optional).collect(Collectors.toImmutableSet());
}
}
@Qualifier @BindingAnnotation @Retention(RetentionPolicy.RUNTIME)
@interface InstanceQualifier {
boolean isWaterWet();
}
class InstanceQualifierImpl<T> implements InstanceQualifier {
final T instance;
InstanceQualifierImpl(T instance) {
this.instance = instance;
}
@Override
public int hashCode() {
return instance.hashCode();
}
@Override
public boolean equals(Object that) {
return this == that || (
that instanceof InstanceQualifierImpl &&
instance.equals(((InstanceQualifierImpl) that).instance)
);
}
@Override
public boolean isWaterWet() {
return true;
}
@Override
public Class<? extends Annotation> annotationType() {
return InstanceQualifier.class;
}
}