// Copyright 2007 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); You may not // use this file except in compliance with the License. You may obtain a copy of // the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by // applicable law or agreed to in writing, software distributed under the // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS // OF ANY KIND, either express or implied. See the License for the specific // language governing permissions and limitations under the License. package com.google.scrollview; import com.google.scrollview.events.SVEvent; import com.google.scrollview.ui.SVImageHandler; import com.google.scrollview.ui.SVWindow; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.regex.Pattern; /** * The ScrollView class is the main class which gets started from the command * line. It sets up LUA and handles the network processing. * @author wanke@google.com */ public class ScrollView { /** The port our server listens at. */ public static int SERVER_PORT = 8461; /** * All SVWindow objects share the same connection stream. The socket is needed * to detect when the connection got closed, in/out are used to send and * receive messages. */ private static Socket socket; private static PrintStream out; public static BufferedReader in; public static float polylineXCoords[]; // The coords being received. public static float polylineYCoords[]; // The coords being received. public static int polylineSize; // The size of the coords arrays. public static int polylineScanned; // The size read so far. private static ArrayList windows; // The id to SVWindow map. private static Pattern intPattern; // For checking integer arguments. private static Pattern floatPattern; // For checking float arguments. /** Keeps track of the number of messages received. */ static int nrInputLines = 0; /** Prints all received messages to the console if true. */ static boolean debugViewNetworkTraffic = false; /** Add a new message to the outgoing queue */ public static void addMessage(SVEvent e) { if (debugViewNetworkTraffic) { System.out.println("(S->c) " + e.toString()); } String str = e.toString(); // Send the whole thing as UTF8. try { byte [] utf8 = str.getBytes("UTF8"); out.write(utf8, 0, utf8.length); } catch (java.io.UnsupportedEncodingException ex) { System.out.println("Oops... can't encode to UTF8... Exiting"); System.exit(0); } out.println(); // Flush the output and check for errors. boolean error = out.checkError(); if (error) { System.out.println("Connection error. Quitting ScrollView Server..."); System.exit(0); } } /** Read one message from client (assuming there are any). */ public static String receiveMessage() throws IOException { return in.readLine(); } /** * The main program loop. Basically loops trough receiving messages and * processing them and then sending messages (if there are any). */ private static void IOLoop() { String inputLine; try { while (!socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown() && socket.isConnected() && socket.isBound()) { inputLine = receiveMessage(); nrInputLines++; if (debugViewNetworkTraffic) { System.out.println("(c->S," + nrInputLines + ")" + inputLine); } if (polylineSize > polylineScanned) { // We are processing a polyline. // Read pairs of coordinates separated by commas. boolean first = true; for (String coordStr : inputLine.split(",")) { int coord = Integer.parseInt(coordStr); if (first) { polylineXCoords[polylineScanned] = coord; } else { polylineYCoords[polylineScanned++] = coord; } first = !first; } assert first; } else if (SVImageHandler.getReadImageData() == false) { // If we are currently not transmitting an image, process this // normally. processInput(inputLine); } // We are still transmitting image data, but there seems to be some // command at the // end of the message attached as well. Thus, we have to split it // accordingly and // first generate the image and afterwards process the remaining // message. else if (inputLine.length() > SVImageHandler.getMissingRemainingBytes()) { String luaCmd = inputLine.substring( SVImageHandler.getMissingRemainingBytes()); String imgData = inputLine.substring(0, SVImageHandler.getMissingRemainingBytes()); SVImageHandler.parseData(imgData); processInput(luaCmd); } else { // We are still in the middle of image data and have not // reached the end yet. SVImageHandler.parseData(inputLine); } } } // Some connection error catch (IOException e) { System.out.println("Connection error. Quitting ScrollView Server..."); } System.exit(0); } // Parse a comma-separated list of arguments into ArrayLists of the // possible types. Each type is stored in order, but the order // distinction between types is lost. // Note that the format is highly constrained to what the client used // to send to LUA: // Quoted string -> String. // true or false -> Boolean. // %f format number -> Float (no %e allowed) // Sequence of digits -> Integer // Nothing else allowed. private static void parseArguments(String argList, ArrayList intList, ArrayList floatList, ArrayList stringList, ArrayList boolList) { // str is only non-null if an argument starts with a single or double // quote. str is set back to null on completion of the string with a // matching quote. If the string contains a comma then str will stay // non-null across multiple argStr values until a matching closing quote. // Backslash escaped quotes do not count as terminating the string. String str = null; for (String argStr : argList.split(",")) { if (str != null) { // Last string was incomplete. Append argStr to it and restore comma. // Execute str += "," + argStr in Java. int length = str.length() + 1 + argStr.length(); StringBuilder appended = new StringBuilder(length); appended.append(str); appended.append(","); appended.append(argStr); str = appended.toString(); } else if (argStr.length() == 0) { continue; } else { char quote = argStr.charAt(0); // If it begins with a quote then it is a string, but may not // end this time if it contained a comma. if (quote == '\'' || quote == '"') { str = argStr; } } if (str != null) { // It began with a quote. Check that it still does. assert str.charAt(0) == '\'' || str.charAt(0) == '"'; int len = str.length(); if (len > 1 && str.charAt(len - 1) == str.charAt(0)) { // We have an ending quote of the right type. Now check that // it is not escaped. Must have an even number of slashes before. int slash = len - 1; while (slash > 0 && str.charAt(slash - 1) == '\\') --slash; if ((len - 1 - slash) % 2 == 0) { // It is now complete. Chop off the quotes and save. // TODO(rays) remove the first backslash of each pair. stringList.add(str.substring(1, len - 1)); str = null; } } // If str is not null here, then we have a string with a comma in it. // Append , and the next argument at the next iteration, but check // that str is null after the loop terminates in case it was an // unterminated string. } else if (floatPattern.matcher(argStr).matches()) { // It is a float. floatList.add(Float.parseFloat(argStr)); } else if (argStr.equals("true")) { boolList.add(true); } else if (argStr.equals("false")) { boolList.add(false); } else if (intPattern.matcher(argStr).matches()) { // Only contains digits so must be an int. intList.add(Integer.parseInt(argStr)); } // else ignore all incompatible arguments for forward compatibility. } // All strings must have been terminated. assert str == null; } /** Executes the LUA command parsed as parameter. */ private static void processInput(String inputLine) { // Execute a function encoded as a LUA statement! Yuk! if (inputLine.charAt(0) == 'w') { // This is a method call on a window. Parse it. String noWLine = inputLine.substring(1); String[] idStrs = noWLine.split("[ :]", 2); int windowID = Integer.parseInt(idStrs[0]); // Find the parentheses. int start = inputLine.indexOf('('); int end = inputLine.lastIndexOf(')'); // Parse the args. ArrayList intList = new ArrayList(4); ArrayList floatList = new ArrayList(2); ArrayList stringList = new ArrayList(4); ArrayList boolList = new ArrayList(3); parseArguments(inputLine.substring(start + 1, end), intList, floatList, stringList, boolList); int colon = inputLine.indexOf(':'); if (colon > 1 && colon < start) { // This is a regular function call. Look for the name and call it. String func = inputLine.substring(colon + 1, start); if (func.equals("drawLine")) { windows.get(windowID).drawLine(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else if (func.equals("createPolyline")) { windows.get(windowID).createPolyline(intList.get(0)); } else if (func.equals("drawPolyline")) { windows.get(windowID).drawPolyline(); } else if (func.equals("drawRectangle")) { windows.get(windowID).drawRectangle(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else if (func.equals("setVisible")) { windows.get(windowID).setVisible(boolList.get(0)); } else if (func.equals("setAlwaysOnTop")) { windows.get(windowID).setAlwaysOnTop(boolList.get(0)); } else if (func.equals("addMessage")) { windows.get(windowID).addMessage(stringList.get(0)); } else if (func.equals("addMessageBox")) { windows.get(windowID).addMessageBox(); } else if (func.equals("clear")) { windows.get(windowID).clear(); } else if (func.equals("setStrokeWidth")) { windows.get(windowID).setStrokeWidth(floatList.get(0)); } else if (func.equals("drawEllipse")) { windows.get(windowID).drawEllipse(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else if (func.equals("pen")) { if (intList.size() == 4) { windows.get(windowID).pen(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else { windows.get(windowID).pen(intList.get(0), intList.get(1), intList.get(2)); } } else if (func.equals("brush")) { if (intList.size() == 4) { windows.get(windowID).brush(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else { windows.get(windowID).brush(intList.get(0), intList.get(1), intList.get(2)); } } else if (func.equals("textAttributes")) { windows.get(windowID).textAttributes(stringList.get(0), intList.get(0), boolList.get(0), boolList.get(1), boolList.get(2)); } else if (func.equals("drawText")) { windows.get(windowID).drawText(intList.get(0), intList.get(1), stringList.get(0)); } else if (func.equals("openImage")) { windows.get(windowID).openImage(stringList.get(0)); } else if (func.equals("drawImage")) { windows.get(windowID).drawImage(stringList.get(0), intList.get(0), intList.get(1)); } else if (func.equals("addMenuBarItem")) { if (boolList.size() > 0) { windows.get(windowID).addMenuBarItem(stringList.get(0), stringList.get(1), intList.get(0), boolList.get(0)); } else if (intList.size() > 0) { windows.get(windowID).addMenuBarItem(stringList.get(0), stringList.get(1), intList.get(0)); } else { windows.get(windowID).addMenuBarItem(stringList.get(0), stringList.get(1)); } } else if (func.equals("addPopupMenuItem")) { if (stringList.size() == 4) { windows.get(windowID).addPopupMenuItem(stringList.get(0), stringList.get(1), intList.get(0), stringList.get(2), stringList.get(3)); } else { windows.get(windowID).addPopupMenuItem(stringList.get(0), stringList.get(1)); } } else if (func.equals("update")) { windows.get(windowID).update(); } else if (func.equals("showInputDialog")) { windows.get(windowID).showInputDialog(stringList.get(0)); } else if (func.equals("showYesNoDialog")) { windows.get(windowID).showYesNoDialog(stringList.get(0)); } else if (func.equals("zoomRectangle")) { windows.get(windowID).zoomRectangle(intList.get(0), intList.get(1), intList.get(2), intList.get(3)); } else if (func.equals("createImage")) { windows.get(windowID).createImage(stringList.get(0), intList.get(0), intList.get(1), intList.get(2)); } else if (func.equals("drawImage")) { windows.get(windowID).drawImage(stringList.get(0), intList.get(0), intList.get(1)); } else if (func.equals("destroy")) { windows.get(windowID).destroy(); } // else for forward compatibility purposes, silently ignore any // unrecognized function call. } else { // No colon. Check for create window. if (idStrs[1].startsWith("= luajava.newInstance")) { while (windows.size() <= windowID) { windows.add(null); } windows.set(windowID, new SVWindow(stringList.get(1), intList.get(0), intList.get(1), intList.get(2), intList.get(3), intList.get(4), intList.get(5), intList.get(6))); } // else for forward compatibility purposes, silently ignore any // unrecognized function call. } } else if (inputLine.startsWith("svmain")) { // Startup or end. Startup is a lua bind, which is now a no-op. if (inputLine.startsWith("svmain:exit")) { exit(); } // else for forward compatibility purposes, silently ignore any // unrecognized function call. } // else for forward compatibility purposes, silently ignore any // unrecognized function call. } /** Called from the client to make the server exit. */ public static void exit() { System.exit(0); } /** * The main function. Sets up LUA and the server connection and then calls the * IOLoop. */ public static void main(String[] args) { if (args.length > 0) { SERVER_PORT = Integer.parseInt(args[0]); } windows = new ArrayList(100); intPattern = Pattern.compile("[0-9-][0-9]*"); floatPattern = Pattern.compile("[0-9-][0-9]*\\.[0-9]*"); try { // Open a socket to listen on. ServerSocket serverSocket = new ServerSocket(SERVER_PORT); System.out.println("Socket started on port " + SERVER_PORT); // Wait (blocking) for an incoming connection socket = serverSocket.accept(); System.out.println("Client connected"); // Setup the streams out = new PrintStream(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8")); } catch (IOException e) { // Something went wrong and we were unable to set up a connection. This is // pretty // much a fatal error. // Note: The server does not get restarted automatically if this happens. e.printStackTrace(); System.exit(1); } // Enter the main program loop. IOLoop(); } }