summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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
Diffstat (limited to 'src')
-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
26 files changed, 2293 insertions, 2 deletions
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);
+ }
+
+
+}