/* * Copyright 2014-2016 Squarespace, 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.squarespace.jersey2.guice; import static com.squarespace.jersey2.guice.GuiceInjectionResolver.GUICE_RESOLVER_NAME; import static org.glassfish.hk2.api.InjectionResolver.SYSTEM_RESOLVER_NAME; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.inject.Qualifier; import javax.inject.Singleton; import org.glassfish.hk2.api.ActiveDescriptor; import org.glassfish.hk2.api.DescriptorVisibility; import org.glassfish.hk2.api.Injectee; import org.glassfish.hk2.api.InjectionResolver; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.NamedImpl; import org.glassfish.hk2.utilities.reflection.ParameterizedTypeImpl; import org.glassfish.hk2.utilities.reflection.ReflectionHelper; import org.jvnet.hk2.annotations.Optional; import org.jvnet.hk2.internal.ConstantActiveDescriptor; import com.google.inject.BindingAnnotation; import com.google.inject.Guice; import com.google.inject.Key; import com.google.inject.internal.Nullability; class BindingUtils { private BindingUtils() {} /** * Creates and returns a {@link InjectionResolver} for {@link javax.inject.Inject}. * * @see javax.inject.Inject * @see InjectionResolver */ public static ActiveDescriptor<InjectionResolver<javax.inject.Inject>> newThreeThirtyInjectionResolverDescriptor(ServiceLocator locator) { GuiceThreeThirtyResolver resolver = new GuiceThreeThirtyResolver(locator); Set<Annotation> qualifiers = Collections.<Annotation>singleton( new NamedImpl(SYSTEM_RESOLVER_NAME)); return newActiveDescriptor(locator, resolver, qualifiers, SYSTEM_RESOLVER_NAME, javax.inject.Inject.class); } /** * Creates and returns a {@link InjectionResolver} for {@link com.google.inject.Inject} * * @see #newThreeThirtyInjectionResolverDescriptor(ServiceLocator) * @see com.google.inject.Inject * @see InjectionResolver */ public static ActiveDescriptor<InjectionResolver<com.google.inject.Inject>> newGuiceInjectionResolverDescriptor(ServiceLocator locator, ActiveDescriptor<? extends InjectionResolver<?>> threeThirtyResolver) { GuiceInjectionResolver resolver = new GuiceInjectionResolver(threeThirtyResolver); Set<Annotation> qualifiers = Collections.emptySet(); return newActiveDescriptor(locator, resolver, qualifiers, GUICE_RESOLVER_NAME, com.google.inject.Inject.class); } /** * @see #newThreeThirtyInjectionResolverDescriptor(ServiceLocator) * @see #newGuiceInjectionResolverDescriptor(ServiceLocator, ActiveDescriptor) */ private static <T extends Annotation> ActiveDescriptor<InjectionResolver<T>> newActiveDescriptor(ServiceLocator locator, InjectionResolver<T> resolver, Set<Annotation> qualifiers, String name, Class<? extends T> clazz) { Set<Type> contracts = Collections.<Type>singleton( new ParameterizedTypeImpl(InjectionResolver.class, clazz)); ActiveDescriptor<InjectionResolver<T>> descriptor = new ConstantActiveDescriptor<InjectionResolver<T>>( resolver, contracts, Singleton.class, name, qualifiers, DescriptorVisibility.NORMAL, 0, (Boolean)null, (Boolean)null, (String)null, locator.getLocatorId(), (Map<String, List<String>>)null); return descriptor; } /** * Returns {@code true} if the given {@link Injectee} can be {@code null}. * * @see Optional * @see Injectee#isOptional() * @see Nullable * @see com.google.inject.Inject#optional() * @see Nullability#allowsNull(Annotation[]) */ public static boolean isNullable(Injectee injectee) { // HK2's optional if (injectee.isOptional()) { return true; } // Guice's optional AnnotatedElement element = injectee.getParent(); if (isGuiceOptional(element)) { return true; } // Any @Nullable? int position = injectee.getPosition(); if (element instanceof Field) { return Nullability.allowsNull(((Field)element).getAnnotations()); } else if (element instanceof Method) { Annotation annotations[][] = ((Method)element).getParameterAnnotations(); return Nullability.allowsNull(annotations[position]); } else if (element instanceof Constructor<?>) { Annotation annotations[][] = ((Constructor<?>)element).getParameterAnnotations(); return Nullability.allowsNull(annotations[position]); } return false; } /** * Returns {@code true} if the given {@link AnnotatedElement} has a * {@link com.google.inject.Inject} {@link Annotation} and it's marked * as being optional. * * @see com.google.inject.Inject#optional() */ private static boolean isGuiceOptional(AnnotatedElement element) { com.google.inject.Inject inject = element.getAnnotation(com.google.inject.Inject.class); if (inject != null) { return inject.optional(); } return false; } /** * Returns {@code true} if the {@link Injectee} has a HK2 SPI * {@link org.jvnet.hk2.annotations.Contract} annotation. * * @see org.jvnet.hk2.annotations.Contract */ public static boolean isHk2Contract(Injectee injectee) { Type type = injectee.getRequiredType(); return isContact(type, org.jvnet.hk2.annotations.Contract.class); } /** * Returns {@code true} if the {@link Injectee} has a Jersey SPI * {@link org.glassfish.jersey.spi.Contract} annotation. * * @see org.glassfish.jersey.spi.Contract */ public static boolean isJerseyContract(Injectee injectee) { Type type = injectee.getRequiredType(); return isContact(type, org.glassfish.jersey.spi.Contract.class); } private static boolean isContact(Type type, Class<? extends Annotation> annotationType) { if (type instanceof Class<?>) { return ((Class<?>)type).isAnnotationPresent(annotationType); } if (type instanceof ParameterizedType) { Type rawType = ((ParameterizedType)type).getRawType(); return isContact(rawType, annotationType); } return false; } /** * Turns a {@link Guice} {@link com.google.inject.name.Named} into a * JSR-330's {@link javax.inject.Named} instance. */ public static javax.inject.Named toThreeThirtyNamed(com.google.inject.name.Named qualifier) { return new NamedImpl(qualifier.value()); } /** * Creates and returns a {@link Key} from the given {@link Injectee}. */ public static Key<?> toKey(Injectee injectee) { Type type = injectee.getRequiredType(); Set<Annotation> qualifiers = getQualifiers(injectee); return newKey(type, qualifiers); } /** * Returns a {@link Set} of qualifier {@link Annotation}s for * the given {@link Key}. * * NOTE: The {@link Set} will be either empty or will have * at most one {@link Annotation} in it. */ public static <T> Set<Annotation> getQualifiers(Key<T> key) { Annotation annotation = key.getAnnotation(); if (annotation != null) { // Replace 'com.google.inject.name.Named' with 'javax.inject.Named' if (annotation instanceof com.google.inject.name.Named) { annotation = toThreeThirtyNamed((com.google.inject.name.Named)annotation); } Class<? extends Annotation> type = annotation.annotationType(); if (type.isAnnotationPresent(Qualifier.class)) { return Collections.singleton(annotation); } return Collections.<Annotation>singleton(new GuiceQualifier<>(key)); } Class<? extends Annotation> annotationType = key.getAnnotationType(); if (annotationType != null) { return Collections.<Annotation>singleton(new GuiceQualifier<>(key)); } return Collections.emptySet(); } /** * Creates and returns a {@link Key} for the given {@link Type} and {@link Set} of {@link Annotation}s. */ private static Key<?> newKey(Type type, Set<? extends Annotation> qualifiers) { if (qualifiers.isEmpty()) { return Key.get(type); } // There can be only one qualifier. if (qualifiers.size() == 1) { for (Annotation first : qualifiers) { return Key.get(type, first); } } return null; } /** * NOTE: There can be only one {@link Annotation} that is a {@link Qualifier} or {@link BindingAnnotation}. * They're the same but HK2 does not know about {@link BindingAnnotation}. * * @see Qualifier * @see BindingAnnotation * @see javax.inject.Named * @see com.google.inject.name.Named */ private static Set<Annotation> getQualifiers(Injectee injectee) { // JSR 330's @Qualifier Set<Annotation> qualifiers = injectee.getRequiredQualifiers(); if (!qualifiers.isEmpty()) { return qualifiers; } AnnotatedElement element = injectee.getParent(); int position = injectee.getPosition(); // Guice's @BindingAnnotation is the same as @Qualifier Annotation annotation = getBindingAnnotation(element, position); if (annotation != null) { return Collections.singleton(annotation); } return Collections.emptySet(); } /** * Returns a {@link BindingAnnotation} for the given {@link AnnotatedElement} and position. */ private static Annotation getBindingAnnotation(AnnotatedElement element, int position) { if (element instanceof Field) { return getBindingAnnotation(((Field)element).getAnnotations()); } if (element instanceof Method) { Annotation annotations[][] = ((Method)element).getParameterAnnotations(); return getBindingAnnotation(annotations[position]); } if (element instanceof Constructor<?>) { Annotation annotations[][] = ((Constructor<?>)element).getParameterAnnotations(); return getBindingAnnotation(annotations[position]); } return null; } /** * Returns the first {@link Annotation} from the given array that * is a {@link BindingAnnotation}. * * @see BindingAnnotation */ private static Annotation getBindingAnnotation(Annotation[] annotations) { for (Annotation annotation : annotations) { Class<? extends Annotation> type = annotation.annotationType(); if (type.isAnnotationPresent(BindingAnnotation.class)) { return annotation; } } return null; } /** * @see ReflectionHelper#getNameFromAllQualifiers(Set, AnnotatedElement) */ public static String getNameFromAllQualifiers(Set<Annotation> qualifiers, AnnotatedElement element) { return ReflectionHelper.getNameFromAllQualifiers(qualifiers, element); } }