• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2007 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); You may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
6 // applicable law or agreed to in writing, software distributed under the
7 // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
8 // OF ANY KIND, either express or implied. See the License for the specific
9 // language governing permissions and limitations under the License.
10 
11 package com.google.scrollview;
12 
13 import com.google.scrollview.events.SVEvent;
14 import com.google.scrollview.ui.SVImageHandler;
15 import com.google.scrollview.ui.SVWindow;
16 
17 import java.io.BufferedReader;
18 import java.io.IOException;
19 import java.io.InputStreamReader;
20 import java.io.PrintStream;
21 import java.net.ServerSocket;
22 import java.net.Socket;
23 import java.util.ArrayList;
24 import java.util.regex.Pattern;
25 
26 
27 /**
28  * The ScrollView class is the main class which gets started from the command
29  * line. It sets up LUA and handles the network processing.
30  * @author wanke@google.com
31  */
32 public class ScrollView {
33 
34   /** The port our server listens at. */
35   public static int SERVER_PORT = 8461;
36 
37   /**
38    * All SVWindow objects share the same connection stream. The socket is needed
39    * to detect when the connection got closed, in/out are used to send and
40    * receive messages.
41    */
42   private static Socket socket;
43   private static PrintStream out;
44   public static BufferedReader in;
45   public static float polylineXCoords[];  // The coords being received.
46   public static float polylineYCoords[];  // The coords being received.
47   public static int polylineSize;       // The size of the coords arrays.
48   public static int polylineScanned;    // The size read so far.
49   private static ArrayList<SVWindow> windows;  // The id to SVWindow map.
50   private static Pattern intPattern;        // For checking integer arguments.
51   private static Pattern floatPattern;     // For checking float arguments.
52 
53   /** Keeps track of the number of messages received. */
54   static int nrInputLines = 0;
55 
56   /** Prints all received messages to the console if true. */
57   static boolean debugViewNetworkTraffic = false;
58 
59   /** Add a new message to the outgoing queue */
addMessage(SVEvent e)60   public static void addMessage(SVEvent e) {
61     if (debugViewNetworkTraffic) {
62       System.out.println("(S->c) " + e.toString());
63     }
64     String str = e.toString();
65     // Send the whole thing as UTF8.
66     try {
67       byte [] utf8 = str.getBytes("UTF8");
68       out.write(utf8, 0, utf8.length);
69     } catch (java.io.UnsupportedEncodingException ex) {
70       System.out.println("Oops... can't encode to UTF8... Exiting");
71       System.exit(0);
72     }
73     out.println();
74     // Flush the output and check for errors.
75     boolean error = out.checkError();
76     if (error) {
77       System.out.println("Connection error. Quitting ScrollView Server...");
78       System.exit(0);
79     }
80   }
81 
82   /** Read one message from client (assuming there are any). */
receiveMessage()83   public static String receiveMessage() throws IOException {
84     return in.readLine();
85   }
86 
87   /**
88    * The main program loop. Basically loops trough receiving messages and
89    * processing them and then sending messages (if there are any).
90    */
IOLoop()91   private static void IOLoop() {
92     String inputLine;
93 
94     try {
95       while (!socket.isClosed() && !socket.isInputShutdown() &&
96              !socket.isOutputShutdown() &&
97              socket.isConnected() && socket.isBound()) {
98         inputLine = receiveMessage();
99         nrInputLines++;
100         if (debugViewNetworkTraffic) {
101           System.out.println("(c->S," + nrInputLines + ")" + inputLine);
102         }
103 
104         if (polylineSize > polylineScanned) {
105           // We are processing a polyline.
106           // Read pairs of coordinates separated by commas.
107           boolean first = true;
108           for (String coordStr : inputLine.split(",")) {
109             int coord = Integer.parseInt(coordStr);
110             if (first) {
111               polylineXCoords[polylineScanned] = coord;
112             } else {
113               polylineYCoords[polylineScanned++] = coord;
114             }
115             first = !first;
116           }
117           assert first;
118         } else if (SVImageHandler.getReadImageData() == false) {
119           // If we are currently not transmitting an image, process this
120           // normally.
121           processInput(inputLine);
122         }
123         // We are still transmitting image data, but there seems to be some
124         // command at the
125         // end of the message attached as well. Thus, we have to split it
126         // accordingly and
127         // first generate the image and afterwards process the remaining
128         // message.
129         else if (inputLine.length() >
130                  SVImageHandler.getMissingRemainingBytes()) {
131           String luaCmd = inputLine.substring(
132               SVImageHandler.getMissingRemainingBytes());
133           String imgData = inputLine.substring(0,
134               SVImageHandler.getMissingRemainingBytes());
135           SVImageHandler.parseData(imgData);
136           processInput(luaCmd);
137         } else { // We are still in the middle of image data and have not
138                  // reached the end yet.
139           SVImageHandler.parseData(inputLine);
140         }
141       }
142     }
143     // Some connection error
144     catch (IOException e) {
145       System.out.println("Connection error. Quitting ScrollView Server...");
146     }
147     System.exit(0);
148   }
149 
150   // Parse a comma-separated list of arguments into ArrayLists of the
151   // possible types. Each type is stored in order, but the order
152   // distinction between types is lost.
153   // Note that the format is highly constrained to what the client used
154   // to send to LUA:
155   // Quoted string -> String.
156   // true or false -> Boolean.
157   // %f format number -> Float (no %e allowed)
158   // Sequence of digits -> Integer
159   // Nothing else allowed.
parseArguments(String argList, ArrayList<Integer> intList, ArrayList<Float> floatList, ArrayList<String> stringList, ArrayList<Boolean> boolList)160   private static void parseArguments(String argList,
161                                      ArrayList<Integer> intList,
162                                      ArrayList<Float> floatList,
163                                      ArrayList<String> stringList,
164                                      ArrayList<Boolean> boolList) {
165     // str is only non-null if an argument starts with a single or double
166     // quote. str is set back to null on completion of the string with a
167     // matching quote. If the string contains a comma then str will stay
168     // non-null across multiple argStr values until a matching closing quote.
169     // Backslash escaped quotes do not count as terminating the string.
170     String str = null;
171     for (String argStr : argList.split(",")) {
172       if (str != null) {
173         // Last string was incomplete. Append argStr to it and restore comma.
174         // Execute str += "," + argStr in Java.
175         int length = str.length() + 1 + argStr.length();
176         StringBuilder appended = new StringBuilder(length);
177         appended.append(str);
178         appended.append(",");
179         appended.append(argStr);
180         str =  appended.toString();
181       } else if (argStr.length() == 0) {
182         continue;
183       } else {
184         char quote = argStr.charAt(0);
185         // If it begins with a quote then it is a string, but may not
186         // end this time if it contained a comma.
187         if (quote == '\'' || quote == '"') {
188           str = argStr;
189         }
190       }
191       if (str != null) {
192         // It began with a quote. Check that it still does.
193         assert str.charAt(0) == '\'' || str.charAt(0) == '"';
194         int len = str.length();
195         if (len > 1 && str.charAt(len - 1) == str.charAt(0)) {
196           // We have an ending quote of the right type. Now check that
197           // it is not escaped. Must have an even number of slashes before.
198           int slash = len - 1;
199           while (slash > 0 && str.charAt(slash - 1) == '\\')
200             --slash;
201           if ((len - 1 - slash) % 2 == 0) {
202             // It is now complete. Chop off the quotes and save.
203             // TODO(rays) remove the first backslash of each pair.
204             stringList.add(str.substring(1, len - 1));
205             str = null;
206           }
207         }
208         // If str is not null here, then we have a string with a comma in it.
209         // Append , and the next argument at the next iteration, but check
210         // that str is null after the loop terminates in case it was an
211         // unterminated string.
212       } else if (floatPattern.matcher(argStr).matches()) {
213         // It is a float.
214         floatList.add(Float.parseFloat(argStr));
215       } else if (argStr.equals("true")) {
216         boolList.add(true);
217       } else if (argStr.equals("false")) {
218         boolList.add(false);
219       } else if (intPattern.matcher(argStr).matches()) {
220         // Only contains digits so must be an int.
221         intList.add(Integer.parseInt(argStr));
222       }
223       // else ignore all incompatible arguments for forward compatibility.
224     }
225     // All strings must have been terminated.
226     assert str == null;
227   }
228 
229   /** Executes the LUA command parsed as parameter. */
processInput(String inputLine)230   private static void processInput(String inputLine) {
231     // Execute a function encoded as a LUA statement! Yuk!
232     if (inputLine.charAt(0) == 'w') {
233       // This is a method call on a window. Parse it.
234       String noWLine = inputLine.substring(1);
235       String[] idStrs = noWLine.split("[ :]", 2);
236       int windowID = Integer.parseInt(idStrs[0]);
237       // Find the parentheses.
238       int start = inputLine.indexOf('(');
239       int end = inputLine.lastIndexOf(')');
240       // Parse the args.
241       ArrayList<Integer> intList = new ArrayList<Integer>(4);
242       ArrayList<Float> floatList = new ArrayList<Float>(2);
243       ArrayList<String> stringList = new ArrayList<String>(4);
244       ArrayList<Boolean> boolList = new ArrayList<Boolean>(3);
245       parseArguments(inputLine.substring(start + 1, end),
246                      intList, floatList, stringList, boolList);
247       int colon = inputLine.indexOf(':');
248       if (colon > 1 && colon < start) {
249         // This is a regular function call. Look for the name and call it.
250         String func = inputLine.substring(colon + 1, start);
251         if (func.equals("drawLine")) {
252           windows.get(windowID).drawLine(intList.get(0), intList.get(1),
253                                          intList.get(2), intList.get(3));
254         } else if (func.equals("createPolyline")) {
255           windows.get(windowID).createPolyline(intList.get(0));
256         } else if (func.equals("drawPolyline")) {
257           windows.get(windowID).drawPolyline();
258         } else if (func.equals("drawRectangle")) {
259           windows.get(windowID).drawRectangle(intList.get(0), intList.get(1),
260                                               intList.get(2), intList.get(3));
261         } else if (func.equals("setVisible")) {
262           windows.get(windowID).setVisible(boolList.get(0));
263         } else if (func.equals("setAlwaysOnTop")) {
264           windows.get(windowID).setAlwaysOnTop(boolList.get(0));
265         } else if (func.equals("addMessage")) {
266           windows.get(windowID).addMessage(stringList.get(0));
267         } else if (func.equals("addMessageBox")) {
268           windows.get(windowID).addMessageBox();
269         } else if (func.equals("clear")) {
270           windows.get(windowID).clear();
271         } else if (func.equals("setStrokeWidth")) {
272           windows.get(windowID).setStrokeWidth(floatList.get(0));
273         } else if (func.equals("drawEllipse")) {
274           windows.get(windowID).drawEllipse(intList.get(0), intList.get(1),
275                                             intList.get(2), intList.get(3));
276         } else if (func.equals("pen")) {
277           if (intList.size() == 4) {
278             windows.get(windowID).pen(intList.get(0), intList.get(1),
279                                       intList.get(2), intList.get(3));
280           } else {
281             windows.get(windowID).pen(intList.get(0), intList.get(1),
282                                       intList.get(2));
283           }
284         } else if (func.equals("brush")) {
285           if (intList.size() == 4) {
286             windows.get(windowID).brush(intList.get(0), intList.get(1),
287                                         intList.get(2), intList.get(3));
288           } else {
289             windows.get(windowID).brush(intList.get(0), intList.get(1),
290                                         intList.get(2));
291           }
292         } else if (func.equals("textAttributes")) {
293           windows.get(windowID).textAttributes(stringList.get(0),
294                                                intList.get(0),
295                                                boolList.get(0),
296                                                boolList.get(1),
297                                                boolList.get(2));
298         } else if (func.equals("drawText")) {
299           windows.get(windowID).drawText(intList.get(0), intList.get(1),
300                                          stringList.get(0));
301         } else if (func.equals("openImage")) {
302           windows.get(windowID).openImage(stringList.get(0));
303         } else if (func.equals("drawImage")) {
304           windows.get(windowID).drawImage(stringList.get(0),
305                                           intList.get(0), intList.get(1));
306         } else if (func.equals("addMenuBarItem")) {
307           if (boolList.size() > 0) {
308             windows.get(windowID).addMenuBarItem(stringList.get(0),
309                                                  stringList.get(1),
310                                                  intList.get(0),
311                                                  boolList.get(0));
312           } else if (intList.size() > 0) {
313             windows.get(windowID).addMenuBarItem(stringList.get(0),
314                                                  stringList.get(1),
315                                                  intList.get(0));
316           } else {
317             windows.get(windowID).addMenuBarItem(stringList.get(0),
318                                                  stringList.get(1));
319           }
320         } else if (func.equals("addPopupMenuItem")) {
321           if (stringList.size() == 4) {
322             windows.get(windowID).addPopupMenuItem(stringList.get(0),
323                                                    stringList.get(1),
324                                                    intList.get(0),
325                                                    stringList.get(2),
326                                                    stringList.get(3));
327           } else {
328              windows.get(windowID).addPopupMenuItem(stringList.get(0),
329                                                     stringList.get(1));
330           }
331         } else if (func.equals("update")) {
332           windows.get(windowID).update();
333         } else if (func.equals("showInputDialog")) {
334           windows.get(windowID).showInputDialog(stringList.get(0));
335         } else if (func.equals("showYesNoDialog")) {
336           windows.get(windowID).showYesNoDialog(stringList.get(0));
337         } else if (func.equals("zoomRectangle")) {
338           windows.get(windowID).zoomRectangle(intList.get(0), intList.get(1),
339                                               intList.get(2), intList.get(3));
340         } else if (func.equals("createImage")) {
341           windows.get(windowID).createImage(stringList.get(0), intList.get(0),
342                                             intList.get(1), intList.get(2));
343         } else if (func.equals("drawImage")) {
344           windows.get(windowID).drawImage(stringList.get(0),
345                                           intList.get(0), intList.get(1));
346         } else if (func.equals("destroy")) {
347           windows.get(windowID).destroy();
348         }
349         // else for forward compatibility purposes, silently ignore any
350         // unrecognized function call.
351       } else {
352         // No colon. Check for create window.
353         if (idStrs[1].startsWith("= luajava.newInstance")) {
354           while (windows.size() <= windowID) {
355             windows.add(null);
356           }
357           windows.set(windowID, new SVWindow(stringList.get(1),
358                                              intList.get(0), intList.get(1),
359                                              intList.get(2), intList.get(3),
360                                              intList.get(4), intList.get(5),
361                                              intList.get(6)));
362         }
363         // else for forward compatibility purposes, silently ignore any
364         // unrecognized function call.
365       }
366     } else if (inputLine.startsWith("svmain")) {
367         // Startup or end. Startup is a lua bind, which is now a no-op.
368         if (inputLine.startsWith("svmain:exit")) {
369           exit();
370         }
371         // else for forward compatibility purposes, silently ignore any
372         // unrecognized function call.
373     }
374     // else for forward compatibility purposes, silently ignore any
375     // unrecognized function call.
376   }
377 
378   /** Called from the client to make the server exit. */
exit()379   public static void exit() {
380     System.exit(0);
381   }
382 
383   /**
384    * The main function. Sets up LUA and the server connection and then calls the
385    * IOLoop.
386    */
main(String[] args)387   public static void main(String[] args) {
388     if (args.length > 0) {
389       SERVER_PORT = Integer.parseInt(args[0]);
390     }
391     windows = new ArrayList<SVWindow>(100);
392     intPattern = Pattern.compile("[0-9-][0-9]*");
393     floatPattern = Pattern.compile("[0-9-][0-9]*\\.[0-9]*");
394 
395     try {
396       // Open a socket to listen on.
397       ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
398       System.out.println("Socket started on port " + SERVER_PORT);
399 
400       // Wait (blocking) for an incoming connection
401       socket = serverSocket.accept();
402       System.out.println("Client connected");
403 
404       // Setup the streams
405       out = new PrintStream(socket.getOutputStream(), true);
406       in =
407           new BufferedReader(new InputStreamReader(socket.getInputStream(),
408               "UTF8"));
409     } catch (IOException e) {
410       // Something went wrong and we were unable to set up a connection. This is
411       // pretty
412       // much a fatal error.
413       // Note: The server does not get restarted automatically if this happens.
414       e.printStackTrace();
415       System.exit(1);
416     }
417 
418     // Enter the main program loop.
419     IOLoop();
420   }
421 }
422