From 7e27f9b6517ee3ed226b507dd873808fcd7ca843 Mon Sep 17 00:00:00 2001 From: Eric Tobias Date: Tue, 3 Nov 2015 11:50:30 +0100 Subject: [PATCH] 2.1.4 - Made SpatialMatrix a Singleton and added a return to the update call. + A call to update will now return whether the update changed the underlying data structure + Added PositionChangeListener + Called above listener when the underlying data structures are changed on a spatial event. + Made SpatialMatrix a singleton to better reflect its nature as a class maintaining some of the application state. + Added various test classes. --- CHANGELOG.txt | 6 + .../dkd/tui/event/PositionChangeEvent.java | 40 +++ .../dkd/tui/event/PositionChangeListener.java | 37 +++ .../itis/dkd/tui/space/SpatialMatrix.java | 32 +- .../tui/space/SpatialPositioningManager.java | 37 ++- .../tui/event/PositionChangeListenerTest.java | 287 ++++++++++++++++++ .../itis/dkd/tui/space/SpatialMatrixTest.java | 33 +- 7 files changed, 465 insertions(+), 7 deletions(-) create mode 100644 TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeEvent.java create mode 100644 TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeListener.java create mode 100644 TULIP/test/lu/list/itis/dkd/tui/event/PositionChangeListenerTest.java diff --git a/CHANGELOG.txt b/CHANGELOG.txt index dfe96a1..cc78417 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,9 @@ +2.1.4 + ++ Added test method for various classes. ++ Changed update method to return whether the underlying data structure have indeed been changed. ++ Added PositionChangeListener. + 2.1.3 + Added method to retrieve widgets aligned with one another horizontally and vertically. diff --git a/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeEvent.java b/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeEvent.java new file mode 100644 index 0000000..fa2567e --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeEvent.java @@ -0,0 +1,40 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2015. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.event; + +import lu.list.itis.dkd.tui.adapter.TangibleObject; + +import java.util.EventObject; + +/** + * @author Eric Tobias [eric.tobias@list.lu] + * @since 2.1 + * @version 2.1.4 + */ +public class PositionChangeEvent extends EventObject { + + private static final long serialVersionUID = -7146965508061315693L; + + /** + * The source that triggered the event. + * + * @param source + */ + public PositionChangeEvent(TangibleObject source) { + super(source); + } +} \ No newline at end of file diff --git a/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeListener.java b/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeListener.java new file mode 100644 index 0000000..77474e3 --- /dev/null +++ b/TULIP/src/lu/list/itis/dkd/tui/event/PositionChangeListener.java @@ -0,0 +1,37 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2015. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.event; + +import lu.list.itis.dkd.tui.space.SpatialPositioningManager; + +import java.util.EventListener; + +/** + * @author Eric Tobias [eric.tobias@list.lu] + * @since 2.1 + * @version 2.1.4 + */ +public interface PositionChangeListener extends EventListener { + + /** + * Method invoked when a position tracked by the spatial position manager has been changed. + * + * @param manager + * The manager holding information on the spatial layout. + */ + public void positionChanged(SpatialPositioningManager manager); +} \ No newline at end of file diff --git a/TULIP/src/lu/list/itis/dkd/tui/space/SpatialMatrix.java b/TULIP/src/lu/list/itis/dkd/tui/space/SpatialMatrix.java index ff484a2..bc41046 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/space/SpatialMatrix.java +++ b/TULIP/src/lu/list/itis/dkd/tui/space/SpatialMatrix.java @@ -36,6 +36,18 @@ public class SpatialMatrix { private ArrayList horizontal = new ArrayList<>(); private ArrayList vertical = new ArrayList<>(); + private static final SpatialMatrix INSTANCE = new SpatialMatrix(); + + private SpatialMatrix() {} + + /** + * Singleton getter. + * + * @return The unique, single instance of this class. + */ + public static SpatialMatrix getInstance() { + return INSTANCE; + } /** * Method used to add an element to the matrix. The method will insert the element into the @@ -74,10 +86,20 @@ public class SpatialMatrix { * * @param object * The object to update. + * @return Will return true if the underlying horizontal or vertical lists have + * been changed, that is, if the order of any of those lists have been changed. The + * method will use an equality check on the lists before (using a copy) and after the + * operations. The method will return false if both lists are equal. + * @see ArrayList#equals(Object) */ - public synchronized void update(TangibleObject object) { + public synchronized boolean update(TangibleObject object) { + ArrayList horizontalCopy = new ArrayList<>(horizontal); + ArrayList verticalCopy = new ArrayList<>(vertical); + remove(object); add(object); + + return !(horizontalCopy.equals(horizontal) && verticalCopy.equals(vertical)); } @@ -393,4 +415,12 @@ public class SpatialMatrix { return "Vertical : " + verticalBuilder.toString() + "\n" + "Horizontal : " + horizontalbuilder.toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + + /** + * Method used to clear the internal maps held by the matrix. Useful during unit testing. + */ + public void clear() { + horizontal.clear(); + vertical.clear(); + } } \ No newline at end of file diff --git a/TULIP/src/lu/list/itis/dkd/tui/space/SpatialPositioningManager.java b/TULIP/src/lu/list/itis/dkd/tui/space/SpatialPositioningManager.java index a205a68..09796ae 100644 --- a/TULIP/src/lu/list/itis/dkd/tui/space/SpatialPositioningManager.java +++ b/TULIP/src/lu/list/itis/dkd/tui/space/SpatialPositioningManager.java @@ -20,6 +20,7 @@ import lu.list.itis.dkd.dbc.annotation.NonNullByDefault; import lu.list.itis.dkd.dbc.annotation.Nullable; import lu.list.itis.dkd.tui.TangibleObjectManager; import lu.list.itis.dkd.tui.adapter.TangibleObject; +import lu.list.itis.dkd.tui.event.PositionChangeListener; import lu.list.itis.dkd.tui.event.SpatialEvent; import lu.list.itis.dkd.tui.event.SpatialEventListener; import lu.list.itis.dkd.tui.utility.Point; @@ -63,7 +64,9 @@ public class SpatialPositioningManager implements SpatialEventListener { private static final Properties properties = PropertiesFetcher.fetchProperties(); private static final SpatialPositioningManager INSTANCE = new SpatialPositioningManager(); - private SpatialMatrix spatialMatrix = new SpatialMatrix(); + private List positionChangeListeners = new ArrayList<>(); + + private SpatialMatrix spatialMatrix = SpatialMatrix.getInstance(); /** * Enumeration showing all alignment types this class supports. @@ -127,7 +130,9 @@ public class SpatialPositioningManager implements SpatialEventListener { } /** - * {@inheritDoc}
+ * {@inheritDoc} The method will also notify all {@link PositionChangeListener} instances of any + * change, that is, it will notify all of any insertion or removals but only notify on updates + * if there is indeed a change to the underlying spatial matrix.
*
* * The event will not be registered if the if of the tangible is not recognized by the @@ -142,13 +147,17 @@ public class SpatialPositioningManager implements SpatialEventListener { switch (event.getType()) { case UPDATE: - spatialMatrix.update(event.getSource()); + if (spatialMatrix.update(event.getSource())) { + notifyPositionChangeListeners(this); + } break; case INSERT: spatialMatrix.add(event.getSource()); + notifyPositionChangeListeners(this); break; case REMOVE: spatialMatrix.remove(event.getSource()); + notifyPositionChangeListeners(this); break; default: logger.log(Level.SEVERE, "Spatial event type \"" + event.getType() + "\" not recognized!"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -169,6 +178,26 @@ public class SpatialPositioningManager implements SpatialEventListener { // // //$NON-NLS-2$ } + /** + * Method used to add a position change listener to this instance of the manager. + * + * @param listener + * The listener to add and notify of any changes. + */ + public void addPositionChangeListener(PositionChangeListener listener) { + this.positionChangeListeners.add(listener); + } + + /** + * Method invoked to notify all {@link PositionChangeListener} instances of a change. + * + * @param manager + * The manager holding information on the spatial layout. + */ + public void notifyPositionChangeListeners(SpatialPositioningManager manager) { + positionChangeListeners.forEach(listener -> listener.positionChanged(manager)); + } + /** * The method will return the left-most widget, that is, the first widget found based based on * its x-axis coordinate. The retrieval is based on the underlying handles. @@ -418,6 +447,6 @@ public class SpatialPositioningManager implements SpatialEventListener { * testing. */ public void reinitialize() { - spatialMatrix = new SpatialMatrix(); + spatialMatrix.clear(); } } \ No newline at end of file diff --git a/TULIP/test/lu/list/itis/dkd/tui/event/PositionChangeListenerTest.java b/TULIP/test/lu/list/itis/dkd/tui/event/PositionChangeListenerTest.java new file mode 100644 index 0000000..adc9d37 --- /dev/null +++ b/TULIP/test/lu/list/itis/dkd/tui/event/PositionChangeListenerTest.java @@ -0,0 +1,287 @@ +/** + * Copyright Luxembourg Institute of Science and Technology, 2015. All rights reserved. + * + * This file is part of TULIP. + * + * TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation, version 3 of the + * License. + * + * TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with TULIP. If + * not, see . + */ +package lu.list.itis.dkd.tui.event; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import lu.list.itis.dkd.dbc.annotation.NonNullByDefault; +import lu.list.itis.dkd.tui.TangibleApplication; +import lu.list.itis.dkd.tui.TangibleInterfaceManager; +import lu.list.itis.dkd.tui.TangibleObjectManager; +import lu.list.itis.dkd.tui.adapter.TangibleObject; +import lu.list.itis.dkd.tui.adapter.TangibleObject.Type; +import lu.list.itis.dkd.tui.adapter.TangibleObjectBuilder; +import lu.list.itis.dkd.tui.adapter.TuiAdapter; +import lu.list.itis.dkd.tui.event.SpatialEvent.SpatialEventType; +import lu.list.itis.dkd.tui.exception.BuildException; +import lu.list.itis.dkd.tui.space.SpatialPositioningManager; +import lu.list.itis.dkd.tui.utility.Point; +import lu.list.itis.dkd.tui.widget.BaseWidget; +import lu.list.itis.dkd.tui.widget.builder.BaseWidgetBuilder; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Properties; +import java.util.logging.Logger; + +/** + * @author Eric Tobias [eric.tobias@list.lu] + * @since 2.1 + * @version 2.1.4 + */ +public class PositionChangeListenerTest { + + /** + * Mock for the {@link PositionChangeListener} registering whether it was called and the + * parameter passed to it. + * + * @author Eric Tobias [eric.tobias@list.lu] + * @since 2.1 + * @version 2.1.4 + */ + public static class PositionChangeListenerMock implements PositionChangeListener { + + private boolean called; + private SpatialPositioningManager caller; + + /** + * Simple getter method for called. + * + * @return The value of called. + */ + public boolean wasCalled() { + return called; + } + + /** + * Simple getter method for caller. + * + * @return The value of caller. + */ + public SpatialPositioningManager getCaller() { + return caller; + } + + /** {@inheritDoc} */ + @Override + public void positionChanged(SpatialPositioningManager manager) { + called = true; + caller = manager; + } + + /** + * Method used to reset the values stored by the mock listener. + */ + public void reset() { + called = false; + caller = null; + } + } + + /** + * Class implementing the abstract {@link TangibleApplication} for test purposes. + * + * @author Eric Tobias [eric.tobias@list.lu] + * @since 2.1 + * @version 2.1.3 + */ + public class TestTangibleApplication extends TangibleApplication { + /** + * Constructor initialising several basic properties. + * + * @param newPropertiesFileURI + * The URI of the properties file to load. + * @throws Exception + */ + protected TestTangibleApplication(String newPropertiesFileURI) throws Exception { + super(newPropertiesFileURI); + + objectManager = new TestTangibleObjectManager(this, TangibleApplication.logger, properties); + objectManager.defineWidgets(); + this.adapter.setObjectManager(objectManager); + this.adapter.addListener(SpatialPositioningManager.getInstance()); + } + + @SuppressWarnings("javadoc") + public TuiAdapter getAdapter() { + return this.adapter; + } + } + + /** + * Class implementing the abstract {@link TangibleApplication} for test purposes. + * + * @author Eric Tobias [eric.tobias@tudor.lu] + * @since 2.1 + * @version 2.1.3 + */ + @SuppressWarnings("serial") + public class TestTangibleInterfaceManager extends TangibleInterfaceManager { + + /** + * @see TangibleInterfaceManager + */ + @SuppressWarnings("javadoc") + public TestTangibleInterfaceManager(TangibleApplication applicationContext, Logger applicationLogger, Properties properties) { + super(applicationContext, applicationLogger, properties); + } + } + + /** + * Class implementing the abstract {@link TangibleObjectManager} for test purposes. + * + * @author Eric Tobias [eric.tobias@tudor.lu] + * @since 2.1 + * @version 2.1.3 + */ + public class TestTangibleObjectManager extends TangibleObjectManager { + /** + * Constructor initialising several fields and setting up the list of managed tangibles. + * + * @param applicationContext + * The {@link TangibleApplication} hosting this manager. + * @param applicationLogger + * The {@link Logger} to use for all logging purposes. + * @param properties + * The properties instance from which to load the default values from. + */ + @NonNullByDefault + public TestTangibleObjectManager(TangibleApplication applicationContext, Logger applicationLogger, Properties properties) { + super(applicationContext, applicationLogger, properties); + } + + /** + * {@inheritDoc} + */ + @Override + public void defineWidgets() throws BuildException { + BaseWidget baseWidget = new BaseWidgetBuilder() + .withHandle(0, new Point(0, 0, 0)) + .build(); + objectList.put(0, baseWidget); + BaseWidget baseWidget_1 = new BaseWidgetBuilder() + .withHandle(1, new Point(10, 10, 10)) + .build(); + objectList.put(1, baseWidget_1); + BaseWidget baseWidget_2 = new BaseWidgetBuilder() + .withHandle(2, new Point(20, 20, 20)) + .build(); + objectList.put(2, baseWidget_2); + BaseWidget baseWidget_3 = new BaseWidgetBuilder() + .withHandle(3, new Point(30, 30, 30)) + .build(); + objectList.put(3, baseWidget_3); + BaseWidget baseWidget_4 = new BaseWidgetBuilder() + .withHandle(4, new Point(40, 40, 40)) + .build(); + objectList.put(4, baseWidget_4); + } + } + + private TestTangibleApplication application; + + private static TangibleObjectBuilder builderZero; + private static TangibleObjectBuilder builderOne; + private static TangibleObjectBuilder builderTwo; + private static TangibleObjectBuilder builderThree; + private static TangibleObjectBuilder builderFour; + + private TangibleObject objectZero; + private TangibleObject objectOne; + private TangibleObject objectTwo; + private TangibleObject objectThree; + private TangibleObject objectFour; + + private static PositionChangeListenerMock listenerMock = new PositionChangeListenerMock(); + + /** + * @throws Exception + * + */ + @BeforeClass + public static void setUpBefore() throws Exception { + builderZero = new TangibleObjectBuilder(0, Type.OBJECT, 0, 0); + builderOne = new TangibleObjectBuilder(1, Type.CURSOR, 10, 10); + builderTwo = new TangibleObjectBuilder(2, Type.BLOB, 20, 20); + builderThree = new TangibleObjectBuilder(3, Type.OBJECT, 30, 30); + builderFour = new TangibleObjectBuilder(4, Type.CURSOR, 40, 40); + + + SpatialPositioningManager.getInstance().addPositionChangeListener(listenerMock); + + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + application = new TestTangibleApplication("test_properties.properties"); //$NON-NLS-1$ + + objectZero = builderZero.build(); + objectOne = builderOne.build(); + objectTwo = builderTwo.build(); + objectThree = builderThree.build(); + objectFour = builderFour.build(); + } + + /** + * Method used to reinitialize the spatial matrix after each test. + */ + @After + public void after() { + SpatialPositioningManager.getInstance().reinitialize(); + objectOne.getPosition().setLocation(new Point(10, 10, 10)); + } + + + /** + * Method used for testing whether the {@link PositionChangeListener} works as intended. + * Incidentally this also tests whether updates on positions are sent out as expected. + */ + @Test + public void test() { + assertFalse(listenerMock.wasCalled()); + application.getAdapter().notifyListeners(new SpatialEvent(objectOne, objectOne.getPosition(), SpatialEventType.INSERT)); + assertTrue(listenerMock.wasCalled()); + assertEquals(SpatialPositioningManager.getInstance(), listenerMock.getCaller()); + listenerMock.reset(); + + application.getAdapter().notifyListeners(new SpatialEvent(objectZero, objectZero.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectThree, objectThree.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectTwo, objectTwo.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectFour, objectFour.getPosition(), SpatialEventType.INSERT)); + + listenerMock.reset(); + + assertFalse(listenerMock.wasCalled()); + application.getAdapter().notifyListeners(new SpatialEvent(objectFour, objectFour.getPosition(), SpatialEventType.REMOVE)); + assertTrue(listenerMock.wasCalled()); + assertEquals(SpatialPositioningManager.getInstance(), listenerMock.getCaller()); + listenerMock.reset(); + + objectOne.getPosition().setLocation(new Point(25, 25, 25)); + application.getAdapter().notifyListeners(new SpatialEvent(objectOne, objectOne.getPosition(), SpatialEventType.UPDATE)); + assertTrue(listenerMock.wasCalled()); + assertEquals(SpatialPositioningManager.getInstance(), listenerMock.getCaller()); + } +} \ No newline at end of file diff --git a/TULIP/test/lu/list/itis/dkd/tui/space/SpatialMatrixTest.java b/TULIP/test/lu/list/itis/dkd/tui/space/SpatialMatrixTest.java index 717a229..eabd550 100644 --- a/TULIP/test/lu/list/itis/dkd/tui/space/SpatialMatrixTest.java +++ b/TULIP/test/lu/list/itis/dkd/tui/space/SpatialMatrixTest.java @@ -172,7 +172,6 @@ public class SpatialMatrixTest { /** * @throws Exception - * */ @BeforeClass public static void setUpBefore() throws Exception { @@ -181,7 +180,6 @@ public class SpatialMatrixTest { builderTwo = new TangibleObjectBuilder(2, Type.BLOB, 20, 20); builderThree = new TangibleObjectBuilder(3, Type.OBJECT, 30, 30); builderFour = new TangibleObjectBuilder(4, Type.CURSOR, 40, 40); - } /** @@ -205,6 +203,12 @@ public class SpatialMatrixTest { @After public void after() { SpatialPositioningManager.getInstance().reinitialize(); + + objectZero.getPosition().toCameraCoordinates().setLocation(new Point(0, 0, 0)); + objectOne.getPosition().toCameraCoordinates().setLocation(new Point(10, 10, 10)); + objectTwo.getPosition().toCameraCoordinates().setLocation(new Point(20, 20, 20)); + objectThree.getPosition().toCameraCoordinates().setLocation(new Point(30, 30, 30)); + objectFour.getPosition().toCameraCoordinates().setLocation(new Point(40, 40, 40)); } /** @@ -301,6 +305,31 @@ public class SpatialMatrixTest { assertEquals(5, matrix.getHorizontal().size()); } + /** + * Test method for + * {@link lu.list.itis.dkd.tui.space.SpatialMatrix#update(lu.list.itis.dkd.tui.adapter.TangibleObject)} + * , specifically, for testing its return value. + */ + @Test + public void voidTestUpdateReturn() { + application.getAdapter().notifyListeners(new SpatialEvent(objectZero, objectZero.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectOne, objectOne.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectThree, objectThree.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectTwo, objectTwo.getPosition(), SpatialEventType.INSERT)); + application.getAdapter().notifyListeners(new SpatialEvent(objectFour, objectFour.getPosition(), SpatialEventType.INSERT)); + + assertFalse(matrix.update(objectOne)); + + objectOne.getPosition().toCameraCoordinates().setLocation(new Point(1, 1, 1)); + assertFalse(matrix.update(objectOne)); + + objectThree.getPosition().toCameraCoordinates().setLocation(new Point(45, 45, 45)); + assertTrue(matrix.update(objectThree)); + + objectThree.getPosition().toCameraCoordinates().setLocation(new Point(5, 5, 5)); + assertTrue(matrix.update(objectThree)); + } + /** * Test method for {@link lu.list.itis.dkd.tui.space.SpatialMatrix#leftOf(int)}. */ -- GitLab