de.xirp.plugin.PluginLoader.java Source code

Java tutorial

Introduction

Here is the source code for de.xirp.plugin.PluginLoader.java

Source

/** 
 * ============================================================================
 * Xirp 2: eXtendable interface for robotic purposes.
 * ============================================================================
 * 
 * Copyright (C) 2005-2007, by Authors and Contributors listed in CREDITS.txt
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at:
 *
 *             http://www.opensource.org/licenses/cpl1.0.php
 *
 * ----------------------------
 * PluginLoader.java
 * ----------------------------
 *
 * Original Author:  Rabea Gransberger [rgransberger AT web.de]
 *                 Matthias Gernand [matthias.gernand AT gmx.de]
 * Contributor(s):   
 *
 * Changes
 * -------
 * 06.02.2006:      Created by Matthias Gernand.
 */
package de.xirp.plugin;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.log4j.Logger;

import de.xirp.profile.Robot;
import de.xirp.util.Constants;
import de.xirp.util.I18n;
import de.xirp.util.collections.MultiValueHashMap;

/**
 * Class which loads plugins.<br>
 * Plugins have to implement the {@link IPlugable} interface and have
 * to define a <code>plugin.properties</code> file to specify the
 * fully qualified path to the plugins main class.<br>
 * Plugins have to be placed in the plugins folder of the application
 * in order to be found by this loader.
 * 
 * @author Matthias Gernand
 * @author Rabea Gransberger
 */
public final class PluginLoader {

    /**
     * Log4j Logger for this class
     */
    private static Logger logClass = Logger.getLogger(PluginLoader.class);
    /**
     * List of loaded plugins identified by their name
     */
    private static HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();
    /**
     * A list of instances for all plugins found by this plugin loader
     */
    @SuppressWarnings("unchecked")
    private static List<IPlugable> instances;

    /**
     * A map of references to a given plugin<br/> main class to
     * plugin -> main class from plugin. The map is cleared after the
     * loading process.
     */
    private static MultiValueHashMap<String, String> refs = new MultiValueHashMap<String, String>();
    /**
     * List of plugins which full fill all needs. List is cleared
     * after the loading process.
     */
    private static List<String> currentPluginList;

    /**
     * Looks for plugins in the plugins directory. Plugins which needs
     * are not full filled are not accepted.
     * 
     * @param manager
     *            instance of the plugin manager which is used for
     *            notifying the application about the loading
     *            progress.
     */
    protected static void searchPlugins(PluginManager manager) {
        logClass.info(I18n.getString("PluginLoader.log.searchPlugins")); //$NON-NLS-1$
        // Get all Files in the Plugin Directory with
        // Filetype jar
        File pluginDir = new File(Constants.PLUGIN_DIR);
        File[] fileList = pluginDir.listFiles(new FilenameFilter() {

            public boolean accept(@SuppressWarnings("unused") File dir, String filename) {
                return filename.endsWith(".jar"); //$NON-NLS-1$
            }

        });

        if (fileList != null) {
            double perFile = 1.0 / fileList.length;
            double cnt = 0;
            try {
                // Iterate over all jars and try to find
                // the plugin.properties file
                // The file is loaded and Information
                // extracted to PluginInfo
                // Plugin is added to List of Plugins
                for (File f : fileList) {
                    String path = f.getAbsolutePath();
                    if (!plugins.containsKey(path)) {
                        manager.throwLoaderProgressEvent(
                                I18n.getString("PluginLoader.progress.searchingInFile", f.getName()), cnt); //$NON-NLS-1$
                        JarFile jar = new JarFile(path);
                        JarEntry entry = jar.getJarEntry("plugin.properties"); //$NON-NLS-1$
                        if (entry != null) {
                            InputStream input = jar.getInputStream(entry);
                            Properties props = new Properties();
                            props.load(input);
                            String mainClass = props.getProperty("plugin.mainclass"); //$NON-NLS-1$
                            PluginInfo info = new PluginInfo(path, mainClass, props);
                            String packageName = ClassUtils.getPackageName(mainClass) + "." //$NON-NLS-1$
                                    + AbstractPlugin.DEFAULT_BUNDLE_NAME;
                            String bundleBaseName = packageName.replaceAll("\\.", "/"); //$NON-NLS-1$  //$NON-NLS-2$
                            for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
                                JarEntry ent = entries.nextElement();
                                String name = ent.getName();
                                if (name.indexOf(bundleBaseName) != -1) {
                                    String locale = name
                                            .substring(name.indexOf(AbstractPlugin.DEFAULT_BUNDLE_NAME));
                                    locale = locale.replaceAll(AbstractPlugin.DEFAULT_BUNDLE_NAME + "_", //$NON-NLS-1$
                                            ""); //$NON-NLS-1$
                                    locale = locale.substring(0, locale.indexOf(".properties")); //$NON-NLS-1$
                                    Locale loc = new Locale(locale);
                                    info.addAvailableLocale(loc);
                                }
                            }
                            PluginManager.extractAll(info);
                            if (isPlugin(info)) {
                                plugins.put(mainClass, info);
                            }
                        }
                    }
                    cnt += perFile;

                }
            } catch (IOException e) {
                logClass.error(
                        I18n.getString("PluginLoader.log.errorSearchingforPlugins") + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
            }
        }
        checkAllNeeds();
        logClass.info(I18n.getString("PluginLoader.log.searchPluginsCompleted") + Constants.LINE_SEPARATOR); //$NON-NLS-1$
    }

    /**
     * Checks the needs of all plugins.
     * 
     * @see IPlugable#requiredLibs()
     */
    @SuppressWarnings("unchecked")
    private static void checkAllNeeds() {
        HashMap<String, IPlugable> plugables = new HashMap<String, IPlugable>();
        MultiValueHashMap<String, String> refs = new MultiValueHashMap<String, String>();

        // list of all plugins
        List<String> fullPluginList = new ArrayList<String>(plugins.size());
        for (PluginInfo info : plugins.values()) {
            fullPluginList.add(info.getMainClass());
        }

        ClassLoader loader = logClass.getClass().getClassLoader();

        // Read the list of available jars from the class path
        String cp = ManagementFactory.getRuntimeMXBean().getClassPath();
        // String cp = System.getProperty("java.class.path");
        // //$NON-NLS-1$
        String[] jars = cp.split(File.pathSeparator);
        List<String> jarList = new ArrayList<String>(jars.length);
        for (String jar : jars) {
            jarList.add(FilenameUtils.getName(jar));
        }
        // The initial list of current plugins equals the full list
        // every plugin which does not full fill the needs
        // it removed from this list
        currentPluginList = new ArrayList<String>(fullPluginList);
        for (PluginInfo info : plugins.values()) {
            try {
                SecurePluginView view = PluginManager.getInstance(info, Robot.NAME_NONE);
                plugables.put(info.getMainClass(), view);
                boolean check = checkNeeds(view, loader, jarList);
                if (!check) {
                    // remove plugins which reference this plugin
                    removeRefs(info.getMainClass());
                }
            } catch (Exception e) {
                logClass.trace(e, e);
            }
        }
        // Remove all plugins of the full list
        // which are no more contained in the current list
        for (String clazz : fullPluginList) {
            if (!currentPluginList.contains(clazz)) {
                plugins.remove(clazz);
                plugables.remove(clazz);
            }
        }
        instances = new ArrayList<IPlugable>(plugables.values());
        refs.clear();
        refs = null;
        currentPluginList.clear();
        currentPluginList = null;
        fullPluginList.clear();
        fullPluginList = null;
    }

    /**
     * Removes plugins from the current list which reference the given
     * plugin
     * 
     * @param mainClass
     *            the main class of the plugin to remove the
     *            references for
     */
    private static void removeRefs(String mainClass) {
        currentPluginList.remove(mainClass);
        // remove plugins which reference this plugin
        for (String otherClass : refs.get(mainClass)) {
            removeRefs(otherClass);
            logClass.info(
                    I18n.getString("PluginLoader.log.removingPluginBecauseOfDependentPlugin", otherClass, mainClass) //$NON-NLS-1$
                            + Constants.LINE_SEPARATOR);
        }
    }

    /**
     * Checks if the given plugin needs other plugins to run.
     * 
     * @param plugin
     *            plugin to check
     * @param jarList
     *            list of jar files on the class path
     * @param loader
     *            the current class loader used to check if a class is
     *            available
     * @return <code>true</code> if all other Plugins needed by this
     *         Plugin are available
     * @see IPlugable#requiredLibs()
     */
    @SuppressWarnings("unchecked")
    private static boolean checkNeeds(IPlugable plugin, ClassLoader loader, List<String> jarList) {
        if (plugin == null) {
            return true;
        }

        List<String> req = plugin.requiredLibs();
        if (req == null) {
            return true;
        }
        boolean ret = true;
        for (String claas : req) {
            if (claas.endsWith(".jar")) { //$NON-NLS-1$
                if (!jarList.contains(claas)) {
                    ret &= false;
                    logClass.warn(I18n.getString("PluginLoader.log.removingPluginBecauseOfMissingLib", //$NON-NLS-1$
                            plugin.getName(), claas) + Constants.LINE_SEPARATOR);
                }
            } else if (!currentPluginList.contains(claas)) {
                boolean hasClass = true;
                try {
                    loader.loadClass(claas);
                } catch (ClassNotFoundException e) {
                    logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
                    hasClass = false;
                }

                if (!hasClass) {
                    logClass.warn(I18n.getString("PluginLoader.log.removingPluginBecauseOfMissingLib", //$NON-NLS-1$
                            plugin.getName(), claas) + Constants.LINE_SEPARATOR);
                    ret &= false;
                    break;
                }
            } else {
                refs.put(claas, plugin.getInfo().getMainClass());
            }
        }
        return ret;
    }

    /**
     * Checks if a plugin for the given main class does exist
     * 
     * @param mainClass
     *            the main class to look for
     * @return <code>true</code> if a plugin for this main class
     *         does exist
     */
    protected static boolean exists(String mainClass) {
        return plugins.containsKey(mainClass);
    }

    /**
     * Gets the information about the specified plugin
     * 
     * @param mainClass
     *            fully qualified path to the plugins main class
     * @return information about the plugin or <code>null</code> if
     *         plugin does not exist
     */
    protected static PluginInfo getInfo(String mainClass) {
        return plugins.get(mainClass);
    }

    /**
     * Gets information about all the found plugins
     * 
     * @return Information about found plugins
     * @see #searchPlugins(PluginManager)
     */
    protected static Collection<PluginInfo> getPlugins() {
        return Collections.unmodifiableCollection(plugins.values());
    }

    /**
     * Gets an instance of every plugin which was loaded and accepted
     * by this plugin loader
     * 
     * @return unmodifiable list of plugin instances
     */
    @SuppressWarnings("unchecked")
    protected static List<IPlugable> getInstances() {
        return Collections.unmodifiableList(instances);
    }

    /**
     * Tests if the given class implements <code>IPlugable</code>
     * and therefore is a plugin.
     * 
     * @param info
     *            information about the plugin
     * @return <code>true</code> if this class is a plugin,
     *         <code>false</code> otherwise
     */
    @SuppressWarnings("unchecked")
    private static boolean isPlugin(PluginInfo info) {
        try {
            URLClassLoader classLoader = PluginManager.getClassLoader(info);
            Class<?> test = Class.forName(info.getMainClass(), true, classLoader);
            boolean isPlugin = IPlugable.class.isAssignableFrom(test);

            return isPlugin;
        } catch (ClassNotFoundException e) {
            logClass.error(I18n.getString("PluginLoader.log.errorSearchingforPlugins", info.getAbsoluteJarPath()) //$NON-NLS-1$
                    + Constants.LINE_SEPARATOR, e);
        } catch (NoClassDefFoundError e) {
            logClass.error(I18n.getString("PluginLoader.log.errorSearchingforPlugins", info.getAbsoluteJarPath()) //$NON-NLS-1$
                    + Constants.LINE_SEPARATOR, e);
        }
        return false;
    }
}