Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b952927
Create Optional annotation
gselzer May 24, 2021
bba2d79
OpInfo: add optional parameter methods
gselzer May 25, 2021
4ae1c7a
Add ReducedOpInfo: first cut
gselzer May 26, 2021
4f39967
Reduction: first cut
gselzer Jun 1, 2021
c7fd790
Stream methods resembling the functional method
gselzer Jun 1, 2021
6af7c36
Test Reductions for Op written as Field
gselzer Jun 1, 2021
4cc519e
Clean DefaultOpEnvironment streams
gselzer Jun 2, 2021
458d9a2
Test OpMethods
gselzer Jun 2, 2021
6c1352c
Move reduction tests to reduction package
gselzer Jun 2, 2021
1249642
Test reduction extensibility
gselzer Jun 2, 2021
83e6009
First cut: prerecord parameter optionality
gselzer Jun 2, 2021
a570f16
Explain reasoning for one method annotated
gselzer Jun 3, 2021
8a4bdb2
Clean OpMethodInfo's Optionality decision making
gselzer Jun 3, 2021
f2316de
Test Class inheriting optional arguments
gselzer Jun 3, 2021
7ad47c2
Test OpField with functional iface optional args
gselzer Jun 3, 2021
a38cab4
Extract common logic to ReductionUtils
gselzer Jun 3, 2021
89634d2
Add names field to OpInfo
gselzer Jun 3, 2021
602bf2f
Move fail-fast reduction logic to reduceInfo
gselzer Jun 3, 2021
8c42102
Separate Op parsing into Consumers
gselzer Jun 3, 2021
809d0e7
Denote Producer as a FunctionalInterface
gselzer Jun 4, 2021
9d81f05
Improve functional method determination
gselzer Jun 4, 2021
97f8482
Prevent multiple class declarations
gselzer Jun 4, 2021
8c3801d
test reduction in methods with dependencies
gselzer Jun 3, 2021
7d1d7d4
Remove comment about non-public reduction
gselzer Jun 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions imagej/imagej-ops2/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module net.imagej.ops2 {

// -- Open plugins to scijava-common
opens net.imagej.ops2;
opens net.imagej.ops2.coloc to org.scijava, org.scijava.ops;
opens net.imagej.ops2.coloc.icq to org.scijava, org.scijava.ops;
opens net.imagej.ops2.coloc.kendallTau to org.scijava, org.scijava.ops;
Expand Down
1 change: 1 addition & 0 deletions scijava/scijava-ops/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// -- Open plugins to scijava-common
opens org.scijava.ops to org.scijava;
opens org.scijava.ops.impl to org.scijava;
opens org.scijava.ops.reduce to org.scijava;

// FIXME: This is a file name and is thus unstable
requires geantyref;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,24 @@ default OpBuilder op(final String opName) {
* Creates an {@link OpInfo} from an {@link Class}.
*
* @param opClass
* @param names the comma-delimited set of names under which this Op is known
* @return an {@link OpInfo} which can make instances of {@code opClass}
*/
OpInfo opify(Class<?> opClass);
OpInfo opify(Class<?> opClass, String names);

/**
* Creates an {@link OpInfo} from an {@link Class} with the given priority.
*
* @param opClass
* @param names the comma-delimited set of names under which this Op is known
* @param priority - the assigned priority of the Op.
* @return an {@link OpInfo} which can make instances of {@code opClass}
*/
OpInfo opify(Class<?> opClass, double priority);
OpInfo opify(Class<?> opClass, String names, double priority);

/**
* Makes the {@link OpInfo} {@code info} known to this {@link OpEnvironment} under the name {@code name}
* Makes the {@link OpInfo} {@code info} known to this {@link OpEnvironment}
* @param info
* @param name
*/
void register(OpInfo info, String name);
void register(OpInfo info);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package org.scijava.ops;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
Expand Down Expand Up @@ -29,7 +30,10 @@ public interface OpInfo extends Comparable<OpInfo> {

/** Gets the associated {@link Struct} metadata. */
Struct struct();


/** Gets the comma-delimited set of names used to identify the Op */
String names();

/** Describes whether this Op can be simplified. */
boolean isSimplifiable();

Expand Down Expand Up @@ -67,6 +71,8 @@ default OpCandidate createCandidate(OpEnvironment env, Logger log, OpRef ref, Ma

AnnotatedElement getAnnotationBearer();

boolean isOptional(Member<?> m);

@Override
default int compareTo(final OpInfo that) {
if (this.priority() < that.priority()) return 1;
Expand Down
31 changes: 31 additions & 0 deletions scijava/scijava-ops/src/main/java/org/scijava/ops/OpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.scijava.ops.matcher.MatchingResult;
Expand Down Expand Up @@ -136,6 +137,15 @@ public static Type[] inputTypes(Struct struct) {
return getTypes(inputs(struct));
}

public static Class<?>[] inputRawTypes(Struct struct) {
Type[] inputTypes = getTypes(inputs(struct));
Class<?>[] inputRawTypes = new Class<?>[inputTypes.length];
for (int i = 0; i < inputTypes.length; i++) {
inputRawTypes[i] = Types.raw(inputTypes[i]);
}
return inputRawTypes;
}

public static Member<?> output(OpCandidate candidate) {
return candidate.opInfo().output();
}
Expand All @@ -150,6 +160,10 @@ public static List<Member<?>> outputs(final Struct struct) {
.collect(Collectors.toList());
}

public static Type outputType(final Struct candidate) {
return outputs(candidate).get(0).getType();
}

public static List<MemberInstance<?>> outputs(StructInstance<?> op) {
return op.members().stream() //
.filter(memberInstance -> memberInstance.member().isOutput()) //
Expand Down Expand Up @@ -435,4 +449,21 @@ public static Class<?> findFirstImplementedFunctionalInterface(final OpRef opRef
}
return null;
}

/**
* Returns the index of the argument that is both the input and the output. <b>If there is no such argument (i.e. the Op produces a pure output), -1 is returned</b>
*
* @return the index of the mutable argument.
*/
public static int ioArgIndex(final OpInfo info) {
List<Member<?>> inputs = OpUtils.inputs(info.struct());
Optional<Member<?>> ioArg = inputs.stream().filter(m -> m.isInput() && m.isOutput()).findFirst();
if(ioArg.isEmpty()) return -1;
Member<?> ioMember = ioArg.get();
return inputs.indexOf(ioMember);
}

public static boolean hasPureOutput(final OpInfo info) {
return ioArgIndex(info) == -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private Functions() {
* @throws NullPointerException If {@code type} is {@code null}.
*/
public static boolean isFunction(Type type) {
return ALL_FUNCTIONS.containsKey(Types.raw(type));
return ALL_FUNCTIONS.containsValue(Types.raw(type));
}

@SuppressWarnings({ "unchecked" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @param <O>
* The type of objects produced.
*/
@FunctionalInterface
public interface Producer<O> extends Supplier<O> {
O create();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -78,6 +80,7 @@
import org.scijava.ops.matcher.OpMatchingException;
import org.scijava.ops.matcher.OpMethodInfo;
import org.scijava.ops.matcher.OpRef;
import org.scijava.ops.reduce.InfoReducer;
import org.scijava.ops.simplify.InfoSimplificationGenerator;
import org.scijava.ops.simplify.SimplificationUtils;
import org.scijava.ops.simplify.SimplifiedOpInfo;
Expand Down Expand Up @@ -136,6 +139,11 @@ public class DefaultOpEnvironment extends AbstractContextual implements OpEnviro
*/
private Map<Class<?>, OpWrapper<?>> wrappers;

/**
* Data structure storing all discoverable {@link InfoReducer}s.
*/
private List<? extends InfoReducer> infoReducers;

public DefaultOpEnvironment(final Context context) {
context.inject(this);
matcher = new DefaultOpMatcher(log);
Expand Down Expand Up @@ -189,19 +197,19 @@ public <T> T bakeLambdaType(final T op, Type reifiedType) {
}

@Override
public OpInfo opify(final Class<?> opClass) {
return opify(opClass, Priority.NORMAL);
public OpInfo opify(final Class<?> opClass, String names) {
return opify(opClass, names, Priority.NORMAL);
}

@Override
public OpInfo opify(final Class<?> opClass, final double priority) {
return new OpClassInfo(opClass, priority, opClass.getAnnotation(Unsimplifiable.class) == null);
public OpInfo opify(final Class<?> opClass, final String names, final double priority) {
return new OpClassInfo(opClass, names, priority, opClass.getAnnotation(Unsimplifiable.class) == null);
}

@Override
public void register(final OpInfo info, final String name) {
public void register(final OpInfo info) {
if (opDirectory == null) initOpDirectory();
addToOpIndex(info, name);
addToOpIndex(info);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -475,6 +483,10 @@ private void initWrappers() {
}
}

private void initInfoReducers() {
infoReducers = pluginService.createInstancesOfType(InfoReducer.class);
}

/**
* Attempts to inject {@link OpDependency} annotated fields of the specified
* object by looking for Ops matching the field type and the name specified in
Expand Down Expand Up @@ -662,46 +674,92 @@ private OpRef inferOpRef(Type type, String name, Map<TypeVariable<?>, Type> type
return new OpRef(name, type, mappedOutputs[0], mappedInputs);
}

/**
* Initializes the Op Directory. There are two phases:
* <ol>
* <li> Ops written as Classes are discovered
* <li> Ops written as Fields and Methods are discovered via {@link OpCollection} annotations
* </ol>
*
* TODO: can this be done in parallel?
*/
private void initOpDirectory() {
opDirectory = new HashMap<>();
initInfoReducers();

// Add regular Ops
for (final PluginInfo<Op> pluginInfo : pluginService.getPluginsOfType(Op.class)) {
try {
final Class<?> opClass = pluginInfo.loadClass();
OpInfo opInfo = new OpClassInfo(opClass);
addToOpIndex(opInfo, pluginInfo.getName());
} catch (InstantiableException exc) {
log.error("Can't load class from plugin info: " + pluginInfo.toString(), exc);
}
}
pluginService.getPluginsOfType(Op.class) //
.stream().forEach(parseOpClass);
// Add Ops contained in an OpCollection
for (final PluginInfo<OpCollection> pluginInfo : pluginService.getPluginsOfType(OpCollection.class)) {
try {
final Class<? extends OpCollection> c = pluginInfo.loadClass();
final List<Field> fields = ClassUtils.getAnnotatedFields(c, OpField.class);
Object instance = null;
for (Field field : fields) {
final boolean isStatic = Modifier.isStatic(field.getModifiers());
if (!isStatic && instance == null) {
instance = field.getDeclaringClass().newInstance();
}
OpInfo opInfo = new OpFieldInfo(isStatic ? null : instance, field);
addToOpIndex(opInfo, field.getAnnotation(OpField.class).names());
}
final List<Method> methods = ClassUtils.getAnnotatedMethods(c, OpMethod.class);
for (final Method method: methods) {
OpInfo opInfo = new OpMethodInfo(method);
addToOpIndex(opInfo, method.getAnnotation(OpMethod.class).names());
pluginService.getPluginsOfType(OpCollection.class) //
.stream().forEach(parseOpCollection);
}

private final Consumer<OpInfo> reduceInfo = (info) -> {
// if there is nothing to reduce, end quickly
boolean hasOptional = OpUtils.inputs(info.struct()).parallelStream() //
.anyMatch(m -> info.isOptional(m)); //
if (!hasOptional) return;

// find a InfoReducer capable of reducing info
Optional<? extends InfoReducer> suitableReducer = infoReducers
.parallelStream().filter(reducer -> reducer.canReduce(info)).findAny();
if (suitableReducer.isEmpty()) {
log.warn("Cannot reduce " + info + ": No suitable InfoReducer!");
return;
}

InfoReducer reducer = suitableReducer.get();
long numReductions = info.struct().members().parallelStream() //
.filter(m -> info.isOptional(m)) //
.count(); //
// add a ReducedOpInfo for all possible reductions
// TODO: how to find the names?
for (int i = 1; i <= numReductions; i++) {
addToOpIndex(reducer.reduce(info, i));
}
};

private final Consumer<PluginInfo<Op>> parseOpClass = (pluginInfo) -> {
try {
final Class<?> opClass = pluginInfo.loadClass();
OpInfo opInfo = new OpClassInfo(opClass, pluginInfo.getName());
addToOpIndex(opInfo);
reduceInfo.accept(opInfo);
} catch (InstantiableException exc) {
log.error("Can't load class from plugin info: " + pluginInfo.toString(), exc);
}
};

private final Consumer<PluginInfo<OpCollection>> parseOpCollection = pluginInfo -> {
try {
final Class<? extends OpCollection> c = pluginInfo.loadClass();
final List<Field> fields = ClassUtils.getAnnotatedFields(c, OpField.class);
Object instance = null;
for (Field field : fields) {
final boolean isStatic = Modifier.isStatic(field.getModifiers());
if (!isStatic && instance == null) {
instance = field.getDeclaringClass().newInstance();
}
} catch (InstantiableException | InstantiationException | IllegalAccessException exc) {
log.error("Can't load class from plugin info: " + pluginInfo.toString(), exc);
String names = field.getAnnotation(OpField.class).names();
OpInfo opInfo = new OpFieldInfo(isStatic ? null : instance, field, names);
addToOpIndex(opInfo);
reduceInfo.accept(opInfo);
}
final List<Method> methods = ClassUtils.getAnnotatedMethods(c, OpMethod.class);
for (final Method method: methods) {
String names = method.getAnnotation(OpMethod.class).names();
OpInfo opInfo = new OpMethodInfo(method, names);
addToOpIndex(opInfo);
reduceInfo.accept(opInfo);
}
} catch (InstantiableException | InstantiationException | IllegalAccessException exc) {
log.error("Can't load class from plugin info: " + pluginInfo.toString(), exc);
}
}
};

private void addToOpIndex(final OpInfo opInfo, final String opNames) {
String[] parsedOpNames = OpUtils.parseOpNames(opNames);
private void addToOpIndex(final OpInfo opInfo) {
String[] parsedOpNames = OpUtils.parseOpNames(opInfo.names());
if (parsedOpNames == null || parsedOpNames.length == 0) {
log.error("Skipping Op " + opInfo.implementationName() + ":\n" + "Op implementation must provide name.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import org.scijava.ops.OpDependencyMember;
import org.scijava.ops.OpInfo;
import org.scijava.ops.OpUtils;
import org.scijava.param.Optional;
import org.scijava.param.ParameterStructs;
import org.scijava.param.ValidityException;
import org.scijava.struct.Member;
import org.scijava.struct.Struct;
import org.scijava.struct.StructInstance;

Expand Down Expand Up @@ -58,6 +60,11 @@ public Struct struct() {
return struct;
}

@Override
public String names() {
return srcInfo.names();
}

// we want the original op to have priority over this one.
@Override
public double priority() {
Expand Down Expand Up @@ -105,4 +112,16 @@ public boolean isSimplifiable() {
return false;
}

/**
* NB for {@link Optional} annotations to be on the Op, they would have to be
* declared within the adapter. Since this is unlikely (and is probably bad
* practice), we will assume that they do not exist.
*/
@Override
public boolean isOptional(Member<?> m) {
if (!struct.members().contains(m)) throw new IllegalArgumentException(
"Member " + m + " is not a Memeber of OpInfo " + this);
return false;
}

}
Loading