com.jaspersoft.studio.editor.defaults.DefaultManager.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.studio.editor.defaults.DefaultManager.java

Source

/*******************************************************************************
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 * 
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 * 
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package com.jaspersoft.studio.editor.defaults;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import net.sf.jasperreports.eclipse.ui.util.UIUtils;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.design.JRDesignElement;
import net.sf.jasperreports.engine.design.JRDesignElementGroup;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.type.BandTypeEnum;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import net.sf.jasperreports.engine.xml.JRXmlDigesterFactory;
import net.sf.jasperreports.engine.xml.JRXmlLoader;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.jaspersoft.studio.JaspersoftStudioPlugin;
import com.jaspersoft.studio.compatibility.JRXmlWriterHelper;
import com.jaspersoft.studio.model.INode;
import com.jaspersoft.studio.model.MGraphicElement;
import com.jaspersoft.studio.model.MReport;
import com.jaspersoft.studio.model.MRoot;
import com.jaspersoft.studio.model.band.MBand;
import com.jaspersoft.studio.model.util.ReportFactory;
import com.jaspersoft.studio.preferences.util.PropertiesHelper;
import com.jaspersoft.studio.utils.jasper.JasperReportsConfiguration;

/**
 * Default manager that keep loaded the actual default template set (if present) 
 * and offer the methods to load another template set or apply it to the new
 * elements. Use the static INSTANCE method to access to the default manager
 * 
 * @author Orlandin Marco
 *
 */
public class DefaultManager {

    /**
     * The Default Manager is global and it is accessible only trough this class
     */
    public static final DefaultManager INSTANCE = new DefaultManager();

    /**
     * The properties file
     */
    private Preferences prefs = PropertiesHelper.INSTANCE_SCOPE
            .getNode(JaspersoftStudioPlugin.getUniqueIdentifier());

    /**
     * Key used in the application properties file to store the list of template set and the actual selected one
     */
    private static final String DEFAULT_KEY = "defaultValues";

    /**
     * List of the available template sets. Each item is the absolute path to the template set file
     */
    private List<String> availableDefaults;

    /**
     * The path to the selected template set file, it's null if no file is selected
     */
    private String actualDefault = null;

    /**
     * The root of the model of the selected template set file, it's null if no file is selected
     */
    private INode defaultReport = null;

    /**
     * The jasperconfiguration of the selected template set file, it's null if no file is selected
     */
    private JasperReportsConfiguration defaultConfig = null;

    /**
     * Map to keep cached each default model in the selected template with it's type. In this 
     * way it's easy to find if there is a template for a specific type and provide an object
     * that offers the method to apply the model to the new element
     */
    private HashMap<Class<?>, MGraphicElement> selectedDefaultsMap;

    /**
     * Resource listener used to see when a resource is deleted and update the template 
     * list if the resource was a template set
     */
    IResourceChangeListener resourceDeletedListener = new IResourceChangeListener() {

        @Override
        public void resourceChanged(IResourceChangeEvent event) {
            List<IFile> resourcesDeleted = new ArrayList<IFile>();
            if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
                iterateResourceDelta(event.getDelta(), resourcesDeleted);
                for (IFile resource : resourcesDeleted) {
                    String resourceString = resource.getRawLocation().toOSString();
                    availableDefaults.remove(resourceString);
                    if (resourceString.equals(actualDefault)) {
                        actualDefault = null;
                        defaultReport = null;
                        defaultConfig = null;
                    }
                }
                savePreferences();
            }
        }
    };

    /**
     * Constructor, since it's private the class can be only accessed by the INSTANCE method
     */
    private DefaultManager() {
        initializeDefaultManager();
        ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceDeletedListener);
    };

    /**
     * Save inside the application properties file the list of all the defined template sets
     * as an xml string
     */
    private void savePreferences() {
        String xmlData = getXMLData();
        prefs.put(DEFAULT_KEY, xmlData);
        try {
            prefs.flush();
        } catch (BackingStoreException e) {
            e.printStackTrace();
        }
    }

    /**
     * Create the xml string with the definition of all the current template sets
     * 
     * @return xml definition
     */
    private String getXMLData() {
        String result = "<defaultReports>";
        for (String defaultReport : availableDefaults) {
            result += "<report path=\"" + defaultReport + "\" default=\"";
            result += defaultReport.equals(actualDefault) + "\"/>";
        }
        result += "</defaultReports>";
        return result;
    }

    /**
     * Load the list of available template sets and if it is defined load the model
     * of the selected template set
     */
    private void initializeDefaultManager() {
        loadPreferences();
        if (actualDefault != null) {
            loadDefaultModel();
        }
    }

    /**
     * Load the list of the template set from the preferences file. If one of them has the default 
     * attribute set to true then it will be the selected default. If more of them has the attribute
     * to true only the last one will be the default. A template set is added to the available ones
     * only if it's file exist
     */
    private void loadPreferences() {
        String defaults = prefs.get(DEFAULT_KEY, "");
        availableDefaults = new ArrayList<String>();
        if (!defaults.isEmpty()) {
            try {
                Document document = JRXmlUtils.parse(new InputSource(new StringReader(defaults)));
                NodeList adapterNodes = document.getDocumentElement().getChildNodes();
                for (int i = 0; i < adapterNodes.getLength(); ++i) {
                    Node adapterNode = adapterNodes.item(i);
                    if (adapterNode.getNodeType() == Node.ELEMENT_NODE) {
                        String path = adapterNode.getAttributes().getNamedItem("path").getNodeValue(); //$NON-NLS-1$
                        String defaultReport = adapterNode.getAttributes().getNamedItem("default").getNodeValue(); //$NON-NLS-1$
                        //Check file existence
                        if (new File(path).exists()) {
                            availableDefaults.add(path);
                            if (Boolean.parseBoolean(defaultReport))
                                actualDefault = path;
                        }
                    }
                }
            } catch (JRException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Load the current default template set, creating it's model structure
     */
    private void loadDefaultModel() {
        InputStream in = null;
        selectedDefaultsMap = new HashMap<Class<?>, MGraphicElement>();
        defaultReport = null;
        try {
            File defaultFile = new File(actualDefault);
            if (defaultFile.exists()) {
                in = new ByteArrayInputStream(FileUtils.readFileToByteArray(defaultFile));
                JasperReportsConfiguration jConfig = getDefaultJRConfig();
                JasperDesign jd = new JRXmlLoader(jConfig, JRXmlDigesterFactory.createDigester(jConfig))
                        .loadXML(in);
                jConfig.setJasperDesign(jd);
                defaultReport = ReportFactory.createReport(jConfig);
                defaultReport.getChildren().get(0).setValue(jd);
                defaultConfig = jConfig;
                setElementsType();
            }
        } catch (Exception e) {
            actualDefault = null;
            defaultReport = null;
            e.printStackTrace();
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                }
        }
    }

    /**
     * Recursive method called when some resource changes, it search for deleted resources inside
     * the delta hierarchy
     * 
     * @param delta actual level of the delta hierarchy
     * @param deleteResources the list of deleted resources actually found
     */
    private void iterateResourceDelta(IResourceDelta delta, List<IFile> deleteResources) {
        if (delta.getKind() == IResourceDelta.REMOVED && delta.getResource() instanceof IFile) {
            deleteResources.add((IFile) delta.getResource());
        }
        for (IResourceDelta affectedResource : delta.getAffectedChildren()) {
            iterateResourceDelta(affectedResource, deleteResources);
        }
    }

    /**
     * Return a JasperReports configuration for the selected Template Set 
     * 
     * @param the file to the JRXML of the template set
     * @return a not null jasperreports configuration
     */
    private JasperReportsConfiguration getDefaultJRConfig() {
        return new JasperReportsConfiguration(DefaultJasperReportsContext.getInstance(), null);
    }

    /**
     * Inspect the loaded model to search the detail band and all it's content to the type cache map
     */
    private void setElementsType() {
        //Search for the detail band
        if (defaultReport == null || defaultReport.getChildren().isEmpty() || !(defaultReport instanceof MRoot))
            return;
        for (INode node : defaultReport.getChildren().get(0).getChildren()) {
            if (node instanceof MBand) {
                MBand band = (MBand) node;
                if (band.getBandType() == BandTypeEnum.DETAIL) {
                    addAll(band);
                    break;
                }
            }
        }
    }

    /**
     * Recursive method to add all nodes inside the current parent to the the type cache map.
     * The key is the node type and the value is the node itself
     * 
     * @param parent the current parent
     */
    private void addAll(INode parent) {
        for (INode node : parent.getChildren()) {
            if (node instanceof MGraphicElement) {
                selectedDefaultsMap.put(node.getClass(), (MGraphicElement) node);
            }
            addAll(node);
        }
    }

    /**
     * Return if there is a default selected
     * 
     * @return true if there is a default selected, false otherwise
     */
    public boolean hasDefault() {
        return actualDefault != null && defaultReport != null;
    }

    /**
     * Apply a template of a specific type to an element. The element and 
     * the class type must have a compatible type. If there is not a defined
     * template for the requested type then it dosen't do anything
     * 
     * @param modelType the type of the request template model
     * @param element the element to which the template will be applied. Typically the
     * model type is the ANode that contains (or will contain) the element 
     */
    public void applyDefault(Class<?> modelType, JRElement element) {
        if (DefaultManager.INSTANCE.hasDefault()) {
            MGraphicElement defaultSetter = selectedDefaultsMap.get(modelType);
            if (defaultSetter != null) {
                defaultSetter.trasnferProperties(element);
            }
        }
    }

    /**
     * Get the value of a property of the template of a specified type. If the
     * type is not available, or the property is not found or there isn't a 
     * template set selected the it return null
     * 
     * @param modelType the type of the template
     * @param propertyId the name of the property
     * @return the value of the property
     */
    public Object getDefaultPropertiesValue(Class<?> modelType, String propertyId) {
        if (hasDefault()) {
            MGraphicElement container = selectedDefaultsMap.get(modelType);
            if (container != null) {
                return container.getPropertyValue(propertyId);
            }
        }
        return null;
    }

    /**
     * Set the current default template set. It will be loaded 
     * and the preferences will be saved if valid
     * 
     * @param path the absolute path to the template set file
     */
    public void setDefaultFile(String path) {
        String oldDefault = actualDefault;
        INode oldDefaultReport = defaultReport;
        JasperReportsConfiguration oldConfig = defaultConfig;
        HashMap<Class<?>, MGraphicElement> oldDefaultMap = selectedDefaultsMap;
        actualDefault = path;
        try {
            loadDefaultModel();
            savePreferences();
        } catch (Exception e) {
            actualDefault = oldDefault;
            defaultReport = oldDefaultReport;
            defaultConfig = oldConfig;
            selectedDefaultsMap = oldDefaultMap;
            e.printStackTrace();
        }
    }

    /**
     * Unset the any selected template set. It will be loaded and
     * the preference saved
     */
    public void unsetDefaultFile() {
        actualDefault = null;
        defaultReport = null;
        defaultConfig = null;
        savePreferences();
    }

    /**
     * Check if a template set is the currently default one
     * 
     * @param path the absolute path to the template set file that you 
     * want to check
     */
    public boolean isCurrentDefault(String path) {
        return path.equals(actualDefault);
    }

    /**
     * Add a new template set
     * 
     * @param path the path to the template set file 
     * @param isCurrentDefault true if the added template set should also be used as default, 
     * false otherwise
     */
    public void addDefaultFile(String path, boolean isCurrentDefault) {
        boolean invalidDefault = false;
        availableDefaults.add(path);
        if (isCurrentDefault) {
            actualDefault = path;
            try {
                loadDefaultModel();
            } catch (Exception e) {
                availableDefaults.remove(path);
                invalidDefault = true;
                actualDefault = null;
                defaultReport = null;
                defaultConfig = null;
                e.printStackTrace();
            }
        }
        if (!invalidDefault) {
            savePreferences();
        }
    }

    /**
     * Return a list of all the available template set. The list 
     * is calculated only when it is requested
     * 
     * @return a list of string where each element is an absolute path to a 
     * template set
     */
    public List<String> getAvailableElements() {
        if (availableDefaults == null) {
            initializeDefaultManager();
        }
        return availableDefaults;
    }

    /**
     * Recursive method to search a jr type inside the current parent and if it is
     * found remove the element with that type and return its parent
     * 
     * @param element use the type of this element to search another element of the same type in the children of the current parent
     * @param actualNode the current parent
     * @return the parent of the removed node if found, null otherwise
     */
    private INode searchAndRemoveOld(JRDesignElement element, INode actualNode) {
        for (INode node : actualNode.getChildren()) {
            if (node.getValue() != null && node.getValue().getClass().equals(element.getClass())
                    && actualNode.getValue() instanceof JRDesignElementGroup) {
                //found must delete and return the parent
                Object parent = actualNode.getValue();
                ((JRDesignElementGroup) parent).removeElement((JRDesignElement) node.getValue());
                return actualNode;
            } else {
                INode subResult = searchAndRemoveOld(element, node);
                if (subResult != null)
                    return subResult;
            }
        }
        return null;
    }

    /**
     * Return the detail band of the actually selected template set
     * 
     * @return the model of the detail band or null if it can't be found
     */
    private INode searchDetailBand() {
        MReport mReport = (MReport) defaultReport.getChildren().get(0);
        for (INode child : mReport.getChildren()) {
            if (child instanceof MBand && child.getValue() != null) {
                MBand band = (MBand) child;
                if (band.getBandType() == BandTypeEnum.DETAIL)
                    return band;
            }
        }
        return null;
    }

    /**
     * Add an element to the current selected template set. The element is 
     * added inside the detail band and if there was an element of the same 
     * type of the added one the old one is removed
     * 
     * @param element element to add
     */
    private void addNewElement(JRDesignElement element) {
        INode parent = searchDetailBand();
        Assert.isNotNull(parent, "The Template Set report must have a detail band");
        if (parent != null) {
            searchAndRemoveOld(element, parent);
            ((JRDesignElementGroup) parent.getValue()).addElement(element);
        }
    }

    /**
     * Add an element to the current selected template set. The element is 
     * added inside the detail band and if there was an element of the same 
     * type of the added one the old one is removed. The element is added always
     * in position 0,0 of the detail band. If the template set hasn't a detail band
     * an error is shown
     * The template set file is updated automatically and if there isn't a selected
     * default template set this method dosen't do anything 
     * 
     * @param element element to add
     */
    public void addElementToCurrentDefault(MGraphicElement element, boolean copyAttributesFromStyles) {
        if (hasDefault()) {
            JRDesignElement newElement = (JRDesignElement) element.getValue().clone();
            if (copyAttributesFromStyles)
                CustomStyleResolver.copyInheritedAttributes(element, newElement);
            newElement.setX(0);
            newElement.setY(0);
            selectedDefaultsMap.put(element.getClass(),
                    (MGraphicElement) ReportFactory.createNode(null, newElement, 0));
            addNewElement(newElement);
            //Update the template file
            UIUtils.getDisplay().syncExec(new Runnable() {

                @Override
                public void run() {
                    try {
                        IFile reportFile = getFileFromPath(actualDefault);
                        String xml = model2xml(JRXmlWriterHelper.LAST_VERSION);
                        reportFile.setContents(
                                new ByteArrayInputStream(
                                        xml.getBytes(net.sf.jasperreports.eclipse.util.FileUtils.UTF8_ENCODING)),
                                IFile.KEEP_HISTORY | IFile.FORCE, new NullProgressMonitor());
                    } catch (Throwable e) {
                        UIUtils.showError(e);
                    }
                }
            });
        }
    }

    /**
     * Get an IFile form an absolute path
     * 
     * @param pathString the absolute path in the filesystem
     * @return the IFile in the workspace
     */
    private IFile getFileFromPath(String pathString) {
        IPath path = new Path(pathString);
        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IFile reportFile = workspace.getRoot().getFileForLocation(path);
        return reportFile;
    }

    /**
     * Generate the xml definition of the current selected template set
     * report
     * 
     * @param version version of the jrxml
     * @return an ml string
     */
    private String model2xml(String version) {
        String xml = null;
        try {
            JasperDesign report = null;
            MReport mReport = (MReport) defaultReport.getChildren().get(0);
            if (mReport != null) {
                report = mReport.getJasperDesign();
            }
            xml = JRXmlWriterHelper.writeReport(defaultConfig, report,
                    net.sf.jasperreports.eclipse.util.FileUtils.UTF8_ENCODING, version);
        } catch (Throwable e) {
            UIUtils.showError(e);
        }
        return xml;
    }

    /**
     * The name of the current template set selected
     * 
     * @return the name of the selected template set or an empty string
     * if no template set is selected
     */
    public String getDefaultName() {
        if (hasDefault()) {
            return new File(actualDefault).getName();
        }
        return "";
    }

    /**
     * Remove a template set from the available template sets list
     * and save the new list on the preferences
     * 
     * @param path the path of the template set to remove
     */
    public void removeDefaultFile(String path) {
        if (isCurrentDefault(path))
            unsetDefaultFile();
        for (String defaults : availableDefaults) {
            if (path.equals(defaults)) {
                availableDefaults.remove(defaults);
                savePreferences();
                break;
            }
        }

    }

    /**
     * Reload the current default from it's file in the workspace
     * Used to refresh the default when the file is modified manually
     */
    public void reloadCurrentDefault() {
        if (actualDefault != null) {
            loadDefaultModel();
        }
    }

}