Skip to content

Commit e243eb9

Browse files
committed
Merge branch 'null-plugin-infos'
This branch fixes a problem where Services and Gateways, even though they implement RichPlugin (and therefore HasPluginInfo) would not retain their associated PluginInfo, causing getInfo() to return null. It introduces some utility method in the PluginInfo class to aid in retrieve and constructing PluginInfos corresponding to Class objects.
2 parents cf53740 + 8e3b401 commit e243eb9

File tree

6 files changed

+333
-21
lines changed

6 files changed

+333
-21
lines changed

src/main/java/org/scijava/AbstractGateway.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.scijava.platform.AppEventService;
5555
import org.scijava.platform.PlatformService;
5656
import org.scijava.plugin.AbstractRichPlugin;
57+
import org.scijava.plugin.PluginInfo;
5758
import org.scijava.plugin.PluginService;
5859
import org.scijava.prefs.PrefService;
5960
import org.scijava.script.ScriptService;
@@ -86,7 +87,15 @@ public AbstractGateway() {
8687

8788
public AbstractGateway(final String appName, final Context context) {
8889
this.appName = appName;
89-
if (context != null) setContext(context);
90+
if (context != null) {
91+
setContext(context);
92+
93+
// NB: Make a best effort to inject plugin metadata.
94+
final PluginInfo<?> info = PluginInfo.getOrCreate(getClass(),
95+
Gateway.class, context.getPluginIndex());
96+
info.inject(this);
97+
Priority.inject(this, info.getPriority());
98+
}
9099
}
91100

92101
// -- Gateway methods --
@@ -114,6 +123,8 @@ public void launch(final String... args) {
114123

115124
@Override
116125
public String getShortName() {
126+
final String pluginName = getInfo() == null ? null : getInfo().getName();
127+
if (pluginName != null && !pluginName.isEmpty()) return pluginName;
117128
return getClass().getSimpleName().toLowerCase();
118129
}
119130

src/main/java/org/scijava/SciJava.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
*
4646
* @author Curtis Rueden
4747
*/
48-
@Plugin(type = Gateway.class)
48+
@Plugin(type = Gateway.class, name = "sj")
4949
public class SciJava extends AbstractGateway {
5050

5151
// -- Constructors --
@@ -113,12 +113,4 @@ public SciJava(final Collection<Class<? extends Service>> serviceClasses) {
113113
public SciJava(final Context context) {
114114
super(SciJavaApp.NAME, context);
115115
}
116-
117-
// -- Gateway methods --
118-
119-
@Override
120-
public String getShortName() {
121-
return "sj";
122-
}
123-
124116
}

src/main/java/org/scijava/plugin/PluginInfo.java

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
package org.scijava.plugin;
3434

3535
import java.net.URL;
36+
import java.util.Collection;
37+
import java.util.Optional;
3638

3739
import org.scijava.AbstractUIDetails;
3840
import org.scijava.Identifiable;
@@ -45,7 +47,6 @@
4547
import org.scijava.UIDetails;
4648
import org.scijava.Versioned;
4749
import org.scijava.input.Accelerator;
48-
import org.scijava.util.ClassUtils;
4950
import org.scijava.util.StringMaker;
5051
import org.scijava.util.Types;
5152
import org.scijava.util.VersionUtils;
@@ -352,6 +353,143 @@ public String getVersion() {
352353
}
353354
}
354355

356+
// -- Utility methods --
357+
358+
/**
359+
* Finds a {@link PluginInfo} of the given plugin class in the specified
360+
* {@link PluginIndex}. <em>Note that to avoid loading plugin classes, class
361+
* identity is determined by class name equality only.</em>
362+
*
363+
* @param pluginClass The concrete class of the plugin whose
364+
* {@link PluginInfo} is desired.
365+
* @param pluginIndex The {@link PluginIndex} to search for a matching
366+
* {@link PluginInfo}.
367+
* @return The matching {@link PluginInfo}, or null if none found.
368+
*/
369+
@SuppressWarnings({ "rawtypes", "unchecked" })
370+
public static <P extends SciJavaPlugin> PluginInfo<?> get(
371+
final Class<P> pluginClass, final PluginIndex pluginIndex)
372+
{
373+
return get(pluginClass, (Collection) pluginIndex.getAll());
374+
}
375+
376+
/**
377+
* Finds a {@link PluginInfo} of the given plugin class and plugin type in the
378+
* specified {@link PluginIndex}. <em>Note that to avoid loading plugin
379+
* classes, class identity is determined by class name equality only.</em>
380+
*
381+
* @param pluginClass The concrete class of the plugin whose
382+
* {@link PluginInfo} is desired.
383+
* @param pluginType The type of the plugin; see {@link #getPluginType()}.
384+
* @param pluginIndex The {@link PluginIndex} to search for a matching
385+
* {@link PluginInfo}.
386+
* @return The matching {@link PluginInfo}, or null if none found.
387+
*/
388+
public static <P extends PT, PT extends SciJavaPlugin> PluginInfo<PT> get(
389+
final Class<P> pluginClass, final Class<PT> pluginType,
390+
final PluginIndex pluginIndex)
391+
{
392+
return get(pluginClass, pluginIndex.getPlugins(pluginType));
393+
}
394+
395+
/**
396+
* Finds a {@link PluginInfo} of the given plugin class in the specified list
397+
* of plugins. <em>Note that to avoid loading plugin classes, class identity
398+
* is determined by class name equality only.</em>
399+
*
400+
* @param pluginClass The concrete class of the plugin whose
401+
* {@link PluginInfo} is desired.
402+
* @param plugins The list of plugins to search for a match.
403+
* @return The matching {@link PluginInfo}, or null if none found.
404+
*/
405+
public static <P extends PT, PT extends SciJavaPlugin> PluginInfo<PT> get(
406+
final Class<P> pluginClass,
407+
final Collection<? extends PluginInfo<PT>> plugins)
408+
{
409+
final String className = pluginClass.getName();
410+
final Optional<? extends PluginInfo<PT>> result = plugins.stream() //
411+
.filter(info -> info.getClassName().equals(className)) //
412+
.findFirst();
413+
return result.isPresent() ? result.get() : null;
414+
}
415+
416+
/**
417+
* Creates a {@link PluginInfo} for the given plugin class. The class must be
418+
* a concrete class annotated with the @{@link Plugin} annotation, from which
419+
* the plugin type will be inferred.
420+
*
421+
* @param pluginClass The concrete class of the plugin for which a new
422+
* {@link PluginInfo} is desired.
423+
* @return A newly created {@link PluginInfo} for the given plugin class.
424+
* @throws IllegalArgumentException if the given class is not annotated
425+
* with @{@link Plugin}, or its annotated {@link Plugin#type()
426+
* type()} is not a supertype of the plugin class.
427+
*/
428+
public static PluginInfo<?> create(
429+
final Class<? extends SciJavaPlugin> pluginClass)
430+
{
431+
@SuppressWarnings({ "rawtypes", "unchecked" })
432+
final PluginInfo<?> info = new PluginInfo(pluginClass, //
433+
pluginType(pluginClass));
434+
return info;
435+
}
436+
437+
/**
438+
* Creates a {@link PluginInfo} for the given plugin class of the specified
439+
* plugin type.
440+
*
441+
* @param pluginClass The concrete class of the plugin for which a new
442+
* {@link PluginInfo} is desired.
443+
* @param pluginType The type of the plugin; see {@link #getPluginType()}.
444+
* @return A newly created {@link PluginInfo} for the given plugin class.
445+
*/
446+
public static <P extends PT, PT extends SciJavaPlugin> PluginInfo<PT> create(
447+
final Class<P> pluginClass, final Class<PT> pluginType)
448+
{
449+
return new PluginInfo<>(pluginClass, pluginType);
450+
}
451+
452+
/**
453+
* Obtains a {@link PluginInfo} for the given plugin class. If one already
454+
* exists in the specified {@link PluginIndex}, it is retrieved (see
455+
* {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see
456+
* {@link #create(Class)}) but not added to the index.
457+
*
458+
* @param pluginClass The concrete class of the plugin whose
459+
* {@link PluginInfo} is desired.
460+
* @param pluginIndex The {@link PluginIndex} to search for a matching
461+
* {@link PluginInfo}.
462+
* @throws IllegalArgumentException when creating a new {@link PluginInfo} if
463+
* the associated plugin type cannot be inferred; see
464+
* {@link #create(Class)}.
465+
*/
466+
public static <P extends SciJavaPlugin> PluginInfo<?> getOrCreate(
467+
final Class<P> pluginClass, final PluginIndex pluginIndex)
468+
{
469+
final PluginInfo<?> existing = get(pluginClass, pluginIndex);
470+
return existing == null ? create(pluginClass) : existing;
471+
}
472+
473+
/**
474+
* Obtains a {@link PluginInfo} for the given plugin class. If one already
475+
* exists in the specified {@link PluginIndex}, it is retrieved (see
476+
* {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see
477+
* {@link #create(Class)}) but not added to the index.
478+
*
479+
* @param pluginClass The concrete class of the plugin whose
480+
* {@link PluginInfo} is desired.
481+
* @param pluginType The type of the plugin; see {@link #getPluginType()}.
482+
* @param pluginIndex The {@link PluginIndex} to search for a matching
483+
* {@link PluginInfo}.
484+
*/
485+
public static <P extends PT, PT extends SciJavaPlugin> PluginInfo<PT>
486+
getOrCreate(final Class<P> pluginClass, final Class<PT> pluginType,
487+
final PluginIndex pluginIndex)
488+
{
489+
final PluginInfo<PT> existing = get(pluginClass, pluginType, pluginIndex);
490+
return existing == null ? create(pluginClass, pluginType) : existing;
491+
}
492+
355493
// -- Helper methods --
356494

357495
/** Populates the entry to match the associated @{@link Plugin} annotation. */
@@ -413,4 +551,23 @@ private MenuPath parseMenuPath(final Menu[] menu) {
413551
return menuPath;
414552
}
415553

554+
/** Extracts the plugin type from a class's @{@link Plugin} annotation. */
555+
private static <P extends SciJavaPlugin> Class<? super P> pluginType(
556+
final Class<P> pluginClass)
557+
{
558+
final Plugin annotation = pluginClass.getAnnotation(Plugin.class);
559+
if (annotation == null) {
560+
throw new IllegalArgumentException(
561+
"Cannot infer plugin type from class '" + pluginClass.getName() +
562+
"' with no @Plugin annotation.");
563+
}
564+
final Class<?> type = annotation.type();
565+
if (!type.isAssignableFrom(pluginClass)) {
566+
throw new IllegalArgumentException("Invalid plugin type '" + //
567+
type.getName() + "' for class '" + pluginClass.getName() + "'");
568+
}
569+
@SuppressWarnings("unchecked")
570+
final Class<? super P> pluginType = (Class<? super P>) type;
571+
return pluginType;
572+
}
416573
}

src/main/java/org/scijava/service/ServiceHelper.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class ServiceHelper extends AbstractContextual {
6565
* Classes to scan when searching for dependencies. Data structure is a map
6666
* with keys being relevant classes, and values being associated priorities.
6767
*/
68-
private final Map<Class<? extends Service>, Double> classPoolMap;
68+
private final Map<Class<? extends Service>, PluginInfo<?>> classPoolMap;
6969

7070
/** Classes to scan when searching for dependencies, sorted by priority. */
7171
private final List<Class<? extends Service>> classPoolList;
@@ -303,9 +303,10 @@ private <S extends Service> S createServiceRecursively(final Class<S> c)
303303
final S service = c.newInstance();
304304
service.setContext(getContext());
305305

306-
// propagate priority if known
307-
final Double priority = classPoolMap.get(c);
308-
if (priority != null) service.setPriority(priority);
306+
// propagate plugin metadata and priority if known
307+
final PluginInfo<?> info = classPoolMap.get(c);
308+
service.setInfo(info);
309+
if (info != null) service.setPriority(info.getPriority());
309310

310311
// NB: If there are any @EventHandler annotated methods, we treat the
311312
// EventService as a required dependency, _unless_ there is also an
@@ -358,7 +359,7 @@ private <S extends Service> S createServiceRecursively(final Class<S> c)
358359

359360
/** Asks the plugin index for all available service implementations. */
360361
private void findServiceClasses(
361-
final Map<Class<? extends Service>, Double> serviceMap,
362+
final Map<Class<? extends Service>, PluginInfo<?>> serviceMap,
362363
final List<Class<? extends Service>> serviceList)
363364
{
364365
// ask the plugin index for the (sorted) list of available services
@@ -368,8 +369,7 @@ private void findServiceClasses(
368369
for (final PluginInfo<Service> info : services) {
369370
try {
370371
final Class<? extends Service> c = info.loadClass();
371-
final double priority = info.getPriority();
372-
serviceMap.put(c, priority);
372+
serviceMap.put(c, info);
373373
serviceList.add(c);
374374
}
375375
catch (final Throwable e) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava;
34+
35+
import static org.junit.Assert.assertEquals;
36+
import static org.junit.Assert.assertNotNull;
37+
import static org.junit.Assert.assertSame;
38+
39+
import org.junit.Test;
40+
import org.scijava.plugin.PluginInfo;
41+
42+
/**
43+
* Tests {@link SciJava}.
44+
*
45+
* @author Curtis Rueden
46+
*/
47+
public class SciJavaTest {
48+
49+
@Test
50+
public void testInfo() {
51+
final SciJava sj = new SciJava();
52+
53+
final PluginInfo<?> infoFromObject = sj.getInfo();
54+
final PluginInfo<?> infoFromServiceNoType = //
55+
sj.plugin().getPlugin(SciJava.class);
56+
final PluginInfo<Gateway> infoFromServiceWithType = //
57+
sj.plugin().getPlugin(SciJava.class, Gateway.class);
58+
assertSame(infoFromServiceNoType, infoFromObject);
59+
assertSame(infoFromServiceWithType, infoFromObject);
60+
61+
assertNotNull(infoFromObject);
62+
assertSame(Gateway.class, infoFromObject.getPluginType());
63+
64+
assertEquals(Priority.NORMAL, sj.getPriority(), 0);
65+
66+
sj.dispose();
67+
}
68+
}

0 commit comments

Comments
 (0)