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

imagingbook.core.plugin.PluginsConfigBuilder Maven / Gradle / Ivy

Go to download

Small core library with minimal dependencies. This code is part of the imagingbook software suite accompanying the image processing textbooks by W. Burger and M.J. Burge (Springer 2006-2022).

The newest version!
/*******************************************************************************
 * This software is provided as a supplement to the authors' textbooks on digital
 * image processing published by Springer-Verlag in various languages and editions.
 * Permission to use and distribute this software is granted under the BSD 2-Clause
 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
 * Visit https://imagingbook.com for additional details.
 ******************************************************************************/
package imagingbook.core.plugin;

import ij.plugin.PlugIn;
import ij.plugin.filter.PlugInFilter;
import imagingbook.core.FileUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * 

* The {@code main()} method of this class creates the {@code plugins.config} file for a given plugins project, which is * to be included in the associated JAR file. The execution is to be triggered during the Maven build or manually by *

*
 * mvn exec:java -Dexec.mainClass="imagingbook.pluginutils.PluginsConfigBuilder"
*

* (at the root of a plugins project). The format of the entries in {@code plugins.config} have the following * structure: *

*
 * menu-level, "plugin-name", package.classname
* for example: *
 * Plugins>Binary>Regions, "Convex Hull Demo", Binary_Regions.Convex_Hull_Demo
*

Note that, technically, menu paths may be more than 2 levels deep, but this * does not seem useful. *

*

* Plugin classes (implementing {@link PlugIn} or {@link PlugInFilter}) may be annotated with {@link IjPluginPath} and * {@link IjPluginName} to specify where in ImageJ's menu tree and by which name the plugin should be installed. This * information is stored in the {@code plugins.config} file at the root of the associated project, which is * automatically added to the project's output JAR file during the Maven build. Example: *

*
 * // file MySuperPlugin.java
 * import ij.plugin.filter.PlugInFilter;
 * import imagingbook.pluginutils.annotations.IjPluginName;
 * import imagingbook.pluginutils.annotations.IjPluginPath;
 * ...
 * {@literal @}IjPluginPath("Plugins>Mine")
 * {@literal @}IjPluginName("Super Plugin")
 * public class MySuperPlugin implements PlugInFilter {
 * 	// plugin code ...
 * }
*

* In this case, plugin {@code MySuperPlugin} should be installed in ImageJ's menu tree as *

 Plugins > Mine > Super Plugin
*

* By default (i.e., if no annotations are present), plugins in the default package are installed at the top-level of * 'Plugins' whereas plugins inside a named package are installed in 'Plugins>package-name' (see below). A * {@link IjPluginPath} annotation may also be attached to a whole package in the associated {@code package-info.java} * file. The following example specifies {@code Plugins>Binary Regions} as the default menu path for all plugins in * package {@code Binary_Regions}: *

*
 * // file Binary_Regions/package-info.java
 * {@literal @}IjPluginPath("Plugins>Binary Regions")
 * package Binary_Regions;
 * import imagingbook.pluginutils.annotations.IjPluginPath;
*

* Individual plugins may override the menu path specified for the containing package, as summarized below: *

* Plugin path priority rules summary: *

*
    *
  1. Value of a {@code @IjPluginPath} annotation at class level (always overrules if exists).
  2. *
  3. Value of a {@code @IjPluginPath} annotation at package level (if exists).
  4. *
  5. {@link #DefaultMenuPath} + {@literal ">"} + package-name if the plugin is inside a named package.
  6. *
  7. {@link #DefaultMenuPath} if the plugin is in the (unnamed) default package.
  8. *
*

* Plugin entry priority rules summary: *

*
    *
  1. Value of {@link IjPluginName} annotation at class level (if exists).
  2. *
  3. Simple name of the plugin class.
  4. *
*

* Note that, in general, ImageJ uses the information in file {@code plugins.config} * only for plugins loaded from a JAR file! *

* * @author WB * @see IjPluginPath * @see IjPluginName */ public class PluginsConfigBuilder { protected static String DefaultMenuPath = "Plugins"; // can be overridden by package or class annotation @IjPluginPath // static String DefaultEntryPrefix = "B&B "; protected static String ConfigFileName = "plugins.config"; protected static String INFO = "[INFO] "; protected static boolean VERBOSE = true; protected static boolean ReplaceUndescoresInClassNames = true; protected static boolean ReplaceUndescoresInPackageNames = true; private final String artifactId; private final String rootPath; /** * Constructor (private), only called from the main() method. * @param rootName the project's root (output) directory * @param artifactId the project's Maven artifact id */ private PluginsConfigBuilder(String rootName, String artifactId) { this.artifactId = artifactId; File rootDir = (rootName != null) ? new File(rootName) : new File(PluginsConfigBuilder.class.getClassLoader().getResource("").getFile()); this.rootPath = rootDir.getAbsolutePath(); } public List> collectPluginClasses(String rootPath) { int n = rootPath.length(); File rootFile = new File(rootPath); if (!rootFile.exists()) { // this happens when executed in a project with POM-packaging return null; } Path start = rootFile.toPath(); List> pluginClasses = new ArrayList<>(); try (final Stream allPaths = Files.walk(start)) { allPaths.filter(Files::isRegularFile).forEach(path -> { String pathName = path.toString(); if (FileUtils.getFileExtension(pathName).equals("class")) { String className = FileUtils.stripFileExtension(pathName); // remove non-class part of filename: className = className.substring(n + 1); if (className.indexOf('-') < 0) { // ignore 'package-info' and 'module-info' // convert to qualified class name: className = className.replace(File.separatorChar, '.'); // find the associated class object (this should never fail): Class clazz = null; try { clazz = Class.forName(className); } catch (final ClassNotFoundException e) { throw new RuntimeException(e.getMessage()); } if (clazz != null && isIjPlugin(clazz)) { pluginClasses.add(clazz); } } } }); } catch (IOException e) { //throw new RuntimeException(e.getMessage()); System.out.println("SOMETHING BAD HAPPENED: " + e.getMessage()); } return pluginClasses; } /** * Writes plugin configuration entries to the specified stream. * * @param pluginClasses a list of plugin classes * @param strm the output stream (e.g., System.out) */ private void writeEntriesToStream(List> pluginClasses, PrintStream strm) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); strm.println("# plugins.config file for " + artifactId + " (automatically generated)"); strm.println("# number of plugins: " + pluginClasses.size()); strm.println("# date: " + LocalDateTime.now().format(formatter)); for (Class clazz : pluginClasses) { // configure menu path: Package pkg = clazz.getPackage(); String menuPath = DefaultMenuPath; if (pkg != null) { // see if 'package-info.java' contains specifies a menu path for this package // TODO: warn if package nesting is deeper than 1 IjPluginPath packageMenuPathAnn = pkg.getDeclaredAnnotation(IjPluginPath.class); String pkgName = (ReplaceUndescoresInPackageNames) ? pkg.getName().replace('_', ' ') : pkg.getName(); menuPath = (packageMenuPathAnn != null) ? packageMenuPathAnn.value() : DefaultMenuPath + ">" + pkgName; } // see if clazz specifies a menu path for this package (overrules package specification) IjPluginPath classMenuPathAnn = clazz.getDeclaredAnnotation(IjPluginPath.class); if (classMenuPathAnn != null) { menuPath = classMenuPathAnn.value(); } // configure menu entry: IjPluginName classMenuEntryAnn = clazz.getDeclaredAnnotation(IjPluginName.class); String className = (ReplaceUndescoresInClassNames) ? clazz.getSimpleName().replace('_', ' ') : clazz.getSimpleName(); String menuEntry = (classMenuEntryAnn != null) ? classMenuEntryAnn.value() : className; // build line for entry in config file: String configLine = String.format("%s, \"%s\", %s", menuPath, menuEntry, clazz.getCanonicalName()); if (VERBOSE) System.out.println(INFO + "*** " + configLine); // strm.format("%s, \"%s\", %s\n", menuPath, menuEntry, clazz.getCanonicalName()); strm.println(configLine); } } private String buildfile() { List> pluginClasses = collectPluginClasses(rootPath); if (pluginClasses == null) { System.out.println(INFO + "WARNING: no target directory (POM project?)"); return null; } if (pluginClasses.isEmpty()) { System.out.println(INFO + "WARNING: no plugin classes found!"); return null; } System.out.println(INFO + "Number of plugins: " + pluginClasses.size()); // writeEntriesToStream(pluginClasses, System.out); File configFile = new File(rootPath + "/" + ConfigFileName); // System.out.println("configPath = " + configFile.getAbsolutePath()); try (PrintStream ps = new PrintStream(configFile)) { writeEntriesToStream(pluginClasses, ps); } catch (FileNotFoundException e) { throw new RuntimeException(e.getMessage()); } return configFile.getAbsolutePath(); } /** * Returns true if the specified {@link Class} object is a sub-type of * {@link PlugIn} or {@link PlugInFilter}. * * @param clazz a {@link Class} object * @return true if a plugin type */ public boolean isIjPlugin(Class clazz) { return PlugIn.class.isAssignableFrom(clazz) || PlugInFilter.class.isAssignableFrom(clazz); } // ---------------------------------------------------------------------------------------------- /** *

* Method to be called from the command line. Builds the {@code plugins.config} file * from the {@code .class} files found in the specified build directory and * stores the file in the same directory. * Takes two (optional) arguments: *

*
    *
  1. The project's build (output) directory (where .class files reside).
  2. *
  3. The project's Maven artifact id.
  4. *
* If no build directory is specified, the current directory is used. * @param args {@code args[0]}: project build (output) directory, {@code args[1]}: project artefact id */ public static void main(String[] args) { String rootName = (args.length > 0) ? args[0] : null; String artifactId = (args.length > 1) ? args[1] : null; System.out.println(INFO); System.out.println(INFO + "--- Building plugins.config file for " + artifactId + " ---"); for (String arg : args) { System.out.println(INFO + " arg = |" + arg + "|"); } // future use to specify general plugins path from POM: // using property "Plugins>B&B " PluginsConfigBuilder builder = new PluginsConfigBuilder(rootName, artifactId); String configPath = builder.buildfile(); if (configPath != null) { System.out.println(INFO + "Config file written: " + configPath); } else { System.out.println(INFO + "No config file written"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy