[397508] Custom EMFv TraversalStrategy needed for stereotype
applications
https://bugs.eclipse.org/bugs/show_bug.cgi?id=397508

Remove the UMLTraversalStrategy registration from the UML plug-in manifest.

Tweak the API documentation and add constructors for reusability.

Add JUnit tests for the UMLTraversalStrategy.
diff --git a/plugins/org.eclipse.uml2.uml/plugin.xml b/plugins/org.eclipse.uml2.uml/plugin.xml
index 57a616e..73a8b56 100644
--- a/plugins/org.eclipse.uml2.uml/plugin.xml
+++ b/plugins/org.eclipse.uml2.uml/plugin.xml
@@ -2,7 +2,7 @@
 <?eclipse version="3.0"?>
 
 <!--
- Copyright (c) 2005, 2013 IBM Corporation, Embarcadero Technologies, CEA, and others.
+ Copyright (c) 2005, 2011 IBM Corporation, Embarcadero Technologies, CEA, and others.
  All rights reserved.   This program and the accompanying materials
  are made available under the terms of the Eclipse Public License v1.0
  which accompanies this distribution, and is available at
@@ -12,7 +12,6 @@
    IBM - initial API and implementation
    Kenn Hussey (Embarcadero Technologies) - 156879, 215488, 226397, 204200, 247980
    Kenn Hussey (CEA) - 327039, 351774
-   E.D.Willink (CEA LIST) - 397508
 
 -->
 
@@ -324,13 +323,5 @@
         </describer>
      </content-type>
   </extension>
-   
-   <extension point="org.eclipse.emf.validation.traversal">
-      <traversalStrategy
-            class="org.eclipse.uml2.uml.validation.UMLTraversalStrategy"
-            namespaceUri="http://www.eclipse.org/uml2/4.0.0/UML">
-         <eclass name="Model"/>
-      </traversalStrategy>
-   </extension>
 
 </plugin>
diff --git a/plugins/org.eclipse.uml2.uml/src/org/eclipse/uml2/uml/validation/UMLTraversalStrategy.java b/plugins/org.eclipse.uml2.uml/src/org/eclipse/uml2/uml/validation/UMLTraversalStrategy.java
index 11069e4..8d278e7 100644
--- a/plugins/org.eclipse.uml2.uml/src/org/eclipse/uml2/uml/validation/UMLTraversalStrategy.java
+++ b/plugins/org.eclipse.uml2.uml/src/org/eclipse/uml2/uml/validation/UMLTraversalStrategy.java
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     E.D.Willink (CEA LIST) - initial API and implementation
+ *     Christian W. Damus (CEA) - 397508
  */
 package org.eclipse.uml2.uml.validation;
 
@@ -23,15 +24,34 @@
 /**
  * A UMLTraversalStrategy extends a standard recursive traversal to insert
  * stereotype applications following each each stereotyped element.
+ * 
+ * @since 4.2
  */
 public class UMLTraversalStrategy
 		implements ITraversalStrategy {
 
-	private final ITraversalStrategy delegate = new ITraversalStrategy.Recursive();
+	private final ITraversalStrategy delegate;
 
-	private Iterator<EObject> stereotypeApplications = null; // Non-null if
-																// stereotypeApplications
-																// maybe pending
+	// non-null if stereotypeApplications may be pending
+	private Iterator<EObject> stereotypeApplications = null;
+
+	/**
+	 * Initializes me with the default recursive strategy as delegate.
+	 */
+	public UMLTraversalStrategy() {
+		this(new ITraversalStrategy.Recursive());
+	}
+
+	/**
+	 * Initializes me with the specified traversal {@code delegate}. I insert
+	 * stereotype applications following each element that it produces.
+	 * 
+	 * @param delegate
+	 *            the traversal strategy to decorate
+	 */
+	public UMLTraversalStrategy(ITraversalStrategy delegate) {
+		this.delegate = delegate;
+	}
 
 	public void elementValidated(EObject element, IStatus status) {
 		delegate.elementValidated(element, status);
diff --git a/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLTraversalStrategyTest.java b/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLTraversalStrategyTest.java
new file mode 100644
index 0000000..72ac371
--- /dev/null
+++ b/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLTraversalStrategyTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2013 CEA and others.
+ * All rights reserved.   This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Christian W. Damus (CEA) - initial API and implementation
+ */
+package org.eclipse.uml2.uml.validation.tests;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.validation.service.ITraversalStrategy;
+import org.eclipse.uml2.uml.Class;
+import org.eclipse.uml2.uml.Element;
+import org.eclipse.uml2.uml.Package;
+import org.eclipse.uml2.uml.Profile;
+import org.eclipse.uml2.uml.Stereotype;
+import org.eclipse.uml2.uml.UMLFactory;
+import org.eclipse.uml2.uml.UMLPackage;
+import org.eclipse.uml2.uml.resource.UMLResource;
+import org.eclipse.uml2.uml.tests.util.StandaloneSupport;
+import org.eclipse.uml2.uml.util.UMLUtil;
+import org.eclipse.uml2.uml.validation.UMLTraversalStrategy;
+
+/**
+ * Test suite for the {@link UMLTraversalStrategy} class and attendant classes.
+ */
+public class UMLTraversalStrategyTest
+		extends TestCase {
+
+	private ITraversalStrategy fixture;
+
+	private Package model;
+
+	private Class utils;
+
+	/**
+	 * Initializes me with my name.
+	 * 
+	 * @param name
+	 *            my name
+	 */
+	public UMLTraversalStrategyTest(String name) {
+		super(name);
+	}
+
+	public static Test suite() {
+		return new TestSuite(UMLTraversalStrategyTest.class,
+			"UML traversal strategy tests"); //$NON-NLS-1$
+	}
+
+	public void testTraversalCoversStereotypesFromRootPackage() {
+		assertTraversal(model, new Element[]{model, utils});
+	}
+
+	public void testTraversalCoversStereotypesFromNestedElement() {
+		assertTraversal(utils, new Element[]{utils}, new Element[]{model});
+	}
+
+	public void testTraversalCoversStereotypesFromRootNonPackage() {
+		// make the Utils class a root
+		EcoreUtil.remove(utils);
+		model.eResource().getContents().add(utils);
+
+		assertTraversal(utils, new Element[]{utils}, new Element[]{model});
+	}
+
+	//
+	// Test framework
+	//
+
+	@Override
+	protected void setUp()
+			throws Exception {
+		super.setUp();
+
+		fixture = new UMLTraversalStrategy();
+
+		ResourceSet rset = new ResourceSetImpl();
+		if (StandaloneSupport.isStandalone()) {
+			StandaloneSupport.init(rset);
+		}
+
+		Resource res = rset.createResource(URI.createURI("bogus:///model.uml"));
+		model = UMLFactory.eINSTANCE.createPackage();
+		model.setName("model");
+		res.getContents().add(model);
+
+		Profile profile = (Profile) UMLUtil.load(rset,
+			URI.createURI(UMLResource.STANDARD_L2_PROFILE_URI),
+			UMLPackage.Literals.PROFILE);
+
+		model.applyProfile(profile);
+		model.applyStereotype(profile.getOwnedStereotype("ModelLibrary"));
+
+		utils = model.createOwnedClass("Utils", false);
+		utils.applyStereotype(profile.getOwnedStereotype("Utility"));
+		utils
+			.applyStereotype(profile.getOwnedStereotype("ImplementationClass"));
+	}
+
+	@Override
+	protected void tearDown()
+			throws Exception {
+
+		ResourceSet rset = model.eResource().getResourceSet();
+		for (Resource next : rset.getResources()) {
+			next.unload();
+		}
+		rset.getResources().clear();
+		rset.eAdapters().clear();
+
+		model = null;
+		fixture = null;
+
+		super.tearDown();
+	}
+
+	EObject requireApplication(Element element, Stereotype stereotype) {
+		EObject result = element.getStereotypeApplication(stereotype);
+		assertNotNull("Stereotype not applied", result);
+		return result;
+	}
+
+	void assertTraversal(Element root, Element[] required) {
+		assertTraversal(root, required, new Element[0]);
+	}
+
+	void assertTraversal(Element root, Element[] required, Element[] forbidden) {
+		List<Element> expectedTraversal = Arrays.asList(required);
+		Set<EObject> unexpected = new java.util.HashSet<EObject>(
+			Arrays.asList(forbidden));
+		for (Element next : forbidden) {
+			unexpected.addAll(next.getStereotypeApplications());
+		}
+
+		PushBackIterator<Element> expected = new PushBackIterator<Element>(
+			expectedTraversal.iterator());
+
+		fixture.startTraversal(Collections.singleton(root),
+			new NullProgressMonitor());
+		while (expected.hasNext()) {
+			assertTrue("Traversal has too few elements", fixture.hasNext());
+
+			Element next = expected.next();
+			EObject actual = fixture.next();
+
+			assertFalse("Traversal includes a forbidden element",
+				unexpected.contains(actual));
+
+			if (next != actual) {
+				// compare it with the next one, then
+				expected.pushBack(next);
+			} else {
+				// now grab its stereotype applications, which are in
+				// non-deterministic order
+				Set<EObject> stereotypeApplications = new java.util.HashSet<EObject>(
+					next.getStereotypeApplications());
+				while (!stereotypeApplications.isEmpty()) {
+					assertTrue("Traversal has too few elements",
+						fixture.hasNext());
+					assertTrue(
+						"Traversal did not provide a stereotype application",
+						stereotypeApplications.remove(fixture.next()));
+				}
+			}
+		}
+	}
+
+	static final class PushBackIterator<E>
+			implements Iterator<E> {
+
+		private final Iterator<E> delegate;
+
+		private E pushedBack;
+
+		PushBackIterator(Iterator<E> delegate) {
+			this.delegate = delegate;
+		}
+
+		public boolean hasNext() {
+			return (pushedBack != null) || delegate.hasNext();
+		}
+
+		public E next() {
+			E result = pushedBack;
+
+			if (result == null) {
+				result = delegate.next();
+			} else {
+				pushedBack = null;
+			}
+
+			return result;
+		}
+
+		public void pushBack(E element) {
+			if (pushedBack != null) {
+				throw new IllegalStateException(
+					"already pushed an element back");
+			} else if (element == null) {
+				throw new IllegalArgumentException("cannot push null back");
+			}
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException("remove");
+		}
+	}
+}
diff --git a/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLValidationTests.java b/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLValidationTests.java
index c9c967b..aec81b8 100644
--- a/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLValidationTests.java
+++ b/tests/org.eclipse.uml2.uml.tests/src/org/eclipse/uml2/uml/validation/tests/UMLValidationTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012 CEA and others.
+ * Copyright (c) 2012, 2013 CEA and others.
  * All rights reserved.   This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,10 +7,12 @@
  *
  * Contributors:
  *   Christian W. Damus (CEA) - initial API and implementation
+ *   Christian W. Damus (CEA) - 397508
  */
 package org.eclipse.uml2.uml.validation.tests;
 
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.uml2.uml.tests.util.StandaloneSupport;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -36,10 +38,10 @@
 
 		// these tests require an Eclipse instance
 		try {
-			if (new EclipseHelper().isEclipseRunning()) {
-				result = new UMLValidationTests(
-					"UML Validation Constraint Provider Tests"); //$NON-NLS-1$
+			if (!StandaloneSupport.isStandalone()) {
+				result = new UMLValidationTests("UML Validation Tests"); //$NON-NLS-1$
 				result.addTest(DelegatingConstraintProviderTest.suite());
+				result.addTest(UMLTraversalStrategyTest.suite());
 			} else {
 				result = new TestSuite(
 					"<UML validation tests require Eclipse to be running>"); //$NON-NLS-1$