PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/src/client/ui/src/com/google/speedtracer/client/WindowChannel.java

http://speedtracer.googlecode.com/
Java | 333 lines | 152 code | 45 blank | 136 comment | 8 complexity | 35240cc5115478f43794c55151a00368 MD5 | raw file
  1. /*
  2. * Copyright 2008 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.speedtracer.client;
  17. import com.google.gwt.core.client.JavaScriptObject;
  18. import com.google.gwt.events.client.Event;
  19. import com.google.gwt.events.client.EventListener;
  20. import com.google.gwt.events.client.EventListenerRemover;
  21. import com.google.speedtracer.client.util.dom.WindowExt;
  22. /**
  23. * Provides a communication channel between two GWT modules. A
  24. * {@link WindowChannel.Server} must be started first, and is responsible for
  25. * create Client channel pairs. One or more {@link WindowChannel.Client}s can
  26. * connect to a single {@link WindowChannel.Server}. Each connection request
  27. * creates a corresponding {@link WindowChannel.Client} endpoint on the server
  28. * side (completing the channel pairing).
  29. */
  30. public class WindowChannel {
  31. /**
  32. * An endpoint in a Channel pair. The object each side uses to send and the
  33. * object each side subscribes to to receive messages.
  34. */
  35. public static class Client {
  36. /**
  37. * Creates and connects a new channel.
  38. *
  39. * connect is asynchronous and it is not safe to call
  40. * {@link #sendMessage(int, Message)} or {@link #close()} before
  41. * {@link Listener#onChannelConnected(Client)} has been invoked.
  42. *
  43. * @param window a shared window
  44. * @param name a shared name for the channel
  45. * @param listener
  46. * @return
  47. */
  48. public static Client connect(WindowExt window, String name,
  49. Listener listener) {
  50. final String property = PROPERTY_NAME + name;
  51. final Connector connector = window.getObjectProperty(property).cast();
  52. assert connector != null;
  53. final Client client = new Client(listener);
  54. // Server will invoke the setSocketCallback setting our socket and will
  55. // call his own onChannelConnected. If the Server sends a message during
  56. // onChannelConnected, handleSend will make sure that our own
  57. // onChannelConnected is invoked before that message is delivered. If the
  58. // Server does not send a message, we will manually call
  59. // onChannelConnected after this call returns.
  60. connector.connect(Socket.create(client), SetSocketCallback.create(client));
  61. assert client.socket != null;
  62. // This will call listener.onChannelConnected if the Server did not send a
  63. // message during his own onChannelConnected.
  64. client.maybeConnectChannel();
  65. final EventListenerRemover[] remover = new EventListenerRemover[1];
  66. remover[0] = window.addUnloadListener(new EventListener() {
  67. public void handleEvent(Event event) {
  68. remover[0].remove();
  69. client.close();
  70. }
  71. });
  72. return client;
  73. }
  74. private boolean connected;
  75. private final Listener listener;
  76. private Socket socket;
  77. private Client(Listener listener) {
  78. this.listener = listener;
  79. }
  80. /**
  81. * Closes the channel.
  82. *
  83. * It is safe to call this method on a closed channel. However, it is not
  84. * safe to call before {@link Listener#onChannelConnected(Client)} has been
  85. * invoked.
  86. */
  87. public void close() {
  88. if (socket != null) {
  89. socket.close();
  90. socket = null;
  91. connected = false;
  92. listener.onChannelClosed(this);
  93. }
  94. }
  95. /**
  96. * Sends a message to the channel peer.
  97. *
  98. * It is an error to call this method before
  99. * {@link Listener#onChannelConnected(Client)} is invoked.
  100. *
  101. * @param type a type id to use to dispatch the message
  102. * @param message the message payload
  103. */
  104. public void sendMessage(int type, Message message) {
  105. assert socket != null;
  106. socket.send(type, message);
  107. }
  108. @SuppressWarnings("unused")
  109. private void handleClose() {
  110. socket = null;
  111. connected = false;
  112. listener.onChannelClosed(this);
  113. }
  114. @SuppressWarnings("unused")
  115. private void handleSend(int type, Message message) {
  116. // Since it is common for listeners to send messages in their
  117. // onChannelConnected callbacks, we must make sure that onChannelConnected
  118. // is called before any messages are delivered.
  119. maybeConnectChannel();
  120. listener.onMessage(this, type, message.<Message> cast());
  121. }
  122. private void maybeConnectChannel() {
  123. if (!connected) {
  124. connected = true;
  125. listener.onChannelConnected(this);
  126. }
  127. }
  128. private void setSocket(Socket socket) {
  129. assert !connected;
  130. this.socket = socket;
  131. }
  132. }
  133. /**
  134. * listener interface to receive channel events.
  135. */
  136. public interface Listener {
  137. /**
  138. * Called when the channel disconnects from its peer.
  139. *
  140. * @param client the channel generating the event
  141. */
  142. void onChannelClosed(Client client);
  143. /**
  144. * Called when the channel connects to its peer.
  145. *
  146. * @param client the channel generating the event
  147. */
  148. void onChannelConnected(Client client);
  149. /**
  150. * Called when a message is sent from the peer channel.
  151. *
  152. * @param client the client that received the event.
  153. * @param type a user specified type id
  154. * @param data the message payload
  155. */
  156. void onMessage(Client client, int type, Message data);
  157. }
  158. /**
  159. * A overlay tag type to give all WindowChannel messages a common base type.
  160. */
  161. public static class Message extends JavaScriptObject {
  162. protected Message() {
  163. }
  164. }
  165. /**
  166. * A wrapper object passed to a {@link WindowChannel.ServerListener} when a
  167. * client tries to connect to the channel. It releases the server side's
  168. * client endpoint.
  169. */
  170. public static class Request {
  171. private final SetSocketCallback callback;
  172. private final Socket socket;
  173. private Request(Socket socket, SetSocketCallback callback) {
  174. this.socket = socket;
  175. this.callback = callback;
  176. }
  177. public Client accept(Listener listener) {
  178. final Client client = new Client(listener);
  179. client.setSocket(socket);
  180. callback.setSocket(Socket.create(client));
  181. client.maybeConnectChannel();
  182. return client;
  183. }
  184. }
  185. /**
  186. * Server responsible for creating and establishing
  187. * {@link WindowChannel.Client} channel pairs.
  188. */
  189. public static class Server {
  190. public static Server listen(WindowExt window, String name,
  191. ServerListener listener) {
  192. final String property = PROPERTY_NAME + name;
  193. assert window.getObjectProperty(property) == null;
  194. final Server server = new Server(window, property, listener);
  195. window.setObjectProperty(property, Connector.create(server));
  196. return server;
  197. }
  198. private final ServerListener listener;
  199. private final String property;
  200. private final WindowExt window;
  201. private Server(WindowExt window, String property, ServerListener listener) {
  202. this.window = window;
  203. this.property = property;
  204. this.listener = listener;
  205. }
  206. public void close() {
  207. assert window.getObjectProperty(property) != null;
  208. window.setObjectProperty(property, null);
  209. }
  210. @SuppressWarnings("unused")
  211. private void handleConnect(Socket socket, SetSocketCallback callback) {
  212. listener.onClientChannelRequested(new Request(socket, callback));
  213. }
  214. }
  215. /**
  216. * Listener interface for receiving connection requests from clients.
  217. */
  218. public interface ServerListener {
  219. void onClientChannelRequested(Request request);
  220. }
  221. /**
  222. * An overlay type around JavaScript function. The Server places an instance
  223. * on the shared window. The slave channel, the second to connect, calls
  224. * {@link #connect(Socket, SetSocketCallback)} to establish a connection.
  225. */
  226. private static final class Connector extends JavaScriptObject {
  227. public static native Connector create(Server server) /*-{
  228. return function(socket, callback) {
  229. server.@com.google.speedtracer.client.WindowChannel$Server::handleConnect(Lcom/google/speedtracer/client/WindowChannel$Socket;Lcom/google/speedtracer/client/WindowChannel$SetSocketCallback;)(
  230. socket, callback);
  231. };
  232. }-*/;
  233. @SuppressWarnings("all")
  234. protected Connector() {
  235. }
  236. public native void connect(Socket socket, SetSocketCallback callback) /*-{
  237. return this(socket, callback);
  238. }-*/;
  239. }
  240. /**
  241. * An overlay type around a JavaScript function. The Server will use this
  242. * function to set the slaves's socket.
  243. */
  244. private static final class SetSocketCallback extends JavaScriptObject {
  245. public static native SetSocketCallback create(Client client) /*-{
  246. return function(socket) {
  247. client.@com.google.speedtracer.client.WindowChannel$Client::setSocket(Lcom/google/speedtracer/client/WindowChannel$Socket;)(
  248. socket);
  249. };
  250. }-*/;
  251. @SuppressWarnings("all")
  252. protected SetSocketCallback() {
  253. }
  254. public native void setSocket(Socket socket) /*-{
  255. this(socket);
  256. }-*/;
  257. }
  258. /**
  259. * The JavaScriptObject implementation that facilitates all messaging between
  260. * the peers. This object is simply a {@link JavaScriptObject} (an array)
  261. * encapsulating two functions, one for messaging and one for connection tear
  262. * down.
  263. */
  264. private static final class Socket extends JavaScriptObject {
  265. static native Socket create(Client client) /*-{
  266. return [
  267. function() {
  268. client.@com.google.speedtracer.client.WindowChannel$Client::handleClose()();
  269. },
  270. function(type, message) {
  271. client.@com.google.speedtracer.client.WindowChannel$Client::handleSend(ILcom/google/speedtracer/client/WindowChannel$Message;)(
  272. type, message);
  273. }];
  274. }-*/;
  275. @SuppressWarnings("all")
  276. protected Socket() {
  277. }
  278. public native void close() /*-{
  279. this[0]();
  280. }-*/;
  281. public native void send(int type, JavaScriptObject data) /*-{
  282. this[1](type, data);
  283. }-*/;
  284. }
  285. private static final String PROPERTY_NAME = "$WindowChannel$";
  286. private WindowChannel() {
  287. }
  288. }