Skip to content

Commit 94d2dab

Browse files
committed
Generalize OpTransformer to n-to-1 transformations
OpTransformer is not bound to fixed source and target types anymore and may specify multiple potential source Ops given a single target Op. This allows using a single transformer class to transform between entire categories of Ops instead of just single Op pairs (e.g. Functions to Computers instead of Function to Computer, BiFunction to BiComputer, etc.). The old 1-to-1 OpTransformer is substituted by OpMapper which improves type safety somewhat by generically typing its source and target types. Above changes bring along the following adjustments: - Merge computer to function transformers into single class - Merge function to computer transformers into single class - Merge functional interface to Op runner transformers into single class - Let lift transformers implement OpMapper; can also be generalized to n-to-1 however
1 parent ae551f1 commit 94d2dab

37 files changed

Lines changed: 1181 additions & 1563 deletions

src/main/java/org/scijava/ops/OpUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
import org.scijava.ops.matcher.OpInfo;
4444
import org.scijava.ops.matcher.OpRef;
4545
import org.scijava.param.ParameterMember;
46+
import org.scijava.param.ParameterStructs;
4647
import org.scijava.param.ValidityException;
4748
import org.scijava.param.ValidityProblem;
4849
import org.scijava.struct.Member;
4950
import org.scijava.struct.MemberInstance;
5051
import org.scijava.struct.Struct;
5152
import org.scijava.struct.StructInstance;
5253
import org.scijava.struct.ValueAccessible;
54+
import org.scijava.util.Types;
5355

5456
/**
5557
* Utility methods for working with ops.
@@ -427,4 +429,14 @@ public static String opString(final OpInfo info) {
427429
sb.append(")\n");
428430
return sb.toString();
429431
}
432+
433+
public static Class<?> findFirstImplementedFunctionalInterface(final OpRef opRef) {
434+
for (final Type opType : opRef.getTypes()) {
435+
final Class<?> functionalInterface = ParameterStructs.findFunctionalInterface(Types.raw(opType));
436+
if (functionalInterface != null) {
437+
return functionalInterface;
438+
}
439+
}
440+
return null;
441+
}
430442
}

src/main/java/org/scijava/ops/transform/DefaultOpTransformerService.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
package org.scijava.ops.transform;
3434

3535
import java.util.ArrayList;
36+
import java.util.Collection;
3637
import java.util.List;
3738

3839
import org.scijava.ops.OpEnvironment;
@@ -63,9 +64,13 @@ public List<OpTransformation> getTansformationsTo(OpRef toRef) {
6364
List<OpTransformation> transforms = new ArrayList<>();
6465

6566
for (OpTransformer ot: getInstances()) {
66-
OpRef fromRef = ot.getRefTransformingTo(toRef);
67-
if (fromRef != null) {
68-
transforms.add(new OpTransformation(fromRef, toRef, ot));
67+
final Collection<OpRef> fromRefs = ot.getRefsTransformingTo(toRef);
68+
if (fromRefs != null) {
69+
for (OpRef fromRef : fromRefs) {
70+
if (fromRef != null) {
71+
transforms.add(new OpTransformation(fromRef, toRef, ot));
72+
}
73+
}
6974
}
7075
}
7176
return transforms;
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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.transform;
31+
32+
import java.lang.reflect.Type;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
35+
import java.util.Collection;
36+
import java.util.Collections;
37+
import java.util.List;
38+
import java.util.function.BiFunction;
39+
import java.util.function.Function;
40+
import java.util.stream.Stream;
41+
42+
import org.scijava.ops.OpService;
43+
import org.scijava.ops.core.computer.BiComputer;
44+
import org.scijava.ops.core.computer.Computer;
45+
import org.scijava.ops.core.computer.Computer3;
46+
import org.scijava.ops.core.computer.Computer4;
47+
import org.scijava.ops.core.computer.Computer5;
48+
import org.scijava.ops.core.function.Function3;
49+
import org.scijava.ops.core.function.Function4;
50+
import org.scijava.ops.core.function.Function5;
51+
import org.scijava.ops.core.function.Function6;
52+
import org.scijava.ops.core.inplace.BiInplaceFirst;
53+
import org.scijava.ops.core.inplace.BiInplaceSecond;
54+
import org.scijava.ops.core.inplace.Inplace;
55+
import org.scijava.ops.core.inplace.Inplace3First;
56+
import org.scijava.ops.core.inplace.Inplace3Second;
57+
import org.scijava.ops.core.inplace.Inplace4First;
58+
import org.scijava.ops.core.inplace.Inplace5First;
59+
import org.scijava.ops.matcher.OpRef;
60+
import org.scijava.ops.util.Computers;
61+
import org.scijava.ops.util.Functions;
62+
import org.scijava.ops.util.Inplaces;
63+
import org.scijava.ops.util.OpRunners;
64+
import org.scijava.param.ParameterStructs;
65+
import org.scijava.plugin.Plugin;
66+
import org.scijava.util.Types;
67+
68+
/**
69+
* @author David Kolb
70+
* @author Marcel Wiedenmann
71+
*/
72+
@Plugin(type = OpTransformer.class)
73+
public class FunctionalToOpRunnerTransformer implements OpTransformer {
74+
75+
@Override
76+
public OpRunner<?> transform(final OpService opService, final Object src, final OpRef targetRef)
77+
throws OpTransformationException
78+
{
79+
final Class<?> srcFunctionalRawType = ParameterStructs.findFunctionalInterface(src.getClass());
80+
if (srcFunctionalRawType != null) {
81+
if (Computers.isComputer(srcFunctionalRawType)) {
82+
// NB: Increase arity by one to account for Op runner's additional
83+
// output parameter. See below for more details.
84+
checkCanTransform(src, Computers.ALL_COMPUTERS.get(srcFunctionalRawType) + 1, targetRef);
85+
return computerToRunner(src, srcFunctionalRawType);
86+
}
87+
if (Functions.isFunction(srcFunctionalRawType)) {
88+
checkCanTransform(src, Functions.ALL_FUNCTIONS.get(srcFunctionalRawType), targetRef);
89+
return functionToRunner(src, srcFunctionalRawType);
90+
}
91+
if (Inplaces.isInplace(srcFunctionalRawType)) {
92+
checkCanTransform(src, Inplaces.ALL_INPLACES.get(srcFunctionalRawType).arity(), targetRef);
93+
return inplaceToRunner(src, srcFunctionalRawType);
94+
}
95+
}
96+
throw createInvalidSourceOpException(src, "does not implement a functional interface.");
97+
}
98+
99+
private static void checkCanTransform(final Object src, final int srcArity, final OpRef targetRef)
100+
throws OpTransformationException
101+
{
102+
String problem = null;
103+
if (!isOpRunner(targetRef)) {
104+
problem = "Target is not an " + OpRunner.class.getName() + ".";
105+
}
106+
else {
107+
final int targetArity = targetRef.getArgs().length;
108+
if (srcArity != targetArity) {
109+
problem = "Source and target arities disagree (" + srcArity + " vs. " + targetArity + ").";
110+
}
111+
}
112+
if (problem != null) {
113+
throw new OpTransformationException("Cannot transform source Op:\n" + src.getClass().getName() +
114+
"into target:\n" + targetRef + "\nusing transformer: " + FunctionalToOpRunnerTransformer.class.getName() +
115+
".\n" + problem);
116+
}
117+
}
118+
119+
private static boolean isOpRunner(final OpRef targetRef) {
120+
return Arrays.stream(targetRef.getTypes()) //
121+
.anyMatch(t -> OpRunner.class.isAssignableFrom(Types.raw(t)));
122+
}
123+
124+
private static OpRunner<?> computerToRunner(final Object src, final Class<?> srcFunctionalRawType)
125+
throws OpTransformationException
126+
{
127+
if (src instanceof Computer) return OpRunners.Computers.toRunner((Computer<?, ?>) src);
128+
if (src instanceof BiComputer) return OpRunners.Computers.toRunner((BiComputer<?, ?, ?>) src);
129+
if (src instanceof Computer3) return OpRunners.Computers.toRunner((Computer3<?, ?, ?, ?>) src);
130+
if (src instanceof Computer4) return OpRunners.Computers.toRunner((Computer4<?, ?, ?, ?, ?>) src);
131+
if (src instanceof Computer5) return OpRunners.Computers.toRunner((Computer5<?, ?, ?, ?, ?, ?>) src);
132+
throw createInvalidSourceOpException(src,
133+
"could not be transformed. The implemented computer type (%s) is not supported by this transformer.",
134+
srcFunctionalRawType.getName());
135+
}
136+
137+
private static OpRunner<?> functionToRunner(final Object src, final Class<?> srcFunctionalRawType)
138+
throws OpTransformationException
139+
{
140+
if (src instanceof Function) return OpRunners.Functions.toRunner((Function<?, ?>) src);
141+
if (src instanceof BiFunction) return OpRunners.Functions.toRunner((BiFunction<?, ?, ?>) src);
142+
if (src instanceof Function3) return OpRunners.Functions.toRunner((Function3<?, ?, ?, ?>) src);
143+
if (src instanceof Function4) return OpRunners.Functions.toRunner((Function4<?, ?, ?, ?, ?>) src);
144+
if (src instanceof Function5) return OpRunners.Functions.toRunner((Function5<?, ?, ?, ?, ?, ?>) src);
145+
if (src instanceof Function6) return OpRunners.Functions.toRunner((Function6<?, ?, ?, ?, ?, ?, ?>) src);
146+
throw createInvalidSourceOpException(src,
147+
"could not be transformed. The implemented function type (%s) is not supported by this transformer.",
148+
srcFunctionalRawType.getName());
149+
}
150+
151+
private static OpRunner<?> inplaceToRunner(final Object src, final Class<?> srcFunctionalRawType)
152+
throws OpTransformationException
153+
{
154+
if (src instanceof Inplace) return OpRunners.Inplaces.toRunner((Inplace<?>) src);
155+
if (src instanceof BiInplaceFirst) return OpRunners.Inplaces.toRunner((BiInplaceFirst<?, ?>) src);
156+
if (src instanceof BiInplaceSecond) return OpRunners.Inplaces.toRunner((BiInplaceSecond<?, ?>) src);
157+
if (src instanceof Inplace3First) return OpRunners.Inplaces.toRunner((Inplace3First<?, ?, ?>) src);
158+
if (src instanceof Inplace3Second) return OpRunners.Inplaces.toRunner((Inplace3Second<?, ?, ?>) src);
159+
if (src instanceof Inplace4First) return OpRunners.Inplaces.toRunner((Inplace4First<?, ?, ?, ?>) src);
160+
if (src instanceof Inplace5First) return OpRunners.Inplaces.toRunner((Inplace5First<?, ?, ?, ?, ?>) src);
161+
throw createInvalidSourceOpException(src,
162+
"could not be transformed. The implemented inplace type (%s) is not supported by this transformer.",
163+
srcFunctionalRawType.getName());
164+
}
165+
166+
private static OpTransformationException createInvalidSourceOpException(final Object src, final String problem,
167+
final Object... problemArgs)
168+
{
169+
return new OpTransformationException("Source Op:\n" + src.getClass().getName() + String.format(problem,
170+
problemArgs));
171+
}
172+
173+
@Override
174+
public Collection<OpRef> getRefsTransformingTo(final OpRef targetRef) {
175+
if (!isOpRunner(targetRef)) {
176+
return Collections.emptyList();
177+
}
178+
final Type[] targetInputParamTypes = targetRef.getArgs();
179+
180+
final int targetArity = targetInputParamTypes.length;
181+
// NB: Decrease arity by one when looking for matching computers since their
182+
// output parameter is part of the Op runner's list of input parameters. The
183+
// Op runner's (additional) output parameter is always of type Object here.
184+
// Note that we don't need to do this for inplaces because their arity is
185+
// counted differently and already includes the output (= mutated input)
186+
// parameter.
187+
final Class<?> srcComputer = getSourceComputerOfArity(targetArity - 1);
188+
final Class<?> srcFunction = getSourceFunctionOfArity(targetArity);
189+
final List<Class<?>> srcInplaces = getInplacesOfArity(targetArity);
190+
191+
final List<OpRef> srcRefs = new ArrayList<>(1 + 1 + srcInplaces.size());
192+
addFunctionSourceRef(srcRefs, srcFunction, targetRef, targetInputParamTypes);
193+
addComputerAndInplacesSourceRefs(srcRefs, srcComputer, srcInplaces, targetRef, targetInputParamTypes);
194+
return srcRefs;
195+
}
196+
197+
private static Class<?> getSourceComputerOfArity(final int arity) {
198+
return Computers.ALL_COMPUTERS.inverse().get(arity);
199+
}
200+
201+
private static Class<?> getSourceFunctionOfArity(final int arity) {
202+
return Functions.ALL_FUNCTIONS.inverse().get(arity);
203+
}
204+
205+
private static List<Class<?>> getInplacesOfArity(final int arity) {
206+
return Inplaces.getInplacesOfArity(arity);
207+
}
208+
209+
private static void addFunctionSourceRef(final List<OpRef> srcRefs, final Class<?> srcFunction, final OpRef targetRef,
210+
final Type[] targetInputParamTypes)
211+
{
212+
if (srcFunction != null) {
213+
// NB: Use both input and output parameters of the Op runner.
214+
final Type[] targetParamTypes = Stream.concat(Arrays.stream(targetInputParamTypes), Stream.of(targetRef
215+
.getOutType())).toArray(Type[]::new);
216+
final Type[] targetOpTypes = parameterizeTargetOpTypes(targetRef, targetParamTypes);
217+
addSourceRef(srcRefs, srcFunction, targetRef, targetOpTypes, targetInputParamTypes);
218+
}
219+
}
220+
221+
private static void addComputerAndInplacesSourceRefs(final List<OpRef> srcRefs, final Class<?> srcComputer,
222+
final List<Class<?>> srcInplaces, final OpRef targetRef, final Type[] targetInputParamTypes)
223+
{
224+
// NB: Use only the input parameters of the Op runner.
225+
final Type[] targetParamTypes = targetInputParamTypes.clone();
226+
final Type[] targetOpTypes = parameterizeTargetOpTypes(targetRef, targetParamTypes);
227+
if (srcComputer != null) addSourceRef(srcRefs, srcComputer, targetRef, targetOpTypes, targetInputParamTypes);
228+
for (final Class<?> srcInplace : srcInplaces) {
229+
addSourceRef(srcRefs, srcInplace, targetRef, targetOpTypes, targetInputParamTypes);
230+
}
231+
}
232+
233+
private static Type[] parameterizeTargetOpTypes(final OpRef targetRef, final Type[] targetParamTypes) {
234+
return Arrays.stream(targetRef.getTypes()).map(t -> Types.parameterize(Types.raw(t), targetParamTypes)).toArray(
235+
Type[]::new);
236+
}
237+
238+
private static void addSourceRef(final List<OpRef> srcRefs, final Class<?> srcOpRawType, final OpRef targetRef,
239+
final Type[] targetOpTypes, final Type[] targetInputParamTypes)
240+
{
241+
final Type[] srcOpTypes = targetOpTypes.clone();
242+
final boolean hit = TypeModUtils.replaceRawTypes(srcOpTypes, OpRunner.class, srcOpRawType);
243+
if (hit) {
244+
srcRefs.add(OpRef.fromTypes(targetRef.getName(), srcOpTypes, targetRef.getOutType(), targetInputParamTypes));
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)