package apes.lib;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import apes.interfaces.AudioFormatPlugin;
import apes.interfaces.TransformPlugin;
/**
* Class for loading/unloading plugins.
*
* @author Johan Ã…hlander (johan.ahlander@gmail.com)
*/
public class PluginHandler
{
/**
* Arraylist of plugins.
*/
private ArrayList<PluginInfo> plugins;
/**
* Custom classloader.
*/
private PluginLoader cl;
/**
* Constructor.
*
* @param path The path to load plugins from.
*/
public PluginHandler(String path)
{
plugins = new ArrayList<PluginInfo>();
cl = new PluginLoader();
addPluginsInPath(path);
}
/**
* Returns an ArrayList of the plugin names.
*
* @return The plugin names.
*/
public ArrayList<String> getPluginNames()
{
ArrayList<String> list = new ArrayList<String>();
for(PluginInfo p : plugins)
{
list.add(p.getName());
}
return list;
}
/**
* Returns the description of a plugin.
*
* @param name Name of the plugin.
* @return The description.
*/
public String getDescription(String name, String language)
{
for(PluginInfo p : plugins)
{
if(p.getName().equals(name))
{
return p.getDescription(language);
}
}
return null;
}
/**
* Returns true if the plugin is loaded.
*
* @param name The name of the plugin.
*/
public Boolean isLoaded(String name)
{
for(PluginInfo p : plugins)
{
if(p.getName().equals(name))
{
return p.isLoaded();
}
}
return false;
}
/**
* Returns an ArrayList of all the transform classes.
*
* @return ArrayList of TransformPlugins.
*/
public ArrayList<TransformPlugin> getTransforms()
{
ArrayList<TransformPlugin> list = new ArrayList<TransformPlugin>();
for(PluginInfo p : plugins)
{
if(p.getType().equals("transform") && p.isLoaded())
{
list.add(p.getTransformObject());
}
}
return list;
}
/**
* Returns an ArrayList of all the format classes.
*
* @return ArrayList of AudioFormatPlugins.
*/
public ArrayList<AudioFormatPlugin> getFormats()
{
ArrayList<AudioFormatPlugin> list = new ArrayList<AudioFormatPlugin>();
for(PluginInfo p : plugins)
{
if(p.getType().equals("format") && p.isLoaded())
{
list.add(p.getAudioFormatObject());
}
}
return list;
}
/**
* Given a path pointing to a directory or a file, will try to add all plugins
* in the directory or the plugin the path was pointing to.
*
* @param str A path pointing to either a file or a directory.
*/
public void addPlugin(String str)
{
File file = new File(str);
if(file.isDirectory())
{
addPluginsInPath(file.getPath());
}
else if(file.isFile())
{
PluginInfo pi = new PluginInfo();
loadFile(file.getPath(), file.getName().substring(0, file.getName().indexOf(".")), pi);
}
}
/**
* Returns a TransformPlugin object.
*
* @param name The name of the plugin.
* @return The TransformPlugin object.
*/
public TransformPlugin getTransform(String name)
{
for(int i = 0; i < plugins.size(); i++)
{
if(plugins.get(i).getName().equals(name) && plugins.get(i).getType().equals("transform"))
{
return plugins.get(i).getTransformObject();
}
}
return null;
}
/**
* Returns a AudioFormatPlugin object.
*
* @param name The name of the plugin.
* @return The AudioFormatPlugin object.
*/
public AudioFormatPlugin getAudioFormat(String name)
{
for(int i = 0; i < plugins.size(); i++)
{
if(plugins.get(i).getName().equals(name) && plugins.get(i).getType().equals("format"))
{
return plugins.get(i).getAudioFormatObject();
}
}
return null;
}
/**
* Unloads a plugin.
*
* @param name The name of the plugin.
*/
public void unloadPlugin(String name)
{
for(int i = 0; i < plugins.size(); i++)
{
if(plugins.get(i).getName().equals(name))
{
if(plugins.get(i).isLoaded())
{
plugins.get(i).unLoad();
/*
* discard the old classloader to remove any references to the class.
* Read somewhere that two gc() should do the trick :-)
*/
cl = new PluginLoader();
System.gc();
System.gc();
return;
}
}
}
}
/**
* Loads an unloaded plugin.
*
* @param name The name of the plugin.
*/
public void loadPlugin(String name)
{
for(PluginInfo p : plugins)
{
if(p.getName().equals(name))
{
File file = new File(p.getPath());
loadFile(file.getPath(), file.getName().substring(0, file.getName().indexOf(".")), p);
}
}
}
/**
* Tries to load all plugins in a directory.
*
* @param path Directory.
*/
private void addPluginsInPath(String path)
{
File dir = new File(path);
String[] files = dir.list();
if(files.length == 0)
{
return;
}
else
{
FilenameFilter filter = new FilenameFilter()
{
public boolean accept(File d, String n)
{
return !n.startsWith(".");
}
};
files = dir.list(filter);
for(String f : files)
{
loadFile(path + "/" + f, f.substring(0, f.indexOf(".")), new PluginInfo());
}
}
}
/**
* Loads a single plugin, given a path and a name.
*
* @param path Directory.
* @param name Name of class to load.
* @param pi PluginInfo object.
*/
private void loadFile(String path, String name, PluginInfo pi)
{
Class<?> cls;
try
{
if(!plugins.contains(pi))
{
pi.setPath(path);
}
if(path.endsWith(".class"))
{
cls = cl.loadClass(path, "apes.plugins." + name);
instancePlugin(cls, pi);
}
else if(path.endsWith(".jar"))
{
cls = cl.loadClass(path, "apes.plugins." + name);
instancePlugin(cls, pi);
}
}
catch(ClassNotFoundException e)
{
e.printStackTrace();
}
}
/**
* Creates a new instance of the class and adds it to the list of loaded
* plugins.
*
* @param cls Class object.
* @param pi The PluginInfo object to assign it to.
*/
private void instancePlugin(Class<?> cls, PluginInfo pi)
{
try
{
if(TransformPlugin.class.isAssignableFrom(cls))
{
TransformPlugin tp = (TransformPlugin)cls.newInstance();
pi.setTransformObject(tp);
if(!plugins.contains(pi))
{
pi.setName(tp.getName());
pi.setDescription(tp.getDescriptions());
pi.setType("transform");
pi.setTransformObject(tp);
plugins.add(pi);
}
}
else if(AudioFormatPlugin.class.isAssignableFrom(cls))
{
AudioFormatPlugin afp = (AudioFormatPlugin)cls.newInstance();
pi.setAudioFormatObject(afp);
if(!plugins.contains(pi))
{
pi.setName(afp.getName());
pi.setDescription(afp.getDescriptions());
pi.setType("format");
pi.setAudioFormatObject(afp);
plugins.add(pi);
}
}
}
catch(InstantiationException e)
{
e.printStackTrace();
}
catch(IllegalAccessException e)
{
e.printStackTrace();
}
}
}
/**
* Custom loader to avoid loading with the system ClassLoader.
*/
class PluginLoader extends ClassLoader
{
/**
* Loads the class.
*
* @param location The path.
* @param name The name.
* @return The class.
*/
public Class<?> loadClass(String location, String name) throws ClassNotFoundException
{
byte[] classBytes = null;
// read the class file
try
{
FileInputStream in = new FileInputStream(location);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int c;
while((c = in.read()) != -1)
{
buf.write(c);
}
classBytes = buf.toByteArray();
}
catch(IOException e)
{
e.printStackTrace();
}
if(classBytes == null)
{
throw new ClassNotFoundException("Cannot load class");
}
// turn it to a class
try
{
Class<?> cls = defineClass(name, classBytes, 0, classBytes.length);
resolveClass(cls);
return cls;
}
catch(ClassFormatError e)
{
e.printStackTrace();
}
return null;
}
/**
* Loads a JAR file. Untested :-)
*
* @param location Directory.
* @param name Name of JAR to load.
* @throws ClassNotFoundException
*/
@SuppressWarnings("deprecation")
public Class<?> loadJAR(String location, String name) throws ClassNotFoundException
{
try
{
File pluginFile = new File(location);
URL[] locations = new URL[] { pluginFile.toURL() };
URLClassLoader classloader = new URLClassLoader(locations);
Class<?> cls = classloader.loadClass(name);
return cls;
}
catch(MalformedURLException e)
{
e.printStackTrace();
}
return null;
}
}
/**
* Class to hold plugin information.
*/
class PluginInfo
{
/**
* The path to the plugin file.
*/
private String path;
/**
* The name of the plugin.
*/
private String name;
/**
* The description of the plugin.
*/
private Map<String, String> desc;
/**
* The type of the plugin, "format" or "transform".
*/
private String type;
/**
* The state of the plugin.
*/
private Boolean loaded;
/**
* Holder for a AudioFormatPlugin object.
*/
private AudioFormatPlugin fObject;
/**
* Holder for a TransformPlugin object.
*/
private TransformPlugin tObject;
/**
* Constructor.
*/
public PluginInfo()
{
desc = new HashMap<String, String>();
}
/**
* Returns path to plugin.
*
* @return The path.
*/
public String getPath()
{
return path;
}
/**
* Sets the path to the plugin.
*
* @param str The path.
*/
public void setPath(String str)
{
path = str;
}
/**
* Returns the name of the plugin.
*
* @return The plugin name.
*/
public String getName()
{
return name;
}
/**
* Sets the name of the plugin.
*
* @param str The name.
*/
public void setName(String str)
{
name = str;
}
/**
* Returns the plugin description.
*
* @param language The chosen language.
* @return The description.
*/
public String getDescription(String language)
{
if(desc.containsKey(language))
{
return desc.get(language);
}
else
{
return desc.get("en");
}
}
/**
* Sets the plugin description.
*
* @param map The description map.
*/
public void setDescription(Map<String, String> map)
{
desc = map;
}
/**
* Returns the type.
*
* @return The type.
*/
public String getType()
{
return type;
}
/**
* Sets the plugin type.
*
* @param str The type.
*/
public void setType(String str)
{
type = str;
}
/**
* Returns true if the plugin is loaded.
*
* @return
*/
public Boolean isLoaded()
{
return loaded;
}
/**
* Returns the AudioFormatPlugin object.
*
* @return Plugin object.
*/
public AudioFormatPlugin getAudioFormatObject()
{
return fObject;
}
/**
* Sets the AudioFormatPlugin object.
*
* @param obj The object.
*/
public void setAudioFormatObject(AudioFormatPlugin obj)
{
fObject = obj;
loaded = true;
}
/**
* Returns the TransformPlugin object.
*
* @return Plugin object.
*/
public TransformPlugin getTransformObject()
{
return tObject;
}
/**
* Sets the TransformPlugin object.
*
* @param obj The object.
*/
public void setTransformObject(TransformPlugin obj)
{
tObject = obj;
loaded = true;
}
/**
* Unloads the plugin object.
*/
public void unLoad()
{
tObject = null;
fObject = null;
loaded = false;
}
}