1 package com.google.inject.throwingproviders;
2 
3 import com.google.common.base.Optional;
4 import com.google.common.base.Predicate;
5 import com.google.common.collect.FluentIterable;
6 import com.google.inject.internal.Errors;
7 import java.lang.reflect.Method;
8 import java.lang.reflect.ParameterizedType;
9 import java.lang.reflect.Type;
10 import java.lang.reflect.TypeVariable;
11 import java.util.Arrays;
12 import java.util.List;
13 
14 /** Helper methods to verify the correctness of CheckedProvider interfaces. */
15 final class ProviderChecker {
16 
ProviderChecker()17   private ProviderChecker() {}
18 
checkInterface( Class<P> interfaceType, Optional<? extends Type> valueType)19   static <P extends CheckedProvider<?>> void checkInterface(
20       Class<P> interfaceType, Optional<? extends Type> valueType) {
21     checkArgument(interfaceType.isInterface(), "%s must be an interface", interfaceType.getName());
22     checkArgument(
23         interfaceType.getGenericInterfaces().length == 1,
24         "%s must extend CheckedProvider (and only CheckedProvider)",
25         interfaceType);
26 
27     boolean tpMode = interfaceType.getInterfaces()[0] == ThrowingProvider.class;
28     if (!tpMode) {
29       checkArgument(
30           interfaceType.getInterfaces()[0] == CheckedProvider.class,
31           "%s must extend CheckedProvider (and only CheckedProvider)",
32           interfaceType);
33     }
34 
35     // Ensure that T is parameterized and unconstrained.
36     ParameterizedType genericThrowingProvider =
37         (ParameterizedType) interfaceType.getGenericInterfaces()[0];
38     if (interfaceType.getTypeParameters().length == 1) {
39       String returnTypeName = interfaceType.getTypeParameters()[0].getName();
40       Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
41       checkArgument(
42           returnType instanceof TypeVariable,
43           "%s does not properly extend CheckedProvider, the first type parameter of CheckedProvider"
44               + " (%s) is not a generic type",
45           interfaceType,
46           returnType);
47       checkArgument(
48           returnTypeName.equals(((TypeVariable) returnType).getName()),
49           "The generic type (%s) of %s does not match the generic type of CheckedProvider (%s)",
50           returnTypeName,
51           interfaceType,
52           ((TypeVariable) returnType).getName());
53     } else {
54       checkArgument(
55           interfaceType.getTypeParameters().length == 0,
56           "%s has more than one generic type parameter: %s",
57           interfaceType,
58           Arrays.asList(interfaceType.getTypeParameters()));
59       if (valueType.isPresent()) {
60         checkArgument(
61             genericThrowingProvider.getActualTypeArguments()[0].equals(valueType.get()),
62             "%s expects the value type to be %s, but it was %s",
63             interfaceType,
64             genericThrowingProvider.getActualTypeArguments()[0],
65             valueType.get());
66       }
67     }
68 
69     if (tpMode) { // only validate exception in ThrowingProvider mode.
70       Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
71       checkArgument(
72           exceptionType instanceof Class,
73           "%s has the wrong Exception generic type (%s) when extending CheckedProvider",
74           interfaceType,
75           exceptionType);
76     }
77 
78     // Skip synthetic/bridge methods because java8 generates
79     // a default method on the interface w/ the superinterface type that
80     // just delegates directly to the overridden method.
81     List<Method> declaredMethods =
82         FluentIterable.from(Arrays.asList(interfaceType.getDeclaredMethods()))
83             .filter(NotSyntheticOrBridgePredicate.INSTANCE)
84             .toList();
85     if (declaredMethods.size() == 1) {
86       Method method = declaredMethods.get(0);
87       checkArgument(
88           method.getName().equals("get"),
89           "%s may not declare any new methods, but declared %s",
90           interfaceType,
91           method);
92       checkArgument(
93           method.getParameterTypes().length == 0,
94           "%s may not declare any new methods, but declared %s",
95           interfaceType,
96           method.toGenericString());
97     } else {
98       checkArgument(
99           declaredMethods.isEmpty(),
100           "%s may not declare any new methods, but declared %s",
101           interfaceType,
102           Arrays.asList(interfaceType.getDeclaredMethods()));
103     }
104   }
105 
checkArgument(boolean condition, String messageFormat, Object... args)106   private static void checkArgument(boolean condition, String messageFormat, Object... args) {
107     if (!condition) {
108       throw new IllegalArgumentException(Errors.format(messageFormat, args));
109     }
110   }
111 
112   private static class NotSyntheticOrBridgePredicate implements Predicate<Method> {
113     static final NotSyntheticOrBridgePredicate INSTANCE = new NotSyntheticOrBridgePredicate();
114 
115     @Override
apply(Method input)116     public boolean apply(Method input) {
117       return !input.isBridge() && !input.isSynthetic();
118     }
119   }
120 }
121