| // 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<SVWindow> 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<Integer> intList, |
| ArrayList<Float> floatList, |
| ArrayList<String> stringList, |
| ArrayList<Boolean> 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<Integer> intList = new ArrayList<Integer>(4); |
| ArrayList<Float> floatList = new ArrayList<Float>(2); |
| ArrayList<String> stringList = new ArrayList<String>(4); |
| ArrayList<Boolean> boolList = new ArrayList<Boolean>(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<SVWindow>(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(); |
| } |
| } |