001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.File; 007import java.net.URL; 008import java.net.URLClassLoader; 009import java.security.AccessController; 010import java.security.PrivilegedAction; 011import java.util.List; 012 013import org.openstreetmap.josm.data.Preferences; 014import org.openstreetmap.josm.gui.MapFrame; 015import org.openstreetmap.josm.gui.MapFrameListener; 016import org.openstreetmap.josm.gui.download.DownloadSelection; 017import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 018import org.openstreetmap.josm.spi.preferences.Config; 019import org.openstreetmap.josm.spi.preferences.IBaseDirectories; 020import org.openstreetmap.josm.tools.Logging; 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 private final IBaseDirectories pluginBaseDirectories = new PluginBaseDirectories(); 056 057 private class PluginBaseDirectories implements IBaseDirectories { 058 private File preferencesDir; 059 private File cacheDir; 060 private File userdataDir; 061 062 @Override 063 public File getPreferencesDirectory(boolean createIfMissing) { 064 if (preferencesDir == null) { 065 preferencesDir = Config.getDirs().getPreferencesDirectory(createIfMissing).toPath() 066 .resolve("plugins").resolve(info.name).toFile(); 067 } 068 if (createIfMissing && !preferencesDir.exists() && !preferencesDir.mkdirs()) { 069 Logging.error(tr("Failed to create missing plugin preferences directory: {0}", preferencesDir.getAbsoluteFile())); 070 } 071 return preferencesDir; 072 } 073 074 @Override 075 public File getUserDataDirectory(boolean createIfMissing) { 076 if (userdataDir == null) { 077 userdataDir = Config.getDirs().getUserDataDirectory(createIfMissing).toPath() 078 .resolve("plugins").resolve(info.name).toFile(); 079 } 080 if (createIfMissing && !userdataDir.exists() && !userdataDir.mkdirs()) { 081 Logging.error(tr("Failed to create missing plugin user data directory: {0}", userdataDir.getAbsoluteFile())); 082 } 083 return userdataDir; 084 } 085 086 @Override 087 public File getCacheDirectory(boolean createIfMissing) { 088 if (cacheDir == null) { 089 cacheDir = Config.getDirs().getCacheDirectory(createIfMissing).toPath() 090 .resolve("plugins").resolve(info.name).toFile(); 091 } 092 if (createIfMissing && !cacheDir.exists() && !cacheDir.mkdirs()) { 093 Logging.error(tr("Failed to create missing plugin cache directory: {0}", cacheDir.getAbsoluteFile())); 094 } 095 return cacheDir; 096 } 097 } 098 099 /** 100 * Creates the plugin 101 * 102 * @param info the plugin information describing the plugin. 103 */ 104 protected Plugin(PluginInformation info) { 105 this.info = info; 106 } 107 108 /** 109 * Replies the plugin information object for this plugin 110 * 111 * @return the plugin information object 112 */ 113 public PluginInformation getPluginInformation() { 114 return info; 115 } 116 117 /** 118 * Sets the plugin information object for this plugin 119 * 120 * @param info the plugin information object 121 */ 122 public void setPluginInformation(PluginInformation info) { 123 this.info = info; 124 } 125 126 /** 127 * Get the directories where this plugin can store various files. 128 * @return the directories where this plugin can store files 129 * @since 13007 130 */ 131 public IBaseDirectories getPluginDirs() { 132 return pluginBaseDirectories; 133 } 134 135 @Override 136 public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {} 137 138 /** 139 * Called in the preferences dialog to create a preferences page for the plugin, 140 * if any available. 141 * @return the preferences dialog, or {@code null} 142 */ 143 public PreferenceSetting getPreferenceSetting() { 144 return null; 145 } 146 147 /** 148 * Called in the download dialog to give the plugin a chance to modify the list 149 * of bounding box selectors. 150 * @param list list of bounding box selectors 151 */ 152 public void addDownloadSelection(List<DownloadSelection> list) {} 153 154 /** 155 * Get a class loader for loading resources from the plugin jar. 156 * 157 * This can be used to avoid getting a file from another plugin that 158 * happens to have a file with the same file name and path. 159 * 160 * Usage: Instead of 161 * getClass().getResource("/resources/pluginProperties.properties"); 162 * write 163 * getPluginResourceClassLoader().getResource("resources/pluginProperties.properties"); 164 * 165 * (Note the missing leading "/".) 166 * @return a class loader for loading resources from the plugin jar 167 */ 168 public ClassLoader getPluginResourceClassLoader() { 169 File pluginDir = Preferences.main().getPluginsDirectory(); 170 File pluginJar = new File(pluginDir, info.name + ".jar"); 171 final URL pluginJarUrl = Utils.fileToURL(pluginJar); 172 return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) 173 () -> new URLClassLoader(new URL[] {pluginJarUrl}, Plugin.class.getClassLoader())); 174 } 175}