001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins; 003 004import java.io.File; 005import java.io.FileNotFoundException; 006import java.io.IOException; 007import java.io.InputStream; 008import java.net.URL; 009import java.net.URLClassLoader; 010import java.nio.file.Files; 011import java.nio.file.StandardCopyOption; 012import java.security.AccessController; 013import java.security.PrivilegedAction; 014import java.util.List; 015 016import org.openstreetmap.josm.Main; 017import org.openstreetmap.josm.gui.MapFrame; 018import org.openstreetmap.josm.gui.MapFrameListener; 019import org.openstreetmap.josm.gui.download.DownloadSelection; 020import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 021import org.openstreetmap.josm.tools.Utils; 022 023/** 024 * For all purposes of loading dynamic resources, the Plugin's class loader should be used 025 * (or else, the plugin jar will not be within the class path). 026 * 027 * A plugin may subclass this abstract base class (but it is optional). 028 * 029 * The actual implementation of this class is optional, as all functions will be called 030 * via reflection. This is to be able to change this interface without the need of 031 * recompiling or even breaking the plugins. If your class does not provide a 032 * function here (or does provide a function with a mismatching signature), it will not 033 * be called. That simple. 034 * 035 * Or in other words: See this base class as an documentation of what automatic callbacks 036 * are provided (you can register yourself to more callbacks in your plugin class 037 * constructor). 038 * 039 * Subclassing Plugin and overriding some functions makes it easy for you to keep sync 040 * with the correct actual plugin architecture of JOSM. 041 * 042 * @author Immanuel.Scholz 043 */ 044public abstract class Plugin implements MapFrameListener { 045 046 /** 047 * This is the info available for this plugin. You can access this from your 048 * constructor. 049 * 050 * (The actual implementation to request the info from a static variable 051 * is a bit hacky, but it works). 052 */ 053 private PluginInformation info; 054 055 /** 056 * Creates the plugin 057 * 058 * @param info the plugin information describing the plugin. 059 */ 060 public Plugin(PluginInformation info) { 061 this.info = info; 062 } 063 064 /** 065 * Replies the plugin information object for this plugin 066 * 067 * @return the plugin information object 068 */ 069 public PluginInformation getPluginInformation() { 070 return info; 071 } 072 073 /** 074 * Sets the plugin information object for this plugin 075 * 076 * @param info the plugin information object 077 */ 078 public void setPluginInformation(PluginInformation info) { 079 this.info = info; 080 } 081 082 /** 083 * @return The directory for the plugin to store all kind of stuff. 084 */ 085 public String getPluginDir() { 086 return new File(Main.pref.getPluginsDirectory(), info.name).getPath(); 087 } 088 089 @Override 090 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {} 091 092 /** 093 * Called in the preferences dialog to create a preferences page for the plugin, 094 * if any available. 095 * @return the preferences dialog, or {@code null} 096 */ 097 public PreferenceSetting getPreferenceSetting() { 098 return null; 099 } 100 101 /** 102 * Called in the download dialog to give the plugin a chance to modify the list 103 * of bounding box selectors. 104 * @param list list of bounding box selectors 105 */ 106 public void addDownloadSelection(List<DownloadSelection> list) {} 107 108 /** 109 * Copies the resource 'from' to the file in the plugin directory named 'to'. 110 * @param from source file 111 * @param to target file 112 * @throws FileNotFoundException if the file exists but is a directory rather than a regular file, 113 * does not exist but cannot be created, or cannot be opened for any other reason 114 * @throws IOException if any other I/O error occurs 115 */ 116 public void copy(String from, String to) throws IOException { 117 String pluginDirName = getPluginDir(); 118 File pluginDir = new File(pluginDirName); 119 if (!pluginDir.exists()) { 120 Utils.mkDirs(pluginDir); 121 } 122 try (InputStream in = getClass().getResourceAsStream(from)) { 123 if (in == null) { 124 throw new IOException("Resource not found: "+from); 125 } 126 Files.copy(in, new File(pluginDirName, to).toPath(), StandardCopyOption.REPLACE_EXISTING); 127 } 128 } 129 130 /** 131 * Get a class loader for loading resources from the plugin jar. 132 * 133 * This can be used to avoid getting a file from another plugin that 134 * happens to have a file with the same file name and path. 135 * 136 * Usage: Instead of 137 * getClass().getResource("/resources/pluginProperties.properties"); 138 * write 139 * getPluginResourceClassLoader().getResource("resources/pluginProperties.properties"); 140 * 141 * (Note the missing leading "/".) 142 * @return a class loader for loading resources from the plugin jar 143 */ 144 public ClassLoader getPluginResourceClassLoader() { 145 File pluginDir = Main.pref.getPluginsDirectory(); 146 File pluginJar = new File(pluginDir, info.name + ".jar"); 147 final URL pluginJarUrl = Utils.fileToURL(pluginJar); 148 return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) 149 () -> new URLClassLoader(new URL[] {pluginJarUrl}, Main.class.getClassLoader())); 150 } 151}