Skip to content

Commit 762e7f1

Browse files
committed
fix primitive types support (especially for constructors and methods) (backport)
1 parent 44294b2 commit 762e7f1

12 files changed

+239
-21
lines changed

CHANGELOG.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
* Fix failing navigation to primitive constructor or method parameter (#5)
2+
* Improve javadoc mentioning primitives behaviour
3+
4+
15
### 3.0.1 (2019-10-10)
2-
- Fix cycled declarations detection (Something<T extends Something<T>)
3-
- Add GenericUtils.orderVariablesForResolution method for ordering type variable declarations
4-
- Partial (#3) fix: support reversed generics declaration order on class
5-
- Fix method generics resolution as types instead of classes (#4)
6-
- Fix constructor generics resolution as types instead of classes
6+
* Fix cycled declarations detection (Something<T extends Something<T>)
7+
* Add GenericUtils.orderVariablesForResolution method for ordering type variable declarations
8+
* Partial (#3) fix: support reversed generics declaration order on class
9+
* Fix method generics resolution as types instead of classes (#4)
10+
* Fix constructor generics resolution as types instead of classes
711

812
### 3.0.0 (2018-06-19)
913
* Add constructor generics support

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* declared directly (e.g. field declaration: {@code Outer<String>.Inner field}) and, in this case, direct
4343
* declaration is used in priority.
4444
* <p>
45-
* It is impossible to to perform incorrect generics resolution through context api: it will either change context
45+
* It is impossible to perform incorrect generics resolution through context api: it will either change context
4646
* automatically or fail with error (with detailed incompatibility explanation).
4747
*
4848
* @author Vyacheslav Rusakov
@@ -58,6 +58,10 @@ public abstract class AbstractGenericsContext {
5858
protected final Map<String, Type> typeGenerics;
5959

6060
public AbstractGenericsContext(final GenericsInfo genericsInfo, final Class<?> type) {
61+
if (type.isPrimitive()) {
62+
throw new IllegalArgumentException(String.format(
63+
"Primitive type %s can't be used for generics context building", type.getSimpleName()));
64+
}
6165
this.genericsInfo = genericsInfo;
6266
this.currentType = type;
6367
// fail on wrong type
@@ -306,6 +310,8 @@ public Class<?> resolveFieldClass(final Field field) {
306310
* Check if type containing generics, belonging to different context in current hierarchy and
307311
* automatically change context to properly resolve generics. Fails when it is impossible to correctly
308312
* resolve generics (preventing incorrect usage).
313+
* <p>
314+
* For primitive type empty list will be returned.
309315
*
310316
* @param type type to resolve generics
311317
* @return resolved generic classes or empty list if type does not use generics
@@ -339,6 +345,8 @@ public List<Class<?>> resolveFieldGenerics(final Field field) {
339345
* Check if type containing generics, belonging to different context in current hierarchy and
340346
* automatically change context to properly resolve generics. Fails when it is impossible to correctly
341347
* resolve generics (preventing incorrect usage).
348+
* <p>
349+
* For primitive type null will be returned.
342350
*
343351
* @param type type to resolve generic
344352
* @return first resolved generic or null if type does not use generics
@@ -354,6 +362,8 @@ public Class<?> resolveGenericOf(final Type type) {
354362
* Use for more informative error message on incorrect usage.
355363
* <p>
356364
* Automatically choose correct context or fail if field does not belong to current hierarchy.
365+
* <p>
366+
* For primitive field null will be returned.
357367
*
358368
* @param field field to resolve generic for
359369
* @return resolved field generic or null if field does not declare generics
@@ -369,6 +379,8 @@ public Class<?> resolveFieldGeneric(final Field field) {
369379
* (if T == String).
370380
* <p>
371381
* Automatically choose correct context or fail if field does not belong to current hierarchy.
382+
* <p>
383+
* If provided type is primitive then it will be returned as is.
372384
*
373385
* @param field field to resolve generics for
374386
* @return field type with resolved generic variables
@@ -390,6 +402,8 @@ public Type resolveFieldType(final Field field) {
390402
* Check if type containing generics, belonging to different context in current hierarchy and
391403
* automatically change context to properly resolve generics. Fails when it is impossible to correctly
392404
* resolve generics (preventing incorrect usage).
405+
* <p>
406+
* If provided type is primitive then it will be returned as is.
393407
*
394408
* @param type type to resolve named generics in
395409
* @return type without named generics (replaced by known actual types)
@@ -404,13 +418,15 @@ public Type resolveType(final Type type) {
404418
* {@code ParameterizedType Map<String, List<T>>} would become
405419
* {@code [Class String, ParameterizedType List<String>]} (assumed generic T is defined as String).
406420
* <p>
407-
* Note: if type is Class then return raw generics definition (for consistency).
421+
* Note: if type is {@code Class} then return raw generics definition (for consistency).
408422
* <p>
409423
* Useful when complete generic types are required.
410424
* <p>
411425
* Check if type containing generics, belonging to different context in current hierarchy and
412426
* automatically change context to properly resolve generics. Fails when it is impossible to correctly
413427
* resolve generics (preventing incorrect usage).
428+
* <p>
429+
* For primitive type empty list would be returned.
414430
*
415431
* @param type type to return resolved generics of
416432
* @return type generics without variables
@@ -494,6 +510,8 @@ public String toStringType(final Type type) {
494510
* <p>
495511
* Note that, in contrast to direct resolution {@code GenericsResolver.resolve(B.class)}, actual root generic
496512
* would be counted for hierarchy resolution.
513+
* <p>
514+
* If field declares primitive type then wrapper class would be used instead.
497515
*
498516
* @param field field in current class hierarchy to resolve type from (may be field from superclass, relative to
499517
* currently selected, in this case context type will be automatically switched)
@@ -549,6 +567,9 @@ public GenericsContext fieldTypeAs(final Field field, final Class<?> asType) {
549567
* Returned context holds reference to original (root) context: {@link GenericsContext#rootContext()}.
550568
* <p>
551569
* Ignored types, used for context creation, are counted (will also be ignored for inlying context building).
570+
* <p>
571+
* If provided type is primitive then wrapper will be used instead (for example, {@link Integer} instead of
572+
* {@link int}).
552573
*
553574
* @param type type to resolve hierarchy from (it must be generified type, resolved in current class)
554575
* @return generics context of type (inlying context)
@@ -573,6 +594,9 @@ public GenericsContext fieldTypeAs(final Field field, final Class<?> asType) {
573594
* are provided for simpler navigation: {@link #fieldTypeAs(Field, Class)},
574595
* {@link MethodGenericsContext#returnTypeAs(Class)}, {@link MethodGenericsContext#parameterTypeAs(int, Class)}
575596
* and {@link ConstructorGenericsContext#parameterTypeAs(int, Class)}.
597+
* <p>
598+
* If provided type is primitive then wrapper will be used instead (for example, {@link Integer} instead of
599+
* {@link int}).
576600
*
577601
* @param type type to resolve actual generics from (it must be generified type, resolved in current class)
578602
* @param asType required target type to build generics context for (must include declared type as base class)

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public List<Class<?>> resolveParameters() {
104104
* }}</pre>
105105
* Resolving parameters types in context of root class:
106106
* {@code constructor(B.class.getConstructor(List.class)).resolveParametersTypes() == [List<Long>]}
107+
* <p>
108+
* Note: may return primitive because it might be important to differentiate actual value.
109+
* Use {@link ru.vyarus.java.generics.resolver.util.TypeUtils#wrapPrimitive(Class)} to box possible primitive,
110+
* if required.
107111
*
108112
* @return resolved constructor parameters types or empty list if constructor doesn't contain parameters
109113
* @see #resolveParameters()
@@ -114,6 +118,10 @@ public List<Type> resolveParametersTypes() {
114118
}
115119

116120
/**
121+
* Note: may return primitive because it might be important to differentiate actual value.
122+
* Use {@link ru.vyarus.java.generics.resolver.util.TypeUtils#wrapPrimitive(Class)} to box possible primitive,
123+
* if required.
124+
*
117125
* @param pos parameter position (form 0)
118126
* @return parameter type with resolved generic variables
119127
* @throws IllegalArgumentException if parameter index is incorrect
@@ -135,6 +143,11 @@ public Type resolveParameterType(final int pos) {
135143
* <p>
136144
* Note that, in contrast to direct resolution {@code GenericsResolver.resolve(B.class)}, actual root generic
137145
* would be counted for hierarchy resolution.
146+
* <p>
147+
* For primitive constructor parameters wrapper class would be used (for example. if target parameter is
148+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
149+
* manually when required (it may be only directly declared primitive argument because it is impossible to
150+
* declare primitive with generic variable).
138151
*
139152
* @param pos parameter position (from 0)
140153
* @return generics context of parameter type
@@ -152,6 +165,11 @@ public GenericsContext parameterType(final int pos) {
152165
* target type hierarchy). This is useful when analyzing object instance (introspecting actual object).
153166
* <p>
154167
* Other than target type, method is the same as {@link #parameterType(int)}.
168+
* <p>
169+
* For primitive constructor parameters wrapper class would be used (for example. if target parameter is
170+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
171+
* manually when required (it may be only directly declared primitive argument because it is impossible to
172+
* declare primitive with generic variable).
155173
*
156174
* @param pos parameter position (from 0)
157175
* @param asType required target type to build generics context for (must include declared type as base class)
@@ -220,7 +238,7 @@ private void checkParameter(final int pos) {
220238
final Type[] genericParams = ctor.getGenericParameterTypes();
221239
if (pos < 0 || pos >= genericParams.length) {
222240
throw new IllegalArgumentException(String.format(
223-
"Can't request parameter %s of constructor '%s' because it have only %s parameters",
241+
"Can't request parameter %s of constructor '%s' because it has only %s parameters",
224242
pos, toStringConstructor(), genericParams.length));
225243
}
226244
}

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public GenericDeclaration getGenericsSource() {
138138

139139
@Override
140140
public GenericsContext type(final Class<?> type) {
141-
return type == currentType ? this : new GenericsContext(genericsInfo, type, root);
141+
return type == currentType ? this : new GenericsContext(genericsInfo, TypeUtils.wrapPrimitive(type), root);
142142
}
143143

144144
@Override
@@ -162,7 +162,10 @@ public ConstructorGenericsContext constructor(final Constructor constructor) {
162162
public GenericsContext inlyingType(final Type type) {
163163
// check type compatibility
164164
final GenericsContext root = chooseContext(type);
165-
final Class target = root.resolveClass(type);
165+
// always wrap primitive because context may be build only for real class (it would require too much
166+
// of additional checks to properly support primitive-based contexts; usually it could be easilly
167+
// checked manually)
168+
final Class target = TypeUtils.wrapPrimitive(root.resolveClass(type));
166169
final GenericsInfo generics;
167170

168171
if (target.getTypeParameters().length > 0 || couldRequireKnownOuterGenerics(root, type)) {
@@ -171,9 +174,7 @@ public GenericsContext inlyingType(final Type type) {
171174
generics = GenericInfoUtils.create(root, type, genericsInfo.getIgnoredTypes());
172175
} else {
173176
// class without generics - use cachable context
174-
generics = GenericsInfoFactory.create(
175-
// always build hierarchy for non primitive type
176-
TypeUtils.wrapPrimitive(target), genericsInfo.getIgnoredTypes());
177+
generics = GenericsInfoFactory.create(target, genericsInfo.getIgnoredTypes());
177178
}
178179

179180
return new GenericsContext(generics, target, root);
@@ -183,7 +184,10 @@ public GenericsContext inlyingType(final Type type) {
183184
public GenericsContext inlyingTypeAs(final Type type, final Class<?> asType) {
184185
// check type compatibility
185186
final GenericsContext root = chooseContext(type);
186-
final Class target = root.resolveClass(type);
187+
// always wrap primitive because context may be build only for real class (it would require too much
188+
// of additional checks to properly support primitive-based contexts; usually it could be easilly
189+
// checked manually)
190+
final Class target = TypeUtils.wrapPrimitive(root.resolveClass(type));
187191
final GenericsInfo generics;
188192
if (target.getTypeParameters().length > 0
189193
|| couldRequireKnownOuterGenerics(root, type) || couldRequireKnownOuterGenerics(root, asType)) {
@@ -192,9 +196,7 @@ public GenericsContext inlyingTypeAs(final Type type, final Class<?> asType) {
192196
generics = GenericInfoUtils.create(root, type, asType, genericsInfo.getIgnoredTypes());
193197
} else {
194198
// class without generics - use cachable context
195-
generics = GenericsInfoFactory.create(
196-
// always build hierarchy for non primitive type
197-
TypeUtils.wrapPrimitive(asType), genericsInfo.getIgnoredTypes());
199+
generics = GenericsInfoFactory.create(asType, genericsInfo.getIgnoredTypes());
198200
}
199201
return new GenericsContext(generics, asType, root);
200202
}

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public List<Class<?>> resolveParameters() {
121121
* }}</pre>
122122
* Resolving parameters types in context of root class:
123123
* {@code method(B.class.getMethod("doSmth", List.class)).resolveParametersTypes() == [List<Long>]}
124+
* <p>
125+
* Note: may return primitives because it might be important to differentiate actual value.
126+
* Use {@link ru.vyarus.java.generics.resolver.util.TypeUtils#wrapPrimitive(Class)} to box possible primitives,
127+
* if required.
124128
*
125129
* @return resolved method parameters types or empty list if method doesn't contain parameters
126130
* @see #resolveParameters()
@@ -130,6 +134,10 @@ public List<Type> resolveParametersTypes() {
130134
}
131135

132136
/**
137+
* Note: may return primitives because it might be important to differentiate actual value.
138+
* Use {@link ru.vyarus.java.generics.resolver.util.TypeUtils#wrapPrimitive(Class)} to box possible primitives,
139+
* if required.
140+
*
133141
* @param pos parameter position (form 0)
134142
* @return parameter type with resolved generic variables
135143
* @throws IllegalArgumentException if parameter index is incorrect
@@ -151,6 +159,11 @@ public Type resolveParameterType(final int pos) {
151159
* <p>
152160
* Note that, in contrast to direct resolution {@code GenericsResolver.resolve(B.class)}, actual root generic
153161
* would be counted for hierarchy resolution.
162+
* <p>
163+
* For primitive parameters wrapper class would be used (for example. if target parameter is
164+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
165+
* manually when required (it may be only directly declared primitive argument because it is impossible to
166+
* declare primitive with generic variable).
154167
*
155168
* @param pos parameter position (from 0)
156169
* @return generics context of parameter type
@@ -168,6 +181,11 @@ public GenericsContext parameterType(final int pos) {
168181
* target type hierarchy). This is useful when analyzing object instance (introspecting actual object).
169182
* <p>
170183
* Other than target type, method is the same as {@link #parameterType(int)}.
184+
* <p>
185+
* For primitive parameters wrapper class would be used (for example. if target parameter is
186+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
187+
* manually when required (it may be only directly declared primitive argument because it is impossible to
188+
* declare primitive with generic variable).
171189
*
172190
* @param pos parameter position (from 0)
173191
* @param asType required target type to build generics context for (must include declared type as base class)
@@ -212,6 +230,10 @@ public Class<?> resolveReturnTypeGeneric() {
212230
* List<T> get();
213231
* }}</pre>.
214232
* {@code (context of B).method(A.class.getMethod("get")).resolveReturnType() == List<Long>}
233+
* <p>
234+
* Note: may return primitive because it might be important to differentiate actual value.
235+
* Use {@link ru.vyarus.java.generics.resolver.util.TypeUtils#wrapPrimitive(Class)} to box possible primitive,
236+
* if required.
215237
*
216238
* @return method return type with resolved generic variables
217239
*/
@@ -230,6 +252,11 @@ public Type resolveReturnType() {
230252
* <p>
231253
* Note that, in contrast to direct resolution {@code GenericsResolver.resolve(B.class)}, actual root generic
232254
* would be counted for hierarchy resolution.
255+
* <p>
256+
* For primitive return type wrapper class would be used (for example. if target parameter is
257+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
258+
* manually when required (it may be only directly declared primitive argument because it is impossible to
259+
* declare primitive with generic variable).
233260
*
234261
* @return generics context of return type
235262
* @see #inlyingType(Type)
@@ -244,6 +271,11 @@ public GenericsContext returnType() {
244271
* type hierarchy). This is useful when analyzing object instance (introspecting actual object).
245272
* <p>
246273
* Other than target type, method is the same as {@link #returnType()}.
274+
* <p>
275+
* For primitive return type wrapper class would be used (for example. if target parameter is
276+
* {@code int} then returned context type would be {@link Integer}). It is not hard to detect primitives
277+
* manually when required (it may be only directly declared primitive argument because it is impossible to
278+
* declare primitive with generic variable).
247279
*
248280
* @param asType required target type to build generics context for (must include declared type as base class)
249281
* @return generics context of requested type with known return type generics
@@ -307,7 +339,7 @@ private void checkParameter(final int pos) {
307339
final Type[] genericParams = meth.getGenericParameterTypes();
308340
if (pos < 0 || pos >= genericParams.length) {
309341
throw new IllegalArgumentException(String.format(
310-
"Can't request parameter %s of method '%s' (%s) because it have only %s parameters",
342+
"Can't request parameter %s of method '%s' (%s) because it has only %s parameters",
311343
pos, toStringMethod(), currentClass().getSimpleName(), genericParams.length));
312344
}
313345
}

src/test/groovy/ru/vyarus/java/generics/resolver/ConstructorIntrospectionTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ class ConstructorIntrospectionTest extends Specification {
5050
context.resolveParameterType(5)
5151
then:
5252
def ex = thrown(IllegalArgumentException)
53-
ex.message == "Can't request parameter 5 of constructor 'ConstructorGenerics(Comparable)' because it have only 1 parameters"
53+
ex.message == "Can't request parameter 5 of constructor 'ConstructorGenerics(Comparable)' because it has only 1 parameters"
5454
}
5555
}

0 commit comments

Comments
 (0)