forked from square/dagger
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKeys.java
More file actions
274 lines (253 loc) · 9.96 KB
/
Copy pathKeys.java
File metadata and controls
274 lines (253 loc) · 9.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
* Copyright (C) 2012 Square, Inc.
* Copyright (C) 2012 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;
import dagger.Lazy;
import dagger.MembersInjector;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import javax.inject.Provider;
import javax.inject.Qualifier;
/**
* Formats strings that identify the value to be injected. Keys are of one of
* three forms:
* <ol>
* <li>{@code com.square.Foo}: provides instances of Foo.
* <li>{@code @com.square.Bar/com.square.Foo}: provides instances of Foo
* qualified by the annotation.
* <li>{@code members/com.square.Foo}: injects members of Foo.
* </ol>
* Bindings from {@code @Provides} methods are of the first two types. BindingsGroup
* created from {@code @Inject}-annotated members of a class are of the first
* and last types.
*/
public final class Keys {
private static final String PROVIDER_PREFIX = Provider.class.getCanonicalName() + "<";
private static final String MEMBERS_INJECTOR_PREFIX =
MembersInjector.class.getCanonicalName() + "<";
private static final String LAZY_PREFIX = Lazy.class.getCanonicalName() + "<";
private static final String SET_PREFIX = Set.class.getCanonicalName() + "<";
private static final Memoizer<Class<? extends Annotation>, Boolean> IS_QUALIFIER_ANNOTATION =
new Memoizer<Class<? extends Annotation>, Boolean>() {
@Override protected Boolean create(Class<? extends Annotation> annotationType) {
return annotationType.isAnnotationPresent(Qualifier.class);
}
};
Keys() {
}
/** Returns a key for {@code type} with no annotation. */
public static String get(Type type) {
return get(type, null);
}
/** Returns a key for the members of {@code type}. */
public static String getMembersKey(Class<?> key) {
// for classes key.getName() is equivalent to get(key)
return "members/".concat(key.getName());
}
/** Returns a key for {@code type} annotated by {@code annotation}. */
private static String get(Type type, Annotation annotation) {
type = boxIfPrimitive(type);
if (annotation == null && type instanceof Class && !((Class<?>) type).isArray()) {
return ((Class<?>) type).getName();
}
StringBuilder result = new StringBuilder();
if (annotation != null) {
result.append(annotation).append("/");
}
typeToString(type, result, true);
return result.toString();
}
/**
* Returns a key for {@code type} annotated with {@code annotations},
* wrapped by {@code Set}, reporting failures against {@code subject}.
*
* @param annotations the annotations on a single method, field or parameter.
* This array may contain at most one qualifier annotation.
*/
public static String getSetKey(Type type, Annotation[] annotations, Object subject) {
Annotation qualifier = extractQualifier(annotations, subject);
type = boxIfPrimitive(type);
StringBuilder result = new StringBuilder();
if (qualifier != null) {
result.append(qualifier).append("/");
}
result.append(SET_PREFIX);
typeToString(type, result, true);
result.append(">");
return result.toString();
}
/**
* Returns a key for {@code type} annotated with {@code annotations},
* reporting failures against {@code subject}.
*
* @param annotations the annotations on a single method, field or parameter.
* This array may contain at most one qualifier annotation.
*/
public static String get(Type type, Annotation[] annotations, Object subject) {
return get(type, extractQualifier(annotations, subject));
}
/**
* Validates that among {@code annotations} there exists only one annotation which is, itself
* qualified by {@code \@Qualifier}
*/
private static Annotation extractQualifier(Annotation[] annotations,
Object subject) {
Annotation qualifier = null;
for (Annotation a : annotations) {
if (!IS_QUALIFIER_ANNOTATION.get(a.annotationType())) {
continue;
}
if (qualifier != null) {
throw new IllegalArgumentException("Too many qualifier annotations on " + subject);
}
qualifier = a;
}
return qualifier;
}
/**
* @param topLevel true if this is a top-level type where primitive types
* like 'int' are forbidden. Recursive calls pass 'false' to support
* arrays like {@code int[]}.
*/
private static void typeToString(Type type, StringBuilder result, boolean topLevel) {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
if (c.isArray()) {
typeToString(c.getComponentType(), result, false);
result.append("[]");
} else if (c.isPrimitive()) {
if (topLevel) {
throw new UnsupportedOperationException("Uninjectable type " + c.getName());
}
result.append(c.getName());
} else {
result.append(c.getName());
}
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
typeToString(parameterizedType.getRawType(), result, true);
Type[] arguments = parameterizedType.getActualTypeArguments();
result.append("<");
for (int i = 0; i < arguments.length; i++) {
if (i != 0) {
result.append(", ");
}
typeToString(arguments[i], result, true);
}
result.append(">");
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) type;
typeToString(genericArrayType.getGenericComponentType(), result, false);
result.append("[]");
} else {
throw new UnsupportedOperationException("Uninjectable type " + type);
}
}
/**
* Returns a key for the type provided by, or injected by this key. For
* example, if this is a key for a {@code Provider<Foo>}, this returns the
* key for {@code Foo}. This retains annotations and supports both Provider
* keys and MembersInjector keys.
*/
static String getBuiltInBindingsKey(String key) {
int start = startOfType(key);
if (substringStartsWith(key, start, PROVIDER_PREFIX)) {
return extractKey(key, start, key.substring(0, start), PROVIDER_PREFIX);
} else if (substringStartsWith(key, start, MEMBERS_INJECTOR_PREFIX)) {
return extractKey(key, start, "members/", MEMBERS_INJECTOR_PREFIX);
} else {
return null;
}
}
/**
* Returns a key for the underlying binding of a Lazy<T> value. For example,
* if this is a key for a {@code Lazy<Foo>}, this returns the key for
* {@code Foo}. This retains annotations.
*/
static String getLazyKey(String key) {
int start = startOfType(key);
if (substringStartsWith(key, start, LAZY_PREFIX)) {
return extractKey(key, start, key.substring(0, start), LAZY_PREFIX);
} else {
return null;
}
}
/**
* Returns the start of a key if it is a plain key, and the start of the
* underlying key if it is an annotated key
*/
private static int startOfType(String key) {
return (key.startsWith("@")) ? key.lastIndexOf('/') + 1 : 0;
}
/**
* Returns an unwrapped key (the key for T from a Provider<T> for example),
* removing all wrapping key information, but preserving annotations or known
* prefixes.
*
* @param key the key from which the delegate key should be extracted.
* @param start
* an index into the key representing the key's "real" start after
* any annotations.
* @param delegatePrefix
* key prefix elements extracted from the underlying delegate
* (annotations, "members/", etc.)
* @param prefix the prefix to strip.
*/
private static String extractKey(String key, int start, String delegatePrefix, String prefix) {
return delegatePrefix + key.substring(start + prefix.length(), key.length() - 1);
}
/** Returns true if {@code string.substring(offset).startsWith(substring)}. */
private static boolean substringStartsWith(String string, int offset, String substring) {
return string.regionMatches(offset, substring, 0, substring.length());
}
/** Returns true if {@code key} has a qualifier annotation. */
public static boolean isAnnotated(String key) {
return key.startsWith("@");
}
/**
* Returns the class name for {@code key}, if {@code key} was created with a
* class instance. Returns null if {@code key} represents a parameterized type
* or an array type.
*/
public static String getClassName(String key) {
int start = 0;
if (key.startsWith("@") || key.startsWith("members/")) {
start = key.lastIndexOf('/') + 1;
}
return (key.indexOf('<', start) == -1 && key.indexOf('[', start) == -1)
? key.substring(start)
: null;
}
/** Returns true if {@code name} is the name of a platform-provided class. */
public static boolean isPlatformType(String name) {
return name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.");
}
private static Type boxIfPrimitive(Type type) {
if (type == byte.class) return Byte.class;
if (type == short.class) return Short.class;
if (type == int.class) return Integer.class;
if (type == long.class) return Long.class;
if (type == char.class) return Character.class;
if (type == boolean.class) return Boolean.class;
if (type == float.class) return Float.class;
if (type == double.class) return Double.class;
if (type == void.class) return Void.class;
return type;
}
}