/*
* This file is part of the OSMembrane project.
* More informations under www.osmembrane.de
*
* The project is licensed under the GNU GENERAL PUBLIC LICENSE 3.0.
* for more details about the license see http://www.osmembrane.de/license/
*
* Source: $HeadURL$ ($Revision$)
* Last changed: $Date$
*/
package de.osmembrane.model.pipeline;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Stack;
import de.osmembrane.model.ModelProxy;
import de.osmembrane.model.algorithms.GraphPlanarizer;
import de.osmembrane.model.algorithms.TarjanAlgorithm;
import de.osmembrane.model.parser.ParserFactory;
import de.osmembrane.model.persistence.AbstractPersistence;
import de.osmembrane.model.persistence.FileException;
import de.osmembrane.model.persistence.FileType;
import de.osmembrane.model.persistence.OSMembranePersistence;
import de.osmembrane.model.persistence.PersistenceFactory;
import de.osmembrane.model.persistence.PipelinePersistenceObject;
import de.osmembrane.model.pipeline.PipelineObserverObject.ChangeType;
import de.osmembrane.model.settings.SettingType;
import de.osmembrane.resources.Constants;
import de.osmembrane.tools.Tools;
/**
* Implementation of {@link AbstractPipeline}.
*
* @author jakob_jarosch
*/
public class Pipeline extends AbstractPipeline {
private Stack<PipelineMemento> undoStack;
private PipelineMemento currentState;
private Stack<PipelineMemento> redoStack;
private List<AbstractFunction> functions;
private URL pipelineFilename;
private AbstractPipelineSettings pipelineSettings;
private boolean savedState;
/**
* Says if the pipeline is silent or not.<br/>
* In the silent-mode the pipeline will not inform any observers.
*/
private boolean silent;
/**
* Says if the pipeline uses undo-redo or not.<br/>
* in disabled mode the pipeline does not save any undo redo steps.
*/
private boolean undoRedoDisabled;
/**
* Creates a default pipeline with<br/>
*
* @link {@link Pipeline#silent} = false
*/
public Pipeline() {
this(false);
}
/**
* Creates a default pipeline with<br/>
*
* @link {@link Pipeline#undoRedoDisabled} = false
*/
public Pipeline(boolean silent) {
this(silent, false);
}
/**
* Constructor for {@link Pipeline}.
*/
public Pipeline(boolean silent, boolean undoRedoDisabled) {
this.functions = new ArrayList<AbstractFunction>();
this.undoStack = new Stack<PipelineMemento>();
this.redoStack = new Stack<PipelineMemento>();
this.silent = silent;
this.undoRedoDisabled = undoRedoDisabled;
this.savedState = true;
this.pipelineFilename = null;
this.pipelineSettings = new PipelineSettings();
/* register the Observer of Persistence to the Pipeline */
addObserver(PersistenceFactory.getInstance());
}
@Override
public AbstractPipelineSettings getSettings() {
return pipelineSettings;
}
@Override
public void clear() {
this.functions.clear();
this.undoStack.clear();
this.redoStack.clear();
pipelineSettings = new PipelineSettings();
pipelineFilename = null;
changeSavedState(true);
/* notify the observers */
changedNotifyObservers(new PipelineObserverObject(
ChangeType.FULLCHANGE, null).setCreateUndoStep(false));
}
@Override
public AbstractFunction[] getFunctions() {
AbstractFunction[] functions = new AbstractFunction[this.functions
.size()];
functions = this.functions.toArray(functions);
return functions;
}
@Override
public void addFunction(AbstractFunction func) {
func.setPipeline(this);
func.addObserver(this);
functions.add(func);
/* notify the observers */
changedNotifyObservers(new PipelineObserverObject(
ChangeType.ADD_FUNCTION, func));
}
@Override
public boolean deleteFunction(AbstractFunction func) {
boolean returnValue = false;
for (AbstractFunction function : functions) {
if (function == func) {
function.unlinkConnectors();
returnValue = functions.remove(function);
break;
}
}
if (returnValue == true) {
/* notify the observers */
changedNotifyObservers(new PipelineObserverObject(
ChangeType.DELETE_FUNCTION, func));
}
return returnValue;
}
@Override
public boolean isComplete() {
/* check all functions */
for (AbstractFunction function : getFunctions()) {
if (!function.isComplete()) {
return false;
}
}
/* all functions seems to be complete, so the pipeline is also complete. */
return true;
}
@Override
public void loadPipeline(URL filename) throws FileException {
AbstractPersistence persistence = PersistenceFactory.getInstance()
.getPersistence(OSMembranePersistence.class);
PipelinePersistenceObject pipeline = (PipelinePersistenceObject) persistence
.load(filename);
clear();
pipelineFilename = filename;
this.functions = pipeline.getFunctions();
for (AbstractFunction function : functions) {
function.addObserver(this);
}
this.pipelineSettings = pipeline.getSettings();
changeSavedState(true);
/* notify the observers */
changedNotifyObservers(new PipelineObserverObject(
ChangeType.FULLCHANGE, null).setCreateUndoStep(false));
}
@Override
public void savePipeline() throws FileException {
savePipeline(pipelineFilename);
}
@Override
public void savePipeline(URL filename) throws FileException {
AbstractPersistence persistence = PersistenceFactory.getInstance()
.getPersistence(OSMembranePersistence.class);
persistence.save(filename, new PipelinePersistenceObject(functions,
pipelineSettings));
pipelineFilename = filename;
/* Saved successfully (persistence has not thrown a FileException */
changeSavedState(true);
changedNotifyObservers(new PipelineObserverObject(
ChangeType.SAVED_PIPELINE, null).setCreateUndoStep(false));
}
@Override
public boolean isSaved() {
return savedState;
}
@Override
public URL getFilename() {
return pipelineFilename;
}
@Override
public void backupPipeline() throws FileException {
AbstractPersistence persistence = PersistenceFactory.getInstance()
.getPersistence(OSMembranePersistence.class);
persistence.save(Constants.DEFAULT_BACKUP_FILE,
new PipelinePersistenceObject(functions, pipelineSettings));
}
@Override
public boolean isBackupAvailable() {
File file = Tools.urlToFile(Constants.DEFAULT_BACKUP_FILE);
return file.isFile();
}
@Override
public void loadBackup() throws FileException {
loadPipeline(Constants.DEFAULT_BACKUP_FILE);
}
@Override
public void clearBackup() {
if (isBackupAvailable()) {
Tools.urlToFile(Constants.DEFAULT_BACKUP_FILE).delete();
}
}
@Override
public void importPipeline(URL filename, FileType type)
throws FileException {
AbstractPersistence persistence = PersistenceFactory.getInstance()
.getPersistence(type.getPersistenceClass());
PipelinePersistenceObject pipeline = (PipelinePersistenceObject) persistence
.load(filename);
clear();
this.functions = pipeline.getFunctions();
for (AbstractFunction function : functions) {
function.addObserver(this);
}
this.pipelineSettings = pipeline.getSettings();
/* notify the observers */
changedNotifyObservers(new PipelineObserverObject(
ChangeType.FULLCHANGE, null));
}
@Override
public String generate(FileType filetype) {
return ParserFactory
.getInstance()
.getParser(filetype.getParserClass())
.parsePipeline(
new PipelinePersistenceObject(functions,
pipelineSettings));
}
@Override
public void exportPipeline(URL filename, FileType type)
throws FileException {
AbstractPersistence persistence = PersistenceFactory.getInstance()
.getPersistence(type.getPersistenceClass());
persistence.save(filename, new PipelinePersistenceObject(functions,
pipelineSettings));
}
@Override
public boolean hasLoop() {
TarjanAlgorithm check = new TarjanAlgorithm(functions);
check.run();
/* check the SCCs */
List<List<AbstractFunction>> sccs = check.getSCC();
for (List<AbstractFunction> scc : sccs) {
if (scc.size() == 1) {
/*
* check if the scc with size 1 links to itself or is just
* standing alone
*/
AbstractFunction function = scc.get(0);
for (AbstractConnector outConnector : function
.getOutConnectors()) {
for (AbstractConnector inConnector : outConnector
.getConnections()) {
if (inConnector.getParent() == function) {
/* found a connection to the function itself */
return true;
}
}
}
} else if (scc.size() > 1) {
return true;
}
}
return false;
}
@Override
public void arrangePipeline() {
GraphPlanarizer gprizer = new GraphPlanarizer(functions);
gprizer.planarize();
changedNotifyObservers(new PipelineObserverObject(
ChangeType.FULLCHANGE, null));
}
@Override
public boolean undo() {
if (!undoAvailable()) {
return false;
}
redoStack.push(currentState);
restoreMemento(undoStack.pop());
return true;
}
@Override
public boolean undoAvailable() {
return !undoStack.isEmpty();
}
@Override
public boolean redo() {
if (!redoAvailable()) {
return false;
}
undoStack.push(currentState);
restoreMemento(redoStack.pop());
return true;
}
@Override
public boolean redoAvailable() {
return !redoStack.isEmpty();
}
@Override
public void update(Observable o, Object arg) {
/* only PipelineObserverObjects will be passed through */
if (arg instanceof PipelineObserverObject) {
changedNotifyObservers((PipelineObserverObject) arg);
}
}
@Override
protected void changedNotifyObservers(PipelineObserverObject poo) {
poo.setPipeline(this);
/* check if the undo-step is really required, or disabled. */
if (poo.createUndoStep() && !undoRedoDisabled) {
/* any changes made, set savedState to false */
changeSavedState(false);
}
if (!silent) {
this.setChanged();
this.notifyObservers(poo);
}
}
private void changeSavedState(boolean state) {
this.savedState = state;
if (state == false) {
saveStep();
} else {
/*
* Update the savedState for the current item (nothing changed in
* the pipeline only the state should be updated.
*/
currentState = new PipelineMemento(functions, savedState);
}
}
private void saveStep() {
undoStack.push(currentState);
redoStack.clear();
currentState = new PipelineMemento(functions, savedState);
int maximumStackSize = ((Integer) ModelProxy.getInstance()
.getSettings().getValue(SettingType.MAXIMUM_UNDO_STEPS))
.intValue();
while (undoStack.size() > maximumStackSize) {
undoStack.remove(0);
}
}
private void restoreMemento(PipelineMemento memento) {
this.functions = memento.getFunctions();
this.savedState = memento.getSavedState();
this.currentState = memento;
for (AbstractFunction function : functions) {
function.addObserver(this);
}
changedNotifyObservers(new PipelineObserverObject(
ChangeType.FULLCHANGE, null).setCreateUndoStep(false));
}
}
/**
* Private internal class of the memento of the undo-/redo-feature.
*
* @author jakob_jarosch
*/
class PipelineMemento {
private List<AbstractFunction> functions = new ArrayList<AbstractFunction>();
private boolean savedState;
public PipelineMemento(List<AbstractFunction> functions, boolean savedState) {
this.functions = deepCopyFunctions(functions);
this.savedState = savedState;
}
public boolean getSavedState() {
return savedState;
}
public List<AbstractFunction> getFunctions() {
return deepCopyFunctions(functions);
}
private List<AbstractFunction> deepCopyFunctions(
List<AbstractFunction> functions) {
/* Use serialization to create a copy of the functions in the pipeline */
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(functions);
ByteArrayInputStream bais = new ByteArrayInputStream(
baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object deepCopy = ois.readObject();
@SuppressWarnings("unchecked")
List<AbstractFunction> copy = (List<AbstractFunction>) deepCopy;
return copy;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return functions;
}
}