/*
* 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.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;
import kameleon.exception.KameleonException;
import kameleon.exception.NotUniquePlugInInfoException;
import kameleon.exception.UnknownExtensionException;
import kameleon.gui.exception.InvalidOutputFileException;
import kameleon.gui.util.FileConstants;
import kameleon.gui.util.LanguageConstants;
import kameleon.plugin.PlugInInfo;
import kameleon.util.IOObject;
import kameleon.util.WorkSpaceManager;
/**
* Model responsible for the handling of the files. Allows the user
* to add new files, remove files from the history, save the most
* recently used files from one session to another, set the analyzers
* used by the files and the generators used for the next generation,
* choose the file charset and view detailed information about the
* files.
*
* @author Schnell Michaël
* @version 1.0
*/
public class FileModel extends MessageModel
implements FileConstants, LanguageConstants {
/**
* Number of files which are stored from session to session.
*/
public static final int HISTORY_SIZE = 10 ;
/**
* Default format of the dates displayed in the graphical interface.
*/
public static final String DEFAULT_DATE_FORMAT = "dd/MM/yyyy HH:mm" ; //$NON-NLS-1$
/**
* Formatter used to display the dates in the default format.
*
* @see #DEFAULT_DATE_FORMAT
*/
public static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(DEFAULT_DATE_FORMAT) ;
/**
* Index of the currently selected file (or {@code null} if no file
* is selected)
*/
protected Integer currentFileIndex ;
/**
* Index of the last file which was removed from the history.
*/
protected int deletedIndex ;
/**
* History of the recently used files and their generations.
*/
protected List<FileInfo> recentFiles ;
/**
* Target file for the generation (provided by the user).
*/
protected File outputFile ;
/**
* Charset used to decode the analyzed files.
*/
protected String charset ;
/**
* Flag indicating if a new file has just been added.
*/
protected boolean newFileAdded ;
/**
* Flag indicating that the selected file in the
* history has just changed.
*/
protected boolean selectionHasChanged ;
/**
* Flag indicating that a file has just been removed.
*/
protected boolean fileRemoved ;
/**
* Ids of the currently selected generators.
*/
protected List<String> selectedGenerators ;
/**
* Builds an instance with the given options.
*
* <p>Reads the history of the recently used files.
*
* @param debugMode
* flag indicating whether the debug mode
* should be activated ({@code true} means
* activated)
*/
public FileModel(boolean debugMode) {
super(debugMode) ;
this.currentFileIndex = null ;
this.newFileAdded = false ;
this.selectionHasChanged = false ;
this.fileRemoved = false ;
this.charset = Charset.defaultCharset().displayName() ;
this.readHistory() ;
}// FileModel(boolean)
/**
* Builds an instance with default options.
*/
public FileModel() {
this(DEFAULT_DEBUG_MODE) ;
}// FileModel()
/**
* Reads the list of recently used files from the history file.
*/
protected void readHistory() {
try {
File history = new File(CONFIG_HISTORY_FILE) ;
if (history.exists()) {
Object obj = IOObject.readObjectFromFile(
CONFIG_HISTORY_FILE) ;
if (obj instanceof List<?>) {
this.recentFiles = (List<FileInfo>) obj ;
return ;
}// if
}// if
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
Message infoMsg = new InformationMessage(
InformationMessage.State.INFORMATION,
READING_HISTORY_ERROR) ;
this.addMessage(infoMsg) ;
}// try
this.recentFiles = new LinkedList<FileInfo>() ;
}// readHistory()
/**
* Writes the list of recently used files to the history file.
*/
protected void writeHistory() {
try {
WorkSpaceManager.ensureWorkSpace() ;
// We only keep the HISTORY_SIZE files inside history.
List<FileInfo> subList = new LinkedList<FileInfo>() ;
int maxI = Math.min(this.recentFiles.size(), HISTORY_SIZE) ;
Iterator<FileInfo> iter = this.recentFiles.iterator() ;
for(int i=0; i<maxI; ++i) {
FileInfo element = iter.next() ;
subList.add(element) ;
}// for
IOObject.writeObjectToFile(CONFIG_HISTORY_FILE, subList) ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
Message infoMsg = new InformationMessage(
InformationMessage.State.INFORMATION,
WRITING_HISTORY_ERROR) ;
this.addMessage(infoMsg) ;
}// try
}// writeHistory()
/**
* Indicates if a file is currently selected.
*
* @return {@code true} if a file is currently selected,
* {@code false} otherwise
*/
public boolean fileIsSelected() {
return (this.currentFileIndex != null) ;
}// fileIsSelected()
/**
* Returns the index of the file with the given path
* in the history file list.
*
* @param path
* absolute path of the requested file
*
* @return index of the requested file or {@code null}
* if the file was not found
*/
public Integer getPosition(String path) {
int position = 0 ;
boolean found = false ;
Iterator<FileInfo> iter = this.recentFiles.iterator() ;
while (iter.hasNext() && !found) {
FileInfo fi = iter.next() ;
found = fi.getPath().equals(path) ;
++position ;
}// while
//TODO Review - possibly throw an exception here
// The given path was not found
if (!found) {
return null ;
}// if
return new Integer(position-1) ;
}// getPosition(String)
/**
* Sets the index for the currently selected file.
*
* @param position
* index of the selected file
*/
public void setCurrentFilePosition(int position) {
this.currentFileIndex = new Integer(position) ;
}// setCurrentFilePosition(int)
/**
* Returns the index of the currently selected file.
*
* @return Index of the currently selected file or {@code -1}
* if no file is selected
*/
public int getCurrentFilePosition() {
if (this.currentFileIndex == null) {
return -1 ;
}// if
return this.currentFileIndex.intValue() ;
}// getCurrentFilePosition()
/**
* Returns a formatted {@code String} the last generation date
* of the currently selected file.
*
* @return Last generation date of the currently selected
* file or {@code null} if the file was never generated
* or if there is no selected file
*/
public String getFileLastGenerationDate() {
FileInfo current = this.getSelectedFileInfo() ;
if (current != null) {
Date last = current.getLastGeneration() ;
if (last != null) {
return DATE_FORMATTER.format(last) ;
}// if
}// if
return null ;
}// getFileLastGenerationDate()
/**
* Returns the extension of the currently selected file.
*
* @return Extension of the currently selected file or
* {@code null} if no file is selected or {@code ""}
* if the file has no extension
*/
public String getFileType() {
FileInfo current = this.getSelectedFileInfo() ;
if (current != null) {
String fileName = current.getName() ;
int positionDot = fileName.lastIndexOf('.') ;
if (positionDot != -1) {
// Extract the extension and return it
return fileName.substring(positionDot+1) ;
}// if
return "" ;//$NON-NLS-1$
}// if
return null ;
}// getFileType()
/**
* Returns an array with the information about the
* recently used files.
*
* @return Array containing all the information about the
* recently used files
*/
public FileInfo[] getRecentFileInfo() {
return this.recentFiles.toArray(
new FileInfo[this.recentFiles.size()]) ;
}// getRecentFileInfo()
/**
* Sets the output file for future generations.
*
* @param selectedFile
* new output file
*
* @throws InvalidOutputFileException
* if an invalid file is provided
*///TODO Further test if the given file is valid
public void setOutputFile(File selectedFile)
throws InvalidOutputFileException {
if (selectedFile == null) {
throw new InvalidOutputFileException() ;
}// if
this.outputFile = selectedFile ;
}// setOutPutFile(File)
/**
* Adds a new file. The model will add the file to history
* and select it.
*
* @param newFile
* added file
*/
public void addFile(File newFile) {
String extension = getExtension(newFile) ;
FileInfo fi = new FileInfo(newFile.getAbsolutePath()) ;
// Test if the file is already in the history
if (this.recentFiles.contains(fi)) {
// We simply selected the added file
this.currentFileIndex = this.getPosition(fi.getPath()) ;
this.selectionHasChanged = true ;
} else {
int insertIndex = 0 ;
if (this.recentFiles.isEmpty()) {
this.recentFiles.add(fi) ;
} else {
this.recentFiles.add(insertIndex, fi);
}// if
this.setCurrentFilePosition(insertIndex) ;
try {
if (this.am.getNAnalyzers(extension) > 0) {
try {
PlugInInfo analyzer =
this.am.getAnalyzerInfo(extension) ;
fi.setIdFormat(analyzer.getId()) ;
} catch (NotUniquePlugInInfoException ex) {
fi.setIdFormat(null) ;
}// try
}// if
} catch (UnknownExtensionException ex) {
fi.setIdFormat(null);
}// try
this.writeHistory() ;
this.selectionHasChanged = true ;
this.newFileAdded = true ;
}// if
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
this.selectionHasChanged = false ;
this.newFileAdded = false ;
}// addFile(File)
/**
* Indicates if a new file has just been added.
*
* @return {@code true} if a new file has just been added,
* {@code false} otherwise
*/
public boolean newFileAdded() {
return this.newFileAdded ;
}// newFileAdded()
/**
* Indicates if the current selection has just changed.
*
* @return {@code true} if the current selection has just changed,
* {@code false} otherwise
*/
public boolean selectionHasChanged() {
return this.selectionHasChanged ;
}// selectedHasChanged()
/**
* Returns the information about the currently selected file.
*
* @return {@code PlugInInfo} for the current selection or
* {@code null} if no file is selected
*/
public FileInfo getSelectedFileInfo() {
if(this.currentFileIndex != null) {
return this.recentFiles.get(
this.currentFileIndex.intValue()) ;
}// if
return null ;
}// getSelectedFileInfo()
/**
* Selects the file with the given path in the history.
*
* @param filePath
* absolute path the of the file to select
*/
public void selectFile(String filePath) {
this.currentFileIndex = this.getPosition(filePath) ;
this.selectionHasChanged = true ;
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
this.selectionHasChanged = false ;
}// selectFile(String)
/**
* Clears the current selection if there is one.
*/
public void clearSelection() {
if (this.currentFileIndex != null) {
this.currentFileIndex = null ;
this.selectionHasChanged = true ;
this.fileRemoved = false ;
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
this.selectionHasChanged = false ;
}// if
}// clearSelection()
/**
* Sets the analyzer used for the currently selected file.
*
* @param analyzerId
* id of the new analyzer for the currently selected file
*///TODO Review the case of no file selected
public void setCurrentFileAnalyzerId(String analyzerId) {
FileInfo current = this.getSelectedFileInfo() ;
if (current != null) {
current.setIdFormat(analyzerId) ;
} // if
}// setCurrentFileAnalyzerId(String)
/**
* Sets the analyzer used for the currently selected file
* and updates the history file.
*
* @param analyzer
* analyzer for the currently selected file
*/
public void setCurrentFileAnalyzer(PlugInInfo analyzer) {
this.setCurrentFileAnalyzerId(analyzer.getId()) ;
this.writeHistory() ;
this.selectionHasChanged = true ;
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
this.selectionHasChanged = false ;
}// setCurrentFileAnalyzer(PlugInInfo)
/**
* Returns the informations about the analyzer for the currently
* selected file.
*
* @return instance of {@code PlugInInfo} for the analyzer used
* by the currently selected file or {@code null} if
* the analyzer is unknown or no file is selected
*/
public PlugInInfo getCurrentFileFormat() {
FileInfo current = this.getSelectedFileInfo() ;
if (current != null) {
String formatId = current.getIdFormat() ;
if (formatId != null) {
return this.getAnalyzer(formatId) ;
}// if
}// if
return null ;
}// getCurrentFileFormat()
/**
* Indicates if the format of the currently selected file is known.
*
* @return {@code true} if the currently selected file has
* a known analyzer, {@code false} otherwise
*/
public boolean currentFileFormatIsKnown() {
return (this.getCurrentFileFormat() != null) ;
}// currentFileFormatIsKnown()
/**
* Removes the current selection from the history list and from
* the history file. If no file is currently selected, nothing
* is done.
*/
public void deleteCurrentSelection() {
if (this.currentFileIndex != null) {
this.deletedIndex = this.currentFileIndex.intValue() ;
this.recentFiles.remove(this.deletedIndex) ;
this.currentFileIndex = null ;
this.writeHistory() ;
this.fileRemoved = true ;
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
this.fileRemoved = false ;
}// if
}// deleteCurrentSelection()
/**
* Indicates if a file has just been removed from the history.
*
* @return {@code true} if a file has just been removed from
* the history, {@code false} otherwise
*/
public boolean fileRemoved() {
return this.fileRemoved ;
}// fileRemoved()
/**
* Returns the index of the deleted file in the history.
*
* @return Index of the deleted file in the history
*/
public int getDeletedIndex() {
return this.deletedIndex ;
}// getDeletedIndex()
/**
* Returns the names of all the available charsets.
*
* @return Array containing the names of all the available
* charsets
*
* @see Charset#availableCharsets()
*/
public static String[] getDefaultCharsets() {
SortedMap<String, Charset> scs = Charset.availableCharsets() ;
return scs.keySet().toArray(new String[scs.size()]) ;
}// getDefaultCharsets()
/**
* Sets the charset used to decode the analyzed files and to
* encode the generated files.
*
* @param charsetName
* name of the new charset
*///TODO Add exception if invalid charset ?
public void setCharset(String charsetName) {
if (Charset.isSupported(charsetName)) {
this.charset = charsetName ;
}// if
}// setCharset(String)
/**
* Returns the charset used to decode the analyzed files and to
* encode the generated files.
*
* @return Name of the charset used to encode and decode files
*/
public String getCharset() {
return this.charset ;
}// getCharset()
/**
* Returns the file extension. The extension is considered to be any sequence of characters behind the
* {@code .} in the file name. If the given file is {@code null} or has no extension, {@code null} is returned.
*
* @param file
* file whose extension is requested
*
* @return Extension of the given file or {@code null}
*
* @see #getExtension(String)
*/
public static String getExtension(File file) {
return getExtension(file.getName()) ;
}// getExtension(File)
/**
* Returns the file extension. The extension is considered to be any sequence of characters behind the
* {@code .} in the file name. If the given file is {@code null} or has no extension, {@code null} is returned.
*
* @param name
* name or path of the file whose extension is requested
*
* @return Extension of the given file or {@code null}
*/
public static String getExtension(String name) {
if (name == null) return null ;
int posDot = name.lastIndexOf('.') ;
if (posDot == -1) return null ;
return name.substring(posDot+1) ;
}// getExtension(String)
/**
* Indicates if at least one generator is selected.
*
* @return {@code true} if at least one generator is selected,
* {@code false} otherwise
*/
public boolean atLeastOneFormatSelected() {
return !this.selectedGenerators.isEmpty() ;
}// atLeastOneFormatSelected()
/**
* Adds the given generator to the list of currently selected
* generators.
*
* @param formatId
* id of the added generator
*/
public void addSelectedFormat(String formatId) {
if (!this.selectedGenerators.contains(formatId)) {
this.selectedGenerators.add(formatId) ;
}// if
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
}// addSelectedFormat(String)
/**
* Removes the given generator to the list of currently selected
* generators.
*
* @param formatId
* id of the removed generator
*/
public void removeSelectedFormat(String formatId) {
this.selectedGenerators.remove(formatId) ;
try {
this.notifyObservers() ;
} catch (KameleonException ke) {
this.displayDebugInformation(ke) ;
}// try
}// removeSelectedFormat(String)
/**
* Indicates whether is possible to make a generation. A generation
* is possible if:
* <ul>
* <li>a file is selected
* <li>the format of the selected file is selected
* <li>at least one generator is selected
* </ul>
*
* @return {@code true} if is possible to make a generation,
* {@code false} otherwise
*/
public boolean generationIsPossible() {
return this.fileIsSelected()
&& this.currentFileFormatIsKnown()
&& this.atLeastOneFormatSelected() ;
}// generationIsPossible()
/**
* Returns all the ids of the selected generators. If no generator
* is selected, an empty list is returned.
*
* @return instance of {@code List<String>} with all the ids
* of the selected generators
*/
public List<String> getSelectedOutputFormats() {
return this.selectedGenerators ;
}// getSelectedOutputFormats()
}// class FileModel