001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.plugin.abstract_; 020 021import java.awt.image.BufferedImage; 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.Enumeration; 028import java.util.List; 029 030import javax.swing.ImageIcon; 031 032import icy.file.FileUtil; 033import icy.gui.frame.IcyFrame; 034import icy.gui.viewer.Viewer; 035import icy.image.IcyBufferedImage; 036import icy.image.ImageUtil; 037import icy.main.Icy; 038import icy.network.NetworkUtil; 039import icy.plugin.PluginDescriptor; 040import icy.plugin.PluginLauncher; 041import icy.plugin.PluginLoader; 042import icy.plugin.interface_.PluginBundled; 043import icy.plugin.interface_.PluginThreaded; 044import icy.preferences.PluginsPreferences; 045import icy.preferences.XMLPreferences; 046import icy.resource.ResourceUtil; 047import icy.sequence.Sequence; 048import icy.system.IcyExceptionHandler; 049import icy.system.SystemUtil; 050import icy.system.audit.Audit; 051import icy.util.ClassUtil; 052 053/** 054 * Base class for Plugin, provide some helper methods.<br> 055 * By default the constructor of a Plugin class is called in the EDT (Event Dispatch Thread).<br> 056 * If the plugin implements the {@link PluginThreaded} there is no more guarantee that is the case. 057 * 058 * @author Fabrice de Chaumont & Stephane 059 */ 060public abstract class Plugin 061{ 062 public static Plugin getPlugin(List<Plugin> list, String className) 063 { 064 for (Plugin plugin : list) 065 if (plugin.getClass().getName().equals(className)) 066 return plugin; 067 068 return null; 069 } 070 071 private PluginDescriptor descriptor; 072 073 /** 074 * Default Plugin constructor.<br> 075 * The {@link PluginLauncher} is normally responsible of Plugin class instantiation. 076 */ 077 public Plugin() 078 { 079 super(); 080 081 // get descriptor from loader 082 descriptor = PluginLoader.getPlugin(getClass().getName()); 083 084 if (descriptor == null) 085 { 086 // descriptor not found (don't check for anonymous plugin class) ? 087 if (!getClass().isAnonymousClass()) 088 { 089 System.out.println( 090 "Warning : Plugin '" + getClass().getName() + "' started but not found in PluginLoader !"); 091 System.out.println("Local XML plugin description file is probably incorrect."); 092 } 093 094 // create dummy descriptor 095 descriptor = new PluginDescriptor(this.getClass()); 096 descriptor.setName(getClass().getSimpleName()); 097 } 098 099 // audit 100 Audit.pluginInstanced(this); 101 } 102 103 @Override 104 protected void finalize() throws Throwable 105 { 106 // unregister plugin (weak reference so we can do it here) 107 Icy.getMainInterface().unRegisterPlugin(this); 108 109 super.finalize(); 110 } 111 112 /** 113 * @return the descriptor 114 */ 115 public PluginDescriptor getDescriptor() 116 { 117 return descriptor; 118 } 119 120 /** 121 * @return the plugin name (from its descriptor) 122 */ 123 public String getName() 124 { 125 return descriptor.getName(); 126 } 127 128 /** 129 * @return <code>true</code> if this is a bundled plugin (see {@link PluginBundled}). 130 */ 131 public boolean isBundled() 132 { 133 return this instanceof PluginBundled; 134 } 135 136 /** 137 * @return the class name of the plugin owner.<br> 138 * If this Plugin is not bundled (see {@link PluginBundled}) then it just returns the 139 * current class name otherwise it will returns the plugin owner class name. 140 */ 141 public String getOwnerClassName() 142 { 143 if (isBundled()) 144 return ((PluginBundled) this).getMainPluginClassName(); 145 146 return getClass().getName(); 147 } 148 149 /** 150 * @return the folder where the plugin is installed (or should be installed). 151 */ 152 public String getInstallFolder() 153 { 154 return ClassUtil.getPathFromQualifiedName(ClassUtil.getPackageName(getClass().getName())); 155 } 156 157 public Viewer getActiveViewer() 158 { 159 return Icy.getMainInterface().getActiveViewer(); 160 } 161 162 public Sequence getActiveSequence() 163 { 164 return Icy.getMainInterface().getActiveSequence(); 165 } 166 167 public IcyBufferedImage getActiveImage() 168 { 169 return Icy.getMainInterface().getActiveImage(); 170 } 171 172 /** 173 * @deprecated Use {@link #getActiveViewer()} instead 174 */ 175 @Deprecated 176 public Viewer getFocusedViewer() 177 { 178 return getActiveViewer(); 179 } 180 181 /** 182 * @deprecated Use {@link #getActiveSequence()} instead 183 */ 184 @Deprecated 185 public Sequence getFocusedSequence() 186 { 187 return getActiveSequence(); 188 } 189 190 /** 191 * @deprecated Use {@link #getActiveImage()} instead 192 */ 193 @Deprecated 194 public IcyBufferedImage getFocusedImage() 195 { 196 return getActiveImage(); 197 } 198 199 public void addIcyFrame(final IcyFrame frame) 200 { 201 frame.addToDesktopPane(); 202 } 203 204 public void addSequence(final Sequence sequence) 205 { 206 Icy.getMainInterface().addSequence(sequence); 207 } 208 209 public void removeSequence(final Sequence sequence) 210 { 211 sequence.close(); 212 } 213 214 public ArrayList<Sequence> getSequences() 215 { 216 return Icy.getMainInterface().getSequences(); 217 } 218 219 /** 220 * Return the resource URL from given resource name.<br> 221 * Ex: <code>getResource("plugins/author/resources/def.xml");</code> 222 * 223 * @param name 224 * resource name 225 */ 226 public URL getResource(String name) 227 { 228 return getClass().getClassLoader().getResource(name); 229 } 230 231 /** 232 * Return resources corresponding to given resource name.<br> 233 * Ex: <code>getResources("plugins/author/resources/def.xml");</code> 234 * 235 * @param name 236 * resource name 237 * @throws IOException 238 */ 239 public Enumeration<URL> getResources(String name) throws IOException 240 { 241 return getClass().getClassLoader().getResources(name); 242 } 243 244 /** 245 * Return the resource as data stream from given resource name.<br> 246 * Ex: <code>getResourceAsStream("plugins/author/resources/def.xml");</code> 247 * 248 * @param name 249 * resource name 250 */ 251 public InputStream getResourceAsStream(String name) 252 { 253 return getClass().getClassLoader().getResourceAsStream(name); 254 } 255 256 /** 257 * Return the image resource from given resource name 258 * Ex: <code>getResourceAsStream("plugins/author/resources/image.png");</code> 259 * 260 * @param resourceName 261 * resource name 262 */ 263 public BufferedImage getImageResource(String resourceName) 264 { 265 return ImageUtil.load(getResourceAsStream(resourceName)); 266 } 267 268 /** 269 * Return the icon resource from given resource name 270 * Ex: <code>getResourceAsStream("plugins/author/resources/icon.png");</code> 271 * 272 * @param resourceName 273 * resource name 274 */ 275 public ImageIcon getIconResource(String resourceName) 276 { 277 return ResourceUtil.getImageIcon(getImageResource(resourceName)); 278 } 279 280 /** 281 * Retrieve the preferences root for this plugin.<br> 282 */ 283 public XMLPreferences getPreferencesRoot() 284 { 285 return PluginsPreferences.root(this); 286 } 287 288 /** 289 * Retrieve the plugin preferences node for specified name.<br> 290 * i.e : getPreferences("window") will return node 291 * "plugins.[authorPackage].[pluginClass].window" 292 */ 293 public XMLPreferences getPreferences(String name) 294 { 295 return getPreferencesRoot().node(name); 296 } 297 298 /** 299 * Returns the base resource path for plugin native libraries.<br/> 300 * Depending the Operating System it can returns these values: 301 * <ul> 302 * <li>lib/unix32</li> 303 * <li>lib/unix64</li> 304 * <li>lib/mac32</li> 305 * <li>lib/mac64</li> 306 * <li>lib/win32</li> 307 * <li>lib/win64</li> 308 * </ul> 309 */ 310 protected String getResourceLibraryPath() 311 { 312 return "lib" + FileUtil.separator + SystemUtil.getOSArchIdString(); 313 } 314 315 /** 316 * Load a packed native library from the JAR file.<br/> 317 * Native libraries should be packaged with the following directory & file structure: 318 * 319 * <pre> 320 * /lib/unix32 321 * libxxx.so 322 * /lib/unix64 323 * libxxx.so 324 * /lib/mac32 325 * libxxx.dylib 326 * /lib/mac64 327 * libxxx.dylib 328 * /lib/win32 329 * xxx.dll 330 * /lib/win64 331 * xxx.dll 332 * /plugins/myname/mypackage 333 * MyPlugin.class 334 * .... 335 * </pre> 336 * 337 * Here "xxx" is the name of the native library.<br/> 338 * Current approach is to unpack the native library into a temporary file and load from there. 339 * 340 * @param libName 341 * @return true if the library was correctly loaded. 342 * @see #prepareLibrary(String) 343 */ 344 public boolean loadLibrary(String libName) 345 { 346 final File file = prepareLibrary(libName); 347 348 if (file == null) 349 return false; 350 351 // and load it 352 System.load(file.getPath()); 353 354 return true; 355 } 356 357 /** 358 * Extract a packed native library from the JAR file to a temporary native library folder so it can be easily loaded 359 * later.<br/> 360 * Native libraries should be packaged with the following directory & file structure: 361 * 362 * <pre> 363 * /lib/unix32 364 * libxxx.so 365 * /lib/unix64 366 * libxxx.so 367 * /lib/mac32 368 * libxxx.dylib 369 * /lib/mac64 370 * libxxx.dylib 371 * /lib/win32 372 * xxx.dll 373 * /lib/win64 374 * xxx.dll 375 * /plugins/myname/mypackage 376 * MyPlugin.class 377 * .... 378 * </pre> 379 * 380 * Here "xxx" is the name of the native library.<br/> 381 * 382 * @param libName 383 * @return the extracted native library file. 384 * @see #loadLibrary(String) 385 */ 386 public File prepareLibrary(String libName) 387 { 388 try 389 { 390 // get mapped library name 391 String mappedlibName = System.mapLibraryName(libName); 392 // get base resource path for native library 393 final String basePath = getResourceLibraryPath() + FileUtil.separator; 394 395 // search for library in resource 396 URL libUrl = getResource(basePath + mappedlibName); 397 398 // not found ? 399 if (libUrl == null) 400 { 401 // jnilib extension may not work, try with "dylib" extension instead 402 if (mappedlibName.endsWith(".jnilib")) 403 { 404 mappedlibName = mappedlibName.substring(0, mappedlibName.length() - 7) + ".dylib"; 405 libUrl = getResource(basePath + mappedlibName); 406 } 407 // do the contrary in case we have an old "jnilib" file and system use "dylib" by default 408 else if (mappedlibName.endsWith(".dylib")) 409 { 410 mappedlibName = mappedlibName.substring(0, mappedlibName.length() - 6) + ".jnilib"; 411 libUrl = getResource(basePath + mappedlibName); 412 } 413 } 414 415 // resource not found --> error 416 if (libUrl == null) 417 throw new IOException("Couldn't find resource " + basePath + mappedlibName); 418 419 // extract resource 420 final File extractedFile = extractResource( 421 SystemUtil.getTempLibraryDirectory() + FileUtil.separator + mappedlibName, libUrl); 422 423 return extractedFile; 424 } 425 catch (IOException e) 426 { 427 System.err.println("Error while extracting packed library " + libName + ": " + e); 428 } 429 430 return null; 431 } 432 433 /** 434 * Extract a resource to the specified path 435 * 436 * @param outputPath 437 * the file to extract the resource to 438 * @param resource 439 * the resource URL 440 * @return the extracted file 441 * @throws IOException 442 */ 443 protected File extractResource(String outputPath, URL resource) throws IOException 444 { 445 // open resource stream 446 final InputStream in = resource.openStream(); 447 // create output file 448 final File result = new File(outputPath); 449 final byte data[]; 450 451 try 452 { 453 // load resource 454 data = NetworkUtil.download(in); 455 } 456 finally 457 { 458 in.close(); 459 } 460 461 // file already exist ?? 462 if (result.exists()) 463 { 464 // same size --> assume it's the same 465 if (result.length() == data.length) 466 return result; 467 468 if (!FileUtil.delete(result, false)) 469 throw new IOException("Cannot overwrite " + result + " file !"); 470 } 471 472 // save resource to file 473 FileUtil.save(result, data, true); 474 475 return result; 476 } 477 478 /** 479 * Report an error log for this plugin (reported to Icy web site which report then to the 480 * author of the plugin). 481 * 482 * @see IcyExceptionHandler#report(PluginDescriptor, String) 483 */ 484 public void report(String errorLog) 485 { 486 IcyExceptionHandler.report(descriptor, errorLog); 487 } 488 489 @Override 490 public String toString() 491 { 492 return getDescriptor().getName(); 493 } 494}