/*
* Copyright (c) 2012, Fromentin Xavier, Schnell Michaël, Dervin Cyrielle, Brabant Quentin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The names of its contributors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Fromentin Xavier, Schnell Michaël, Dervin Cyrielle OR Brabant Quentin
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package kameleon.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import kameleon.exception.FileDeletingException;
import kameleon.exception.FileReadingException;
import kameleon.exception.FileWritingException;
import kameleon.exception.InvalidPlugInException;
import kameleon.exception.KameleonException;
import kameleon.exception.UnknownPlugInException;
import kameleon.plugin.AnalyzerManager;
import kameleon.plugin.PlugIn;
import kameleon.plugin.PlugInInfo;
/**
* Utility class for writing, reading and loading plug-ins and their files.
*
* @author Schnell Michaël
* @version 1.0
*/
public class IOPlugIn implements FileConstants {
/**
* Utility constant for the absolute path of a jar file.
*/
private static final String JAR_FILE =
String.format("%%s%s%%s.jar", File.separator) ; //$NON-NLS-1$
/**
* Manager which should be updated when a plug-in is added or removed.
*/
protected AnalyzerManager am ;
/**
* Absolute path of the folder containing the java archives of
* the analyzer plug-ins.
*/
protected String analyzerFolder ;
/**
* Absolute path of the folder containing the java archives of
* the generator plug-ins.
*/
protected String generatorFolder ;
/**
* Absolute path of the file containing the information about
* the analyzer plug-ins.
*/
protected String analyzerConfigFile ;
/**
* Absolute path of the file containing the information about
* the generator plug-ins.
*/
protected String generatorConfigFile ;
/**
* Absolute path of the folder containing the resources for the plug-ins.
*/
protected String ressourceFolder ;
/**
* Sole constructor.
*
* @param am
* manager which should be updated when a plug-in is added or removed
*
* @param analyzerFolder
* absolute path of the folder containing the java archives of
* the analyzer plug-ins
*
* @param generatorFolder
* absolute path of the folder containing the java archives of
* the generator plug-ins
*
* @param analyzerConfigFile
* absolute path of the file containing the information about
* the analyzer plug-ins
*
* @param generatorConfigFile
* absolute path of the file containing the information about
* the generator plug-ins
*
* @param ressourceFolder
* absolute path of the folder containing the resources for the plug-ins
*/
public IOPlugIn(AnalyzerManager am, String analyzerFolder,
String generatorFolder, String analyzerConfigFile,
String generatorConfigFile, String ressourceFolder) {
super();
this.am = am;
this.analyzerFolder = analyzerFolder;
this.generatorFolder = generatorFolder;
this.analyzerConfigFile = analyzerConfigFile;
this.generatorConfigFile = generatorConfigFile;
this.ressourceFolder = ressourceFolder;
}// IOPlugIn(AnalyzerManager, String, String, String, String, String)
/**
* Adds a given plug-in to the software.
*
* <p>For a plug-in to be valid, the source file should be a valid java
* archive. Inside this archive, there should be two things:
* <ol>
* <li>A file {@code plugin.info} containing a serialized instance of {@code PlugInInfo}.
* This file contains the informations about the plug-in.
* <li>The executable code for the plug-in (usual content of a java archive, that is {@code .class} files).
* </ol>
*
* <p>The function copies the jar into the correct folder (analyzer of
* generator folder depending on the type of plug-in) and adds the information about
* the new plug-in in the concerned configuration file. If an analyzer plug-in was added,
* the analyzer manager is notified. Finally the pictures for the given plug-in are
* copied into the resource folder.
*
* <p><b>Should any of these steps fail, the software is left in an unstable state.
* No actions are undone !</b>
*
* @param plugin
* plug-in source file
*
* @return instance of {@code PlugInInfo} with the information about the newly added plug-in
*
* @throws FileReadingException
* if an error occurred while reading
*
* @throws FileWritingException
* if an error occurred while writing
*
* @throws InvalidPlugInException
* if the given plug-in is invalid
*/
//TODO Undo intermediate state
public PlugInInfo addPlugIn(File plugin)
throws InvalidPlugInException, FileReadingException, FileWritingException {
try {
ZipFile plugInZip = new ZipFile(plugin) ;
/* Retrieve plug-in information object */
ZipEntry infoFileEntry = plugInZip.getEntry(PLUGIN_INFO_FILE_NAME) ;
if (infoFileEntry == null) {
throw new InvalidPlugInException(plugin.getName()) ;
}// if
PlugInInfo info = (PlugInInfo) IOObject.readObjectFromFile(
new BufferedInputStream(
plugInZip.getInputStream(infoFileEntry))) ;
// Close the zip file in order to copy it
plugInZip.close() ;
/* Determine the nature of the plug-in before adding it */
if (info.isAnalyzer()) {
addJar(plugin, info, this.analyzerFolder) ;
addConfiguration(info, this.analyzerConfigFile) ;
this.am.addAnalyzerInfo(info) ;
} else {
addJar(plugin, info, this.generatorFolder) ;
addConfiguration(info, this.generatorConfigFile) ;
}// if
copyImages(info) ;
return info ;
} catch (ZipException e) {
throw new InvalidPlugInException(plugin.getName()) ;
} catch (IOException e) {
throw new FileReadingException(plugin) ;
}// try
}// addPlugIn(File, String, String, String)
/**
* Copies the given zip file into the given folder.
*
* @param pluginFile
* source zip file
*
* @param info
* instance of {@code PlugInInfo} for the given plug-in
*
* @param tagetFolder
* target folder for the extracted jar
*
* @throws FileReadingException
* if an error occurred while reading
*
* @throws FileWritingException
* if an error occurred while writing
*/
private static void addJar(File pluginFile, PlugInInfo info, String tagetFolder)
throws FileReadingException, FileWritingException {
String jarName = String.format("%s.jar", info.getJarName()) ; //$NON-NLS-1$
/* Create the streams */
InputStream src ;
try {
src = new BufferedInputStream(
new FileInputStream(pluginFile)) ;
} catch (IOException e) {
throw new FileReadingException() ;
}// try
OutputStream dest ;
try {
dest = new BufferedOutputStream(
new FileOutputStream(String.format(PATH_EXTENSION,
tagetFolder, jarName))) ;
} catch (IOException e) {
throw new FileWritingException() ;
}// try
/* Do the actual copying */
IOFile.copyFile(src, dest) ;
/* Close the streams */
try {
src.close() ;
} catch (IOException e) {
throw new FileReadingException() ;
}// try
try {
dest.close() ;
} catch (IOException e) {
throw new FileWritingException() ;
}// try
}// addJar(ZipFile, PlugInInfo, String)
/**
* Adds the given {@code PlugInInfo} to the list read in the given
* configuration file.
*
* @param info
* instance to add in the list
*
* @param configFile
* source configuration file for the list
*
* @throws FileWritingException
* if an error occurred while writing
*/
private static void addConfiguration(PlugInInfo info, String configFile) throws FileWritingException
{
List<PlugInInfo> knownPlugIns = null ;
try {
knownPlugIns = IOObject.readList(configFile) ;
} catch (KameleonException e) {
knownPlugIns = new LinkedList<PlugInInfo>() ;
}// try
int index = knownPlugIns.indexOf(info) ;
// Plug-in is new
if (index == -1) {
knownPlugIns.add(info) ;
} else {// Plug-in is being updated
knownPlugIns.set(index, info) ;
}// if
IOObject.writeObjectToFile(configFile, knownPlugIns) ;
}// addConfiguration(PlugInInfo, String)
/**
* Removes the given plug-in from the software.
*
* @param info
* information about the plug-in which should be removed
*
// * @throws FileDeletingException
// * if files used by the plug-in could not be deleted
*
* @throws UnknownPlugInException
* if the removed plug-in does not exist
*/
//TODO Undo intermediate state
public void removePlugIn(PlugInInfo info)
throws /*FileDeletingException, */UnknownPlugInException {
String configFile, folder ;
if (info.isAnalyzer()) {
configFile = this.analyzerConfigFile ;
folder = this.analyzerFolder ;
} else {
configFile = this.generatorConfigFile ;
folder = this.generatorFolder ;
}// if
removeConfiguration(info, configFile) ;
try {
removeJar(folder, info.getJarName()) ;
//TODO Handle the case of partially removed pictures
removeImages(info) ;
} catch (KameleonException ke) {
// TODO: handle exception
forceRemoveImages(info) ;
}// try
}// removePlugIn(PlugInInfo)
/**
* Deleted the given jar file from the given folder.
*
* @param parentFolder
* folder containing the jar file which should be deleted
*
* @param jarName
* name of the jar file which should be deleted (without the extension)
*
* @throws FileDeletingException
* if the jar of the plug-in could not be deleted
*/
private static void removeJar(String parentFolder, String jarName)
throws FileDeletingException {
String jarPath = String.format(JAR_FILE, parentFolder, jarName) ;
File jar = new File(jarPath) ;
boolean sucess = jar.delete() ;
if (!sucess) {
throw new FileDeletingException(jar) ;
}// if
}// removeJar(String)
/**
* Removes the given instance of {@code PlugInInfo} from the list read
* in the given configuration file. The configuration file is updated
* after the removal.
*
* @param info
* instance of [@ode PlugInInfo] which should be removed
*
* @param configFile
* source configuration file for the list from which the
* instance should be removed
*
* @throws UnknownPlugInException
* if the given plug-in is not listed in the configuration file
*///TODO Review thrown exceptions
private static void removeConfiguration(PlugInInfo info, String configFile)
throws UnknownPlugInException {
try {
List<PlugInInfo> knownPlugIns = IOObject.readList(configFile) ;
// Plug-in is unknown
if (!knownPlugIns.contains(info)) {
throw new UnknownPlugInException(info) ;
}// if
// Remove plug-in info and save the result
knownPlugIns.remove(info) ;
IOObject.writeObjectToFile(configFile, knownPlugIns) ;
} catch(UnknownPlugInException uke) {
throw uke ;
} catch(KameleonException ke) {
/* Configuration file is corrupt, we cannot remove the plug-in. */
}// try
}// removeConfiguration(PlugInInfo, String)
/**
* Removes the pictures used by the given plug-in.
*
* @param info
* information about the plug-in whose file are removed
*
* @throws FileDeletingException
* if a picture could not be deleted
*/
private static void removeImages(PlugInInfo info)
throws FileDeletingException {
String basePath = String.format(PATH_EXTENSION,
PLUG_IN_RESOURCES_FOLDER, info.getId()) ;
String[] pictures = new String[]{
FORMAT_GRAY_ICON_FILE_NAME,
FORMAT_ICON_FILE_NAME,
FORMAT_MINI_FILE_NAME
} ;
for(String picture : pictures) {
File pictureFile = new File(
String.format(picture, basePath)) ;
boolean deleted ;
try {
deleted = pictureFile.delete() ;
} catch (SecurityException se) {
deleted = false;
}// try
if (!deleted) {
throw new FileDeletingException(pictureFile) ;
}// if
}// for
}// removeImages(PlugInInfo)
/**
* Removes the pictures used by the given plug-in without throwing
* an exception if one file could not be deleted.
*
* @param info
* information about the plug-in whose file are removed
*/
private static void forceRemoveImages(PlugInInfo info) {
String basePath = String.format(PATH_EXTENSION,
PLUG_IN_RESOURCES_FOLDER, info.getId()) ;
String[] pictures = new String[]{
FORMAT_GRAY_ICON_FILE_NAME,
FORMAT_ICON_FILE_NAME,
FORMAT_MINI_FILE_NAME
} ;
for(String picture : pictures) {
File pictureFile = new File(
String.format(picture, basePath)) ;
try {
pictureFile.delete() ;
} catch (SecurityException se) {
/* Ignore exception. */
}// try
}// for
}// removeImages(PlugInInfo)
/**
* Copies the pictures of the plug-in into the resource folder.
*
* <p>The copying is achieved by calling the {@code copyPicture(String)} function
* from the "main" class of the given plug-in.
*
* @param info
* information about the plug-in whose pictures should be copied
*
* @throws InvalidPlugInException
* if the given plug-in is invalid
*
* @see PlugInInfo
* @see PlugIn#copyPicture(String)
*/
private void copyImages(PlugInInfo info) throws InvalidPlugInException {
ClassLoader loader = null ;
if (info.isAnalyzer()) {
loader = loadPlugIn(info, this.analyzerFolder) ;
} else {
loader = loadPlugIn(info, this.generatorFolder) ;
}// if
try {
loader.loadClass(info.getPlugInClass()) ;
Class<?> plugInClass = Class.forName(info.getPlugInClass(), true, loader) ;
Class<? extends PlugIn> plClass = plugInClass.asSubclass(PlugIn.class) ;
Constructor<? extends PlugIn> constructor = plClass.getConstructor() ;
PlugIn plugIn = constructor.newInstance() ;
plugIn.copyPicture(this.ressourceFolder) ;
} catch (Exception e) {
/*=== Possible exceptions ===
* - ClassNotFoundException
* - IllegalAccessException
* - IllegalArgumentException
* - InstantiationException
* - InvocationTargetException
* - NoSuchMethodException
* - SecurityException
*/
throw new InvalidPlugInException(info, e) ;
}// try
}// copyImages(ZipFile)
/**
* Returns an instance of {@code ClassLoader} containing the classes of the
* loaded plug-in.
*
* @param info
* information about the loaded plug-in
*
* @param parentFolder
* folder containing the java archive of the loaded plug-in
*
* @return A {@code ClassLoader} which can be used to instantiate the "main"
* class from the loaded plug-in
*
* @throws InvalidPlugInException
* if the given plug-in is invalid
*/
public static ClassLoader loadPlugIn(PlugInInfo info, String parentFolder)
throws InvalidPlugInException {
String jarPath = String.format(JAR_FILE, parentFolder, info.getJarName()) ;
try {
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { new File(jarPath).toURI().toURL() }
) ;
return loader ;
} catch (MalformedURLException e) {
throw new InvalidPlugInException(info) ;
}// try
}// loadPlugIn(PlugInInfo, String, String)
}// class IOPlugIn