Skip to content

Commit 5474d71

Browse files
Treiblesschorlectrueden
authored andcommitted
Add utility to do certain type modifications
1 parent 45d4851 commit 5474d71

2 files changed

Lines changed: 395 additions & 0 deletions

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/*
2+
* #%L
3+
* ImageJ software for multidimensional image processing and analysis.
4+
* %%
5+
* Copyright (C) 2014 - 2018 ImageJ developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.ops.transform;
31+
32+
import java.lang.reflect.Array;
33+
import java.lang.reflect.ParameterizedType;
34+
import java.lang.reflect.Type;
35+
import java.util.Arrays;
36+
import java.util.List;
37+
import java.util.function.Function;
38+
39+
import org.scijava.util.Types;
40+
41+
/**
42+
* Utility class to do modification on {@link Type}s.
43+
*
44+
* @author David Kolb
45+
*/
46+
public final class TypeModUtils {
47+
48+
private TypeModUtils() {
49+
// NB: prevent instantiation of utility class.
50+
}
51+
52+
// -- Utility methods --
53+
54+
55+
/**
56+
* Attempts to perform "unlifting" of the specified types. See {@link #unliftParameterizedType(Type, Class, Class, Integer...)}.
57+
* The passed arry of types will be mutated inplace. Returns a boolean indicating if the list changed.
58+
*
59+
* @param types
60+
* @param searchRawType
61+
* @param unliftRawArgType
62+
* @param argIndices
63+
* @return
64+
*/
65+
public static boolean unliftParameterizedTypes(Type[] types, Class<?> searchRawType, Class<?> unliftRawArgType, Integer... argIndices) {
66+
return mutateTypes(types, t -> unliftParameterizedType(t, searchRawType, unliftRawArgType, argIndices));
67+
}
68+
69+
/**
70+
* Attempts to "unlift" the type arguments of the specified parameterized type if:
71+
* <ul>
72+
* <li>The type is a {@link ParameterizedType}</li>
73+
* <li>The types raw type equals the search raw type</li>
74+
* <li>The raw type of the type argument equals the specified unlift raw argument type</li>
75+
* </ul>
76+
* Otherwise, null will be returned indicating that "unlifting" is not possible.
77+
* The list of integers specifies which type arguments should be considered. If non are given,
78+
* all arguments will be processed. If arrays (reified or generic) should be "unlifted",
79+
* {@link Array}.class can be passed as the unlift raw arg type. Also see
80+
* {@link #unliftType(Type, Class)}.</br></br>
81+
* E.g.
82+
* <pre>
83+
* The type:
84+
* Function&lt;Iterable&lt;Double&gt;, String[]&gt;
85+
*
86+
* With: searchRawType = Function.class and unliftRawArgType = Iterable.class
87+
* will result in:
88+
* Function&lt;Double, String[]&gt;
89+
*
90+
* With: searchRawType = Function.class and unliftRawArgType = Array.class
91+
* will result in:
92+
* Function&lt;Iterable&lt;Double&gt;, String&gt;
93+
*
94+
* </pre>
95+
*
96+
*
97+
* @param type the type to "unlift"
98+
* @param searchRawType the raw type to check for
99+
* @param unliftRawArgType the raw type of type arguments that should be unlifted
100+
* @param argIndices indices of type arguments to process
101+
* @return "unlifted" type or null if not possible
102+
*/
103+
public static Type unliftParameterizedType(Type type, Class<?> searchRawType, Class<?> unliftRawArgType, Integer... argIndices) {
104+
if (type instanceof ParameterizedType) {
105+
ParameterizedType casted = (ParameterizedType) type;
106+
107+
if (Types.raw(type).equals(searchRawType)) {
108+
Type[] typeArgs = casted.getActualTypeArguments();
109+
boolean hit = unliftTypes(typeArgs, unliftRawArgType, argIndices);
110+
111+
if(hit) {
112+
return Types.parameterize(searchRawType, typeArgs);
113+
}
114+
}
115+
}
116+
return null;
117+
}
118+
119+
/**
120+
* Attempts to perform "unlifting" of the specified types. See {@link #unliftType(Type, Class)}.
121+
* The passed arry of types will be mutated inplace. Returns a boolean indicating if the list changed.
122+
*
123+
* @param types
124+
* @param unliftRawType
125+
* @param argIndices
126+
* @return
127+
*/
128+
public static boolean unliftTypes(Type[] types, Class<?> unliftRawType, Integer... argIndices) {
129+
return mutateTypes(types, t -> unliftType(t, unliftRawType), argIndices);
130+
}
131+
132+
/**
133+
* Attempts to "unlift" the specified type directly if its raw type matches the specified unlift raw type.
134+
* "Unlifiting" will be performed in two cases:
135+
* <ol>
136+
* <li>The specified type is an array type (either reified or generic, pass {@link Array}.class as unlift raw type).
137+
* Then the component type will be returned.</li>
138+
* <li>The specified type is a parameterized type. Then the first type argument will be returned if the parameterized
139+
* types raw type equals the unlift raw type and it has exactly one type argument.</li>
140+
* </ol>
141+
* Otherwise null will be returned indicating that "unlifting" is not possible.</br></br>
142+
* E.g.
143+
* <pre>
144+
* The type:
145+
* Iterable&lt;Double&gt;
146+
*
147+
* With: unliftRawType = Iterable.class
148+
* will result in:
149+
* Double
150+
*
151+
* The type:
152+
* Double[]
153+
*
154+
* With: unliftRawType = Array.class
155+
* will result in:
156+
* Double
157+
* </pre>
158+
*
159+
* @param type the type to "unlift"
160+
* @param unliftRawType the raw type to check for
161+
* @return "unlifted" type or null if not possible
162+
*/
163+
public static Type unliftType(Type type, Class<?> unliftRawType) {
164+
if (unliftRawType.equals(Array.class)) {
165+
return Types.component(type);
166+
} else if (type instanceof ParameterizedType) {
167+
ParameterizedType casted = (ParameterizedType) type;
168+
169+
if (Types.raw(casted).equals(unliftRawType)) {
170+
Type[] typeArgs = casted.getActualTypeArguments();
171+
if (typeArgs.length == 1) {
172+
return typeArgs[0];
173+
}
174+
}
175+
}
176+
return null;
177+
}
178+
179+
180+
/**
181+
* Attempts to perform raw type replacement of the specified types. See {@link #replaceRawType(Type, Class, Class)}.
182+
* The passed array of types will be mutated inplace. Returns a boolean indicating if the list changed.
183+
*
184+
* @param types
185+
* @param searchClass
186+
* @param replacement
187+
* @param indices
188+
* @return
189+
*/
190+
public static boolean replaceRawTypes(Type[] types, Class<?> searchClass, Class<?> replacement, Integer... indices) {
191+
return mutateTypes(types, t -> replaceRawType(t, searchClass, replacement), indices);
192+
}
193+
194+
/**
195+
* Attempts to exchange the raw type of the specified type with the specified replacement if the original
196+
* raw type equals the search class. This method does not check if the returned type is actually permitted
197+
* (i.e. if the passed type is a parameterized type, the replacement may have a different amount of type
198+
* arguments). If type is a class, the replacement will be returned if the class equals the search class.
199+
* If type is a parameterized type, its raw type will be exchanged.
200+
*
201+
* @param type the type whose raw type should be replaced
202+
* @param searchClass the raw type to check for
203+
* @param replacement the replacement type
204+
* @return
205+
*/
206+
public static Type replaceRawType(Type type, Class<?> searchClass, Class<?> replacement) {
207+
if (type instanceof ParameterizedType) {
208+
ParameterizedType casted = (ParameterizedType) type;
209+
Class<?> rawType = Types.raw(type);
210+
211+
if (rawType.equals(searchClass)) {
212+
return Types.parameterize(replacement, casted.getActualTypeArguments());
213+
}
214+
} else if (type instanceof Class) {
215+
if (((Class<?>) type).equals(searchClass)) {
216+
return replacement;
217+
}
218+
}
219+
return null;
220+
}
221+
222+
private static boolean mutateTypes(Type[] types, Function<Type, Type> typeFunc, Integer... indices) {
223+
List<Integer> is = Arrays.asList(indices);
224+
225+
boolean hit = false;
226+
for (int i = 0; i < types.length; i++) {
227+
if (!is.contains(i) && !is.isEmpty()) {
228+
continue;
229+
}
230+
Type replaced = typeFunc.apply(types[i]);
231+
232+
if (replaced != null) {
233+
types[i] = replaced;
234+
hit = true;
235+
}
236+
}
237+
return hit;
238+
}
239+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* #%L
3+
* SciJava Operations: a framework for reusable algorithms.
4+
* %%
5+
* Copyright (C) 2018 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.ops.util;
31+
32+
import static org.junit.Assert.assertFalse;
33+
import static org.junit.Assert.assertTrue;
34+
35+
import java.lang.reflect.Array;
36+
import java.lang.reflect.Type;
37+
import java.util.Arrays;
38+
import java.util.List;
39+
import java.util.function.Consumer;
40+
import java.util.function.Function;
41+
import java.util.function.Supplier;
42+
43+
import org.junit.Test;
44+
import org.scijava.ops.core.Computer;
45+
import org.scijava.ops.transform.TypeModUtils;
46+
import org.scijava.ops.types.Nil;
47+
48+
public class TypeModUtilsTest {
49+
50+
@Test
51+
public <A, B, C> void replaceRaw() {
52+
Nil<Iterable<A>> n1 = new Nil<Iterable<A>>() {
53+
};
54+
Nil<Function<Double, String>> n2 = new Nil<Function<Double, String>>() {
55+
};
56+
Nil<A> n3 = new Nil<A>() {
57+
};
58+
Nil<B[]> n4 = new Nil<B[]>() {
59+
};
60+
Nil<Supplier<? extends Number>> n5 = new Nil<Supplier<? extends Number>>() {
61+
};
62+
Nil<Consumer<C>> n6 = new Nil<Consumer<C>>() {
63+
};
64+
65+
Type[] types = new Type[] { Double.class, String.class, Iterable.class, String.class };
66+
assertTrue(TypeModUtils.replaceRawTypes(types, String.class, Double.class));
67+
assertTrue(Arrays.deepEquals(types, new Type[] { Double.class, Double.class, Iterable.class, Double.class }));
68+
69+
types = new Type[] { Double.class, String.class, Iterable.class, String.class };
70+
assertTrue(TypeModUtils.replaceRawTypes(types, String.class, Double.class, 1));
71+
assertTrue(Arrays.deepEquals(types, new Type[] { Double.class, Double.class, Iterable.class, String.class }));
72+
73+
types = new Type[] { Double.class, String.class, Iterable.class, String.class };
74+
assertTrue(TypeModUtils.replaceRawTypes(types, String.class, Double.class, 1, 3));
75+
assertTrue(Arrays.deepEquals(types, new Type[] { Double.class, Double.class, Iterable.class, Double.class }));
76+
77+
types = new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() };
78+
assertTrue(TypeModUtils.replaceRawTypes(types, Function.class, Function.class));
79+
assertTrue(Arrays.deepEquals(types,
80+
new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() }));
81+
82+
types = new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() };
83+
assertTrue(TypeModUtils.replaceRawTypes(types, Function.class, Computer.class));
84+
Nil<Computer<Double, String>> y2 = new Nil<Computer<Double, String>>() {
85+
};
86+
assertTrue(Arrays.deepEquals(types,
87+
new Type[] { n1.getType(), y2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() }));
88+
89+
types = new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() };
90+
assertTrue(TypeModUtils.replaceRawTypes(types, Supplier.class, Iterable.class));
91+
Nil<Iterable<? extends Number>> y5 = new Nil<Iterable<? extends Number>>() {
92+
};
93+
assertTrue(Arrays.deepEquals(types,
94+
new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), y5.getType(), n6.getType() }));
95+
96+
types = new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() };
97+
assertTrue(TypeModUtils.replaceRawTypes(types, Consumer.class, List.class));
98+
Nil<List<C>> y6 = new Nil<List<C>>() {
99+
};
100+
assertTrue(Arrays.deepEquals(types,
101+
new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), y6.getType() }));
102+
103+
types = new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() };
104+
assertFalse(TypeModUtils.replaceRawTypes(types, Integer.class, List.class));
105+
assertTrue(Arrays.deepEquals(types,
106+
new Type[] { n1.getType(), n2.getType(), n3.getType(), n4.getType(), n5.getType(), n6.getType() }));
107+
}
108+
109+
@Test
110+
public <A, B, C> void unliftParameterized() {
111+
Nil<Computer<A, B[]>> n1 = new Nil<Computer<A, B[]>>() {
112+
};
113+
Nil<Function<Double, String[]>> n2 = new Nil<Function<Double, String[]>>() {
114+
};
115+
Nil<Function<C[], Iterable<String>>> n3 = new Nil<Function<C[], Iterable<String>>>() {
116+
};
117+
Nil<Computer<C[], Function<String, Integer>>> n4 = new Nil<Computer<C[], Function<String, Integer>>>() {
118+
};
119+
120+
Nil<Function<Double, String>> y2 = new Nil<Function<Double, String>>() {
121+
};
122+
Nil<Function<C, Iterable<String>>> y3 = new Nil<Function<C, Iterable<String>>>() {
123+
};
124+
Nil<Function<C[], String>> y31 = new Nil<Function<C[], String>>() {
125+
};
126+
127+
Type[] types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
128+
assertFalse(TypeModUtils.unliftParameterizedTypes(types, Computer.class, Iterable.class));
129+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), n2.getType(), n3.getType() }));
130+
131+
types = new Type[] { n1.getType(), n2.getType(), n4.getType() };
132+
assertFalse(TypeModUtils.unliftParameterizedTypes(types, Computer.class, Function.class));
133+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), n2.getType(), n4.getType() }));
134+
135+
types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
136+
assertTrue(TypeModUtils.unliftParameterizedTypes(types, Function.class, Iterable.class));
137+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), n2.getType(), y31.getType() }));
138+
139+
types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
140+
assertTrue(TypeModUtils.unliftParameterizedTypes(types, Function.class, Array.class));
141+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), y2.getType(), y3.getType() }));
142+
143+
types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
144+
assertTrue(TypeModUtils.unliftParameterizedTypes(types, Function.class, Array.class, 1));
145+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), y2.getType(), n3.getType() }));
146+
147+
types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
148+
assertTrue(TypeModUtils.unliftParameterizedTypes(types, Function.class, Array.class, 0));
149+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), n2.getType(), y3.getType() }));
150+
151+
types = new Type[] { n1.getType(), n2.getType(), n3.getType() };
152+
assertTrue(TypeModUtils.unliftParameterizedTypes(types, Function.class, Array.class, 0, 1));
153+
assertTrue(Arrays.deepEquals(types, new Type[] { n1.getType(), y2.getType(), y3.getType() }));
154+
155+
}
156+
}

0 commit comments

Comments
 (0)