org.corpus_tools.salt.util.SaltUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.corpus_tools.salt.util.SaltUtil.java

Source

/**
 * Copyright 2009 Humboldt-Universitt zu Berlin, INRIA.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 */
package org.corpus_tools.salt.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.corpus_tools.salt.SaltFactory;
import org.corpus_tools.salt.common.SCorpus;
import org.corpus_tools.salt.common.SCorpusGraph;
import org.corpus_tools.salt.common.SDocument;
import org.corpus_tools.salt.common.SDocumentGraph;
import org.corpus_tools.salt.common.SaltProject;
import org.corpus_tools.salt.core.SAnnotation;
import org.corpus_tools.salt.core.SAnnotationContainer;
import org.corpus_tools.salt.core.SFeature;
import org.corpus_tools.salt.core.SLayer;
import org.corpus_tools.salt.core.SMetaAnnotation;
import org.corpus_tools.salt.core.SNode;
import org.corpus_tools.salt.core.SProcessingAnnotation;
import org.corpus_tools.salt.core.SRelation;
import org.corpus_tools.salt.exceptions.SaltException;
import org.corpus_tools.salt.exceptions.SaltResourceException;
import org.corpus_tools.salt.graph.Identifier;
import org.corpus_tools.salt.graph.Label;
import org.corpus_tools.salt.semantics.SCatAnnotation;
import org.corpus_tools.salt.semantics.SLemmaAnnotation;
import org.corpus_tools.salt.semantics.SPOSAnnotation;
import org.corpus_tools.salt.semantics.SSentenceAnnotation;
import org.corpus_tools.salt.semantics.STypeAnnotation;
import org.corpus_tools.salt.semantics.SWordAnnotation;
import org.corpus_tools.salt.util.internal.persistence.SaltXML10Handler;
import org.corpus_tools.salt.util.internal.persistence.SaltXML10Writer;
import org.corpus_tools.salt.util.internal.persistence.dot.SCorpusGraphDOTWriter;
import org.corpus_tools.salt.util.internal.persistence.dot.SDocumentGraphDOTWriter;
import org.eclipse.emf.common.util.URI;
import org.omg.PortableInterceptor.ServerIdHelper;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * This class contains a set of helpful methods.
 * 
 * @author florian
 *
 */
public class SaltUtil {

    // ===================================> common Salt stuff
    /** The ending of a Salt XML file. **/
    public static final String FILE_ENDING_SALT_XML = "salt";
    /** The ending of a dot file. **/
    public static final String FILE_ENDING_DOT = "dot";
    /**
     * Default name of the saltProject file.
     */
    public static final String FILE_SALT_PROJECT = "saltProject" + "." + FILE_ENDING_SALT_XML;

    /**
     * default name for of derived namespace-attribute
     */
    public static final String SALT_NAMESPACE = "salt";
    /**
     * Separator between namespace and name: qname= NAMESPACE
     * {@value #NAMESPACE_SEPERATOR} NAME.
     */
    public static final String NAMESPACE_SEPERATOR = "::";
    /**
     * A NULL value as String.
     */
    public static final String SALT_NULL_VALUE = SaltUtil.SALT_NAMESPACE + SaltUtil.NAMESPACE_SEPERATOR + "NULL";
    // ===================================< common Salt stuff

    // ======================================> index names
    /** name of index for node-types */
    public static final String IDX_NODETYPE = "idx_sNodeType";
    /** name of index for relation-types */
    public static final String IDX_RELATIONTYPE = "idx_sRelationType";
    /** name of index for relating ids and nodes */
    public static final String IDX_ID_NODES = "idx_id_nodes";
    /** name of index for relating ids and nodes (inverse) */
    public static final String IDX_ID_NODES_INVERSE = "idx_id_nodes_inverse";
    /** name of index for relating ids and relations */
    public static final String IDX_ID_RELATIONS = "idx_id_relation";
    /** name of index for relating ids and relations (inverse) */
    public static final String IDX_ID_RELATIONS_INVERSE = "idx_id_relation_inverse";
    /** name of index for relating ids and layers */
    public static final String IDX_ID_LAYER = "idx_id_layer";
    /** name of index for relating node ids and outgoing relations */
    public static final String IDX_OUT_RELATIONS = "idx_out_relations";
    /** name of index for relating node ids and incoming relations */
    public static final String IDX_IN_RELATIONS = "idx_in_relations";
    // ======================================< index names

    // ======================================> keywords for features
    /**
     * Name of {@link Label} to store the identifier of a node, relation, graph
     * or layer.
     */
    public static final String LABEL_ID = "id";
    /**
     * Qualified name of {@link Label} to store the identifier of a node,
     * relation, graph or layer.
     */
    public static final String LABEL_ID_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + LABEL_ID;
    /**
     * Name for {@link SFeature} to store the type of a labels value.
     */
    public static final String FEAT_VALUE_DATATYPE = "SVAL_TYPE";
    /**
     * Name for {@link SFeature} to store the name of a node, relation, graph or
     * label.
     */
    public static final String FEAT_NAME = "SNAME";
    /**
     * Qualified name for {@link SFeature} to store the name of a node,
     * relation, graph or label.
     */
    public static final String FEAT_NAME_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + FEAT_NAME;
    /**
     * Name for {@link SFeature} to store the type of a relation.
     */
    public static final String FEAT_TYPE = "STYPE";
    /**
     * Qualified name for {@link SFeature} to store the type of a relation.
     */
    public static final String FEAT_TYPE_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + FEAT_TYPE;

    /**
     * Name for {@link SFeature} to store the {@link SDocumentGraph} in a
     * {@link SDocument} object
     */
    public static final String FEAT_SDOCUMENT_GRAPH = "SDOCUMENT_GRAPH";
    /**
     * Qualified name for {@link SFeature} to store the {@link SDocumentGraph}
     * in a {@link SDocument} object
     */
    public static final String FEAT_SDOCUMENT_GRAPH_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR
            + "SDOCUMENT_GRAPH";
    /**
     * Name for {@link SFeature} to store the {@link SDocument} corresponding to
     * a {@link SDocumentGraph} object
     */
    public static final String FEAT_SDOCUMENT = "SDOCUMENT";
    /**
     * Qualified name for {@link SFeature} to store the {@link SDocument}
     * corresponding to a {@link SDocumentGraph} object
     */
    public static final String FEAT_SDOCUMENT_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + "SDOCUMENT";
    /**
     * Name for {@link SFeature} to store the uri reference of a
     * {@link SDocumentGraph} in a {@link SDocument}
     */
    public static final String FEAT_SDOCUMENT_GRAPH_LOCATION = "SDOCUMENT_GRAPH_LOCATION";
    /**
     * Qualified name name for {@link SFeature} to store the uri reference of a
     * {@link SDocumentGraph} in a {@link SDocument}
     */
    public static final String FEAT_SDOCUMENT_GRAPH_LOCATION_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR
            + FEAT_SDOCUMENT_GRAPH_LOCATION;
    /**
     * The name of the {@link SFeature} for the reference to an audio file.
     */
    public static final String FEAT_SMEDIA_REFERNCE = "SAUDIO_REFERENCE";
    /**
     * The qualified name of the name of the {@link SFeature} for the reference
     * to an audio file.
     */
    public static final String FEAT_SMEDIA_REFERNCE_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR
            + FEAT_SMEDIA_REFERNCE;

    /** The name of the {@link SFeature} for the start value. */
    public static final String FEAT_SSTART = "SSTART";
    /**
     * The qualified name of the name of the {@link SFeature} for the start
     * value.
     */
    public static final String FEAT_SSTART_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + FEAT_SSTART;
    /** The name of the {@link SFeature} for the end value. */
    public static final String FEAT_SEND = "SEND";
    /**
     * The qualified name of the name of the {@link SFeature} for the end value.
     */
    public static final String FEAT_SEND_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + FEAT_SEND;
    /**
     * The name of the {@link SFeature} for the data label for textual sources,
     * etc..
     */
    public static final String FEAT_SDATA = "SDATA";

    /**
     * Qualified name name of the {@link SFeature} for the data label for
     * textual sources, etc..
     */
    public static final String FEAT_SDATA_QNAME = SALT_NAMESPACE + NAMESPACE_SEPERATOR + FEAT_SDATA;

    // ======================================< keywords for features
    // ======================================> keywords for semantics
    /** name for {@link SCatAnnotation} **/
    public static final String SEMANTICS_CAT = "cat";
    /** name for {@link SPOSAnnotation} **/
    public static final String SEMANTICS_POS = "pos";
    /** name for {@link SLemmaAnnotation} **/
    public static final String SEMANTICS_LEMMA = "lemma";
    /** name for {@link SSentenceAnnotation} **/
    public static final String SEMANTICS_SENTENCE = "sentence";
    /** value for {@link STypeAnnotation} **/
    public static final String SEMANTICS_TYPE = "type";
    /** value for {@link SWordAnnotation} **/
    public static final String SEMANTICS_WORD = "word";
    /** name for {@link SWordAnnotation} and {@link SSentenceAnnotation} **/
    public static final String SEMANTICS_UNIT = "unit";

    // ======================================< keywords for semantics

    /**
     * Returns the concatenation of a labels namespace and a labels name as a
     * qualified name: qname= NAMESPACE {@value #NAMESPACE_SEPERATOR} NAME.
     * 
     * @param namespace
     *            the namespace of a label
     * @param name
     *            the name of a label
     */
    public static String createQName(String namespace, String name) {
        if (namespace == null || namespace.isEmpty()) {
            return name;
        } else if (name == null || name.isEmpty()) {
            return namespace + Label.NS_SEPERATOR;
        } else {
            return namespace + Label.NS_SEPERATOR + name;
        }
    }

    /**
     * Splits the passed qName into a namespace and a name.
     * 
     * @return {@link Pair#getLeft()} is the namespace, whereas
     *         {@link Pair#getRight()} is the name
     */
    public static Pair<String, String> splitQName(String qName) {
        Pair<String, String> retVal = null;
        if ((qName != null) && (!qName.isEmpty())) {
            String[] fullName = qName.split(NAMESPACE_SEPERATOR);
            StringBuffer ns = new StringBuffer();
            String name = null;
            for (int i = 0; i < fullName.length; i++) {
                // last part of fullname reached
                if (i == fullName.length - 1) {
                    name = fullName[i];
                } else {
                    if ((ns.toString().isEmpty())) {
                        ns.append(fullName[i]);
                    } else {
                        ns.append(NAMESPACE_SEPERATOR);
                        ns.append(fullName[i]);
                    }
                }
            }
            if ((ns.length() != 0) && (name.length() != 0)) {
                retVal = new ImmutablePair<String, String>(ns.toString(), name);
            } else if (ns.length() != 0) {
                retVal = new ImmutablePair<String, String>(ns.toString(), null);
            } else if (name.length() != 0) {
                retVal = new ImmutablePair<String, String>(null, name);
            }
        }
        return (retVal);
    }

    /**
     * Creates a Salt URI from the passed path.
     * 
     * @param path
     *            path to an element
     * @return corresponding Salt URI
     */
    public static URI createSaltURI(String path) {
        URI uri = null;
        if (path != null && !path.isEmpty()) {
            if (path.startsWith(SALT_NAMESPACE + ":")) {
                uri = URI.createURI(path);
            } else {
                uri = URI.createURI(SALT_NAMESPACE + ":" + path);
            }
        }
        return (uri);
    }

    /**
     * Returns a global ID for the passed object (either if {@link Identifier}
     * belongs to {@link SDocument} or {@link SCorpus} object). A global id only
     * can be returned, if the element is contained in a {@link SCorpusGraph}
     * object. If this is not the case, the {@link ServerIdHelper} is returned.
     * 
     * @param id
     *            Object to retrieve global id.
     * @return String value for global id, if one is given.
     */
    public static String getGlobalId(Identifier id) {
        StringBuffer globalId = new StringBuffer();
        if ((id != null) && (id.getIdentifiableElement() != null)) {
            SCorpusGraph graph = null;
            if ((id.getIdentifiableElement() instanceof SDocument)
                    && (((SDocument) id.getIdentifiableElement()).getGraph() != null)) {
                graph = ((SDocument) id.getIdentifiableElement()).getGraph();
            } else if ((id.getIdentifiableElement() instanceof SCorpus)
                    && (((SCorpus) id.getIdentifiableElement()).getGraph() != null)) {
                graph = ((SCorpus) id.getIdentifiableElement()).getGraph();
            } else if (id.getIdentifiableElement() instanceof SNode) {
                SNode sNode = (SNode) id.getIdentifiableElement();
                if ((sNode.getGraph() != null)) {
                    if (sNode.getGraph() instanceof SCorpusGraph) {
                        graph = (SCorpusGraph) sNode.getGraph();
                    } else if (sNode.getGraph() instanceof SDocumentGraph) {
                        graph = ((SDocumentGraph) sNode.getGraph()).getDocument().getGraph();
                    }
                }
            } else if (id.getIdentifiableElement() instanceof SRelation) {
                SRelation<SNode, SNode> sRel = (SRelation<SNode, SNode>) id.getIdentifiableElement();
                if ((sRel.getGraph() != null)) {
                    if (sRel.getGraph() instanceof SCorpusGraph) {
                        graph = (SCorpusGraph) sRel.getGraph();
                    } else if (sRel.getGraph() instanceof SDocumentGraph) {
                        graph = ((SDocumentGraph) sRel.getGraph()).getDocument().getGraph();
                    }
                }
            }

            if (graph != null) {
                globalId.append(SALT_NAMESPACE + ":");
                SaltProject project = graph.getSaltProject();
                if (project != null) {
                    globalId.append("/");
                    globalId.append(project.getCorpusGraphs().indexOf(graph));
                    globalId.append("/");
                }
                String actualId = id.getId().replace(SALT_NAMESPACE + ":", "");
                if (actualId.startsWith("/")) {
                    actualId = actualId.substring(1, actualId.length());
                }
                globalId.append(actualId);
                if (id.getIdentifiableElement() instanceof SCorpus) {
                    globalId.append("/");
                }
            } else {
                globalId.append(id.getId());
            }
        }
        return (globalId.toString());
    }

    // ================================================> Persistence SaltXML
    /**
     * Loads an object coming from a SaltXML (.{@link #FILE_ENDING_SALT_XML})
     * and returns it.
     * 
     * If multiple objects are contained in the file it will only load the first one.
     * 
     * @param location
     *            {@link URI} to SaltXML file containing the object or {@code null} if the fule does not contain any objects.
     * @return loaded object
     * 
     * @see #loadObjects(org.eclipse.emf.common.util.URI) loadObjects(...): similar function that returns all the objects of a file.
     */
    public static Object load(URI location) {
        // actially get all the objects that are included in the file
        List<Object> objects = loadObjects(location);
        if (objects == null || objects.isEmpty()) {
            return null;
        } else {
            // only return the first one.s
            return objects.get(0);
        }
    }

    // ================================================> Persistence SaltXML
    /**
     * Loads a list of root objects coming from a SaltXML (.{@link #FILE_ENDING_SALT_XML})
     * and returns it.
     * 
     * @param objectURI
     *            {@link URI} to SaltXML file containing the object
     * @return loaded objects
     */
    public static List<Object> loadObjects(URI location) {
        if (location == null) {
            throw new SaltResourceException("Cannot load Salt object, because the given uri is null.");
        }
        File objectFile = new File(
                (location.toFileString() == null) ? location.toString() : location.toFileString());
        if (!objectFile.exists()) {
            throw new SaltResourceException("Cannot load Salt object, because the file '"
                    + objectFile.getAbsolutePath() + "' does not exist.");
        }

        SAXParser parser;
        XMLReader xmlReader;
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SaltXML10Handler contentHandler = new SaltXML10Handler();

        try {
            parser = factory.newSAXParser();
            xmlReader = parser.getXMLReader();
            xmlReader.setContentHandler(contentHandler);
        } catch (ParserConfigurationException e) {
            throw new SaltResourceException(
                    "Cannot load Salt object from file '" + objectFile.getAbsolutePath() + "'.", e);
        } catch (Exception e) {
            throw new SaltResourceException(
                    "Cannot load Salt object from file '" + objectFile.getAbsolutePath() + "'.", e);
        }
        try {
            InputStream inputStream = new FileInputStream(objectFile);
            Reader reader = new InputStreamReader(inputStream, "UTF-8");
            InputSource is = new InputSource(reader);
            is.setEncoding("UTF-8");
            xmlReader.parse(is);
        } catch (SAXException e) {
            try {
                parser = factory.newSAXParser();
                xmlReader = parser.getXMLReader();
                xmlReader.setContentHandler(contentHandler);
                xmlReader.parse(objectFile.getAbsolutePath());
            } catch (Exception e1) {
                throw new SaltResourceException(
                        "Cannot load Salt object from file '" + objectFile.getAbsolutePath() + "'.", e1);
            }
        } catch (Exception e) {
            if (e instanceof SaltException) {
                throw (SaltException) e;
            } else {
                throw new SaltResourceException(
                        "Cannot load Salt object from file'" + objectFile + "', because of a nested exception. ",
                        e);
            }
        }
        return contentHandler.getRootObjects();
    }

    /**
     * Iterates through the documents of a corpus graph and sets the correct
     * document graph locations.
     * 
     * @see SDocument#getDocumentGraphLocation()
     * @param corpusGraph
     * @param root
     *            The root folder or the salt project file.
     */
    private static void insertDocumentGraphLocations(SCorpusGraph corpusGraph, URI root) {
        if (corpusGraph == null || root == null) {
            return;
        }

        File rootFile = new File(root.toFileString());
        if (rootFile.isFile()) {
            rootFile = rootFile.getParentFile();
            root = URI.createFileURI(rootFile.getAbsolutePath());
        }
        for (SDocument doc : corpusGraph.getDocuments()) {
            URI location = root;
            location = location.appendSegments(doc.getPath().segments());
            location = location.appendFileExtension(SaltUtil.FILE_ENDING_SALT_XML);
            File f = new File(location.toFileString());
            if (f.exists()) {
                doc.setDocumentGraphLocation(location);
            }
        }
    }

    /**
     * Moves the content of <code>source</code> to <code>target</code>. Caution:
     * Object contained in <code>source</code> will be moved, which from
     * <code>target</code> to <code>source</code>, which will mean, that object
     * are not content of <code>source</code> any more after using
     * {@link #moveCorpusGraph(SCorpusGraph, SCorpusGraph)}.
     * 
     * @param source
     *            {@link SCorpusGraph} delivering the content to
     *            moveSCorpusGraph
     * @param target
     *            {@link SCorpusGraph} object to where the content will be moved
     */
    public static void moveCorpusGraph(SCorpusGraph source, SCorpusGraph target) {
        // copy all sRelations and source and target SNode as well from loaded
        // graph into existing one
        for (SRelation<SNode, SNode> sRelation : new LinkedList<>(source.getRelations())) {
            if (target.getNode(sRelation.getSource().getId()) == null)
                target.addNode(sRelation.getSource());
            if (target.getNode(sRelation.getTarget().getId()) == null)
                target.addNode(sRelation.getTarget());
            target.addRelation(sRelation);
        }

        // copy all sNodes from loaded graph into existing one
        for (SNode sNode : new LinkedList<>(source.getNodes())) {
            if (target.getNode(sNode.getId()) == null)
                target.addNode(sNode);
            target.addNode(sNode);
        }

        // copy all sAnnotation from loaded graph into existing one
        for (SAnnotation sAnno : source.getAnnotations())
            target.addAnnotation(sAnno);

        // copy all sMetaAnnotation from loaded graph into existing one
        for (SMetaAnnotation sMetaAnno : source.getMetaAnnotations())
            target.addMetaAnnotation(sMetaAnno);
        // copy all sProcessingAnnotation from loaded graph into existing one
        for (SProcessingAnnotation sProcAnno : source.getProcessingAnnotations())
            target.addProcessingAnnotation(sProcAnno);

        // copy all SFeature from loaded graph into existing one
        for (SFeature sfeature : source.getFeatures()) {
            target.addFeature(sfeature);
        }

        // copy identifier
        target.setIdentifier(source.getIdentifier());

        // copy all sLayer from loaded graph into existing one
        for (SLayer sLayer : new LinkedList<>(source.getLayers()))
            target.addLayer(sLayer);
    }

    /**
     * Persists the passed {@link SDocumentGraph} object in a SaltXML file at
     * the passed location.
     * 
     * @param documentGraph
     *            {@link SDocumentGraph} object to persist
     * @param location
     *            location of where to persist object as {@link URI}
     */
    public static void saveDocumentGraph(SDocumentGraph documentGraph, URI location) {
        if (documentGraph != null && location != null) {
            String path = location.toFileString();
            if (path == null || path.isEmpty()) {
                path = location.toString();
            }
            File out = new File(path);
            out.getParentFile().mkdirs();

            SaltXML10Writer writer = new SaltXML10Writer(out);
            writer.writeDocumentGraph(documentGraph);
        }
    }

    /**
     * Loads a {@link SDocumentGraph} object and returns it. The location of
     * where to find the SaltXML containing the {@link SDocumentGraph} object is
     * given by the passed {@link URI} object.
     * 
     * @param location
     *            location of SaltXML to load {@link SDocumentGraph} object.
     */
    public static SDocumentGraph loadDocumentGraph(URI location) {
        SDocumentGraph retVal = null;
        Object obj = load(location);
        if (obj == null) {
            throw new SaltResourceException("Cannot load the requested " + SDocumentGraph.class.getName()
                    + ", because file located at contains no such object, the returned object was null.");
        } else if (obj instanceof SDocumentGraph) {
            retVal = (SDocumentGraph) obj;
        } else {
            throw new SaltResourceException("Cannot load the requested " + SDocumentGraph.class.getName()
                    + ", because file located at contains no such object. It contains: " + obj.getClass());
        }
        return (retVal);

    }

    /**
     * Persists the passed {@link SCorpusGraph} object in a
     * {@value SaltUtil#FILE_ENDING_SALT_XML} file at the passed location. The
     * relation between all {@link SDocument}s and their {@link SDocumentGraph}
     * will be removed.
     * 
     * @param corpusGraph
     *            {@link SCorpusGraph} object to persist
     * @param location
     *            location of where to persist object as {@link URI}
     */
    public static void saveCorpusGraph(SCorpusGraph corpusGraph, URI location) {
        if ((corpusGraph.getDocuments() != null) && (corpusGraph.getDocuments().size() > 0)) {
            Iterator<SDocument> docs = corpusGraph.getDocuments().iterator();
            while (docs.hasNext()) {
                SDocument doc = docs.next();
                URI docURI = location;
                for (String seg : doc.getPath().segments()) {
                    docURI = docURI.appendSegment(seg);
                }
                docURI = docURI.appendFileExtension(FILE_ENDING_SALT_XML);
                if (doc.getDocumentGraph() != null) {
                    // store document structure
                    doc.saveDocumentGraph(docURI);
                } else {
                    // only store folder structure
                    URI corpUri = docURI.trimFileExtension().trimSegments(1);
                    String str = corpUri.toFileString();
                    if (str == null || str.isEmpty()) {
                        str = corpUri.toString();
                    }
                    File corpFile = new File(str);
                    corpFile.mkdirs();
                }
            }
        }
    }

    /**
     * Loads the given SaltXML file (.{@value SaltFactory#FILE_ENDING_SALT})
     * into this object. If the given SaltXML file does not contain a
     * {@link SCorpusGraph} object persisting, an exception will be thrown. If
     * the SaltXML file contains persistings for more than one
     * {@link SCorpusGraph} object, the first one will be loaded.
     * 
     * @param sCorpusGraphURI
     *            the {@link URI} to locate the SaltXML file
     * @return
     */
    public static SCorpusGraph loadCorpusGraph(URI sCorpusGraphURI) {
        return loadCorpusGraph(sCorpusGraphURI, 0);
    }

    /**
     * Loads the given SaltXML file (.{@value SaltFactory#FILE_ENDING_SALT})
     * into this object. If the given SaltXML file does not contain a
     * {@link SCorpusGraph} object persisting, an exception will be thrown. The
     * parameter <code>idxOfSCorpusGraph</code> determines which object shall be
     * load, in case of the given SaltXML file contains more than one persisting
     * of {@link SCorpusGraph} objects.
     * 
     * @param sCorpusGraphUri
     *            the {@link URI} to locate the SaltXML file
     * @param idxOfSCorpusGraph
     *            number of graph to be load, note that the list of graphs
     *            starts with 0
     * @return
     */
    public static SCorpusGraph loadCorpusGraph(URI sCorpusGraphUri, Integer idxOfSCorpusGraph) {
        if (sCorpusGraphUri == null)
            throw new SaltResourceException("Cannot load '" + SCorpusGraph.class.getSimpleName()
                    + "' object, because the passed uri is empty. ");

        SCorpusGraph retVal = null;

        if (!sCorpusGraphUri.toFileString().endsWith("." + SaltUtil.FILE_ENDING_SALT_XML)) {
            // looks weird, but is necessary in case of uri ends with /
            if (sCorpusGraphUri.toString().endsWith("/")) {
                sCorpusGraphUri = sCorpusGraphUri.trimSegments(1);
            }
            sCorpusGraphUri = sCorpusGraphUri.appendSegment(SaltUtil.FILE_SALT_PROJECT);
        }

        Object obj = load(sCorpusGraphUri);
        if (obj instanceof SCorpusGraph) {
            retVal = (SCorpusGraph) obj;
        } else if (obj instanceof SaltProject) {
            if ((((SaltProject) obj).getCorpusGraphs() != null)
                    && (((SaltProject) obj).getCorpusGraphs().size() >= idxOfSCorpusGraph)) {
                retVal = ((SaltProject) obj).getCorpusGraphs().get(idxOfSCorpusGraph);
            }
        }

        if (retVal != null) {
            insertDocumentGraphLocations(retVal, sCorpusGraphUri);
        }

        return (retVal);
    }

    /**
     * Persists the passed {@link SaltProject} object in a SaltXML file at the
     * passed location. The relation between all {@link SDocument}s and their
     * {@link SDocumentGraph} will be removed.
     * 
     * @param saltProject
     *            {@link SaltProject} object to persist
     * @param location
     *            location of where to persist object as {@link URI}
     */
    public static void saveSaltProject(SaltProject saltProject, URI location) {
        if (location == null) {
            throw new SaltResourceException("Cannot save SaltProject, because the given uri is null.");
        }
        URI saltProjectFolder = null;
        URI saltProjectFile = null;
        if (FILE_ENDING_SALT_XML.equals(location.fileExtension())) {
            // location is a file name
            saltProjectFile = location;
            saltProjectFolder = location.trimFileExtension().trimSegments(1);
        } else {
            // location is a folder name
            saltProjectFile = location.appendSegment(FILE_SALT_PROJECT);
            saltProjectFolder = location;
        }
        // in case the salt project folder does not exist, create it
        String str = saltProjectFolder.toFileString();
        if (str == null || str.isEmpty()) {
            str = saltProjectFolder.toString();
        }
        File folder = new File(str);
        folder.mkdirs();

        // write SaltProject to file
        SaltXML10Writer writer = new SaltXML10Writer(saltProjectFile);
        writer.writeSaltProject(saltProject);

        // write folders and document structures to file
        if ((saltProject.getCorpusGraphs() != null) && (saltProject.getCorpusGraphs().size() > 0)) {
            // store all documents if exist
            Iterator<SCorpusGraph> cGraphs = saltProject.getCorpusGraphs().iterator();
            while (cGraphs.hasNext()) {
                SCorpusGraph cGraph = cGraphs.next();
                saveCorpusGraph(cGraph, saltProjectFolder);
            }
        }
    }

    /**
     * Loads a SaltProject from given uri and returns it as object structure.
     * This does not load the document graphs which are belong to the
     * SaltProject from the disk. You have to call
     * {@link SDocument#loadDocumentGraph() } on each document to load the
     * actual document graph.
     * 
     * @param location
     *            location to the Salt project file
     * @return returns a saltProject, which is filled with data coming from
     *         corpus in uri
     */
    public static SaltProject loadSaltProject(URI location) {
        if (!FILE_ENDING_SALT_XML.equals(location.fileExtension())) {
            location = location.appendSegment(FILE_SALT_PROJECT);
        }

        SaltProject saltProject = null;
        if (location == null) {
            throw new SaltResourceException("Can not load SaltProject, because the given uri is null. ");
        }
        File saltProjectFile = null;
        try {
            saltProjectFile = new File(location.toFileString());
        } catch (Exception e) {
            throw new SaltResourceException("Can not load SaltProject. ", e);
        }
        if (!saltProjectFile.exists()) {
            throw new SaltResourceException(
                    "Can not load SaltProject, because path '" + saltProjectFile + "' does not exist. ");
        }

        Object project = load(location);
        if (project instanceof SaltProject) {
            saltProject = (SaltProject) project;
        } else {
            throw new SaltResourceException("Can not load SaltProject, because the file at '" + saltProjectFile
                    + "' does not contain a Salt project. ");
        }

        for (SCorpusGraph corpusGraph : saltProject.getCorpusGraphs()) {
            insertDocumentGraphLocations(corpusGraph, location);
        }
        return (saltProject);
    }

    // ==================================================< Persistence SaltXML
    // ==================================================> Persistence DOT
    /**
     * Stores a {@link SCorpusGraph} into DOT file.
     * 
     * @param corpusGraph
     *            the corpus graph to be stored
     * @param location
     *            the location to where the corpus graph is stored
     */
    public static void saveCorpusGraph_DOT(SCorpusGraph corpusGraph, URI location) {
        URI targetUri = null;
        if (location.fileExtension() == null) {
            if ((corpusGraph.getSaltProject() != null)
                    && (corpusGraph.getSaltProject().getCorpusGraphs().size() > 1)) {
                Integer pos = corpusGraph.getSaltProject().getCorpusGraphs().indexOf(corpusGraph);
                targetUri = location.appendSegment(pos.toString()).appendFileExtension(SaltUtil.FILE_ENDING_DOT);
            } else {
                List<SNode> roots = corpusGraph.getRoots();
                if ((roots != null) && (!roots.isEmpty())) {
                    targetUri = location.appendSegment(((SCorpus) roots.get(0)).getName())
                            .appendFileExtension(SaltUtil.FILE_ENDING_DOT);
                } else {
                    targetUri = location.appendSegment(corpusGraph.getName())
                            .appendFileExtension(SaltUtil.FILE_ENDING_DOT);
                }
            }
        } else {
            targetUri = location;
        }
        SCorpusGraphDOTWriter writer = new SCorpusGraphDOTWriter();
        writer.setSCorpusGraph(corpusGraph);
        writer.setOutputURI(targetUri);
        writer.save();
    }

    /**
     * Stores a {@link SDocumentGraph} into DOT file.
     * 
     * @param documentGraph
     *            the document graph to be stored
     * @param location
     *            the location to where the document graph is stored
     */
    public static void saveDocumentGraph_DOT(SDocumentGraph documentGraph, URI location) {
        URI targetUri = null;
        if (location.fileExtension() == null) {
            if (documentGraph.getDocument() != null) {
                targetUri = location.appendSegment(documentGraph.getDocument().getName())
                        .appendFileExtension(SaltUtil.FILE_ENDING_DOT);
            } else {
                targetUri = location.appendSegment(documentGraph.getName())
                        .appendFileExtension(SaltUtil.FILE_ENDING_DOT);
            }
        } else {
            targetUri = location;
        }
        SDocumentGraphDOTWriter writer = new SDocumentGraphDOTWriter();
        writer.setDocumentGraph(documentGraph);
        writer.setOutputURI(targetUri);
        writer.save();
    }

    /**
     * This method stores a Salt model in the dot syntax (see:
     * http://www.graphviz.org/) in a file. The stored dot graph can be
     * visualized via the Graphviz toolbox (see: http://www.graphviz.org/) into
     * a bunch of graphical formats like jpeg, png, svg etc.. <br/>
     * In case of a {@link SaltProject} like the following is stored:
     * 
     * <pre>
     * |-----------------------------------------------|
     * | SaltProject:                                  |
     * |-----------------------------------------------|
     * | corpus-structure 0      | corpus-structure 1  |
     * |                         |                     |
     * |          c1             |       c1            |
     * |        /     \          |       |             |
     * |       c2      c3        |       d1            |
     * |   /   |   \   |   \     |                     |
     * |  d1   d2  d3  d4  d5    |                     |
     * |-----------------------------------------------|
     * </pre>
     * 
     * A structure like the following is created:
     * 
     * <pre>
     * 
     *  |-0
     *  | |-c1
     *  | | |-c2
     *  | |   |-d1.dot
     *  | |   |-d2.dot
     *  | |   |-d3.dot
     *  | | |-c3
     *  | |   |-d4.dot
     *  | |   |-d5.dot
     *  | |-0.dot
     *  |-1
     *    |-c1
     *    | |-d1.dot
     *    |-1.dot
     * </pre>
     */
    public static void save_DOT(Object saltObject, URI location) {
        if (location == null) {
            throw new SaltResourceException(
                    "Exception in storing Salt model to dot file, because no uri was given.");
        }
        if (saltObject == null) {
            throw new SaltResourceException(
                    "Exception in storing Salt model to dot file. Cannot write more than one content per file.");
        }

        if (saltObject instanceof SCorpus) {
            SCorpus sCorpus = (SCorpus) saltObject;
            if (sCorpus.getGraph() != null) {
                saltObject = sCorpus.getGraph();
            } else {
                throw new SaltResourceException(
                        "Cannot save Salt model to DOT format, because the given " + SCorpus.class.getSimpleName()
                                + " is not part of a " + SCorpusGraph.class.getSimpleName() + " container");
            }
        } else if (saltObject instanceof SDocument) {
            SDocument sDocument = (SDocument) saltObject;
            if (sDocument.getDocumentGraph() != null) {
                saltObject = sDocument.getDocumentGraph();
            } else {
                throw new SaltResourceException(
                        "Cannot save Salt model to DOT format, because the given " + SDocument.class.getSimpleName()
                                + " does not contain a " + SDocumentGraph.class.getSimpleName() + " content");
            }
        }

        // if content is a SDocumentGraph or SCorpusGraph, outputURI does not
        // have to be changed
        if (saltObject instanceof SCorpusGraph) {
            saveCorpusGraph_DOT((SCorpusGraph) saltObject, location);
        } else if (saltObject instanceof SDocumentGraph) {
            saveDocumentGraph_DOT((SDocumentGraph) saltObject, location);
        }
        // if it is a SaltProject, different URIs for the different components
        // of the project are needed
        else if (saltObject instanceof SaltProject) {
            Collection<SCorpusGraph> corpGraphs = Collections
                    .synchronizedCollection(((SaltProject) saltObject).getCorpusGraphs());
            Integer corpIndex = 0;
            for (SCorpusGraph sCorpusGraph : corpGraphs) {
                URI corpUri = location;
                saveCorpusGraph_DOT(sCorpusGraph, corpUri);

                if (corpGraphs.size() > 1) {
                    corpUri = corpUri.appendSegment(corpIndex.toString());
                }
                for (int docIndex = 0; docIndex < sCorpusGraph.getDocuments().size(); docIndex++) {
                    SDocument sDocument = sCorpusGraph.getDocuments().get(docIndex);
                    if (sDocument.getDocumentGraph() != null) {
                        URI docURI = corpUri;
                        for (String seg : sDocument.getPath().segments()) {
                            docURI = docURI.appendSegment(seg);
                        }
                        SDocumentGraph sDocGraph = sDocument.getDocumentGraph();
                        saveDocumentGraph_DOT(sDocGraph, docURI.appendFileExtension(SaltUtil.FILE_ENDING_DOT));
                        // when calling saveResource(), the sCorpusGraph content
                        // will be attached to the resource and therefore
                        // removed from list of SaltProject, therefore the graph
                        // must be artificially added again
                        sDocument.setDocumentGraph(sDocGraph);
                    }
                }
                corpIndex++;
            }
        } else {
            throw new SaltResourceException("Cannot save Salt model to DOT format, because content is neither "
                    + SCorpusGraph.class.getSimpleName() + ", " + SDocumentGraph.class.getSimpleName() + " nor "
                    + SaltProject.class.getSimpleName() + " content. The given content is of type: '"
                    + saltObject.getClass() + "'.");
        }
    }

    // ===================================================< Persistence DOT

    /**
     * Returns the annotation at position idx in the passed set of annotations.
     * 
     * @param idx
     *            position of the annotation to be returned
     * @param annotations
     *            set of annotations
     * @return annotation at position idx
     */
    public static <P extends Label> P getAnnotation(Integer idx, Set<P> annotations) {
        P retVal = null;
        if (annotations != null && annotations.size() < idx) {
            Iterator<P> it = annotations.iterator();
            for (int i = 0; i <= idx; i++) {
                retVal = it.next();
            }
        }
        return (retVal);
    }

    /**
     * Moves all {@link SAnnotation} objects from <code>from</code> to
     * <code>to</code>.
     * 
     * @param from
     *            {@link SAnnotatableElement} object from which
     *            {@link SAnnotation} object should be moved
     * @param to
     *            {@link SAnnotatableElement} object to which
     *            {@link SAnnotation} object should be moved
     */
    public static void moveAnnotations(SAnnotationContainer from, SAnnotationContainer to) {
        if ((from != null) && (to != null)) {
            for (SAnnotation fromSAnno : from.getAnnotations()) {
                // if to contains an SAnnotation with the same namespace and
                // name
                String newSName = fromSAnno.getName();
                if (to.getAnnotation(fromSAnno.getQName()) != null) {
                    int i = 1;
                    while (to.getAnnotation(fromSAnno.getQName() + "_" + i) != null) {
                        // while there is an anno "annoQName_i", increment i
                        i++;
                    } // while there is an anno "annoQName_i" , increment i
                    newSName = fromSAnno.getName() + "_" + i;
                    fromSAnno.setName(newSName);
                    to.addAnnotation(fromSAnno);
                } else {
                    // moveSCorpusGraph
                    to.addAnnotation(fromSAnno);
                }
            }
        }
    }

    /**
     * Moves all {@link SMetaAnnotation} objects from <code>from</code> to
     * <code>to</code>.
     * 
     * @param from
     *            {@link SMetaAnnotatableElement} object from which
     *            {@link SMetaAnnotation} object should be moved
     * @param to
     *            {@link SMetaAnnotatableElement} object to which
     *            {@link SMetaAnnotation} object should be moved
     */
    public static void moveMetaAnnotations(SAnnotationContainer from, SAnnotationContainer to) {
        if ((from != null) && (to != null)) {
            for (SMetaAnnotation fromSAnno : from.getMetaAnnotations()) {
                String newSName = fromSAnno.getName();
                if (to.getMetaAnnotation(fromSAnno.getQName()) != null) {
                    int i = 1;
                    while (to.getMetaAnnotation(fromSAnno.getQName() + "_" + i) != null) {
                        // while there is a meta anno "annoQName_i" , increment
                        // i
                        i++;
                    } // while there is a meta anno "annoQName_i" , increment i
                    newSName = fromSAnno.getName() + "_" + i;
                    fromSAnno.setName(newSName);
                    to.addMetaAnnotation(fromSAnno);

                } else {
                    // moveSCorpusGraph
                    to.addMetaAnnotation(fromSAnno);
                }
            }
        }
    }

    /**
     * Splits an annotation string of the form 'namespace::name=value
     * (,namespace::name=value)* into a collection of (namespace, name, value).
     * 
     * @param marshalledString
     *            the annotation string to be unmarschalled
     * @return a collection of (namespace, name, value).
     */
    public static Collection<String[]> unmarshalAnnotation(String marshalledString) {
        Collection<String[]> retVal = new ArrayList<>();
        String left = null;
        String middle = null;
        String right = null;
        if ((marshalledString != null) && (!marshalledString.isEmpty())) {
            String[] annotations = marshalledString.split(";");
            for (String annotation : annotations) {
                left = null;
                middle = null;
                right = null;
                String[] nsParts = annotation.split(Label.NS_SEPERATOR);
                String rest;
                if (nsParts.length > 2) {
                    throw new SaltException("The given annotation String '" + annotation
                            + "' is not conform to language: (SNS::)?SNAME(=SVALUE)?(;SNS::SNAME=SVALUE)++");
                } else if (nsParts.length == 2) {
                    left = nsParts[0].trim();
                    if (left.isEmpty()) {
                        left = null;
                    }
                    rest = nsParts[1].trim();
                } else {
                    rest = nsParts[0].trim();
                }
                String[] nameParts = rest.split("=");
                if (nameParts.length > 2) {
                    throw new SaltException("The given annotation String '" + annotation
                            + "' is not conform to language: (SNS::)?SNAME(=SVALUE)?(;SNS::SNAME=SVALUE)++");
                } else if (nameParts.length == 2) {
                    middle = nameParts[0].trim();
                    if (middle.isEmpty()) {
                        middle = null;
                    }
                    right = nameParts[1].trim();
                    if (right.isEmpty()) {
                        right = null;
                    }
                } else {
                    middle = nameParts[0].trim();
                    if (middle.isEmpty()) {
                        middle = null;
                    }
                }
                String[] triple = { left, middle, right };
                retVal.add(triple);
            }
        }
        return (retVal);
    }
}