Skip to content

Add Op factory methods #23

@wiedenm

Description

@wiedenm

Op methods are intended as a third way between Op fields and regular Op classes. They try to provide the conciseness of @OpFields while still enabling the use of other Ops via OpDependencys.
A typical implementation of an Op method may look like this:

@Plugin(type = OpCollection.class)
public class MyOpCollection {

	@OpMethod(names = "test.multiplyNumericStrings")
	@Parameter(key = "numericString1")
	@Parameter(key = "numericString2")
	@Parameter(key = "multipliedNumericStrings", type = ItemIO.OUTPUT)
	@OpDependency(name = "test.parseInteger")
	public BiFunction<String, String, Integer> createMultiplyNumericStringsOp(
		final Function<String, Integer> parseIntegerOp)
	{
		return (i1, i2) -> parseIntegerOp.apply(i1) * parseIntegerOp.apply(i2);
	}
}

Here createMultiplyNumericStringsOp is a factory method ("Op method") that creates and returns the actual "test.multiplyNumericStrings" Op, whenever a new instance of this Op is requested by the user. The @Parameter annotations above the factory method describe the parameters of the returned Op itself, not the factory method. The @OpDependency annotation corresponds to the parameter of the factory method. The Op matching system takes care of resolving this dependency and passes it as an argument which can then be captured by the Op lambda.

The current progress on this issue is captured on branch op-methods, the relevant commits being b4b0d3b through a9e860e. The last of these commits is a (passing) test that further demonstrates the intended behavior.

In the long run, this way of injecting Op dependencies could make "ordinary" Op classes redundant. E.g., the above Op method could be a replacement for the following Op class:

@Plugin(type = Op.class, name = "test.multiplyNumericStrings")
@Parameter(key = "numericString1")
@Parameter(key = "numericString2")
@Parameter(key = "multipliedNumericStrings", type = ItemIO.OUTPUT)
public class MultiplyNumericStrings implements BiFunction<String, String, Integer> {

	@OpDependency(name = "test.parseInteger")
	private Function<String, Integer> parseIntegerOp;

	@Override
	public Integer apply(String i1, String i2) {
		return parseIntegerOp.apply(i1) * parseIntegerOp.apply(i2);
	}
}

The benefit of that is that the factory method could also be used by ciients that don't want to use scijava-op's matching mechanism (given the method is public), simply by calling the method and manually providing a suitable Op as argument. In contrast, using the Op class at the bottom would require the use of reflection as there's no conventional access to the Op dependency field.
Also, Op methods could be thought of as an additional layer on top of POJOs to make them searchable and matchable, while keeping the business logic of the Op unaware of scijava-op entirely:

// POJO, no dependencies on scijava-op. Required Op is injected via constructor.
public class MultiplyNumericStrings implements BiFunction<String, String, Integer> {

	private final Function<String, Integer> parseIntegerOp;

	public MultiplyNumericStrings(Function<String, Integer> parseIntegerOp) {
		this.parseIntegerOp = parseIntegerOp;
	}

	@Override
	public Integer apply(String i1, String i2) {
		return parseIntegerOp.apply(i1) * parseIntegerOp.apply(i2);
	}
}
// scijava-ops-specific layer on top
@Plugin(type = OpCollection.class)
public class MyOpCollection {

	@OpMethod(names = "test.multiplyNumericStrings")
	@Parameter(key = "numericString1")
	@Parameter(key = "numericString2")
	@Parameter(key = "multipliedNumericStrings", type = ItemIO.OUTPUT)
	@OpDependency(name = "test.parseInteger")
	public BiFunction<String, String, Integer> createMultiplyNumericStringsOp(
		final Function<String, Integer> parseIntegerOp)
	{
		return new MultiplyNumericStrings(parseIntegerOp);
	}
}

Splitting the declaration and implementation of an Op like this would even allow us to write the declaration in some other language/file format than Java, if this proves beneficial.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions