
org.jerkar.tool.PluginDictionnary Maven / Gradle / Ivy
Show all versions of core Show documentation
package org.jerkar.tool;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jerkar.api.java.JkClassLoader;
import org.jerkar.api.utils.JkUtilsString;
/**
* Contains the Plugin description for all concrete plugin classes extending a
* given base class.
*
* Jerkar offers a very simple, yet powerful, plugin mechanism.
* Basically it offers to discover every classes in the classpath that inherit
* from a given class and that respect a certain naming convention.
*
* The convention naming is as follow : The class simple name should be prefixed
* by the simple name of the plugin base class.
* For example, a plugin class for
* or.jerkar.java.build.JkBuildPlugin
class must be named
* 'my.package.JkJavaBuildPluginXxxxx.class' to be discovered :Xxxxx will be its
* short name, while my.package.JkJavaBuildPluginXxxxx will be its full name.
*
* @param
* The plugin base class.
* @author Jerome Angibaud
*
* @see {@link JkPluginDescription}
*/
final class PluginDictionnary {
private static final Map, Set>> CACHE = new HashMap, Set>>();
/**
* Creates a {@link PluginDictionnary} for the specified extension points.
* That means, this instance will refer to all plugin extending the
* specified extension point in the parameter templateClass.
*/
public static PluginDictionnary of(Class templateClass) {
final PluginDictionnary result = new PluginDictionnary(templateClass);
if (CACHE.containsKey(templateClass)) {
final Set> pluginClasses = CACHE.get(templateClass);
result.plugins = toPluginSet(templateClass, pluginClasses);
}
return result;
}
private Set> plugins;
private final Class templateClass;
private PluginDictionnary(Class extendingClass) {
super();
this.templateClass = extendingClass;
}
/**
* Returns all the plugins present in classpath for this template class.
*/
public Set> getAll() {
if (plugins == null) {
synchronized (this) {
final Set> result = loadAllPlugins(templateClass);
this.plugins = Collections.unmodifiableSet(result);
}
}
return this.plugins;
}
/**
* Returns the plugin having a full name equals to the specified name. If
* not found, returns the plugin having a short name equals to the specified
* name. Note that the short name is capitalized for you so using
* "myPluging" or "MyPlugin" is equal. If not found, returns
* null
.
*/
public JkPluginDescription loadByName(String name) {
if (!name.contains(".")) {
final JkPluginDescription result = loadPluginHavingShortName(templateClass,
JkUtilsString.capitalize(name));
if (result != null) {
return result;
}
}
return loadPluginsHavingLongName(templateClass, name);
}
public JkPluginDescription loadByNameOrFail(String name) {
final JkPluginDescription result = loadByName(name);
if (result == null) {
throw new IllegalArgumentException("No class found having name "
+ simpleClassName(templateClass, name) + " for plugin '" + name + "'.");
}
return result;
}
private static String simpleClassName(Class> templateClass, String pluginName) {
return templateClass.getSimpleName() + JkUtilsString.capitalize(pluginName);
}
@Override
public String toString() {
if (this.plugins == null) {
return "Not loaded (template class = " + this.templateClass + ")";
}
return this.plugins.toString();
}
private static Set> loadAllPlugins(Class templateClass) {
final String nameSuffix = templateClass.getSimpleName();
return loadPlugins(templateClass, "**/" + nameSuffix + "*", "**/*$" + nameSuffix + "*");
}
private static JkPluginDescription loadPluginHavingShortName(Class templateClass,
String shortName) {
final String simpleName = simpleClassName(templateClass, shortName);
final Set> set = loadPlugins(templateClass, "**/" + simpleName);
set.addAll(loadPlugins(templateClass, "**/*$" + simpleName));
if (set.size() > 1) {
throw new JkException("Several plugin have the same short name : '" + shortName
+ "'. Please disambiguate with using plugin long name (full class name)."
+ " Following plugins have same shortName : " + set);
}
if (set.isEmpty()) {
return null;
}
return set.iterator().next();
}
private static JkPluginDescription loadPluginsHavingLongName(Class templateClass,
String longName) {
final Class extends T> pluginClass = JkClassLoader.current().loadIfExist(longName);
if (pluginClass == null) {
return null;
}
return new JkPluginDescription(templateClass, pluginClass);
}
private static Set> loadPlugins(Class templateClass,
String... patterns) {
final Set> matchingClasses = JkClassLoader.of(templateClass).loadClasses(patterns);
final Set> result = new HashSet>();
for (final Class> candidate : matchingClasses) {
if (templateClass.isAssignableFrom(candidate)
&& !Modifier.isAbstract(candidate.getModifiers())
&& !candidate.equals(templateClass)) {
result.add(candidate);
}
}
return toPluginSet(templateClass, result);
}
@SuppressWarnings("unchecked")
private static Set> toPluginSet(Class extendingClass,
Iterable> classes) {
final Set> result = new HashSet>();
for (final Class> clazz : classes) {
result.add(new JkPluginDescription(extendingClass, (Class extends T>) clazz));
}
return result;
}
/**
* Give the description of a plugin class as its names, its purpose and its
* base class.
*
* @author Jerome Angibaud
* @param
*/
public static class JkPluginDescription {
private static String shortName(Class> extendingClass, Class> clazz) {
return JkUtilsString.substringAfterFirst(clazz.getSimpleName(),
extendingClass.getSimpleName());
}
private static String longName(Class> extendingClass, Class> clazz) {
return clazz.getName();
}
/**
* Returns all JkPlugins
instances declared as field in the
* specified instance. It includes fields declared in the specified
* instance class and the ones declared in its super classes.
*/
public static List> declaredAsField(JkBuild hostingInstance) {
final List> result = new LinkedList>();
final List> templateClasses = hostingInstance.pluginTemplateClasses();
for (final Class