Merge branch 'master' into bugs/397508
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
new file mode 100644
index 0000000..8d278e7
--- /dev/null
+++ b/plugins/org.eclipse.uml2.uml/src/org/eclipse/uml2/uml/validation/UMLTraversalStrategy.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2013 CEA LIST 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:
+ *     E.D.Willink (CEA LIST) - initial API and implementation
+ *     Christian W. Damus (CEA) - 397508
+ */
+package org.eclipse.uml2.uml.validation;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.validation.service.ITraversalStrategy;
+import org.eclipse.uml2.uml.Element;
+
+/**
+ * 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;
+
+	// 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);
+	}
+
+	public boolean hasNext() {
+		if (stereotypeApplications != null) {
+			if (stereotypeApplications.hasNext()) {
+				return true;
+			}
+			stereotypeApplications = null;
+		}
+		return delegate.hasNext();
+	}
+
+	public boolean isClientContextChanged() {
+		return delegate.isClientContextChanged();
+	}
+
+	public EObject next() {
+		if (stereotypeApplications != null) {
+			return stereotypeApplications.next();
+		}
+		EObject next = delegate.next();
+		if (next instanceof Element) {
+			List<EObject> stereotypeApplicationsList = ((Element) next)
+				.getStereotypeApplications();
+			if (stereotypeApplicationsList.size() > 0) {
+				stereotypeApplications = stereotypeApplicationsList.iterator();
+			}
+		}
+		return next;
+	}
+
+	public void startTraversal(Collection<? extends EObject> traversalRoots,
+			IProgressMonitor monitor) {
+		delegate.startTraversal(traversalRoots, monitor);
+	}
+}
\ No newline at end of file
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$