Commit 83bf257b authored by Eric Tobias's avatar Eric Tobias

Bootstrapping and library updates.

This commit constitutes the first nightly bootstrapping build. As of now, bootstrapping supports most coronas but no InformationProvides. Only the BaseWidget is supported at the moment.

+ String Externalization for to make changes regarding the boostrapping XML maintainable.
+ Updated some libraries such as Guava and JDOM2 (see Changelog)
+ Added constructors to most base builders of the coronas and the base widget to work with a provided JDOM2 Element node.
+ Added bootstrapping classes with static methods to provide support to bootstrap other major classes such as concrete NetworkArapter implementations.
parent b580cbde
2.2.0
Implementation of XML based bootstrapping for widgets and coronas.
+ Moved to Guava 19
+ Moved to JDOM2 2.0.6
+ Added org.eclipse.osgi in version 3.7.1 to manage string internationalization (licensed as EPL 1)
+ Image and its related builders, notably those for the image-based corona, no longer throw a BuildException. This was already the case but the signature still contained the exception being thrown.
+ Removed the field method to set "alwaysActive" for coronas from the CoronaBuilder. The concept was removed as it is thought that coronas should not be always active.
+ Removed field and methods for corona persistence from Corona and the CoronaBuilder. No scenario exists that accounts for persistence and the related methods when drawing were not supported either.
+ Removed the field "background" and all related methods from Corona. Adjusted Image accordingly. The field was not used. Conceptually, coronas should never be part of the background.
2.1.4
+ Added test method for various classes.
......
......@@ -10,13 +10,15 @@ History
TULIP was designed and implemented in an iterative process as of 2014. The bulk of the framework has been developed by @etobias with the help of and under the watchful eyes of @vmaquil who heads the research activities in the Natural User Interfaces domain. From January to June 2014 the framework saw the first major developments with some complementary implementation and bug fixing activity done during the Summer and Fall of 2014. In Fall 2014, @yrangoni added the first traces of a lightweight logging library.
In January 2015 the project was migrated to Git, using GitLab as a platform.
Current development
We currently develop an adapter for the computer vision framework and are in the stages of testing it. Similarly, we are developing adapters to address networked devices.
We are currently developping a boostrapping mechanism that will allow to instantiate widgets and their coronas from an XML file. This development is part of the Re-Engage project.
Libraries and Licenses
As of now, the provided sources are still in development.
Currently the project depends on an in-house annotation library that is distributed with this project as well as Guava (licensed under Apache 2.0), JDOM (licensed under an Apache-style open source license), and the TUIO Java client (licensed as LGPL).
Currently the project depends on an in-house annotation library that is distributed with this project as well as Guava (licensed under Apache 2.0), JDOM2 (licensed under an Apache-style open source license), and the TUIO Java client (licensed as LGPL). The project uses the Eclipse OSGI Utility library (licensed as EPL1) for string externalization.
The network adapters are using BlueCove (licensed under Apache 2.0) for Bluetooth communication and RXTX (licensed as LGPL) for Xbee communication.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<tangibles>
<objects>
<object>
<type>BaseWidget</type>
<coronas>
<corona>
<type>Shadow</type>
<centre>
<x>0</x>
<y>0</y>
<z>0</z>
</centre>
</corona>
</coronas>
<name>Marty</name>
<definingShape>Circle</definingShape>
<networkAdapter></networkAdapter>
</object>
</objects>
<cursors>
<cursor>
<type></type>
<coronas>
<corona></corona>
</coronas>
<name></name>
<defining-shape></defining-shape>
<network-adapter></network-adapter>
</cursor>
</cursors>
<blobs>
<blob>
<type></type>
<coronas>
<corona></corona>
</coronas>
<name></name>
<defining-shape></defining-shape>
<network-adapter></network-adapter>
</blob>
</blobs>
</tangibles>
<!-- Font: http://docs.oracle.com/javase/1.5.0/docs/api/java/awt/Font.html#decode%28java.lang.String%29 -->
ALPHA=alpha
BACKGROUND_COLOUR=backgroundColour
BORDER_THICKNESS=borderThickness
BORDER_WIDTH=borderWidth
CENTRE_NODE=centre
CENTRED=centred
COLOUR=colour
CORONAS_NODE=coronas
DEFINING_SHAPE_NODE=definingShape
DRAW_BORDER=drawBorder
DRAW_PRIORITY_NODE=drawPriority
EDGE_COLOUR=edgeColour
FILL_COLOUR=fillColour
FONT=font
FONT_SIZE=fontSize
HANDLE_NODE=handle
HANDLES_NODE=handles
IMAGE=image
INFORMATION=information
INITIAL_ROTATION_NODE=initialRotation
INITIAL_TRANSLATION_NODE=initialTranslation
JAVA_AWT_COLOR_NAMESPACE=java.awt.Color
LENGTH=length
LINE=line
LINE_HEIGHT_RATIO=lineHeightRatio
LINE_WIDTH=lineWidth
NAME_NODE=name
NETWORK_ADAPTER_NODE=networkAdapter
RADIUS=radius
ROTATE_WITH_HANDLE_NODE=rotateWithHandle
SHAPE_NODE=shape
SPIN_ON_CORONA_CENTRE_NODE=spinOnCoronaCentre
START=start
TEXT=text
<?xml version="1.0" encoding="UTF-8"?>
<tangibles>
<objects>
<object>
<type>BaseWidget</type>
<handles>
<handle>1</handle>
</handles>
<coronas>
<corona>
<type>Shadow</type>
<handle>1</handle>
<centre>
<x>0</x>
<y>0</y>
<z>0</z>
</centre>
</corona>
</coronas>
<name>Marty</name>
<definingShape>Circle</definingShape>
<networkAdapter></networkAdapter>
</object>
</objects>
<cursors>
<cursor>
<type></type>
<coronas>
<corona></corona>
</coronas>
<name></name>
<defining-shape></defining-shape>
<network-adapter></network-adapter>
</cursor>
</cursors>
<blobs>
<blob>
<type></type>
<coronas>
<corona></corona>
</coronas>
<name></name>
<defining-shape></defining-shape>
<network-adapter></network-adapter>
</blob>
</blobs>
</tangibles>
......@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>lu.list.itis.dkd.tui</groupId>
<artifactId>tulip</artifactId>
<version>2.0.41</version>
<version>2.2.0</version>
<name>TULIP</name>
<description>A framework for tabletop tangible user interface applications.</description>
<build>
......@@ -23,12 +23,12 @@
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>15.0</version>
<version>19.0</version>
</dependency>
<dependency>
<groupId>net.sf.bluecove</groupId>
......@@ -40,7 +40,6 @@
<artifactId>rxtx</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>net.sourceforge.tuio</groupId>
<artifactId>tuio</artifactId>
......@@ -51,5 +50,10 @@
<artifactId>dbc-annotation</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.7.1</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -47,7 +47,7 @@ import javax.swing.JFrame;
* Class managing the setup of the window displaying all top-level TUI related visualisation as well
* as any configuration behaviour.
*
* @author Eric TOBIAS [eric.tobias@list.lu]
* @author Eric Tobias [eric.tobias@list.lu]
* @since 1.0
* @version 1.0.2
*/
......@@ -113,8 +113,9 @@ public class TangibleInterfaceManager extends JComponent {
title = interfaceProperties.getProperty("frameTitle", "TangibleApplication"); //$NON-NLS-1$ //$NON-NLS-2$
String calibration = interfaceProperties.getProperty("calibrationFileURI"); //$NON-NLS-1$
if (calibration != null)
if (calibration != null) {
calibrationFileURI = calibration;
}
device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
Calibration.loadFromFile(calibrationFileURI);
......
......@@ -42,23 +42,23 @@ import java.util.logging.Logger;
* like. The class also holds the collection of tangibles managed by the application. The class uses
* locks to synchronise all calls to manage objects.
*
* @author Eric TOBIAS [eric.tobias@list.lu]
* @author Eric Tobias [eric.tobias@list.lu]
* @author Nicolas Gilmard
* @since 1.0
* @version 2.0.2
* @version 2.2.0
*/
@NonNullByDefault
public abstract class TangibleObjectManager {
/**
* This dictionary stores all tangibles detected by reacTIVision relevant to the application.
*/
protected volatile static ConcurrentHashMap<Integer, BaseWidget> objectList = new ConcurrentHashMap<>();
protected volatile static ConcurrentHashMap<Integer, BaseWidget> objectMap = new ConcurrentHashMap<>();
/** This dictionary stores all cursors detected by reacTIVision. */
protected volatile static ConcurrentHashMap<Integer, BaseWidget> cursorList = new ConcurrentHashMap<>();
protected volatile static ConcurrentHashMap<Integer, BaseWidget> cursorMap = new ConcurrentHashMap<>();
/** This dictionary stores all blobs detected by reacTIVision. */
protected volatile static ConcurrentHashMap<Integer, BaseWidget> blobList = new ConcurrentHashMap<>();
protected volatile static ConcurrentHashMap<Integer, BaseWidget> blobMap = new ConcurrentHashMap<>();
/** A {@link Logger} to log all messages during execution. */
protected Logger logger;
......@@ -95,15 +95,14 @@ public abstract class TangibleObjectManager {
* When adding Widgets to map to cursors, refer to {@link IdMapper#remapCursorId(int)}. Cursor
* IDs are remapped as not to clash with any symbol ID assigned by reacTIVision. A good practice
* is to reason with regular cursor IDs and add them to the map by calling for example:
* <code>objectList.put(remapCursorID(0), W extends BaseWidget);</code> to the first cursor that
* <code>objectMap.put(remapCursorID(0), W extends BaseWidget);</code> to the first cursor that
* is placed to the widget in the put statement.<br>
* <br>
*
* To dynamically allocate widgets to cursors, the <code>addTuioCursor()</code> method should be
* overridden or reimplimented to dynamically instantiate the desired widget and add it to the
* dictionary of objects. This would require manual removal of the dynamically created widget in
* the <code>
* removeTuioCursor()</code> method.
* the <code>removeTuioCursor()</code> method.
*
* @throws BuildException
* Exception raised when the building on a widget or corona instance cannot complete
......@@ -119,7 +118,7 @@ public abstract class TangibleObjectManager {
private void ensureConsistency() {
Vector<TangibleObject> objectBlackList = new Vector<>();
for (TangibleObject object : tangibleApplication.getActiveObjects()) {
if (!objectList.keySet().contains(object.getObjectId())) {
if (!objectMap.keySet().contains(object.getObjectId())) {
objectBlackList.add(object);
}
}
......@@ -130,7 +129,7 @@ public abstract class TangibleObjectManager {
Vector<TangibleObject> cursorBlackList = new Vector<>();
for (TangibleObject cursor : tangibleApplication.getActiveCursors()) {
if (!cursorList.keySet().contains(cursor.getObjectId())) {
if (!cursorMap.keySet().contains(cursor.getObjectId())) {
cursorBlackList.add(cursor);
}
}
......@@ -148,9 +147,9 @@ public abstract class TangibleObjectManager {
*/
public static synchronized Collection<BaseWidget> getWidgets() {
Collection<BaseWidget> widgets = new ArrayList<>();
widgets.addAll(objectList.values());
widgets.addAll(cursorList.values());
widgets.addAll(blobList.values());
widgets.addAll(objectMap.values());
widgets.addAll(cursorMap.values());
widgets.addAll(blobMap.values());
return widgets;
}
......@@ -164,14 +163,14 @@ public abstract class TangibleObjectManager {
* identifier.
*/
public static synchronized @Nullable BaseWidget getWidget(int identifier) {
if (null != objectList.get(identifier)) {
return objectList.get(identifier);
if (null != objectMap.get(identifier)) {
return objectMap.get(identifier);
}
if (null != cursorList.get(identifier)) {
return cursorList.get(identifier);
if (null != cursorMap.get(identifier)) {
return cursorMap.get(identifier);
}
return blobList.get(identifier);
return blobMap.get(identifier);
}
/**
......@@ -197,15 +196,15 @@ public abstract class TangibleObjectManager {
switch (tangibleObject.getType()) {
case CURSOR:
cursorList.putIfAbsent(tangibleObject.getObjectId(), new BaseWidgetBuilder().withCorona(tangibleObject.getObjectId(), new ShadowBuilder(new Point()).withShape(ShapeFactory.buildCircle(5)).build()).build());
cursorMap.putIfAbsent(tangibleObject.getObjectId(), new BaseWidgetBuilder().withCorona(tangibleObject.getObjectId(), new ShadowBuilder(new Point()).withShape(ShapeFactory.buildCircle(5)).build()).build());
cursorList.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
cursorMap.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
ensureConsistency();
break;
case OBJECT:
if (objectList.containsKey(tangibleObject.getObjectId())) {
objectList.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
if (objectMap.containsKey(tangibleObject.getObjectId())) {
objectMap.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
} else {
logger.log(Level.WARNING, "The recognised symbol was not assigned to a widget!"); //$NON-NLS-1$
}
......@@ -214,9 +213,9 @@ public abstract class TangibleObjectManager {
case BLOB:
// TODO This line add a small shadow with each blob. This behaviour might not be
// desired but needs to be investigated. Opening issue #27.
blobList.putIfAbsent(tangibleObject.getObjectId(), new BaseWidgetBuilder().withCorona(tangibleObject.getObjectId(), new ShadowBuilder(new Point()).withShape(ShapeFactory.buildCircle(5)).build()).build());
blobMap.putIfAbsent(tangibleObject.getObjectId(), new BaseWidgetBuilder().withCorona(tangibleObject.getObjectId(), new ShadowBuilder(new Point()).withShape(ShapeFactory.buildCircle(5)).build()).build());
blobList.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
blobMap.get(tangibleObject.getObjectId()).actionDrop(tangibleObject);
break;
default:
logger.log(Level.WARNING, "The recognised symbol was not assigned to a widget!"); //$NON-NLS-1$
......@@ -236,22 +235,22 @@ public abstract class TangibleObjectManager {
switch (tangibleObject.getType()) {
case CURSOR:
if (cursorList.containsKey(tangibleObject.getObjectId())) {
cursorList.remove(tangibleObject.getObjectId()).actionLift(tangibleObject);
if (cursorMap.containsKey(tangibleObject.getObjectId())) {
cursorMap.remove(tangibleObject.getObjectId()).actionLift(tangibleObject);
}
break;
case OBJECT:
if (objectList.containsKey(tangibleObject.getObjectId())) {
objectList.get(tangibleObject.getObjectId()).actionLift(tangibleObject);
if (objectMap.containsKey(tangibleObject.getObjectId())) {
objectMap.get(tangibleObject.getObjectId()).actionLift(tangibleObject);
} else {
logger.log(Level.WARNING, "The recognised symbol was not assigned to a widget!"); //$NON-NLS-1$
}
break;
case BLOB:
if (blobList.containsKey(tangibleObject.getObjectId())) {
blobList.remove(tangibleObject.getObjectId()).actionLift(tangibleObject);
if (blobMap.containsKey(tangibleObject.getObjectId())) {
blobMap.remove(tangibleObject.getObjectId()).actionLift(tangibleObject);
}
break;
default:
......@@ -272,22 +271,22 @@ public abstract class TangibleObjectManager {
switch (tangibleObject.getType()) {
case CURSOR:
if (cursorList.containsKey(tangibleObject.getObjectId())) {
cursorList.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
if (cursorMap.containsKey(tangibleObject.getObjectId())) {
cursorMap.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
} else {
logger.log(Level.WARNING, "The cursor could not be recognised!"); //$NON-NLS-1$
}
break;
case OBJECT:
if (objectList.containsKey(tangibleObject.getObjectId())) {
objectList.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
if (objectMap.containsKey(tangibleObject.getObjectId())) {
objectMap.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
} else {
logger.log(Level.WARNING, "The recognised symbol was not assigned to a widget!"); //$NON-NLS-1$
}
break;
case BLOB:
if (blobList.containsKey(tangibleObject.getObjectId())) {
blobList.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
if (blobMap.containsKey(tangibleObject.getObjectId())) {
blobMap.get(tangibleObject.getObjectId()).actionMove(tangibleObject);
} else {
logger.log(Level.WARNING, "The blob could not be recognised!"); //$NON-NLS-1$
}
......
/**
* 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.bootstrapping;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.widget.corona.Corona;
import lu.list.itis.dkd.tui.widget.corona.builder.CoronaBuilder;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import org.jdom2.Element;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author Eric Tobias [eric.tobias@list.lu]
* @since 2.2
* @version 2.2.0
*/
public class CoronaBootstrapper {
private static final String CORONA_BUILDER_NAMESPACE = "lu.list.itis.dkd.tui.widget.corona.builder."; //$NON-NLS-1$
/**
* Method used to determine the appropriate builder for a given corona and then issue a build
* call.
*
* @param coronaNode
* The node from a larger document that contains, as children, all the necessary
* information to resolve the correct builder and build the final, concrete, corona.
* @return The final concrete corona as defined by the children of the element node.
* @throws ClassNotFoundException
* Thrown when the class of the builder for the corona could not be found.
* @throws SecurityException
* Thrown when the constructor cannot be retrieved due to some security constraints.
* @throws NoSuchMethodException
* Thrown when no constructor with the given parameter type is available.
* @throws InvocationTargetException
* Thrown if the invocation of any constructor through reflection throws an exception.
* @throws IllegalArgumentException
* Thrown when the provided argument is not eligible for the builder's constructor.
* @throws IllegalAccessException
* Thrown if this Constructor object is enforcing Java language access control and the
* underlying constructor is inaccessible.
* @throws InstantiationException
* Thrown if the class that declares the underlying constructor represents an abstract
* class.
*/
private static Corona getCorona(Element coronaNode) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
Element type = coronaNode.getChild("type"); //$NON-NLS-1$
Class<?> builder = Class.forName(CORONA_BUILDER_NAMESPACE + type.getValue() + "Builder"); //$NON-NLS-1$
@SuppressWarnings("unchecked")
Constructor<CoronaBuilder<?>> constructor = (Constructor<CoronaBuilder<?>>) builder.getConstructor(new Class[] {Element.class});
return constructor.newInstance(new Object[] {coronaNode}).build();
}
/**
* Method used for bootstrapping all coronas found as child nodes of a root node. The method
* will look for child nodes names <code>corona</code>.
*
* @param coronaRootNode
* The root node that holds all <code>corona</code> child nodes.
* @return A map containing all concrete {@link Corona} instances that could be build from the
* provided root node keyed to the handle ID to which they are associated. *
* @throws BuildException
* Thrown when a corona was not associated to any handle.
*/
public static Multimap<Integer, Corona> getCoronas(Element coronaRootNode) throws BuildException {
Multimap<Integer, Corona> coronas = TreeMultimap.create();
for (Element coronaNode : coronaRootNode.getChildren("corona")) { //$NON-NLS-1$
try {
int handleId = Integer.parseInt(coronaNode.getChildText("handle")); //$NON-NLS-1$
coronas.put(handleId, getCorona(coronaNode));
} catch (NumberFormatException nfe) {
throw new BuildException("The corona was not associated with any handle. The handle ID may not be null!", nfe); //$NON-NLS-1$
} catch (ReflectiveOperationException roe) {
throw new BuildException("One or more operations failed while issuing a reflective call!", roe); //$NON-NLS-1$
}
}
return coronas;
}
}
\ No newline at end of file
/**
* 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 <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.bootstrapping;
import org.eclipse.osgi.util.NLS;
/**
* Auto-generated class for string externalization to be used to bundle all node names.
*
* @author Eric Tobias [eric.tobias@list.lu]
* @since 2.2
* @version 2.2.0
*/
@SuppressWarnings("javadoc")
public class Messages extends NLS {
private static final String BUNDLE_NAME = "messages"; //$NON-NLS-1$
public static String ALPHA;
public static String BACKGROUND_COLOUR;
public static String BORDER_THICKNESS;
public static String BORDER_WIDTH;
public static String CENTRE_NODE;
public static String CENTRED;
public static String COLOUR;
public static String CORONAS_NODE;
public static String DEFINING_SHAPE_NODE;
public static String DRAW_BORDER;
public static String DRAW_PRIORITY_NODE;
public static String EDGE_COLOUR;
public static String FILL_COLOUR;
public static String FONT;
public static String FONT_SIZE;
public static String HANDLE_NODE;
public static String HANDLES_NODE;
public static String IMAGE;
public static String INFORMATION;
public static String INITIAL_ROTATION_NODE;
public static String INITIAL_TRANSLATION_NODE;
public static String JAVA_AWT_COLOR_NAMESPACE;
public static String LENGTH;
public static String LINE;
public static String LINE_HEIGHT_RATIO;
public static String LINE_WIDTH;
public static String NAME_NODE;
public static String NETWORK_ADAPTER_NODE;
public static String RADIUS;
public static String ROTATE_WITH_HANDLE_NODE;
public static String SHAPE_NODE;
public static String SPIN_ON_CORONA_CENTRE_NODE;
public static String START;
public static String TEXT;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);