Skip to content

Commit f31a468

Browse files
committed
PluginInfo: add utility methods to get PluginInfos
1 parent 3f0db78 commit f31a468

File tree

2 files changed

+245
-3
lines changed

2 files changed

+245
-3
lines changed

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

Lines changed: 158 additions & 0 deletions
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;
@@ -351,6 +353,143 @@ public String getVersion() {
351353
}
352354
}
353355

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+
354493
// -- Helper methods --
355494

356495
/** Populates the entry to match the associated @{@link Plugin} annotation. */
@@ -412,4 +551,23 @@ private MenuPath parseMenuPath(final Menu[] menu) {
412551
return menuPath;
413552
}
414553

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+
}
415573
}

src/test/java/org/scijava/plugin/PluginInfoTest.java

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,16 @@
3333
package org.scijava.plugin;
3434

3535
import static org.junit.Assert.assertEquals;
36+
import static org.junit.Assert.assertNotNull;
37+
import static org.junit.Assert.assertNotSame;
38+
import static org.junit.Assert.assertNull;
3639
import static org.junit.Assert.assertSame;
40+
import static org.junit.Assert.fail;
3741

3842
import java.util.List;
3943

44+
import org.junit.After;
45+
import org.junit.Before;
4046
import org.junit.Test;
4147
import org.scijava.Context;
4248
import org.scijava.InstantiableException;
@@ -49,11 +55,24 @@
4955
*/
5056
public class PluginInfoTest {
5157

58+
private Context context;
59+
private PluginIndex pluginIndex;
60+
61+
@Before
62+
public void setUp() {
63+
context = new Context(true);
64+
pluginIndex = context.getPluginIndex();
65+
}
66+
67+
@After
68+
public void tearDown() {
69+
context.dispose();
70+
context = null;
71+
pluginIndex = null;
72+
}
73+
5274
@Test
5375
public void testNames() throws InstantiableException {
54-
final Context context = new Context(true);
55-
final PluginIndex pluginIndex = context.getPluginIndex();
56-
5776
final List<PluginInfo<?>> infos = pluginIndex.get(IceCream.class);
5877
assertEquals(3, infos.size());
5978

@@ -62,9 +81,74 @@ public void testNames() throws InstantiableException {
6281
assertPlugin(Flavorless.class, IceCream.class, "", infos.get(2));
6382
}
6483

84+
@Test
85+
public void testGet() throws InstantiableException {
86+
final PluginInfo<?> chocolateInfo = //
87+
PluginInfo.get(Chocolate.class, pluginIndex);
88+
assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo);
89+
90+
final PluginInfo<IceCream> chocolateInfoWithType = //
91+
PluginInfo.get(Chocolate.class, IceCream.class, pluginIndex);
92+
assertSame(chocolateInfo, chocolateInfoWithType);
93+
94+
class Sherbet implements IceCream {}
95+
assertNull(PluginInfo.get(Sherbet.class, pluginIndex));
96+
}
97+
98+
@Test
99+
public void testCreate() throws InstantiableException {
100+
final PluginInfo<?> chocolateInfo = PluginInfo.create(Chocolate.class);
101+
assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo);
102+
103+
final PluginInfo<IceCream> chocolateInfoWithType = //
104+
PluginInfo.create(Chocolate.class, IceCream.class);
105+
assertPlugin(Chocolate.class, IceCream.class, "chocolate",
106+
chocolateInfoWithType);
107+
assertNotSame(chocolateInfo, chocolateInfoWithType);
108+
109+
class Sherbet implements IceCream {}
110+
final PluginInfo<IceCream> sherbetInfoWithType = //
111+
PluginInfo.create(Sherbet.class, IceCream.class);
112+
assertPlugin(Sherbet.class, IceCream.class, null, sherbetInfoWithType);
113+
114+
try {
115+
final PluginInfo<?> result = PluginInfo.create(Sherbet.class);
116+
fail("Expected IllegalArgumentException but got: " + result);
117+
}
118+
catch (final IllegalArgumentException exc) {
119+
// NB: Expected.
120+
}
121+
}
122+
123+
@Test
124+
public void testGetOrCreate() throws InstantiableException {
125+
final PluginInfo<?> chocolateInfo = //
126+
PluginInfo.getOrCreate(Chocolate.class, pluginIndex);
127+
assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo);
128+
129+
final PluginInfo<IceCream> chocolateInfoWithType = //
130+
PluginInfo.getOrCreate(Chocolate.class, IceCream.class, pluginIndex);
131+
assertSame(chocolateInfo, chocolateInfoWithType);
132+
133+
class Sherbet implements IceCream {}
134+
final PluginInfo<IceCream> sherbetInfoWithType = //
135+
PluginInfo.getOrCreate(Sherbet.class, IceCream.class, pluginIndex);
136+
assertPlugin(Sherbet.class, IceCream.class, null, sherbetInfoWithType);
137+
138+
try {
139+
final PluginInfo<?> result = //
140+
PluginInfo.getOrCreate(Sherbet.class, pluginIndex);
141+
fail("Expected IllegalArgumentException but got: " + result);
142+
}
143+
catch (final IllegalArgumentException exc) {
144+
// NB: Expected.
145+
}
146+
}
147+
65148
private void assertPlugin(Class<?> pluginClass, Class<?> pluginType,
66149
String name, PluginInfo<?> info) throws InstantiableException
67150
{
151+
assertNotNull(info);
68152
assertSame(pluginClass, info.loadClass());
69153
assertSame(pluginType, info.getPluginType());
70154
assertEquals(name, info.getName());

0 commit comments

Comments
 (0)