Support for composite-over-composite overlays

Change-Id: Ibbaa4b51552bea14824479bc81892004cea9f5a9
diff --git a/releng/target-platforms/oxygen/ide.target b/releng/target-platforms/oxygen/ide.target
index 3898aba..2287685 100644
--- a/releng/target-platforms/oxygen/ide.target
+++ b/releng/target-platforms/oxygen/ide.target
@@ -1,14 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?pde version="3.8"?><target includeMode="feature" name="oxygen" sequenceNumber="84">
+<?pde version="3.8"?><target includeMode="feature" name="oxygen" sequenceNumber="87">
 <locations>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
 <unit id="com.google.gson" version="2.7.0.v20170129-0911"/>
 <unit id="com.google.guava" version="21.0.0.v20170206-1425"/>
-<unit id="org.antlr.runtime" version="3.2.0.v201101311130"/>
-<repository location="http://download.eclipse.org/tools/orbit/S-builds/S20170306214312/repository/"/>
+<repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170919201930/repository"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
-<unit id="org.eclipse.nebula.widgets.grid.feature.feature.group" version="1.0.0.201705121512"/>
+<unit id="org.eclipse.nebula.widgets.grid.feature.feature.group" version="1.1.0.201712122235"/>
 <repository location="http://download.eclipse.org/nebula/snapshot/"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
@@ -18,9 +17,10 @@
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
 <unit id="org.eclipse.pde.feature.group" version="0.0.0"/>
 <unit id="org.eclipse.platform.sdk" version="0.0.0"/>
-<repository location="http://download.eclipse.org/eclipse/updates/4.7milestones"/>
+<repository location="http://download.eclipse.org/releases/oxygen"/>
 </location>
 <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
+<unit id="org.antlr.runtime" version="3.2.0.v201101311130"/>
 <unit id="org.eclipse.emf.common.feature.group" version="0.0.0"/>
 <unit id="org.eclipse.emf.edit.ui.feature.group" version="0.0.0"/>
 <unit id="org.eclipse.emf.sdk.feature.group" version="0.0.0"/>
diff --git a/runtime/tesla/org.eclipse.rcptt.tesla.jface.aspects/src/org/eclipse/rcptt/tesla/jface/ImageSources.java b/runtime/tesla/org.eclipse.rcptt.tesla.jface.aspects/src/org/eclipse/rcptt/tesla/jface/ImageSources.java
index bf8e153..4ca6b83 100644
--- a/runtime/tesla/org.eclipse.rcptt.tesla.jface.aspects/src/org/eclipse/rcptt/tesla/jface/ImageSources.java
+++ b/runtime/tesla/org.eclipse.rcptt.tesla.jface.aspects/src/org/eclipse/rcptt/tesla/jface/ImageSources.java
@@ -23,8 +23,8 @@
 
 public enum ImageSources {
 	INSTANCE;
-	private Map<ImageDescriptor, ImageSource> descriptors = new WeakIdentityHashMap<ImageDescriptor, ImageSource>();
-	private Map<Object, ImageSource> sources = new WeakIdentityHashMap<Object, ImageSource>();
+	private final Map<ImageDescriptor, ImageSource> descriptors = new WeakIdentityHashMap<ImageDescriptor, ImageSource>();
+	private final Map<Object, ImageSource> sources = new WeakIdentityHashMap<Object, ImageSource>();
 
 	public ImageSource find(Image image) {
 		return sources.get(dedup(image));
@@ -124,7 +124,13 @@
 		public final List<ImageSource> children = new ArrayList<ImageSource>();
 
 		public void addUnique(ImageSource source) {
-			assert !(source instanceof CompositeSource);
+			if (source instanceof CompositeSource) {
+				for (ImageSource child : ((CompositeSource) source).children) {
+					addUnique(child);
+				}
+				return;
+			}
+			assert !(source instanceof CompositeSource) : "CompositeSource should never have another CompositeSource as its child, as children are searhable but CompositeSource.equals() is not overridden";
 			for (ImageSource src : children) {
 				if (src.equals(source)) {
 					return;
diff --git a/runtime/tests/org.eclipse.rcptt.tesla.jface.aspects.test/src/org/eclipse/rcptt/tesla/jface/aspects/test/ImageSourcesTest.java b/runtime/tests/org.eclipse.rcptt.tesla.jface.aspects.test/src/org/eclipse/rcptt/tesla/jface/aspects/test/ImageSourcesTest.java
index 3720aea..558a142 100644
--- a/runtime/tests/org.eclipse.rcptt.tesla.jface.aspects.test/src/org/eclipse/rcptt/tesla/jface/aspects/test/ImageSourcesTest.java
+++ b/runtime/tests/org.eclipse.rcptt.tesla.jface.aspects.test/src/org/eclipse/rcptt/tesla/jface/aspects/test/ImageSourcesTest.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.rcptt.tesla.jface.aspects.test;
 
+import java.io.IOException;
 import java.net.URL;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -22,16 +23,20 @@
 import org.eclipse.rcptt.tesla.jface.ImageSources.CompositeSource;
 import org.eclipse.rcptt.tesla.jface.ImageSources.ImageSource;
 import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Resource;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.PlatformUI;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.Closer;
 
 public class ImageSourcesTest {
 	private static final ISharedImages SHARED_IMAGES = PlatformUI.getWorkbench().getSharedImages();
+	private final Closer closer = Closer.create();
 	
 	@Test
 	public void getImageData() {
@@ -43,13 +48,67 @@
 		DecorationOverlayIcon icon = new DecorationOverlayIcon(folder, overlays);
 		Image iconImage = (Image)icon.createResource(Display.getCurrent());
 		try {
-			ImageSource imageSource = ImageSources.INSTANCE.find(iconImage);
-			CompositeSource composite = (CompositeSource) imageSource;
-			List<String> strings = composite.children.stream().map(Object::toString).collect(Collectors.toList());
-			Assert.assertEquals(ImmutableList.of("org.eclipse.ui/icons/full/obj16/fldr_obj.png", "org.eclipse.ui/icons/full/ovr16/warning_ovr.png"), strings);
+			Assert.assertEquals(
+			ImmutableList.of("org.eclipse.ui/icons/full/obj16/fldr_obj.png", "org.eclipse.ui/icons/full/ovr16/warning_ovr.png"), 
+			extractStrings(iconImage));
 		} finally {
 			iconImage.dispose();
 		}
 	}
 
+
+
+	private List<String> extractStrings(Image iconImage) {
+		ImageSource imageSource = ImageSources.INSTANCE.find(iconImage);
+		CompositeSource composite = (CompositeSource) imageSource;
+		List<String> strings = composite.children.stream().map(Object::toString).collect(Collectors.toList());
+		return strings;
+	}
+	
+	
+	
+	@Test
+	public void compositeOverComposite() {
+		ImageDescriptor folder = SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER);
+		ImageDescriptor file = SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJ_FILE);
+		ImageDescriptor error = SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_ERROR);
+		ImageDescriptor warning = SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_DEC_FIELD_WARNING);
+
+		folder = compose(folder, error);
+		file = compose(file, warning);
+		
+		Image target = compose(folder, file).createImage();
+		try {
+			Assert.assertEquals(
+			ImmutableList.of(
+							"org.eclipse.ui/icons/full/obj16/fldr_obj.png",
+							"org.eclipse.ui/icons/full/ovr16/error_ovr.png",
+							"org.eclipse.ui/icons/full/obj16/file_obj.png",
+							"org.eclipse.ui/icons/full/ovr16/warning_ovr.png"
+			)
+			,
+			 extractStrings(target)
+			);
+			
+		} finally {
+			target.dispose();
+		}
+	}
+	
+	ImageDescriptor compose(ImageDescriptor image1, ImageDescriptor image2) {
+		ImageDescriptor[] overlays = new ImageDescriptor[IDecoration.BOTTOM_RIGHT + 1];
+		overlays[IDecoration.BOTTOM_RIGHT] = image2;
+		return new DecorationOverlayIcon(disposeAfter(image1.createImage()), overlays);
+	}
+
+	private <T extends Resource> T disposeAfter(T disposable) {
+		closer.register(() -> disposable.dispose());
+		return disposable;
+	}
+
+	@After
+	public void after() throws IOException {
+		closer.close();
+	}
+
 }