Skip to content

Commit fd50807

Browse files
committed
add wildcards support
add types repackaging to properly resolve complex generics cases add type generic access methods by generic name add ability to control descriptors cache
1 parent 024881a commit fd50807

25 files changed

+636
-119
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
* Add wildcards support
2+
* Improve complex generics resolution
3+
* Add access by generic name methods in GenericsContext
4+
* Add ability to disable cache using property and method to clear current cache
5+
16
### 1.0.0 (2014-11-19)
27

38
* Initial release

src/main/java/ru/vyarus/java/generics/resolver/context/GenericsContext.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ public Type genericType(final int position) {
104104
return genericTypes().get(position);
105105
}
106106

107+
/**
108+
* {@code class B<T, K>}.
109+
* <pre>{@code class A extends B<Object, C<Long>>
110+
* type(B.class).genericType("K") == ParametrizedType }</pre>
111+
*
112+
* @param genericName generic position (from 0)
113+
* @return generic type
114+
* @throws java.lang.IllegalArgumentException for wrong generic name
115+
* @see #genericTypes() for details
116+
*/
117+
public Type genericType(final String genericName) {
118+
return typeGenerics.get(checkGenericName(genericName));
119+
}
120+
107121
/**
108122
* {@code class A extends B<Object, C<Long>>}.
109123
* <pre>{@code type(B.class).generic(1) == C.class }</pre>
@@ -117,6 +131,20 @@ public Class<?> generic(final int position) {
117131
return resolveClass(genericTypes().get(position));
118132
}
119133

134+
/**
135+
* {@code class B<T, K>}.
136+
* <pre>{@code class A extends B<Object, C<Long>>
137+
* type(B.class).generic("K") == C.class }</pre>
138+
*
139+
* @param genericName generic name
140+
* @return resolved generic class
141+
* @throws java.lang.IllegalArgumentException for wrong generic name
142+
* @see #resolveClass(java.lang.reflect.Type)
143+
*/
144+
public Class<?> generic(final String genericName) {
145+
return resolveClass(typeGenerics.get(checkGenericName(genericName)));
146+
}
147+
120148
/**
121149
* {@code class A extends B<Object, C<Long>>}.
122150
* <pre>{@code type(B.class).genericAsString(1) == "C<Long>" }</pre>
@@ -130,6 +158,20 @@ public String genericAsString(final int position) {
130158
return toStringType(genericType(position));
131159
}
132160

161+
/**
162+
* {@code class B<T, K>}.
163+
* <pre>{@code class A extends B<Object, C<Long>>
164+
* type(B.class).genericAsString("K") == "C<Long>" }</pre>
165+
*
166+
* @param genericName generic name
167+
* @return resolved generic string representation
168+
* @throws java.lang.IllegalArgumentException for wrong generic name
169+
* @see #toStringType(java.lang.reflect.Type)
170+
*/
171+
public String genericAsString(final String genericName) {
172+
return toStringType(typeGenerics.get(checkGenericName(genericName)));
173+
}
174+
133175
/**
134176
* {@code class A extends B<Object, C<Long>>} and {@code class B<T, K>}.
135177
* <pre>{@code type(B.class).genericsMap() == ["T": Object.class, "K": ParametrizedType]}</pre>
@@ -254,4 +296,12 @@ public String toStringType(final Type type) {
254296
public GenericsContext type(final Class<?> type) {
255297
return new GenericsContext(genericsInfo, type);
256298
}
299+
300+
private String checkGenericName(final String genericName) {
301+
if (!typeGenerics.containsKey(genericName)) {
302+
throw new IllegalArgumentException(String.format("Type %s doesn't contain generic with name '%s'",
303+
currentType.getName(), genericName));
304+
}
305+
return genericName;
306+
}
257307
}
Lines changed: 46 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
package ru.vyarus.java.generics.resolver.context;
22

3-
import java.lang.reflect.ParameterizedType;
4-
import java.lang.reflect.Type;
5-
import java.lang.reflect.TypeVariable;
6-
import java.util.*;
3+
import ru.vyarus.java.generics.resolver.util.GenericInfoUtils;
4+
5+
import java.util.Map;
6+
import java.util.WeakHashMap;
77
import java.util.concurrent.locks.ReentrantLock;
88

99
/**
1010
* Analyze class hierarchy and produce class hierarchy resolved generics object.
1111
* Resolved generics descriptors are cached.
1212
* <p>Note: when ignore classes used, cache will not work: such descriptors are always resolved.</p>
13+
* <p>Cache may be disabled (e.g. when JRebel used) by using environment variable or system property e.g.:
14+
* {@code System.setProperty(GenericsInfoFactory.CACHE_PROPERTY, 'false')}.
15+
* Property value checked on cache write. To clear current cache state use static method.</p>
1316
*
1417
* @author Vyacheslav Rusakov
1518
* @since 16.10.2014
1619
*/
1720
public final class GenericsInfoFactory {
1821

22+
/**
23+
* System property or environment variable name to disable cache.
24+
* If value is 'false' - cache disabled, otherwise cache enabled.
25+
*/
26+
public static final String CACHE_PROPERTY = GenericsInfoFactory.class.getName() + ".cache";
27+
1928
private static final Map<Class<?>, GenericsInfo> CACHE = new WeakHashMap<Class<?>, GenericsInfo>();
2029
// lock will not affect performance for cached descriptors, just to make sure nothing was build two times
2130
private static final ReentrantLock LOCK = new ReentrantLock();
22-
@SuppressWarnings("PMD.LooseCoupling")
23-
private static final LinkedHashMap<String, Type> EMPTY_MAP = new LinkedHashMap<String, Type>(0);
24-
private static final String GROOVY_OBJECT = "GroovyObject";
2531

2632
private GenericsInfoFactory() {
2733
}
@@ -35,20 +41,23 @@ private GenericsInfoFactory() {
3541
* @return descriptor for class hierarchy generics substitution
3642
*/
3743
public static GenericsInfo create(final Class<?> type, final Class<?>... ignoreClasses) {
38-
GenericsInfo descriptor = ignoreClasses.length > 0 ? buildDescriptor(type, ignoreClasses) : CACHE.get(type);
44+
GenericsInfo descriptor = ignoreClasses.length > 0
45+
? GenericInfoUtils.create(type, ignoreClasses) : CACHE.get(type);
3946
if (descriptor == null) {
4047
LOCK.lock();
4148
try {
4249
if (CACHE.get(type) != null) {
4350
// descriptor could be created while thread wait for lock
4451
descriptor = CACHE.get(type);
4552
} else {
46-
descriptor = buildDescriptor(type);
47-
// internal check
48-
if (CACHE.get(type) != null) {
49-
throw new IllegalStateException("Bad concurrency: descriptor already present in cache");
53+
descriptor = GenericInfoUtils.create(type);
54+
if (isCacheEnabled()) {
55+
// internal check
56+
if (CACHE.get(type) != null) {
57+
throw new IllegalStateException("Bad concurrency: descriptor already present in cache");
58+
}
59+
CACHE.put(type, descriptor);
5060
}
51-
CACHE.put(type, descriptor);
5261
}
5362
} finally {
5463
LOCK.unlock();
@@ -57,89 +66,34 @@ public static GenericsInfo create(final Class<?> type, final Class<?>... ignoreC
5766
return descriptor;
5867
}
5968

60-
private static GenericsInfo buildDescriptor(final Class<?> type, final Class<?>... ignoreClasses) {
61-
final Map<Class<?>, LinkedHashMap<String, Type>> generics =
62-
new HashMap<Class<?>, LinkedHashMap<String, Type>>();
63-
analyzeType(generics, type, Arrays.asList(ignoreClasses));
64-
return new GenericsInfo(type, generics);
65-
}
66-
67-
private static void analyzeType(final Map<Class<?>, LinkedHashMap<String, Type>> types, final Class<?> type,
68-
final List<Class<?>> ignoreClasses) {
69-
Class<?> supertype = type;
70-
while (true) {
71-
for (Type iface : supertype.getGenericInterfaces()) {
72-
analyzeInterface(types, iface, supertype, ignoreClasses);
73-
}
74-
final Class next = supertype.getSuperclass();
75-
if (next == null || Object.class == next || ignoreClasses.contains(next)) {
76-
break;
77-
}
78-
types.put(next, analyzeParent(supertype, types.get(supertype)));
79-
supertype = next;
80-
}
81-
}
82-
83-
private static void analyzeInterface(final Map<Class<?>, LinkedHashMap<String, Type>> types, final Type iface,
84-
final Class<?> supertype, final List<Class<?>> ignoreClasses) {
85-
final Class interfaceType = iface instanceof ParameterizedType
86-
? (Class) ((ParameterizedType) iface).getRawType()
87-
: (Class) iface;
88-
if (!ignoreClasses.contains(interfaceType)) {
89-
if (iface instanceof ParameterizedType) {
90-
final ParameterizedType parametrization = (ParameterizedType) iface;
91-
final LinkedHashMap<String, Type> generics =
92-
resolveGenerics(parametrization, types.get(supertype));
93-
94-
// no generics case and same resolved generics are ok (even if types in different branches of hierarchy)
95-
if (types.containsKey(interfaceType) && !generics.equals(types.get(interfaceType))) {
96-
throw new IllegalStateException(String.format(
97-
"Duplicate interface %s declaration in hierarchy: "
98-
+ "can't properly resolve generics.", interfaceType.getName()));
99-
}
100-
types.put(interfaceType, generics);
101-
} else if (!GROOVY_OBJECT.equals(interfaceType.getSimpleName())) {
102-
// avoid groovy specific interface (all groovy objects implements it)
103-
types.put(interfaceType, EMPTY_MAP);
104-
}
105-
analyzeType(types, interfaceType, ignoreClasses);
69+
/**
70+
* Clears cached descriptors (already parsed).
71+
* Cache could be completely disabled using system property or environment variable
72+
*
73+
* @see #CACHE_PROPERTY
74+
*/
75+
public static void clearCache() {
76+
LOCK.lock();
77+
try {
78+
CACHE.clear();
79+
} finally {
80+
LOCK.unlock();
10681
}
10782
}
10883

109-
// LinkedHashMap used instead of usual map to avoid accidental simple map usage (order is important!)
110-
@SuppressWarnings("PMD.LooseCoupling")
111-
private static LinkedHashMap<String, Type> analyzeParent(final Class type,
112-
final Map<String, Type> rootGenerics) {
113-
LinkedHashMap<String, Type> generics = null;
114-
final Class parent = type.getSuperclass();
115-
if (!type.isInterface() && parent != null && parent != Object.class
116-
&& type.getGenericSuperclass() instanceof ParameterizedType) {
117-
generics = resolveGenerics((ParameterizedType) type.getGenericSuperclass(), rootGenerics);
118-
}
119-
return generics == null ? EMPTY_MAP : generics;
84+
/**
85+
* Disables descriptors cache.
86+
*/
87+
public static void disableCache() {
88+
System.setProperty(CACHE_PROPERTY, Boolean.FALSE.toString());
12089
}
12190

122-
@SuppressWarnings("PMD.LooseCoupling")
123-
private static LinkedHashMap<String, Type> resolveGenerics(final ParameterizedType type,
124-
final Map<String, Type> rootGenerics) {
125-
final LinkedHashMap<String, Type> generics = new LinkedHashMap<String, Type>();
126-
final Type[] genericTypes = type.getActualTypeArguments();
127-
final Class interfaceType = (Class) type.getRawType();
128-
final TypeVariable[] genericNames = interfaceType.getTypeParameters();
129-
130-
final int cnt = genericNames.length;
131-
for (int i = 0; i < cnt; i++) {
132-
final Type genericType = genericTypes[i];
133-
Type resolvedGenericType;
134-
if (genericType instanceof TypeVariable) {
135-
// simple named generics resolved to target types
136-
resolvedGenericType = rootGenerics.get(((TypeVariable) genericType).getName());
137-
} else {
138-
// composite generics passed as is
139-
resolvedGenericType = genericType;
140-
}
141-
generics.put(genericNames[i].getName(), resolvedGenericType);
142-
}
143-
return generics;
91+
/**
92+
* @return true is cache enabled, false otherwise
93+
*/
94+
public static boolean isCacheEnabled() {
95+
final String no = Boolean.FALSE.toString();
96+
return !no.equals(System.getenv(CACHE_PROPERTY))
97+
&& !no.equals(System.getProperty(CACHE_PROPERTY));
14498
}
14599
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package ru.vyarus.java.generics.resolver.context.container;
2+
3+
import ru.vyarus.java.generics.resolver.util.TypeToStringUtils;
4+
5+
import java.lang.reflect.GenericArrayType;
6+
import java.lang.reflect.Type;
7+
import java.util.Collections;
8+
9+
/**
10+
* Wrapper to hold resolved array type.
11+
*
12+
* @author Vyacheslav Rusakov
13+
* @since 15.12.2014
14+
*/
15+
public class GenericArrayTypeImpl implements GenericArrayType {
16+
17+
private final Type componentType;
18+
19+
public GenericArrayTypeImpl(final Type componentType) {
20+
this.componentType = componentType;
21+
}
22+
23+
@Override
24+
public Type getGenericComponentType() {
25+
return componentType;
26+
}
27+
28+
@Override
29+
public String toString() {
30+
return TypeToStringUtils.toStringType(this, Collections.<String, Type>emptyMap());
31+
}
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ru.vyarus.java.generics.resolver.context.container;
2+
3+
import ru.vyarus.java.generics.resolver.util.TypeToStringUtils;
4+
5+
import java.lang.reflect.ParameterizedType;
6+
import java.lang.reflect.Type;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
10+
/**
11+
* Wrapper to hold resolved parametrization.
12+
*
13+
* @author Vyacheslav Rusakov
14+
* @since 15.12.2014
15+
*/
16+
public class ParameterizedTypeImpl implements ParameterizedType {
17+
18+
private final Type rawType;
19+
private final Type[] actualArguments;
20+
private final Type ownerType;
21+
22+
public ParameterizedTypeImpl(final Type rawType, final Type[] actualArguments, final Type ownerType) {
23+
this.rawType = rawType;
24+
this.actualArguments = Arrays.copyOf(actualArguments, actualArguments.length);
25+
this.ownerType = ownerType;
26+
}
27+
28+
@Override
29+
public Type[] getActualTypeArguments() {
30+
return Arrays.copyOf(actualArguments, actualArguments.length);
31+
}
32+
33+
@Override
34+
public Type getRawType() {
35+
return rawType;
36+
}
37+
38+
@Override
39+
public Type getOwnerType() {
40+
return ownerType;
41+
}
42+
43+
@Override
44+
public String toString() {
45+
return TypeToStringUtils.toStringType(this, Collections.<String, Type>emptyMap());
46+
}
47+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ru.vyarus.java.generics.resolver.context.container;
2+
3+
import ru.vyarus.java.generics.resolver.util.TypeToStringUtils;
4+
5+
import java.lang.reflect.Type;
6+
import java.lang.reflect.WildcardType;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
10+
/**
11+
* Wrapper to hold resolved bounds types.
12+
*
13+
* @author Vyacheslav Rusakov
14+
* @since 15.12.2014
15+
*/
16+
public class WildcardTypeImpl implements WildcardType {
17+
18+
private final Type[] upperBounds;
19+
private final Type[] lowerBounds;
20+
21+
@SuppressWarnings("PMD.UseVarargs")
22+
public WildcardTypeImpl(final Type[] upperBounds, final Type[] lowerBounds) {
23+
this.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length);
24+
this.lowerBounds = Arrays.copyOf(lowerBounds, lowerBounds.length);
25+
}
26+
27+
@Override
28+
public Type[] getLowerBounds() {
29+
return Arrays.copyOf(lowerBounds, lowerBounds.length);
30+
}
31+
32+
@Override
33+
public Type[] getUpperBounds() {
34+
return Arrays.copyOf(upperBounds, upperBounds.length);
35+
}
36+
37+
@Override
38+
public String toString() {
39+
return TypeToStringUtils.toStringType(this, Collections.<String, Type>emptyMap());
40+
}
41+
}

0 commit comments

Comments
 (0)