diff --git a/pom.xml b/pom.xml index c8fee2714..1d37d9336 100644 --- a/pom.xml +++ b/pom.xml @@ -47,12 +47,13 @@ imagej/imagej-ops2 imagej/imagej-testutil - scijava/scijava-persist + scijava/scijava-discovery scijava/scijava-function scijava/scijava-ops-api - scijava/scijava-ops-discovery scijava/scijava-ops-engine + scijava/scijava-ops-serviceloader scijava/scijava-ops-spi + scijava/scijava-persist scijava/scijava-struct scijava/scijava-taglets scijava/scijava-testutil diff --git a/scijava/scijava-discovery/.gitignore b/scijava/scijava-discovery/.gitignore new file mode 100644 index 000000000..00d2ab71d --- /dev/null +++ b/scijava/scijava-discovery/.gitignore @@ -0,0 +1,2 @@ +/.apt_generated/ +/.apt_generated_tests/ diff --git a/scijava/scijava-ops-discovery/LICENSE.txt b/scijava/scijava-discovery/LICENSE.txt similarity index 100% rename from scijava/scijava-ops-discovery/LICENSE.txt rename to scijava/scijava-discovery/LICENSE.txt diff --git a/scijava/scijava-ops-discovery/README.md b/scijava/scijava-discovery/README.md similarity index 100% rename from scijava/scijava-ops-discovery/README.md rename to scijava/scijava-discovery/README.md diff --git a/scijava/scijava-ops-discovery/bin/generate.groovy b/scijava/scijava-discovery/bin/generate.groovy similarity index 100% rename from scijava/scijava-ops-discovery/bin/generate.groovy rename to scijava/scijava-discovery/bin/generate.groovy diff --git a/scijava/scijava-ops-discovery/pom.xml b/scijava/scijava-discovery/pom.xml similarity index 73% rename from scijava/scijava-ops-discovery/pom.xml rename to scijava/scijava-discovery/pom.xml index b55aca60c..e79586d2d 100644 --- a/scijava/scijava-ops-discovery/pom.xml +++ b/scijava/scijava-discovery/pom.xml @@ -11,11 +11,11 @@ ../.. - scijava-ops-discovery + scijava-discovery - SciJava Ops Discovery - SciJava Operations Discovery: Discovery mechanisms used by the SciJava Operations framework. - https://github.com/scijava/scijava-ops-discovery + SciJava Discovery + SciJava Discovery: Discovery mechanisms used by the SciJava Framework. + https://github.com/scijava/scijava-discovery 2021 SciJava @@ -78,7 +78,7 @@ Image.sc Forum - https://forum.image.sc/tags/scijava-ops-discovery + https://forum.image.sc/tags/scijava-discovery @@ -90,7 +90,7 @@ GitHub Issues - https://github.com/scijava/scijava-ops-discovery/issues + https://github.com/scijava/scijava-discovery/issues Travis CI @@ -98,14 +98,13 @@ - org.scijava.ops.discovery.Main - org.scijava.ops.discovery + org.scijava.discovery.Main + org.scijava.discovery bsd_2 SciJava developers. - - ${scijava.allowedDuplicateClasses},com.github.therapi.runtimejavadoc.repack.com.eclipsesource.json.* - ${scijava-ops-discovery.allowedDuplicateClasses} + ${scijava.allowedDuplicateClasses},com.github.therapi.runtimejavadoc.repack.com.eclipsesource.json.* + ${scijava-discovery.allowedDuplicateClasses} diff --git a/scijava/scijava-discovery/src/main/java/module-info.java b/scijava/scijava-discovery/src/main/java/module-info.java new file mode 100644 index 000000000..10b0759ef --- /dev/null +++ b/scijava/scijava-discovery/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.scijava.discovery { + + exports org.scijava.discovery; + +} diff --git a/scijava/scijava-discovery/src/main/java/org/scijava/discovery/Discoverer.java b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/Discoverer.java new file mode 100644 index 000000000..e851d17c4 --- /dev/null +++ b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/Discoverer.java @@ -0,0 +1,10 @@ + +package org.scijava.discovery; + +import java.util.List; + +public interface Discoverer { + + List> implementingClasses(Class c); + +} diff --git a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Implementation.java b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/Implementation.java similarity index 89% rename from scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Implementation.java rename to scijava/scijava-discovery/src/main/java/org/scijava/discovery/Implementation.java index ed7b2d434..a42ae5986 100644 --- a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Implementation.java +++ b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/Implementation.java @@ -1,4 +1,4 @@ -package org.scijava.ops.discovery; +package org.scijava.discovery; import java.lang.reflect.Type; diff --git a/scijava/scijava-discovery/src/main/java/org/scijava/discovery/StaticDiscoverer.java b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/StaticDiscoverer.java new file mode 100644 index 000000000..ca931326c --- /dev/null +++ b/scijava/scijava-discovery/src/main/java/org/scijava/discovery/StaticDiscoverer.java @@ -0,0 +1,30 @@ + +package org.scijava.discovery; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class StaticDiscoverer implements Discoverer { + + Map, String> names; + + public StaticDiscoverer() { + names = new HashMap<>(); + } + + public void register(Class c, String name) { + names.put(c, name); + } + + @SuppressWarnings("unchecked") + @Override + public List> implementingClasses(Class c) { + return names.keySet().stream() // + .filter(cls -> cls.isAssignableFrom(c)) // + .map(cls -> (Class) cls) // + .collect(Collectors.toList()); + } + +} diff --git a/scijava/scijava-ops-discovery/.gitignore b/scijava/scijava-ops-discovery/.gitignore index 00d2ab71d..ae3c17260 100644 --- a/scijava/scijava-ops-discovery/.gitignore +++ b/scijava/scijava-ops-discovery/.gitignore @@ -1,2 +1 @@ -/.apt_generated/ -/.apt_generated_tests/ +/bin/ diff --git a/scijava/scijava-ops-discovery/src/main/java/module-info.java b/scijava/scijava-ops-discovery/src/main/java/module-info.java deleted file mode 100644 index e1803e6dd..000000000 --- a/scijava/scijava-ops-discovery/src/main/java/module-info.java +++ /dev/null @@ -1,5 +0,0 @@ -module org.scijava.ops.discovery { - - exports org.scijava.ops.discovery; - -} diff --git a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Discoverer.java b/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Discoverer.java deleted file mode 100644 index 52dcdc50c..000000000 --- a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/Discoverer.java +++ /dev/null @@ -1,14 +0,0 @@ - -package org.scijava.ops.discovery; - -import java.util.List; - -public interface Discoverer { - - List> implementingClasses(Class c); - - List implementingInstances(Class c, Class[] constructorClasses, Object[] constructorArgs); - - List> implementationsOf(Class c); - -} diff --git a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/StaticDiscoverer.java b/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/StaticDiscoverer.java deleted file mode 100644 index d6038f6e9..000000000 --- a/scijava/scijava-ops-discovery/src/main/java/org/scijava/ops/discovery/StaticDiscoverer.java +++ /dev/null @@ -1,58 +0,0 @@ - -package org.scijava.ops.discovery; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class StaticDiscoverer implements Discoverer { - - Map, String> names; - - public StaticDiscoverer() { - names = new HashMap<>(); - } - - public void register(Class c, String name) { - names.put(c, name); - } - - @SuppressWarnings("unchecked") - @Override - public List> implementingClasses(Class c) { - return names.keySet().stream() // - .filter(cls -> cls.isAssignableFrom(c)) // - .map(cls -> (Class) cls) // - .collect(Collectors.toList()); - } - - @Override - public List implementingInstances(Class c, - Class[] constructorClasses, Object[] constructorArgs) - { - return implementingClasses(c).stream() // - .map(cls -> classToObjectOrNull(cls, constructorClasses, constructorArgs)) // - .filter(o -> o != null) // - .collect(Collectors.toList()); - } - - private T classToObjectOrNull(Class c, Class[] constructorClasses, Object[] constructorArgs) { - try { - return c.getDeclaredConstructor(constructorClasses).newInstance( - constructorArgs); - } - catch (Throwable t) - { - return null; - } - } - - @Override - public List> implementationsOf(Class c) { - return implementingClasses(c).stream() // - .map(cls -> new Implementation<>(cls, c, names.get(cls))) // - .collect(Collectors.toList()); - } - -} diff --git a/scijava/scijava-ops-engine/pom.xml b/scijava/scijava-ops-engine/pom.xml index 334c4bcca..6d6a4eda0 100644 --- a/scijava/scijava-ops-engine/pom.xml +++ b/scijava/scijava-ops-engine/pom.xml @@ -133,7 +133,12 @@ org.scijava - scijava-ops-discovery + scijava-discovery + ${project.version} + + + org.scijava + scijava-ops-serviceloader ${project.version} diff --git a/scijava/scijava-ops-engine/src/main/java/module-info.java b/scijava/scijava-ops-engine/src/main/java/module-info.java index 40e4e563e..915e545ff 100644 --- a/scijava/scijava-ops-engine/src/main/java/module-info.java +++ b/scijava/scijava-ops-engine/src/main/java/module-info.java @@ -34,10 +34,11 @@ requires java.desktop; requires org.scijava; + requires org.scijava.discovery; requires org.scijava.function; requires org.scijava.struct; requires org.scijava.ops.api; - requires org.scijava.ops.discovery; + requires org.scijava.ops.serviceloader; requires org.scijava.ops.spi; requires org.scijava.types; requires javassist; @@ -45,4 +46,6 @@ requires therapi.runtime.javadoc; uses javax.annotation.processing.Processor; + provides org.scijava.ops.spi.OpCollection with org.scijava.ops.engine.copy.CopyOpCollection; + provides org.scijava.ops.spi.Op with org.scijava.ops.engine.stats.Mean.MeanFunction; } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/copy/CopyOpCollection.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/copy/CopyOpCollection.java index ed692dd1c..f268b311d 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/copy/CopyOpCollection.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/copy/CopyOpCollection.java @@ -4,10 +4,8 @@ import org.scijava.function.Computers; import org.scijava.ops.spi.OpCollection; import org.scijava.ops.spi.OpField; -import org.scijava.plugin.Plugin; -@Plugin(type = OpCollection.class) -public class CopyOpCollection { +public class CopyOpCollection implements OpCollection{ @OpField(names = "cp, copy", priority = Priority.LOW, params = "array, arrayCopy") public static final Computers.Arity1 copyPrimitiveDoubleArray = (from, to) -> { diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java index c8a153870..3572075f0 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java @@ -47,6 +47,7 @@ import java.util.stream.Collectors; import org.scijava.Priority; +import org.scijava.discovery.Discoverer; import org.scijava.log.LogService; import org.scijava.ops.api.Hints; import org.scijava.ops.api.OpCandidate; @@ -57,7 +58,6 @@ import org.scijava.ops.api.OpInfoGenerator; import org.scijava.ops.api.OpRef; import org.scijava.ops.api.OpWrapper; -import org.scijava.ops.discovery.Discoverer; import org.scijava.ops.engine.BaseOpHints.Adaptation; import org.scijava.ops.engine.BaseOpHints.DependencyMatching; import org.scijava.ops.engine.BaseOpHints.Simplification; @@ -92,7 +92,7 @@ */ public class DefaultOpEnvironment implements OpEnvironment { - private final Discoverer discoverer; + private final List discoverers; private OpMatcher matcher; @@ -137,8 +137,17 @@ public class DefaultOpEnvironment implements OpEnvironment { */ private Hints environmentHints = null; - public DefaultOpEnvironment(final Discoverer d, final TypeService typeService, final LogService log, final OpHistoryService history, final List infoGenerators) { - this.discoverer = d; + public DefaultOpEnvironment(final TypeService typeService, final LogService log, final OpHistoryService history, final List infoGenerators, final List d) { + this.discoverers = d; + this.typeService = typeService; + this.log = log; + this.history = history; + this.infoGenerators = infoGenerators; + matcher = new DefaultOpMatcher(); + } + + public DefaultOpEnvironment(final TypeService typeService, final LogService log, final OpHistoryService history, final List infoGenerators, final Discoverer... d) { + this.discoverers = Arrays.asList(d); this.typeService = typeService; this.log = log; this.history = history; @@ -486,11 +495,19 @@ private List resolveOpDependencies(OpCandidate candidate, Hi private void initWrappers() { wrappers = new HashMap<>(); - Class[] constructorClasses = {}; - Object[] constructorObjects = {}; - for (OpWrapper wrapper : discoverer.implementingInstances(OpWrapper.class, constructorClasses, constructorObjects)) { - wrappers.put(wrapper.type(), wrapper); - } + for (Discoverer d : discoverers) + for (Class cls : d.implementingClasses(OpWrapper.class)) + { + OpWrapper wrapper; + try { + wrapper = cls.getDeclaredConstructor().newInstance(); + wrappers.put(wrapper.type(), wrapper); + } + catch (Throwable t) + { + log.warn("OpWrapper " + cls + " not instantiated. Due to " + t); + } + } } /** diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpService.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpService.java index 3a5cf2dfb..63ef7b2aa 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpService.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpService.java @@ -35,14 +35,14 @@ import org.scijava.Context; import org.scijava.InstantiableException; +import org.scijava.discovery.Discoverer; import org.scijava.log.LogService; import org.scijava.ops.api.OpBuilder; import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpInfoGenerator; -import org.scijava.ops.discovery.Discoverer; -import org.scijava.ops.discovery.Implementation; import org.scijava.ops.engine.OpHistoryService; import org.scijava.ops.engine.OpService; +import org.scijava.ops.serviceloader.ServiceLoaderDiscoverer; import org.scijava.plugin.Plugin; import org.scijava.plugin.PluginInfo; import org.scijava.plugin.PluginService; @@ -88,15 +88,16 @@ public OpEnvironment env() { private synchronized void initEnv() { if (env != null) return; - PluginService plugins = context().getService(PluginService.class); LogService log = context().getService(LogService.class); TypeService types = context().getService(TypeService.class); OpHistoryService history = context().getService(OpHistoryService.class); + Discoverer d1 = new PluginBasedDiscoverer(context()); + Discoverer d2 = new ServiceLoaderDiscoverer(); List infoGenerators = Arrays.asList( - new PluginBasedClassOpInfoGenerator(plugins), - new PluginBasedOpCollectionInfoGenerator(plugins)); - env = new DefaultOpEnvironment(new PluginBasedDiscoverer(context()), types, - log, history, infoGenerators); + new PluginBasedClassOpInfoGenerator(d1, d2), + new OpClassBasedClassOpInfoGenerator(d1, d2), + new OpCollectionInfoGenerator(d1, d2)); + env = new DefaultOpEnvironment(types, log, history, infoGenerators, d1, d2); } } @@ -110,7 +111,7 @@ public PluginBasedDiscoverer(Context ctx) { @Override @SuppressWarnings("unchecked") - public List> implementingClasses(Class c) { + public List> implementingClasses(Class c) { if (!SciJavaPlugin.class.isAssignableFrom(c)) { throw new UnsupportedOperationException( "Current discovery mechanism tied to SciJava Context; only able to search for SciJavaPlugins"); @@ -122,36 +123,6 @@ public List> implementingClasses(Class c) { .filter(cls -> cls != null).collect(Collectors.toList()); } - @Override - @SuppressWarnings("unchecked") - public List implementingInstances(Class c, - Class[] constructorClasses, Object[] constructorArgs) - { - if (!SciJavaPlugin.class.isAssignableFrom(c)) { - throw new UnsupportedOperationException( - "Current discovery mechanism tied to SciJava Context; only able to search for SciJavaPlugins"); - } - List instances = p.createInstancesOfType( - (Class) c); - return instances.stream().map(instance -> (T) instance).collect(Collectors - .toList()); - } - - @Override - @SuppressWarnings("unchecked") - public List> implementationsOf(Class c) { - if (!SciJavaPlugin.class.isAssignableFrom(c)) { - throw new UnsupportedOperationException( - "Current discovery mechanism tied to SciJava Context; only able to search for SciJavaPlugins"); - } - List> instances = p.getPluginsOfType( - (Class) c); - return instances.stream() // - .map(instance -> makeDiscoveryOrNull(c, instance)) // - .filter(d -> d.implementation() != null) // - .collect(Collectors.toList()); - } - @SuppressWarnings("unchecked") private Class makeClassOrNull(@SuppressWarnings("unused") Class type, PluginInfo instance) @@ -164,11 +135,4 @@ private Class makeClassOrNull(@SuppressWarnings("unused") Class type, } } - private Implementation makeDiscoveryOrNull(Class type, - PluginInfo instance) - { - return new Implementation<>(makeClassOrNull(type, instance), type, instance - .getName()); - } - } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassBasedClassOpInfoGenerator.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassBasedClassOpInfoGenerator.java new file mode 100644 index 000000000..14ec2ad33 --- /dev/null +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassBasedClassOpInfoGenerator.java @@ -0,0 +1,38 @@ + +package org.scijava.ops.engine.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.scijava.discovery.Discoverer; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.api.OpInfoGenerator; +import org.scijava.ops.api.OpUtils; +import org.scijava.ops.engine.matcher.impl.OpClassInfo; +import org.scijava.ops.spi.Op; +import org.scijava.ops.spi.OpClass; + +public class OpClassBasedClassOpInfoGenerator implements OpInfoGenerator { + + private final List discoverers; + + public OpClassBasedClassOpInfoGenerator(Discoverer... d) { + this.discoverers = Arrays.asList(d); + } + + @Override + public List generateInfos() { + List infos = discoverers.stream() // + .flatMap(d -> d.implementingClasses(Op.class).stream()) // + .filter(cls -> cls.getAnnotation(OpClass.class) != null) // + .map(cls -> { + OpClass p = cls.getAnnotation(OpClass.class); + String[] parsedOpNames = OpUtils.parseOpNames(p.names()); + return new OpClassInfo(cls, parsedOpNames); + }) // + .collect(Collectors.toList()); + return infos; + } + +} diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedOpCollectionInfoGenerator.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java similarity index 56% rename from scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedOpCollectionInfoGenerator.java rename to scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java index c4334cf05..b780a942c 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedOpCollectionInfoGenerator.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java @@ -4,9 +4,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; -import org.scijava.InstantiableException; +import org.scijava.discovery.Discoverer; import org.scijava.ops.api.OpInfo; import org.scijava.ops.api.OpInfoGenerator; import org.scijava.ops.api.OpUtils; @@ -15,26 +17,25 @@ import org.scijava.ops.spi.OpCollection; import org.scijava.ops.spi.OpField; import org.scijava.ops.spi.OpMethod; -import org.scijava.plugin.PluginInfo; -import org.scijava.plugin.PluginService; import org.scijava.util.ClassUtils; -public class PluginBasedOpCollectionInfoGenerator implements OpInfoGenerator { +public class OpCollectionInfoGenerator implements OpInfoGenerator { - private final PluginService service; + private final List discoverers; - public PluginBasedOpCollectionInfoGenerator(PluginService service) { - this.service = service; + public OpCollectionInfoGenerator(Discoverer... d) { + this.discoverers = Arrays.asList(d); } @Override public List generateInfos() { - List infos = new ArrayList<>(); - for (PluginInfo info : service.getPluginsOfType(OpCollection.class) ) { + List infos = discoverers.stream() // + .flatMap(d -> d.implementingClasses(OpCollection.class).stream()) // + .map(cls -> { try { - Class c = info.loadClass(); - final List fields = ClassUtils.getAnnotatedFields(c, OpField.class); + List collectionInfos = new ArrayList<>(); + final List fields = ClassUtils.getAnnotatedFields(cls, OpField.class); Object instance = null; for (Field field : fields) { final boolean isStatic = Modifier.isStatic(field.getModifiers()); @@ -43,20 +44,25 @@ public List generateInfos() { } String unparsedOpNames = field.getAnnotation(OpField.class).names(); String[] parsedOpNames = OpUtils.parseOpNames(unparsedOpNames); - infos.add(new OpFieldInfo(isStatic ? null : instance, field, + collectionInfos.add(new OpFieldInfo(isStatic ? null : instance, field, parsedOpNames)); } - final List methods = ClassUtils.getAnnotatedMethods(c, OpMethod.class); + final List methods = ClassUtils.getAnnotatedMethods(cls, OpMethod.class); for (final Method method: methods) { String unparsedOpNames = method.getAnnotation(OpMethod.class).names(); String[] parsedOpNames = OpUtils.parseOpNames(unparsedOpNames); - infos.add(new OpMethodInfo(method, parsedOpNames)); + collectionInfos.add(new OpMethodInfo(method, parsedOpNames)); } - } catch (InstantiationException | IllegalAccessException | InstantiableException exc) { + return collectionInfos; + } catch (InstantiationException | IllegalAccessException exc) { // TODO: Consider how best to handle this. - exc.printStackTrace(); + return null; } - } + + }) // + .filter(list -> list!= null) // + .flatMap(list -> list.stream()) // + .collect(Collectors.toList()); return infos; } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedClassOpInfoGenerator.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedClassOpInfoGenerator.java index 8bc55e2fb..7c8124af2 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedClassOpInfoGenerator.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/PluginBasedClassOpInfoGenerator.java @@ -1,41 +1,37 @@ + package org.scijava.ops.engine.impl; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; -import org.scijava.InstantiableException; +import org.scijava.discovery.Discoverer; import org.scijava.ops.api.OpInfo; import org.scijava.ops.api.OpInfoGenerator; import org.scijava.ops.api.OpUtils; import org.scijava.ops.engine.matcher.impl.OpClassInfo; import org.scijava.ops.spi.Op; -import org.scijava.plugin.PluginInfo; -import org.scijava.plugin.PluginService; - +import org.scijava.plugin.Plugin; public class PluginBasedClassOpInfoGenerator implements OpInfoGenerator { - private final PluginService service; + private final List discoverers; - public PluginBasedClassOpInfoGenerator(PluginService service) { - this.service = service; + public PluginBasedClassOpInfoGenerator(Discoverer... d) { + this.discoverers = Arrays.asList(d); } @Override public List generateInfos() { - List infos = new ArrayList<>(); - for (PluginInfo info : service.getPluginsOfType(Op.class) ) { - Class opClass; - try { - opClass = info.loadClass(); - String[] parsedOpNames = OpUtils.parseOpNames(info.getName()); - infos.add(new OpClassInfo(opClass, parsedOpNames)); - } - catch (InstantiableException exc) { - exc.printStackTrace(); - } - - } + List infos = discoverers.stream() // + .flatMap(d -> d.implementingClasses(Op.class).stream()) // + .filter(cls -> cls.getAnnotation(Plugin.class) != null) // + .map(cls -> { + Plugin p = cls.getAnnotation(Plugin.class); + String[] parsedOpNames = OpUtils.parseOpNames(p.name()); + return new OpClassInfo(cls, parsedOpNames); + }) // + .collect(Collectors.toList()); return infos; } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/stats/Mean.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/stats/Mean.java index a6a304e96..212f770df 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/stats/Mean.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/stats/Mean.java @@ -4,13 +4,13 @@ import java.util.function.Function; import org.scijava.ops.spi.Op; +import org.scijava.ops.spi.OpClass; import org.scijava.ops.spi.OpDependency; -import org.scijava.plugin.Plugin; public class Mean { - @Plugin(type = Op.class, name = "stats.mean") - public static class MeanFunction implements Function, O>{ + @OpClass(names = "stats.mean") + public static class MeanFunction implements Function, O>, Op{ @OpDependency(name = "math.add") Function, O> sumFunc; diff --git a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/copy/CopyOpCollectionTest.java b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/copy/CopyOpCollectionTest.java new file mode 100644 index 000000000..7e23215e5 --- /dev/null +++ b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/copy/CopyOpCollectionTest.java @@ -0,0 +1,47 @@ +/* + * #%L + * SciJava Operations: a framework for reusable algorithms. + * %% + * Copyright (C) 2016 - 2019 SciJava Ops 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.engine.copy; + +import org.junit.Assert; +import org.junit.Test; +import org.scijava.ops.engine.AbstractTestEnvironment; + +public class CopyOpCollectionTest extends AbstractTestEnvironment { + + @Test + public void testCopyOp() { + double[] input = {1, 2, 3}; + double[] output = {4, 5, 6}; + ops.op("cp").input(input).output(output).compute(); + + Assert.assertArrayEquals(input, output, 0.); + } + +} diff --git a/scijava/scijava-ops-engine/templates/main/java/module-info.vm b/scijava/scijava-ops-engine/templates/main/java/module-info.vm index 089320bc7..5229b4638 100644 --- a/scijava/scijava-ops-engine/templates/main/java/module-info.vm +++ b/scijava/scijava-ops-engine/templates/main/java/module-info.vm @@ -23,10 +23,11 @@ module org.scijava.ops.engine { requires java.desktop; requires org.scijava; + requires org.scijava.discovery; requires org.scijava.function; requires org.scijava.struct; requires org.scijava.ops.api; - requires org.scijava.ops.discovery; + requires org.scijava.ops.serviceloader; requires org.scijava.ops.spi; requires org.scijava.types; requires javassist; @@ -34,4 +35,6 @@ module org.scijava.ops.engine { requires therapi.runtime.javadoc; uses javax.annotation.processing.Processor; + provides org.scijava.ops.spi.OpCollection with org.scijava.ops.engine.copy.CopyOpCollection; + provides org.scijava.ops.spi.Op with org.scijava.ops.engine.stats.Mean.MeanFunction; } diff --git a/scijava/scijava-ops-serviceloader/.gitignore b/scijava/scijava-ops-serviceloader/.gitignore new file mode 100644 index 000000000..00d2ab71d --- /dev/null +++ b/scijava/scijava-ops-serviceloader/.gitignore @@ -0,0 +1,2 @@ +/.apt_generated/ +/.apt_generated_tests/ diff --git a/scijava/scijava-ops-serviceloader/LICENSE.txt b/scijava/scijava-ops-serviceloader/LICENSE.txt new file mode 100644 index 000000000..ef17d5e7a --- /dev/null +++ b/scijava/scijava-ops-serviceloader/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2016 - 2019, SciJava Ops developers. +All rights reserved. + +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 HOLDER 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. diff --git a/scijava/scijava-ops-serviceloader/README.md b/scijava/scijava-ops-serviceloader/README.md new file mode 100644 index 000000000..0a48dfba3 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/README.md @@ -0,0 +1,26 @@ +# SciJava Ops Service Loader: A [`ServiceLoader`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) extension of SciJava Discovery +This module provides an implementation of `Discoverer` that is powered by the `ServiceLoader` from Java's Module System. + +## What can `ServiceLoaderDiscoverer` discover? + +`ServiceLoaderDiscoverer` can discover implementations of *any* interface declared to be `use`d in its `module-info.java`. Although we limit the `use`d interfaces to those defined in SciJava Ops SPI (this is, after all, an Ops project), the process is the same for any other interface. + +# I wrote an implementation, how can I make it discoverable via `ServiceLoaderDiscoverer`? + +Suppose your module `com.example.foo` contains an implementation `com.example.foo.FooOp` of `org.scijava.ops.spi.Op`, and you wise to make `FooOp` discoverable from `ServiceLoaderDiscoverer`. Make sure to: + +* Declare SciJava Ops Service Loader as a dependency; if using Maven, it looks like this: + +```java + + org.scijava + scijava-ops-serviceLoader + 0-SNAPSHOT + 0) { + builder.append("\n"); + } + builder.append(line); + } + value = builder.toString(); + } + + context.put(key, parseValue(sh, translationsFile, key, value)); + } + + return context; +} + +/* + * Translates a template into many files in the outputDirectory, + * given a translations file in INI style; e.g.: + * + * [filename1] + * variable1 = value1 + * variable2 = value2 + * ... + * [filename2] + * variable1 = value3 + * variable2 = value4 + * ... + */ +def translate(templateSubdirectory, templateFile, translationsFile) { + debug("translate('$templateSubdirectory', '$templateFile', '$translationsFile')") + + // initialize the Velocity engine + engine = new org.apache.velocity.app.VelocityEngine(); + p = new java.util.Properties(); + // fail if template uses an invalid expression; e.g., an undefined variable + p.setProperty("runtime.references.strict", "true"); + // tell Velocity where the templates are located + p.setProperty("file.resource.loader.path", "$templateSubdirectory"); + // tell Velocity to log to stderr rather than to a velocity.log file + p.setProperty(org.apache.velocity.runtime.RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, + "org.apache.velocity.runtime.log.SystemLogChute"); + engine.init(p); + + // read translation lines + outputFilename = null; + context = globalContext = new org.apache.velocity.VelocityContext(); + reader = new java.io.BufferedReader(new java.io.FileReader("$templateSubdirectory/$translationsFile")); + + readTranslation(engine, context, reader, templateSubdirectory, templateFile, translationsFile, false); + + reader.close(); + + // process the template + processTemplate(engine, context, templateFile, outputFilename); +} + +/* Recursively translates all templates in the given directory. */ +def translateDirectory(templateSubdirectory) { + debug("translateDirectory('$templateSubdirectory')") + + for (file in new java.io.File(templateSubdirectory).listFiles()) { + if (file.isDirectory()) { + // process subdirectories recursively + translateDirectory(file.getPath()); + } + else { + // process Velocity template files only + name = file.getName(); + if (!name.endsWith('.vm')) continue; + prefix = name.substring(0, name.lastIndexOf('.')); + translate(templateSubdirectory, name, prefix + '.list'); + } + } +} + +try { + translateDirectory(templateDirectory); +} +catch (Throwable t) { + t.printStackTrace(System.err); + throw t; +} diff --git a/scijava/scijava-ops-serviceloader/pom.xml b/scijava/scijava-ops-serviceloader/pom.xml new file mode 100644 index 000000000..03d3f1cda --- /dev/null +++ b/scijava/scijava-ops-serviceloader/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + + org.scijava + scijava-incubator + 0-SNAPSHOT + ../.. + + + scijava-ops-serviceloader + + SciJava Ops Service Loader + SciJava Operations Service Loader: A ServiceLoader based implementation of SciJava Ops Discovery. + https://github.com/scijava/scijava-ops-serviceloader + 2021 + + SciJava + https://scijava.org/ + + + + Simplified BSD License + repo + + + + + + ctrueden + Curtis Rueden + https://imagej.net/User:Rueden + + founder + lead + reviewer + support + maintainer + + + + gselzer + Gabriel Selzer + + founder + developer + debugger + reviewer + support + + + + + + Christian Dietz + https://imagej.net/User:Dietzc + + founder + + + dietzc + + + + David Kolb + + founder + + + Treiblesschorle + + + + + + + Image.sc Forum + https://forum.image.sc/tags/scijava-ops-serviceloader + + + + + scm:git:git://github.com/scijava/incubator + scm:git:git@github.com:scijava/incubator + HEAD + https://github.com/scijava/incubator + + + GitHub Issues + https://github.com/scijava/scijava-ops-serviceloader/issues + + + Travis CI + https://travis-ci.com/scijava/incubator + + + + org.scijava.ops.serviceloader.Main + org.scijava.ops.serviceloader + + bsd_2 + SciJava developers. + + ${scijava.allowedDuplicateClasses},com.github.therapi.runtimejavadoc.repack.com.eclipsesource.json.* + ${scijava-ops-serviceloader.allowedDuplicateClasses} + + + + org.scijava + scijava-ops-spi + ${project.version} + + + org.scijava + scijava-discovery + ${project.version} + + + + junit + junit + test + + + diff --git a/scijava/scijava-ops-serviceloader/src/main/java/module-info.java b/scijava/scijava-ops-serviceloader/src/main/java/module-info.java new file mode 100644 index 000000000..753123255 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/src/main/java/module-info.java @@ -0,0 +1,18 @@ + +module org.scijava.ops.serviceloader { + + exports org.scijava.ops.serviceloader; + + requires org.scijava.ops.spi; + requires org.scijava.discovery; + + uses org.scijava.ops.spi.Op; + uses org.scijava.ops.spi.OpCollection; + + provides org.scijava.ops.spi.Op with + org.scijava.ops.serviceloader.ServiceBasedAdder; + + provides org.scijava.ops.spi.OpCollection with + org.scijava.ops.serviceloader.ServiceBasedMultipliers; + +} diff --git a/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedAdder.java b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedAdder.java new file mode 100644 index 000000000..2d309f549 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedAdder.java @@ -0,0 +1,19 @@ + +package org.scijava.ops.serviceloader; + +import java.util.function.BiFunction; + +import org.scijava.ops.spi.Op; +import org.scijava.ops.spi.OpClass; + +@OpClass(names = "math.add") +public class ServiceBasedAdder implements BiFunction, + Op +{ + + @Override + public Double apply(Number t, Number u) { + return t.doubleValue() + u.doubleValue(); + } + +} diff --git a/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedMultipliers.java b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedMultipliers.java new file mode 100644 index 000000000..b5c2baa71 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceBasedMultipliers.java @@ -0,0 +1,15 @@ + +package org.scijava.ops.serviceloader; + +import java.util.function.BiFunction; + +import org.scijava.ops.spi.OpCollection; +import org.scijava.ops.spi.OpField; + +public class ServiceBasedMultipliers implements OpCollection { + + @OpField(names = "math.multiply") + public final BiFunction fieldMultiplier = (in1, + in2) -> in1.doubleValue() * in2.doubleValue(); + +} diff --git a/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceLoaderDiscoverer.java b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceLoaderDiscoverer.java new file mode 100644 index 000000000..f0ad681e3 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/src/main/java/org/scijava/ops/serviceloader/ServiceLoaderDiscoverer.java @@ -0,0 +1,26 @@ + +package org.scijava.ops.serviceloader; + +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import org.scijava.discovery.Discoverer; + +public class ServiceLoaderDiscoverer implements Discoverer { + + @SuppressWarnings("unchecked") + @Override + public List> implementingClasses(Class c) { + // If we cannot use c, we cannot find any implementations + Module thisModule = this.getClass().getModule(); + if (!thisModule.canUse(c)) return Collections.emptyList(); + + // If we can use c, look up the implementations + ServiceLoader loader = ServiceLoader.load(c); + return loader.stream().map(p -> (Class) p.get().getClass()) // + .collect(Collectors.toList()); + } + +} diff --git a/scijava/scijava-ops-serviceloader/src/test/java/org/scijava/ops/serviceloader/ServiceLoaderDiscovererTest.java b/scijava/scijava-ops-serviceloader/src/test/java/org/scijava/ops/serviceloader/ServiceLoaderDiscovererTest.java new file mode 100644 index 000000000..e22dce445 --- /dev/null +++ b/scijava/scijava-ops-serviceloader/src/test/java/org/scijava/ops/serviceloader/ServiceLoaderDiscovererTest.java @@ -0,0 +1,29 @@ +package org.scijava.ops.serviceloader; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.scijava.discovery.Discoverer; +import org.scijava.ops.spi.Op; +import org.scijava.ops.spi.OpCollection; + +public class ServiceLoaderDiscovererTest { + + @Test + public void testServiceLoaderWithOps() { + Discoverer d = new ServiceLoaderDiscoverer(); + List> implementingClasses = d.implementingClasses(Op.class); + Assert.assertTrue(implementingClasses.contains(ServiceBasedAdder.class)); + Assert.assertEquals(implementingClasses.size(), 1); + } + + @Test + public void testServiceLoaderWithOpCollections() { + Discoverer d = new ServiceLoaderDiscoverer(); + List> implementingClasses = d.implementingClasses(OpCollection.class); + Assert.assertTrue(implementingClasses.contains(ServiceBasedMultipliers.class)); + Assert.assertEquals(implementingClasses.size(), 1); + } +} + diff --git a/scijava/scijava-ops-spi/src/main/java/org/scijava/ops/spi/OpClass.java b/scijava/scijava-ops-spi/src/main/java/org/scijava/ops/spi/OpClass.java new file mode 100644 index 000000000..7e6e33b41 --- /dev/null +++ b/scijava/scijava-ops-spi/src/main/java/org/scijava/ops/spi/OpClass.java @@ -0,0 +1,23 @@ +package org.scijava.ops.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.scijava.Priority; + +/** Annotates an op declared as a field in an {@link OpCollection}. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface OpClass { + + String names(); + + // the names of the parameters (inputs and outputs) that will appear in a call + // to help(). + String[] params() default ""; + + double priority() default Priority.NORMAL; + +}