Add test cases for prefix-namespace mapping

Add tests to QNameReferenceTest to ensure that our implementation
1) writes correct documents w.r.t. QNames and
   prefix-namespace-declarations
- loads and resolves valid documents with different scenarios:
 2) normal: default=target ns; prefix for imported elements
 3) inverse: default=imported namespace; prefix for local elements
 4) no ns: no default and no target namespace
- has a reasonable fallback behavior for slightly incorrect documents
  with unprefixed QNames (to be interpreted as local references):
 5) no default but target namespace
 6) no import element associated with default namespace

7) Add a test for a newly discovered bug: different namespaces with the
same last path segment are mapped to the same prefix ('urn:modelB' and
'urn:path:modelB' both have 'modelB' as default prefix).

Tests 1, 3 and 7 fail currently. This will be fixed by subsequent
commits.

Bug: 329668
Bug: 329804
diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_defaultNotResolvable.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_defaultNotResolvable.bpmn2
new file mode 100644
index 0000000..a4e0c81
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_defaultNotResolvable.bpmn2
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defAId" 

+    exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model A" 

+    targetNamespace="urn:modelA"

+    xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:b="urn:modelB">

+  <import importType="http://www.omg.org/spec/BPMN/20100524/MODEL" 

+    location="res/qname_prefixNs_referenced.bpmn2" namespace="urn:modelB"/>

+  <collaboration id="colId"/>

+  <process id="proc1Id" definitionalCollaborationRef="colId">

+    <supports>b:proc2Id</supports>

+  </process>

+</definitions>

diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_inverse.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_inverse.bpmn2
new file mode 100644
index 0000000..e3f54c2
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_inverse.bpmn2
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defAId" 

+    exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model A" 

+    targetNamespace="urn:modelA"

+    xmlns:a="urn:modelA" xmlns="urn:modelB">

+  <bpmn2:import importType="http://www.omg.org/spec/BPMN/20100524/MODEL" 

+    location="res/qname_prefixNs_referenced.bpmn2" namespace="urn:modelB"/>

+  <bpmn2:collaboration id="colId"/>

+  <bpmn2:process id="proc1Id" definitionalCollaborationRef="a:colId">

+    <bpmn2:supports>proc2Id</bpmn2:supports>

+  </bpmn2:process>

+</bpmn2:definitions>

diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_noDefaultButTarget.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_noDefaultButTarget.bpmn2
new file mode 100644
index 0000000..5a167ff
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_noDefaultButTarget.bpmn2
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defAId" 

+    exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model A" targetNamespace="urn:modelA">

+  <bpmn2:collaboration id="colId"/>

+  <bpmn2:process id="proc1Id" definitionalCollaborationRef="colId"/>

+</bpmn2:definitions>

diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_noNs.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_noNs.bpmn2
new file mode 100644
index 0000000..95385b7
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_noNs.bpmn2
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defAId" 

+    exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model A">

+  <bpmn2:collaboration id="colId"/>

+  <bpmn2:process id="proc1Id" definitionalCollaborationRef="colId"/>

+</bpmn2:definitions>

diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_normal.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_normal.bpmn2
new file mode 100644
index 0000000..94eb196
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_normal.bpmn2
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defAId" 

+    exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model A" 

+    targetNamespace="urn:modelA"

+    xmlns="urn:modelA" xmlns:b="urn:modelB">

+  <bpmn2:import importType="http://www.omg.org/spec/BPMN/20100524/MODEL" 

+    location="res/qname_prefixNs_referenced.bpmn2" namespace="urn:modelB"/>

+  <bpmn2:collaboration id="colId"/>

+  <bpmn2:process id="proc1Id" definitionalCollaborationRef="colId">

+    <bpmn2:supports>b:proc2Id</bpmn2:supports>

+  </bpmn2:process>

+</bpmn2:definitions>

diff --git a/org.eclipse.bpmn2.tests/res/qname_prefixNs_referenced.bpmn2 b/org.eclipse.bpmn2.tests/res/qname_prefixNs_referenced.bpmn2
new file mode 100644
index 0000000..d3a923f
--- /dev/null
+++ b/org.eclipse.bpmn2.tests/res/qname_prefixNs_referenced.bpmn2
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" id="defBId" 

+	exporter="org.eclipse.bpmn2" exporterVersion="0.7.0" name="Model B" 

+	targetNamespace="urn:modelB">

+  <bpmn2:process id="proc2Id"/>

+</bpmn2:definitions>

diff --git a/org.eclipse.bpmn2.tests/src/org/eclipse/bpmn2/tests/QNameReferenceTest.java b/org.eclipse.bpmn2.tests/src/org/eclipse/bpmn2/tests/QNameReferenceTest.java
index 37444c6..df81f16 100644
--- a/org.eclipse.bpmn2.tests/src/org/eclipse/bpmn2/tests/QNameReferenceTest.java
+++ b/org.eclipse.bpmn2.tests/src/org/eclipse/bpmn2/tests/QNameReferenceTest.java
@@ -12,13 +12,17 @@
 
 import static org.junit.Assert.*;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import javax.xml.parsers.DocumentBuilderFactory;
+
 import org.eclipse.bpmn2.Bpmn2Factory;
 import org.eclipse.bpmn2.Collaboration;
 import org.eclipse.bpmn2.Definitions;
+import org.eclipse.bpmn2.DocumentRoot;
 import org.eclipse.bpmn2.EventDefinition;
 import org.eclipse.bpmn2.Import;
 import org.eclipse.bpmn2.LinkEventDefinition;
@@ -31,6 +35,8 @@
 import org.eclipse.emf.ecore.xmi.ClassNotFoundException;
 import org.junit.Before;
 import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
 
 /**
  * Tests our QName (de)resolution mechanism both for intra- and inter-model references.
@@ -215,4 +221,132 @@
                 .eIsProxy());
         assertEquals(B_process.getId(), A_processNew.getSupports().get(0).getId());
     }
+
+    @Test
+    public void testQNameSerialization() throws Exception {
+        A_process.setId("A_process_id");
+        A_process.setDefinitionalCollaborationRef(A_collab);
+        A_process.getSupports().add(B_process);
+
+        saveAndLoadModels("serialization");
+
+        DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
+        Document xml = fact.newDocumentBuilder()
+                .parse(new File(A_resource.getURI().toFileString()));
+        Node pNode = xml.getElementsByTagName("bpmn2:process").item(0);
+
+        String ref = pNode.getAttributes().getNamedItem("definitionalCollaborationRef")
+                .getNodeValue();
+        String prefixLocal = ref.indexOf(":") == -1 ? "" : ref.split(":")[0];
+        assertEquals("The prefix for local references is not mapped to the target namespace.",
+                A_model.getTargetNamespace(), ((DocumentRoot) A_resource.getContents().get(0))
+                        .getXMLNSPrefixMap().get(prefixLocal));
+
+        ref = pNode.getChildNodes().item(1).getTextContent();
+        String prefixModelB = ref.indexOf(":") == -1 ? "" : ref.split(":")[0];
+        assertEquals("The prefix for references to model b is not mapped to its target namespace.",
+                B_model.getTargetNamespace(), ((DocumentRoot) A_resource.getContents().get(0))
+                        .getXMLNSPrefixMap().get(prefixModelB));
+    }
+
+    /**
+     * Tests the standard case for the mapping of prefixes to namespaces:
+     * - the default namespace is set to the target namespace and local references do not use a prefix
+     * - cross-file references use a prefix associated with an imported namespace
+     */
+    @Test
+    public void testPrefixNamespaceMapping() {
+        testQNameResolve("normal", true);
+    }
+
+    /**
+     * Tests the inverse case for the mapping of prefixes to namespaces:
+     * - an explicit prefix is associated with the target namespace and used in local references
+     * - the default namespace point to an imported namespace and cross-file reference do not use a prefix
+     */
+    @Test
+    public void testPrefixNamespaceMappingInverse() {
+        testQNameResolve("inverse", true);
+    }
+
+    /**
+     * Tests the special case for the mapping of prefixes to namespaces:
+     * - neither a default namespace nor a target namespace are defined, unprefixed QNames thus point to local elements 
+     */
+    @Test
+    public void testPrefixNamespaceMappingNoNs() {
+        testQNameResolve("noNs", false);
+    }
+
+    /**
+     * Tests the special case for the mapping of prefixes to namespaces:
+     * - no default namespace but a target namespace is defined, unprefixed QNames are invalid (strict interpretation)
+     * - chosen fallback behavior: unprefixed QNames should be interpreted as references to local elements
+     */
+    @Test
+    public void testPrefixNamespaceMappingNoDefaultButTarget() {
+        testQNameResolve("noDefaultButTarget", false);
+    }
+
+    /**
+     * Tests the special case for the mapping of prefixes to namespaces:
+     * - the default namespace is neither the target namespace nor mapped by an import element
+     * - strict interpretation: unprefixed QNames can not be resolved
+     * - chosen fallback behavior (?): unprefixed QNames should be interpreted as references to local elements
+     * -- guideline: treat unprefixed QNames as local (guess), unless default namespace is mapped by import element
+     */
+    @Test
+    public void testPrefixNamespaceMappingDefaultNotResolvable() {
+        testQNameResolve("defaultNotResolvable", true);
+    }
+
+    /**
+     * Tests if QNames are correctly resolved in the specified test case.
+     * @param testCase Identifier of the test case, pointing to a BPMN2 file 
+     * under 'res/qname_prefixNs_<i>testCase</i>.bpmn2' (with defined content).
+     * @param crossFile <code>true</code>, if this test case features a cross-file reference
+     * to be checked (Process.supports).
+     */
+    protected void testQNameResolve(String testCase, boolean crossFile) {
+        Resource res = TestHelper.getResource(URI.createFileURI("res/qname_prefixNs_" + testCase
+                + ".bpmn2"));
+        Process p = (Process) res.getEObject("proc1Id");
+        assertFalse("Resolution of local reference failed, test case: " + testCase, p
+                .getDefinitionalCollaborationRef().eIsProxy());
+        if (crossFile)
+            assertFalse("Resolution of cross-file reference failed, test case: " + testCase, p
+                    .getSupports().get(0).eIsProxy());
+    }
+
+    /**
+     * Tests the automated generation of prefixes in case multiple imports would use the same prefix (as 
+     * automatically generated from the namespace).
+     * @throws Exception
+     */
+    @Test
+    public void testPrefixClash() throws Exception {
+        Definitions C_model = TestHelper.initBasicModel("urn:path:modelB"); // same ns ending as B_model
+        Process C_process = Bpmn2Factory.eINSTANCE.createProcess();
+        C_model.getRootElements().add(C_process);
+        Resource C_resource = saveAndLoadModel("qname_prefixClash_C", C_model);
+
+        Import importCintoA = Bpmn2Factory.eINSTANCE.createImport();
+        importCintoA.setImportType("http://www.omg.org/spec/BPMN/20100524/MODEL");
+        importCintoA.setLocation(C_resource.getURI().toString());
+        importCintoA.setNamespace(C_model.getTargetNamespace());
+        A_model.getImports().add(importCintoA);
+
+        A_process.getSupports().add(B_process);
+        A_process.getSupports().add(C_process);
+        saveAndLoadModels("prefixClash");
+
+        Process A_processNew = (Process) A_resource.getEObject(A_process.getId());
+        assertFalse("Reference to element of model B could not be resolved", A_processNew
+                .getSupports().get(0).eIsProxy());
+        assertEquals(B_process.getId(), A_processNew.getSupports().get(0).getId());
+
+        assertFalse("Reference to element of model C could not be resolved", A_processNew
+                .getSupports().get(1).eIsProxy());
+        assertEquals(C_process.getId(), A_processNew.getSupports().get(1).getId());
+    }
 }