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