org.scijava.ops.api.OpEnvironment Maven / Gradle / Ivy
Show all versions of scijava-ops-api Show documentation
/*
* #%L
* The public API of SciJava Ops.
* %%
* Copyright (C) 2021 - 2024 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package org.scijava.ops.api;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.SortedSet;
import org.scijava.discovery.Discoverer;
import org.scijava.priority.Prioritized;
import org.scijava.priority.Priority;
import org.scijava.types.Nil;
/**
* An op environment is the top-level entry point into op execution. It provides
* all the built-in functionality of ops in a single place, including:
*
* - The pool of available ops, from which candidates are chosen.
* - Type-safe, built-in method signatures for all op implementations.
* - Selection (a.k.a. "matching") of op implementations from
* {@link OpRequest} descriptors.
*
*
* Customizing the {@link OpEnvironment} allows customization of any or all of
* the above. Potential use cases include:
*
* - Limiting or extending the pool of available op implementations.
* - Caching op outputs to improve subsequent time performance.
* - Configuration of environment "hints" to improve performance in time or
* space.
*
*
* @author Curtis Rueden
* @author Gabriel Selzer
*/
public interface OpEnvironment extends Prioritized {
/**
* Generates an empty {@link OpEnvironment}, which can be populated
* with the Ops of the caller's choice.
*
* @return an empty {@link OpEnvironment}
* @see #build() for an {@link OpEnvironment} that is fully populated
*/
static OpEnvironment buildEmpty() {
Optional opsOptional = Discoverer //
.using(ServiceLoader::load) //
.discoverMax(OpEnvironment.class);
return opsOptional.orElseThrow( //
() -> new RuntimeException("No OpEnvironment Provided!") //
);
}
/**
* Generates an {@link OpEnvironment} with all available Ops.
*
* @return an {@link OpEnvironment} with all available Ops.
* @see #buildEmpty() for an {@link OpEnvironment} that is empty
*/
static OpEnvironment build() {
OpEnvironment ops = buildEmpty();
ops.discoverEverything();
return ops;
}
/**
* Obtains all Ops in the {@link OpEnvironment}, sorted by priority.
*
* @return a {@link SortedSet} containing all Ops contained in the
* {@link OpEnvironment}.
*/
default SortedSet infos() {
return infos(null, getDefaultHints());
}
/**
* Obtains all Ops in the {@link OpEnvironment} that are named {@code name},
* sorted by priority.
*
* @param name the {@link String} of all Ops to be returned.
* @return a {@link SortedSet} containing all Ops in the {@link OpEnvironment}
* named {@code name}
*/
SortedSet infos(String name);
/**
* Obtains all Ops in the {@link OpEnvironment} that match {@code hints},
* sorted by priority
*
* @param hints the {@link Hints} used to filter available Ops.
* @return a {@link SortedSet} containing all Ops in the {@link OpEnvironment}
* matching {@code hints}
*/
SortedSet infos(Hints hints);
/**
* Obtains all Ops in the {@link OpEnvironment} that are named {@code name}
* and match {@code hints}, sorted by priority
*
* @param name the {@link String} of all Ops to be returned.
* @param hints the {@link Hints} used to filter available Ops.
* @return a {@link SortedSet} containing all Ops in the {@link OpEnvironment}
* named {@code name} and matching {@code hints}
*/
SortedSet infos(String name, Hints hints);
void discoverUsing(Discoverer... d);
void discoverEverything();
OpHistory history();
// TODO: Add interface method: OpInfo info(final String opName, final Nil
// specialType, final Nil>[] inTypes, final Nil> outType);
/**
* Returns an Op fitting the provided arguments. NB implementations of this
* method likely depend on the {@link Hints} set by
* {@link OpEnvironment#setDefaultHints(Hints)}, which provides no guarantee
* of thread-safety. Users interested in parallel Op matching should consider
* using {@link OpEnvironment#op(String, Nil, Nil[], Nil, Hints)} instead.
*
* @param the {@link Type} of the Op
* @param opName the name of the Op
* @param specialType the generic {@link Type} of the Op
* @param inTypes the arguments (inputs) to the Op
* @param outType the return of the Op (note that it may also be an argument)
* @return an instance of an Op aligning with the search parameters
*/
default T op( //
final String opName, //
final Nil specialType, //
final Nil>[] inTypes, //
final Nil> outType //
) {
return op(opName, specialType, inTypes, outType, getDefaultHints());
}
/**
* Returns an Op fitting the provided arguments.
*
* @param the {@link Type} of the Op
* @param opName the name of the Op
* @param specialType the generic {@link Type} of the Op
* @param inTypes the arguments (inputs) to the Op
* @param outType the return of the Op (note that it may also be an argument)
* @param hints the {@link Hints} that should guide this matching call
* @return an instance of an Op aligning with the search parameters
*/
T op(final String opName, final Nil specialType,
final Nil>[] inTypes, final Nil> outType, Hints hints);
default T opFromInfoTree(InfoTree tree, Nil specialType) {
return opFromInfoTree(tree, specialType, getDefaultHints());
}
T opFromInfoTree(InfoTree tree, Nil specialType, Hints hints);
/**
* Returns an Op fitting the provided arguments.
*
* @param the {@link Type} of the Op
* @param signature the signature of the Op
* @param specialType the generic {@link Type} of the Op
* @return an instance of an Op aligning with the search parameters
*/
default T opFromSignature(final String signature,
final Nil specialType)
{
InfoTree info = treeFromSignature(signature);
return opFromInfoTree(info, specialType);
}
InfoTree treeFromSignature(final String signature);
/**
*
* Entry point for convenient Op calls, providing a builder-style interface to
* walk through the process step-by-step.
*
*
* The general order of specification:
*
*
* - The op name (and {@link Hints}, if desired)
* - The number of input(s)
* - The type or value(s) of input(s)
* - One of:
*
* - The type or value of the output
* - Which input should be modified in-place
*
*
*
* The first two steps are required, at a minimum. The choices you make will
* determine the type of Op that is matched:
*
* - No inputs →
Producer
or Computer
* - Inputs with an output value →
Computer
* - Inputs with an output type →
Computer
or
* Function
* - Inputs with no output →
Inplace
or
* Function
with unknown (Object
) return
*
*
* Examples: {@code OpEnvironment env = new DefaultOpEnvironment();}
*
*
* - {@code env.op("create").outType(DoubleType.class).create();} —
* run an Op creating an instance of the ImgLib2 {@code DoubleType}
* - {@code env.op("create").outType(DoubleType.class).create();} —
* same as above.
* - {@code env.op("create", hints).outType(DoubleType.class).create();}
* — same as above but matching with the provided {@link Hints}.
* - {@code env.op("create").outType(Img.class).create();} — run an Op
* creating a raw instance of an ImgLib2 {@code Img}.
* - {@code env.op("create").outType(new
* Nil>(){}).create();} — run an Op creating an instance
* of an ImgLib2 {@code Img
}.
* - {@code env.op("math.add").inType(Integer.class, Integer.class).function();}
* — get an instance of an Op to add two integers together. Return type
* will be {@code Object}.
* - {@code env.op("math.add").inType(Integer.class, Integer.class).function();}
* — same as above.
* - {@code env.op("math.add").input(1, 1).outType(Double.class).apply();}
* — run an Op combining two integers. Return type will be
* {@code Double}.
* - {@code env.op("math.add").input(img1, img2).output(result).compute();}
* — run an Op combining two images and storing the result in a
* pre-allocated image.
* - {@code env.op("filter.addPoissonNoise").input(img1).mutate();} —
* run an Op adding poisson noise to an input image.
*
*
* @param opName The name of the Op to run
* @return The {@link OpBuilder} instance for builder chaining.
* @throws OpMatchingException if the Op request cannot be satisfied
* @see #op(String, Hints) To specify a Hints instance to use
* @see OpBuilder
*/
default OpBuilder op(final String opName) {
return new OpBuilder(this, opName);
}
/**
* As {@link #op(String)} but using a provided {@link Hints}.
*
* @param opName The name of the Op to run
* @param hints The {@code Hints} instance to use for Op matching
* @return The {@link OpBuilder} instance for builder chaining.
* @throws OpMatchingException if the Op request cannot be satisfied
* @see #op(String) To use the default Hints for this OpEnvironment
*/
default OpBuilder op(final String opName, final Hints hints) {
return new OpBuilder(this, opName, hints);
}
/** Discerns the generic type of an object instance. */
Type genericType(Object obj);
/**
* Enriches a lambda expression with its generic type. Its usage is necessary
* in order to find Ops that could take this lamdba expression as an argument.
*
* Suppose, for example, that a user has a lambda expression of type
* {@code Function}, and they wish to use the
* {@code engine.adapt} Op to transform it into a
* {@code Function, Iterable>}.
* Naively, they write:
* {@code
* Function doubler = x -> 2*x;
* Function, Iterable> iterableDoubler =
* ops.op("engine.adapt").input(doubler).outType(new Nil<>() {}).apply();
* }
* but it does not work, because the Ops engine cannot know the generic type
* of the {@code doubler} object. One workaround is to specify the input
* generic type explicitly:
* {@code
* Function doubler = x -> 2*x;
* Function, Iterable> iterableDoubler =
* ops.op("engine.adapt")
* .inType(new Nil() {})
* .outType(new Nil<>() {}).function().apply(doubler);
* }
* Another approach is to use this {@code typeLambda} method as follows:
* {@code
* Function doubler = ops.typeLambda(new Nil<>() {}, x -> 2*x);
* Function, Iterable> iterableDoubler =
* ops.op("engine.adapt").input(doubler).outType(new Nil<>() {}).apply();
* }
* In the above example, the {@code doubler} object returned by
* {@code typeLambda} has its generic type information embedded, rather than
* being a raw lambda where the generic type information is erased at runtime.
*
* Note: {@code typeLambda} does not need to be used with anonymous
* subclasses; these already retain their type parameters at runtime. It is
* only lambda expressions that need to be passed to this method.
*
* @param The lambda's functional type
* (e.g. {@link java.util.function.Function}{@code })
* @param opType The generic type of the lambda expression to be embedded.
* (e.g. {@code new Nil>() {}})
* @param lambda A lambda expression fulfilling the functional interface
* contract of the stated {@code opType}.
* @return An enriched version of the lambda object that embeds knowledge of
* its generic type by implementing the
* {@link org.scijava.common3.GenericTyped} interface.
*/
T typeLambda(Nil opType, T lambda);
/**
* Creates an {@link OpInfo} from a {@link Class}.
*
* @param opClass the {@link Class} from which to derive the Op
* @param names the name(s) of the Op
* @return an {@link OpInfo} which can make instances of {@code opClass}
*/
default OpInfo opify(Class> opClass, String... names) {
return opify(opClass, Priority.NORMAL, names);
}
/**
* Creates an {@link OpInfo} from a {@link Class} with the given priority.
*
* @param opClass the {@link Class} from which to derive the Op
* @param priority the assigned priority of the Op.
* @param names the name(s) of the Op
* @return an {@link OpInfo} which can make instances of {@code opClass}
*/
OpInfo opify(Class> opClass, double priority, String... names);
/**
* Registers all {@link OpInfo} derived from {@link Object}s within the passed
* array. Elements within this {@link Object[]} might be:
*
* - {@link OpInfo}s
* - {@link java.util.Collection}s or arrays of the above
* - {@link Object}s (such as, but not limited to, {@link Class}es
* or {@link Method}s) from which {@link OpInfo}s could be derived
*
*
* @param objects the {@link Object}s that should be made discoverable to this
* {@link OpEnvironment}
*/
void register(Object... objects);
/**
* Sets the {@link Hints} for the {@link OpEnvironment}. Every Call to
* {@link #op} that does not pass a {@link Hints} will copy the
* Hints passed here (to prevent multiple Op calls from accessing/changing the
* same {@link Hints}). In the case that no {@link Hints} have been
* given to the Environment before {@code op} is called, the
* implementation will create a suitable stand-in.
*
* Note that this method does not inherently provide any guarantees
* pertaining to thread-safety; this API is provided purely for convenience.
* Any calls to {@link OpEnvironment#op(String, Nil, Nil[], Nil)} that require
* a specific {@link Hints} should not use this method, instead opting
* for {@link OpEnvironment#op(String, Nil, Nil[], Nil, Hints)}.
*
* @param hints the {@link Hints} to be set as default
*/
void setDefaultHints(Hints hints);
/**
* Returns a copy of the default {@link Hints} object.
*
* @return the default {@link Hints}
*/
Hints getDefaultHints();
/**
* Returns the descriptions for all Ops contained within this
* {@link OpEnvironment}
*
* @return a String describing all Ops in the environment matching
* {@code name}
*/
default String help() {
return help(new PartialOpRequest());
}
/**
* Returns the descriptions for all Ops contained within this
* {@link OpEnvironment} matching {@code name}
*
* @param name the {@link String} name to filter on
* @return a {@link String} describing all Ops in the environment matching
* {@code name}
*/
default String help(final String name) {
return help(new PartialOpRequest(name));
}
/**
* Returns simple descriptions for all Ops identifiable by a given name within
* this {@link OpEnvironment}
*
* @param request the {@link OpRequest} to filter on
* @return a {@link String} containing a (set of) lines for each Op matching
* {@code request}
*/
String help(final OpRequest request);
/**
* Returns verbose descriptions for all Ops contained within this
* {@link OpEnvironment}
*
* @return a {@link String} containing a (set of) lines for each Op
*/
default String helpVerbose() {
return helpVerbose(new PartialOpRequest());
}
/**
* Returns verbose descriptions for all Ops contained within this
* {@link OpEnvironment}
*
* @param name the {@link String} name to filter on
* @return a {@link String} describing all Ops in the environment matching
* {@code name}
*/
default String helpVerbose(final String name) {
return helpVerbose(new PartialOpRequest(name));
}
/**
* Returns verbose descriptions for all Ops contained within this
* {@link OpEnvironment}
*
* @param request the {@link OpRequest} to filter on
* @return a {@link String} containing a (set of) lines for each Op matching
* {@code request}
*/
String helpVerbose(final OpRequest request);
}