/*
 This file is part of the software framework JStatCom, 
 a library for building applications for data based analysis.

 Copyright (C) 2000-2005  Markus Kraetzig

 This library is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as
 published by the Free Software Foundation; either version 2.1 of the
 License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 USA
 */

package com.jstatcom.project;

import java.awt.event.ActionEvent;
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.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;
import javax.swing.ListModel;

import javolution37.javolution.xml.ObjectReader;
import javolution37.javolution.xml.ObjectWriter;
import javolution37.javolution.xml.XmlFormat;

import org.apache.log4j.Logger;

import com.jstatcom.component.StdMessages;
import com.jstatcom.component.TopFrame;
import com.jstatcom.component.TopFrameReference;
import com.jstatcom.io.FileSupport;

/**
 * Manages storing and retrieving project data and project states for all
 * modules. This class is a Singleton. All modules taking part in the project
 * management system must register with this object with the method
 * addProjectStateHandler at program start. On deserialization of
 * projects, the data is restored and the modules states are set.
 * 
 * @author Markus Kraetzig 
 */
public final class ProjectManager {
    private static final Logger log = Logger.getLogger(ProjectManager.class);

    private final Map addedPackagePrefsMap = new HashMap();

    private static final ProjectManager INSTANCE = new ProjectManager();

    private final Map handlerMap = new HashMap();

    private final Map projectStatesMap = new HashMap();

    private AbstractAction serializationAction = null;

    private AbstractAction deSerializationAction = null;

    private TopFrame topFrame = null;

    /**
     * Default constructor.
     */
    private ProjectManager() {
        // not to be invoked
    }

    /**
     * Gets singleton instance of this class.
     * 
     * @return project manager
     */
    public static ProjectManager getInstance() {
        return INSTANCE;
    }

    /**
     * Gets an abstract action instance that can be reused by components to
     * invoke project serialization. For example, it could be used by "Save
     * Project" button.
     * 
     * @return abstract action
     */
    public AbstractAction getSerializationAction() {
        if (serializationAction == null) {
            URL url = getClass().getResource("/images/saveprojectas.gif");
            Icon icon = null;
            if (url != null)
                icon = new ImageIcon(url);
            serializationAction = new AbstractAction("Save Project As", icon) {

                public void actionPerformed(ActionEvent evt) {
                    ProjectDialog pDialog = ProjectDialog.getInstance();
                    pDialog.setSaveDialog(true);
                    pDialog.setVisible(true);
                    if (pDialog.isCancelled())
                        return;
                    ProjectTreeNode node = pDialog.getProjectNode();
                    if (node == null)
                        throw new IllegalStateException(
                                "Project node from dialog was null.");
                    try {
                        ProjectManager.getInstance().serializeProject(
                                node.getProjectFile(), node.getName(),
                                node.getDescription());
                    } catch (Throwable ex) {
                        String msg = "There was an error saving a project to file:\n\""
                                + node.getProjectFile().getAbsolutePath()
                                + "\".";
                        log.error(msg, ex);
                        StdMessages.error(msg + "\nThe error message was:\n\""
                                + ex.getMessage()
                                + "\"\nPlease see the log for more details.",
                                "Project Save Error");
                    }

                }
            };
            serializationAction.putValue(Action.ACCELERATOR_KEY, KeyStroke
                    .getKeyStroke("control S"));
        }
        return serializationAction;
    }

    /**
     * Gets an abstract action instance that can be reused by components to
     * invoke project deserialization. For example, it could be used by "Open
     * Project" button.
     * 
     * @return abstract action
     */
    public AbstractAction getDeSerializationAction() {
        if (deSerializationAction == null) {
            URL url = getClass().getResource("/images/openproject.gif");
            Icon icon = null;
            if (url != null)
                icon = new ImageIcon(url);
            deSerializationAction = new AbstractAction("Open Project", icon) {

                public void actionPerformed(ActionEvent evt) {
                    File file = FileSupport.getInstance().selectProjectFile(
                            TopFrameReference.getTopFrameRef());
                    if (file == null)
                        return;
                    try {
                        ProjectManager.getInstance().deSerializeProject(file);
                    } catch (Throwable ex) {
                        String msg = "There was an error loading a project from file:\n\""
                                + file.getAbsolutePath() + "\".";
                        log.error(msg, ex);
                        StdMessages.error(msg + "\nThe error message was:\n\""
                                + ex.getMessage()
                                + "\"\nPlease see the log for more details.",
                                "Project Load Error");
                    }

                }
            };
            deSerializationAction.putValue(Action.ACCELERATOR_KEY, KeyStroke
                    .getKeyStroke("control O"));

        }
        return deSerializationAction;
    }

    /**
     * Serializes the fully specified project to the file
     * currentProjectFile.
     * 
     * @param currentProjectFile
     *            file to use for project serialization
     * @param project
     *            fully specified Project instance
     * @throws IllegalArgumentException
     *             if (currentProjectFile == null || project == null)
     */
    public void setProjectToFile(File currentProjectFile, Project project) {
        if (currentProjectFile == null || project == null)
            throw new IllegalArgumentException("Argument was null.");
        if (topFrame != null)
            project.setCurrentModuleFrame(topFrame.getTopModuleByName());

        try {
            BufferedOutputStream fout = new BufferedOutputStream(
                    new FileOutputStream(currentProjectFile));

            OutputStream out = new GZIPOutputStream(fout);
            ObjectWriter objectWriter = new ObjectWriter();
            objectWriter.setPackagePrefix("", "com.jstatcom.model");
            objectWriter.setPackagePrefix("jstatcom", "com.jstatcom");
            for (String nam : addedPackagePrefsMap.keySet())
                objectWriter.setPackagePrefix(nam, addedPackagePrefsMap
                        .get(nam));

            objectWriter.write(project, out); // Uses UTF-8
            fout.close();
        } catch (IOException ivjExc) {
            throw new RuntimeException(ivjExc);
        }
    }

    /**
     * Adds a package prefix. This can be used to reduce the size of the created
     * XML file. For example, in the XML header it will appear as
     * xmlns:jstatcom="java:com.jstatcom", where "jstatcom" is
     * the name and "com.jstatcom" the package name. The package name will be
     * replaced by the name in all classes of that package in the XML file.
     * 

* The package name "jstatcom" and "com.jstatcom" is reserved and must not * be used. * * @param name * @param packageName * @throws IllegalArgumentException * if null or empty argument or if * name of packageName has already * been used. */ public void addPackagePrefix(String name, String packageName) { if (name == null || packageName == null) throw new IllegalArgumentException("Argument was null."); if (name.length() == 0 || packageName.length() == 0) throw new IllegalArgumentException("Argument was empty."); if (name.equalsIgnoreCase("jstatcom") || packageName.equalsIgnoreCase("com.jstatcom")) throw new IllegalArgumentException( "Tried to overwrite default package prefix."); if (addedPackagePrefsMap.containsKey(name)) throw new IllegalArgumentException("Package prefix \"" + name + "\" already set."); if (addedPackagePrefsMap.containsValue(packageName)) throw new IllegalArgumentException("Package prefix for \"" + packageName + "\" already set."); addedPackagePrefsMap.put(name, packageName); } /** * Stores the current state as a project to currentProjectFile. * * @param currentProjectFile * the file to store the project to * @param name * the name of the project * @param description * the project description * @throws IllegalArgumentException * if (currentProjectFile == null) */ public void serializeProject(File currentProjectFile, String name, String description) { if (currentProjectFile == null) throw new IllegalArgumentException("Argument was null."); Project project = new Project(name, description); if (topFrame != null) project.setCurrentModuleFrame(topFrame.getTopModuleByName()); ListModel listModel = OutputList.getInstance().getModel(); for (int i = 0; i < listModel.getSize(); i++) project.addOutputData((OutputData) listModel.getElementAt(i)); // data types ProjectData[] pData = ProjectDataManager.getInstance().getProjectData(); for (int i = 0; i < pData.length; i++) project.addProjectData(pData[i]); // project states for (Iterator iter = handlerMap.values().iterator(); iter.hasNext();) { ProjectStateHandler element = (ProjectStateHandler) iter.next(); project.addProjectState(element.getState()); } setProjectToFile(currentProjectFile, project); ProjectTreeNode node = new ProjectTreeNode(project, currentProjectFile, true); ProjectTree.getInstance().addProjectNode(node); } /** * Gets project from currentProjectFile. * * @param currentProjectFile * the file to read the project from * @return deserialized project * @throws IllegalArgumentException * if (currentProjectFile == null) */ public Project getProjectFromFile(File currentProjectFile) { if (currentProjectFile == null) throw new IllegalArgumentException("Project file was null."); if (!currentProjectFile.isFile() || !currentProjectFile.canRead()) throw new IllegalArgumentException("Project file " + currentProjectFile.getAbsolutePath() + " does not exist or is not readable."); // deserialize project ObjectReader constructor = new ObjectReader(); Project project = null; try { try { // GZIPped is default case, but might fail InputStream fin = new BufferedInputStream(new FileInputStream( currentProjectFile)); InputStream in = new GZIPInputStream(fin); project = (Project) constructor.read(in); in.close(); } catch (Throwable t) { // probably no gzip project = null; log.warn("GZIP import failed for file \"" + currentProjectFile + "\": " + t.getMessage()); } InputStream fin = new BufferedInputStream(new FileInputStream( currentProjectFile)); if (project == null) // read uncompressed project = (Project) constructor.read(fin); fin.close(); } catch (Throwable t) { log.error("XML deserialization failed", t); } return project; } /** * Restores project from currentProjectFile. * * @param currentProjectFile * the file to read the project from * @return reference to the newly created project tree node * @throws IllegalArgumentException * if (currentProjectFile == null) */ public ProjectTreeNode deSerializeProject(File currentProjectFile) { Project project = getProjectFromFile(currentProjectFile); ProjectTreeNode node = new ProjectTreeNode(project, currentProjectFile, true); ProjectTree.getInstance().addProjectNode(node); // set data for (Iterator iter = project.getProjectDataIterator(); iter.hasNext();) { ProjectData element = (ProjectData) iter.next(); ProjectDataManager.getInstance().setProjectData(element); } // set output OutputList.getInstance().removeAll(); for (Iterator iter = project.getOutputDataIterator(); iter.hasNext();) { OutputData element = (OutputData) iter.next(); OutputList.getInstance().addOutput(element); } // set handler states from retrieved states for (Iterator iter = project.getProjectStatesIterator(); iter.hasNext();) { ProjectState element = (ProjectState) iter.next(); ProjectStateHandler handler = handlerMap .get(element.getHandlerID()); if (handler != null) try { handler.setState(element); } catch (Throwable t) { log.error("Failed to set state for element: " + element, t); } else projectStatesMap.put(element.getHandlerID(), element); } if (topFrame != null) { String top = topFrame.getTopModuleByName(); String newCurrent = project.getCurrentModuleFrame(); if (!newCurrent.equals(top)) topFrame.setTopModuleByName(newCurrent); } return node; } /** * Adds a project state handler, typically a modelling module, to the list * of all handlers. The state will be stored as part of the project and it * is restored when the project is deserialized. *

* This method should be called at program start when all handlers are being * initialized. * * @param handler * element of a project that hanldles project states, usually a * ModelFrame * @throws IllegalArgumentException * if (handler == null) */ public void addProjectStateHandler(ProjectStateHandler handler) { if (handler == null) throw new IllegalArgumentException("Argument was null."); handlerMap.put(handler.getID(), handler); if (projectStatesMap.containsKey(handler.getID())) try { handler.setState(projectStatesMap.get(handler.getID())); } catch (Throwable t) { log.error("Failed to set state for handler ID:" + handler.getID(), t); } } /** * Sets the reference to the TopFrame component that can be * used for (de)serializing projects. It is not strictly required to use * this setting. * * @param topFrame * reference to the TopFrame instance that is used * as the main application frame */ public void setTopFrame(TopFrame topFrame) { this.topFrame = topFrame; } /** * Gets the reference to the TopFrame component that has been * set earlier. * * @return reference to the currently set TopFrame instance * or null if none has been set before */ public TopFrame getTopFrame() { return topFrame; } /** * Adds a classname transformation. This is useful to load serialized XML project files * after a classname was changed. * * @param oldClassName full classname before renaming * @param newClassName full classname after renaming */ public void addClassTransformation(String oldClassName, String newClassName) { XmlFormat.addClassTransformation(oldClassName, newClassName); } }

Ads help maintain this website.