All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.plugin.PluginInfo Maven / Gradle / Ivy

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
 * Institute of Molecular Cell Biology and Genetics, University of
 * Konstanz, and KNIME GmbH.
 * %%
 * 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.plugin;

import java.net.URL;
import java.util.Collection;
import java.util.Optional;

import org.scijava.AbstractUIDetails;
import org.scijava.Identifiable;
import org.scijava.Instantiable;
import org.scijava.InstantiableException;
import org.scijava.Locatable;
import org.scijava.MenuEntry;
import org.scijava.MenuPath;
import org.scijava.Priority;
import org.scijava.UIDetails;
import org.scijava.Versioned;
import org.scijava.input.Accelerator;
import org.scijava.util.StringMaker;
import org.scijava.util.Types;
import org.scijava.util.VersionUtils;

/**
 * A collection of metadata about a particular plugin.
 * 

* For performance reasons, the metadata is populated without actually loading * the plugin class, by reading from an efficient binary cache (see * {@link org.scijava.plugin.DefaultPluginService} for details). As such, we can * very quickly build a complex structure containing all available plugins * without waiting for the Java class loader. *

* * @param The type of plugin described by this metadata. See * {@link SciJavaPlugin} for a list of common plugin types. * @author Curtis Rueden * @see Plugin * @see PluginService */ public class PluginInfo extends AbstractUIDetails implements Instantiable, Identifiable, Locatable, Versioned { /** Fully qualified class name of this plugin. */ private String className; /** Class object for this plugin. Lazily loaded. */ private Class pluginClass; /** Type of this entry's plugin; e.g., {@link org.scijava.service.Service}. */ private Class pluginType; /** Annotation describing the plugin. */ private Plugin annotation; /** Class loader to use when loading the class with {@link #loadClass()}. */ private ClassLoader classLoader; /** * Creates a new plugin metadata object. * * @param className The name of the class, which must implement * {@link SciJavaPlugin}. * @param pluginType The type of plugin described by this metadata. * See {@link SciJavaPlugin} for a list of common plugin types. */ public PluginInfo(final String className, final Class pluginType) { this(className, null, pluginType, null, null); } /** * Creates a new plugin metadata object. * * @param className The name of the class, which must implement * {@link SciJavaPlugin}. * @param pluginType The type of plugin described by this metadata. * See {@link SciJavaPlugin} for a list of common plugin types. * @param annotation The @{@link Plugin} annotation to associate with this * metadata object. */ public PluginInfo(final String className, final Class pluginType, final Plugin annotation) { this(className, null, pluginType, annotation, null); } /** * Creates a new plugin metadata object. * * @param className The name of the class, which must implement * {@link SciJavaPlugin}. * @param pluginType The type of plugin described by this metadata. * See {@link SciJavaPlugin} for a list of common plugin types. * @param annotation The @{@link Plugin} annotation to associate with this * metadata object. * @param classLoader The {@link ClassLoader} to use when loading the class * via {@link #loadClass()}, or null to use the current thread's * context class loader by default. */ public PluginInfo(final String className, final Class pluginType, final Plugin annotation, final ClassLoader classLoader) { this(className, null, pluginType, annotation, classLoader); } /** * Creates a new plugin metadata object. * * @param pluginClass The plugin class, which must implement * {@link SciJavaPlugin}. * @param pluginType The type of plugin described by this metadata. * See {@link SciJavaPlugin} for a list of common plugin types. */ public PluginInfo(final Class pluginClass, final Class pluginType) { this(null, pluginClass, pluginType, null, null); } /** * Creates a new plugin metadata object. * * @param pluginClass The plugin class, which must implement * {@link SciJavaPlugin}. * @param pluginType The type of plugin described by this metadata. * See {@link SciJavaPlugin} for a list of common plugin types. * @param annotation The @{@link Plugin} annotation to associate with this * metadata object. */ public PluginInfo(final Class pluginClass, final Class pluginType, final Plugin annotation) { this(null, pluginClass, pluginType, annotation, null); } protected PluginInfo(final String className, final Class pluginClass, final Class pluginType, final Plugin annotation, final ClassLoader classLoader) { if (pluginClass != null) { if (className != null) { throw new IllegalArgumentException( "className and pluginClass are mutually exclusive"); } setPluginClass(pluginClass); } else { this.className = className; } setPluginType(pluginType); setMenuPath(null); setMenuRoot(UIDetails.APPLICATION_MENU_ROOT); if (annotation == null) { // attempt to obtain the annotation from the plugin class, if available if (pluginClass != null) { this.annotation = pluginClass.getAnnotation(Plugin.class); } } else this.annotation = annotation; populateValues(); this.classLoader = classLoader; } // -- PluginInfo methods -- /** * Explicitly sets the {@link Class} of the item objects. *

* This is useful if your class is produced by something other than the system * classloader. *

*/ public void setPluginClass(final Class pluginClass) { this.pluginClass = pluginClass; } /** * Obtains the {@link Class} of the item objects, if that class has already * been loaded. * * @return The {@link Class}, or null if it has not yet been loaded by * {@link #loadClass}. */ public Class getPluginClass() { return pluginClass; } /** * Sets the type of plugin described by the metadata. * @see SciJavaPlugin for a list of common plugin types. */ public void setPluginType(final Class pluginType) { this.pluginType = pluginType; } /** * Gets the type of plugin described by the metadata. * @see SciJavaPlugin for a list of common plugin types. */ public Class getPluginType() { return pluginType; } /** Gets the associated @{@link Plugin} annotation. */ public Plugin getAnnotation() { return annotation; } /** * Gets the URL corresponding to the icon resource path. * * @see #getIconPath() */ public URL getIconURL() throws InstantiableException { final String iconPath = getIconPath(); if (iconPath == null || iconPath.isEmpty()) return null; return loadClass().getResource(iconPath); } /** * Injects the metadata into the given object. Note that this is only possible * if the given object implements the {@link HasPluginInfo} interface. * * @param o The object to which the metadata should be assigned. * @return true If the metadata was successfully injected. */ public boolean inject(final Object o) { if (!(o instanceof HasPluginInfo)) return false; final HasPluginInfo hi = (HasPluginInfo) o; hi.setInfo(this); return true; } // -- Object methods -- @Override public String toString() { final StringMaker sm = new StringMaker(); sm.append("class", className); sm.append(super.toString()); sm.append("pluginType", pluginType); return sm.toString(); } // -- Instantiable methods -- @Override public String getClassName() { if (pluginClass != null) return pluginClass.getName(); return className; } @Override public Class loadClass() throws InstantiableException { if (pluginClass == null) { try { final Class c = Types.load(className, classLoader, false); @SuppressWarnings("unchecked") final Class typedClass = (Class) c; pluginClass = typedClass; } catch (final IllegalArgumentException exc) { throw new InstantiableException("Class not found: " + className, exc); } } return pluginClass; } @Override public PT createInstance() throws InstantiableException { final Class c = loadClass(); // instantiate plugin final PT instance; try { instance = c.newInstance(); inject(instance); Priority.inject(instance, getPriority()); } catch (final InstantiationException e) { throw new InstantiableException(e); } catch (final IllegalAccessException e) { throw new InstantiableException(e); } return instance; } // -- Identifiable methods -- @Override public String getIdentifier() { try { return "plugin:" + loadClass(); } catch (final InstantiableException exc) { return null; } } // -- Locatable methods -- @Override public String getLocation() { try { return Types.location(loadClass()).toExternalForm(); } catch (InstantiableException exc) { return null; } } // -- Versioned methods -- @Override public String getVersion() { try { return VersionUtils.getVersion(loadClass()); } catch (InstantiableException exc) { return null; } } // -- Utility methods -- /** * Finds a {@link PluginInfo} of the given plugin class in the specified * {@link PluginIndex}. Note that to avoid loading plugin classes, class * identity is determined by class name equality only. * * @param pluginClass The concrete class of the plugin whose * {@link PluginInfo} is desired. * @param pluginIndex The {@link PluginIndex} to search for a matching * {@link PluginInfo}. * @return The matching {@link PluginInfo}, or null if none found. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static

PluginInfo get( final Class

pluginClass, final PluginIndex pluginIndex) { return get(pluginClass, (Collection) pluginIndex.getAll()); } /** * Finds a {@link PluginInfo} of the given plugin class and plugin type in the * specified {@link PluginIndex}. Note that to avoid loading plugin * classes, class identity is determined by class name equality only. * * @param pluginClass The concrete class of the plugin whose * {@link PluginInfo} is desired. * @param pluginType The type of the plugin; see {@link #getPluginType()}. * @param pluginIndex The {@link PluginIndex} to search for a matching * {@link PluginInfo}. * @return The matching {@link PluginInfo}, or null if none found. */ public static

PluginInfo get( final Class

pluginClass, final Class pluginType, final PluginIndex pluginIndex) { return get(pluginClass, pluginIndex.getPlugins(pluginType)); } /** * Finds a {@link PluginInfo} of the given plugin class in the specified list * of plugins. Note that to avoid loading plugin classes, class identity * is determined by class name equality only. * * @param pluginClass The concrete class of the plugin whose * {@link PluginInfo} is desired. * @param plugins The list of plugins to search for a match. * @return The matching {@link PluginInfo}, or null if none found. */ public static

PluginInfo get( final Class

pluginClass, final Collection> plugins) { final String className = pluginClass.getName(); final Optional> result = plugins.stream() // .filter(info -> info.getClassName().equals(className)) // .findFirst(); return result.isPresent() ? result.get() : null; } /** * Creates a {@link PluginInfo} for the given plugin class. The class must be * a concrete class annotated with the @{@link Plugin} annotation, from which * the plugin type will be inferred. * * @param pluginClass The concrete class of the plugin for which a new * {@link PluginInfo} is desired. * @return A newly created {@link PluginInfo} for the given plugin class. * @throws IllegalArgumentException if the given class is not annotated * with @{@link Plugin}, or its annotated {@link Plugin#type() * type()} is not a supertype of the plugin class. */ public static PluginInfo create( final Class pluginClass) { @SuppressWarnings({ "rawtypes", "unchecked" }) final PluginInfo info = new PluginInfo(pluginClass, // pluginType(pluginClass)); return info; } /** * Creates a {@link PluginInfo} for the given plugin class of the specified * plugin type. * * @param pluginClass The concrete class of the plugin for which a new * {@link PluginInfo} is desired. * @param pluginType The type of the plugin; see {@link #getPluginType()}. * @return A newly created {@link PluginInfo} for the given plugin class. */ public static

PluginInfo create( final Class

pluginClass, final Class pluginType) { return new PluginInfo<>(pluginClass, pluginType); } /** * Obtains a {@link PluginInfo} for the given plugin class. If one already * exists in the specified {@link PluginIndex}, it is retrieved (see * {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see * {@link #create(Class)}) but not added to the index. * * @param pluginClass The concrete class of the plugin whose * {@link PluginInfo} is desired. * @param pluginIndex The {@link PluginIndex} to search for a matching * {@link PluginInfo}. * @throws IllegalArgumentException when creating a new {@link PluginInfo} if * the associated plugin type cannot be inferred; see * {@link #create(Class)}. */ public static

PluginInfo getOrCreate( final Class

pluginClass, final PluginIndex pluginIndex) { final PluginInfo existing = get(pluginClass, pluginIndex); return existing == null ? create(pluginClass) : existing; } /** * Obtains a {@link PluginInfo} for the given plugin class. If one already * exists in the specified {@link PluginIndex}, it is retrieved (see * {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see * {@link #create(Class)}) but not added to the index. * * @param pluginClass The concrete class of the plugin whose * {@link PluginInfo} is desired. * @param pluginType The type of the plugin; see {@link #getPluginType()}. * @param pluginIndex The {@link PluginIndex} to search for a matching * {@link PluginInfo}. */ public static

PluginInfo getOrCreate(final Class

pluginClass, final Class pluginType, final PluginIndex pluginIndex) { final PluginInfo existing = get(pluginClass, pluginType, pluginIndex); return existing == null ? create(pluginClass, pluginType) : existing; } // -- Helper methods -- /** Populates the entry to match the associated @{@link Plugin} annotation. */ private void populateValues() { final Plugin ann = getAnnotation(); if (ann == null) return; setName(ann.name()); setLabel(ann.label()); setDescription(ann.description()); final MenuPath menuPath; final Menu[] menu = ann.menu(); if (menu.length > 0) { menuPath = parseMenuPath(menu); } else { // parse menuPath attribute menuPath = new MenuPath(ann.menuPath()); } setMenuPath(menuPath); setMenuRoot(ann.menuRoot()); final String iconPath = ann.iconPath(); setIconPath(iconPath); setPriority(ann.priority()); setEnabled(ann.enabled()); setVisible(ann.visible()); setSelectable(ann.selectable()); setSelectionGroup(ann.selectionGroup()); // add default icon if none attached to leaf final MenuEntry menuLeaf = menuPath.getLeaf(); if (menuLeaf != null && !iconPath.isEmpty()) { final String menuIconPath = menuLeaf.getIconPath(); if (menuIconPath == null || menuIconPath.isEmpty()) { menuLeaf.setIconPath(iconPath); } } // populate extra attributes for (final Attr attr : ann.attrs()) { final String name = attr.name(); final String value = attr.value(); set(name, value); } } private MenuPath parseMenuPath(final Menu[] menu) { final MenuPath menuPath = new MenuPath(); for (int i = 0; i < menu.length; i++) { final String name = menu[i].label(); final double weight = menu[i].weight(); final char mnemonic = menu[i].mnemonic(); final Accelerator acc = Accelerator.create(menu[i].accelerator()); final String iconPath = menu[i].iconPath(); menuPath.add(new MenuEntry(name, weight, mnemonic, acc, iconPath)); } return menuPath; } /** Extracts the plugin type from a class's @{@link Plugin} annotation. */ private static

Class pluginType( final Class

pluginClass) { final Plugin annotation = pluginClass.getAnnotation(Plugin.class); if (annotation == null) { throw new IllegalArgumentException( "Cannot infer plugin type from class '" + pluginClass.getName() + "' with no @Plugin annotation."); } final Class type = annotation.type(); if (!type.isAssignableFrom(pluginClass)) { throw new IllegalArgumentException("Invalid plugin type '" + // type.getName() + "' for class '" + pluginClass.getName() + "'"); } @SuppressWarnings("unchecked") final Class pluginType = (Class) type; return pluginType; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy