de.uni_koblenz.jgralab.utilities.rsa2tg.Rsa2Tg.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_koblenz.jgralab.utilities.rsa2tg.Rsa2Tg.java

Source

/*
 * JGraLab - The Java Graph Laboratory
 *
 * Copyright (C) 2006-2014 Institute for Software Technology
 *                         University of Koblenz-Landau, Germany
 *                         ist@uni-koblenz.de
 *
 * For bug reports, documentation and further information, visit
 *
 *                         https://github.com/jgralab/jgralab
 *
 * 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>.
 *
 * Additional permission under GNU GPL version 3 section 7
 *
 * If you modify this Program, or any covered work, by linking or combining
 * it with Eclipse (or a modified version of that program or an Eclipse
 * plugin), containing parts covered by the terms of the Eclipse Public
 * License (EPL), the licensors of this Program grant you additional
 * permission to convey the resulting work.  Corresponding Source for a
 * non-source form of such a combination shall include the source code for
 * the parts of JGraLab used as well as that of the covered work.
 */

package de.uni_koblenz.jgralab.utilities.rsa2tg;

import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.DEFAULT_MAX_MULTIPLICITY;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.DEFAULT_MIN_MULTIPLICITY;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ANNOTATED_ELEMENT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ASSOCIATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ASSOCIATION_CLASS;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_AGGREGATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_ASSOCIATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_CLASSIFIER;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_CLIENT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_CONSTRAINED_ELEMENT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_GENERAL;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_HREF;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_ISDERIVED;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_IS_ABSRACT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_KEY;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_NAME;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_SUPPLIER;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_TYPE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ATTRIBUTE_VALUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_BODY;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_CLASS;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_COMPOSITE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_DEFAULT_VALUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_DETAILS;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ENUMERATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_ENUMERATION_LITERAL;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_E_ANNOTATIONS;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_GENERALIZATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_INSTANCE_VALUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_LANGUAGE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_LITERAL_BOOLEAN;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_LITERAL_INTEGER;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_LITERAL_STRING;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_LOWER_VALUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_MEMBER_END;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_MODEL;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OPAQUE_EXPRESSION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OWNEDEND;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OWNEDRULE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OWNED_ATTRIBUTE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OWNED_COMMENT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_OWNED_LITERAL;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_PACKAGE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_PACKAGED_ELEMENT;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_PRIMITIVE_TYPE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_PROPERTY;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_REALIZATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_SHARED;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_SPECIFICATION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_TRUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.UML_UPPER_VALUE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.XMI_EXTENSION;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.XMI_NAMESPACE_PREFIX;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.XMI_TYPE;
import static de.uni_koblenz.jgralab.utilities.rsa2tg.XMIConstants.XMI_XMI;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.pcollections.PVector;

import de.uni_koblenz.ist.utilities.option_handler.OptionHandler;
import de.uni_koblenz.ist.utilities.xml.XmlProcessor;
import de.uni_koblenz.jgralab.AttributedElement;
import de.uni_koblenz.jgralab.Edge;
import de.uni_koblenz.jgralab.EdgeDirection;
import de.uni_koblenz.jgralab.ImplementationType;
import de.uni_koblenz.jgralab.JGraLab;
import de.uni_koblenz.jgralab.Vertex;
import de.uni_koblenz.jgralab.exception.GraphIOException;
import de.uni_koblenz.jgralab.graphmarker.BooleanGraphMarker;
import de.uni_koblenz.jgralab.graphmarker.GraphMarker;
import de.uni_koblenz.jgralab.graphvalidator.ConstraintViolation;
import de.uni_koblenz.jgralab.graphvalidator.GraphValidator;
import de.uni_koblenz.jgralab.greql.GreqlQuery;
import de.uni_koblenz.jgralab.grumlschema.GrumlSchema;
import de.uni_koblenz.jgralab.grumlschema.SchemaGraph;
import de.uni_koblenz.jgralab.grumlschema.domains.CollectionDomain;
import de.uni_koblenz.jgralab.grumlschema.domains.Domain;
import de.uni_koblenz.jgralab.grumlschema.domains.EnumDomain;
import de.uni_koblenz.jgralab.grumlschema.domains.HasRecordDomainComponent;
import de.uni_koblenz.jgralab.grumlschema.domains.MapDomain;
import de.uni_koblenz.jgralab.grumlschema.domains.RecordDomain;
import de.uni_koblenz.jgralab.grumlschema.domains.StringDomain;
import de.uni_koblenz.jgralab.grumlschema.structure.AggregationKind;
import de.uni_koblenz.jgralab.grumlschema.structure.Annotates;
import de.uni_koblenz.jgralab.grumlschema.structure.Attribute;
import de.uni_koblenz.jgralab.grumlschema.structure.AttributedElementClass;
import de.uni_koblenz.jgralab.grumlschema.structure.ComesFrom;
import de.uni_koblenz.jgralab.grumlschema.structure.Comment;
import de.uni_koblenz.jgralab.grumlschema.structure.Constraint;
import de.uni_koblenz.jgralab.grumlschema.structure.ContainsGraphElementClass;
import de.uni_koblenz.jgralab.grumlschema.structure.EdgeClass;
import de.uni_koblenz.jgralab.grumlschema.structure.EndsAt;
import de.uni_koblenz.jgralab.grumlschema.structure.GoesTo;
import de.uni_koblenz.jgralab.grumlschema.structure.GraphClass;
import de.uni_koblenz.jgralab.grumlschema.structure.GraphElementClass;
import de.uni_koblenz.jgralab.grumlschema.structure.HasAttribute;
import de.uni_koblenz.jgralab.grumlschema.structure.HasDomain;
import de.uni_koblenz.jgralab.grumlschema.structure.IncidenceClass;
import de.uni_koblenz.jgralab.grumlschema.structure.NamedElement;
import de.uni_koblenz.jgralab.grumlschema.structure.Package;
import de.uni_koblenz.jgralab.grumlschema.structure.Schema;
import de.uni_koblenz.jgralab.grumlschema.structure.SpecializesEdgeClass;
import de.uni_koblenz.jgralab.grumlschema.structure.SpecializesVertexClass;
import de.uni_koblenz.jgralab.grumlschema.structure.VertexClass;
import de.uni_koblenz.jgralab.utilities.tg2dot.Tg2Dot;

/**
 * Rsa2Tg is a utility that converts XMI files exported from IBM (tm) Rational
 * Software Architect (tm) into a TG schema file. The converter is based on a
 * SAX parser. As intermediate format, a grUML schema graph is created from the
 * XMI elements.
 * 
 * @author ist@uni-koblenz.de
 * 
 */
public class Rsa2Tg extends XmlProcessor {

    private static final String OPTION_FILENAME_VALIDATION = "r";

    private static final String OPTION_FILENAME_SCHEMA_GRAPH = "s";

    private static final String OPTION_FILENAME_DOT = "e";

    private static final String OPTION_FILENAME_SCHEMA = "o";

    private static final String OPTION_USE_NAVIGABILITY = "n";

    private static final String OPTION_REMOVE_UNUSED_DOMAINS = "u";

    private static final String OPTION_KEEP_EMPTY_PACKAGES = "k";

    private static final String OPTION_USE_ROLE_NAME = "f";

    private static final String OPTION_REMOVE_COMMENTS = "c";

    private static final String OPTION_IGNORE_UNKNOWN_STEREOTYPES = "c";

    /**
     * Contains XML element names in the format "name>xmiId"
     */
    private Stack<String> xmiIdStack;

    /**
     * The schema graph.
     */
    private SchemaGraph sg;

    /**
     * The {@link Schema} vertex of the schema graph.
     */
    private Schema schema;

    /**
     * The {@link GraphClass} vertex of the schema graph.
     */
    private GraphClass graphClass;

    /**
     * A Stack containing the package hierarchy. Packages and their nesting are
     * represented as tree in XML. The top element is the current package.
     */
    private Stack<Package> packageStack;

    /**
     * Maps XMI-Ids to vertices and edges of the schema graph.
     */
    private Map<String, Vertex> idMap;

    /**
     * Remembers the current class id for processing of nested elements.
     */
    private String currentClassId;

    /**
     * Remembers the current {@link VertexClass}/{@link EdgeClass} vertex for
     * processing of nested elements.
     */
    private AttributedElementClass currentClass;

    /**
     * Remembers the current {@link RecordDomain} vertex for processing of
     * nested elements.
     */
    private RecordDomain currentRecordDomain;

    /**
     * Remembers the current domain component edge for processing of nested
     * elements.
     */
    private HasRecordDomainComponent currentRecordDomainComponent;

    /**
     * Remembers the current {@link Attribute} vertex for processing of nested
     * elements.
     */
    private Attribute currentAttribute;

    /**
     * Marks {@link VertexClass} and {@link EdgeClass} vertices with a set of
     * XMI Ids of superclasses.
     */
    private GraphMarker<Set<String>> generalizations;

    /**
     * Keeps track of 'uml:Realization's (key = client id, value = set of
     * supplier ids) as workaround for missing generalizations between
     * association and association class.
     */
    private Map<String, Set<String>> realizations;

    /**
     * Marks {@link Attribute} vertices with the XMI Id of its type if the type
     * can not be resolved at the time the Attribute is processed.
     */
    private GraphMarker<String> attributeType;

    /**
     * Marks {@link HasRecordDomainComponent} edges with the XMI Id of its type
     * if the type can not be resolved at the time the component is processed.
     */
    private GraphMarker<String> recordComponentType;

    /**
     * Maps qualified names of domains to the corresponding {@link Domain}
     * vertex.
     */
    private Map<String, Domain> domainMap;

    /**
     * A set of preliminary vertices which are created to have a target vertex
     * for edges where the real target can only be created later (i.e. forward
     * references in XMI). After processing is finished, this set must be empty,
     * since each preliminary vertex has to be replaced by the correct vertex.
     */
    private Set<Vertex> preliminaryVertices;

    /**
     * Remembers the current association end edge ({@link To}/{@link From}
     * edge), which can be an ownedEnd or an ownedAttribute, for processing of
     * nested elements.
     */
    private IncidenceClass currentAssociationEnd;

    /**
     * The Set of {@link To}/{@link From} edges, which are represented by
     * ownedEnd elements (used to determine the direction of edges).
     */
    private Set<IncidenceClass> ownedEnds;

    /**
     * True if currently processing a constraint (ownedRule) element.
     */
    private boolean inConstraint;

    /**
     * The XMI Id of the constrained element if the constraint has exactly one
     * constrained element, null otherwise. If set to null, the constraint will
     * be attached to the {@link GraphClass} vertex.
     */
    private String constrainedElementId;

    /**
     * Maps the XMI Id of constrained elements to the list of constraints.
     * Constrains are the character data inside a body element of ownedRule
     * elements.
     */
    private Map<String, List<String>> constraints;

    /**
     * Maps the XMI Id of commented elements to the list of comments.
     */
    private Map<String, List<String>> comments;

    /**
     * When creating {@link EdgeClass} names, also use the role name of the
     * 'from' end.
     */
    private boolean useFromRole;

    /**
     * After processing is complete, remove {@link Domain} vertices which are
     * not used by an attribute or by a record domain component.
     */
    private boolean removeUnusedDomains;

    private boolean removeComments;

    /**
     * After processing is complete, also keep {@link Package} vertices which
     * contain no {@link Domains} and no {@link GraphElementClass}es.
     */
    private boolean keepEmptyPackages;

    /**
     * When determining the edge direction, also take navigability of
     * associations into account (rather than the drawing direction only).
     */
    private boolean useNavigability;

    /**
     * Suppresses the direct output into a dot- and tg-file.
     */
    private boolean suppressOutput;

    /**
     * Filename for the {@link Schema}.
     */
    private String filenameSchema;

    /**
     * Filename for the {@link SchemaGraph};
     */
    private String filenameSchemaGraph;

    /**
     * Filename for dot.
     */
    private String filenameDot;

    private boolean ignoreUnknownStereotypes;

    /**
     * Filename for validation
     */
    private String filenameValidation;

    private String annotatedElementId;

    private boolean inComment;

    private boolean inOwnedAttribute;

    private GreqlQuery edgeClassAcyclicQuery;
    private GreqlQuery vertexClassAcyclicQuery;

    private boolean inDefaultValue;

    private int modelRootElementNestingDepth;

    private Set<Package> ignoredPackages;

    private boolean inSpecification;

    private Set<GraphElementClass> derivedGraphElementClasses;

    /**
     * Processes an XMI-file to a TG-file as schema or a schema in a grUML
     * graph. For all command line options see
     * {@link Rsa2Tg#processCommandLineOptions(String[])}.
     * 
     * @param args
     *            {@link String} array of command line options.
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        System.out.println("RSA to TG");
        System.out.println("=========");
        JGraLab.setLogLevel(Level.OFF);

        // Retrieving all command line options
        CommandLine cli = processCommandLineOptions(args);

        assert cli != null : "No CommandLine object has been generated!";
        // All XMI input files
        File input = new File(cli.getOptionValue('i'));

        Rsa2Tg r = new Rsa2Tg();

        r.setUseFromRole(cli.hasOption(OPTION_USE_ROLE_NAME));
        r.setRemoveUnusedDomains(cli.hasOption(OPTION_REMOVE_UNUSED_DOMAINS));
        r.setKeepEmptyPackages(cli.hasOption(OPTION_KEEP_EMPTY_PACKAGES));
        r.setUseNavigability(cli.hasOption(OPTION_USE_NAVIGABILITY));
        r.setRemoveComments(cli.hasOption(OPTION_REMOVE_COMMENTS));
        r.setIgnoreUnknownStereotypes(cli.hasOption(OPTION_IGNORE_UNKNOWN_STEREOTYPES));

        // apply options
        r.setFilenameSchema(cli.getOptionValue(OPTION_FILENAME_SCHEMA));
        r.setFilenameSchemaGraph(cli.getOptionValue(OPTION_FILENAME_SCHEMA_GRAPH));
        r.setFilenameDot(cli.getOptionValue(OPTION_FILENAME_DOT));
        r.setFilenameValidation(cli.getOptionValue(OPTION_FILENAME_VALIDATION));

        // If no output option is selected, Rsa2Tg will write at least the
        // schema file.
        boolean noOutputOptionSelected = !cli.hasOption(OPTION_FILENAME_SCHEMA)
                && !cli.hasOption(OPTION_FILENAME_SCHEMA_GRAPH) && !cli.hasOption(OPTION_FILENAME_DOT)
                && !cli.hasOption(OPTION_FILENAME_VALIDATION);
        if (noOutputOptionSelected) {
            System.out
                    .println("No output option has been selected. " + "A TG-file for the Schema will be written.");

            // filename have to be set
            r.setFilenameSchema(createFilename(input));
        }

        try {
            System.out.println("processing: " + input.getPath() + "\n");
            r.process(input.getPath());
        } catch (Exception e) {
            System.err.println("An Exception occured while processing " + input + ".");
            System.err.println(e.getMessage());
            e.printStackTrace();
        }

        System.out.println("Fini.");
    }

    public void setRemoveComments(boolean removeComments) {
        this.removeComments = removeComments;
    }

    /**
     * Processes all command line parameters and returns a {@link CommandLine}
     * object, which holds all values included in the given {@link String}
     * array.
     * 
     * @param args
     *            {@link CommandLine} parameters.
     * @return {@link CommandLine} object, which holds all necessary values.
     */
    public static CommandLine processCommandLineOptions(String[] args) {

        // Creates a OptionHandler.
        String toolString = "java " + Rsa2Tg.class.getName();
        String versionString = JGraLab.getInfo(false);

        // TODO Add an additional help string to the help page.
        // This String needs to be included into the OptionHandler, but
        // the functionality is not present.

        // String additional =
        // "If no optional output option is selected, a file with the name "
        // + "\"<InputFileName>.rsa.tg\" will be written."
        // + "\n\n"
        // + toolString;

        OptionHandler oh = new OptionHandler(toolString, versionString);

        // Several Options are declared.
        Option validate = new Option(OPTION_FILENAME_VALIDATION, "report", true,
                "(optional): writes a validation report to the given filename. "
                        + "Free naming, but should look like this: '<filename>.html'");
        validate.setRequired(false);
        validate.setArgName("filename");
        oh.addOption(validate);

        Option export = new Option(OPTION_FILENAME_DOT, "export", true,
                "(optional): writes a GraphViz DOT file to the given filename. "
                        + "Free naming, but should look like this: '<filename>.dot'");
        export.setRequired(false);
        export.setArgName("filename");
        oh.addOption(export);

        Option schemaGraph = new Option(OPTION_FILENAME_SCHEMA_GRAPH, "schemaGraph", true,
                "(optional): writes a TG-file of the Schema as graph instance to the given filename. "
                        + "Free naming, but should look like this:  '<filename>.tg'");
        schemaGraph.setRequired(false);
        schemaGraph.setArgName("filename");
        oh.addOption(schemaGraph);

        Option input = new Option("i", "input", true, "(required): UML 2.1-XMI exchange model file of the Schema.");
        input.setRequired(true);
        input.setArgName("filename");
        oh.addOption(input);

        Option output = new Option(OPTION_FILENAME_SCHEMA, "output", true,
                "(optional): writes a TG-file of the Schema to the given filename. "
                        + "Free naming, but should look like this: '<filename>.rsa.tg.'");
        output.setRequired(false);
        output.setArgName("filename");
        oh.addOption(output);

        Option fromRole = new Option(OPTION_USE_ROLE_NAME, "useFromRole", false,
                "(optional): if this flag is set, the name of from roles will be used for creating undefined EdgeClass names.");
        fromRole.setRequired(false);
        oh.addOption(fromRole);

        Option unusedDomains = new Option(OPTION_REMOVE_UNUSED_DOMAINS, "removeUnusedDomains", false,
                "(optional): if this flag is set, all unused domains be deleted.");
        unusedDomains.setRequired(false);
        oh.addOption(unusedDomains);

        Option removeComments = new Option(OPTION_REMOVE_COMMENTS, "removeComments", false,
                "(optional): if this flag is set, all comments are removed.");
        removeComments.setRequired(false);
        oh.addOption(removeComments);

        Option emptyPackages = new Option(OPTION_KEEP_EMPTY_PACKAGES, "keepEmptyPackages", false,
                "(optional): if this flag is set, empty packages will be retained.");
        emptyPackages.setRequired(false);
        oh.addOption(emptyPackages);

        Option navigability = new Option(OPTION_USE_NAVIGABILITY, "useNavigability", false,
                "(optional): if this flag is set, navigability information will be interpreted as reading direction.");
        navigability.setRequired(false);
        oh.addOption(navigability);

        Option ignoreStereotypes = new Option(OPTION_IGNORE_UNKNOWN_STEREOTYPES, "ignoreUnknownStereotypes", false,
                "(optional): if this flag is set, unknown stereotypes are ignored.");
        ignoreStereotypes.setRequired(false);
        oh.addOption(ignoreStereotypes);

        // Parses the given command line parameters with all created Option.
        return oh.parse(args);
    }

    /**
     * Creates a file path similar to the of <code>inputFile</code>, but with
     * the file extension '.rsa.tg'.
     * 
     * @param file
     *            Is a File object, which is path used to created the new Path.
     * @return New generated Path with the extension '.rsa.tg'.
     */
    public static String createFilename(File file) {
        StringBuilder filenameBuilder = new StringBuilder();

        // The path of the input XMI-file is used.
        filenameBuilder.append(file.getParent());
        filenameBuilder.append(File.separatorChar);

        String filename = file.getName();
        int periodePosition = filename.lastIndexOf('.');
        if (periodePosition != -1) {
            filename = filename.substring(0, periodePosition);
        }

        // The simple name of the Schema will be the filename.
        // filenameBuilder.append(r.getSchemaGraph().getFirstSchema()
        // .get_name());
        filenameBuilder.append(filename);
        // The extension is ....
        filenameBuilder.append(".rsa.tg");
        return filenameBuilder.toString();
    }

    /**
     * Creates a Rsa2Tg converter.
     */
    public Rsa2Tg() {
        // Sets all names of XML-elements, which should be ignored.
        addIgnoredElements("profileApplication", "packageImport", "Ecore:EReference");
    }

    /**
     * Sets up several a {@link SchemaGraph} and data structures before the
     * processing can start.
     */
    @Override
    public void startDocument() {

        sg = GrumlSchema.instance().createSchemaGraph(ImplementationType.STANDARD);

        // Initializing all necessary data structures for processing purposes.
        xmiIdStack = new Stack<>();
        idMap = new HashMap<>();
        packageStack = new Stack<>();
        generalizations = new GraphMarker<>(sg);
        realizations = new HashMap<>();
        attributeType = new GraphMarker<>(sg);
        recordComponentType = new GraphMarker<>(sg);
        domainMap = new HashMap<>();
        preliminaryVertices = new HashSet<>();
        ownedEnds = new HashSet<>();
        constraints = new HashMap<>();
        comments = new HashMap<>();
        ignoredPackages = new HashSet<>();
        modelRootElementNestingDepth = 1;
        derivedGraphElementClasses = new HashSet<>();
    }

    /**
     * Processes a XML element and decides how to handle it in order to get a
     * {@link Schema} element.
     * 
     * @throws XMLStreamException
     */
    @Override
    protected void startElement(String name) throws XMLStreamException {

        // TODO Comment the Meaning of this action. Does '1' or '2' have a
        // meaning?
        if ((getNestingDepth() == 1) && name.equals(XMI_XMI)) {
            modelRootElementNestingDepth = 2;
            return;
        }

        String xmiId = getAttribute(XMI_NAMESPACE_PREFIX, "id");
        xmiIdStack.push(xmiId);

        Vertex vertexId = null;
        if (getNestingDepth() == modelRootElementNestingDepth) {
            // In case of a root element
            vertexId = createDefaultElements(name);
        } else {
            vertexId = processXMIElements(name, xmiId);
        }

        // Links an existing XMI-id to a Vertex-id
        if ((xmiId != null) && (vertexId != null)) {
            idMap.put(xmiId, vertexId);
        }
    }

    private Vertex createDefaultElements(String name) throws XMLStreamException {
        if (name.equals(UML_MODEL) || name.equals(UML_PACKAGE)) {
            setSchemaQualifiedName();
            createGraphClass();
            createDefaultPackage();
        } else {
            // Unexpected root element
            throw new ProcessingException(getParser(), getFilename(),
                    "Root element must be " + UML_MODEL + " or " + UML_PACKAGE + ", buf was " + name);
        }
        return schema;
    }

    private void setSchemaQualifiedName() throws XMLStreamException {
        // Gets the Schema name, creates a Schema and processes it.
        String nm = getAttribute(UML_ATTRIBUTE_NAME);

        int p = nm.lastIndexOf('.');
        schema = sg.createSchema();

        // In case nm (:= Schema-name) contains only a name and not a
        // package prefix
        if (p == -1) {
            throw new ProcessingException(getParser(), getFilename(),
                    "A Schema must have a package prefix!\nProcessed qualified name: " + nm);
        }

        schema.set_packagePrefix(nm.substring(0, p));
        schema.set_name(nm.substring(p + 1));
    }

    private void createGraphClass() {
        // Generates a GraphClass and links it with the created Schema
        graphClass = sg.createGraphClass();
        sg.createDefinesGraphClass(schema, graphClass);
    }

    private void createDefaultPackage() {
        // Creates a default Package, links it and pushes it to the
        // packageStack.
        Package defaultPackage = sg.createPackage();
        defaultPackage.set_qualifiedName("");
        sg.createContainsDefaultPackage(schema, defaultPackage);
        packageStack.push(defaultPackage);
    }

    private Vertex processXMIElements(String name, String xmiId) throws XMLStreamException {
        // inside top level element

        // Type is retrieved
        String type = getAttribute(XMI_NAMESPACE_PREFIX, UML_ATTRIBUTE_TYPE);
        Vertex vertexId = null;
        // Package element, which
        if (name.equals(UML_PACKAGED_ELEMENT)) {
            if (type.equals(UML_PACKAGE)) {
                vertexId = handlePackage();
            } else if (type.equals(UML_CLASS)) {
                vertexId = handleClass(xmiId);
            } else if (type.equals(UML_ASSOCIATION) || type.equals(UML_ASSOCIATION_CLASS)) {
                vertexId = handleAssociation(xmiId);
            } else if (type.equals(UML_ENUMERATION)) {
                vertexId = handleEnumeration();
            } else if (type.equals(UML_PRIMITIVE_TYPE)) {
                vertexId = handlePrimitiveType(xmiId);
            } else if (type.equals(UML_REALIZATION)) {
                handleRealization();
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, type));
            }

        } else if (name.equals(UML_OWNEDRULE)) {
            // Owned rule
            inConstraint = true;
            constrainedElementId = getAttribute(UML_ATTRIBUTE_CONSTRAINED_ELEMENT);
            // If the ID is null, the constraint is attached to the
            // GraphClass

            if (constrainedElementId != null) {
                // There can be more than one ID, separated by spaces ==>
                // the constraint is attached to the GraphClass.
                int p = constrainedElementId.indexOf(' ');
                if (p >= 0) {
                    constrainedElementId = null;
                }
            }
        } else if (name.equals(UML_BODY)) {
            if (!inConstraint && !inComment && !inDefaultValue && !inSpecification) {
                // Throw an error for body elements, which aren't
                // contained in a constraint or comment
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, null));
            }
        } else if (name.equals(UML_SPECIFICATION)) {
            // Specification is ignored for most elements
            inSpecification = true;
        } else if (name.equals(UML_LANGUAGE)) {
            if (!inConstraint) {
                // Throw an error for specification elements, which aren't
                // contained in a constraint.
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, null));
            }
        } else if (name.equals(UML_OWNEDEND)) {
            // Owned end marks the end of the current class, which should be
            // an edgeClasss.
            if (type.equals(UML_PROPERTY) && (currentClass instanceof EdgeClass)) {
                handleAssociationEnd(xmiId);
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, type));
            }

        } else if (name.equals(UML_OWNED_ATTRIBUTE)) {
            inOwnedAttribute = true;
            // Handles the attributes of the current element
            if (type.equals(UML_PROPERTY)) {
                handleOwnedAttribute(xmiId);
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, type));
            }

        } else if (name.equals(UML_ATTRIBUTE_TYPE)) {
            // Handles the type of the current attribute, which should be a
            // primitive type.
            if (!inDefaultValue) {
                if (type.equals(UML_PRIMITIVE_TYPE)) {
                    handleNestedTypeElement(xmiId);
                } else {
                    throw new ProcessingException(getParser(), getFilename(),
                            createUnexpectedElementMessage(name, type));
                }
            }
        } else if (name.equals(UML_OWNED_LITERAL)) {
            // Handles the literal of the current enumeration.
            if (type.equals(UML_ENUMERATION_LITERAL)) {
                handleEnumerationLiteral();
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        createUnexpectedElementMessage(name, type));
            }
        } else if (name.equals(XMI_EXTENSION)) {
            // ignore
        } else if (name.equals(UML_E_ANNOTATIONS)) {
            // ignore
        } else if (name.equals(UML_GENERALIZATION)) {
            handleGeneralization();
        } else if (name.equals(UML_DETAILS)) {
            handleStereotype();
        } else if (name.equals(UML_LOWER_VALUE)) {
            handleLowerValue();
        } else if (name.equals(UML_UPPER_VALUE)) {
            handleUpperValue();
        } else if (name.equals(UML_OWNED_COMMENT)) {
            annotatedElementId = getAttribute(UML_ANNOTATED_ELEMENT);
            inComment = true;
        } else if (name.equals(UML_DEFAULT_VALUE)) {
            String xmiType = getAttribute(XMI_NAMESPACE_PREFIX, XMI_TYPE);
            if (isPrimitiveDefaultValue(xmiType)) {
                // boolean, integer, string, or enumeration value
                handlePrimitiveDefaultValue(xmiId, xmiType);
            } else if (!xmiType.equals(UML_OPAQUE_EXPRESSION)) {
                System.out.println("Warning: Unexpected default value type '" + xmiType + "' for attribute '"
                        + currentAttribute.get_name() + "' of " + currentClass.getSchemaClass().getSimpleName()
                        + " '" + currentClass.get_qualifiedName() + "' in file '" + getFilename() + "' at line "
                        + getParser().getLocation().getLineNumber());
                // throw new ProcessingException(getParser(), getFileName(),
                // "Unexpected default value type " + xmiType
                // + " for attribute '"
                // + currentAttribute.get_name() + "' of "
                // + currentClass.getSchemaClass().getSimpleName()
                // + " '" + currentClass.get_qualifiedName() + "'");
            }
            inDefaultValue = true;
        } else {
            // for unexpected XMI tags
            throw new ProcessingException(getParser(), getFilename(), createUnexpectedElementMessage(name, type));
        }
        return vertexId;
    }

    private boolean isPrimitiveDefaultValue(String xmiType) {
        return xmiType.equals(UML_LITERAL_STRING) || xmiType.equals(UML_LITERAL_INTEGER)
                || xmiType.equals(UML_LITERAL_BOOLEAN) || xmiType.equals(UML_INSTANCE_VALUE);
    }

    private void handlePrimitiveDefaultValue(String xmiId, String xmiType) throws XMLStreamException {
        if (xmiType.equals(UML_INSTANCE_VALUE)) {
            String value = getAttribute(UML_ATTRIBUTE_NAME);
            handleDefaultValue(xmiId, value);
            return;
        }

        String value = getAttribute(UML_ATTRIBUTE_VALUE);
        if (xmiType.equals(UML_LITERAL_BOOLEAN)) {
            if (value == null) {
                value = "f"; // XML schema default
            } else {
                assert value.equals("true") || value.equals("false");
                // true/false => t/f
                value = value.substring(0, 1);
            }
            handleDefaultValue(xmiId, value);
            return;
        } else if (xmiType.equals(UML_LITERAL_INTEGER)) {
            if (value == null) {
                value = "0"; // XML schema default
            }
            handleDefaultValue(xmiId, value);
            return;
        }

        if (value == null) {
            System.out.println("Warning: Undefined default value for attribute '" + currentAttribute.get_name()
                    + "' of " + currentClass.getSchemaClass().getSimpleName() + " '"
                    + currentClass.get_qualifiedName() + "' in file '" + getFilename() + "' at line "
                    + getParser().getLocation().getLineNumber());
            return;
        }

        if (xmiType.equals(UML_LITERAL_STRING)) {
            value = "\"" + value + "\"";
            handleDefaultValue(xmiId, value);
        } else {
            System.out.println("Warning: Undefined default value type '" + xmiType + "' for attribute '"
                    + currentAttribute.get_name() + "' of " + currentClass.getSchemaClass().getSimpleName() + " '"
                    + currentClass.get_qualifiedName() + "' in file '" + getFilename() + "' at line "
                    + getParser().getLocation().getLineNumber());
        }
    }

    private void handleDefaultValue(String xmiId, String value) {
        if (currentAttribute == null) {
            throw new ProcessingException(getParser(), getFilename(), "Found a <defaultValue> tag (XMI id " + xmiId
                    + ") outside an attribute definition (e.g. in a <<record>> class)");
        }
        currentAttribute.set_defaultValue(value);
    }

    /**
     * Processes a XML end element tags in order to set internal states.
     * 
     * @param name
     *            Name of the XML element, which will be closed.
     * @param content
     *            StringBuilder object, which holds the contents of the current
     *            end element.
     * 
     * @throws XMLStreamException
     */
    @Override
    protected void endElement(String name, StringBuilder content) throws XMLStreamException {
        if (getNestingDepth() < modelRootElementNestingDepth) {
            return;
        }

        String xmiId = xmiIdStack.pop();

        if (name.equals(UML_BODY)) {
            if (inConstraint) {
                assert !inComment && !inDefaultValue;
                handleConstraint(content.toString().trim().replace("\\s+", " "));
            } else if (inComment) {
                assert !inDefaultValue;
                handleComment(content.toString());
            } else if (inDefaultValue) {
                handleDefaultValue(xmiId, content.toString().trim());
            }
        }
        AttributedElement<?, ?> elem = idMap.get(xmiId);
        if (elem != null) {
            if (elem instanceof Package) {

                // There should be at least one package element in the
                // stack.
                if (packageStack.size() <= 1) {
                    throw new ProcessingException(getParser(), getFilename(),
                            "XMI file is malformed. There is probably one end element to much.");
                }
                packageStack.pop();
            } else if (elem instanceof AttributedElementClass) {
                currentClassId = null;
                currentClass = null;
                currentAttribute = null;
            } else if (elem instanceof RecordDomain) {
                currentRecordDomain = null;
                currentAttribute = null;
            } else if (elem instanceof Attribute) {
                currentAttribute = null;
            }
        }
        if (name.equals(UML_PACKAGE)) {
            packageStack.pop();

            // There should be no packages left over.
            if (packageStack.size() != 0) {
                throw new ProcessingException(getParser(), getFilename(),
                        "XMI file is malformed. There is probably one end element to much.");
            }
        } else if (name.equals(UML_OWNED_ATTRIBUTE)) {
            currentRecordDomainComponent = null;
            if (currentAssociationEnd != null) {
                checkMultiplicities(currentAssociationEnd);
                currentAssociationEnd = null;
            }
            inOwnedAttribute = false;
        } else if (name.equals(UML_OWNEDEND)) {
            checkMultiplicities(currentAssociationEnd);
            currentAssociationEnd = null;
        } else if (name.equals(UML_OWNEDRULE)) {
            inConstraint = false;
            constrainedElementId = null;
        } else if (name.equals(UML_OWNED_COMMENT)) {
            inComment = false;
            annotatedElementId = null;
        } else if (name.equals(UML_DEFAULT_VALUE)) {
            inDefaultValue = false;
        } else if (name.equals(UML_SPECIFICATION)) {
            inSpecification = false;
        }
    }

    private void checkMultiplicities(IncidenceClass inc) {
        int min = inc.get_min();
        int max = inc.get_max();
        assert min >= 0;
        assert max > 0;
        if (min == Integer.MAX_VALUE) {
            throw new ProcessingException(getFilename(),
                    "Error in multiplicities: lower bound must not be *" + " at association end " + inc);
        }
        if (min > max) {
            throw new ProcessingException(getFilename(), "Error in multiplicities: lower bound (" + min
                    + ") must be <= upper bound (" + max + ") at association end " + inc);
        }
    }

    private void handleComment(String body) {
        // decode RSA's clumsy HTML-like comments...
        body = body.replace("<p>", " ");
        body = body.replace("</p>", "\n");
        body = body.replace("<br>", "\n");
        // RSA uses \u00a0 (NO-BREAK SPACE) sometimes. Use a normal space
        // instead.
        body = body.replace('\u00a0', ' ');
        String[] lines = body.split("\n");
        StringBuilder text = new StringBuilder();
        for (String line : lines) {
            line = line.replaceAll("\\s+", " ").trim();
            if (line.length() > 0) {
                if (text.length() > 0) {
                    text.append("\n");
                }
                text.append(line);
            }
        }
        if (text.length() == 0) {
            return;
        }
        List<String> commentList = comments.get(annotatedElementId);
        if (commentList == null) {
            commentList = new LinkedList<>();
            comments.put(annotatedElementId, commentList);
        }
        commentList.add(text.toString());
    }

    /**
     * Finalizes the created {@link SchemaGraph} by creating missing links
     * between several objects.
     * 
     * @throws XMLStreamException
     * @throws GraphIOException
     */
    @Override
    public void endDocument() throws XMLStreamException {
        // finalizes processing by creating missing links
        assert schema != null;
        assert graphClass != null;
        // The qualified name of the GraphClass should be set.
        if (graphClass.get_qualifiedName() == null) {
            throw new ProcessingException(getParser(), getFilename(), "No <<graphclass>> defined in schema '"
                    + schema.get_packagePrefix() + "." + schema.get_name() + "'");
        }

        // Checks whether each enum domain has at least one literal
        checkEnumDomains();

        // Now the RSA XMI file has been processed, pending actions to link
        // elements can be performed
        linkGeneralizations();
        linkRecordDomainComponents();
        linkAttributeDomains();

        removeIgnoredPackages();

        if (isUseNavigability()) {
            correctEdgeDirection();
        }

        // the following depends on correct subsets relations between incidences
        attachConstraints();

        // the following depends on correct edge directions and edgeclass
        // generalizations
        checkSubsettingOfAllIncidenceClasses();

        // delete derived GraphElementClasses
        for (GraphElementClass gec : derivedGraphElementClasses) {
            assert gec.isValid();
            Edge current = gec.getFirstIncidence();
            while (current != null) {
                Edge next = current.getNextIncidence();
                if (current.isInstanceOf(Annotates.EC)) {
                    Vertex comment = next.getThat();
                    if (comment.getDegree() == 1) {
                        comment.delete();
                    }
                }
                current = next;
            }
            gec.delete();
        }

        createEdgeClassNames();

        checkAttributes();

        if (isRemoveUnusedDomains()) {
            removeUnusedDomains();
        }

        if (!isRemoveComments()) {
            attachComments();
        }

        if (!isKeepEmptyPackages()) {
            removeEmptyPackages();
        }

        // preliminaryVertices must be empty at this time of processing,
        // otherwise there is an error...
        if (!preliminaryVertices.isEmpty()) {
            System.err.println("Remaining preliminary vertices (" + preliminaryVertices.size() + "):");
            for (Vertex v : preliminaryVertices) {
                System.err.println(attributedElement2String(v));
            }
            throw new ProcessingException(getFilename(), "There are still vertices left over. ");
        }

        if (!suppressOutput) {
            try {
                writeOutput();
            } catch (GraphIOException e) {
                throw new XMLStreamException(e);
            }
        }
    }

    private boolean isRemoveComments() {
        return removeComments;
    }

    private void checkAttributes() {
        GraphClass graphClass = sg.getFirstGraphClass();
        Map<String, AttributedElementClass> definedAttributes = new HashMap<>();
        for (Attribute a : graphClass.get_attributes()) {
            if (definedAttributes.containsKey(a.get_name())) {
                throw new RuntimeException(
                        "Attribute " + a.get_name() + " at " + graphClass.get_qualifiedName() + " is duplicate.");
            }
            definedAttributes.put(a.get_name(), graphClass);
        }

        for (GraphElementClass gec : sg.getGraphElementClassVertices()) {
            boolean isVertexClass = gec.isInstanceOf(VertexClass.VC);
            definedAttributes = new HashMap<>();
            BooleanGraphMarker alreadyChecked = new BooleanGraphMarker(sg);
            Queue<GraphElementClass> queue = new LinkedList<>();
            queue.add(gec);
            while (!queue.isEmpty()) {
                GraphElementClass current = queue.poll();
                if (alreadyChecked.isMarked(current)) {
                    continue;
                }
                for (Attribute att : current.get_attributes()) {
                    if (definedAttributes.containsKey(att.get_name())) {
                        AttributedElementClass childClass = definedAttributes.get(att.get_name());
                        throw new RuntimeException("The name of the "
                                + ((childClass == gec) && (current != gec) ? "" : "inherited ") + "attribute "
                                + att.get_name() + " of " + (isVertexClass ? "VertexClass" : "EdgeClass") + " "
                                + childClass.get_qualifiedName()
                                + (current == gec ? " is duplicate"
                                        : (" is the same name as the inherited attribute of "
                                                + (isVertexClass ? "VertexClass" : "EdgeClass") + " "
                                                + current.get_qualifiedName()))
                                + ".");
                    } else {
                        definedAttributes.put(att.get_name(), current);
                    }
                }
                alreadyChecked.mark(current);
                for (Edge toSuperClass : current.incidences(
                        isVertexClass ? SpecializesVertexClass.EC : SpecializesEdgeClass.EC, EdgeDirection.OUT)) {
                    GraphElementClass superClass = (GraphElementClass) toSuperClass.getThat();
                    if (!alreadyChecked.isMarked(superClass)) {
                        queue.add(superClass);
                    }
                }
            }
        }
    }

    /**
     * Removes all GraphElementClasses in ignored Packages from the schema
     * graph.
     */
    private void removeIgnoredPackages() {
        System.out.println("Removing ignored packages...");
        int n = 0;
        for (Package pkg : ignoredPackages) {
            n += removePackage(pkg);
        }
        System.out.println("\tRemoved " + n + " package" + (n == 1 ? "" : "s") + ".");
    }

    /**
     * Removes the GraphElementClasses in the Package <code>pkg</code> from the
     * schema graph, including subpackages.
     * 
     * @param pkg
     *            a Package
     */
    private int removePackage(Package pkg) {
        if (!pkg.isValid()) {
            // possibly alread deleted
            return 0;
        }
        int n = 0;
        // recursively descend into subpackages
        List<Package> subPackages = new ArrayList<>();
        for (Package sub : pkg.get_subpackages()) {
            subPackages.add(sub);
        }
        for (Package sub : subPackages) {
            n += removePackage(sub);
        }

        // remove all GraphElementClasses
        for (ContainsGraphElementClass c = pkg
                .getFirstContainsGraphElementClassIncidence(EdgeDirection.OUT); c != null; c = pkg
                        .getFirstContainsGraphElementClassIncidence(EdgeDirection.OUT)) {
            GraphElementClass gec = (GraphElementClass) c.getThat();

            if (gec instanceof EdgeClass) {
                // in case of an EdgeClass, also remove IncidenceClasses
                EdgeClass ec = (EdgeClass) gec;
                ec.get_to().delete();
                ec.get_from().delete();
            } else if (gec instanceof VertexClass) {
                // in case of an EdgeClass, also remove incident EdgeClasses
                VertexClass vc = (VertexClass) gec;
                for (EndsAt e = vc.getFirstEndsAtIncidence(EdgeDirection.IN); e != null; e = vc
                        .getFirstEndsAtIncidence(EdgeDirection.IN)) {
                    EdgeClass ec;
                    // the EdgeClass can be either outgoing (ComesFrom) or
                    // ingoing (GoesTo) to this VertexClass
                    ComesFrom cf = ((IncidenceClass) e.getThat()).getFirstComesFromIncidence();
                    if (cf == null) {
                        GoesTo gt = ((IncidenceClass) e.getThat()).getFirstGoesToIncidence();
                        ec = (EdgeClass) gt.getThat();
                    } else {
                        ec = (EdgeClass) cf.getThat();
                    }
                    // remove IncidenceClasses
                    ec.get_to().delete();
                    ec.get_from().delete();
                    // remove Attributes of EdgeClass
                    removeAttributes(ec);
                    ec.delete();
                }
            }
            // remove Attributes of GraphElementClass
            removeAttributes(gec);
            gec.delete();
        }
        // remove the package itself if it's totally empty (degree is 1 since
        // the ContainsSubpackage edge to the parent package still exists)
        if (pkg.getDegree() == 1) {
            ++n;
            System.out.println("\t- removing " + pkg.get_qualifiedName());
            pkg.delete();
        }
        return n;
    }

    /**
     * Removes all Attribute vertices of AttributedElementClass <code>aec</code>
     * from the schema graph
     * 
     * @param aec
     */
    private void removeAttributes(AttributedElementClass aec) {
        for (HasAttribute ha = aec.getFirstHasAttributeIncidence(EdgeDirection.OUT); ha != null; ha = aec
                .getFirstHasAttributeIncidence(EdgeDirection.OUT)) {
            ha.getThat().delete();
        }
    }

    /**
     * Checks whether all Enumeration domains contain at least one literal.
     * 
     * @throws ProcessingException
     *             if any enumeration is empty
     */
    private void checkEnumDomains() {
        System.out.println("Checking enumeration domains...");
        ArrayList<String> faultyDomains = new ArrayList<>();
        for (EnumDomain ed : sg.getEnumDomainVertices()) {
            if (ed.get_enumConstants().size() < 1) {
                faultyDomains.add(ed.get_qualifiedName());
            }
        }
        if (faultyDomains.size() > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append("The following enumeration domain").append(faultyDomains.size() == 1 ? " has" : "s have")
                    .append(" no literals");
            String delim = ": ";
            for (String name : faultyDomains) {
                sb.append(delim).append(name);
                delim = ", ";
            }
            throw new ProcessingException(getFilename(), sb.toString());
        }
    }

    private void attachComments() {
        System.out.println("Attaching comments to annotated elements...");
        for (String id : comments.keySet()) {
            NamedElement annotatedElement = null;
            if (domainMap.containsKey(id)) {
                annotatedElement = domainMap.get(id);
            } else if (idMap.containsKey(id)) {
                Vertex v = idMap.get(id);
                annotatedElement = (NamedElement) v;
            }
            if (annotatedElement == null) {
                System.out.println("\t- Couldn't find annotated element for XMI id " + id
                        + "\n\t  => attaching to GraphClass (Comment starts with '" + comments.get(id).get(0)
                        + "'");
                annotatedElement = graphClass;
            }
            assert annotatedElement != null;
            if (annotatedElement.isValid()) {
                List<String> lines = comments.get(id);
                for (String line : lines) {
                    Comment c = sg.createComment();
                    c.set_text(line);
                    sg.createAnnotates(c, annotatedElement);
                }
            }
        }
    }

    private void checkSubsettingOfAllIncidenceClasses() {
        System.out.println("Checking subsets relationships...");
        // for each specialisation between edge classes, add a subsets edge
        // between their incidence classes
        SpecializesEdgeClass spec = sg.getFirstSpecializesEdgeClass();
        while (spec != null) {
            EdgeClass subClass = spec.getAlpha();
            EdgeClass superClass = spec.getOmega();

            assert subClass.getFirstComesFromIncidence() != null;
            assert superClass.getFirstComesFromIncidence() != null;
            checkSubsettingOfIncidences(subClass, superClass,
                    (IncidenceClass) subClass.getFirstComesFromIncidence().getThat(),
                    (IncidenceClass) superClass.getFirstComesFromIncidence().getThat());

            assert subClass.getFirstGoesToIncidence() != null;
            assert superClass.getFirstGoesToIncidence() != null;
            checkSubsettingOfIncidences(subClass, superClass,
                    (IncidenceClass) subClass.getFirstGoesToIncidence().getThat(),
                    (IncidenceClass) superClass.getFirstGoesToIncidence().getThat());
            spec = spec.getNextSpecializesEdgeClassInGraph();
        }

        // Generalisation hierarchy is complete
    }

    private void checkSubsettingOfIncidences(EdgeClass subClass, EdgeClass superClass, IncidenceClass subInc,
            IncidenceClass superInc) {
        assert getDirection(subInc) != null;
        assert getDirection(superInc) != null;

        // Check incidence directions
        if (getDirection(subInc) != getDirection(superInc)) {
            throw new ProcessingException(getFilename(),
                    "Incompatible incidence direction in specialisation " + subClass + " --> " + superClass);
        }

        // Check multiplicities: Subclass must not have greater upper bound than
        // superclass
        if (subInc.get_max() > superInc.get_max()) {
            throw new ProcessingException(getFilename(),
                    "Subclass has higher upper bound (" + subInc.get_max() + ") than superclass ("
                            + superInc.get_max() + ") in specialisation " + subClass.get_qualifiedName() + " --> "
                            + superClass.get_qualifiedName());
        }

        // COMPOSITE end may specialize any other end
        // SHARED end may specialize only SHARED and NONE ends
        // NONE end may specialize only NONE ends
        AggregationKind subAgg = subInc.get_aggregation();
        AggregationKind superAgg = superInc.get_aggregation();
        if (((subAgg == AggregationKind.SHARED) && (superAgg == AggregationKind.COMPOSITE))
                || ((subAgg == AggregationKind.NONE) && (superAgg != AggregationKind.NONE))) {
            throw new ProcessingException(getFilename(),
                    "Incompatible aggregation kinds (" + subAgg + " specialises " + superAgg
                            + ") in generalisation " + subClass.get_qualifiedName() + " --> "
                            + superClass.get_qualifiedName());

        }
    }

    private EdgeDirection getDirection(IncidenceClass inc) {
        assert (inc.getFirstComesFromIncidence() == null) || (inc.getFirstGoesToIncidence() == null);
        if (inc.getFirstComesFromIncidence() != null) {
            return EdgeDirection.OUT;
        } else if (inc.getFirstGoesToIncidence() != null) {
            return EdgeDirection.IN;
        } else {
            return null;
        }
    }

    /**
     * Writes a DOT file and a TG file out.
     * 
     * @throws XMLStreamException
     * @throws GraphIOException
     */
    public void writeOutput() throws XMLStreamException, GraphIOException {

        boolean fileCreated = false;

        if (filenameDot != null) {
            try {
                printTypeAndFilename("GraphvViz DOT file", filenameDot);
                writeDotFile(filenameDot);
                fileCreated = true;
            } catch (IOException e) {
                System.out.println("Could not create DOT file.");
                System.out.println("Exception was " + e);
            }
        }

        if (filenameSchemaGraph != null) {
            printTypeAndFilename("schemagraph", filenameSchemaGraph);
            writeSchemaGraph(filenameSchemaGraph);
            fileCreated = true;
        }

        // The Graph is always validated, but not always written to a hard
        // drive.
        System.out.println("Validating schema graph...");
        validateGraph(filenameValidation);
        if (filenameValidation != null) {
            fileCreated = true;
        }

        if (filenameSchema != null) {
            printTypeAndFilename("schema", filenameSchema);
            writeSchema(filenameSchema);
            fileCreated = true;
        }

        if (!fileCreated) {
            System.out.println("No files have been created.");
        }
    }

    private void printTypeAndFilename(String type, String filename) {
        System.out.println("Writing " + type + " to: " + filename);
    }

    /**
     * Performs a graph validation and writes a report in a file.
     * 
     * @param schemaName
     *            Name of the Schema.
     * @param relativePathPrefix
     *            Relative path to a folder.
     */
    private void validateGraph(String filePath) {
        if (filePath != null) {
            printTypeAndFilename("validation report", filePath);
        }

        try {
            GraphValidator validator = new GraphValidator(sg);
            Set<ConstraintViolation> s;
            if (filePath != null) {
                s = validator.createValidationReport(filePath);
            } else {
                s = validator.validate();
            }
            if (!s.isEmpty()) {
                System.err.println("The schema graph is not valid:");
                for (ConstraintViolation currentViolation : s) {
                    // print out violations
                    System.err.println(currentViolation);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates a message for an unexpected element and includes its type.
     * 
     * @param name
     *            Name of the unexpected element.
     * @param type
     *            Type of the unexpected element.
     * @return Created message.
     */
    private String createUnexpectedElementMessage(String name, String type) {

        String typeInsertion = type != null ? " of type " + type : "";
        return "Unexpected element <" + name + ">" + typeInsertion + ".";
    }

    /**
     * Handles a 'uml:Package' element by creating a corresponding grUML Package
     * element.
     * 
     * @return Created Package object as Vertex.
     */
    private Vertex handlePackage() throws XMLStreamException {

        Package pkg = sg.createPackage();
        pkg.set_qualifiedName(getQualifiedName(getAttribute(UML_ATTRIBUTE_NAME)));
        sg.createContainsSubPackage(packageStack.peek(), pkg);
        packageStack.push(pkg);
        return pkg;
    }

    /**
     * Handles a 'uml:Class' element by creating a corresponding grUML
     * {@link VertexClass} element.
     * 
     * @param xmiId
     *            XMI ID in RSA XMI file.
     * @return Created VertexClass as {@link Vertex}.
     * @throws XMLStreamException
     */
    private Vertex handleClass(String xmiId) throws XMLStreamException {

        AttributedElement<?, ?> ae = idMap.get(xmiId);
        VertexClass vc = null;
        if (ae != null) {

            // Element with ID xmiID must be a VertexClass
            if (!(ae instanceof VertexClass)) {
                throw new ProcessingException(getParser(), getFilename(),
                        "The element with ID '" + xmiId + "' is not a class. (VertexClass)");
            }

            assert preliminaryVertices.contains(ae);
            preliminaryVertices.remove(ae);
            vc = (VertexClass) ae;
        } else {
            vc = sg.createVertexClass();
        }
        currentClassId = xmiId;
        currentClass = vc;
        String abs = getAttribute(UML_ATTRIBUTE_IS_ABSRACT);
        vc.set_abstract((abs != null) && abs.equals(UML_TRUE));
        vc.set_qualifiedName(getQualifiedName(getAttribute(UML_ATTRIBUTE_NAME)));
        sg.createContainsGraphElementClass(packageStack.peek(), vc);

        String isDerived = getAttribute(UML_ATTRIBUTE_ISDERIVED);
        boolean derived = (isDerived != null) && isDerived.equals(UML_TRUE);

        if (derived) {
            derivedGraphElementClasses.add(vc);
        }

        // System.out.println("currentClass = " + currentClass + " "
        // + currentClass.getQualifiedName());
        return vc;
    }

    /**
     * Handles a 'uml:Association' or a 'uml:AssociationClass' element by
     * creating a corresponding {@link EdgeClass} element.
     * 
     * @param xmiId
     *            XMI ID in XMI file.
     * @return Created EdgeClass as {@link Vertex}.
     * @throws XMLStreamException
     */
    private Vertex handleAssociation(String xmiId) throws XMLStreamException {

        // create an EdgeClass at first, probably, this has to
        // become an Aggregation or Composition later...
        AttributedElement<?, ?> ae = idMap.get(xmiId);
        EdgeClass ec = null;
        if (ae != null) {
            if (!(ae instanceof EdgeClass)) {
                throw new ProcessingException(getParser(), getFilename(),
                        "The XMI id " + xmiId + " must denonte an EdgeClass, but is "
                                + ae.getAttributedElementClass().getQualifiedName());
            }

            assert preliminaryVertices.contains(ae);
            preliminaryVertices.remove(ae);
            ec = (EdgeClass) ae;
        } else {
            ec = sg.createEdgeClass();
        }
        currentClassId = xmiId;
        currentClass = ec;
        String abs = getAttribute(UML_ATTRIBUTE_IS_ABSRACT);
        ec.set_abstract((abs != null) && abs.equals(UML_TRUE));
        String n = getAttribute(UML_ATTRIBUTE_NAME);
        n = n == null ? "" : n.trim();
        if (n.length() > 0) {
            n = Character.toUpperCase(n.charAt(0)) + n.substring(1);
        }
        ec.set_qualifiedName(getQualifiedName(n));
        sg.createContainsGraphElementClass(packageStack.peek(), ec);

        String memberEnd = getAttribute(UML_MEMBER_END);
        // An association have to have a end member.
        if (memberEnd == null) {
            throw new ProcessingException(getParser(), getFilename(),
                    "The association with ID '" + xmiId + "' has no end member. (EdgeClass)");
        }
        memberEnd = memberEnd.trim().replaceAll("\\s+", " ");
        int p = memberEnd.indexOf(' ');
        String targetEnd = memberEnd.substring(0, p);
        String sourceEnd = memberEnd.substring(p + 1);

        IncidenceClass inc = (IncidenceClass) idMap.get(sourceEnd);
        if (inc == null) {
            VertexClass vc = sg.createVertexClass();
            preliminaryVertices.add(vc);
            vc.set_qualifiedName("preliminary for source end " + sourceEnd);
            inc = sg.createIncidenceClass();
            inc.set_aggregation(AggregationKind.NONE);
            inc.set_min(DEFAULT_MIN_MULTIPLICITY);
            inc.set_max(DEFAULT_MAX_MULTIPLICITY);
            sg.createComesFrom(ec, inc);
            sg.createEndsAt(inc, vc);
            idMap.put(sourceEnd, inc);
        }

        inc = (IncidenceClass) idMap.get(targetEnd);
        if (inc != null) {
            assert inc.isValid();
            assert getDirection(inc) == EdgeDirection.OUT;

            IncidenceClass to = sg.createIncidenceClass();

            IncidenceClass from = inc;
            sg.createGoesTo(ec, to);
            sg.createEndsAt(to, (VertexClass) from.getFirstEndsAtIncidence().getThat());

            to.set_aggregation(from.get_aggregation());
            to.set_max(from.get_max());
            to.set_min(from.get_min());
            to.set_roleName(from.get_roleName());

            if (ownedEnds.contains(from)) {
                ownedEnds.remove(from);
                ownedEnds.add(to);
            }
            inc.delete();
            idMap.put(targetEnd, to);
        } else {
            VertexClass vc = sg.createVertexClass();
            preliminaryVertices.add(vc);
            vc.set_qualifiedName("preliminary for target end " + targetEnd);
            inc = sg.createIncidenceClass();
            inc.set_aggregation(AggregationKind.NONE);
            inc.set_min(DEFAULT_MIN_MULTIPLICITY);
            inc.set_max(DEFAULT_MAX_MULTIPLICITY);
            sg.createGoesTo(ec, inc);
            sg.createEndsAt(inc, vc);
            idMap.put(targetEnd, inc);
        }

        String isDerived = getAttribute(UML_ATTRIBUTE_ISDERIVED);
        boolean derived = (isDerived != null) && isDerived.equals(UML_TRUE);

        if (derived) {
            derivedGraphElementClasses.add(ec);
        }
        return ec;
    }

    /**
     * Handles a 'uml:Enumeration' element by creating a corresponding
     * {@link EnumDomain} element.
     * 
     * @return Created EnumDomain as {@link Vertex}.
     * @throws XMLStreamException
     */
    private Vertex handleEnumeration() throws XMLStreamException {
        EnumDomain ed = sg.createEnumDomain();
        Package p = packageStack.peek();
        ed.set_qualifiedName(getQualifiedName(getAttribute(UML_ATTRIBUTE_NAME)));
        sg.createContainsDomain(p, ed);
        PVector<String> empty = JGraLab.vector();
        ed.set_enumConstants(empty);
        Domain dom = domainMap.get(ed.get_qualifiedName());
        if (dom != null) {
            // there was a preliminary vertex for this domain
            // link the edges to the correct one

            assert preliminaryVertices.contains(dom);
            reconnectEdges(dom, ed);
            // delete preliminary vertex
            dom.delete();
            preliminaryVertices.remove(dom);
        }
        domainMap.put(ed.get_qualifiedName(), ed);
        return ed;
    }

    /**
     * Handles a 'uml:PrimitiveType' element by creating a corresponding
     * {@link Domain} element.
     * 
     * @return Created Domain as Vertex.
     */
    private Vertex handlePrimitiveType(String xmiId) throws XMLStreamException {

        String typeName = getAttribute(UML_ATTRIBUTE_NAME);

        if (typeName == null) {
            throw new ProcessingException(getParser(), getFilename(),
                    "No type name in primitive type. XMI ID: " + xmiId);
        }
        typeName = typeName.replaceAll("\\s", "");

        if (typeName.length() == 0) {
            throw new ProcessingException(getParser(), getFilename(),
                    "Type name in primitive type is empty. XMI ID: " + xmiId);
        }
        Domain dom = createDomain(typeName);

        assert dom != null;
        return dom;
    }

    /**
     * Handles a 'uml:Realization' by putting it into a map of realizations. By
     * this, missing generalizations can be traced.
     * 
     * @throws XMLStreamException
     */
    private void handleRealization() throws XMLStreamException {

        String supplier = getAttribute(UML_ATTRIBUTE_SUPPLIER);
        String client = getAttribute(UML_ATTRIBUTE_CLIENT);
        Set<String> reals = realizations.get(client);
        if (reals == null) {
            reals = new TreeSet<>();
            realizations.put(client, reals);
        }
        reals.add(supplier);
    }

    /**
     * Creates a String for a {@link AttributedElementClass} by writing the
     * AttributedElementClass name first and than a list of attributes with
     * their values of the AttributedElementClass.
     * 
     * @param attributedElement
     *            {@link AttributedElement}, of which a {@link String}
     *            representation should be created.
     * @return A String representing the given AttributedElement.
     */
    private String attributedElement2String(AttributedElement<?, ?> attributedElement) {

        StringBuilder sb = new StringBuilder();

        de.uni_koblenz.jgralab.schema.AttributedElementClass<?, ?> aec = attributedElement
                .getAttributedElementClass();
        sb.append(attributedElement);
        sb.append(" { ");

        for (de.uni_koblenz.jgralab.schema.Attribute attr : aec.getAttributeList()) {
            sb.append(attr.getName());
            sb.append(" = ");
            sb.append(attributedElement.getAttribute(attr.getName()));
            sb.append("; ");
        }
        sb.append("}\n");

        return sb.toString();
    }

    /**
     * Handles a 'uml:EnumerationLiteral' by creating a corresponding
     * enumeration literal and adding it to its {@link EnumDomain}.
     * 
     * @param xmiId
     * 
     * @throws XMLStreamException
     */
    private void handleEnumerationLiteral() throws XMLStreamException {

        String s = getAttribute(UML_ATTRIBUTE_NAME);

        // A Literal must be declared.
        if (s == null) {
            throw new ProcessingException(getParser(), getFilename(), "No Literal declared.");
        }
        s = s.trim();

        // Literal must not be empty.
        if (s.length() <= 0) {
            throw new ProcessingException(getParser(), getFilename(), "Literal is empty.");
        }

        String classifier = getAttribute(UML_ATTRIBUTE_CLASSIFIER);

        // Exception "No Enum found for Literal " ... " found.
        if (classifier == null) {
            throw new ProcessingException(getParser(), getFilename(),
                    "No Enumeration found for Literal '" + s + "'.");
        }
        EnumDomain ed = (EnumDomain) idMap.get(classifier);

        if (!s.equals(s.toUpperCase())) {
            System.out.println("Warning: Enumeration literal '" + s + "' in enumeration + '"
                    + ed.get_qualifiedName() + "' should be all uppercase letters.");
        }

        ed.set_enumConstants(ed.get_enumConstants().plus(s));
    }

    /**
     * Writes the current processed {@link Schema} as a Schema to a TG file.
     * 
     * @param schemaName
     *            Name of the Schema.
     */
    private void writeSchema(String schemaName) {
        try {
            SchemaGraph2Tg sg2tg = new SchemaGraph2Tg(sg, schemaName);
            sg2tg.process();
        } catch (IOException e) {
            throw new RuntimeException("SchemaGraph2Tg failed with an IOException!", e);
        }
    }

    /**
     * Corrects the current edge direction of every {@link EdgeClass} by using
     * the navigability.
     */
    private void correctEdgeDirection() {
        if (!isUseNavigability()) {
            return;
        }
        System.out.println("Correcting edge directions according to navigability...");
        for (EdgeClass e : sg.getEdgeClassVertices()) {
            ComesFrom cf = e.getFirstComesFromIncidence();
            if (cf == null) {
                throw new ProcessingException(getFilename(),
                        "EdgeClass " + e.get_qualifiedName() + " has no ComesFrom incidence");
            }
            GoesTo gt = e.getFirstGoesToIncidence();
            if (gt == null) {
                throw new ProcessingException(getFilename(),
                        "EdgeClass " + e.get_qualifiedName() + " has no GoesTo incidence");
            }

            IncidenceClass from = (IncidenceClass) cf.getThat();
            IncidenceClass to = (IncidenceClass) gt.getThat();

            boolean fromIsNavigable = !ownedEnds.contains(from);
            boolean toIsNavigable = !ownedEnds.contains(to);
            if (fromIsNavigable == toIsNavigable) {
                // no navigability specified or both ends navigable:
                // do nothing, edge direction is determined by order of memerEnd
                // in association
                continue;
            }

            if (toIsNavigable) {
                // "to" end is marked navigable, nothing to change
                continue;
            }

            // "from" end is marked navigable, swap edge direction
            assert getDirection(to) == EdgeDirection.IN;
            assert getDirection(from) == EdgeDirection.OUT;

            IncidenceClass inc = (IncidenceClass) cf.getThat();
            cf.setThat(gt.getThat());
            gt.setThat(inc);
        }

    }

    /**
     * Attaches all Constraint objects to their corresponding
     * {@link AttributedElementClass}.
     * 
     * @throws XMLStreamException
     */
    private void attachConstraints() throws XMLStreamException {
        System.out.println("Attaching constraints...");
        for (String constrainedElementId : constraints.keySet()) {
            List<String> l = constraints.get(constrainedElementId);
            if (l.size() == 0) {
                continue;
            }
            Vertex ae = idMap.get(constrainedElementId);
            if (ae == null) {
                ae = graphClass;
            }

            if (!ae.isValid()) {
                // vertex has been removed
                continue;
            }

            // Constraint are attached to GraphClass, VertexClass, EdgeClass or
            // Association Ends.
            if (!(ae instanceof AttributedElementClass) && !(ae instanceof IncidenceClass)) {
                throw new ProcessingException(getFilename(),
                        "Constraint can only be attached to GraphClass, "
                                + "VertexClass, EdgeClass or association ends. Offending element is " + ae
                                + " (XMI id " + constrainedElementId + ")");
            }

            if (ae instanceof AttributedElementClass) {
                if (((AttributedElementClass) ae).isValid()) {
                    for (String text : l) {
                        addGreqlConstraint((AttributedElementClass) ae, text);
                    }
                }
            } else {
                throw new ProcessingException(getFilename(), "Don't know what to do with constraint(s) at element "
                        + ae + " (XMI id " + constrainedElementId + ")");
            }
        }
    }

    /*
     * EdgeClasses with a simple name of the form $<numbers>$ will be renamed as
     * if there was no name. $<number>:<midName>$ will be renamed as if there
     * was no name, but the middle part is fixed to <midName> instead of using
     * Contains/IsPartOf/LinksTo. That allows for "unnamed" association classes.
     */
    private static final Pattern GENNAME_PATTERN = Pattern.compile("(.*)\\$\\p{Digit}+(:(\\w+))?\\$$");

    /**
     * Creates {@link EdgeClass} names for all EdgeClass objects, which do have
     * an empty String or a String, which ends with a '.'.
     */
    private void createEdgeClassNames() {
        System.out.println("Creating missing edge class names...");
        for (EdgeClass ec : sg.getEdgeClassVertices()) {
            String name = ec.get_qualifiedName().trim();

            // invent an edgeclass name
            String ecName = null;

            Matcher m = GENNAME_PATTERN.matcher(name);
            if (m.matches()) {
                name = m.group(1);
                ecName = m.group(m.groupCount());
            }

            if (!name.equals("") && !name.endsWith(".")) {
                continue;
            }

            IncidenceClass to = (IncidenceClass) ec.getFirstGoesToIncidence().getThat();
            IncidenceClass from = (IncidenceClass) ec.getFirstComesFromIncidence().getThat();

            String toRole = to.get_roleName();
            if ((toRole == null) || toRole.equals("")) {
                toRole = ((VertexClass) to.getFirstEndsAtIncidence().getThat()).get_qualifiedName();
                int p = toRole.lastIndexOf('.');
                if (p >= 0) {
                    toRole = toRole.substring(p + 1);
                }
            } else {
                toRole = Character.toUpperCase(toRole.charAt(0)) + toRole.substring(1);
            }

            // There must be a 'to' role name, which is different than null and
            // not empty.
            if ((toRole == null) || (toRole.length() <= 0)) {
                throw new ProcessingException(getFilename(),
                        "There is no role name 'to' for the edge '" + name + "' defined.");
            }

            if (ecName == null) {
                if ((from.get_aggregation() != AggregationKind.NONE)
                        || (to.get_aggregation() != AggregationKind.NONE)) {
                    if (to.get_aggregation() != AggregationKind.NONE) {
                        ecName = "Contains" + toRole;
                    } else {
                        ecName = "IsPartOf" + toRole;
                    }
                } else {
                    ecName = "LinksTo" + toRole;
                }
            } else {
                ecName += toRole;
            }

            if (isUseFromRole()) {
                String fromRole = from.get_roleName();
                if ((fromRole == null) || fromRole.equals("")) {
                    fromRole = ((VertexClass) from.getFirstEndsAtIncidence().getThat()).get_qualifiedName();
                    int p = fromRole.lastIndexOf('.');
                    if (p >= 0) {
                        fromRole = fromRole.substring(p + 1);
                    }
                } else {
                    fromRole = Character.toUpperCase(fromRole.charAt(0)) + fromRole.substring(1);
                }

                // There must be a 'from' role name, which is different than
                // null and not empty.
                if ((fromRole == null) || (fromRole.length() <= 0)) {
                    throw new ProcessingException(getFilename(),
                            "There is no role name of 'from' for the edge '" + name + "' defined.");
                }
                name += fromRole;
            }

            assert (ecName != null) && (ecName.length() > 0);
            ec.set_qualifiedName(name + ecName);
        }
    }

    /**
     * Removes unused {@link Domain} objects, which are included in the current
     * {@link SchemaGraph}.
     */
    private void removeUnusedDomains() {
        System.out.println("Removing unused domains...");
        Domain d = sg.getFirstDomain();
        while (d != null) {
            Domain n = d.getNextDomain();
            // unused if in-degree of all but Annotates edges is <=1 (one
            // incoming edge is the ContainsDomain edge from a Package)
            if ((d.getDegree(EdgeDirection.IN) - d.getDegree(Annotates.EC, EdgeDirection.IN)) <= 1) {
                // System.out.println("...remove unused domain '"
                // + d.getQualifiedName() + "'");

                // remove possible comments
                List<? extends Comment> comments = d.remove_comments();
                for (Comment c : comments) {
                    c.delete();
                }
                d.delete();
                d = sg.getFirstDomain();
            } else {
                d = n;
            }
        }
    }

    /**
     * Resolves all preliminary {@link StringDomain}, which store the domain id,
     * to existing {@link Domain} objects and links them to their corresponding
     * {@link RecordDomain} objects.
     */
    private void linkRecordDomainComponents() {

        for (HasRecordDomainComponent comp : sg.getHasRecordDomainComponentEdges()) {

            String domainId = recordComponentType.getMark(comp);
            if (domainId == null) {
                recordComponentType.removeMark(comp);
                continue;
            }

            Domain dom = (Domain) idMap.get(domainId);
            if (dom != null) {
                Domain d = comp.getOmega();

                // preliminary domain vertex exists and has type StringDomain,
                // but the name of the StringDomain is the "real" domain name
                assert (d instanceof StringDomain) && d.get_qualifiedName().equals(domainId)
                        && preliminaryVertices.contains(d);
                comp.setOmega(dom);
                d.delete();
                preliminaryVertices.remove(d);
                recordComponentType.removeMark(comp);
            } else {
                throw new ProcessingException(getFilename(), "Undefined Domain with ID '" + domainId + "' found.");
            }
        }

        if (!recordComponentType.isEmpty()) {
            throw new ProcessingException(getFilename(), "Some RecordDomains have unresolved component types.");
        }
    }

    /**
     * Links {@link Attribute} and {@link Domain} objects to each other by
     * creating a {@link HasDomain} edge.
     */
    private void linkAttributeDomains() {

        for (Attribute att : sg.getAttributeVertices()) {
            String domainId = attributeType.getMark(att);
            if (domainId == null) {
                assert att.getDegree(HasDomain.EC, EdgeDirection.OUT) == 1 : "Attribute '" + att.get_name()
                        + "' of " + att.getFirstHasAttributeIncidence().getThat().getSchemaClass().getSimpleName()
                        + " '"
                        + ((AttributedElementClass) att.getFirstHasAttributeIncidence().getThat())
                                .get_qualifiedName()
                        + "' has " + att.getDegree(HasDomain.EC, EdgeDirection.OUT) + " domain(s)";
                continue;
            }
            Domain dom = (Domain) idMap.get(domainId);
            if (dom != null) {
                sg.createHasDomain(att, dom);
                attributeType.removeMark(att);
            } else {
                // Every Attribute must have a Domain.
                throw new ProcessingException(getFilename(), "Undefined Domain with ID '" + domainId + "' found.");
            }

            assert att.getDegree(HasDomain.EC, EdgeDirection.OUT) == 1;
        }

        // If 'attributeType' is not empty, there will be a Domain objects
        // left over.
        if (!attributeType.isEmpty()) {
            throw new ProcessingException(getFilename(),
                    "There are some Attribute objects, whos domains are not resolved.");
        }
    }

    /**
     * Writes the {@link SchemaGraph} as Dotty-Graph to a DOT file with the name
     * of 'dotName'.
     * 
     * @param dotName
     *            File name of the DOT output file.
     * @throws IOException
     */
    private void writeDotFile(String dotName) throws IOException {
        Tg2Dot tg2Dot = new Tg2Dot();
        tg2Dot.setGraph(sg);
        tg2Dot.setPrintEdgeAttributes(true);
        tg2Dot.setOutputFile(dotName);
        tg2Dot.convert();
    }

    /**
     * Writes the {@link SchemaGraph} as a Graph to a TG file with the specified
     * file name <code>schemaGraphName</code>.
     * 
     * @param schemaGraphName
     *            File name of the TG output file.
     * @throws GraphIOException
     */
    private void writeSchemaGraph(String schemaGraphName) throws GraphIOException {
        sg.save(schemaGraphName);
    }

    /**
     * Realizes the Generalization relationship by linking
     * {@link AttributedElementClass} objects to their direct superclass(es).
     */
    private void linkGeneralizations() {
        for (String clientId : realizations.keySet()) {
            Set<String> suppliers = realizations.get(clientId);
            AttributedElementClass client = (AttributedElementClass) idMap.get(clientId);
            if (suppliers.size() > 0) {
                Set<String> superClasses = generalizations.getMark(client);
                if (superClasses == null) {
                    superClasses = new TreeSet<>();
                    generalizations.mark(client, superClasses);
                }
                superClasses.addAll(suppliers);
            }
        }

        for (AttributedElement<?, ?> ae : generalizations.getMarkedElements()) {
            AttributedElementClass sub = (AttributedElementClass) ae;

            Set<String> superclasses = generalizations.getMark(sub);
            for (String id : superclasses) {
                AttributedElementClass sup = (AttributedElementClass) idMap.get(id);

                if (sup == null) {
                    // No superclass with the specified ID has been found.
                    throw new ProcessingException(getFilename(),
                            "The superclass with XMI id '" + id + "' could not be found.");
                }
                if (sup instanceof VertexClass) {
                    // VertexClass can only specialize a VertexClass
                    if (!(sub instanceof VertexClass)) {
                        throw new ProcessingException(getFilename(),
                                "Different types in generalization: " + sub.getSchemaClass().getSimpleName() + " '"
                                        + sub.get_qualifiedName() + "' can not be subclass of "
                                        + sub.getSchemaClass().getSimpleName() + " '" + sup.get_qualifiedName()
                                        + "'");
                    }

                    sg.createSpecializesVertexClass((VertexClass) ae, (VertexClass) sup);
                    if (!vertexClassHierarchyIsAcyclic()) {
                        throw new ProcessingException(getFilename(),
                                "Cycle in vertex class hierarchy. Involved classes are '" + sub.get_qualifiedName()
                                        + "' and '" + sup.get_qualifiedName() + "'");
                    }
                } else if (sup instanceof EdgeClass) {
                    // EdgeClass can only specialize an EdgeClass
                    if (!(sub instanceof EdgeClass)) {
                        throw new ProcessingException(getFilename(),
                                "Different types in generalization: " + sub.getSchemaClass().getSimpleName() + " '"
                                        + sub.get_qualifiedName() + "' can not be subclass of "
                                        + sub.getSchemaClass().getSimpleName() + " '" + sup.get_qualifiedName()
                                        + "'");
                    }

                    sg.createSpecializesEdgeClass((EdgeClass) ae, (EdgeClass) sup);
                    if (!edgeClassHierarchyIsAcyclic()) {
                        throw new ProcessingException(getFilename(),
                                "Cycle in edge class hierarchy. Involved classes are '" + sub.get_qualifiedName()
                                        + "' and '" + sup.get_qualifiedName() + "'");
                    }
                } else {
                    // Should not get here
                    throw new RuntimeException(
                            "FIXME: Unexpected super class type. Super class must be VertexClass or EdgeClass!");
                }
            }
        }
        generalizations.clear();
    }

    /**
     * Checks whether the edge class generalization hierarchy is acyclic.
     * 
     * @return true iff the edge class generalization hierarchy is acyclic.
     */
    private boolean edgeClassHierarchyIsAcyclic() {
        if (edgeClassAcyclicQuery == null) {
            edgeClassAcyclicQuery = GreqlQuery
                    .createQuery("on edgeTypeSubgraph{structure.SpecializesEdgeClass}(): isAcyclic()");
        }
        return (Boolean) edgeClassAcyclicQuery.evaluate(sg);
    }

    /**
     * Checks whether the vertex class generalization hierarchy is acyclic.
     * 
     * @return true iff the vertex class generalization hierarchy is acyclic.
     */
    private boolean vertexClassHierarchyIsAcyclic() {
        if (vertexClassAcyclicQuery == null) {
            vertexClassAcyclicQuery = GreqlQuery
                    .createQuery("on edgeTypeSubgraph{structure.SpecializesVertexClass}(): isAcyclic()");
        }
        return (Boolean) vertexClassAcyclicQuery.evaluate(sg);
    }

    /**
     * Removes empty {@link Package} objects from the {@link SchemaGraph}.
     */
    private void removeEmptyPackages() {
        // remove all empty packages except the default package
        System.out.println("Removing empty packages...");
        Package p = sg.getFirstPackage();
        int removed = 0;
        while (p != null) {
            Package n = p.getNextPackage();
            int commentCount = p.getDegree(Annotates.EC);
            if (((p.getDegree() - commentCount) == 1) && (p.get_qualifiedName().length() > 0)) {
                System.out.println("\t- empty package '" + p.get_qualifiedName() + "' removed"
                        + (commentCount > 0
                                ? commentCount == 1 ? " including 1 comment"
                                        : " including " + commentCount + " comments"
                                : ""));
                if (commentCount > 0) {
                    for (Annotates a = p.getFirstAnnotatesIncidence(); a != null; a = p
                            .getFirstAnnotatesIncidence()) {
                        a.getThat().delete();
                    }
                }
                p.delete();
                ++removed;
                // start over to capture packages that become empty after
                // deletion of p
                p = sg.getFirstPackage();
            } else {
                p = n;
            }
        }
        System.out.println("\tRemoved " + removed + " package" + (removed == 1 ? "" : "s") + ".");
    }

    /**
     * Handles a {@link Constraint} by adding it to a preliminary {@link Map} of
     * Constraints and their ids.
     * 
     * @param text
     *            Constraint as {@link String}.
     * @param line
     *            Line number, at which the current Constraint has been found.
     *            Only needed for exception purposes.
     * @throws XMLStreamException
     */
    private void handleConstraint(String text) throws XMLStreamException {
        if (text.startsWith("\"")) {
            List<String> l = constraints.get(constrainedElementId);
            if (l == null) {
                l = new LinkedList<>();
                constraints.put(constrainedElementId, l);
            }
            l.add(text);
        } else if (text.startsWith("subsets")) {
            System.err.println("warning: {subsets ...} constraint at element " + constrainedElementId
                    + " ignored (don't forget to model generalizations between associations)");
        } else if (text.startsWith("union")) {
            System.err.println("warning: {union} constraint at element " + constrainedElementId
                    + " ignored (don't forget to add an <<abstract>> stereotype to the association)");
        } else if (text.startsWith("ordered")) {
            System.err.println("warning: {ordered} constraint at element " + constrainedElementId
                    + " ignored (TGraphs are ordered by default)");
        } else {
            throw new ProcessingException(getFilename(), getParser().getLocation().getLineNumber(),
                    "Illegal constraint format: " + text);
        }
    }

    /**
     * Adds a Greql constraint to a {@link AttributedElementClass} object.
     * 
     * @param constrainedClass
     *            {@link AttributedElementClass}, which should be constraint.
     * @param text
     *            Constraint as String.
     * @throws XMLStreamException
     */
    private void addGreqlConstraint(AttributedElementClass constrainedClass, String text)
            throws XMLStreamException {

        assert constrainedClass != null;
        Constraint constraint = sg.createConstraint();
        sg.createHasConstraint(constrainedClass, constraint);

        // the "text" must contain 2 or 3 space-separated quoted ("...") strings
        int stringCount = 0;
        char[] ch = text.toCharArray();
        boolean inString = false;
        boolean escape = false;
        int beginIndex = 0;
        for (int i = 0; i < ch.length; ++i) {
            char c = ch[i];
            if (inString) {
                if (c == '\\') {
                    escape = true;
                } else if (!escape && (c == '"')) {
                    ++stringCount;
                    String constraintText = text.substring(beginIndex + 1, i).trim().replaceAll("\\\\(.)", "$1");
                    if (constraintText.isEmpty()) {
                        constraintText = null;
                    }
                    switch (stringCount) {
                    case 1:
                        constraint.set_message(constraintText);
                        break;
                    case 2:
                        constraint.set_predicateQuery(constraintText);
                        break;
                    case 3:
                        constraint.set_offendingElementsQuery(constraintText);
                        break;
                    default:
                        throw new ProcessingException(getFilename(),
                                "Illegal constraint format. The constraint text was '" + text + "'.");
                    }
                    inString = false;
                } else if (escape && (c == '"')) {
                    escape = false;
                }
            } else {
                if (Character.isWhitespace(c)) {
                    // ignore
                } else {
                    if (c == '"') {
                        inString = true;
                        beginIndex = i;
                    } else {
                        throw new ProcessingException(getFilename(),
                                "Illegal constraint format. The constraint text was '" + text
                                        + "'.  Expected '\"' but got '" + c + "'.  (position = " + i + ").");
                    }
                }
            }
        }
        if (inString || escape || (stringCount < 2) || (stringCount > 3)) {
            throw new ProcessingException(getFilename(),
                    "Illegal constraint format.  The constraint text was '" + text + "'.");
        }
    }

    /**
     * Sets the upper bound of the multiplicity of an {@link Edge} as the 'max'
     * value of the current 'from' or 'to' Edge.
     * 
     * @throws XMLStreamException
     */
    private void handleUpperValue() throws XMLStreamException {
        assert (currentAssociationEnd != null) || inOwnedAttribute;
        int n = getValue();
        if ((currentAssociationEnd == null) && inOwnedAttribute) {
            if (n != 1) {
                throw new ProcessingException(getParser(), getFilename(),
                        "grUML does not support attribute multiplicities other than 1..1");
            }
        } else {
            assert currentAssociationEnd != null;
            assert n >= 1;
            currentAssociationEnd.set_max(n);
        }
    }

    /**
     * Retrieves the value of the 'value' attribute of the current XML element
     * and returns it.
     * 
     * @throws XMLStreamException
     * @return Retrieved integer value.
     */
    private int getValue() throws XMLStreamException {
        String val = getAttribute(UML_ATTRIBUTE_VALUE);
        return val == null ? 0 : val.equals("*") ? Integer.MAX_VALUE : Integer.parseInt(val);
    }

    /**
     * Sets the lower bound of the multiplicity of an {@link Edge} as the 'min'
     * value of the current 'from' or 'to' Edge.
     * 
     * @throws XMLStreamException
     */
    private void handleLowerValue() throws XMLStreamException {
        assert (currentAssociationEnd != null) || inOwnedAttribute;
        int n = getValue();
        if ((currentAssociationEnd == null) && inOwnedAttribute) {
            if (n != 1) {
                throw new ProcessingException(getParser(), getFilename(),
                        "grUML does not support attribute multiplicities other than 1..1");
            }
        } else {
            assert currentAssociationEnd != null;
            assert n >= 0;
            currentAssociationEnd.set_min(n);
        }
    }

    /**
     * Handles the stereotypes '<<graphclass>>', '<<record>>' and '<<abstract>>'
     * by taking the appropriate action for every stereotype.
     * 
     * '<<graphclass>>': The GraphClass will get the qualified name and all edge
     * of the stereotyped class. The stereotyped class will be deleted.
     * 
     * '<<record>>': A RecordDomain will be created and the qualified name and
     * all attributes will be transfered to it. The stereotyped class will be
     * deleted.
     * 
     * '<<abstract>>': The stereotype will be set to abstract.
     * 
     * @throws XMLStreamException
     */
    private void handleStereotype() throws XMLStreamException {
        String key = getAttribute(UML_ATTRIBUTE_KEY);

        if ((currentClass == null) && (currentClassId == null) && (currentAssociationEnd == null)
                && (currentAttribute == null) && (currentRecordDomain == null)
                && (currentRecordDomainComponent == null)) {
            if (key.equals("rsa2tg_ignore")) {
                ignoredPackages.add(packageStack.peek());
                return;
            } else {
                throw new ProcessingException(getParser(), getFilename(), "Unexpected stereotype <<" + key + ">>");
            }
        }

        if (currentClass == null) {
            throw new ProcessingException(getParser(), getFilename(), "Unexpected stereotype <<" + key + ">>");
        }

        if (key.equals("graphclass")) {
            // convert currentClass to graphClass

            // The stereotype '<<graphclass>>' can only be attached to UML
            // classes.
            if (!(currentClass instanceof VertexClass)) {
                throw new ProcessingException(getParser(), getFilename(),
                        "The stereotype '<<graphclass>>' is only valid for a UML class.");
            }

            AttributedElementClass aec = (AttributedElementClass) idMap.get(currentClassId);
            assert graphClass != null;
            graphClass.set_qualifiedName(aec.get_qualifiedName());
            Edge e = aec.getFirstIncidence();
            while (e != null) {
                Edge n = e.getNextIncidence();
                if (e instanceof ContainsGraphElementClass) {
                    e.delete();
                } else {
                    e.setThis(graphClass);
                }
                e = n;
            }
            aec.delete();
            idMap.put(currentClassId, graphClass);
            currentClass = graphClass;

        } else if (key.equals("record")) {
            // convert current class to RecordDomain

            // The stereotype '<<record>>' can only be attached to UML classes.
            if (!(currentClass instanceof VertexClass)) {
                throw new ProcessingException(getParser(), getFilename(),
                        "The stereotype '<<record>>' is only allow for UML-classes.");
            }

            RecordDomain rd = sg.createRecordDomain();
            rd.set_qualifiedName(currentClass.get_qualifiedName());
            Edge e = currentClass.getFirstIncidence();
            while (e != null) {
                Edge n = e.getNextIncidence();
                if (e instanceof ContainsGraphElementClass) {
                    sg.createContainsDomain((Package) e.getThat(), rd);
                    e.delete();
                } else if (e instanceof HasAttribute) {
                    Attribute att = (Attribute) e.getThat();
                    Edge d = att.getFirstHasDomainIncidence();
                    if (d != null) {
                        Domain dom = (Domain) e.getThat();
                        HasRecordDomainComponent comp = sg.createHasRecordDomainComponent(rd, dom);
                        comp.set_name(att.get_name());
                    } else {
                        String typeId = attributeType.getMark(att);

                        // There have to be a typeId.
                        if (typeId == null) {
                            throw new ProcessingException(getParser(), getFilename(),
                                    "No type id has been defined.");
                        }
                        Domain dom = sg.createStringDomain();
                        dom.set_qualifiedName(typeId);
                        preliminaryVertices.add(dom);
                        HasRecordDomainComponent comp = sg.createHasRecordDomainComponent(rd, dom);
                        recordComponentType.mark(comp, typeId);
                        attributeType.removeMark(att);
                    }
                    att.delete();
                }
                e = n;
            }
            if (currentClass.getDegree() != 0) {
                throw new ProcessingException(getParser(), getFilename(), "The <<record>> class '"
                        + currentClass.get_qualifiedName() + "' must not have any association.");
            }
            domainMap.put(rd.get_qualifiedName(), rd);
            idMap.put(currentClassId, rd);
            currentRecordDomain = rd;
            currentClass.delete();
            currentClass = null;
            currentClassId = null;
        } else if (key.equals("abstract")) {
            if (currentClass instanceof GraphElementClass) {
                GraphElementClass gec = (GraphElementClass) currentClass;
                gec.set_abstract(true);
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        "The stereotype <<abstract>> can only be specified for vertex and edge classes, but not for class '"
                                + currentClass.get_qualifiedName() + "'");
            }
        } else {
            if (ignoreUnknownStereotypes()) {
                System.out.println("Ignored stereotype <<" + key + ">> of " + currentClass);
            } else {
                throw new ProcessingException(getParser(), getFilename(),
                        "Unexpected stereotype '<<" + key + ">>'.");
            }
        }
    }

    /**
     * Handles a 'generalization' XML element by marking the current class.
     * 
     * @param parser
     *            {@link XMLStreamReader}, which points to the current XML
     *            element.
     */
    private void handleGeneralization() throws XMLStreamException {
        String general = getAttribute(UML_ATTRIBUTE_GENERAL);
        Set<String> gens = generalizations.getMark(currentClass);
        if (gens == null) {
            gens = new TreeSet<>();
            generalizations.mark(currentClass, gens);
        }
        gens.add(general);
    }

    /**
     * Handles a nested 'uml:PrimitivType' XML element by creating a
     * corresponding {@link Domain}.
     * 
     * @param xmiId
     *            XMI id of corresponding attribute
     * @throws XMLStreamException
     */
    private void handleNestedTypeElement(String xmiId) throws XMLStreamException {
        if ((currentAttribute == null) && (currentRecordDomain == null)) {
            throw new ProcessingException(getParser(), getFilename(),
                    "unexpected primitive type in element (XMI id " + xmiId + ")");
        }
        String href = getAttribute(UML_ATTRIBUTE_HREF);
        if (href == null) {
            throw new ProcessingException(getParser(), getFilename(),
                    "No type name specified in primitive type href of attribute (XMI id " + xmiId + ")");
        }
        Domain dom = null;
        if (href.endsWith("#String")) {
            dom = createDomain("String");
        } else if (href.endsWith("#Integer")) {
            dom = createDomain("Integer");
        } else if (href.endsWith("#Boolean")) {
            dom = createDomain("Boolean");
        } else if (href.endsWith("#double")) {
            dom = createDomain("Double");
        } else {
            throw new ProcessingException(getParser(), getFilename(),
                    "Unknown primitive type with href '" + href + "' in attribute (XMI id " + xmiId + ")");
        }

        assert dom != null;
        if (currentRecordDomain != null) {
            // type of record domain component
            assert currentRecordDomainComponent != null;
            Domain d = currentRecordDomainComponent.getOmega();
            assert (d instanceof StringDomain) && (d.get_qualifiedName() == null)
                    && preliminaryVertices.contains(d);
            currentRecordDomainComponent.setOmega(dom);
            d.delete();
            preliminaryVertices.remove(d);
            recordComponentType.removeMark(currentRecordDomainComponent);
        } else {
            // type of an attribute of an AttributedElementClass
            assert currentAttribute != null;
            sg.createHasDomain(currentAttribute, dom);
            attributeType.removeMark(currentAttribute);
        }
    }

    /**
     * Handles a 'ownedAttribute' XML element of type 'uml:Property' by creating
     * a {@link Attribute} and linking it with its
     * {@link AttributedElementClass}.
     * 
     * @param parser
     *            {@link XMLStreamReader}, which points to the current XML
     *            element.
     * @param xmiId
     *            XMI id of the current XML element.
     */
    private void handleOwnedAttribute(String xmiId) throws XMLStreamException {
        String association = getAttribute(UML_ATTRIBUTE_ASSOCIATION);
        if (association == null) {
            String attrName = getAttribute(UML_ATTRIBUTE_NAME);

            if ((currentClass == null) && (currentRecordDomain == null)) {
                throw new ProcessingException(getParser(), getFilename(),
                        "Found an attribute '" + attrName + "' (XMI id " + xmiId + ") outside a class!");
            }

            if (attrName == null) {
                throw new ProcessingException(getParser(), getFilename(),
                        "No attribute name in ownedAttribute (XMI id " + xmiId + ")");
            }
            attrName = attrName.trim();

            if (attrName.length() == 0) {
                throw new ProcessingException(getParser(), getFilename(),
                        "Empty attribute name in ownedAttribute (XMI id " + xmiId + ")");
            }

            String isDerived = getAttribute(UML_ATTRIBUTE_ISDERIVED);
            boolean derived = (isDerived != null) && isDerived.equals(UML_TRUE);

            if (derived) {
                // ignore derived attributes
                return;
            }

            String typeId = getAttribute(UML_ATTRIBUTE_TYPE);

            if (currentClass != null) {
                // property is an "ordinary" attribute
                Attribute att = sg.createAttribute();
                currentAttribute = att;
                att.set_name(attrName);
                sg.createHasAttribute(currentClass, att);
                if (typeId != null) {
                    attributeType.mark(att, typeId);
                }
            } else {
                // property is a record component
                assert currentRecordDomain != null;
                currentAttribute = null;
                currentRecordDomainComponent = null;
                if (typeId != null) {
                    Vertex v = idMap.get(typeId);
                    if (v != null) {
                        assert v instanceof Domain : "typeID says " + typeId + " which is no Domain!";
                        currentRecordDomainComponent = sg.createHasRecordDomainComponent(currentRecordDomain,
                                (Domain) v);
                    } else {
                        Domain dom = sg.createStringDomain();
                        dom.set_qualifiedName(typeId);
                        preliminaryVertices.add(dom);
                        currentRecordDomainComponent = sg.createHasRecordDomainComponent(currentRecordDomain, dom);
                        recordComponentType.mark(currentRecordDomainComponent, typeId);
                    }
                } else {
                    Domain dom = sg.createStringDomain();
                    preliminaryVertices.add(dom);
                    currentRecordDomainComponent = sg.createHasRecordDomainComponent(currentRecordDomain, dom);
                }
                currentRecordDomainComponent.set_name(attrName);
            }
        } else {
            handleAssociationEnd(xmiId);
        }
    }

    /**
     * Handles a 'ownedEnd' XML element of type 'uml:Property' by creating an
     * appropriate {@link From} edge.
     * 
     * @param xmiId
     * @throws XMLStreamException
     */
    private void handleAssociationEnd(String xmiId) throws XMLStreamException {
        String endName = getAttribute(UML_ATTRIBUTE_NAME);
        if ((currentClass == null) || (currentRecordDomain != null)) {
            throw new ProcessingException(getParser(), getFilename(), "Found an association end '" + endName
                    + "' (XMI id " + xmiId + ") outside a class or in a record domain");
        }

        String agg = getAttribute(UML_ATTRIBUTE_AGGREGATION);
        boolean aggregation = (agg != null) && agg.equals(UML_SHARED);
        boolean composition = (agg != null) && agg.equals(UML_COMPOSITE);

        String typeId = getAttribute(UML_ATTRIBUTE_TYPE);

        if (typeId == null) {
            throw new ProcessingException(getParser(), getFilename(),
                    "No type attribute in association end (XMI id" + xmiId + ")");
        }

        IncidenceClass inc = (IncidenceClass) idMap.get(xmiId);
        if (inc == null) {
            // try to find the end's VertexClass
            // if not found, create a preliminary VertexClass
            VertexClass vc = null;

            // we have an "ownedEnd", vertex class id is in "type" attribute

            AttributedElement<?, ?> ae = idMap.get(typeId);
            if (ae != null) {
                if (!(ae instanceof VertexClass)) {
                    throw new ProcessingException(getParser(), getFilename(),
                            "Type attribute of association end (XMI id " + xmiId
                                    + ") must denote a VertexClass, but is "
                                    + ae.getAttributedElementClass().getQualifiedName());
                }
                // VertexClass found
                vc = (VertexClass) ae;
            } else {
                // create a preliminary vertex class
                vc = sg.createVertexClass();
                vc.set_qualifiedName(typeId);
                preliminaryVertices.add(vc);
                idMap.put(typeId, vc);
            }

            // try to find the end's EdgeClass
            EdgeClass ec = null;
            if (currentClass instanceof EdgeClass) {
                // we have an "ownedEnd", so the end's Edge is the
                // currentClass
                ec = (EdgeClass) currentClass;
                idMap.put(currentClassId, currentClass);
            } else {
                // we have an ownedAttribute
                // edge class id is in "association"
                String associationId = getAttribute(UML_ATTRIBUTE_ASSOCIATION);

                if (associationId == null) {
                    throw new ProcessingException(getParser(), getFilename(),
                            "No assiocation attribute in association end (XMI id " + xmiId + ")");
                }
                ae = idMap.get(associationId);

                if (ae != null) {
                    if (!(ae instanceof EdgeClass)) {
                        throw new ProcessingException(getParser(), getFilename(),
                                "Assiocation attribute of association end (XMI id " + xmiId
                                        + ") must denote an EdgeClass, but is "
                                        + ae.getAttributedElementClass().getQualifiedName());
                    }
                    // EdgeClass found
                    ec = (EdgeClass) ae;
                } else {
                    // create a preliminary edge class
                    ec = sg.createEdgeClass();
                }

                preliminaryVertices.add(ec);
                idMap.put(associationId, ec);
            }

            assert (vc != null) && (ec != null);
            inc = sg.createIncidenceClass();
            inc.set_min(DEFAULT_MIN_MULTIPLICITY);
            inc.set_max(DEFAULT_MAX_MULTIPLICITY);
            sg.createComesFrom(ec, inc);
            sg.createEndsAt(inc, vc);
        } else {
            EdgeClass ec = (EdgeClass) (inc.getFirstComesFromIncidence() != null ? inc.getFirstComesFromIncidence()
                    : inc.getFirstGoesToIncidence()).getThat();
            String id = null;
            for (Entry<String, Vertex> idEntry : idMap.entrySet()) {
                if (idEntry.getValue() == ec) {
                    id = idEntry.getKey();
                    break;
                }
            }

            assert id != null;
            idMap.put(id, ec);

            // an ownedEnd of an association or an ownedAttribute of a class
            // with a possibly preliminary vertex class
            VertexClass vc = (VertexClass) inc.getFirstEndsAtIncidence().getThat();
            if (preliminaryVertices.contains(vc)) {

                AttributedElement<?, ?> ae = idMap.get(typeId);

                if ((ae != null) && !vc.equals(ae)) {
                    if (!(ae instanceof VertexClass)) {
                        throw new ProcessingException(getParser(), getFilename(),
                                "Type attribute of association end (XMI id " + xmiId
                                        + ") must denote a VertexClass, but is "
                                        + ae.getAttributedElementClass().getQualifiedName());
                    }
                    inc.getFirstEndsAtIncidence().setOmega((VertexClass) ae);

                    Set<String> gens = generalizations.getMark(vc);
                    if (gens != null) {
                        generalizations.removeMark(vc);
                        generalizations.mark(ae, gens);
                    }

                    vc.delete();
                    preliminaryVertices.remove(vc);
                } else if (ae == null) {
                    idMap.put(typeId, vc);
                } else {
                    throw new RuntimeException("FIXME: Unexpected type. You should not get here!");
                }
            }
        }

        assert inc != null;
        currentAssociationEnd = inc;
        if (currentClass instanceof EdgeClass) {
            ownedEnds.add(inc);
        }
        inc.set_aggregation(aggregation ? AggregationKind.SHARED
                : composition ? AggregationKind.COMPOSITE : AggregationKind.NONE);
        idMap.put(xmiId, inc);
        inc.set_roleName(endName);
    }

    /**
     * Reconnects all edges of an <code>oldVertex</code> to
     * <code>newVertex</code>.
     * 
     * @param oldVertex
     *            Old {@link Vertex}, of which all edge should be reattached.
     * @param newVertex
     *            New {@link Vertex}, to which all edge should be attached.
     */
    private void reconnectEdges(Vertex oldVertex, Vertex newVertex) {
        Edge curr = oldVertex.getFirstIncidence();
        while (curr != null) {
            Edge next = curr.getNextIncidence();
            curr.setThis(newVertex);
            curr = next;
        }
    }

    /**
     * Creates a Domain vertex corresponding to the specified
     * <code>typeName</code>.
     * 
     * This vertex can also be a preliminary vertex which has to be replaced by
     * the correct Domain later. In this case, there is no "ContainsDomain"
     * edge, and the type is "StringDomain".
     * 
     * @param typeName
     *            Describes the Domain, which should be created.
     * @return Created Domain.
     */
    private Domain createDomain(String typeName) {
        Domain dom = domainMap.get(typeName);
        if (dom != null) {
            return dom;
        }

        if (typeName.equals("String")) {
            dom = sg.createStringDomain();
        } else if (typeName.equals("Integer")) {
            dom = sg.createIntegerDomain();
        } else if (typeName.equals("Double")) {
            dom = sg.createDoubleDomain();
        } else if (typeName.equals("Long")) {
            dom = sg.createLongDomain();
        } else if (typeName.equals("Boolean")) {
            dom = sg.createBooleanDomain();
        } else if (typeName.startsWith("Map<") && typeName.endsWith(">")) {
            dom = sg.createMapDomain();
            String keyValueDomains = typeName.substring(4, typeName.length() - 1);
            char[] c = keyValueDomains.toCharArray();
            // find the delimiting ',' and take into account nested domains
            int p = 0;
            for (int i = 0; i < c.length; ++i) {
                if ((c[i] == ',') && (p == 0)) {
                    p = i;
                    break;
                }
                if (c[i] == '<') {
                    ++p;
                } else if (c[i] == '>') {
                    --p;
                }
                if (p < 0) {
                    throw new ProcessingException(getFilename(),
                            "Error in primitive type name: '" + typeName + "'");
                }
            }

            if ((p <= 0) || (p >= (c.length - 1))) {
                throw new ProcessingException(getFilename(), "Error in primitive type name: '" + typeName + "'");
            }
            String keyDomainName = keyValueDomains.substring(0, p);
            Domain keyDomain = createDomain(keyDomainName);
            assert keyDomain != null;

            String valueDomainName = keyValueDomains.substring(p + 1);
            Domain valueDomain = createDomain(valueDomainName);
            assert valueDomain != null;

            sg.createHasKeyDomain((MapDomain) dom, keyDomain);
            sg.createHasValueDomain((MapDomain) dom, valueDomain);

            // Adds a space between
            typeName = "Map<" + keyDomainName + ", " + valueDomainName + '>';

        } else if (typeName.startsWith("List<") && typeName.endsWith(">")) {
            dom = sg.createListDomain();
            String compTypeName = typeName.substring(5, typeName.length() - 1);
            Domain compDomain = createDomain(compTypeName);
            assert compDomain != null;

            sg.createHasBaseDomain((CollectionDomain) dom, compDomain);
        } else if (typeName.startsWith("Set<") && typeName.endsWith(">")) {
            dom = sg.createSetDomain();
            String compTypeName = typeName.substring(4, typeName.length() - 1);
            Domain compDomain = createDomain(compTypeName);
            assert compDomain != null;

            sg.createHasBaseDomain((CollectionDomain) dom, compDomain);
        }
        if (dom != null) {
            sg.createContainsDomain(packageStack.get(0), dom);
        } else {
            // there must exist a named domain (Enum or Record)
            // but this was not yet created in the graph
            // create preliminary domain vertex which will
            // later be re-linked and deleted
            dom = sg.createStringDomain();
            preliminaryVertices.add(dom);
        }

        assert dom != null;
        dom.set_qualifiedName(typeName);
        domainMap.put(typeName, dom);
        return dom;
    }

    /**
     * Returns the qualified name for the simple name <code>simpleName</code>.
     * The qualified name consists the (already qualified) name of the package
     * on top of the package stack and the name <code>simpleName</code>,
     * separated by a dot. If the top package is the default package, the name
     * <code>simpleName</code> is already the qualified name. If the package
     * stack is empty
     * 
     * @param simpleName
     *            a simple name of a class or package
     * @return the qualified name for the simple name
     */
    private String getQualifiedName(String simpleName) {

        assert simpleName != null;
        simpleName = simpleName.trim();
        Package p = packageStack.peek();

        assert p != null;
        if (p.get_qualifiedName().equals("")) {
            return simpleName;
        } else {
            return p.get_qualifiedName() + "." + simpleName;
        }
    }

    /**
     * <code>true</code> indicates, that the roles from edges should be used.
     * 
     * @param useFromRole
     *            Value for the <code>useFromRole</code> flag.
     */
    public void setUseFromRole(boolean useFromRole) {
        this.useFromRole = useFromRole;
    }

    /**
     * Will return <code>true</code>, if the roles from the from edge should be
     * used.
     * 
     * @return Value of the <code>useFromRole</code> flag.
     */
    public boolean isUseFromRole() {
        return useFromRole;
    }

    public boolean ignoreUnknownStereotypes() {
        return ignoreUnknownStereotypes;
    }

    public void setIgnoreUnknownStereotypes(boolean ignore) {
        ignoreUnknownStereotypes = ignore;
    }

    /**
     * <code>true</code> forces the removal of all unlinked {@link Domain}
     * objects.
     * 
     * @param removeUnusedDomains
     *            Value of the <code>removeUnusedDomain</code> flag.
     */
    public void setRemoveUnusedDomains(boolean removeUnusedDomains) {
        this.removeUnusedDomains = removeUnusedDomains;
    }

    /**
     * Will return <code>true</code>, if unlinked {@link Domain} objects should
     * be removed in the last processing step.
     * 
     * @return Value of the <code>removeUnusedDoimain</code> flag.
     */
    public boolean isRemoveUnusedDomains() {
        return removeUnusedDomains;
    }

    /**
     * <code>true</code> indicates, that the navigability of edges should be
     * used.
     * 
     * @param useNavigability
     *            Value for the <code>useNavigability</code> flag.
     */
    public void setUseNavigability(boolean useNavigability) {
        this.useNavigability = useNavigability;
    }

    /**
     * Will return <code>true</code>, if the navigability of edges should be
     * used.
     * 
     * @return Value of the <code>useNavigability</code> flag.
     */
    public boolean isUseNavigability() {
        return useNavigability;
    }

    /**
     * Returns the {@link SchemaGraph}, which has been created after executing
     * {@link Rsa2Tg#process(String)}.
     * 
     * @return Created SchemaGraph.
     */
    public SchemaGraph getSchemaGraph() {
        return sg;
    }

    /**
     * Determines whether or not all output will be suppressed.
     * 
     * @param suppressOutput
     *            Value for the <code>suppressOutput</code> flag.
     */
    public void setSuppressOutput(boolean suppressOutput) {
        this.suppressOutput = suppressOutput;
    }

    /**
     * Returns the file name of the TG Schema file.
     * 
     * @return File name as {@link String}.
     */
    public String getFilenameSchema() {
        return filenameSchema;
    }

    /**
     * Sets the file name of the TG Schema file.
     * 
     * @param filenameSchema
     *            File name as {@link String}.
     */
    public void setFilenameSchema(String filenameSchema) {
        this.filenameSchema = filenameSchema;
    }

    /**
     * Returns the file name of the TG grUML SchemaGraph file.
     * 
     * @return File name as {@link String}.
     */
    public String getFilenameSchemaGraph() {
        return filenameSchemaGraph;
    }

    /**
     * Sets the file name of the TG grUML SchemaGraph file.
     * 
     * @param filenameSchemaGraph
     *            file name as {@link String}.
     */
    public void setFilenameSchemaGraph(String filenameSchemaGraph) {
        this.filenameSchemaGraph = filenameSchemaGraph;
    }

    /**
     * Returns the file name of the DOT file.
     * 
     * @return File name as {@link String}.
     */
    public String getFilenameDot() {
        return filenameDot;
    }

    /**
     * Sets the file name of the DOT file.
     * 
     * @param filenameDot
     *            File name as {@link String}.
     */
    public void setFilenameDot(String filenameDot) {
        this.filenameDot = filenameDot;
    }

    /**
     * Returns the file name of the HTML validation file.
     * 
     * @return File name as {@link String}.
     */
    public String getFilenameValidation() {
        return filenameValidation;
    }

    /**
     * Sets the file name of the HTML validation file.
     * 
     * @param filenameValidation
     *            File name as {@link String}.
     */
    public void setFilenameValidation(String filenameValidation) {
        this.filenameValidation = filenameValidation;
    }

    public boolean isKeepEmptyPackages() {
        return keepEmptyPackages;
    }

    public void setKeepEmptyPackages(boolean removeEmptyPackages) {
        keepEmptyPackages = removeEmptyPackages;
    }

}