1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.jcurl.demo.smack;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.util.concurrent.Executor;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import java.util.zip.GZIPInputStream;
31 import java.util.zip.GZIPOutputStream;
32
33 import org.apache.commons.codec.binary.Base64;
34 import org.jcurl.core.ui.ChangeManager;
35 import org.jcurl.core.ui.Memento;
36 import org.jcurl.core.ui.UndoableMemento;
37 import org.jivesoftware.smack.Chat;
38 import org.jivesoftware.smack.ChatManagerListener;
39 import org.jivesoftware.smack.MessageListener;
40 import org.jivesoftware.smack.XMPPException;
41 import org.jivesoftware.smack.packet.Message;
42 import org.jivesoftware.smack.packet.PacketExtension;
43
44
45
46
47
48
49
50
51 public class SmackChangeManager extends ChangeManager implements
52 ChatManagerListener {
53
54 private class MementoPackageExtension implements PacketExtension {
55 private final Memento<?> me;
56 private String xml = null;
57
58 public MementoPackageExtension(final Memento<?> me) {
59 this.me = me;
60 }
61
62 public String getElementName() {
63 return temporaryElem;
64 }
65
66 public String getNamespace() {
67 return namespace;
68 }
69
70 public String toXML() {
71 if (xml != null)
72 return xml;
73 return xml = toXml(me);
74 }
75 }
76
77 private static final String JCURL_GRAPHICS_SYNC = "jcurl graphics sync";
78 private static final String namespace = "/xep/"
79 + SmackChangeManager.class.getName();
80 static final Pattern pat = Pattern
81 .compile("<([^ >]+)\\s+xmlns='([^']*)'><([^ >]+)\\s+id='([^']*)'>([^<]*)</([^ >]+)></([^ >]+)>");
82 private static final String subnode = "base64gz";
83 private static final String temporaryElem = "temporary";
84 private static final String US_ASCII = "US-ASCII";
85
86
87
88
89
90
91 static Memento<?> fromXml(final String xml) {
92 final Matcher m = pat.matcher(xml);
93 if (!m.matches())
94 throw new IllegalStateException("xml doesn't match: " + xml);
95 if (!m.group(1).equals(m.group(7)) || !m.group(3).equals(m.group(6)))
96 throw new IllegalStateException("xml not well formed: " + xml);
97 if (!namespace.equals(m.group(2)))
98 throw new IllegalStateException("wrong namespace: " + m.group(2));
99 if (!temporaryElem.equals(m.group(1)))
100 throw new IllegalStateException("wrong elementName: " + m.group(1));
101 if (!subnode.equals(m.group(3)))
102 throw new IllegalStateException("wrong subnode: " + m.group(3));
103 final String id = m.group(4);
104 try {
105
106 final ByteArrayInputStream bin = new ByteArrayInputStream(Base64
107 .decodeBase64(m.group(5).getBytes(US_ASCII)));
108 final GZIPInputStream gin = new GZIPInputStream(bin);
109 final ObjectInputStream oin = new ObjectInputStream(gin);
110 try {
111 final Memento<?> me = (Memento<?>) oin.readObject();
112
113 me.setContext(null);
114 return me;
115 } catch (final ClassNotFoundException e) {
116 throw new IllegalStateException("Unhandled", e);
117 } finally {
118 oin.close();
119 }
120 } catch (final IOException e) {
121 throw new RuntimeException("Unhandled", e);
122 }
123 }
124
125
126
127
128
129
130 static String toXml(final Memento<?> me) {
131 final StringBuilder s = new StringBuilder();
132 s.append('<').append(temporaryElem).append(" xmlns='")
133 .append(namespace).append("'>");
134 s.append("<").append(subnode).append(" id='");
135 {
136
137 s.append("TODO id");
138 }
139 s.append("'>");
140 try {
141
142 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
143 final GZIPOutputStream gout = new GZIPOutputStream(bout);
144 final ObjectOutputStream oout = new ObjectOutputStream(gout);
145 oout.writeObject(me);
146 oout.close();
147 s.append(new String(Base64.encodeBase64(bout.toByteArray(), false),
148 US_ASCII));
149 } catch (final IOException e) {
150 throw new RuntimeException("Unhandled", e);
151 }
152 s.append("</").append(subnode).append(">");
153 s.append("</").append(temporaryElem).append('>');
154 return s.toString();
155 }
156
157 private Chat chat = null;
158
159 private final MessageListener msg = new MessageListener() {
160
161 public void processMessage(final Chat arg0, final Message arg1) {
162 final PacketExtension pe = arg1.getExtension(namespace);
163 if (pe == null)
164 return;
165 if (temporaryElem.equals(pe.getElementName()))
166 superTemporary(fromXml(pe.toXML()));
167 else if (superUndoable(convert_(arg1)))
168 ;
169 else
170 throw new IllegalStateException(pe.getElementName());
171 return;
172 }
173 };
174
175 public SmackChangeManager() {
176 super();
177 }
178
179 public SmackChangeManager(final Executor executor) {
180 super(executor);
181 }
182
183 public void chatCreated(final Chat chat, final boolean arg1) {
184 if (this.chat != null)
185 this.chat.removeMessageListener(msg);
186 this.chat = chat;
187 if (this.chat != null)
188 this.chat.addMessageListener(msg);
189 }
190
191 private Message convert(final UndoableMemento<?> pre) {
192
193 throw new UnsupportedOperationException();
194 }
195
196 private UndoableMemento<?> convert_(final Message m) {
197
198 throw new UnsupportedOperationException();
199 }
200
201 private boolean superTemporary(final Memento<?> m) {
202 if (m == null)
203 return false;
204 super.temporary(m);
205 return true;
206 }
207
208 private boolean superUndoable(final UndoableMemento m) {
209 if (m == null)
210 return false;
211
212
213 throw new UnsupportedOperationException();
214 }
215
216
217
218
219
220 @Override
221 public void temporary(final Memento<?> m) {
222 super.temporary(m);
223 if (chat == null)
224 return;
225 try {
226 final Message r = new Message();
227 r.setBody(JCURL_GRAPHICS_SYNC);
228 r.addExtension(new MementoPackageExtension(m));
229 chat.sendMessage(r);
230 } catch (final XMPPException e) {
231 throw new RuntimeException("Unhandled", e);
232 }
233 }
234
235
236
237
238
239
240 @Override
241 public <E> UndoableMemento<E> undoable(final Memento<E> pre,
242 final Memento<E> post) {
243 final UndoableMemento<E> m = super.undoable(pre, post);
244 if (m == null)
245 return m;
246 if (chat != null)
247 try {
248 chat.sendMessage(convert(m));
249 } catch (final XMPPException e) {
250 throw new RuntimeException("Unhandled", e);
251 }
252 return m;
253 }
254 }