// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.layer;
import static org.hamcrest.CoreMatchers.any;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import javax.swing.Action;
import javax.swing.Icon;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.testutils.ExpectedRootException;
import org.openstreetmap.josm.tools.bugreport.ReportedException;
/**
* Test the {@link LayerManager} class.
* @author Michael Zangl
*
*/
public class LayerManagerTest {
/**
* This is a layer that can be used in tests. It does not do anything and provides a simple, fake implementation.
* @author Michael Zangl
*/
public static class TestLayer extends Layer {
/**
* Create a new test layer.
*/
public TestLayer() {
super("Test Layer");
}
@Override
public void paint(Graphics2D g, MapView mv, Bounds bbox) {
}
@Override
public void visitBoundingBox(BoundingXYVisitor v) {
}
@Override
public void mergeFrom(Layer from) {
}
@Override
public boolean isMergable(Layer other) {
return false;
}
@Override
public String getToolTipText() {
return null;
}
@Override
public Action[] getMenuEntries() {
return new Action[0];
}
@Override
public Object getInfoComponent() {
return null;
}
@Override
public Icon getIcon() {
return new Icon() {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
// nop
}
@Override
public int getIconWidth() {
return 10;
}
@Override
public int getIconHeight() {
return 10;
}
};
}
@Override
public LayerPositionStrategy getDefaultLayerPosition() {
return LayerPositionStrategy.afterLast(o -> true);
}
}
protected static class TestLayer2 extends TestLayer {}
/**
* Intercepts the events for easier testing.
* @author Michael Zangl
*
*/
protected class CapturingLayerChangeListener implements LayerChangeListener {
protected LayerAddEvent layerAdded;
protected LayerRemoveEvent layerRemoved;
protected LayerOrderChangeEvent layerOrderChanged;
@Override
public void layerAdded(LayerAddEvent e) {
GuiHelper.assertCallFromEdt();
assertNull(layerAdded);
assertSame(layerManager, e.getSource());
layerAdded = e;
}
@Override
public void layerRemoving(LayerRemoveEvent e) {
GuiHelper.assertCallFromEdt();
assertNull(layerRemoved);
assertSame(layerManager, e.getSource());
layerRemoved = e;
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
GuiHelper.assertCallFromEdt();
assertNull(layerOrderChanged);
assertSame(layerManager, e.getSource());
layerOrderChanged = e;
}
}
private final class ResetStateChangeListener extends CapturingLayerChangeListener {
int removed = 0;
@Override
public void layerRemoving(LayerRemoveEvent e) {
// count only
removed++;
}
}
protected LayerManager layerManager;
/**
* Rule used to expect exceptions.
*/
@Rule
public ExpectedRootException thrown = ExpectedRootException.none();
/**
* Set up test layer manager.
*/
@Before
public void setUp() {
layerManager = new LayerManager();
}
/**
* {@link LayerManager#addLayer(Layer)}
*/
@Test
public void testAddLayer() {
Layer layer1 = new TestLayer() {
@Override
public LayerPositionStrategy getDefaultLayerPosition() {
return LayerPositionStrategy.IN_FRONT;
}
@Override
public boolean isBackgroundLayer() {
return true;
}
};
Layer layer2 = new TestLayer() {
@Override
public LayerPositionStrategy getDefaultLayerPosition() {
return LayerPositionStrategy.IN_FRONT;
}
};
Layer layer3 = new TestLayer() {
@Override
public LayerPositionStrategy getDefaultLayerPosition() {
return LayerPositionStrategy.BEFORE_FIRST_BACKGROUND_LAYER;
}
};
layerManager.addLayer(layer1);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1));
layerManager.addLayer(layer2);
assertEquals(layerManager.getLayers(), Arrays.asList(layer2, layer1));
layerManager.addLayer(layer3);
assertEquals(layerManager.getLayers(), Arrays.asList(layer2, layer3, layer1));
// event
TestLayer layer4 = new TestLayer();
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
layerManager.addLayer(layer4);
assertSame(layer4, l.layerAdded.getAddedLayer());
assertNull(l.layerRemoved);
assertNull(l.layerOrderChanged);
}
/**
* {@link LayerManager#addLayer(Layer)}: duplicate layers
*/
@Test
public void testAddLayerFails() {
thrown.expect(ReportedException.class);
thrown.expectCause(any(InvocationTargetException.class));
thrown.expectRootCause(any(IllegalArgumentException.class));
TestLayer layer1 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer1);
}
/**
* {@link LayerManager#addLayer(Layer)}: illegal default layer position
*/
@Test
public void testAddLayerIllegalPosition() {
thrown.expect(ReportedException.class);
thrown.expectCause(any(InvocationTargetException.class));
thrown.expectRootCause(any(IndexOutOfBoundsException.class));
TestLayer layer1 = new TestLayer() {
@Override
public LayerPositionStrategy getDefaultLayerPosition() {
return manager -> 42;
}
};
layerManager.addLayer(layer1);
}
/**
* {@link LayerManager#removeLayer(Layer)}
*/
@Test
public void testRemoveLayer() {
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1, layer2));
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
layerManager.removeLayer(layer2);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1));
assertNull(l.layerAdded);
assertSame(layer2, l.layerRemoved.getRemovedLayer());
assertNull(l.layerOrderChanged);
}
/**
* {@link LayerManager#moveLayer(Layer, int)}
*/
@Test
public void testMoveLayer() {
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1, layer2));
layerManager.moveLayer(layer2, 0);
assertEquals(layerManager.getLayers(), Arrays.asList(layer2, layer1));
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
layerManager.moveLayer(layer2, 1);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1, layer2));
assertNull(l.layerAdded);
assertNull(l.layerRemoved);
assertNotNull(l.layerOrderChanged);
// This should not change anything and not fire any event
layerManager.moveLayer(layer2, 1);
assertEquals(layerManager.getLayers(), Arrays.asList(layer1, layer2));
}
/**
* {@link LayerManager#moveLayer(Layer, int)} fails for wrong index
*/
@Test
public void testMoveLayerFailsRange() {
thrown.expect(ReportedException.class);
thrown.expectCause(any(InvocationTargetException.class));
thrown.expectRootCause(any(IndexOutOfBoundsException.class));
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
layerManager.moveLayer(layer2, 2);
}
/**
* {@link LayerManager#moveLayer(Layer, int)} fails for wrong layer
*/
@Test
public void testMoveLayerFailsNotInList() {
thrown.expect(ReportedException.class);
thrown.expectCause(any(InvocationTargetException.class));
thrown.expectRootCause(any(IllegalArgumentException.class));
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.moveLayer(layer2, 0);
}
/**
* {@link LayerManager#getLayers()} unmodifiable
*/
@Test(expected = UnsupportedOperationException.class)
public void testGetLayers() {
// list should be immutable
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
layerManager.getLayers().remove(0);
}
/**
* {@link LayerManager#getLayersOfType(Class)}
*/
@Test
public void testGetLayersOfType() {
TestLayer2 layer1 = new TestLayer2();
TestLayer2 layer2 = new TestLayer2();
layerManager.addLayer(layer1);
layerManager.addLayer(new TestLayer());
layerManager.addLayer(layer2);
assertEquals(layerManager.getLayersOfType(TestLayer2.class), Arrays.asList(layer1, layer2));
}
/**
* {@link LayerManager#containsLayer(Layer)}
*/
@Test
public void testContainsLayer() {
TestLayer layer = new TestLayer();
layerManager.addLayer(layer);
layerManager.addLayer(new TestLayer());
assertTrue(layerManager.containsLayer(layer));
assertFalse(layerManager.containsLayer(new TestLayer()));
}
/**
* {@link LayerManager#addLayerChangeListener(LayerChangeListener)}
*/
@Test
public void testAddLayerChangeListener() {
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
assertNull(l.layerAdded);
assertNull(l.layerRemoved);
assertNull(l.layerOrderChanged);
}
/**
* {@link LayerManager#addLayerChangeListener(LayerChangeListener)} twice
*/
@Test(expected = IllegalArgumentException.class)
public void testAddLayerChangeListenerDupplicates() {
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
layerManager.addLayerChangeListener(l);
}
/**
* {@link LayerManager#addLayerChangeListener(LayerChangeListener, boolean)} fires fake add events
*/
@Test
public void testAddLayerChangeListenerFire() {
final ArrayList<Layer> fired = new ArrayList<>();
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
layerManager.addLayerChangeListener(new LayerChangeListener() {
@Override
public void layerRemoving(LayerRemoveEvent e) {
fail();
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
fail();
}
@Override
public void layerAdded(LayerAddEvent e) {
fired.add(e.getAddedLayer());
}
}, true);
assertEquals(Arrays.asList(layer1, layer2), fired);
}
/**
* {@link LayerManager#removeLayerChangeListener(LayerChangeListener)}
*/
@Test
public void testRemoveLayerChangeListener() {
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.addLayerChangeListener(l);
layerManager.addLayer(new TestLayer());
layerManager.removeLayerChangeListener(l);
layerManager.addLayer(new TestLayer());
// threw exception when fired twice.
assertNotNull(l.layerAdded);
assertNull(l.layerRemoved);
assertNull(l.layerOrderChanged);
}
/**
* {@link LayerManager#removeLayerChangeListener(LayerChangeListener)} listener not in list
*/
@Test(expected = IllegalArgumentException.class)
public void testRemoveLayerChangeListenerNotAdded() {
CapturingLayerChangeListener l = new CapturingLayerChangeListener();
layerManager.removeLayerChangeListener(l);
}
/**
* {@link LayerManager#removeLayerChangeListener(LayerChangeListener, boolean)} fires fake remove events
*/
@Test
public void testRemoveLayerChangeListenerFire() {
final ArrayList<Layer> fired = new ArrayList<>();
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
LayerChangeListener listener = new LayerChangeListener() {
@Override
public void layerRemoving(LayerRemoveEvent e) {
fired.add(e.getRemovedLayer());
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
fail();
}
@Override
public void layerAdded(LayerAddEvent e) {
fail();
}
};
layerManager.addLayerChangeListener(listener, false);
layerManager.removeLayerChangeListener(listener, true);
assertEquals(Arrays.asList(layer1, layer2), fired);
}
/**
* Test {@link LayerRemoveEvent#scheduleRemoval(java.util.Collection)}
*/
@Test
public void testLayerRemoveScheduleRemoval() {
TestLayer layer1 = new TestLayer();
TestLayer layer2 = new TestLayer();
layerManager.addLayer(layer1);
layerManager.addLayer(layer2);
layerManager.addLayerChangeListener(new LayerChangeListener() {
@Override
public void layerRemoving(LayerRemoveEvent e) {
if (e.getRemovedLayer() == layer1) {
e.scheduleRemoval(Collections.singleton(layer2));
}
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
fail();
}
@Override
public void layerAdded(LayerAddEvent e) {
fail();
}
});
layerManager.removeLayer(layer1);
assertEquals(0, layerManager.getLayers().size());
}
/**
* Test {@link LayerManager#resetState()}
*/
@Test
public void testResetState() {
ResetStateChangeListener changeListener = new ResetStateChangeListener();
layerManager.addLayer(new TestLayer());
layerManager.addLayerChangeListener(changeListener);
layerManager.addLayer(new TestLayer());
assertEquals(2, layerManager.getLayers().size());
assertNotNull(changeListener.layerAdded);
layerManager.resetState();
changeListener.layerAdded = null;
assertEquals(2, changeListener.removed);
assertEquals(0, layerManager.getLayers().size());
layerManager.addLayer(new TestLayer());
assertNull(changeListener.layerAdded);
}
}