Java tutorial
/* * TV-Browser * Copyright (C) 04-2003 Martin Oberhauser (martin@tvbrowser.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * CVS information: * $RCSfile$ * $Source$ * $Date: 2010-08-24 00:22:31 +0200 (Tue, 24 Aug 2010) $ * $Author: ds10 $ * $Revision: 6712 $ */ package tvbrowser.core; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import org.apache.commons.lang.StringUtils; import tvbrowser.core.plugin.AbstractPluginProxy; import tvbrowser.core.plugin.BeanShellPluginProxy; import tvbrowser.core.plugin.JavaPluginProxy; import tvbrowser.core.plugin.PluginBaseInfo; import tvbrowser.core.plugin.PluginProxy; import tvbrowser.core.plugin.PluginProxyManager; import tvbrowser.core.tvdataservice.DefaultTvDataServiceProxy; import tvbrowser.core.tvdataservice.TvDataServiceProxy; import tvbrowser.core.tvdataservice.TvDataServiceProxyManager; import util.exc.ErrorHandler; import util.exc.TvBrowserException; import util.io.IOUtilities; import devplugin.Plugin; import devplugin.PluginInfo; import devplugin.Version; /** * The PluginLoader loads all plugins and assigns each plugin to * the appropriate manager (TvDataServiceProxyManager or PluginProxyManager) */ public class PluginLoader { private static final String[] IGNORED_PLUGINS = new String[] { "FavoritesPlugin.jar", "ReminderPlugin.jar", "ProgramInfo.jar", "SearchPlugin.jar", "ShowviewPlugin.jar" }; private static final String PLUGIN_PROXY_EXTENSION = ".jar.proxy"; private static final String PLUGIN_INSTALL_EXTENSION = ".inst"; /** The logger for this class */ private static final Logger mLog = Logger.getLogger(PluginLoader.class.getName()); private static PluginLoader mInstance; /** The name of the directory where the plugins are located in TV-Browser 2.1 and later */ private static String PLUGIN_DIRECTORY = "plugins"; private HashSet<String> mSuccessfullyLoadedPluginFiles; private HashMap<Object, File> mDeleteablePlugin; private ArrayList<PluginProxy> loadedProxies; private ArrayList<String> mNewInstalledPlugins = new ArrayList<String>(); private PluginLoader() { mSuccessfullyLoadedPluginFiles = new HashSet<String>(); mDeleteablePlugin = new HashMap<Object, File>(); } public static PluginLoader getInstance() { if (mInstance == null) { mInstance = new PluginLoader(); } return mInstance; } /** * Installs all plugins that could not be installed the last time, because an * old version was in use. */ public void installPendingPlugins() { File[] installableFiles = new File(Settings.propPluginsDirectory.getString()) .listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String fileName) { return fileName.endsWith(PLUGIN_INSTALL_EXTENSION); } }); if (installableFiles == null || installableFiles.length == 0) { // Nothing to do return; } // Install all pending plugins for (File file : installableFiles) { // This plugin wants to be installed String fileName = file.getAbsolutePath(); String oldFileName = fileName.substring(0, fileName.length() - PLUGIN_INSTALL_EXTENSION.length()); File oldFile = new File(oldFileName); // delete the old proxy, this will force loading of the new plugin (even if it's not active) String oldProxyName = getProxyFileName(oldFile); File oldProxy = new File(oldProxyName); if (oldProxy.exists()) { deletePluginProxy(oldProxy); } String oldIconName = getProxyIconFileName(oldFile); File oldIcon = new File(oldIconName); if (oldIcon.exists()) { deletePluginProxy(oldIcon); } // Delete the old file deletePluginProxy(oldFile); // Rename the file, so the PluginLoader will install it later if (!file.renameTo(oldFile)) { mLog.warning("Installing pending plugin failed: " + fileName); } mNewInstalledPlugins.add(oldFileName); } } private PluginProxy loadProxy(File proxyFile) { String lcFileName = proxyFile.getName().toLowerCase(); if (!lcFileName.endsWith(".proxy")) { mLog.warning("not a valid proxy file " + proxyFile.getAbsolutePath()); return null; } if (proxyFile.canRead()) { JavaPluginProxy proxy = readPluginProxy(proxyFile); if (proxy != null) { PluginProxyManager.getInstance().registerPlugin(proxy); if (new File(proxy.getPluginFileName()).getParentFile() .equals(new File(Settings.propPluginsDirectory.getString()))) { File pluginFile = new File(proxy.getPluginFileName()); mDeleteablePlugin.put(proxy, pluginFile); } return proxy; } } return null; } /** * Loads the plugin from the file system * @param pluginFile File to load * @param deleteable is the Plugin deleteable */ public Object loadPlugin(File pluginFile, boolean deleteable) { Object plugin = null; String lcFileName = pluginFile.getName().toLowerCase(); if (mSuccessfullyLoadedPluginFiles.contains(lcFileName)) { mLog.warning("cannot load plugin " + pluginFile.getAbsolutePath() + " - already loaded"); return null; } try { if (lcFileName.endsWith(".jar")) { plugin = loadJavaPlugin(pluginFile); } else if (lcFileName.endsWith(".bsh")) { plugin = loadBeanShellPlugin(pluginFile); } else { mLog.warning("Unknown plugin type: " + pluginFile.getAbsolutePath()); } if (plugin instanceof Plugin) { // check if the proxy is already loaded, but the plugin was not loaded yet JavaPluginProxy javaplugin = (JavaPluginProxy) PluginProxyManager.getInstance() .getPluginForId(JavaPluginProxy.getJavaPluginId((Plugin) plugin)); if (javaplugin != null) { javaplugin.setPlugin((Plugin) plugin, pluginFile.getPath()); } // it was not yet loaded, so create new proxy else { javaplugin = new JavaPluginProxy((Plugin) plugin, pluginFile.getPath()); PluginProxyManager.getInstance().registerPlugin(javaplugin); } if (mNewInstalledPlugins.contains(pluginFile.getAbsolutePath())) { // add this plugin to the icon settings for program panels String iconText = ((Plugin) plugin).getProgramTableIconText(); if (iconText != null && !iconText.isEmpty()) { if (!Settings.propProgramTableIconPlugins.containsItem(((Plugin) plugin).getId())) { Settings.propProgramTableIconPlugins.addItem(((Plugin) plugin).getId()); } } } if (deleteable) { mDeleteablePlugin.put(javaplugin, pluginFile); } saveProxyInfo(pluginFile, javaplugin); } else if (plugin instanceof AbstractPluginProxy) { PluginProxyManager.getInstance().registerPlugin((AbstractPluginProxy) plugin); if (deleteable) { mDeleteablePlugin.put(plugin, pluginFile); } } else if (plugin instanceof devplugin.AbstractTvDataService) { TvDataServiceProxy proxy = new DefaultTvDataServiceProxy((devplugin.AbstractTvDataService) plugin); TvDataServiceProxyManager.getInstance().registerTvDataService(proxy); if (deleteable) { mDeleteablePlugin.put(proxy, pluginFile); } } if (plugin != null) { mSuccessfullyLoadedPluginFiles.add(lcFileName); mLog.info("Loaded plugin " + pluginFile.getAbsolutePath()); } } catch (Throwable thr) { mLog.log(Level.WARNING, "Loading plugin file failed: " + pluginFile.getAbsolutePath(), thr); thr.printStackTrace(); } return plugin; } /** * read the contents of a proxy file to get the necessary * information about the plugin managed by this proxy to recreate * the proxy without the plugin actually being loaded * * @param proxyFile * @return pluginProxy */ private JavaPluginProxy readPluginProxy(File proxyFile) { DataInputStream in = null; try { in = new DataInputStream(new BufferedInputStream(new FileInputStream(proxyFile))); String name = in.readUTF(); String author = in.readUTF(); String description = in.readUTF(); String license = in.readUTF(); DummyPlugin.setCurrentVersion(new Version(in)); String pluginId = in.readUTF(); in.readLong(); // file size is unused String lcFileName = in.readUTF(); in.close(); // check existence of plugin file File pluginFile = new File(lcFileName); if (!pluginFile.canRead()) { deletePluginProxy(proxyFile); return null; } // everything seems fine, create plugin proxy and plugin info PluginInfo info = new PluginInfo(DummyPlugin.class, name, description, author, license); // now get icon String iconFileName = getProxyIconFileName(proxyFile); return new JavaPluginProxy(info, lcFileName, pluginId, iconFileName); } catch (Exception e) { if (in != null) { try { in.close(); } catch (IOException e1) { // ignore } } // delete proxy on read error, maybe the format has changed deletePluginProxy(proxyFile); return null; } } private void deletePluginProxy(File proxyFile) { String iconName = getProxyIconFileName(proxyFile); proxyFile.delete(); File iconFile = new File(iconName); iconFile.delete(); } /** * Saves the information of a plugin to disk, so it does not need to be loaded next time * @param pluginFile full plugin file name * @param proxy proxy of the plugin */ private void saveProxyInfo(File pluginFile, JavaPluginProxy proxy) { try { String proxyFileName = getProxyFileName(pluginFile); DataOutputStream out = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(proxyFileName))); PluginInfo info = proxy.getInfo(); out.writeUTF(info.getName()); out.writeUTF(info.getAuthor()); out.writeUTF(info.getDescription()); String license = info.getLicense(); if (license == null) { license = ""; } out.writeUTF(license); info.getVersion().writeData(out); //write version out.writeUTF(proxy.getId()); out.writeLong(pluginFile.length()); out.writeUTF(proxy.getPluginFileName()); out.close(); // also store the plugin icon, if it is not yet available String iconFileName = getProxyIconFileName(pluginFile); File iconFile = new File(iconFileName); if (!iconFile.exists()) { Icon pluginIcon = proxy.getPluginIcon(); if (pluginIcon != null && pluginIcon instanceof ImageIcon) { IOUtilities.writeImageIconToFile((ImageIcon) pluginIcon, "png", iconFile); } } } catch (Exception e) { } } /** * get the file name of the proxied icon for a plugin * @param pluginFile * @return icon file name */ private String getProxyIconFileName(File pluginFile) { String name = pluginFile.getName(); name = StringUtils.substringBefore(name, "."); return Settings.getUserSettingsDirName() + File.separatorChar + name + ".icon.png"; } /** * file name of the proxy file for a plugin * * @param pluginFile * @return proxy file name */ private String getProxyFileName(File pluginFile) { return Settings.getUserSettingsDirName() + File.separatorChar + pluginFile.getName() + ".proxy"; } /** * Loads all plugins within the specified folder * @param folder specific Folder * @param deleteable True if the Plugins in this Folder are deleteable */ private void loadPlugins(File folder, boolean deleteable) { // check for plugin proxies only one time per run if (loadedProxies == null) { loadedProxies = new ArrayList<PluginProxy>(); final String[] deactivatedPluginArr = Settings.propDeactivatedPlugins.getStringArray(); // only check proxies if at least one plugin is not active if (deactivatedPluginArr != null && deactivatedPluginArr.length > 0) { File settingsDir = new File(Settings.getUserSettingsDirName()); File[] proxyFiles = settingsDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { if (!name.endsWith(PLUGIN_PROXY_EXTENSION)) { return false; } String mainName = name.substring(0, name.length() - PLUGIN_PROXY_EXTENSION.length()) .toLowerCase(); for (String deactivatedId : deactivatedPluginArr) { if (deactivatedId.contains(mainName)) { return true; } } return false; } }); if (proxyFiles != null) { for (File proxyFile : proxyFiles) { PluginProxy proxy = loadProxy(proxyFile); if (proxy != null) { loadedProxies.add(proxy); mLog.info("Loaded plugin proxy " + proxyFile); } else { mLog.warning("Failed loading plugin proxy " + proxyFile); } } } } } File[] fileArr = folder.listFiles(new FilenameFilter() { public boolean accept(File dir, String fileName) { for (String ignored : IGNORED_PLUGINS) { if (fileName.equalsIgnoreCase(ignored)) { return false; } } return true; } }); if (fileArr == null) { return; } // remove old cleverepg service, if new one is found File oldService = null; ArrayList<File> files = new ArrayList<File>(Arrays.asList(fileArr)); for (File file : fileArr) { if (file.getName().equalsIgnoreCase("CleverEPGDataService3.jar")) { for (File file2 : files) { if (file2.getName().equalsIgnoreCase("CleverEPGDataService.jar")) { oldService = file2; break; } } } } if (oldService != null) { files.remove(oldService); } for (File file : files) { boolean load = true; for (PluginProxy proxy : loadedProxies) { if (proxy.getPluginFileName().equalsIgnoreCase(file.getPath())) { load = false; break; } } if (load) { loadPlugin(file, deleteable); } } } public void loadAllPlugins() { /* 0) delete all plugins the user doesn't want anymore */ String[] files = Settings.propDeleteFilesAtStart.getStringArray(); if ((files != null) && (files.length > 0)) { for (String file : files) { try { mLog.info("Deleting " + file); new File(file).delete(); } catch (Exception e) { e.printStackTrace(); } } Settings.propDeleteFilesAtStart.setStringArray(new String[0]); } /* 1) load all plugins from the plugins folder in the user's home directory */ String pluginsFolderName = Settings.propPluginsDirectory.getString(); File f = new File(pluginsFolderName); boolean success = true; if (!f.exists()) { if (!f.mkdirs()) { mLog.warning("Could not create plugins folder " + f.getAbsolutePath()); success = false; } } if (success) { loadPlugins(f, true); } /* 2) load the plugins from the plugins folder */ loadPlugins(new File(PluginProxyManager.PLUGIN_DIRECTORY), false); /* 3) load the plugins from the tvdataservice folder */ loadPlugins(new File(TvDataServiceProxyManager.PLUGIN_DIRECTORY), false); } private Object loadJavaPlugin(File jarFile) throws TvBrowserException { Object plugin = null; // Create a class loader for the plugin ClassLoader classLoader; ClassLoader classLoader2 = null; try { URL[] urls = new URL[] { jarFile.toURI().toURL() }; classLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader()); try { if (!new File(PLUGIN_DIRECTORY).equals(jarFile.getParentFile()) && new File(PLUGIN_DIRECTORY, jarFile.getName()).isFile()) { urls = new URL[] { new File(PLUGIN_DIRECTORY, jarFile.getName()).toURI().toURL() }; classLoader2 = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader()); } } catch (MalformedURLException exc) { } } catch (MalformedURLException exc) { throw new TvBrowserException(getClass(), "error.1", "Loading Jar file of a plugin failed: {0}.", jarFile.getAbsolutePath(), exc); } // Get the plugin name String pluginName = jarFile.getName(); if (pluginName.endsWith(".jar")) { pluginName = pluginName.substring(0, pluginName.length() - 4); } boolean isBlockedDataService = false; // Create a plugin instance try { Class pluginClass = classLoader.loadClass(pluginName.toLowerCase() + "." + pluginName); Method getVersion = pluginClass.getMethod("getVersion", new Class[0]); Version version1 = null; try { version1 = (Version) getVersion.invoke(pluginClass, new Object[0]); } catch (Exception e) { } if (version1 == null || version1.toString().equals("0.0.0.0")) { mLog.warning("Did not load plugin " + pluginName + ", version is too old."); return null; } if (pluginClass.getSuperclass().equals(devplugin.AbstractTvDataService.class) || classLoader2 != null) { getVersion = pluginClass.getMethod("getVersion", new Class[0]); version1 = (Version) getVersion.invoke(pluginClass, new Object[0]); if (pluginClass.getSuperclass().equals(devplugin.AbstractTvDataService.class)) { isBlockedDataService = Settings.propBlockedPluginArray .isBlocked(pluginName.toLowerCase() + "." + pluginName, version1); } } if (classLoader2 != null) { try { Class pluginClass2 = classLoader2.loadClass(pluginName.toLowerCase() + "." + pluginName); Method getVersion2 = pluginClass2.getMethod("getVersion", new Class[0]); Version version2 = (Version) getVersion2.invoke(pluginClass2, new Object[0]); if (version2.compareTo(version1) > 0) { return null; } } catch (Throwable t) { } } try { Method preInstancing = pluginClass.getMethod("preInstancing", new Class[0]); preInstancing.invoke(pluginClass, new Object[0]); } catch (Throwable ti) { } if (!isBlockedDataService) { plugin = pluginClass.newInstance(); } } catch (Throwable thr) { throw new TvBrowserException(getClass(), "error.2", "Could not load plugin {0}.", jarFile.getAbsolutePath(), thr); } return plugin; } private Object loadBeanShellPlugin(File file) { return new BeanShellPluginProxy(file); } /** * Delete a Plugin * @param proxy Proxy for the Plugin that should be deleted * @return true if successful */ public boolean deletePlugin(PluginProxy proxy) { if (deletePluginOrService(proxy)) { try { PluginProxyManager.getInstance().removePlugin(proxy); } catch (TvBrowserException exc) { ErrorHandler.handle(exc); return false; } return true; } return false; } /** * Delete a data service * @param service Data service that should be deleted * @return true if successful * @since 2.7 */ public boolean deleteDataService(TvDataServiceProxy service) { return deletePluginOrService(service); } private boolean deletePluginOrService(Object plugin) { // mark plugin file for deletion File file = mDeleteablePlugin.get(plugin); if (file != null) { Settings.propDeleteFilesAtStart.addItem(file.toString()); // mark proxy file for deletion String proxyFile = getProxyFileName(file); Settings.propDeleteFilesAtStart.addItem(proxyFile); mDeleteablePlugin.remove(plugin); return true; } return false; } /** * Is a Plugin deleteable ? * @param plugin Plugin that should be deleted * @return true if deleteable */ public boolean isPluginDeletable(PluginProxy plugin) { return mDeleteablePlugin.containsKey(plugin); } /** * Is a data service deleteable ? * @param service Data service that should be deleted * @return true if deleteable * @since 2.7 */ public boolean isDataServiceDeletable(TvDataServiceProxy service) { return mDeleteablePlugin.containsKey(service); } /** * delete all plugin proxies to force re-reading the plugin classes on next start */ public void deleteAllPluginProxies() { File settingsDir = new File(Settings.getUserSettingsDirName()); File[] proxyFiles = settingsDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(PLUGIN_PROXY_EXTENSION); } }); if (proxyFiles != null) { for (File proxyFile : proxyFiles) { Settings.propDeleteFilesAtStart.addItem(proxyFile.toString()); } } } /** * Gets the base infos for all available plugins. * <p> * @return The array with the base info for all available plugins. */ public PluginBaseInfo[] getInfoOfAvailablePlugins() { ArrayList<PluginBaseInfo> availablePlugins = new ArrayList<PluginBaseInfo>(); /* 1) get base info for plugins in user dir */ getBaseInfoOfPluginsInDirectory(new File(Settings.propPluginsDirectory.getString()), availablePlugins); /* 2) get base info for plugins in the plugins folder */ getBaseInfoOfPluginsInDirectory(new File(PluginProxyManager.PLUGIN_DIRECTORY), availablePlugins); /* 3) get base info for in the tvdataservice folder */ getBaseInfoOfPluginsInDirectory(new File(TvDataServiceProxyManager.PLUGIN_DIRECTORY), availablePlugins); return availablePlugins.toArray(new PluginBaseInfo[availablePlugins.size()]); } private void getBaseInfoOfPluginsInDirectory(File directory, ArrayList<PluginBaseInfo> availablePlugins) { if (directory.exists()) { File[] pluginFiles = directory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { for (String ignored : IGNORED_PLUGINS) { if (name.equalsIgnoreCase(ignored)) { return false; } } return name.toLowerCase().endsWith(".jar"); } }); getBaseInfoOfPlugins(pluginFiles, availablePlugins); } } private void getBaseInfoOfPlugins(File[] plugins, ArrayList<PluginBaseInfo> availablePlugins) { for (File plugin : plugins) { URL[] urls; // Get the plugin name String pluginName = plugin.getName(); pluginName = pluginName.substring(0, pluginName.length() - 4); Class pluginClass = null; try { urls = new URL[] { plugin.toURI().toURL() }; ClassLoader classLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader()); pluginClass = classLoader.loadClass(pluginName.toLowerCase() + "." + pluginName); Method getVersion = pluginClass.getMethod("getVersion", new Class[0]); try { Version version = (Version) getVersion.invoke(pluginClass, new Object[0]); PluginBaseInfo baseInfo = new PluginBaseInfo("java." + pluginClass.getName(), version); if (!availablePlugins.contains(baseInfo)) { availablePlugins.add(baseInfo); } } catch (Exception e) { } } catch (Throwable t) { urls = null; pluginClass = null; System.gc(); PluginBaseInfo baseInfo = new PluginBaseInfo("java." + pluginName.toLowerCase() + "." + pluginName, new Version(0, 0)); if (!availablePlugins.contains(baseInfo)) { availablePlugins.add(baseInfo); } mLog.info("Could not load base info for plugin file '" + plugin.getAbsolutePath() + "'. Use default version instead."); } } } }