Skip to content

Commit 302d57d

Browse files
gselzerctrueden
authored andcommitted
Work on OpAdaptationInfoChainGenerator
That's a mouthful :) Plus: * Test Adaptation recovery with dependencies * substituteTypeVars: add GenericArrayType support * InfoChainGenerator determinism Signed-off-by: Curtis Rueden <ctrueden@wisc.edu>
1 parent b4bdc97 commit 302d57d

6 files changed

Lines changed: 155 additions & 3 deletions

File tree

scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/InfoChainGenerator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
package org.scijava.ops.api;
33

44
import java.util.Collection;
5+
import java.util.Collections;
6+
import java.util.List;
57
import java.util.Map;
68
import java.util.Optional;
9+
import java.util.stream.Collectors;
710

811
import org.scijava.plugin.SciJavaPlugin;
912

@@ -38,9 +41,10 @@ InfoChain generate(String signature, Map<String, OpInfo> idMap,
3841
static Optional<InfoChainGenerator> findSuitableGenerator(String signature,
3942
Collection<InfoChainGenerator> generators)
4043
{
41-
return generators.stream() //
44+
Optional<InfoChainGenerator> gen = generators.stream() //
4245
.filter(g -> g.canGenerate(signature)) //
4346
.max((i1, i2) -> (int) (i1.priority() - i2.priority()));
47+
return gen;
4448
}
4549

4650
/**

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultInfoChainGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public boolean canGenerate(String signature) {
6666

6767
@Override
6868
public double priority() {
69-
return Double.MIN_VALUE;
69+
return -(1e10);
7070
}
7171

7272
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
package org.scijava.ops.engine.matcher.impl;
3+
4+
import java.lang.reflect.Type;
5+
import java.lang.reflect.TypeVariable;
6+
import java.util.Collection;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
11+
import org.scijava.ops.api.InfoChain;
12+
import org.scijava.ops.api.InfoChainGenerator;
13+
import org.scijava.ops.api.OpInfo;
14+
import org.scijava.plugin.Plugin;
15+
import org.scijava.types.Types;
16+
17+
@Plugin(type = InfoChainGenerator.class)
18+
public class AdaptationInfoChainGenerator implements InfoChainGenerator {
19+
20+
@Override
21+
public InfoChain generate(String signature, Map<String, OpInfo> idMap,
22+
Collection<InfoChainGenerator> generators)
23+
{
24+
// Resolve adaptor
25+
int start = 0;
26+
int end = matchingCurlyBrace(signature, signature.indexOf('{'));
27+
String adaptorSignature = signature.substring(start, end + 1);
28+
Optional<InfoChainGenerator> adaptorGenOpt = InfoChainGenerator.findSuitableGenerator(adaptorSignature, generators);
29+
if (adaptorGenOpt == null) throw new IllegalArgumentException("Could not find an InfoChainGenerator able to handle id" + adaptorSignature);
30+
InfoChain adaptorChain = adaptorGenOpt.get().generate(adaptorSignature, idMap, generators);
31+
32+
// Resolve adapted Op
33+
int delim_start = signature.indexOf(OpAdaptationInfo.IMPL_DELIMITER);
34+
if (delim_start == -1) throw new IllegalStateException("Op signature " +
35+
signature + " must have signature delimiter " +
36+
OpAdaptationInfo.IMPL_DELIMITER +
37+
", but does not! Was InfoChainGenerator.canGenerate() called?");
38+
int originalOpStart = delim_start + OpAdaptationInfo.IMPL_DELIMITER.length();
39+
String originalSignature = signature.substring(originalOpStart);
40+
Optional<InfoChainGenerator> originalGenOpt = InfoChainGenerator.findSuitableGenerator(originalSignature, generators);
41+
if (originalGenOpt == null) throw new IllegalArgumentException("Could not find an InfoChainGenerator able to handle id" + originalSignature);
42+
InfoChain originalChain = originalGenOpt.get().generate(originalSignature, idMap, generators);
43+
44+
// Rebuild original chain with an OpAdaptationInfo
45+
OpInfo originalInfo = originalChain.info();
46+
// TODO: The op type is wrong!
47+
Map<TypeVariable<?>, Type> typeVarAssigns = new HashMap<>();
48+
if (!Types.isAssignable(originalInfo.opType(), adaptorChain.info().inputs().get(0).getType(), typeVarAssigns)) throw new IllegalArgumentException("The adaptor cannot be used on Op " + originalInfo);
49+
Type adaptedOpType = Types.substituteTypeVariables(adaptorChain.info().output().getType(), typeVarAssigns);
50+
OpInfo adaptedInfo = new OpAdaptationInfo(originalInfo, adaptedOpType, adaptorChain);
51+
return new InfoChain(adaptedInfo, originalChain.dependencies());
52+
53+
}
54+
55+
/**
56+
* To prevent intensive signature validation, we do two passes:
57+
* <ol>
58+
* <li>If there is no adaptation delimiter, return false fast
59+
* <li>If there is an adaptation delimiter, return true iff it is the
60+
* outermost piece
61+
* </ol>
62+
*/
63+
@Override
64+
public boolean canGenerate(String signature) {
65+
// Pass 1 - fail fast iff no adapted Ops
66+
if (!signature.contains(OpAdaptationInfo.IMPL_DELIMITER)) return false;
67+
68+
// Pass 2 - return true iff adaptor delimiter directly follows adaptor
69+
// signature.
70+
int adaptorDepsStart = signature.indexOf('{');
71+
int adaptorDepsEnd = matchingCurlyBrace(signature, adaptorDepsStart);
72+
return signature.indexOf(
73+
OpAdaptationInfo.IMPL_DELIMITER) == adaptorDepsEnd + 1;
74+
}
75+
76+
private int matchingCurlyBrace(String s, int startBraceIndex) {
77+
int braceCount = 0;
78+
for (int i = startBraceIndex; i < s.length(); i++) {
79+
if (s.charAt(i) == DEP_START_DELIM) braceCount++;
80+
else if (s.charAt(i) == DEP_END_DELIM) {
81+
braceCount--;
82+
if (braceCount == 0) return i;
83+
}
84+
}
85+
throw new IllegalArgumentException("Signature" + s +
86+
" does not have a curly brace matching the one at index " +
87+
startBraceIndex);
88+
}
89+
90+
@Override
91+
public double priority() {
92+
return 0;
93+
}
94+
95+
}

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpAdaptationInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*/
3434
public class OpAdaptationInfo implements OpInfo {
3535

36-
private static final String IMPL_DELIMITER = "|Adaptation|";
36+
protected static final String IMPL_DELIMITER = "|Adaptation|";
3737

3838
private final OpInfo srcInfo;
3939
private final InfoChain adaptorChain;

scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/ProvenanceTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.junit.Assert;
1111
import org.junit.Test;
1212
import org.scijava.Priority;
13+
import org.scijava.function.Computers;
1314
import org.scijava.function.Producer;
1415
import org.scijava.ops.api.Hints;
1516
import org.scijava.ops.api.InfoChain;
@@ -190,6 +191,33 @@ public void testOpWithDependencyRecoveryFromString() {
190191
Function<Double[], Thing> actual = ops.env().opFromID(signature, special, new Nil[] {inType}, outType);
191192
}
192193

194+
@Test
195+
public void testAdaptationRecoveryFromString() {
196+
Function<Double[], Thing[]> f = ops.op("test.provenanceMapped").inType(Double[].class).outType(Thing[].class).function();
197+
Thing[] apply = f.apply(new Double[] {1., 2., 3.});
198+
@SuppressWarnings("unchecked")
199+
InfoChain chain = ((RichOp<Function<Double[], Thing[]>>) f).infoChain();
200+
String signature = chain.signature();
201+
Nil<Function<Double[], Thing[]>> special = new Nil<>() {};
202+
Nil<Double[]> inType = Nil.of(Double[].class);
203+
Nil<Thing[]> outType = Nil.of(Thing[].class);
204+
Function<Double[], Thing[]> actual = ops.env().opFromID(signature, special, new Nil[] {inType}, outType);
205+
apply = actual.apply(new Double[] {1., 2., 3.});
206+
}
207+
208+
@Test
209+
public void testAdaptedOpWithDependencies() {
210+
Function<Double[][], Thing[]> f = ops.op("test.provenanceMapper").inType(Double[][].class).outType(Thing[].class).function();
211+
@SuppressWarnings("unchecked")
212+
InfoChain chain = ((RichOp<Function<Double[], Thing[]>>) f).infoChain();
213+
String signature = chain.signature();
214+
Nil<Function<Double[][], Thing[]>> special = new Nil<>() {};
215+
Nil<Double[][]> inType = Nil.of(Double[][].class);
216+
Nil<Thing[]> outType = Nil.of(Thing[].class);
217+
Function<Double[][], Thing[]> actual = ops.env().opFromID(signature, special, new Nil[] {inType}, outType);
218+
Thing[] apply = actual.apply(new Double[][] {new Double[] {1., 2., 3.}});
219+
}
220+
193221
private OpInfo singularInfoOfName(String name) {
194222
Iterator<OpInfo> infos = ops.env().infos(name).iterator();
195223
Assert.assertTrue(infos.hasNext());
@@ -198,4 +226,23 @@ private OpInfo singularInfoOfName(String name) {
198226
return info;
199227
}
200228

229+
@OpField(names = "test.provenanceComputer")
230+
public final Computers.Arity1<Double[], Double[]> op = (in, out) -> {
231+
for (int i = 0; i < in.length && i < out.length; i++)
232+
out[i] = in[i];
233+
};
234+
235+
@Test
236+
public void testAdaptatorWithDependencies() {
237+
Function<Double[], Double[]> f = ops.op("test.provenanceComputer").inType(Double[].class).outType(Double[].class).function();
238+
@SuppressWarnings("unchecked")
239+
InfoChain chain = ((RichOp<Function<Double[], Double[]>>) f).infoChain();
240+
String signature = chain.signature();
241+
Nil<Function<Double[], Double[]>> special = new Nil<>() {};
242+
Nil<Double[]> inType = Nil.of(Double[].class);
243+
Nil<Double[]> outType = Nil.of(Double[].class);
244+
Function<Double[], Double[]> actual = ops.env().opFromID(signature, special, new Nil[] {inType}, outType);
245+
Double[] apply = actual.apply(new Double[] {1., 2., 3.});
246+
}
247+
201248
}

scijava/scijava-types/src/main/java/org/scijava/types/Types.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,6 +2596,12 @@ private static Type substituteTypeVariables(final Type type,
25962596
}
25972597
return replacementType;
25982598
}
2599+
if (type instanceof GenericArrayType && typeVarAssigns != null) {
2600+
final GenericArrayType genArrType = (GenericArrayType) type;
2601+
final Type replacementType = Types.substituteTypeVariables(genArrType
2602+
.getGenericComponentType(), typeVarAssigns);
2603+
return new GenericArrayTypeImpl(replacementType);
2604+
}
25992605
return type;
26002606
}
26012607

0 commit comments

Comments
 (0)