Skip to content

Commit 9d543fd

Browse files
Treiblesschorlectrueden
authored andcommitted
Add TypeUtils class and tests
1 parent 1322f05 commit 9d543fd

2 files changed

Lines changed: 712 additions & 0 deletions

File tree

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.util;
34+
35+
import java.lang.reflect.ParameterizedType;
36+
import java.lang.reflect.Type;
37+
import java.lang.reflect.TypeVariable;
38+
import java.lang.reflect.WildcardType;
39+
import java.util.Arrays;
40+
import java.util.HashMap;
41+
import java.util.Map;
42+
43+
import com.google.common.base.Objects;
44+
45+
public final class TypeUtils {
46+
47+
private TypeUtils() {
48+
// prevent instantiation of utility class
49+
}
50+
51+
/**
52+
* Checks whether it would be legal to assign the {@link ParameterizedType}
53+
* source, represented as a raw type, to the specified
54+
* {@link ParameterizedType} destination (which could possibly be a
55+
* supertype of the source type). Thereby, possible {@link TypeVariable}s
56+
* contained in the parameters of the source are tried to be inferred.
57+
* Inference will be done by simple matching of an encountered
58+
* {@link TypeVariable} in the source to the corresponding type in the
59+
* parameters of the destination. If an {@link TypeVariable} is encountered
60+
* more than once, the corresponding type in the destination needs to
61+
* perfectly match. Else, false will be rturned.</br>
62+
* </br>
63+
* Examples:
64+
* <ul>
65+
* If we have a class:
66+
* <li>
67+
*
68+
* <pre>
69+
* class NumberSupplier&lt;M extends Number&gt; implements Supplier&lt;M&gt;
70+
* </li>
71+
* </ul>
72+
* <ul>
73+
* The following check will return true:
74+
* <li>
75+
*
76+
* <pre>
77+
* checkGenericAssignability(NumberSupplier.class, new
78+
* Nil&lt;Supplier&lt;Double&gt;&gt;() {}.getType())</li>
79+
* </ul>
80+
* </ul>
81+
* <ul>
82+
* Which will check if the following assignment would be legal:
83+
* <li>
84+
*
85+
* <pre>
86+
* Supplier&lt;Double&gt; list = new NumberSupplier&lt;&gt;()</li>
87+
* </ul>
88+
* </ul>
89+
* <ul>
90+
* Here, the parameter {@code <M extends Number>} can be inferred to be of
91+
* type {@code Double} from the type {@code Supplier<Double>}
92+
* </ul>
93+
* <ul>
94+
* Consequently the following will return false:
95+
* <li>
96+
*
97+
* <pre>
98+
* checkGenericAssignability(NumberSupplier.class, new
99+
* Nil&lt;Supplier&lt;String&gt;&gt;() {}.getType())</li>
100+
* </ul>
101+
* <ul>
102+
* {@code <M extends Number>} can't be inferred as type {@code String} is
103+
* not within the bounds of {@code M}.
104+
* </ul>
105+
* <ul>
106+
* Furthermore, the following will return false for:
107+
* {@code class NumberFunc<M extends Number> implements Function<M, M>}:
108+
* <li>
109+
*
110+
* <pre>
111+
* checkGenericAssignability(NumberSupplier.class, new
112+
* Nil&lt;Function&lt;Double, Integer&gt;&gt;() {}.getType())</li>
113+
* </ul>
114+
* <ul>
115+
* {@code <M extends Number>} can't be inferred as types
116+
* {@code Double, Integer} are ambiguous for the double usage of {@code M}.
117+
* </ul>
118+
*
119+
* @param src
120+
* raw type representing parameterized of which assignment should
121+
* be checked
122+
* @param dest
123+
* the parameterized type for which assignment should be checked
124+
* to
125+
* @return whether and assignment of source to destination would be a legal
126+
* java statement
127+
*/
128+
public static boolean checkGenericAssignability(Class<?> src, ParameterizedType dest) {
129+
// check raw assignability
130+
if (!Types.isAssignable(src, Types.raw(dest)))
131+
return false;
132+
133+
Type[] destTypes = dest.getActualTypeArguments();
134+
// get type arguments of raw src for common (possible supertype) dest
135+
Type[] srcTypes = getParams(src, Types.raw(dest));
136+
137+
// if the number of type arguments does not match, the types can't be
138+
// assignable
139+
// TODO: Find out if this could ever happen with the way how we retrieve
140+
// the arguments above
141+
if (srcTypes.length != destTypes.length) {
142+
return false;
143+
}
144+
145+
Type[] mappedSrcTypes = null;
146+
try {
147+
Map<TypeVariable<?>, Type> typeAssigns = new HashMap<TypeVariable<?>, Type>();
148+
// Try to infer type variables contained in the type arguments of
149+
// sry
150+
inferTypeVariables(srcTypes, destTypes, typeAssigns);
151+
// Map the vars to the inferred types
152+
mappedSrcTypes = mapVarToTypes(srcTypes, typeAssigns);
153+
} catch (TypeInferenceException e) {
154+
// types can't be inferred
155+
return false;
156+
}
157+
158+
// Build a new parameterized type from inferred types and check
159+
// assignability
160+
Class<?> matchingRawType = Types.raw(dest);
161+
Type inferredSrcType = containsNull(mappedSrcTypes) ? src : Types.parameterize(matchingRawType, mappedSrcTypes);
162+
if (!Types.isAssignable(inferredSrcType, dest)) {
163+
return false;
164+
}
165+
return true;
166+
}
167+
168+
/**
169+
* Exception indicating that type vars could not be inferred.
170+
*/
171+
private static class TypeInferenceException extends Exception {
172+
/**
173+
*
174+
*/
175+
private static final long serialVersionUID = 7147530827546663700L;
176+
}
177+
178+
/**
179+
* Map type vars in specified type list to types using the specified map. In
180+
* doing so, type vars mapping to other type vars will not be followed but
181+
* just repalced.
182+
*
183+
* @param typesToMap
184+
* @param typeAssigns
185+
* @return
186+
*/
187+
private static Type[] mapVarToTypes(Type[] typesToMap, Map<TypeVariable<?>, Type> typeAssigns) {
188+
return Arrays.stream(typesToMap).map(type -> Types.unrollVariables(typeAssigns, type, false))
189+
.toArray(Type[]::new);
190+
}
191+
192+
private static <M> boolean containsNull(M[] arr) {
193+
return !Arrays.stream(arr).noneMatch(m -> m == null);
194+
}
195+
196+
/**
197+
* Tries to infer type vars contained in types from corresponding types from
198+
* inferFrom, putting them into the specified map.
199+
*
200+
* @param types
201+
* @param inferFrom
202+
* @param typeAssigns
203+
* @throws TypeInferenceException
204+
*/
205+
private static void inferTypeVariables(Type[] types, Type[] inferFrom, Map<TypeVariable<?>, Type> typeAssigns)
206+
throws TypeInferenceException {
207+
if (typeAssigns == null)
208+
throw new IllegalArgumentException();
209+
// Check all pairs of types
210+
for (int i = 0; i < types.length; i++) {
211+
if (types[i] instanceof TypeVariable) {
212+
TypeVariable<?> varType = (TypeVariable<?>) types[i];
213+
Type from = inferFrom[i];
214+
215+
// If current type var is absent put it to the map. Otherwise,
216+
// we already encountered that var.
217+
// Hence, we require them to be exactly the same.
218+
Type current = typeAssigns.putIfAbsent(varType, from);
219+
if (current != null) {
220+
if (!Objects.equal(from, current)) {
221+
throw new TypeInferenceException();
222+
}
223+
}
224+
225+
// Bounds could also contain type vars, hence go into recursion
226+
for (Type bound : varType.getBounds()) {
227+
// If the bound of the current var to infer is also a var:
228+
// If we already encountered the var bound, we check if the current type to infer from is
229+
// assignable to the already inferred bound. In this case we do not require equality as
230+
// one var is bounded by another and not the same.
231+
// E.g. we want to infer thy types of vars:
232+
// A extends Number, B extends A
233+
// From types:
234+
// Number, Double
235+
// First A is bound to Number, next B to Double. Then we check the bounds for B. We encounter A,
236+
// for which we already inferred Number. Hence, it suffices to check whether Double can be assigned
237+
// to Number, it does not have to be equal as it is just a transitive bound for B.
238+
// Else go into recursion as we encountered a ned var.
239+
if (bound instanceof TypeVariable && typeAssigns.get((TypeVariable<?>)bound) != null) {
240+
Type typeAssignForBound = typeAssigns.get((TypeVariable<?>)bound);
241+
if(!Types.isAssignable(from, typeAssignForBound)) {
242+
throw new TypeInferenceException();
243+
}
244+
} else {
245+
inferTypeVariables(new Type[]{bound}, new Type[]{from}, typeAssigns);
246+
}
247+
}
248+
} else if (types[i] instanceof ParameterizedType) {
249+
// Recursively follow parameterized types
250+
if (!(inferFrom[i] instanceof ParameterizedType)) {
251+
throw new TypeInferenceException();
252+
}
253+
ParameterizedType paramType = (ParameterizedType) types[i];
254+
ParameterizedType paramInferFrom = (ParameterizedType) inferFrom[i];
255+
inferTypeVariables(paramType.getActualTypeArguments(), paramInferFrom.getActualTypeArguments(),
256+
typeAssigns);
257+
258+
} else if (types[i] instanceof WildcardType) {
259+
// TODO Do we need to specifically handle Wildcards? Or are they
260+
// sufficiently handled by Types.satisfies below?
261+
}
262+
}
263+
// Check if the inferred types satisfy their bounds
264+
if (!Types.satisfies(typeAssigns)) {
265+
throw new TypeInferenceException();
266+
}
267+
}
268+
269+
/**
270+
* Finds the type parameters of the most specific super type of the
271+
* specified subType whose erasure is the specified superErasure. Hence,
272+
* will return the type parameters of superErasure possibly narrowed down by
273+
* subType. If superErasure is not raw or not a super type of subType, an
274+
* empty array will be returned.
275+
*
276+
* @param subType
277+
* the type to narrow down type parameters
278+
* @param superErasure
279+
* the erasure of an super type of subType to get the parameters
280+
* from
281+
* @return type parameters of superErasure possibly narrowed down by
282+
* subType, or empty type array if no exists or superErasure is not
283+
* a super type of subtype
284+
*/
285+
public static Type[] getParams(Class<?> subType, Class<?> superErasure) {
286+
Type pt = Types.parameterizeRaw(subType);
287+
Type superType = Types.getExactSuperType(pt, superErasure);
288+
if (superType != null && superType instanceof ParameterizedType) {
289+
return ((ParameterizedType) superType).getActualTypeArguments();
290+
}
291+
return new Type[0];
292+
}
293+
}

0 commit comments

Comments
 (0)