Skip to content

Commit ab8973d

Browse files
committed
WIP: start working on lambda field structs
1 parent ff52115 commit ab8973d

File tree

4 files changed

+326
-1
lines changed

4 files changed

+326
-1
lines changed

ops/pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,28 @@
8989
<properties>
9090
<license.licenseName>bsd_2</license.licenseName>
9191
<license.copyrightOwners>SciJava developers.</license.copyrightOwners>
92+
<enforcer.skip>true</enforcer.skip>
9293
</properties>
94+
95+
<dependencies>
96+
<dependency>
97+
<groupId>net.imglib2</groupId>
98+
<artifactId>imglib2</artifactId>
99+
</dependency>
100+
<dependency>
101+
<groupId>org.scijava</groupId>
102+
<artifactId>scijava-param</artifactId>
103+
<version>${project.version}</version>
104+
</dependency>
105+
<dependency>
106+
<groupId>org.scijava</groupId>
107+
<artifactId>scijava-struct</artifactId>
108+
<version>${project.version}</version>
109+
</dependency>
110+
<dependency>
111+
<groupId>junit</groupId>
112+
<artifactId>junit</artifactId>
113+
<scope>test</scope>
114+
</dependency>
115+
</dependencies>
93116
</project>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
2+
package org.scijava.ops;
3+
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertSame;
6+
7+
import java.lang.reflect.Type;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.function.BiFunction;
11+
import java.util.function.Function;
12+
import java.util.stream.Stream;
13+
14+
import net.imglib2.type.numeric.RealType;
15+
import org.junit.Test;
16+
import org.scijava.param.Parameter;
17+
import org.scijava.param.ValidityException;
18+
import org.scijava.struct.ItemIO;
19+
import org.scijava.struct.Member;
20+
import org.scijava.struct.Struct;
21+
22+
/**
23+
* Playing with method parameters.
24+
*
25+
* @author Curtis Rueden
26+
*/
27+
public class MethodParameterTest<I extends RealType<I>, O extends RealType<O>, T extends RealType<T>> {
28+
29+
// Calling a static method via reflection is slow.
30+
// Wrapping a static method as a lambda is potentially faster.
31+
// Static methods in imglib2-algorithm can be lambdaed using :: syntax.
32+
// May want to make TriFunction, Function4, Function5, etc. ifaces.
33+
// Can we write new ops as lambdas?
34+
35+
//@SomeAnnotationHere?
36+
public BiFunction<T, T, T> adder = (x, y) -> {
37+
final T result = x.copy();
38+
result.add(y);
39+
return result;
40+
};
41+
42+
// The above is reasonably concise, but does not implement e.g. MathAddOp.
43+
// What if we get rid of the need to type it that way? Why do we need it?
44+
// The string name of the op should be enough, no?
45+
// There is no benefit to the MathAddOp interface; we don't use it.
46+
// Will be simpler, and less convoluted and confusing, without it.
47+
48+
// To get type safety back, we could do:
49+
// ops.op(MathOps.ADD, new Nil<...>() {});
50+
51+
// Another challenge is: how can/do we annotate the parameters?
52+
// It's fine if everything can be inferred with defaults, but in some
53+
// cases the programmer will want to define some of the attributes.
54+
55+
//@SomeAnnotationHere?
56+
@Parameter(key = "y", description = "The Y thing. It has some properties.")
57+
@Parameter(key = "x")
58+
@Parameter(key = "result", type = ItemIO.BOTH)
59+
public BiComputerOp<T, T, T> adderComputer = (x, y, result) -> {
60+
result.set(x);
61+
result.add(y);
62+
};
63+
64+
// Above is a computer. Also concise. Does it make sense to reuse @Parameter?
65+
66+
// Also: how do we discover this? The OpEnvironment will need its own
67+
// op cache. Its currency needs to be Structs. And we need new logic for
68+
// structifying the above field. When a new instance is requested,
69+
// it could call clone() on the existing instance (?), rather than
70+
// using the no-args constructor.
71+
72+
// And what about static methods from e.g. imglib2-algorithm?
73+
// Here comes a method. Let's pretend it resides in a different class
74+
// somewhere we don't control, but we want it as an op:
75+
76+
public static <V extends RealType<V>> V myAwesomeAdderFunction(
77+
final V x,
78+
final V y)
79+
{
80+
final V result = x.copy();
81+
result.add(y);
82+
return result;
83+
}
84+
85+
// The above method is not a functional interface, but can become one:
86+
87+
//@SomeAnnotationHere?
88+
public BiFunction<T, T, T> adderFromMethod =
89+
MethodParameterTest::myAwesomeAdderFunction;
90+
91+
// What about extra/secondary parameters? Can we do that, too?
92+
// Yes, but the syntax becomes much less concise:
93+
94+
//@SomeAnnotationHere?
95+
public BiComputerOp<T, T, T> addAndScale = new BiComputerOp<T, T, T>() {
96+
97+
@Parameter
98+
private T scale;
99+
100+
@Override
101+
public void accept(
102+
final @Parameter(key = "x") T x,
103+
final @Parameter(key = "y") T y,
104+
final @Parameter(key = "result") T result)
105+
{
106+
result.set(x);
107+
result.add(y);
108+
result.mul(scale);
109+
}
110+
};
111+
112+
// At that point, it is just another class. May as well define it
113+
// explicitly as such using "public static class" instead of as a field.
114+
// Same number of lines of code!
115+
116+
@Test
117+
public void testStuff() throws Exception {
118+
BiFunction<String, String, String> stringFunc = UsefulMethods::concat;
119+
System.out.println(stringFunc.apply("x", "y"));
120+
121+
BiFunction<Integer, Integer, Integer> intFunc = UsefulMethods::concat;
122+
System.out.println(intFunc.getClass());
123+
Stream.of(intFunc.getClass().getDeclaredMethods()).forEach(System.out::println);
124+
System.out.println(intFunc.getClass().getSuperclass());
125+
Stream.of(intFunc.getClass().getInterfaces()).forEach(System.out::println);
126+
System.out.println(intFunc.apply(5, 6));
127+
128+
@Parameter
129+
final Function<String, String> doubler = v -> v + v;
130+
131+
final Struct p = //
132+
MethodStructs.function2(UsefulMethods::concat);
133+
final List<Member<?>> items = p.members();
134+
assertParam("a", int.class, ItemIO.INPUT, items.get(0));
135+
assertParam("b", Double.class, ItemIO.INPUT, items.get(1));
136+
assertParam("c", byte.class, ItemIO.INPUT, items.get(2));
137+
assertParam("d", Object.class, ItemIO.INPUT, items.get(3));
138+
assertParam("o", double.class, ItemIO.OUTPUT, items.get(4));
139+
assertParam("p", String.class, ItemIO.OUTPUT, items.get(5));
140+
}
141+
142+
// -- Helper methods --
143+
144+
private void assertParam(final String key, final Type type,
145+
final ItemIO ioType, final Member<?> pMember)
146+
{
147+
assertEquals(key, pMember.getKey());
148+
assertEquals(type, pMember.getType());
149+
assertSame(ioType, pMember.getIOType());
150+
}
151+
152+
// -- Helper classes --
153+
154+
public static class UsefulMethods {
155+
156+
public static String concat(final String s, final String t) {
157+
return "String:" + s + t;
158+
}
159+
160+
public static Integer concat(final Integer s, final Integer t) {
161+
return s + t + 1;
162+
}
163+
164+
public static int concat(final int s, final int t) {
165+
return s + t;
166+
}
167+
168+
public static String concat(final int a, final Double b, final byte c, final Object d) {
169+
return "" + a + b + c + d;
170+
}
171+
}
172+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.scijava.ops;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.Modifier;
6+
import java.lang.reflect.Type;
7+
import java.util.ArrayList;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Set;
11+
import java.util.function.BiFunction;
12+
import java.util.function.Function;
13+
14+
import org.scijava.param.FieldParameterMember;
15+
import org.scijava.param.FunctionalParameterMember;
16+
import org.scijava.param.Parameter;
17+
import org.scijava.param.ParameterMember;
18+
import org.scijava.param.Parameters;
19+
import org.scijava.param.ValidityException;
20+
import org.scijava.param.ValidityProblem;
21+
import org.scijava.struct.Member;
22+
import org.scijava.struct.Struct;
23+
import org.scijava.util.ClassUtils;
24+
import org.scijava.util.Types;
25+
26+
public final class MethodStructs {
27+
28+
public static Struct function2(final BiFunction<?, ?, ?> function)
29+
throws ValidityException
30+
{
31+
final List<Member<?>> items = parse(function);
32+
return () -> items;
33+
}
34+
35+
public static Struct function(final Function<?, ?> function)
36+
throws ValidityException
37+
{
38+
final List<Member<?>> items = parse(function);
39+
return () -> items;
40+
}
41+
42+
public static List<Member<?>> parse(final Method m)
43+
throws ValidityException
44+
{
45+
if (m == null) return null;
46+
47+
m.getGenericParameterTypes();
48+
49+
final ArrayList<Member<?>> items = new ArrayList<>();
50+
final ArrayList<ValidityProblem> problems = new ArrayList<>();
51+
52+
// NB: Reject abstract classes.
53+
if (Modifier.isAbstract(type.getModifiers())) {
54+
problems.add(new ValidityProblem("Struct class is abstract"));
55+
}
56+
57+
final Set<String> names = new HashSet<>();
58+
59+
// Parse class level (i.e., generic) @Parameter annotations.
60+
61+
final Class<?> paramsClass = findParametersDeclaration(type);
62+
if (paramsClass != null) {
63+
final Parameters params = paramsClass.getAnnotation(Parameters.class);
64+
final Class<?> functionalType = findFunctionalInterface(paramsClass);
65+
final Parameter[] p = params.value();
66+
final int paramCount = functionalType.getTypeParameters().length;
67+
// TODO: Consider allowing partial override of class @Parameters.
68+
if (p.length == paramCount) {
69+
for (int i=0; i<p.length; i++) {
70+
String key = p[i].key();
71+
final Type itemType = Types.param(type, functionalType, i);
72+
final Class<?> rawItemType = Types.raw(itemType);
73+
final boolean valid = checkValidity(p[i], key, rawItemType, false,
74+
names, problems);
75+
if (!valid) continue; // NB: Skip invalid parameters.
76+
77+
// add item to the list
78+
// TODO make more DRY
79+
try {
80+
final ParameterMember<?> item = //
81+
new FunctionalParameterMember<>(itemType, p[i]);
82+
names.add(key);
83+
items.add(item);
84+
}
85+
catch (final ValidityException exc) {
86+
problems.addAll(exc.problems());
87+
}
88+
}
89+
}
90+
else {
91+
problems.add(new ValidityProblem("Need " + paramCount +
92+
" parameters for " + functionalType.getName() + " but got " +
93+
p.length));
94+
}
95+
}
96+
97+
// Parse field level @Parameter annotations.
98+
99+
final List<Field> fields = ClassUtils.getAnnotatedFields(type,
100+
Parameter.class);
101+
102+
for (final Field f : fields) {
103+
f.setAccessible(true); // expose private fields
104+
105+
final Parameter param = f.getAnnotation(Parameter.class);
106+
107+
final String name = f.getName();
108+
final boolean isFinal = Modifier.isFinal(f.getModifiers());
109+
final boolean valid = checkValidity(param, name, f.getType(),
110+
isFinal, names, problems);
111+
if (!valid) continue; // NB: Skip invalid parameters.
112+
113+
// add item to the list
114+
try {
115+
final ParameterMember<?> item = new FieldParameterMember<>(f, type);
116+
names.add(name);
117+
items.add(item);
118+
}
119+
catch (final ValidityException exc) {
120+
problems.addAll(exc.problems());
121+
}
122+
}
123+
124+
// Fail if there were any problems.
125+
126+
if (!problems.isEmpty()) throw new ValidityException(problems);
127+
128+
return items;
129+
}
130+
}

param/src/main/java/org/scijava/param/Parameter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
* @author Curtis Rueden
5252
*/
5353
@Retention(RetentionPolicy.RUNTIME)
54-
@Target({ ElementType.FIELD, ElementType.TYPE })
54+
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE })
5555
@Repeatable(Parameters.class)
5656
public @interface Parameter {
5757

0 commit comments

Comments
 (0)