// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.MapFrameListener;
import org.openstreetmap.josm.gui.download.DownloadSelection;
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
import org.openstreetmap.josm.tools.Utils;
/**
* For all purposes of loading dynamic resources, the Plugin's class loader should be used
* (or else, the plugin jar will not be within the class path).
*
* A plugin may subclass this abstract base class (but it is optional).
*
* The actual implementation of this class is optional, as all functions will be called
* via reflection. This is to be able to change this interface without the need of
* recompiling or even breaking the plugins. If your class does not provide a
* function here (or does provide a function with a mismatching signature), it will not
* be called. That simple.
*
* Or in other words: See this base class as an documentation of what automatic callbacks
* are provided (you can register yourself to more callbacks in your plugin class
* constructor).
*
* Subclassing Plugin and overriding some functions makes it easy for you to keep sync
* with the correct actual plugin architecture of JOSM.
*
* @author Immanuel.Scholz
*/
public abstract class Plugin implements MapFrameListener {
/**
* This is the info available for this plugin. You can access this from your
* constructor.
*
* (The actual implementation to request the info from a static variable
* is a bit hacky, but it works).
*/
private PluginInformation info;
/**
* Creates the plugin
*
* @param info the plugin information describing the plugin.
*/
public Plugin(PluginInformation info) {
this.info = info;
}
/**
* Replies the plugin information object for this plugin
*
* @return the plugin information object
*/
public PluginInformation getPluginInformation() {
return info;
}
/**
* Sets the plugin information object for this plugin
*
* @param info the plugin information object
*/
public void setPluginInformation(PluginInformation info) {
this.info = info;
}
/**
* @return The directory for the plugin to store all kind of stuff.
*/
public String getPluginDir() {
return new File(Main.pref.getPluginsDirectory(), info.name).getPath();
}
@Override
public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {}
/**
* Called in the preferences dialog to create a preferences page for the plugin,
* if any available.
* @return the preferences dialog, or {@code null}
*/
public PreferenceSetting getPreferenceSetting() {
return null;
}
/**
* Called in the download dialog to give the plugin a chance to modify the list
* of bounding box selectors.
* @param list list of bounding box selectors
*/
public void addDownloadSelection(List<DownloadSelection> list) {}
/**
* Copies the resource 'from' to the file in the plugin directory named 'to'.
* @param from source file
* @param to target file
* @throws FileNotFoundException if the file exists but is a directory rather than a regular file,
* does not exist but cannot be created, or cannot be opened for any other reason
* @throws IOException if any other I/O error occurs
*/
public void copy(String from, String to) throws IOException {
String pluginDirName = getPluginDir();
File pluginDir = new File(pluginDirName);
if (!pluginDir.exists()) {
Utils.mkDirs(pluginDir);
}
try (InputStream in = getClass().getResourceAsStream(from)) {
if (in == null) {
throw new IOException("Resource not found: "+from);
}
Files.copy(in, new File(pluginDirName, to).toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
/**
* Get a class loader for loading resources from the plugin jar.
*
* This can be used to avoid getting a file from another plugin that
* happens to have a file with the same file name and path.
*
* Usage: Instead of
* getClass().getResource("/resources/pluginProperties.properties");
* write
* getPluginResourceClassLoader().getResource("resources/pluginProperties.properties");
*
* (Note the missing leading "/".)
* @return a class loader for loading resources from the plugin jar
*/
public ClassLoader getPluginResourceClassLoader() {
File pluginDir = Main.pref.getPluginsDirectory();
File pluginJar = new File(pluginDir, info.name + ".jar");
final URL pluginJarUrl = Utils.fileToURL(pluginJar);
return AccessController.doPrivileged((PrivilegedAction<ClassLoader>)
() -> new URLClassLoader(new URL[] {pluginJarUrl}, Main.class.getClassLoader()));
}
}