summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Clayton <alex.clayton@isode.com>2016-02-26 16:51:13 (GMT)
committerAlex Clayton <alex.clayton@isode.com>2016-03-09 16:44:17 (GMT)
commit8f112a856705b800d1a8797bec5d9396a9c00b34 (patch)
tree6468ad42172e1d1f68138a26679dbaf85b58e6b9
parent8fe752626726ca8d058ce437127a37d5d738a5eb (diff)
downloadstroke-8f112a856705b800d1a8797bec5d9396a9c00b34.zip
stroke-8f112a856705b800d1a8797bec5d9396a9c00b34.tar.bz2
Add Whiteboard Functionality
Add the Whiteboard classes to stroke. Test-information: Unit tests all pass. Change-Id: Id409c09d0fc1f82864e5d706c413b9d984a7db82
-rw-r--r--PortingProgress.txt16
-rw-r--r--src/com/isode/stroke/elements/DiscoInfo.java3
-rw-r--r--src/com/isode/stroke/elements/WhiteboardColor.java69
-rw-r--r--src/com/isode/stroke/elements/WhiteboardDeleteOperation.java33
-rw-r--r--src/com/isode/stroke/elements/WhiteboardElement.java30
-rw-r--r--src/com/isode/stroke/elements/WhiteboardElementVisitor.java21
-rw-r--r--src/com/isode/stroke/elements/WhiteboardEllipseElement.java71
-rw-r--r--src/com/isode/stroke/elements/WhiteboardFreehandPathElement.java65
-rw-r--r--src/com/isode/stroke/elements/WhiteboardInsertOperation.java33
-rw-r--r--src/com/isode/stroke/elements/WhiteboardLineElement.java64
-rw-r--r--src/com/isode/stroke/elements/WhiteboardOperation.java52
-rw-r--r--src/com/isode/stroke/elements/WhiteboardPayload.java67
-rw-r--r--src/com/isode/stroke/elements/WhiteboardPolygonElement.java70
-rw-r--r--src/com/isode/stroke/elements/WhiteboardRectElement.java71
-rw-r--r--src/com/isode/stroke/elements/WhiteboardTextElement.java61
-rw-r--r--src/com/isode/stroke/elements/WhiteboardUpdateOperation.java43
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java2
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/WhiteboardParser.java390
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/WhiteboardSerializer.java221
-rw-r--r--src/com/isode/stroke/whiteboard/IncomingWhiteboardSession.java60
-rw-r--r--src/com/isode/stroke/whiteboard/OutgoingWhiteboardSession.java76
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardClient.java149
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardResponder.java52
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardServer.java70
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardSession.java89
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardSessionManager.java169
-rw-r--r--src/com/isode/stroke/whiteboard/WhiteboardTransformer.java264
-rw-r--r--test/com/isode/stroke/whiteboard/WhiteboardClientTest.java672
-rw-r--r--test/com/isode/stroke/whiteboard/WhiteboardServerTest.java146
29 files changed, 3119 insertions, 10 deletions
diff --git a/PortingProgress.txt b/PortingProgress.txt
index 0dcb71d..fca63cd 100644
--- a/PortingProgress.txt
+++ b/PortingProgress.txt
@@ -1,6 +1,6 @@
Porting Progress from Swiften to Stroke.
This file indicates the porting progress from Swiften Library to Stroke. It indicates, upto which HEAD changes are ported to Stroke. Also indicates any remarks associated with it.
-To Be Ported: history, linklocal, WhiteBoard.
+To Be Ported: history, linklocal.
-----
Adhoc:
@@ -73,8 +73,6 @@ Elements:
All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 except for:
-WhiteBoard Functionalities -- Not Yet Ported!
-
Individual Comments:
ToplevelElement -- Not Required in Stroke.Element does the work!
@@ -157,8 +155,7 @@ Parser:
All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 except for:
ExpatParser -- Not yet ported. Requires expact library.
-LibXMLParser -- Not yet ported. Requires libxml library.
-WhiteboardParser -- Not Yet Ported! Needs Whiteboard classes importing.
+LibXMLParser -- Not yet ported. Requires libxml library.
-----
Presence:
@@ -191,9 +188,7 @@ Windows Authenticator not needed!
-----
Serializer:
-All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39 except for:
-
-WhiteboardSerializer -- Not Yet Ported! Needs whiteboard classes importing.
+All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39.
-----
Session:
@@ -227,3 +222,8 @@ supported in java.
VCards:
All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39.
+
+-----
+Whiteboard:
+
+All files ported to 6ca201d0b48f4273e24dd7bff17c4a46eeaddf39.
diff --git a/src/com/isode/stroke/elements/DiscoInfo.java b/src/com/isode/stroke/elements/DiscoInfo.java
index 35786e7..78e43ff 100644
--- a/src/com/isode/stroke/elements/DiscoInfo.java
+++ b/src/com/isode/stroke/elements/DiscoInfo.java
@@ -3,7 +3,7 @@
* All rights reserved.
*/
/*
- * Copyright (c) 2010-2015, Isode Limited, London, England.
+ * Copyright (c) 2010-2016, Isode Limited, London, England.
* All rights reserved.
*/
package com.isode.stroke.elements;
@@ -33,6 +33,7 @@ public class DiscoInfo extends Payload {
public static final String JingleTransportsS5BFeature = "urn:xmpp:jingle:transports:s5b:1";
public static final String Bytestream = "http://jabber.org/protocol/bytestreams";
public static final String MessageDeliveryReceiptsFeature = "urn:xmpp:receipts";
+ public static final String WhiteboardFeature = "http://swift.im/whiteboard";
public static class Identity implements Comparable<Identity> {
private final String name_;
diff --git a/src/com/isode/stroke/elements/WhiteboardColor.java b/src/com/isode/stroke/elements/WhiteboardColor.java
new file mode 100644
index 0000000..f40f618
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardColor.java
@@ -0,0 +1,69 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardColor {
+
+ private final int red_, green_, blue_;
+ private int alpha_;
+
+ public WhiteboardColor() {
+ this(0,0,0,255);
+ }
+
+ public WhiteboardColor(int red,int green,int blue) {
+ this(red,green,blue,255);
+ }
+
+ public WhiteboardColor(int red,int green,int blue,int alpha) {
+ red_ = red;
+ green_ = green;
+ blue_ = blue;
+ alpha_ = alpha;
+ }
+
+ public WhiteboardColor(String hex) {
+ alpha_ = 255;
+ int value = Integer.parseInt(hex.substring(1));
+ red_ = (value >> 16) & 0xFF;
+ green_ = (value >> 8) & 0xFF;
+ blue_ = value & 0xFF;
+ }
+
+ public String toHex() {
+ int value = (red_ << 16) + (green_ << 8) + blue_;
+ StringBuilder builder = new StringBuilder(Integer.toHexString(value));
+ while (builder.length() < 6) {
+ builder.insert(0, '0');
+ }
+ builder.insert(0, '#');
+ return builder.toString();
+ }
+
+ public int getRed() {
+ return red_;
+ }
+
+ public int getGreen() {
+ return green_;
+ }
+
+ public int getBlue() {
+ return blue_;
+ }
+
+ public int getAlpha() {
+ return alpha_;
+ }
+
+ public void setAlpha(int alpha) {
+ alpha_ = alpha;
+ }
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardDeleteOperation.java b/src/com/isode/stroke/elements/WhiteboardDeleteOperation.java
new file mode 100644
index 0000000..87ab226
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardDeleteOperation.java
@@ -0,0 +1,33 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardDeleteOperation extends WhiteboardOperation {
+
+ private String elementID_ = "";
+
+ public WhiteboardDeleteOperation() {
+ // Empty Constructor
+ }
+
+ public WhiteboardDeleteOperation(WhiteboardDeleteOperation other) {
+ super(other);
+ this.elementID_ = other.elementID_;
+ }
+
+ public String getElementID() {
+ return elementID_;
+ }
+
+ public void setElementID(String id) {
+ elementID_ = id;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardElement.java b/src/com/isode/stroke/elements/WhiteboardElement.java
new file mode 100644
index 0000000..5e2912a
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardElement.java
@@ -0,0 +1,30 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public abstract class WhiteboardElement {
+
+ private String id_ = "";
+
+ public WhiteboardElement() {
+ // Empty Constructor
+ }
+
+ public abstract void accept(WhiteboardElementVisitor visitor);
+
+ public final String getID() {
+ return id_;
+ }
+
+ public final void setID(String id) {
+ id_ = id;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardElementVisitor.java b/src/com/isode/stroke/elements/WhiteboardElementVisitor.java
new file mode 100644
index 0000000..0853373
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardElementVisitor.java
@@ -0,0 +1,21 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public interface WhiteboardElementVisitor {
+
+ public void visit(WhiteboardLineElement element);
+ public void visit(WhiteboardFreehandPathElement element);
+ public void visit(WhiteboardRectElement element);
+ public void visit(WhiteboardPolygonElement element);
+ public void visit(WhiteboardTextElement element);
+ public void visit(WhiteboardEllipseElement element);
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardEllipseElement.java b/src/com/isode/stroke/elements/WhiteboardEllipseElement.java
new file mode 100644
index 0000000..4cadc81
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardEllipseElement.java
@@ -0,0 +1,71 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardEllipseElement extends WhiteboardElement {
+
+ private final int cx_, cy_, rx_, ry_;
+ private WhiteboardColor penColor_;
+ private WhiteboardColor brushColor_;
+ private int penWidth_;
+
+ public WhiteboardEllipseElement(int cx, int cy, int rx, int ry) {
+ cx_ = cx;
+ cy_ = cy;
+ rx_ = rx;
+ ry_ = ry;
+ }
+
+ public int getCX() {
+ return cx_;
+ }
+
+ public int getCY() {
+ return cy_;
+ }
+
+ public int getRX() {
+ return rx_;
+ }
+
+ public int getRY() {
+ return ry_;
+ }
+
+ public WhiteboardColor getPenColor() {
+ return penColor_;
+ }
+
+ public void setPenColor(WhiteboardColor color) {
+ penColor_ = color;
+ }
+
+ public WhiteboardColor getBrushColor() {
+ return brushColor_;
+ }
+
+ public void setBrushColor(WhiteboardColor color) {
+ brushColor_ = color;
+ }
+
+ public int getPenWidth() {
+ return penWidth_;
+ }
+
+ public void setPenWidth(int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardFreehandPathElement.java b/src/com/isode/stroke/elements/WhiteboardFreehandPathElement.java
new file mode 100644
index 0000000..3885841
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardFreehandPathElement.java
@@ -0,0 +1,65 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class WhiteboardFreehandPathElement extends WhiteboardElement {
+
+ private List<Point> points_ = new ArrayList<Point>();
+ private WhiteboardColor color_ = new WhiteboardColor();
+ private int penWidth_ = 0;
+
+ public WhiteboardFreehandPathElement() {
+ // Empty Constructor
+ }
+
+ public void setPoints(Collection<? extends Point> points) {
+ points_.clear();
+ points_.addAll(points_);
+ }
+
+ public List<Point> getPoints() {
+ return new ArrayList<Point>(points_);
+ }
+
+ public WhiteboardColor getColor() {
+ return color_;
+ }
+
+ public void setColor(WhiteboardColor color) {
+ color_ = color;
+ }
+
+ public int getPenWidth() {
+ return penWidth_;
+ }
+
+ public void setPenWidth(int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ public static class Point {
+ public final int x;
+ public final int y;
+ public Point(int x,int y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardInsertOperation.java b/src/com/isode/stroke/elements/WhiteboardInsertOperation.java
new file mode 100644
index 0000000..b4c9722
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardInsertOperation.java
@@ -0,0 +1,33 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardInsertOperation extends WhiteboardOperation {
+
+ private WhiteboardElement element_;
+
+ public WhiteboardInsertOperation() {
+ // Empty Constructor
+ }
+
+ public WhiteboardInsertOperation(WhiteboardInsertOperation other) {
+ super(other);
+ this.element_ = other.element_;
+ }
+
+ public WhiteboardElement getElement() {
+ return element_;
+ }
+
+ public void setElement(WhiteboardElement element) {
+ element_ = element;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardLineElement.java b/src/com/isode/stroke/elements/WhiteboardLineElement.java
new file mode 100644
index 0000000..df4ab9b
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardLineElement.java
@@ -0,0 +1,64 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardLineElement extends WhiteboardElement {
+
+ private final int x1_, x2_, y1_, y2_;
+
+ private WhiteboardColor color_ = new WhiteboardColor();
+
+ private int penWidth_ = 1;
+
+ public WhiteboardLineElement(int x1,int y1,int x2,int y2) {
+ x1_ = x1;
+ y1_ = y1;
+ x2_ = x2;
+ y2_ = y2;
+ }
+
+ public int x1() {
+ return x1_;
+ }
+
+ public int x2() {
+ return x2_;
+ }
+
+ public int y1() {
+ return y1_;
+ }
+
+ public int y2() {
+ return y2_;
+ }
+
+ public WhiteboardColor getColor() {
+ return color_;
+ }
+
+ public void setColor(WhiteboardColor color) {
+ color_ = color;
+ }
+
+ public int getPenWidth() {
+ return penWidth_;
+ }
+
+ public void setPenWidth(int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardOperation.java b/src/com/isode/stroke/elements/WhiteboardOperation.java
new file mode 100644
index 0000000..d980f7c
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardOperation.java
@@ -0,0 +1,52 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardOperation {
+
+ private String id_ = "";
+ private String parentID_ = "";
+ private int pos_ = 0;
+
+ public WhiteboardOperation() {
+ // Empty Constructor
+ }
+
+ public WhiteboardOperation(WhiteboardOperation other) {
+ this.id_ = other.id_;
+ this.parentID_ = other.parentID_;
+ this.pos_ = other.pos_;
+ }
+
+ public String getID() {
+ return id_;
+ }
+
+ public void setID(String id) {
+ id_ = id;
+ }
+
+ public String getParentID() {
+ return parentID_;
+ }
+
+ public void setParentID(String parentID) {
+ parentID_ = parentID;
+ }
+
+ public int getPos() {
+ return pos_;
+ }
+
+ public void setPos(int pos) {
+ pos_ = pos;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardPayload.java b/src/com/isode/stroke/elements/WhiteboardPayload.java
new file mode 100644
index 0000000..337f6bb
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardPayload.java
@@ -0,0 +1,67 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardPayload extends Payload {
+
+ private String data_;
+ private Type type_;
+ private WhiteboardElement element_;
+ private WhiteboardOperation operation_;
+
+ public enum Type {
+ UnknownType,
+ Data,
+ SessionRequest,
+ SessionAccept,
+ SessionTerminate;
+ }
+
+ public WhiteboardPayload() {
+ this(Type.Data);
+ }
+
+ public WhiteboardPayload(Type type) {
+ type_ = type;
+ }
+
+ public void setData(String data) {
+ data_ = data;
+ }
+
+ public String getData() {
+ return data_;
+ }
+
+ public Type getType() {
+ return type_;
+ }
+
+ public void setType(Type type) {
+ type_ = type;
+ }
+
+ public WhiteboardElement getElement() {
+ return element_;
+ }
+
+ public void setElement(WhiteboardElement element) {
+ element_ = element;
+ }
+
+ public WhiteboardOperation getOperation() {
+ return operation_;
+ }
+
+ public void setOperation(WhiteboardOperation operation) {
+ operation_ = operation;
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardPolygonElement.java b/src/com/isode/stroke/elements/WhiteboardPolygonElement.java
new file mode 100644
index 0000000..3beb1c7
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardPolygonElement.java
@@ -0,0 +1,70 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class WhiteboardPolygonElement extends WhiteboardElement {
+
+ private final List<Point> points_ = new ArrayList<Point>();
+ private WhiteboardColor penColor_;
+ private WhiteboardColor brushColor_;
+ private int penWidth_ = 0;
+
+ public List<Point> getPoints() {
+ return new ArrayList<Point>(points_);
+ }
+
+ public void setPoints(Collection<? extends Point> points) {
+ points_.clear();
+ points_.addAll(points);
+ }
+
+ public WhiteboardColor getPenColor() {
+ return penColor_;
+ }
+
+ public void setPenColor(WhiteboardColor color) {
+ penColor_ = color;
+ }
+
+ public WhiteboardColor getBrushColor() {
+ return brushColor_;
+ }
+
+ public void setBrushColor(WhiteboardColor color) {
+ brushColor_ = color;
+ }
+
+ public int getPenWidth() {
+ return penWidth_;
+ }
+
+ public void setPenWidth(int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ public static class Point {
+ public final int x;
+ public final int y;
+ public Point(int x,int y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardRectElement.java b/src/com/isode/stroke/elements/WhiteboardRectElement.java
new file mode 100644
index 0000000..205c8c1
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardRectElement.java
@@ -0,0 +1,71 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardRectElement extends WhiteboardElement {
+
+ private int x_, y_, width_, height_;
+ private WhiteboardColor penColor_;
+ private WhiteboardColor brushColor_;
+ private int penWidth_ = 1;
+
+ public WhiteboardRectElement(int x, int y, int width, int height) {
+ x_ = x;
+ y_ = y;
+ width_ = width;
+ height_ = height;
+ }
+
+ public int getX() {
+ return x_;
+ }
+
+ public int getY() {
+ return y_;
+ }
+
+ public int getWidth() {
+ return width_;
+ }
+
+ public int getHeight() {
+ return height_;
+ }
+
+ public WhiteboardColor getPenColor() {
+ return penColor_;
+ }
+
+ public void setPenColor(WhiteboardColor color) {
+ penColor_ = color;
+ }
+
+ public WhiteboardColor getBrushColor() {
+ return brushColor_;
+ }
+
+ public void setBrushColor(WhiteboardColor color) {
+ brushColor_ = color;
+ }
+
+ public int getPenWidth() {
+ return penWidth_;
+ }
+
+ public void setPenWidth(int penWidth) {
+ penWidth_ = penWidth;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardTextElement.java b/src/com/isode/stroke/elements/WhiteboardTextElement.java
new file mode 100644
index 0000000..487aec0
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardTextElement.java
@@ -0,0 +1,61 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardTextElement extends WhiteboardElement {
+
+ private final int x_, y_;
+ private int size_;
+ private String text_;
+ private WhiteboardColor color_;
+
+ public WhiteboardTextElement(int x, int y) {
+ x_ = x;
+ y_ = y;
+ }
+
+ public void setText(String text) {
+ text_ = text;
+ }
+
+ public String getText() {
+ return text_;
+ }
+
+ public int getX() {
+ return x_;
+ }
+
+ public int getY() {
+ return y_;
+ }
+
+ public WhiteboardColor getColor() {
+ return color_;
+ }
+
+ public void setColor(WhiteboardColor color) {
+ color_ = color;
+ }
+
+ public int getSize() {
+ return size_;
+ }
+
+ public void setSize(int size) {
+ size_ = size;
+ }
+
+ @Override
+ public void accept(WhiteboardElementVisitor visitor) {
+ visitor.visit(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/elements/WhiteboardUpdateOperation.java b/src/com/isode/stroke/elements/WhiteboardUpdateOperation.java
new file mode 100644
index 0000000..9ca5e32
--- /dev/null
+++ b/src/com/isode/stroke/elements/WhiteboardUpdateOperation.java
@@ -0,0 +1,43 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.elements;
+
+public class WhiteboardUpdateOperation extends WhiteboardOperation {
+
+ private WhiteboardElement element_;
+ private int newPos_ = 0;
+
+ public WhiteboardUpdateOperation() {
+ // Empty Constructor
+ }
+
+ public WhiteboardUpdateOperation(WhiteboardUpdateOperation other) {
+ super(other);
+ this.element_ = other.element_;
+ this.newPos_ = other.newPos_;
+ }
+
+ public WhiteboardElement getElement() {
+ return element_;
+ }
+
+ public void setElement(WhiteboardElement element) {
+ element_ = element;
+ }
+
+ public int getNewPos() {
+ return newPos_;
+ }
+
+ public void setNewPos(int newPos) {
+ newPos_ = newPos;
+ }
+
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
index 39dd9f7..e8d9898 100644
--- a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
+++ b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
@@ -64,7 +64,7 @@ public class FullPayloadParserFactoryCollection extends PayloadParserFactoryColl
addFactory(new GenericPayloadParserFactory<JingleFileTransferFileInfoParser>("file", JingleFileTransferFileInfoParser.class));
addFactory(new GenericPayloadParserFactory<JingleFileTransferHashParser>("checksum", JingleFileTransferHashParser.class));
addFactory(new GenericPayloadParserFactory<S5BProxyRequestParser>("query", "http://jabber.org/protocol/bytestreams",S5BProxyRequestParser.class));
- // addFactory(new GenericPayloadParserFactory<WhiteboardParser>("wb","http://swift.im/whiteboard",WhiteboardParser.class));
+ addFactory(new GenericPayloadParserFactory<WhiteboardParser>("wb","http://swift.im/whiteboard",WhiteboardParser.class));
addFactory(new GenericPayloadParserFactory<UserLocationParser>("geoloc", "http://jabber.org/protocol/geoloc", UserLocationParser.class));
addFactory(new GenericPayloadParserFactory<UserTuneParser>("tune", "http://jabber.org/protocol/tune", UserTuneParser.class));
addFactory(new DeliveryReceiptParserFactory());
diff --git a/src/com/isode/stroke/parser/payloadparsers/WhiteboardParser.java b/src/com/isode/stroke/parser/payloadparsers/WhiteboardParser.java
new file mode 100644
index 0000000..3cbe8d8
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/WhiteboardParser.java
@@ -0,0 +1,390 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.parser.payloadparsers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.isode.stroke.elements.WhiteboardColor;
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardElement;
+import com.isode.stroke.elements.WhiteboardEllipseElement;
+import com.isode.stroke.elements.WhiteboardFreehandPathElement;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardLineElement;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.elements.WhiteboardPolygonElement;
+import com.isode.stroke.elements.WhiteboardRectElement;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+import com.isode.stroke.elements.WhiteboardPayload.Type;
+import com.isode.stroke.elements.WhiteboardTextElement;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+public class WhiteboardParser extends GenericPayloadParser<WhiteboardPayload> {
+
+ private boolean actualIsText;
+ private int level_;
+ private String data_;
+ private WhiteboardElement wbElement;
+ private WhiteboardOperation operation;
+
+ public WhiteboardParser() {
+ super(new WhiteboardPayload());
+ }
+
+ @Override
+ public void handleStartElement(String element, String ns,
+ AttributeMap attributes) {
+ if (level_ == 0) {
+ getPayloadInternal().setType(stringToType(getAttributeOr(attributes, "type", "")));
+ }
+ else if (level_ == 1) {
+ String type = getAttributeOr(attributes, "type", "");
+ if (type.equals("insert")) {
+ operation = new WhiteboardInsertOperation();
+ }
+ else if (type.equals("update")) {
+ WhiteboardUpdateOperation updateOp = new WhiteboardUpdateOperation();
+ String move = getAttributeOr(attributes, "newpos", "0");
+ updateOp.setNewPos(Integer.parseInt(move));
+ operation = updateOp;
+ }
+ else if (type.equals("delete")) {
+ WhiteboardDeleteOperation deleteOp = new WhiteboardDeleteOperation();
+ deleteOp.setElementID(getAttributeOr(attributes, "elementid", ""));
+ operation = deleteOp;
+ }
+ if (operation != null) {
+ operation.setID(getAttributeOr(attributes, "id", ""));
+ operation.setParentID(getAttributeOr(attributes, "parentid", ""));
+ try {
+ operation.setPos(getIntAttribute(attributes, "pos", 0));
+ } catch (NumberFormatException e) {
+ // Dont set pos
+ }
+ }
+
+ }
+ else if (level_ == 2) {
+ if ("line".equals(element)) {
+ int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+ try {
+ x1 = getIntAttribute(attributes, "x1", 0);
+ y1 = getIntAttribute(attributes, "y1", 0);
+ x2 = getIntAttribute(attributes, "x2", 0);
+ y2 = getIntAttribute(attributes, "y2", 0);
+ } catch (NumberFormatException e) {
+ }
+ WhiteboardLineElement whiteboardElement = new WhiteboardLineElement(x1, y1, x2, y2);
+
+ WhiteboardColor color = new WhiteboardColor(getAttributeOr(attributes, "stroke", "#000000"));
+ color.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ whiteboardElement.setColor(color);
+
+ int penWidth = 1;
+ try {
+ penWidth = getIntAttribute(attributes, "stroke-width", 1);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+
+ whiteboardElement.setPenWidth(penWidth);
+ whiteboardElement.setID(getAttributeOr(attributes,"id",""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ else if ("path".equals(element)) {
+ WhiteboardFreehandPathElement whiteboardElement = new WhiteboardFreehandPathElement();
+ String pathData = getAttributeOr(attributes, "d", "");
+ List<WhiteboardFreehandPathElement.Point> points =
+ new ArrayList<WhiteboardFreehandPathElement.Point>();
+ if (!pathData.isEmpty() && pathData.charAt(0) == 'M') {
+ try {
+ int pos = 1, npos;
+ int x, y;
+ if (pathData.charAt(pos) == ' ') {
+ pos++;
+ }
+ npos = pathData.indexOf(' ',pos);
+ x = Integer.parseInt(pathData.substring(pos, npos));
+ pos = npos+1;
+ npos = pathData.indexOf('L',pos);
+ y = Integer.parseInt(pathData.substring(pos,npos));
+ pos = npos+1;
+ if (pos < pathData.length() && pathData.charAt(pos) == ' ') {
+ pos++;
+ }
+ points.add(new WhiteboardFreehandPathElement.Point(x,y));
+ while (pos < pathData.length()) {
+ npos = pathData.indexOf(' ',pos);
+ x = Integer.parseInt(pathData.substring(pos, npos));
+ pos = npos+1;
+ npos = pathData.indexOf(' ',pos);
+ y = Integer.parseInt(pathData.substring(pos, npos));
+ pos = npos+1;
+ points.add(new WhiteboardFreehandPathElement.Point(x,y));
+ }
+ }
+ catch (NumberFormatException e) {
+ // Empty catch
+ }
+ }
+ whiteboardElement.setPoints(points);
+
+ int penWidth = 1;
+ try {
+ penWidth = getIntAttribute(attributes, "stroke-width", 1);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+ whiteboardElement.setPenWidth(penWidth);
+
+ WhiteboardColor color = new WhiteboardColor(getAttributeOr(attributes, "stroke", "#000000"));
+ color.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ whiteboardElement.setColor(color);
+ whiteboardElement.setID(getAttributeOr(attributes,"id",""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ else if ("rect".equals(element)) {
+ int x = 0, y = 0, width = 0, height = 0;
+ try {
+ x = getIntAttribute(attributes, "x", 0);
+ y = getIntAttribute(attributes, "y", 0);
+ width = getIntAttribute(attributes, "width", 0);
+ height = getIntAttribute(attributes, "height", 0);
+ } catch (Exception e) {
+ // Empty Catch
+ }
+
+ WhiteboardRectElement whiteboardElement = new WhiteboardRectElement(x,y,width,height);
+
+ int penWidth = 1;
+ try {
+ penWidth = getIntAttribute(attributes, "stroke-width", 1);
+ } catch (NumberFormatException e) {
+ // Empty Catch Block
+ }
+ whiteboardElement.setPenWidth(penWidth);
+
+ WhiteboardColor penColor = new WhiteboardColor(getAttributeOr(attributes, "stroke", "#000000"));
+ WhiteboardColor brushColor = new WhiteboardColor(getAttributeOr(attributes, "fill", "#000000"));
+ penColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ brushColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "fill-opacity", "1")));
+ whiteboardElement.setPenColor(penColor);
+ whiteboardElement.setBrushColor(brushColor);;
+ whiteboardElement.setID(getAttributeOr(attributes, "id", ""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ else if ("polygon".equals(element)) {
+ WhiteboardPolygonElement whiteboardElement = new WhiteboardPolygonElement();
+
+ String pointsData = getAttributeOr(attributes, "points", "");
+ List<WhiteboardPolygonElement.Point> points = new ArrayList<WhiteboardPolygonElement.Point>();
+ int pos = 0;
+ int npos;
+ int x,y;
+ try {
+ while (pos < pointsData.length()) {
+ npos = pointsData.indexOf(',', pos);
+ if (npos == -1) {
+ break;
+ }
+ x = Integer.parseInt(pointsData.substring(pos, npos));
+ pos = npos+1;
+ npos = pointsData.indexOf(' ',pos);
+ if (npos == -1) {
+ npos = pointsData.length();
+ }
+ y = Integer.parseInt(pointsData.substring(pos,npos));
+ pos = npos+1;
+ points.add(new WhiteboardPolygonElement.Point(x,y));
+ }
+ } catch (NumberFormatException e) {
+ // Empty catch
+ }
+
+ whiteboardElement.setPoints(points);
+
+ int penWidth = 0;
+ try {
+ penWidth = getIntAttribute(attributes, "stroke-width", 1);
+ } catch (Exception e) {
+ // Empty catch
+ }
+
+ WhiteboardColor penColor = new WhiteboardColor(getAttributeOr(attributes, "stroke", "#000000"));
+ WhiteboardColor brushColor = new WhiteboardColor(getAttributeOr(attributes, "fill", "#000000"));
+ penColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ brushColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "fill-opacity", "1")));
+
+ whiteboardElement.setPenColor(penColor);
+ whiteboardElement.setBrushColor(brushColor);
+ whiteboardElement.setID(getAttributeOr(attributes, "id", ""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ else if ("text".equals(element)) {
+ int x = 0, y = 0;
+ try {
+ x = getIntAttribute(attributes, "x", 0);
+ y = getIntAttribute(attributes, "y", 0);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+
+ WhiteboardTextElement whiteboardElement = new WhiteboardTextElement(x, y);
+
+ actualIsText = true;
+ WhiteboardColor color = new WhiteboardColor(getAttributeOr(attributes, "fill", "#000000"));
+ color.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ whiteboardElement.setColor(color);
+
+ int fontSize = 1;
+ try {
+ fontSize = getIntAttribute(attributes, "font-size", 12);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+
+ whiteboardElement.setSize(fontSize);
+ whiteboardElement.setID(getAttributeOr(attributes, "id", ""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ else if ("ellipse".equals(element)) {
+ int cx = 0, cy = 0, rx = 0, ry = 0;
+ try {
+ cx = getIntAttribute(attributes, "cx", 0);
+ cy = getIntAttribute(attributes, "cy", 0);
+ rx = getIntAttribute(attributes, "rx", 0);
+ ry = getIntAttribute(attributes, "ry", 0);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+
+ WhiteboardEllipseElement whiteboardElement = new WhiteboardEllipseElement(cx, cy, rx, ry);
+
+ int penWidth = 1;
+ try {
+ penWidth = getIntAttribute(attributes, "stroke-width", 1);
+ } catch (NumberFormatException e) {
+ // Empty Catch
+ }
+ whiteboardElement.setPenWidth(penWidth);
+
+ WhiteboardColor penColor = new WhiteboardColor(getAttributeOr(attributes, "stroke", "#000000"));
+ WhiteboardColor brushColor = new WhiteboardColor(getAttributeOr(attributes,"fill","#000000"));
+ penColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "opacity", "1")));
+ brushColor.setAlpha(opacityToAlpha(getAttributeOr(attributes, "fill-opacity", "1")));
+ whiteboardElement.setPenColor(penColor);
+ whiteboardElement.setBrushColor(brushColor);
+ whiteboardElement.setID(getAttributeOr(attributes, "id", ""));
+ getPayloadInternal().setElement(whiteboardElement);
+ wbElement = whiteboardElement;
+ }
+ }
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ if (level_ == 0) {
+ getPayloadInternal().setData(data_);
+ } else if (level_ == 1) {
+ if (operation instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation insertOp = (WhiteboardInsertOperation) operation;
+ insertOp.setElement(wbElement);
+ }
+ if (operation instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation updateOp = (WhiteboardUpdateOperation) operation;
+ updateOp.setElement(wbElement);
+ }
+ getPayloadInternal().setOperation(operation);
+ } else if (level_ == 2) {
+ if (element == "text") {
+ actualIsText = false;
+ }
+ }
+
+ }
+
+ @Override
+ public void handleCharacterData(String data) {
+ if (level_ == 3 && actualIsText) {
+ WhiteboardTextElement element = (WhiteboardTextElement) getPayloadInternal().getElement();
+ element.setText(data);
+ }
+ }
+
+ private WhiteboardPayload.Type stringToType(String type) {
+ if (type == "data") {
+ return Type.Data;
+ } else if (type == "session-request") {
+ return Type.SessionRequest;
+ } else if (type == "session-accept") {
+ return Type.SessionAccept;
+ } else if (type == "session-terminate") {
+ return Type.SessionTerminate;
+ } else {
+ return Type.UnknownType;
+ }
+ }
+
+ private int opacityToAlpha(String opacity) {
+ int value = 255;
+ int location = opacity.indexOf('.');
+ if (location != -1 && opacity.length() > (3+location)) {
+ String stringValue = opacity.substring(location+1,location+3);
+ try {
+ value = Integer.parseInt(stringValue)*255/100;
+ } catch (NumberFormatException nfe) {
+ value = 255;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Gets the given attribute from a {@link AttributeMap} if it is set and none
+ * {@code null}, otherwise returns a default value.
+ * @param attributeMap An {@link AttributeMap}
+ * @param attribute The name of the attribute to get from the map.
+ * @param defaultValue Default value to return if the attribute is not set
+ * (or is set to {@code null}) in the {@link AttributeMap}
+ * @return The value of the attribute in the {@link AttributeMap} if it is
+ * none {@code null} or {@code defaultValue}
+ */
+ private String getAttributeOr(AttributeMap attributeMap,String attribute,String defaultValue) {
+ String value = attributeMap.getAttribute(attribute);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * Gets an int value for a given attribute in an attirbute map, or a default value
+ * if that attribute is not set.
+ * @param attributeMap An {@link AttributeMap}
+ * @param attribute The name of the attribute to get from the map
+ * @param defaultValue The default value to return if the attribute is not set.
+ * @throws NumberFormatException if the attribute value can not be passed into an integer.
+ * @return The value of the attribute as an int or defaultValue if it was not set.
+ */
+ private int getIntAttribute(AttributeMap attributeMap,String attribute,int defaultValue) throws NumberFormatException {
+ String stringValue = getAttributeOr(attributeMap, attribute, String.valueOf(defaultValue));
+ return Integer.parseInt(stringValue);
+ }
+
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/WhiteboardSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/WhiteboardSerializer.java
new file mode 100644
index 0000000..b0f84f5
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/WhiteboardSerializer.java
@@ -0,0 +1,221 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.serializer.payloadserializers;
+
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardElementVisitor;
+import com.isode.stroke.elements.WhiteboardEllipseElement;
+import com.isode.stroke.elements.WhiteboardFreehandPathElement;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardLineElement;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.elements.WhiteboardPayload.Type;
+import com.isode.stroke.elements.WhiteboardPolygonElement;
+import com.isode.stroke.elements.WhiteboardRectElement;
+import com.isode.stroke.elements.WhiteboardTextElement;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+public class WhiteboardSerializer extends GenericPayloadSerializer<WhiteboardPayload> {
+
+ private static class WhiteboardElementSerializingVisitor implements WhiteboardElementVisitor {
+
+ private XMLElement element;
+
+ @Override
+ public void visit(WhiteboardLineElement line) {
+ element = new XMLElement("line");
+ element.setAttribute("x1", String.valueOf(line.x1()));
+ element.setAttribute("y1", String.valueOf(line.y1()));
+ element.setAttribute("x2", String.valueOf(line.x2()));
+ element.setAttribute("y2", String.valueOf(line.y2()));
+ element.setAttribute("id", line.getID());
+ element.setAttribute("stroke", line.getColor().toHex());
+ element.setAttribute("stroke-width", String.valueOf(line.getPenWidth()));
+ element.setAttribute("opacity", alphaToOpacity(line.getColor().getAlpha()));
+ }
+
+ @Override
+ public void visit(WhiteboardFreehandPathElement path) {
+ element = new XMLElement("path");
+ element.setAttribute("id", path.getID());
+ element.setAttribute("stroke", path.getColor().toHex());
+ element.setAttribute("stroke-width", String.valueOf(path.getPenWidth()));
+ element.setAttribute("opacity", alphaToOpacity(path.getColor().getAlpha()));
+ StringBuilder pathDataBuilder = new StringBuilder();
+ if (!path.getPoints().isEmpty()) {
+ Iterator<WhiteboardFreehandPathElement.Point> it = path.getPoints().iterator();
+ WhiteboardFreehandPathElement.Point point = it.next();
+ pathDataBuilder.append('M');
+ pathDataBuilder.append(point.x);
+ pathDataBuilder.append(' ');
+ pathDataBuilder.append(point.y);
+ pathDataBuilder.append('L');
+ while (it.hasNext()) {
+ point = it.next();
+ pathDataBuilder.append(point.x);
+ pathDataBuilder.append(' ');
+ pathDataBuilder.append(point.y);
+ pathDataBuilder.append(' ');
+ }
+ }
+ element.setAttribute("d", pathDataBuilder.toString());
+ }
+
+ @Override
+ public void visit(WhiteboardRectElement rect) {
+ element = new XMLElement("rect");
+ element.setAttribute("x", String.valueOf(rect.getX()));
+ element.setAttribute("y", String.valueOf(rect.getY()));
+ element.setAttribute("width", String.valueOf(rect.getWidth()));
+ element.setAttribute("height", String.valueOf(rect.getHeight()));
+ element.setAttribute("id", rect.getID());
+ element.setAttribute("stroke", rect.getPenColor().toHex());
+ element.setAttribute("fill", rect.getBrushColor().toHex());;
+ element.setAttribute("stroke-width", String.valueOf(rect.getPenWidth()));
+ element.setAttribute("opacity", alphaToOpacity(rect.getPenColor().getAlpha()));
+ element.setAttribute("fill-opacity", alphaToOpacity(rect.getBrushColor().getAlpha()));
+ }
+
+ @Override
+ public void visit(WhiteboardPolygonElement polygon) {
+ element = new XMLElement("polygon");
+ element.setAttribute("id", polygon.getID());
+ element.setAttribute("stroke", polygon.getPenColor().toHex());
+ element.setAttribute("fill", polygon.getBrushColor().toHex());;
+ element.setAttribute("stroke-width", String.valueOf(polygon.getPenWidth()));
+ element.setAttribute("opacity", alphaToOpacity(polygon.getPenColor().getAlpha()));
+ element.setAttribute("fill-opacity", alphaToOpacity(polygon.getBrushColor().getAlpha()));
+ StringBuilder points = new StringBuilder();
+ for (WhiteboardPolygonElement.Point point : polygon.getPoints()) {
+ points.append(point.x);
+ points.append(',');
+ points.append(point.y);
+ points.append(' ');
+ }
+ element.setAttribute("points", points.toString());
+
+ }
+
+ @Override
+ public void visit(WhiteboardTextElement text) {
+ element = new XMLElement("text");
+ element.setAttribute("x", String.valueOf(text.getX()));
+ element.setAttribute("y", String.valueOf(text.getY()));
+ element.setAttribute("font-size", String.valueOf(text.getSize()));
+ element.setAttribute("id", text.getID());
+ element.setAttribute("fill", text.getColor().toHex());
+ element.setAttribute("opacity", alphaToOpacity(text.getColor().getAlpha()));
+ element.addNode(new XMLTextNode(text.getText()));
+
+ }
+
+ @Override
+ public void visit(WhiteboardEllipseElement ellipse) {
+ element = new XMLElement("ellipse");
+ element.setAttribute("cx", String.valueOf(ellipse.getCX()));
+ element.setAttribute("cy", String.valueOf(ellipse.getCY()));
+ element.setAttribute("rx", String.valueOf(ellipse.getRX()));
+ element.setAttribute("ry", String.valueOf(ellipse.getRY()));
+ element.setAttribute("id", ellipse.getID());
+ element.setAttribute("stroke", ellipse.getPenColor().toHex());
+ element.setAttribute("fill", ellipse.getBrushColor().toHex());;
+ element.setAttribute("stroke-width", String.valueOf(ellipse.getPenWidth()));
+ element.setAttribute("opacity", alphaToOpacity(ellipse.getPenColor().getAlpha()));
+ element.setAttribute("fill-opacity", alphaToOpacity(ellipse.getBrushColor().getAlpha()));
+ }
+
+ public XMLElement getResult() {
+ return element;
+ }
+
+ private String alphaToOpacity(int alpha) {
+ int opacity = 100*alpha/254;
+ if (opacity == 100) {
+ return "1";
+ } else {
+ return String.format(".%d", opacity);
+ }
+ }
+
+ }
+
+ private final Logger logger = Logger.getLogger(this.getClass().getName());
+
+ public WhiteboardSerializer() {
+ super(WhiteboardPayload.class);
+ }
+
+ @Override
+ protected String serializePayload(WhiteboardPayload payload) {
+ XMLElement element = new XMLElement("wb","http://swift.im/whiteboard");
+ if (payload.getType() == Type.Data) {
+ XMLElement operationNode = new XMLElement("operation");
+ WhiteboardElementSerializingVisitor visitor = new WhiteboardElementSerializingVisitor();
+ WhiteboardOperation operation = payload.getOperation();
+ if (operation instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation insertOp = (WhiteboardInsertOperation) operation;
+ operationNode.setAttribute("type", "insert");
+ operationNode.setAttribute("pos", String.valueOf(insertOp.getPos()));
+ operationNode.setAttribute("id", insertOp.getID());
+ operationNode.setAttribute("parentid", insertOp.getParentID());
+ insertOp.getElement().accept(visitor);
+ operationNode.addNode(operationNode);
+ }
+ if (operation instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation updateOp = (WhiteboardUpdateOperation) operation;
+ operationNode.setAttribute("type", "update");
+ operationNode.setAttribute("pos", String.valueOf(updateOp.getPos()));
+ operationNode.setAttribute("id", updateOp.getID());
+ operationNode.setAttribute("parentid", updateOp.getParentID());
+ operationNode.setAttribute("newpos", String.valueOf(updateOp.getNewPos()));
+ updateOp.getElement().accept(visitor);
+ operationNode.addNode(visitor.getResult());
+ }
+ if (operation instanceof WhiteboardDeleteOperation) {
+ WhiteboardDeleteOperation deleteOp = new WhiteboardDeleteOperation();
+ operationNode.setAttribute("type", "delete");
+ operationNode.setAttribute("pos", String.valueOf(deleteOp.getPos()));
+ operationNode.setAttribute("id", deleteOp.getID());
+ operationNode.setAttribute("parentid", deleteOp.getParentID());
+ operationNode.setAttribute("elementid", deleteOp.getElementID());
+ }
+ element.addNode(operationNode);
+ }
+ element.setAttribute("type", typeToString(payload.getType()));
+ return element.serialize();
+ }
+
+ private String typeToString(Type type) {
+ switch (type) {
+ case Data:
+ return "data";
+ case SessionAccept:
+ return "session-accept";
+ case SessionRequest:
+ return "session-request";
+ case SessionTerminate:
+ return "session-terminate";
+ case UnknownType:
+ logger.warning("Warning: Serializing unknown action value.");
+ return "";
+ default:
+ assert(false);
+ return "";
+ }
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/IncomingWhiteboardSession.java b/src/com/isode/stroke/whiteboard/IncomingWhiteboardSession.java
new file mode 100644
index 0000000..0ed9a01
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/IncomingWhiteboardSession.java
@@ -0,0 +1,60 @@
+package com.isode.stroke.whiteboard;
+
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+
+public class IncomingWhiteboardSession extends WhiteboardSession {
+
+ private final WhiteboardClient client = new WhiteboardClient();
+
+ public IncomingWhiteboardSession(JID jid, IQRouter router) {
+ super(jid, router);
+ }
+
+ public void accept() {
+ WhiteboardPayload payload = new WhiteboardPayload(WhiteboardPayload.Type.SessionAccept);
+ GenericRequest<WhiteboardPayload> request =
+ new GenericRequest<WhiteboardPayload>(IQ.Type.Set, toJID_, payload, router_);
+ request.send();
+ onRequestAccepted.emit(toJID_);
+ }
+
+ @Override
+ public void sendOperation(WhiteboardOperation operation) {
+ operation.setID(idGenerator_.generateID());
+ operation.setParentID(lastOpID_);
+ lastOpID_ = operation.getID();
+
+ WhiteboardOperation result = client.handleLocalOperationReceived(operation);
+
+ if (result != null) {
+ WhiteboardPayload payload = new WhiteboardPayload();
+ payload.setOperation(result);
+ sendPayload(payload);
+ }
+ }
+
+ @Override
+ protected void handleIncomingOperation(WhiteboardOperation operation) {
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(operation);
+ if (pairResult.client != null) {
+ if (pairResult.client.getPos() != -1) {
+ onOperationReceived.emit(pairResult.client);
+ }
+ lastOpID_ = pairResult.client.getID();
+ }
+
+ if (pairResult.server != null) {
+ WhiteboardPayload payload = new WhiteboardPayload();
+ payload.setOperation(pairResult.server);
+ sendPayload(payload);
+ }
+ }
+
+
+
+}
diff --git a/src/com/isode/stroke/whiteboard/OutgoingWhiteboardSession.java b/src/com/isode/stroke/whiteboard/OutgoingWhiteboardSession.java
new file mode 100644
index 0000000..3a79a4c
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/OutgoingWhiteboardSession.java
@@ -0,0 +1,76 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Slot2;
+
+public class OutgoingWhiteboardSession extends WhiteboardSession {
+
+ private final WhiteboardServer server = new WhiteboardServer();
+
+ public OutgoingWhiteboardSession(JID jid, IQRouter router) {
+ super(jid, router);
+ }
+
+ public void startSession() {
+
+ WhiteboardPayload payload = new WhiteboardPayload(WhiteboardPayload.Type.SessionRequest);
+ GenericRequest<WhiteboardPayload> request =
+ new GenericRequest<WhiteboardPayload>(IQ.Type.Set, toJID_, payload, router_);
+ request.onResponse.connect(new Slot2<WhiteboardPayload, ErrorPayload>() {
+
+ @Override
+ public void call(WhiteboardPayload payload, ErrorPayload error) {
+ handleRequestResponse(payload, error);
+ }
+
+ });
+ request.send();
+ }
+
+ private void handleRequestResponse(WhiteboardPayload payload,ErrorPayload error) {
+ if (error != null) {
+ onRequestRejected.emit(toJID_);
+ }
+ }
+
+ @Override
+ public void sendOperation(WhiteboardOperation operation) {
+ operation.setID(idGenerator_.generateID());
+ operation.setParentID(lastOpID_);
+ lastOpID_ = operation.getID();
+
+ server.handleLocalOperationReceived(operation);
+ WhiteboardPayload payload = new WhiteboardPayload();
+ payload.setOperation(operation);
+ sendPayload(payload);
+ }
+
+ @Override
+ protected void handleIncomingOperation(WhiteboardOperation operation) {
+ WhiteboardOperation op = server.handleClientOperationReceived(operation);
+ if (op.getPos() != -1) {
+ onOperationReceived.emit(op);
+ }
+ lastOpID_ = op.getID();
+
+ WhiteboardPayload payload = new WhiteboardPayload();
+ payload.setOperation(op);
+ sendPayload(payload);
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardClient.java b/src/com/isode/stroke/whiteboard/WhiteboardClient.java
new file mode 100644
index 0000000..7b7bb10
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardClient.java
@@ -0,0 +1,149 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+import com.isode.stroke.whiteboard.WhiteboardTransformer.Pair;
+
+public class WhiteboardClient {
+
+ private final List<WhiteboardOperation> localOperations_ = new ArrayList<WhiteboardOperation>();
+ private final List<WhiteboardOperation> serverOperations_ = new ArrayList<WhiteboardOperation>();
+ private final List<WhiteboardOperation> bridge_ = new ArrayList<WhiteboardOperation>();
+ private String lastSentOperationID_ = "";
+
+ public static class Result {
+ public final WhiteboardOperation client;
+ public final WhiteboardOperation server;
+ public Result(WhiteboardOperation client,WhiteboardOperation server) {
+ this.client = client;
+ this.server = server;
+ }
+ }
+
+ public WhiteboardOperation handleLocalOperationReceived(WhiteboardOperation operation) {
+ localOperations_.add(operation);
+
+ WhiteboardOperation op = null;
+ if (operation instanceof WhiteboardInsertOperation) {
+ op = new WhiteboardInsertOperation((WhiteboardInsertOperation) operation);
+ }
+ else if (operation instanceof WhiteboardUpdateOperation) {
+ op = new WhiteboardUpdateOperation((WhiteboardUpdateOperation) operation);
+ }
+ else if (operation instanceof WhiteboardDeleteOperation) {
+ op = new WhiteboardDeleteOperation((WhiteboardDeleteOperation) operation);
+ }
+
+ if (!bridge_.isEmpty()) {
+ WhiteboardOperation back = bridge_.get(bridge_.size()-1);
+ op.setParentID(back.getID());
+ }
+ bridge_.add(op);
+
+ if (lastSentOperationID_.isEmpty()) {
+ if (operation instanceof WhiteboardInsertOperation) {
+ op = new WhiteboardInsertOperation((WhiteboardInsertOperation) operation);
+ }
+ else if (operation instanceof WhiteboardUpdateOperation) {
+ op = new WhiteboardUpdateOperation((WhiteboardUpdateOperation) operation);
+ }
+ else if (operation instanceof WhiteboardDeleteOperation) {
+ op = new WhiteboardDeleteOperation((WhiteboardDeleteOperation) operation);
+ }
+
+ if (!serverOperations_.isEmpty()) {
+ WhiteboardOperation back = serverOperations_.get(serverOperations_.size()-1);
+ op.setParentID(back.getID());
+ }
+ lastSentOperationID_ = operation.getID();
+ return op;
+ }
+ else{
+ return null;
+ }
+ }
+
+ public Result handleServerOperationReceived(WhiteboardOperation operation) {
+ serverOperations_.add(operation);
+ WhiteboardOperation clientResult = null, serverResult = null;
+ if (localOperations_.size() == (serverOperations_.size()-1) ) {
+ localOperations_.add(operation);
+ clientResult = operation;
+ }
+ else if (lastSentOperationID_ == operation.getID()) {
+ //Client received confirmation about own operation and it sends next operation to server
+ if (!bridge_.isEmpty() && lastSentOperationID_ .equals(bridge_.get(0).getID())) {
+ bridge_.remove(0);
+ }
+
+ if (!bridge_.isEmpty() && lastSentOperationID_ .equals(bridge_.get(0).getParentID())) {
+ lastSentOperationID_ = bridge_.get(0).getID();
+ serverResult = bridge_.get(0);
+ }
+ if (serverResult == null) {
+ lastSentOperationID_ = "";
+ }
+ }
+ else if (!bridge_.isEmpty()) {
+ WhiteboardOperation temp;
+ Pair opPair = WhiteboardTransformer.transform(bridge_.get(0), operation);
+ temp = opPair.first;
+
+ bridge_.set(0, opPair.second);
+ String previousID = bridge_.get(0).getID();
+ for (int i = 1; i < bridge_.size(); ++i) {
+ opPair = WhiteboardTransformer.transform(bridge_.get(i), temp);
+ temp = opPair.first;
+ bridge_.set(i, opPair.second);
+ bridge_.get(i).setParentID(previousID);
+ previousID = bridge_.get(i).getID();
+ }
+
+ WhiteboardOperation localBack = localOperations_.get(localOperations_.size()-1);
+ temp.setParentID(localBack.getID());
+ localOperations_.add(temp);
+ clientResult = temp;
+ }
+
+ return new Result(clientResult, serverResult);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Client\n");
+ for (WhiteboardOperation op : localOperations_) {
+ builder.append(op.getID());
+ builder.append(' ');
+ builder.append(op.getPos());
+ builder.append('\n');
+ }
+ builder.append("Server\n");
+ for (WhiteboardOperation op : serverOperations_) {
+ builder.append(op.getID());
+ builder.append(' ');
+ builder.append(op.getPos());
+ builder.append('\n');
+ }
+ return builder.toString();
+ }
+
+ public void print() {
+ System.out.println(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardResponder.java b/src/com/isode/stroke/whiteboard/WhiteboardResponder.java
new file mode 100644
index 0000000..6f80115
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardResponder.java
@@ -0,0 +1,52 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.queries.SetResponder;
+import com.isode.stroke.elements.ErrorPayload;
+
+public class WhiteboardResponder extends SetResponder<WhiteboardPayload> {
+
+ private final WhiteboardSessionManager sessionManager_;
+ private final IQRouter router_;
+
+ public WhiteboardResponder(WhiteboardSessionManager sessionManager,IQRouter router) {
+ super(new WhiteboardPayload(),router);
+ sessionManager_ = sessionManager;
+ router_ = router;
+ }
+
+ @Override
+ protected boolean handleSetRequest(JID from, JID to, String id,
+ WhiteboardPayload payload) {
+ if (payload.getType() == WhiteboardPayload.Type.SessionRequest) {
+ if (sessionManager_.getSession(from) != null) {
+ sendError(from, id, ErrorPayload.Condition.Conflict, ErrorPayload.Type.Cancel);
+ }
+ else {
+ sendResponse(from, id, null);
+ IncomingWhiteboardSession session = new IncomingWhiteboardSession(from, router_);
+ sessionManager_.handleIncomingSession(session);
+ }
+ }
+ else {
+ sendResponse(from, id, null);
+ WhiteboardSession session = sessionManager_.getSession(from);
+ if (session != null) {
+ session.handleIncomingAction(payload);
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardServer.java b/src/com/isode/stroke/whiteboard/WhiteboardServer.java
new file mode 100644
index 0000000..7685671
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardServer.java
@@ -0,0 +1,70 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.isode.stroke.elements.WhiteboardOperation;
+
+public class WhiteboardServer {
+
+ private final List<WhiteboardOperation> operations_ = new ArrayList<WhiteboardOperation>();
+
+ public void handleLocalOperationReceived(WhiteboardOperation operation) {
+ operations_.add(operation);
+ }
+
+ public WhiteboardOperation handleClientOperationReceived(WhiteboardOperation newOperation) {
+
+ if (operations_.isEmpty() ||
+ newOperation.getParentID().equals(operations_.get(operations_.size()-1).getID())) {
+ operations_.add(newOperation);
+ return newOperation;
+ }
+ for (int i = (operations_.size()-1); i >= 0; i--) {
+ WhiteboardOperation operation = operations_.get(i);
+ while (newOperation.getParentID().equals(operation.getParentID())) {
+ WhiteboardTransformer.Pair tResult =
+ WhiteboardTransformer.transform(newOperation, operation);
+ if (i == (operations_.size()-1)) {
+ operations_.add(tResult.second);
+ return tResult.second;
+ }
+ else {
+ newOperation = tResult.second;
+ i++;
+ operation = operations_.get(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Server:\n");
+ for (WhiteboardOperation op : operations_) {
+ builder.append(op.getID());
+ builder.append(" '");
+ builder.append(op.getParentID());
+ builder.append("' ");
+ builder.append(op.getPos());
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ public void print() {
+ System.out.println(this);
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardSession.java b/src/com/isode/stroke/whiteboard/WhiteboardSession.java
new file mode 100644
index 0000000..cc1957e
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardSession.java
@@ -0,0 +1,89 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import com.isode.stroke.base.IDGenerator;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.WhiteboardElement;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardPayload;
+import com.isode.stroke.elements.WhiteboardPayload.Type;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Signal1;
+
+public abstract class WhiteboardSession {
+
+ protected final JID toJID_;
+ protected final IQRouter router_;
+ protected String lastOpID_ = "";
+ protected final IDGenerator idGenerator_ = new IDGenerator();
+
+ public final Signal1<WhiteboardElement> onElementReceived = new Signal1<WhiteboardElement>();
+ public final Signal1<WhiteboardOperation> onOperationReceived = new Signal1<WhiteboardOperation>();
+ public final Signal1<JID> onSessionTerminated = new Signal1<JID>();
+ public final Signal1<JID> onRequestAccepted = new Signal1<JID>();
+ public final Signal1<JID> onRequestRejected = new Signal1<JID>();
+
+ public WhiteboardSession(JID jid,IQRouter router) {
+ toJID_ = jid;
+ router_ = router;
+ }
+
+ public void handleIncomingAction(WhiteboardPayload payload) {
+ switch(payload.getType()) {
+ case Data:
+ handleIncomingOperation(payload.getOperation());
+ return;
+ case SessionAccept:
+ onRequestAccepted.emit(toJID_);
+ return;
+ case SessionTerminate:
+ onSessionTerminated.emit(toJID_);
+ return;
+ case SessionRequest:
+ case UnknownType:
+ default:
+ return;
+ }
+ }
+ public void sendElement(WhiteboardElement element) {
+ WhiteboardPayload payload = new WhiteboardPayload();
+ payload.setElement(element);
+ GenericRequest<WhiteboardPayload> request =
+ new GenericRequest<WhiteboardPayload>(IQ.Type.Set, toJID_, payload, router_);
+ request.send();
+ }
+
+ public abstract void sendOperation(WhiteboardOperation operation);
+
+ public void cancel() {
+ if (router_.isAvailable()) {
+ WhiteboardPayload payload = new WhiteboardPayload(Type.SessionTerminate);
+ GenericRequest<WhiteboardPayload> request =
+ new GenericRequest<WhiteboardPayload>(IQ.Type.Set, toJID_, payload, router_);
+ request.send();
+ }
+ onSessionTerminated.emit(toJID_);
+ }
+
+ public JID getTo() {
+ return toJID_;
+ }
+
+ protected abstract void handleIncomingOperation(WhiteboardOperation operation);
+
+ protected final void sendPayload(WhiteboardPayload payload) {
+ GenericRequest<WhiteboardPayload> request = new GenericRequest<WhiteboardPayload>(IQ.Type.Set, toJID_, payload, router_);
+ request.send();
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardSessionManager.java b/src/com/isode/stroke/whiteboard/WhiteboardSessionManager.java
new file mode 100644
index 0000000..d797303
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardSessionManager.java
@@ -0,0 +1,169 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.disco.EntityCapsProvider;
+import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.presence.PresenceOracle;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Slot1;
+
+public class WhiteboardSessionManager {
+
+ private final Map<JID,WhiteboardSession> sessions_ = new HashMap<JID,WhiteboardSession>();
+ private final IQRouter router_;
+ private final StanzaChannel stanzaChannel_;
+ private final PresenceOracle presenceOracle_;
+ private final EntityCapsProvider capsProvider_;
+ private WhiteboardResponder responder;
+
+ public final Signal1<IncomingWhiteboardSession> onSessionRequest =
+ new Signal1<IncomingWhiteboardSession>();
+
+ public WhiteboardSessionManager(IQRouter router, StanzaChannel stanzaChannel,
+ PresenceOracle presenceOracle, EntityCapsProvider capsProvider) {
+ router_ = router;
+ stanzaChannel_ = stanzaChannel;
+ presenceOracle_ = presenceOracle;
+ capsProvider_ = capsProvider;
+ responder = new WhiteboardResponder(this, router_);
+ responder.start();
+ stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() {
+
+ @Override
+ public void call(Presence presence) {
+ handlePresenceReceived(presence);
+ }
+
+ });
+ stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() {
+
+ @Override
+ public void call(Boolean p1) {
+ handleAvailableChanged(p1.booleanValue());
+ }
+
+ });
+ }
+
+ // Unlike in C++ we can't put this in a destructor to automatically be called when object is
+ // destroyed. Must be called manually.
+ public void stop() {
+ responder.stop();
+ }
+
+ public WhiteboardSession getSession(JID to) {
+ return sessions_.get(to);
+ }
+
+ public WhiteboardSession requestSession(JID to) {
+ WhiteboardSession session = getSession(to);
+ if (session == null) {
+ OutgoingWhiteboardSession outgoingSession = createOutgoingSession(to);
+ outgoingSession.startSession();
+ return outgoingSession;
+ } else {
+ return session;
+ }
+ }
+
+ private JID getFullJID(JID bareJID) {
+ JID fullReceipientJID = null;
+ int priority = Integer.MIN_VALUE;
+
+ //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Remko Tronçon @ 11:11
+ List<Presence> presences =
+ new ArrayList<Presence>(presenceOracle_.getAllPresence(bareJID));
+
+ //iterate over them
+ for (Presence pres : presences) {
+ if (pres.getPriority() > priority) {
+ // look up caps from the jid
+ DiscoInfo info = capsProvider_.getCaps(pres.getFrom());
+ if (info != null && info.hasFeature(DiscoInfo.WhiteboardFeature)) {
+ priority = pres.getPriority();
+ fullReceipientJID = pres.getFrom();
+ }
+ }
+ }
+
+ return fullReceipientJID;
+ }
+
+ private OutgoingWhiteboardSession createOutgoingSession(JID to) {
+ JID fullJID = to;
+ if (fullJID.isBare()) {
+ fullJID = getFullJID(fullJID);
+ }
+ OutgoingWhiteboardSession session = new OutgoingWhiteboardSession(fullJID, router_);
+ sessions_.put(fullJID, session);
+ session.onSessionTerminated.connect(new Slot1<JID>() {
+
+ @Override
+ public void call(JID jid) {
+ deleteSessionEntry(jid);
+ }
+
+ });
+ session.onRequestRejected.connect(new Slot1<JID>() {
+
+ @Override
+ public void call(JID jid) {
+ deleteSessionEntry(jid);
+ }
+
+ });
+ return session;
+ }
+
+ public void handleIncomingSession(IncomingWhiteboardSession session) {
+ sessions_.put(session.getTo(), session);
+ session.onSessionTerminated.connect(new Slot1<JID>() {
+
+ @Override
+ public void call(JID jid) {
+ deleteSessionEntry(jid);
+ }
+
+ });
+ onSessionRequest.emit(session);
+ }
+
+ private void handlePresenceReceived(Presence presence) {
+ if (!presence.isAvailable()) {
+ WhiteboardSession session = getSession(presence.getFrom());
+ if (session != null) {
+ session.cancel();
+ }
+ }
+ }
+ private void handleAvailableChanged(boolean available) {
+ if (!available) {
+ Map<JID,WhiteboardSession> sessionsCopy = new HashMap<JID,WhiteboardSession>(sessions_);
+ for (WhiteboardSession session : sessionsCopy.values()) {
+ session.cancel();
+ }
+ }
+ }
+
+ private void deleteSessionEntry(JID contact) {
+ sessions_.remove(contact);
+ }
+
+}
diff --git a/src/com/isode/stroke/whiteboard/WhiteboardTransformer.java b/src/com/isode/stroke/whiteboard/WhiteboardTransformer.java
new file mode 100644
index 0000000..4d20498
--- /dev/null
+++ b/src/com/isode/stroke/whiteboard/WhiteboardTransformer.java
@@ -0,0 +1,264 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+
+public final class WhiteboardTransformer {
+
+ public static final class Pair {
+ public final WhiteboardOperation first;
+ public final WhiteboardOperation second;
+ public Pair(WhiteboardOperation first,WhiteboardOperation second) {
+ this.first = first;
+ this.second = second;
+ }
+ public Pair() {
+ this(new WhiteboardOperation(),new WhiteboardOperation());
+ }
+ }
+
+ private WhiteboardTransformer() {
+ // Static class so constructor is private
+ }
+
+ public static Pair transform(WhiteboardOperation clientOp,WhiteboardOperation serverOp) {
+ if (clientOp instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation clientInsert = (WhiteboardInsertOperation) clientOp;
+ if (serverOp instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation serverInsert = (WhiteboardInsertOperation) serverOp;
+ return transform(clientInsert, serverInsert);
+ }
+ else if (serverOp instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation serverUpdate = (WhiteboardUpdateOperation) serverOp;
+ return transform(clientInsert, serverUpdate);
+ }
+ else if (serverOp instanceof WhiteboardDeleteOperation) {
+ WhiteboardDeleteOperation serverDelete = (WhiteboardDeleteOperation) serverOp;
+ return transform(clientInsert, serverDelete);
+ }
+ }
+ else if (clientOp instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation clientUpdate = (WhiteboardUpdateOperation) clientOp;
+ if (serverOp instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation serverInsert = (WhiteboardInsertOperation) serverOp;
+ return transform(clientUpdate, serverInsert);
+ }
+ else if (serverOp instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation serverUpdate = (WhiteboardUpdateOperation) serverOp;
+ return transform(clientUpdate, serverUpdate);
+ }
+ else if (serverOp instanceof WhiteboardDeleteOperation) {
+ WhiteboardDeleteOperation serverDelete = (WhiteboardDeleteOperation) serverOp;
+ return transform(clientUpdate, serverDelete);
+ }
+ }
+ else if (clientOp instanceof WhiteboardDeleteOperation) {
+ WhiteboardDeleteOperation clientDelete = (WhiteboardDeleteOperation) clientOp;
+ if (serverOp instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation serverInsert = (WhiteboardInsertOperation) serverOp;
+ return transform(clientDelete, serverInsert);
+ }
+ else if (serverOp instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation serverUpdate = (WhiteboardUpdateOperation) serverOp;
+ return transform(clientDelete, serverUpdate);
+ }
+ else if (serverOp instanceof WhiteboardDeleteOperation) {
+ WhiteboardDeleteOperation serverDelete = (WhiteboardDeleteOperation) serverOp;
+ return transform(clientDelete, serverDelete);
+ }
+ }
+ return new Pair();
+ }
+
+ public static Pair transform(WhiteboardInsertOperation clientOp,WhiteboardInsertOperation serverOp) {
+ WhiteboardInsertOperation first = new WhiteboardInsertOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardInsertOperation second = new WhiteboardInsertOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (clientOp.getPos() <= serverOp.getPos()) {
+ first.setPos(first.getPos()+1);
+ }
+ else {
+ second.setPos(second.getPos()+1);
+ }
+ return new Pair(first,second);
+ }
+
+ public static Pair transform(WhiteboardUpdateOperation clientOp,WhiteboardUpdateOperation serverOp) {
+
+ WhiteboardUpdateOperation first = new WhiteboardUpdateOperation(serverOp);
+ first.setParentID(clientOp.getID());
+
+ WhiteboardUpdateOperation second;
+ if (clientOp.getPos() == serverOp.getPos()) {
+ second = new WhiteboardUpdateOperation(serverOp);
+ second.setID(clientOp.getID());
+ second.setParentID(serverOp.getID());
+ }
+ else {
+ second = new WhiteboardUpdateOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ }
+
+ if (clientOp.getPos() < serverOp.getPos() && clientOp.getNewPos() > serverOp.getPos()) {
+ first.setPos(first.getPos()-1);
+ if (clientOp.getNewPos() >= serverOp.getNewPos()) {
+ first.setNewPos(first.getNewPos()-1);
+ }
+ }
+ else if (clientOp.getNewPos() >= serverOp.getNewPos()) {
+ first.setNewPos(first.getNewPos()-1);
+ }
+ if (serverOp.getPos() < clientOp.getPos() && serverOp.getNewPos() > clientOp.getPos()) {
+ second.setPos(second.getPos()-1);
+ if (serverOp.getNewPos() >= clientOp.getNewPos()) {
+ second.setNewPos(second.getNewPos()-1);
+ }
+ }
+ else if (serverOp.getNewPos() >= clientOp.getNewPos()) {
+ second.setNewPos(second.getNewPos()-1);
+ }
+ return new Pair(first,second);
+ }
+
+ public static Pair transform(WhiteboardUpdateOperation clientOp,WhiteboardInsertOperation serverOp) {
+ WhiteboardInsertOperation first = new WhiteboardInsertOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardUpdateOperation second = new WhiteboardUpdateOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (serverOp.getPos() <= clientOp.getPos()) {
+ second.setPos(second.getPos()+1);
+ }
+ return new Pair(first, second);
+ }
+
+ public static Pair transform(WhiteboardInsertOperation clientOp,WhiteboardUpdateOperation serverOp) {
+ WhiteboardUpdateOperation first = new WhiteboardUpdateOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardInsertOperation second = new WhiteboardInsertOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (serverOp.getPos() >= clientOp.getPos()) {
+ first.setPos(first.getPos()+1);
+ }
+ return new Pair(first,second);
+ }
+
+ public static Pair transform(WhiteboardDeleteOperation clientOp, WhiteboardDeleteOperation serverOp) {
+ WhiteboardDeleteOperation first = new WhiteboardDeleteOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardDeleteOperation second = new WhiteboardDeleteOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (clientOp.getPos() == -1) {
+ second.setPos(-1);
+ }
+ if (serverOp.getPos() == -1) {
+ first.setPos(-1);
+ }
+ if (clientOp.getPos() < serverOp.getPos()) {
+ first.setPos(first.getPos()-1);
+ }
+ else if (clientOp.getPos() > serverOp.getPos()) {
+ second.setPos(second.getPos()-1);
+ }
+ else {
+ first.setPos(-1);
+ second.setPos(-1);
+ }
+ return new Pair(first, second);
+ }
+
+ public static Pair transform(WhiteboardInsertOperation clientOp, WhiteboardDeleteOperation serverOp) {
+ WhiteboardDeleteOperation first = new WhiteboardDeleteOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardInsertOperation second = new WhiteboardInsertOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (clientOp.getPos() <= serverOp.getPos()) {
+ first.setPos(first.getPos()+1);
+ }
+ else if (serverOp.getPos() != -1) {
+ second.setPos(second.getPos()-1);
+ }
+ return new Pair(first, second);
+ }
+
+ public static Pair transform(WhiteboardDeleteOperation clientOp, WhiteboardInsertOperation serverOp) {
+ WhiteboardInsertOperation first = new WhiteboardInsertOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardDeleteOperation second = new WhiteboardDeleteOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (serverOp.getPos() <= clientOp.getPos()) {
+ second.setPos(second.getPos()+1);
+ }
+ else if (clientOp.getPos() != -1) {
+ first.setPos(first.getPos()-1);
+ }
+ return new Pair(first, second);
+ }
+
+ public static Pair transform(WhiteboardUpdateOperation clientOp, WhiteboardDeleteOperation serverOp) {
+ WhiteboardDeleteOperation first = new WhiteboardDeleteOperation(serverOp);
+ first.setParentID(clientOp.getID());
+ WhiteboardUpdateOperation updateOp = new WhiteboardUpdateOperation(clientOp);;
+ WhiteboardOperation second = updateOp;
+ second.setParentID(serverOp.getID());
+ if (clientOp.getPos() == serverOp.getPos()) {
+ WhiteboardDeleteOperation deleteOp = new WhiteboardDeleteOperation();
+ second = deleteOp;
+ second.setPos(-1);
+ second.setID(clientOp.getID());
+ second.setParentID(serverOp.getID());
+ deleteOp.setElementID(serverOp.getElementID());
+ }
+ else if (clientOp.getPos() > serverOp.getPos() && clientOp.getNewPos() <= serverOp.getPos()) {
+ second.setPos(second.getPos()-1);
+ }
+ else if (clientOp.getPos() < serverOp.getPos() && clientOp.getNewPos() >= serverOp.getPos()) {
+ updateOp.setNewPos(updateOp.getNewPos()-1);
+ }
+ else if (clientOp.getPos() > serverOp.getPos()) {
+ second.setPos(second.getPos() - 1);
+ updateOp.setNewPos(updateOp.getNewPos()-1);
+ }
+ return new Pair(first, second);
+ }
+
+ public static Pair transform(WhiteboardDeleteOperation clientOp, WhiteboardUpdateOperation serverOp) {
+ WhiteboardUpdateOperation updateOp = new WhiteboardUpdateOperation(serverOp);
+ WhiteboardOperation first = updateOp;
+ first.setParentID(clientOp.getID());
+ WhiteboardDeleteOperation second = new WhiteboardDeleteOperation(clientOp);
+ second.setParentID(serverOp.getID());
+ if (clientOp.getPos() == serverOp.getPos()) {
+ WhiteboardDeleteOperation deleteOp = new WhiteboardDeleteOperation();
+ first = deleteOp;
+ first.setPos(-1);
+ first.setID(serverOp.getID());
+ first.setParentID(clientOp.getID());
+ deleteOp.setElementID(clientOp.getElementID());
+ }
+ else if (clientOp.getPos() < serverOp.getPos() && clientOp.getPos() >= serverOp.getNewPos()) {
+ first.setPos(first.getPos()-1);
+ }
+ else if (clientOp.getPos() > serverOp.getPos() && clientOp.getPos() <= serverOp.getNewPos()) {
+ updateOp.setNewPos(updateOp.getNewPos()-1);
+ }
+ else if (clientOp.getPos() < serverOp.getPos()) {
+ first.setPos(first.getPos()-1);
+ updateOp.setNewPos(updateOp.getNewPos()-1);
+ }
+ return new Pair(first, second);
+ }
+
+
+}
diff --git a/test/com/isode/stroke/whiteboard/WhiteboardClientTest.java b/test/com/isode/stroke/whiteboard/WhiteboardClientTest.java
new file mode 100644
index 0000000..ae051de
--- /dev/null
+++ b/test/com/isode/stroke/whiteboard/WhiteboardClientTest.java
@@ -0,0 +1,672 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardElement;
+import com.isode.stroke.elements.WhiteboardEllipseElement;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+
+
+/**
+ * Tests for {@link WhiteboardClient}
+ */
+public class WhiteboardClientTest {
+
+ /*!
+ * /\
+ * \/
+ * \
+ */
+ @Test
+ public void testSynchronize_simplestSync() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives server operation parented off "0", which isn't last client operation
+ //so it have to be transformed against local operations and then transformed value can
+ //be returned to draw
+ serverOp = createInsertOperation("b", "0", 1);
+ WhiteboardEllipseElement bElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(bElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "b", "a", 2, bElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation from the server about processed "a" operation, it had to
+ //be transformed against "b" on the server side to receive operation parented off "b".
+ //There aren't any waiting operations to send to server, this operation should return
+ //nothing
+ serverOp = createInsertOperation("a", "b", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives local operation, it doesn't have to be transformed against anything
+ //but operation returned to send to the server should be parented off last server
+ //operation, which is "b"
+ clientOp = createInsertOperation("c", "b", 3);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(cElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "c", "a", 3, cElement);
+
+ //Client receives confirmation from the server about processed "a" operation, it
+ //should be the same operation as it was sent because server didn't have to
+ //transform it
+ clientOp = createInsertOperation("c", "a", 3);
+ clientOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Results:
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //b 1
+ //a 1
+ //c 3
+ //
+ //what gives 0abc on both sides
+ }
+
+ /*!
+ * /
+ * /
+ * \
+ */
+ @Test
+ public void testSynchronize_withoutTranslation() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(cElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "c", "0", 1, cElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(dElement);
+ assertNull(client.handleLocalOperationReceived(clientOp));
+
+ //Client receives confirmation about processing "c" operation, it should be the
+ //same as sent operation because it wasn't transformed, client could send now
+ //operation "d"
+ clientOp = createInsertOperation("c", "0", 1);
+ clientOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ checkOperation(pairResult.server, "d", "c", 2, dElement);
+ assertNull(pairResult.client);
+
+ //Client receives confirmation about processing "d", it should be the same as
+ //sent operation. There aren't any operations in queue to send.
+ clientOp = createInsertOperation("d", "c", 2);
+ clientOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(clientOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives new operation from server, it's parented off "d" which is at
+ //the end of local history so it doesn't have to be transformed, so operation
+ //to pass to window should be the same
+ serverOp = createInsertOperation("e", "d", 3);
+ WhiteboardEllipseElement eElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(eElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ WhiteboardInsertOperation result = (WhiteboardInsertOperation) pairResult.client;
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //e 3
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //e 3
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ @Test
+ public void testSynchronize_nonInterrupted() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertEquals(null, pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement bElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(bElement);
+ assertNull(client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ assertNull(pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 4, dElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ assertNull(pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ serverOp = createInsertOperation("b", "a", 2);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * / /
+ * \/
+ */
+ @Test
+ public void testSynchronize_clientInterruption() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement bElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(bElement);
+ assertNull(client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ assertNull(pairResult.server);
+
+ //Client receives new local operation, client is still waiting for ack so, it
+ //should return nothing
+ clientOp = createInsertOperation("e", "a", 4);
+ WhiteboardEllipseElement eElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(eElement);
+ assertNull(client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new server operation, to add it to local history it should be transformed
+ //against result of previous transformations and operation "e", returned operation should
+ //be parented off "e", which was last local operation
+ serverOp = createInsertOperation("d", "c", 2);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "e", 5, dElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "a", it had to be transformed against
+ //"c" and "d" and it is now parented off "d", returned value should be next operation
+ //which have to be send to server("b" parented off server version of "a").
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ assertNull(pairResult.client);
+
+ //Client receives confirmation about processing "b", it is the same operation as sent because
+ //it didn't have to be transformed, returned value should be next operation
+ //which have to be send to server("e" parented off server version of "b").
+ serverOp = createInsertOperation("b", "a", 2);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "e", "b", 4, eElement);
+ assertNull(pairResult.client);
+
+ //Client receives confirmation about processing "b", it is the same operation as sent because
+ //it didn't have to be transformed, there aren't any operations to send so this function returns
+ //nothing
+ serverOp = createInsertOperation("e", "b", 4);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Result:
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //e 4
+ //d 5
+ //
+ //Server operations:
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //e 4
+ //what gives 0abced on both sides
+ }
+
+ /*!
+ * /\
+ * / /
+ * \ \
+ * \/
+ */
+ @Test
+ public void testSynchronize_serverInterruption() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientOp = createInsertOperation("b", "a", 2);
+ WhiteboardEllipseElement bElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(bElement);
+ assertNull(client.handleLocalOperationReceived(clientOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "0", 1);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 3, cElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "a", it had to be transformed against
+ //"c" and it is now parented off "c", returned value should be next operation
+ //which have to be send to server("b" parented off server version of "a").
+ serverOp = createInsertOperation("a", "c", 1);
+ serverOp.setElement(aElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 2, bElement);
+ assertNull(pairResult.client);
+
+ //Client receives new server operation, to add it to local history it should be transformed
+ //against result of previous transformation(but only with transformation of "b"), returned
+ //operation should be parented off "c", which was last local operation
+ serverOp = createInsertOperation("d", "a", 3);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 4, dElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "b", it had to be transformed against
+ //"d" because both operations was parented off server version of "a".
+ //there aren't any operations to send so this function returns nothing.
+ serverOp = createInsertOperation("b", "d", 2);
+ serverOp.setElement(bElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //a 1
+ //d 3
+ //b 2
+ //
+ //what gives 0abcd on both sides
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ @Test
+ public void testSynchronize_nonInterruptedMixOperations() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp = createInsertOperation("a", "0", 1);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientOp), "a", "0", 1, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ WhiteboardUpdateOperation clientUpdateOp = createUpdateOperation("b", "a", 0);
+ WhiteboardEllipseElement bElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientUpdateOp.setElement(bElement);
+ assertNull(client.handleLocalOperationReceived(clientUpdateOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ WhiteboardUpdateOperation serverUpdateOp = createUpdateOperation("c", "0", 0);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverUpdateOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.client, "c", "b", 0, cElement);
+ assertNull(pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("d", "c", 1);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "d", "c", 2, dElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverOp = createInsertOperation("a", "d", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.server, "b", "a", 0, cElement);
+ assertNull(pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ serverUpdateOp = createUpdateOperation("b", "a", 0);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+ /*!
+ * /\
+ * / \
+ * \ /
+ * \/
+ */
+ @Test
+ public void testSynchronize_nonInterruptedMixOperations2() {
+ WhiteboardClient client = new WhiteboardClient();
+ WhiteboardInsertOperation serverOp = createInsertOperation("0", "", 0);
+ WhiteboardClient.Result pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+
+ serverOp = createInsertOperation("1", "0", 1);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ assertEquals(serverOp, pairResult.client);
+ assertNull(pairResult.server);
+ //Client receives first local operation, because it's parented off "0" which exists
+ //in server history and client doesn't wait for any operation ack from server,
+ //so this operation could be send
+ WhiteboardInsertOperation clientOp;
+ WhiteboardUpdateOperation clientUpdateOp;
+ WhiteboardDeleteOperation clientDeleteOp;
+ clientUpdateOp = createUpdateOperation("a", "1", 0);
+ WhiteboardEllipseElement aElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientUpdateOp.setElement(aElement);
+ checkOperation(client.handleLocalOperationReceived(clientUpdateOp), "a", "1", 0, aElement);
+
+ //Client receives second local operation, client didn't receive ack about previous
+ //operation from the server so it can't be send.
+ clientDeleteOp = createDeleteOperation("b", "a", 1);
+ assertNull(client.handleLocalOperationReceived(clientDeleteOp));
+
+ //Client receives new operation from server, it should be transformed against
+ //"a" and "b" before adding to local operations history because it's parented off "0".
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ serverOp = createInsertOperation("c", "1", 2);
+ WhiteboardEllipseElement cElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverOp.setElement(cElement);
+ pairResult = client.handleServerOperationReceived(serverOp);
+ checkOperation(pairResult.client, "c", "b", 1, cElement);
+ assertNull(pairResult.server);
+
+ //Client receives new operation from server, it should be transformed against
+ //results of previous transformations, returned operation should be parented off
+ //"c" existing in local history.
+ //Because client is waiting for ack of "a", there is no operation to send to server
+ WhiteboardUpdateOperation serverUpdateOp = createUpdateOperation("d", "c", 0);
+ WhiteboardEllipseElement dElement = new WhiteboardEllipseElement(0,0,0,0);
+ serverUpdateOp.setElement(dElement);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.client, "d", "c", 0, dElement);
+ assertNull(pairResult.server);
+
+ //Client receives confirmation about processing "a", it should send next operation
+ //to server which is "b", but it should be version parented of transformed "a"
+ serverUpdateOp = createUpdateOperation("a", "d", 0);
+ pairResult = client.handleServerOperationReceived(serverUpdateOp);
+ checkOperation(pairResult.server, "b", "a", 1);
+ assertNull(pairResult.client);
+
+
+ //Client receives confirmation about processing "b", there aren't any operations
+ //waiting so it should return nothing.
+ WhiteboardDeleteOperation serverDeleteOp = createDeleteOperation("b", "a", 0);
+ pairResult = client.handleServerOperationReceived(serverDeleteOp);
+ assertNull(pairResult.client);
+ assertNull(pairResult.server);
+
+ //Client operations:
+ //ID pos
+ //0 0
+ //a 1
+ //b 2
+ //c 3
+ //d 4
+ //
+ //Server operations:
+ //ID pos
+ //0 0
+ //c 1
+ //d 2
+ //a 1
+ //b 2
+ //
+ //what gives 0abcd on both sides.
+ }
+
+ private WhiteboardInsertOperation createInsertOperation(String id, String parent, int pos) {
+ WhiteboardInsertOperation operation = new WhiteboardInsertOperation();
+ operation.setParentID(parent);
+ operation.setID(id);
+ operation.setPos(pos);
+ return operation;
+ }
+
+ private WhiteboardUpdateOperation createUpdateOperation(String id, String parent, int pos) {
+ WhiteboardUpdateOperation operation = new WhiteboardUpdateOperation();
+ operation.setParentID(parent);
+ operation.setID(id);
+ operation.setPos(pos);
+ return operation;
+ }
+
+ private WhiteboardDeleteOperation createDeleteOperation(String id, String parent, int pos) {
+ WhiteboardDeleteOperation operation = new WhiteboardDeleteOperation();
+ operation.setParentID(parent);
+ operation.setID(id);
+ operation.setPos(pos);
+ return operation;
+ }
+
+ private void checkOperation(WhiteboardOperation operation, String id, String parent) {
+ checkOperation(operation,id,parent,-1);
+ }
+
+ private void checkOperation(WhiteboardOperation operation, String id, String parent, int pos) {
+ checkOperation(operation,id,parent,pos,null);
+ }
+
+ private void checkOperation(WhiteboardOperation operation, String id, String parent, int pos, WhiteboardElement element) {
+ assertEquals(id, operation.getID());
+ assertEquals(parent, operation.getParentID());
+ if (pos != -1) {
+ assertEquals(pos, operation.getPos());
+ }
+
+ if (element != null) {
+ if (operation instanceof WhiteboardInsertOperation) {
+ WhiteboardInsertOperation insertOp = (WhiteboardInsertOperation) operation;
+ assertEquals(element,insertOp.getElement());
+ }
+ else if (operation instanceof WhiteboardUpdateOperation) {
+ WhiteboardUpdateOperation updateOp = (WhiteboardUpdateOperation) operation;
+ assertEquals(element,updateOp.getElement());
+ }
+ }
+ }
+
+}
diff --git a/test/com/isode/stroke/whiteboard/WhiteboardServerTest.java b/test/com/isode/stroke/whiteboard/WhiteboardServerTest.java
new file mode 100644
index 0000000..c90d8c3
--- /dev/null
+++ b/test/com/isode/stroke/whiteboard/WhiteboardServerTest.java
@@ -0,0 +1,146 @@
+/* Copyright (c) 2016, Isode Limited, London, England.
+ * All rights reserved.
+ *
+ * Acquisition and use of this software and related materials for any
+ * purpose requires a written license agreement from Isode Limited,
+ * or a written license from an organisation licensed by Isode Limited
+ * to grant such a license.
+ *
+ */
+package com.isode.stroke.whiteboard;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.isode.stroke.elements.WhiteboardDeleteOperation;
+import com.isode.stroke.elements.WhiteboardEllipseElement;
+import com.isode.stroke.elements.WhiteboardInsertOperation;
+import com.isode.stroke.elements.WhiteboardOperation;
+import com.isode.stroke.elements.WhiteboardUpdateOperation;
+
+/**
+ * Tests for {@link WhiteboardServer}
+ */
+public class WhiteboardServerTest {
+
+ @Test
+ public void testSimpleOp() {
+ WhiteboardServer server = new WhiteboardServer();
+ WhiteboardInsertOperation firstOp = new WhiteboardInsertOperation();
+ firstOp.setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardInsertOperation serverOp = new WhiteboardInsertOperation();
+ serverOp.setID("b");
+ serverOp.setParentID("0");
+ serverOp.setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardInsertOperation clientOp = new WhiteboardInsertOperation();
+ WhiteboardEllipseElement clientElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setID("a");
+ clientOp.setParentID("0");
+ clientOp.setPos(1);
+ clientOp.setElement(clientElement);
+ WhiteboardOperation resultOp = server.handleClientOperationReceived(clientOp);
+ assertNotNull(resultOp);
+ assertTrue("resultOp is not a WhiteboardInsertOperation",
+ resultOp instanceof WhiteboardInsertOperation);
+ WhiteboardInsertOperation op = (WhiteboardInsertOperation) resultOp;
+ assertEquals("b",op.getParentID());
+ assertEquals("a",op.getID());
+ assertEquals(1,op.getPos());
+ assertEquals(clientElement,op.getElement());
+ }
+
+ @Test
+ public void testSimpleOp1() {
+ WhiteboardServer server = new WhiteboardServer();
+ WhiteboardInsertOperation firstOp = new WhiteboardInsertOperation();
+ firstOp.setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardDeleteOperation serverOp = new WhiteboardDeleteOperation();
+ serverOp.setID("b");
+ serverOp.setParentID("0");
+ serverOp.setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardUpdateOperation clientOp = new WhiteboardUpdateOperation();
+ WhiteboardEllipseElement clientElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setID("a");
+ clientOp.setParentID("0");
+ clientOp.setPos(1);
+ clientOp.setElement(clientElement);
+ WhiteboardOperation resultOp = server.handleClientOperationReceived(clientOp);
+ assertNotNull(resultOp);
+ assertTrue("resultOp is not a WhiteboardDeleteOperation",
+ resultOp instanceof WhiteboardDeleteOperation);
+ WhiteboardDeleteOperation op = (WhiteboardDeleteOperation) resultOp;
+ assertEquals("b", op.getParentID());
+ assertEquals("a", op.getID());
+ assertEquals(-1, op.getPos());
+ }
+
+ @Test
+ public void testSimpleOp2() {
+ WhiteboardServer server = new WhiteboardServer();
+ WhiteboardInsertOperation firstOp = new WhiteboardInsertOperation();
+ firstOp.setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardUpdateOperation serverOp = new WhiteboardUpdateOperation();
+ serverOp.setID("b");
+ serverOp.setParentID("0");
+ serverOp.setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardDeleteOperation clientOp = new WhiteboardDeleteOperation();
+ clientOp.setID("a");
+ clientOp.setParentID("0");
+ clientOp.setPos(1);
+ WhiteboardOperation resultOp = server.handleClientOperationReceived(clientOp);
+ assertNotNull(resultOp);
+ assertTrue("resultOp is not a WhiteboardDeleteOperation",
+ resultOp instanceof WhiteboardDeleteOperation);
+ WhiteboardDeleteOperation op = (WhiteboardDeleteOperation) resultOp;
+ assertEquals("b", op.getParentID());
+ assertEquals("a", op.getID());
+ assertEquals(1, op.getPos());
+ }
+
+ @Test
+ public void testFewSimpleOps() {
+ WhiteboardServer server = new WhiteboardServer();
+ WhiteboardInsertOperation firstOp = new WhiteboardInsertOperation();
+ firstOp.setID("0");
+ server.handleLocalOperationReceived(firstOp);
+ WhiteboardInsertOperation serverOp = new WhiteboardInsertOperation();
+ serverOp.setID("a");
+ serverOp.setParentID("0");
+ serverOp.setPos(1);
+ server.handleLocalOperationReceived(serverOp);
+ serverOp = new WhiteboardInsertOperation();
+ serverOp.setID("b");
+ serverOp.setParentID("a");
+ serverOp.setPos(2);
+ server.handleLocalOperationReceived(serverOp);
+ serverOp = new WhiteboardInsertOperation();
+ serverOp.setID("c");
+ serverOp.setParentID("b");
+ serverOp.setPos(3);
+ server.handleLocalOperationReceived(serverOp);
+ WhiteboardInsertOperation clientOp = new WhiteboardInsertOperation();
+ WhiteboardEllipseElement clientElement = new WhiteboardEllipseElement(0,0,0,0);
+ clientOp.setID("d");
+ clientOp.setParentID("0");
+ clientOp.setPos(1);
+ clientOp.setElement(clientElement);
+ WhiteboardOperation resultOp = server.handleClientOperationReceived(clientOp);
+ assertNotNull(resultOp);
+ assertTrue("resultOp is not a WhiteboardInsertOperation",resultOp instanceof WhiteboardInsertOperation);
+ WhiteboardInsertOperation op = (WhiteboardInsertOperation) resultOp;
+ assertEquals("c", op.getParentID());
+ assertEquals("d", op.getID());
+ assertEquals(1, op.getPos());
+ assertEquals(clientElement, op.getElement());
+ }
+
+}