de.interactive_instruments.ShapeChange.Model.EA.EADocument.java Source code

Java tutorial

Introduction

Here is the source code for de.interactive_instruments.ShapeChange.Model.EA.EADocument.java

Source

/**
 * ShapeChange - processing application schemas for geographic information
 *
 * This file is part of ShapeChange. ShapeChange takes a ISO 19109
 * Application Schema from a UML model and translates it into a
 * GML Application Schema or other implementation representations.
 *
 * Additional information about the software can be found at
 * http://shapechange.net/
 *
 * (c) 2002-2014 interactive instruments GmbH, Bonn, Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:
 * interactive instruments GmbH
 * Trierer Strasse 70-72
 * 53115 Bonn
 * Germany
 */

package de.interactive_instruments.ShapeChange.Model.EA;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;

import javax.imageio.ImageIO;

import de.interactive_instruments.ShapeChange.Options;
import de.interactive_instruments.ShapeChange.ShapeChangeAbortException;
import de.interactive_instruments.ShapeChange.ShapeChangeResult;
import de.interactive_instruments.ShapeChange.StructuredNumber;
import de.interactive_instruments.ShapeChange.Model.ClassInfo;
import de.interactive_instruments.ShapeChange.Model.ImageMetadata;
import de.interactive_instruments.ShapeChange.Model.Model;
import de.interactive_instruments.ShapeChange.Model.ModelImpl;
import de.interactive_instruments.ShapeChange.Model.PackageInfo;
import de.interactive_instruments.ShapeChange.Model.PropertyInfo;
import de.interactive_instruments.ShapeChange.UI.StatusBoard;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.CharUtils;
import org.sparx.*;

public class EADocument extends ModelImpl implements Model {

    public static final int STATUS_EADOCUMENT_INITSTART = 101;
    public static final int STATUS_EADOCUMENT_READMODEL = 102;
    public static final int STATUS_EADOCUMENT_ESTABLISHCLASSES = 103;
    public static final int STATUS_EADOCUMENT_READCONSTARINTS = 104;

    public Options options = null;
    public ShapeChangeResult result = null;

    /** EA repository object */
    protected Repository repository = null;

    /** Character endcoding */
    protected final String characterEncoding = "Windows-1252";

    /** Caches for all classes and packages */
    // Id to classes ...
    SortedMap<String, ClassInfoEA> fClassById = new TreeMap<String, ClassInfoEA>();
    // Name to classes ...
    SortedMap<String, ClassInfoEA> fClassByName = new TreeMap<String, ClassInfoEA>();
    // Id to packages ...
    SortedMap<String, PackageInfoEA> fPackageById = new TreeMap<String, PackageInfoEA>();
    // ElmtId to packages ...
    SortedMap<String, PackageInfoEA> fPackageByElmtId = new TreeMap<String, PackageInfoEA>();
    // AssociationId to associations ...
    SortedMap<String, AssociationInfoEA> fAssociationById = new TreeMap<String, AssociationInfoEA>();

    protected Set<String> excludedPackageNames = null;

    public EADocument() {
        super();
    }

    public EADocument(ShapeChangeResult r, Options o, String repositoryFileName) throws ShapeChangeAbortException {
        super();
        initialise(r, o, repositoryFileName);
    }

    /** Return options and configuration object. */
    public Options options() {
        return options;
    } // options()

    /** Return result object for error reporting. */
    public ShapeChangeResult result() {
        return result;
    } // result()

    /** Connect to EA Repository with security information*/
    public void initialise(ShapeChangeResult r, Options o, String repositoryFileNameOrConnectionString,
            String username, String password) throws ShapeChangeAbortException {

        options = o;
        result = r;

        StatusBoard.getStatusBoard().statusChanged(STATUS_EADOCUMENT_INITSTART);

        /** Connect to EA repository */
        repository = new Repository();

        if (!repository.OpenFile2(repositoryFileNameOrConnectionString, username, password)) {
            String errormsg = repository.GetLastError();
            r.addFatalError(null, 35, errormsg, repositoryFileNameOrConnectionString, username, password);
            throw new ShapeChangeAbortException();
        }

        executeCommonInitializationProcedure();
    }

    /** Connect to EA Repository without security information */
    public void initialise(ShapeChangeResult r, Options o, String repositoryFileNameOrConnectionString)
            throws ShapeChangeAbortException {

        options = o;
        result = r;

        StatusBoard.getStatusBoard().statusChanged(STATUS_EADOCUMENT_INITSTART);

        String connectionString;

        /** Determine if we are dealing with a file or server based repository*/

        if (repositoryFileNameOrConnectionString.contains("DBType=")
                || repositoryFileNameOrConnectionString.contains("Connect=Cloud")) {

            /* We are dealing with a server based repository. */

            connectionString = repositoryFileNameOrConnectionString;

        } else {

            /* We have an EAP file. Ensure that it exists */

            java.io.File repfile = new java.io.File(repositoryFileNameOrConnectionString);
            boolean ex = true;
            if (!repfile.exists()) {
                ex = false;
                if (!repositoryFileNameOrConnectionString.toLowerCase().endsWith(".eap")) {
                    repositoryFileNameOrConnectionString += ".eap";
                    repfile = new java.io.File(repositoryFileNameOrConnectionString);
                    ex = repfile.exists();
                }
            }
            if (!ex) {
                r.addFatalError(null, 31, repositoryFileNameOrConnectionString);
                throw new ShapeChangeAbortException();
            }

            connectionString = repfile.getAbsolutePath();
        }

        /** Connect to EA Repository */
        repository = new Repository();

        if (!repository.OpenFile(connectionString)) {
            String errormsg = repository.GetLastError();
            r.addFatalError(null, 30, errormsg, connectionString);
            throw new ShapeChangeAbortException();
        }

        executeCommonInitializationProcedure();
    }

    public void executeCommonInitializationProcedure() throws ShapeChangeAbortException {

        // determine if specific packages should not be loaded
        this.excludedPackageNames = options.getExcludedPackages();

        /** Cache classes and packages */
        // First set up initial evaluation tasks of packages consisting
        // of the models in the repository
        class EvalTask {
            PackageInfoEA fatherPI;
            org.sparx.Package eaPackage;

            EvalTask(PackageInfoEA fpi, org.sparx.Package p) {
                fatherPI = fpi;
                eaPackage = p;
            }
        }

        StatusBoard.getStatusBoard().statusChanged(STATUS_EADOCUMENT_READMODEL);

        LinkedList<EvalTask> evalp = new LinkedList<EvalTask>();
        Collection<org.sparx.Package> model = repository.GetModels();
        for (org.sparx.Package p : model) {

            // Check if this model and all its contents shall be excluded
            String name = p.GetName();
            if (excludedPackageNames != null && excludedPackageNames.contains(name)) {
                // stop processing this model and continue with the next
                continue;
            }

            evalp.addLast(new EvalTask(null, p));
        }

        // Now remove tasks from the list, adding further tasks as we proceed
        // until we have no more tasks to evaluate
        while (evalp.size() > 0) {
            // Remove next evaluation task
            EvalTask et = evalp.removeFirst();
            org.sparx.Package pack = et.eaPackage;
            PackageInfoEA fpi = et.fatherPI;

            // Check if this package and all its contents shall be excluded from
            // the model
            String name = pack.GetName();
            if (excludedPackageNames != null && excludedPackageNames.contains(name)) {
                // stop processing this package and continue with the next
                continue;
            }

            // Add to package cache. The PackageInfo Ctor does the necessary
            // parent/child linkage of packages
            Element packelmt = pack.GetElement();
            PackageInfoEA pi = new PackageInfoEA(this, fpi, pack, packelmt);
            fPackageById.put(pi.id(), pi);
            if (packelmt != null)
                this.fPackageByElmtId.put(new Integer(packelmt.GetElementID()).toString(), pi);
            // Now pick all classes and add these to their to caches.
            for (org.sparx.Element elmt : pack.GetElements()) {
                String type = elmt.GetType();
                if (!type.equals("DataType") && !type.equals("Class") && !type.equals("Interface")
                        && !type.equals("Enumeration"))
                    continue;
                ClassInfoEA ci = new ClassInfoEA(this, pi, elmt);
                fClassById.put(ci.id(), ci);
                // TODO What's happening to identical class names? How is this
                // supposed to be handled? Open issue.While classifier names
                // have to be
                // unique per app schema only, it is a legacy from Rational Rose
                // that it is expected that classifier names are unique in the
                // whole
                // model. The correct solution would be to add namespace
                // qualifiers.
                fClassByName.put(ci.name(), ci);
            }
            // Add next level packages for further evaluation
            for (org.sparx.Package pnxt : pack.GetPackages()) {
                evalp.addLast(new EvalTask(pi, pnxt));
            }
        }

        StatusBoard.getStatusBoard().statusChanged(STATUS_EADOCUMENT_ESTABLISHCLASSES);

        /**
         * Now that all classes are collected, in a second go establish class
         * derivation hierarchy and all other associations between classes.
         */
        for (ClassInfoEA ci : fClassById.values()) {

            // Generalization - class derivation hierarchy
            ci.establishClassDerivationHierarchy();
            // Other associations where the class is source or target
            ci.establishAssociations();
        }

        String checkingConstraints = options.parameter("checkingConstraints");
        if (checkingConstraints == null || !checkingConstraints.toLowerCase().trim().equals("disabled")) {
            StatusBoard.getStatusBoard().statusChanged(STATUS_EADOCUMENT_READCONSTARINTS);

            // TODO The following may be removed when constraints have been
            // tested.
            /** In a third go collect all constraints */
            for (ClassInfoEA ci : fClassById.values()) {
                ci.constraints();
                SortedMap<StructuredNumber, PropertyInfo> props = ci.properties();
                for (PropertyInfo pi : props.values())
                    pi.constraints();
            }
        }

        /**
         * Loop over all schemas (i.e packages with a target namespace) and
         * store the schema location, so that it can be added in import
         * statements
         */
        SortedSet<PackageInfo> schemas = schemas("");
        for (Iterator<PackageInfo> i = schemas.iterator(); i.hasNext();) {
            PackageInfo pi = i.next();
            options.addSchemaLocation(pi.targetNamespace(), pi.xsdDocument());
        }

        // ==============================
        // load diagrams if so requested
        String loadDiagrams = options.parameter("loadDiagrams");

        if (loadDiagrams != null && loadDiagrams.equalsIgnoreCase("true")) {

            java.io.File tmpDir = options.imageTmpDir();

            if (tmpDir.exists()) {

                // probably content from previous run, delete the content of the directory
                try {
                    FileUtils.deleteDirectory(tmpDir);
                } catch (IOException e) {
                    result.addWarning(null, 34, tmpDir.getAbsolutePath());
                }

                if (!tmpDir.exists()) {
                    try {
                        FileUtils.forceMkdir(tmpDir);
                    } catch (IOException e) {
                        result.addWarning(null, 32, tmpDir.getAbsolutePath());
                    }
                }
            }

            AtomicInteger imgIdCounter = new AtomicInteger(0);

            SortedSet<? extends PackageInfo> selectedSchema = this.selectedSchemas();

            for (PackageInfo pi : selectedSchema) {

                if (pi == null) {
                    continue;
                }

                // Only process schemas in a namespace and name that matches a
                // user-selected pattern
                if (options.skipSchema(null, pi))
                    continue;

                saveDiagrams(imgIdCounter, "img", tmpDir, escapeFileName(tmpDir.getName()), pi);
            }
        }

    } // EA Document Ctor

    /**
     * Replace all spaces with underscores and removes non-ASCII characters and removes ASCII characters that are not printable.
     *
     * @param filename
     * @return
     */
    private String escapeFileName(String filename) {

        if (filename == null) {

            return null;

        } else {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < filename.length(); i++) {
                char aChar = filename.charAt(i);
                if (aChar == ' ') {
                    sb.append('_');
                } else if (CharUtils.isAsciiPrintable(aChar)) {
                    sb.append(aChar);
                } else {
                    // ignore character if not ASCII printable
                }
            }
            return sb.toString();
        }
    }

    private void saveDiagrams(AtomicInteger imgIdCounter, String imgIdPrefix, java.io.File targetFolder,
            String relPathWithTargetFolder, PackageInfo pi) {

        if (!targetFolder.exists()) {
            targetFolder.mkdir();
        }

        java.io.File pi_folder = new java.io.File(targetFolder, escapeFileName(pi.name()));

        if (!pi_folder.mkdir()) {
            result.addWarning(null, 32, pi_folder.getAbsolutePath());
        }

        String newRelPathWithTargetFolder = relPathWithTargetFolder + "/" + escapeFileName(pi.name());

        Project projectInterface = repository.GetProjectInterface();

        PackageInfoEA piEa = fPackageById.get(pi.id());

        List<Diagram> diagramList = getDiagramsOfPackage(piEa);

        String packageDiagramRegex = options.parameter("packageDiagramRegex");
        if (packageDiagramRegex == null) {
            packageDiagramRegex = Options.IMAGE_INCLUSION_PACKAGE_REGEX;
        }
        String classDiagramRegex = options.parameter("classDiagramRegex");
        if (classDiagramRegex == null) {
            classDiagramRegex = Options.IMAGE_INCLUSION_CLASS_REGEX;
        }

        String elementNameKeyForMatching = Options.ELEMENT_NAME_KEY_FOR_DIAGRAM_MATCHING;
        String regexForModelElement;

        for (Diagram d : diagramList) {

            String imgId = imgIdPrefix + imgIdCounter.incrementAndGet();
            String imgFileName = escapeFileName(imgId + ".jpg");
            String imgName = d.GetName();

            java.io.File img = new java.io.File(pi_folder, imgFileName);

            String relPathToFile = newRelPathWithTargetFolder + "/" + imgFileName;

            String type = d.GetType();

            /*
             * before saving the diagram, ensure that it is relevant for at
             * least one model element
             */
            boolean relevantDiagram = false;

            if (type.equalsIgnoreCase("Package")) {

                regexForModelElement = packageDiagramRegex.replaceAll(elementNameKeyForMatching, pi.name());
                if (imgName.matches(regexForModelElement)) {
                    relevantDiagram = true;
                }

            } else if (type.equalsIgnoreCase("Logical")) {

                regexForModelElement = packageDiagramRegex.replaceAll(elementNameKeyForMatching, pi.name());
                if (imgName.matches(regexForModelElement)) {
                    relevantDiagram = true;
                }

                SortedSet<ClassInfo> clTmp = this.classes(pi);

                if (clTmp == null || clTmp.isEmpty()) {

                    // no classes in package, thus the logical diagram cannot be
                    // relevant

                } else if (relevantDiagram) {
                    // we have already established that this is a relevant diagram

                } else {

                    for (ClassInfo ci : clTmp) {

                        // only process classes from this package
                        if (ci.pkg() == pi) {

                            regexForModelElement = classDiagramRegex.replaceAll(elementNameKeyForMatching,
                                    ci.name());
                            if (imgName.matches(regexForModelElement)) {
                                relevantDiagram = true;
                                // we established that this is a relevant diagram
                                break;
                            }
                        }
                    }
                }
            } else {
                // unsupported diagram type -> irrelevant
            }

            if (!relevantDiagram) {
                continue;
            }

            repository.OpenDiagram(d.GetDiagramID());
            projectInterface.SaveDiagramImageToFile(img.getAbsolutePath());
            repository.CloseDiagram(d.GetDiagramID());

            BufferedImage bimg;
            int width = 400;
            int height = 400;

            try {

                bimg = ImageIO.read(img);
                width = bimg.getWidth();
                height = bimg.getHeight();

            } catch (IOException e) {
                result.addError(null, 33, imgName, pi.name());
                e.printStackTrace(System.err);
                continue;
            }

            ImageMetadata imgMeta = new ImageMetadata(imgId, imgName, img, relPathToFile, width, height);

            if (type.equalsIgnoreCase("Package")) {

                // we already checked that the diagram is relevant for this
                // package
                addDiagramToPackage(pi, imgMeta);

            } else if (type.equalsIgnoreCase("Logical")) {

                regexForModelElement = packageDiagramRegex.replaceAll(elementNameKeyForMatching, pi.name());
                if (imgName.matches(regexForModelElement)) {
                    addDiagramToPackage(pi, imgMeta);
                }

                SortedSet<ClassInfo> clTmp = this.classes(pi);

                if (clTmp == null || clTmp.isEmpty()) {

                    continue;

                } else {

                    for (ClassInfo ci : clTmp) {

                        // only process classes from this package
                        if (ci.pkg() == pi) {

                            regexForModelElement = classDiagramRegex.replaceAll(elementNameKeyForMatching,
                                    ci.name());
                            if (imgName.matches(regexForModelElement)) {
                                addDiagramToClass(ci, imgMeta);
                            }

                        }
                    }
                }
            } else {
                // unsupported diagram type - irrelevant
            }
        }

        SortedSet<PackageInfo> children = pi.containedPackages();

        if (children != null) {

            for (PackageInfo piChild : children) {

                if (piChild.targetNamespace().equals(pi.targetNamespace())) {
                    saveDiagrams(imgIdCounter, imgIdPrefix, pi_folder, newRelPathWithTargetFolder, piChild);
                }
            }
        }
    }

    /**
     * @return list of diagrams of the given package. The list is sorted by name if parameter sortDiagramsByName is set to true (default),
     * or by the order in the EA model if set to false.
     */
    private List<Diagram> getDiagramsOfPackage(PackageInfoEA piEa) {
        // note that this is an org.sparx.Collection
        Collection<Diagram> diagrams = piEa.eaPackage.GetDiagrams();

        List<Diagram> diagramList = new ArrayList<Diagram>();
        for (Diagram d : diagrams) {
            diagramList.add(d);
        }

        boolean sortDiagramsByName;
        String paramSortDiagramsByName = options.parameter("sortDiagramsByName");
        if (paramSortDiagramsByName == null) {
            sortDiagramsByName = true; // default
        } else {
            sortDiagramsByName = Boolean.parseBoolean(paramSortDiagramsByName);
        }
        if (sortDiagramsByName) {
            Collections.sort(diagramList, new Comparator<Diagram>() {

                @Override
                public int compare(Diagram o1, Diagram o2) {
                    return o1.GetName().compareTo(o2.GetName());
                }

            });
        }
        return diagramList;
    }

    private void addDiagramToClass(ClassInfo ci, ImageMetadata imgMeta) {

        List<ImageMetadata> ciDiagrams = ci.getDiagrams();

        if (ciDiagrams == null) {
            ciDiagrams = new ArrayList<ImageMetadata>();
        }

        ciDiagrams.add(imgMeta);

        ci.setDiagrams(ciDiagrams);
    }

    private void addDiagramToPackage(PackageInfo pi, ImageMetadata imgMeta) {

        List<ImageMetadata> piDiagrams = pi.getDiagrams();

        if (piDiagrams == null) {
            piDiagrams = new ArrayList<ImageMetadata>();
        }

        piDiagrams.add(imgMeta);

        pi.setDiagrams(piDiagrams);
    }

    /** Return character encoding of repository (Windows-1252) */
    public String characterEncoding() {
        return characterEncoding;
    } // charEncoding()

    /** Return ClassInfo object given the id of a class */
    public ClassInfo classById(String id) {
        return fClassById.get(id);
    } // classById()

    /** Return ClassInfo object given the name of the class */
    // TODO To be clarified: How are classes treated, which have identical names
    // but reside in different packages? See above.
    public ClassInfo classByName(String nam) {
        return fClassByName.get(nam);
    } // classByName()

    /**
     * @see de.interactive_instruments.ShapeChange.Model.Model#classes(de.interactive_instruments.ShapeChange.Model.PackageInfo)
     */
    public SortedSet<ClassInfo> classes(PackageInfo pi) {
        // To hold the result ...
        SortedSet<ClassInfo> res = new TreeSet<ClassInfo>();
        // Get targetNamespace. Needed to find out, when we enter other app
        // schemas.
        String tns = pi.targetNamespace();

        return addClasses((PackageInfoEA) pi, tns, res);
    } // classes()

    /**
     * Return all ClassInfo objects contained in the given package and in sub-
     * packages, which do not belong to an app schema different to the one of
     * the given package.
     */
    // 2014-04-03: clarify javadoc
    private SortedSet<ClassInfo> addClasses(PackageInfoEA pi, String tns, SortedSet<ClassInfo> res) {
        // Are we a different app schema? If so, skip
        String ctns = pi.targetNamespace();
        if (ctns != null && (tns == null || !tns.equals(ctns)))
            return res;
        // Same app schema, first add classes to output ...
        res.addAll(pi.childCI);
        // .. then descend to next packages
        for (PackageInfoEA cpi : pi.childPI) {
            res = addClasses(cpi, tns, res);
        }
        return res;
    } // addClasses()

    /** Return PackageInfo object given the id of a package */
    public PackageInfo packageById(String id) {
        return fPackageById.get(id);
    } // packageById()

    /**
     * Collect and return all PackageInfo objects tagged as being a schema. If a
     * name is given, only the package with the specified name will be
     * considered.
     */
    public SortedSet<PackageInfo> schemas(String name) {
        SortedSet<PackageInfo> res = new TreeSet<PackageInfo>();
        for (PackageInfo pi : fPackageById.values()) {
            if (pi.isSchema())
                if (name != null && !name.equals("")) {
                    if (pi.name().equals(name))
                        res.add(pi);
                } else
                    res.add(pi);
            else if (pi.isAppSchema()) {
                // Validation is done later now, code can be removed
                // MessageContext mc = result.addError(null, 146, pi.name(),
                // "targetNamespace");
                // if (mc!=null) mc.addDetail(null,400,"Package",pi.fullName());
            }
        }
        return res;
    } // schemas()

    /** Return the model input type */
    public int type() {
        return Options.EA7;
    } // type()

    /** Shutdown EA model and quit EA */
    public void shutdown() {
        if (repository != null) {
            repository.CloseFile();
            repository.Exit();
            repository = null;
        }
    }

    /**
     * This auxiliary static method replaces some SGML entities which EA7.5
     * substitutes for a few symbols in some string fields, but does not bother
     * to translate back in its automation interface.
     */
    public static String removeSpuriousEA75EntitiesFromStrings(String s) {
        // Straight return if there is nothing to do
        if (!s.contains("&"))
            return s;
        // Replacement of spurious entities
        return s.replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&").replaceAll("&euro;",
                "");
    }

    /**
     * Return repository object (for applications using only the EA model
     * option)
     */
    public Repository repository() {
        return repository;
    }

    @Override
    public HashSet<PackageInfo> packages() {
        HashSet<PackageInfo> allPackages = new HashSet<PackageInfo>();
        for (PackageInfo pi : fPackageById.values()) {
            allPackages.add(pi);
        }
        return allPackages;
    }

}