/*
* 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.gui.model;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import javax.swing.SwingWorker;
import kameleon.document.Document;
import kameleon.exception.InvalidPlugInException;
import kameleon.exception.KameleonException;
import kameleon.gui.exception.InvalidGenerationException;
import kameleon.gui.model.GenerationMessage.GenerationState;
import kameleon.gui.model.GenerationMessage.State;
import kameleon.plugin.Analyzer;
import kameleon.plugin.Generator;
import kameleon.plugin.PlugInInfo;
import kameleon.util.IOPlugIn;
/**
* Model responsible for the generation process.
*
* <p>//TODO ADD A LOT MORE DETAILS !
*
* @author Brabant Quentin, Dervin Cyrielle, Fromentin Xavier, Schnell Michaël
* @version 1.0
*/
public class GenerationModel extends FileModel {
/**
* Flag indicating that a generator has just been added
* or removed.
*/
protected boolean generatorAddedOrRemoved ;
/**
* Flag indicating that the generation process has just
* finished.
*/
protected boolean generationFinished ;
/**
* Builds an instance with the given options.
*
* <p>Reads the history and the the configuration files
* to find the installed plug-ins.
*
* @param debugMode
* flag indicating whether the debug mode
* should be activated ({@code true} means
* activated)
*/
public GenerationModel(boolean debugMode) {
super(debugMode) ;
this.generatorAddedOrRemoved = false ;
this.selectedGenerators = new ArrayList<String>() ;
try {
this.initializePlugIns() ;
} catch(KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
}// GenerationModel(boolean)
/**
* Builds an instance with default options.
*/
public GenerationModel() {
this(DEFAULT_DEBUG_MODE) ;
}// GenerationModel()
/**
* Generates the absolute file paths for the files generated by
* the selected generators.
*
* <p>If only generator is selected, the model will only check is
* the given file extension is valid. If not, a valid (default) one
* is appended to the file name.
*
* <p>If multiple generators are selected, the software will
* append {@code "_<format name>"} to each file name before the
* valid extension. Same as before, if the given extension is not
* valid, a default one is added.
*
* //TODO Add an example
*
* @return Array containing the generated names, the order of the
* names is the same as the order of the generators
*///TODO Review null pointer
protected String[] generateFileNames() {
if (this.selectedGenerators.size() == 1) {
return new String[]{generateName(
this.outputFile.getAbsolutePath(),
this.getGenerator(this.selectedGenerators.get(0)))} ;
}// if
String[] names = new String[this.selectedGenerators.size()] ;
Iterator<String> iter = this.selectedGenerators.iterator() ;
String baseName = this.outputFile.getAbsolutePath() ;
for(int n = 0; iter.hasNext(); ++n) {
names[n] = generateExtendedName(baseName,
this.getGenerator(iter.next())) ;
}// for
return names ;
}// generateFileNames()
/**
* Generates the file name for a given generator using the provided
* name. Checks if the given extension is valid for the given
* generator and if is not, appends a valid one to the file name.
* If the given generator has no valid extension, the file name
* is left unchanged.
*
* @param baseName
* absolute path of the wanted file
*
* @param generatorInfo
* generator of the file whose name is created
*
* @return Absolute path for the file
*///TODO Review null pointers
protected static String generateName(String baseName, PlugInInfo generatorInfo) {
String ext = getExtension(baseName) ;
StringBuilder name = new StringBuilder(baseName) ;
if (!isValidExtension(ext, generatorInfo)) {
String[] validExtensions = generatorInfo.getExtensions() ;
if (validExtensions.length > 0) {
name.append('.') ;
name.append(validExtensions[0]) ;
}// if
}// if
return name.toString() ;
}// generateName(String, PlugInInfo)
/**
* Generates the file name for a given generator using the provided
* name. Checks if the given extension is valid for the given
* generator and if is not, appends a valid one to the file name.
* If the given generator has no valid extension, the file name
* is left unchanged. Also adds {@code "_<format name>"} before
* the (valid) extension. This is done in order to prevent multiple
* generators from generate the same file.
*
* @param baseName
* absolute path of the wanted file
*
* @param generatorInfo
* generator of the file whose name is created
*
* @return Absolute path for the file
*/
protected static String generateExtendedName(String baseName, PlugInInfo generatorInfo) {
String ext = getExtension(baseName) ;
StringBuilder name = new StringBuilder(baseName) ;
if (!isValidExtension(ext, generatorInfo)) {
name.append('_') ;
name.append(generatorInfo.getFormatName()) ;
String[] validExtensions = generatorInfo.getExtensions() ;
if (validExtensions.length > 0) {
name.append('.') ;
name.append(validExtensions[0]) ;
}// if
} else {
int posDot = baseName.lastIndexOf('.') ;
name.insert(posDot, generatorInfo.getFormatName()) ;
name.insert(posDot, '_') ;
}// if
return name.toString() ;
}// generateExtendedName(String, PlugInInfo)
/**
* Indicates if the given extension is commonly used by the given
* plug-in (analyzer or generator).
*
* @param extension
* extension which should be tested
*
* @param generatorInfo
* plug-in for which the extension should be tested
*
* @return {@code true} if the plug-in commonly uses the given
* extension, {@code false} otherwise
*
* @see PlugInInfo
*/
protected static boolean isValidExtension(String extension, PlugInInfo generatorInfo) {
boolean valid = (generatorInfo.getExtensions().length == 0) ;
for(String ext : generatorInfo.getExtensions()) {
valid = ext.equals(extension) ;
if (valid) break ;
}// if
return valid ;
}// isValidExtension(String, PlugInInfo)
/**
* Returns an instance of the "main" class of the given plug-in.
*
* @param info
* information about he plug-in whose instance is requested
*
* @param parentFolder
* absolute path the of the folder which contains the
* java archive of the plug-in
*
* @param targetClass
* either {@code Analyzer.class} of {@code Generator.class}
*
* @return Instance of the main class of the given plug-in
*
* @throws InvalidPlugInException
* If the main class could not be instantiated
*
* @see PlugInInfo
* @see Analyzer
* @see Generator
*///TODO Add exception for illegal argument
private static <T> T getInstance(PlugInInfo info, String parentFolder, Class<T> targetClass)
throws InvalidPlugInException {
try {
ClassLoader loader = IOPlugIn.loadPlugIn(info, parentFolder) ;
loader.loadClass(info.getPlugInClass()) ;
Class<?> plugInClass = Class.forName(info.getPlugInClass(), true, loader) ;
Class<? extends T> plClass = plugInClass.asSubclass(targetClass) ;
Constructor<? extends T> constructor = plClass.getConstructor() ;
return constructor.newInstance() ;
} catch (Exception e) {
/* === Possible exceptions ===
* - ClassNotFoundException
* - IllegalAccessException
* - IllegalArgumentException
* - InstantiationException
* - InvocationTargetException
* - NoSuchMethodException
* - SecurityException
* === ------------------- === */
throw new InvalidPlugInException(info, e) ;
}// try
}// getInstance(PlugInInfo, String, Class<T>)
/**
* Launches the analyzing process with the given analyzer.
*
* @param analyzer
* plug-in used to analyzed the currently selected file
*
* @return instance of {@code Document} resulting from the
* analyzing process
*
* @throws InvalidGenerationException
* if the generation is impossible
*
* @throws KameleonException
* if an error occurred while analyzing the selected file
*/
protected Document launchAnalyzer(PlugInInfo analyzer) throws KameleonException {
if (!this.generationIsPossible()) {
throw new InvalidGenerationException() ;
}// if
return GenerationModel.launchAnalyzer(analyzer,
this.getSelectedFileInfo().getPath()) ;
}// launchAnalyzer(PlugInInfo)
/**
* Launches the analyzing process with the given analyzer on the
* given file.
*
* @param analyzer
* plug-in used to analyzed the given file
*
* @param path
* absolute path of the analyzed file
*
* @return instance of {@code Document} resulting from the
*
* @throws KameleonException
* if an error occurred while analyzing the given file
*/
public static Document launchAnalyzer(PlugInInfo analyzer, String path)
throws KameleonException {
Analyzer plugIn = getInstance(analyzer, ANALYSER_FOLDER, Analyzer.class) ;
return plugIn.analyze(path) ;
}// launchAnalyzer(PlugInInfi, String)
/**
* Launches the generation of the given file with the given
* generator.
*
* @param generator
* plug-in used to generate the given file
*
* @param document
* source for the content of the generated file
*
* @param targetFile
* absolute path of the generated file
*
* @throws KameleonException
* if an error occurred while generating the file
*/
public static void launchGenerator(PlugInInfo generator, Document document, String targetFile)
throws KameleonException {
Generator plugIn = getInstance(generator, GENERATOR_FOLDER, Generator.class) ;
plugIn.generate(document, targetFile) ;
}// launchGeneration(PlugInInfo, Document, String)
/**
* Indicates if a generator has just been added or removed.
*
* @return {@code true} if a generator has just been added
* or removed, {@code false} otherwise
*/
public boolean generatorAddedOrRemoved() {
return this.generatorAddedOrRemoved ;
}// generatorAddedOrRemoved()
/**
* Adds a new file. If the file is a plug-in installation file,
* the model will attempt to install the plug-in. Otherwise
* the model will add the file to history and select it.
*
* @param newFile
* added file
*/
@Override
public void addFile(File newFile) {
// Test if the added file is a plug-in
String extension = getExtension(newFile) ;
if (PLUGIN_EXTENSION.equals(extension)) {
// Install the plug-in
this.addPlugIn(newFile) ;
return ;
}// if
super.addFile(newFile) ;
}// addFile(File)
/**
* {@inheritDoc}
*
* @return {@code PlugInInfo} about the newly added plug-in
* or {@code null} if the plug-in could not be added
*/
@Override
public PlugInInfo addPlugIn(File plugin) {
PlugInMessage pmsg = new PlugInMessage(true, null) ;
try {
pmsg.setMessage(ADD_PLUG_IN_START_MESSAGE) ;
this.addMessage(pmsg) ;
PlugInInfo info = super.addPlugIn(plugin) ;
pmsg.setState(PlugInMessage.State.SUCCESS) ;
pmsg.setMessage(
info.isAnalyzer() ? ADD_ANALYZER_SUCESS_MESSAGE
: ADD_GENERATOR_SUCESS_MESSAGE,
info.getFormatName()) ;
this.generatorAddedOrRemoved = !info.isAnalyzer() ;
this.updateMessage(pmsg) ;
this.generatorAddedOrRemoved = false ;
return info ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
pmsg.setState(PlugInMessage.State.ERROR) ;
pmsg.setMessage(ADD_PLUG_IN_ERROR_MESSAGE) ;
this.updateMessage(pmsg) ;
}// try
return null ;
}// adPlugIn(File)
/**
* {@inheritDoc}
*
* @return {@code PlugInInfo} about the newly added plug-in
* or {@code null} if the plug-in could not be added
*/
@Override
public PlugInInfo addPlugIn(String pluginPath) {
return this.addPlugIn(new File(pluginPath)) ;
}// addPlugIn(String)
/**
* {@inheritDoc}
*/
@Override
public void removeAnalyzer(String analyzerId) {
this.removePlugIn(this.getAnalyzer(analyzerId)) ;
}// removeAnalyzer(String)
/**
* {@inheritDoc}
*/
@Override
public void removeGenerator(String generatorId) {
this.removeSelectedFormat(generatorId);
this.removePlugIn(this.getGenerator(generatorId)) ;
}// removeGenerator(String)
/**
* {@inheritDoc}
*/
@Override
public void removePlugIn(PlugInInfo info) {
PlugInMessage pmsg = new PlugInMessage(false, null) ;
try {
pmsg.setMessage(
info.isAnalyzer() ? REMOVE_ANALYZER_START_MESSAGE
: REMOVE_GENERATOR_START_MESSAGE,
info.getFormatName()) ;
this.addMessage(pmsg) ;
super.removePlugIn(info) ;
pmsg.setState(PlugInMessage.State.SUCCESS) ;
pmsg.setMessage(
info.isAnalyzer() ? REMOVE_ANALYZER_SUCESS_MESSAGE
: REMOVE_GENERATOR_SUCESS_MESSAGE,
info.getFormatName()) ;
this.generatorAddedOrRemoved = true ;
this.updateMessage(pmsg) ;
this.generatorAddedOrRemoved = false ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
pmsg.setState(PlugInMessage.State.ERROR) ;
pmsg.setMessage(
info.isAnalyzer() ? REMOVE_ANALYZER_ERROR_MESSAGE
: REMOVE_GENERATOR_ERROR_MESSAGE,
info.getFormatName()) ;
this.updateMessage(pmsg) ;
}// try
}// removePlugIn(PlugInInfo)
/**
* Launches generation in a new thread using the options of the
* given message.
*
* @param message
* information about the generation
*/
public void fastGeneration(GenerationMessage message) {
message.initializeStates() ;
this.updateMessage(message) ;
new GenerationThread(this, message).execute() ;
}// fastGeneration(GenerationMessage)
/**
* Launches the generation for the selected file with the
* selected generators. If the generation is impossible,
* this function does nothing.
*/
public void launchGeneration() {
if (this.generationIsPossible()) {
// Initialize message
GenerationMessage message = new GenerationMessage(
this.getSelectedFileInfo()) ;
message.setLastGeneration(new Date()) ;// now
System.out.println(this.selectedGenerators);
message.setTargetFormatId(this.selectedGenerators.toArray(
new String[this.selectedGenerators.size()])) ;
message.setTargetPaths(this.generateFileNames()) ;
this.addMessage(message) ;
// Do the actual generation
this.fastGeneration(message) ;
// Update hisotry
FileInfo current = this.getSelectedFileInfo() ;
current.setLastGeneration(message.getLastGeneration()) ;
current.setTargetFormatId(message.getTargetFormatId()) ;
current.setTargetPaths(message.getTargetPath()) ;
this.writeHistory() ;
}// if
}// launchGeneration()
/**
* Indicates whether the generation process has just finished.
*
* @return {@code true} if the generation process has just
* finished, {@code false} otherwise
*/
public boolean generationFinished() {
return this.generationFinished ;
}// generationFinished()
/**
* Notifies the observers that the current generation process
* has just finished.
*
* @param message
* message associated with the finished generation
*/
public void notifyGenerationFinished(Message message) {
this.generationFinished = true ;
this.updateMessage(message) ;
this.generationFinished = false ;
}// notifyGenerationFinished(Message)
/**
* Subclass of {@link SwingWorker} used to host the generation
* process.
*
* @author Schnell Michaël
* @version 1.0
*/
private class GenerationThread extends SwingWorker<Void, Void> {
/**
* Model of the graphical interface.
*/
private GenerationModel model ;
/**
* Message associated with this generation process.
*/
private GenerationMessage message ;
/**
* Builds an instance with the given values.
*
* @param model
* model of the graphical interface
*
* @param message
* message containing the information about the
* generation process
*/
public GenerationThread(GenerationModel model, GenerationMessage message) {
super();
this.model = model ;
this.message = message ;
}// GenerationThread(GenerationModel, GenerationMessage)
/**
* Executes the generation of the files requested in the
* given message as a background task.
*/
@Override
protected Void doInBackground() {
int rowIndex = 0 ;
String[] outputFiles = this.message.getTargetPath() ;
String[] genIds = this.message.getTargetFormatId() ;
boolean fileNotGenerated = false ;
try {
PlugInInfo analyzer = this.model.getAnalyzer(
this.message.getIdFormat()) ;
Document document = GenerationModel.launchAnalyzer(
analyzer, this.message.getPath()) ;
this.message.setState(GenerationState.GENERATING) ;
this.model.updateMessage(this.message) ;
for(rowIndex=0; rowIndex<genIds.length; ++rowIndex) {
this.message.setState(rowIndex, State.CONVERTING) ;
this.model.updateMessage(this.message) ;
String genId = genIds[rowIndex] ;
PlugInInfo generator = this.model.getGenerator(genId) ;
try {
GenerationModel.launchGenerator(
generator, document, outputFiles[rowIndex]) ;
this.message.setState(rowIndex, State.CONVERTED) ;
} catch (KameleonException ke) {
this.message.setState(rowIndex, State.ERROR) ;
fileNotGenerated = true ;
}// try
this.model.updateMessage(this.message) ;
}// for
this.message.setLastGeneration(new Date()) ;
if (fileNotGenerated) {
this.message.setState(GenerationState.PARTIAL_SUCESS) ;
} else {
this.message.setState(GenerationState.COMPLETE_SUCESS) ;
}// if
this.model.updateMessage(this.message) ;
} catch (KameleonException ke) {
while (rowIndex < genIds.length) {
this.message.setState(rowIndex, State.ERROR) ;
rowIndex++ ;
}// while
this.message.setState(GenerationState.ERROR) ;
this.model.updateMessage(this.message) ;
this.model.displayDebugInformation(ke) ;
}// try
this.model.notifyGenerationFinished(this.message) ;
return null ;
}// doInBackground()
}// class GenerationProcess
}// class Model