package processing.app.legacy; import org.apache.commons.compress.utils.IOUtils; import java.io.*; import java.text.NumberFormat; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public class PApplet { /** Path to sketch folder */ public String sketchPath; //folder; /** * Full name of the Java version (i.e. 1.5.0_11). * Prior to 0125, this was only the first three digits. */ public static final String javaVersionName = System.getProperty("java.version"); /** * Version of Java that's in use, whether 1.1 or 1.3 or whatever, * stored as a float. *

* Note that because this is stored as a float, the values may * not be exactly 1.3 or 1.4. Instead, make sure you're * comparing against 1.3f or 1.4f, which will have the same amount * of error (i.e. 1.40000001). This could just be a double, but * since Processing only uses floats, it's safer for this to be a float * because there's no good way to specify a double with the preproc. */ public static final float javaVersion = new Float(javaVersionName.substring(0, 3)).floatValue(); /** * Current platform in use, one of the * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. */ static public int platform; /** * Name associated with the current 'platform' (see PConstants.platformNames) */ //static public String platformName; static { String osname = System.getProperty("os.name"); if (osname.indexOf("Mac") != -1) { platform = PConstants.MACOSX; } else if (osname.indexOf("Windows") != -1) { platform = PConstants.WINDOWS; } else if (osname.equals("Linux")) { // true for the ibm vm platform = PConstants.LINUX; } else { platform = PConstants.OTHER; } } /** * GIF image of the Processing logo. */ static public final byte[] ICON_IMAGE = { 71, 73, 70, 56, 57, 97, 16, 0, 16, 0, -60, 0, 0, 0, 0, 0, 0, 0, -127, 0, -127, 0, 0, -127, -127, -127, 0, 0, -127, 0, -127, -127, -127, 0, -127, -127, -127, -63, -63, -63, 0, 0, -1, 0, -1, 0, 0, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, 0, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, -7, 4, 9, 0, 0, 16, 0, 44, 0, 0, 0, 0, 16, 0, 16, 0, 0, 5, 75, 32, 36, -118, -57, 96, 14, -57, -88, 66, -27, -23, -90, -86, 43, -97, 99, 59, -65, -30, 125, -77, 3, -14, -4, 8, -109, 15, -120, -22, 61, 78, 15, -124, 15, 25, 28, 28, 93, 63, -45, 115, -22, -116, 90, -83, 82, 89, -44, -103, 61, 44, -91, -54, -89, 19, -111, 50, 18, -51, -55, 1, 73, -121, -53, -79, 77, 43, -101, 12, -74, -30, -99, -24, -94, 16, 0, 59, }; /** * Split the provided String at wherever whitespace occurs. Multiple * whitespace (extra spaces or tabs or whatever) between items will count as a * single break. *

* The whitespace characters are "\t\n\r\f", which are the defaults for * java.util.StringTokenizer, plus the unicode non-breaking space character, * which is found commonly on files created by or used in conjunction with Mac * OS X (character 160, or 0x00A0 in hex). * *

   * i.e. splitTokens("a b") -> { "a", "b" }
   *      splitTokens("a    b") -> { "a", "b" }
   *      splitTokens("a\tb") -> { "a", "b" }
   *      splitTokens("a \t  b  ") -> { "a", "b" }
   * 
*/ static public String[] splitTokens(String what) { return splitTokens(what, PConstants.WHITESPACE); } /** * Splits a string into pieces, using any of the chars in the String 'delim' * as separator characters. For instance, in addition to white space, you * might want to treat commas as a separator. The delimeter characters won't * appear in the returned String array. * *
   * i.e. splitTokens("a, b", " ,") -> { "a", "b" }
   * 
* * To include all the whitespace possibilities, use the variable WHITESPACE, * found in PConstants: * *
   * i.e. splitTokens("a   | b", WHITESPACE + "|");  ->  { "a", "b" }
   * 
*/ static public String[] splitTokens(String what, String delim) { StringTokenizer toker = new StringTokenizer(what, delim); String pieces[] = new String[toker.countTokens()]; int index = 0; while (toker.hasMoreTokens()) { pieces[index++] = toker.nextToken(); } return pieces; } /** * Split a string into pieces along a specific character. Most commonly used * to break up a String along a space or a tab character. *

* This operates differently than the others, where the single delimeter is * the only breaking point, and consecutive delimeters will produce an empty * string (""). This way, one can split on tab characters, but maintain the * column alignments (of say an excel file) where there are empty columns. */ static public String[] split(String what, char delim) { // do this so that the exception occurs inside the user's // program, rather than appearing to be a bug inside split() if (what == null) return null; char chars[] = what.toCharArray(); int splitCount = 0; // 1; for (int i = 0; i < chars.length; i++) { if (chars[i] == delim) splitCount++; } if (splitCount == 0) { String splits[] = new String[1]; splits[0] = new String(what); return splits; } String splits[] = new String[splitCount + 1]; int splitIndex = 0; int startIndex = 0; for (int i = 0; i < chars.length; i++) { if (chars[i] == delim) { splits[splitIndex++] = new String(chars, startIndex, i - startIndex); startIndex = i + 1; } } splits[splitIndex] = new String(chars, startIndex, chars.length - startIndex); return splits; } static public String[] subset(String list[], int start, int count) { String output[] = new String[count]; System.arraycopy(list, start, output, 0, count); return output; } /** * Join an array of Strings together as a single String, * separated by the whatever's passed in for the separator. */ static public String join(String str[], char separator) { return join(str, String.valueOf(separator)); } /** * Join an array of Strings together as a single String, * separated by the whatever's passed in for the separator. *

* To use this on numbers, first pass the array to nf() or nfs() * to get a list of String objects, then use join on that. *

   * e.g. String stuff[] = { "apple", "bear", "cat" };
   *      String list = join(stuff, ", ");
   *      // list is now "apple, bear, cat"
*/ static public String join(String str[], String separator) { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < str.length; i++) { if (i != 0) buffer.append(separator); buffer.append(str[i]); } return buffer.toString(); } /** * Parse a String into an int value. Returns 0 if the value is bad. */ static final public int parseInt(String what) { return parseInt(what, 0); } /** * Parse a String to an int, and provide an alternate value that * should be used when the number is invalid. */ static final public int parseInt(String what, int otherwise) { try { int offset = what.indexOf('.'); if (offset == -1) { return Integer.parseInt(what); } else { return Integer.parseInt(what.substring(0, offset)); } } catch (NumberFormatException e) { } return otherwise; } /** * Make an array of int elements from an array of String objects. * If the String can't be parsed as a number, it will be set to zero. * * String s[] = { "1", "300", "44" }; * int numbers[] = parseInt(s); * * numbers will contain { 1, 300, 44 } */ static public int[] parseInt(String what[]) { return parseInt(what, 0); } /** * Make an array of int elements from an array of String objects. * If the String can't be parsed as a number, its entry in the * array will be set to the value of the "missing" parameter. * * String s[] = { "1", "300", "apple", "44" }; * int numbers[] = parseInt(s, 9999); * * numbers will contain { 1, 300, 9999, 44 } */ static public int[] parseInt(String what[], int missing) { int output[] = new int[what.length]; for (int i = 0; i < what.length; i++) { try { output[i] = Integer.parseInt(what[i]); } catch (NumberFormatException e) { output[i] = missing; } } return output; } static public String[] loadStrings(File file) { InputStream is = null; try { is = createInput(file); if (is != null) return loadStrings(is); return null; } finally { IOUtils.closeQuietly(is); } } static public String[] loadStrings(InputStream input) { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); String lines[] = new String[100]; int lineCount = 0; String line = null; while ((line = reader.readLine()) != null) { if (lineCount == lines.length) { String temp[] = new String[lineCount << 1]; System.arraycopy(lines, 0, temp, 0, lineCount); lines = temp; } lines[lineCount++] = line; } if (lineCount == lines.length) { return lines; } // resize array to appropriate amount for these lines String output[] = new String[lineCount]; System.arraycopy(lines, 0, output, 0, lineCount); return output; } catch (IOException e) { e.printStackTrace(); //throw new RuntimeException("Error inside loadStrings()"); } finally { IOUtils.closeQuietly(reader); } return null; } public void saveStrings(String filename, String strings[]) { saveStrings(saveFile(filename), strings); } static public void saveStrings(File file, String strings[]) { OutputStream outputStream = null; try { outputStream = createOutput(file); saveStrings(outputStream, strings); } finally { IOUtils.closeQuietly(outputStream); } } static public void saveStrings(OutputStream output, String strings[]) { PrintWriter writer = null; try { writer = createWriter(output); if (writer == null) { return; } for (String string : strings) { writer.println(string); } writer.flush(); } finally { IOUtils.closeQuietly(writer); } } static public int[] expand(int list[]) { return expand(list, list.length << 1); } static public int[] expand(int list[], int newSize) { int temp[] = new int[newSize]; System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); return temp; } static final public String hex(int what, int digits) { String stuff = Integer.toHexString(what).toUpperCase(); int length = stuff.length(); if (length > digits) { return stuff.substring(length - digits); } else if (length < digits) { return "00000000".substring(8 - (digits-length)) + stuff; } return stuff; } static public final int constrain(int amt, int low, int high) { return (amt < low) ? low : ((amt > high) ? high : amt); } static public final float constrain(float amt, float low, float high) { return (amt < low) ? low : ((amt > high) ? high : amt); } /** * Attempts to open an application or file using your platform's launcher. The file parameter is a String specifying the file name and location. The location parameter must be a full path name, or the name of an executable in the system's PATH. In most cases, using a full path is the best option, rather than relying on the system PATH. Be sure to make the file executable before attempting to open it (chmod +x). *

* The args parameter is a String or String array which is passed to the command line. If you have multiple parameters, e.g. an application and a document, or a command with multiple switches, use the version that takes a String array, and place each individual item in a separate element. *

* If args is a String (not an array), then it can only be a single file or application with no parameters. It's not the same as executing that String using a shell. For instance, open("jikes -help") will not work properly. *

* This function behaves differently on each platform. On Windows, the parameters are sent to the Windows shell via "cmd /c". On Mac OS X, the "open" command is used (type "man open" in Terminal.app for documentation). On Linux, it first tries gnome-open, then kde-open, but if neither are available, it sends the command to the shell without any alterations. *

* For users familiar with Java, this is not quite the same as Runtime.exec(), because the launcher command is prepended. Instead, the exec(String[]) function is a shortcut for Runtime.getRuntime.exec(String[]). * * @webref input:files * @param filename name of the file * @usage Application */ static public void open(String filename) { open(new String[] { filename }); } static String openLauncher; /** * Launch a process using a platforms shell. This version uses an array * to make it easier to deal with spaces in the individual elements. * (This avoids the situation of trying to put single or double quotes * around different bits). * * @param list of commands passed to the command line */ static public Process open(String argv[]) { String[] params = null; if (platform == PConstants.WINDOWS) { // just launching the .html file via the shell works // but make sure to chmod +x the .html files first // also place quotes around it in case there's a space // in the user.dir part of the url params = new String[] { "cmd", "/c" }; } else if (platform == PConstants.MACOSX) { params = new String[] { "open" }; } else if (platform == PConstants.LINUX) { if (openLauncher == null) { // Attempt to use gnome-open try { Process p = Runtime.getRuntime().exec(new String[] { "gnome-open" }); /*int result =*/ p.waitFor(); // Not installed will throw an IOException (JDK 1.4.2, Ubuntu 7.04) openLauncher = "gnome-open"; } catch (Exception e) { } } if (openLauncher == null) { // Attempt with kde-open try { Process p = Runtime.getRuntime().exec(new String[] { "kde-open" }); /*int result =*/ p.waitFor(); openLauncher = "kde-open"; } catch (Exception e) { } } if (openLauncher == null) { System.err.println("Could not find gnome-open or kde-open, " + "the open() command may not work."); } if (openLauncher != null) { params = new String[] { openLauncher }; } //} else { // give up and just pass it to Runtime.exec() //open(new String[] { filename }); //params = new String[] { filename }; } if (params != null) { // If the 'open', 'gnome-open' or 'cmd' are already included if (params[0].equals(argv[0])) { // then don't prepend those params again return exec(argv); } else { params = concat(params, argv); return exec(params); } } else { return exec(argv); } } static public Process exec(String[] argv) { try { return Runtime.getRuntime().exec(argv); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Could not open " + join(argv, ' ')); } } static public String[] concat(String a[], String b[]) { String c[] = new String[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } /** * Identical to match(), except that it returns an array of all matches in * the specified String, rather than just the first. */ static public String[][] matchAll(String what, String regexp) { Pattern p = Pattern.compile(regexp, Pattern.MULTILINE | Pattern.DOTALL); Matcher m = p.matcher(what); ArrayList results = new ArrayList(); int count = m.groupCount() + 1; while (m.find()) { String[] groups = new String[count]; for (int i = 0; i < count; i++) { groups[i] = m.group(i); } results.add(groups); } if (results.isEmpty()) { return null; } String[][] matches = new String[results.size()][count]; for (int i = 0; i < matches.length; i++) { matches[i] = (String[]) results.get(i); } return matches; } /** * Match a string with a regular expression, and returns the match as an * array. The first index is the matching expression, and array elements * [1] and higher represent each of the groups (sequences found in parens). * * This uses multiline matching (Pattern.MULTILINE) and dotall mode * (Pattern.DOTALL) by default, so that ^ and $ match the beginning and * end of any lines found in the source, and the . operator will also * pick up newline characters. */ static public String[] match(String what, String regexp) { Pattern p = Pattern.compile(regexp, Pattern.MULTILINE | Pattern.DOTALL); Matcher m = p.matcher(what); if (m.find()) { int count = m.groupCount() + 1; String[] groups = new String[count]; for (int i = 0; i < count; i++) { groups[i] = m.group(i); } return groups; } return null; } /** * Integer number formatter. */ static private NumberFormat int_nf; static private int int_nf_digits; static private boolean int_nf_commas; static public String[] nf(int num[], int digits) { String formatted[] = new String[num.length]; for (int i = 0; i < formatted.length; i++) { formatted[i] = nf(num[i], digits); } return formatted; } static public String nf(int num, int digits) { if ((int_nf != null) && (int_nf_digits == digits) && !int_nf_commas) { return int_nf.format(num); } int_nf = NumberFormat.getInstance(); int_nf.setGroupingUsed(false); // no commas int_nf_commas = false; int_nf.setMinimumIntegerDigits(digits); int_nf_digits = digits; return int_nf.format(num); } static final public String[] str(int x[]) { String s[] = new String[x.length]; for (int i = 0; i < x.length; i++) s[i] = String.valueOf(x[i]); return s; } /** * I want to print lines to a file. I have RSI from typing these * eight lines of code so many times. */ static public PrintWriter createWriter(File file) { try { createPath(file); // make sure in-between folders exist OutputStream output = new FileOutputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { output = new GZIPOutputStream(output); } return createWriter(output); } catch (Exception e) { if (file == null) { throw new RuntimeException("File passed to createWriter() was null"); } else { e.printStackTrace(); throw new RuntimeException("Couldn't create a writer for " + file.getAbsolutePath()); } } //return null; } /** * I want to print lines to a file. Why am I always explaining myself? * It's the JavaSoft API engineers who need to explain themselves. */ static public PrintWriter createWriter(OutputStream output) { try { OutputStreamWriter osw = new OutputStreamWriter(output, "UTF-8"); return new PrintWriter(osw); } catch (UnsupportedEncodingException e) { } // not gonna happen return null; } static public InputStream createInput(File file) { if (file == null) { throw new IllegalArgumentException("File passed to createInput() was null"); } try { InputStream input = new FileInputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { return new GZIPInputStream(input); } return input; } catch (IOException e) { System.err.println("Could not createInput() for " + file); e.printStackTrace(); return null; } } /** * Returns a path inside the applet folder to save to. Like sketchPath(), * but creates any in-between folders so that things save properly. *

* All saveXxxx() functions use the path to the sketch folder, rather than * its data folder. Once exported, the data folder will be found inside the * jar file of the exported application or applet. In this case, it's not * possible to save data into the jar file, because it will often be running * from a server, or marked in-use if running from a local file system. * With this in mind, saving to the data path doesn't make sense anyway. * If you know you're running locally, and want to save to the data folder, * use saveXxxx("data/blah.dat"). */ public String savePath(String where) { if (where == null) return null; String filename = sketchPath(where); createPath(filename); return filename; } /** * Identical to savePath(), but returns a File object. */ public File saveFile(String where) { return new File(savePath(where)); } /** * Similar to createInput() (formerly openStream), this creates a Java * OutputStream for a given filename or path. The file will be created in * the sketch folder, or in the same folder as an exported application. *

* If the path does not exist, intermediate folders will be created. If an * exception occurs, it will be printed to the console, and null will be * returned. *

* Future releases may also add support for handling HTTP POST via this * method (for better symmetry with createInput), however that's maybe a * little too clever (and then we'd have to add the same features to the * other file functions like createWriter). Who you callin' bloated? */ public OutputStream createOutput(String filename) { return createOutput(saveFile(filename)); } static public OutputStream createOutput(File file) { try { createPath(file); // make sure the path exists FileOutputStream fos = new FileOutputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { return new GZIPOutputStream(fos); } return fos; } catch (IOException e) { e.printStackTrace(); } return null; } /** * Prepend the sketch folder path to the filename (or path) that is * passed in. External libraries should use this function to save to * the sketch folder. *

* Note that when running as an applet inside a web browser, * the sketchPath will be set to null, because security restrictions * prevent applets from accessing that information. *

* This will also cause an error if the sketch is not inited properly, * meaning that init() was never called on the PApplet when hosted * my some other main() or by other code. For proper use of init(), * see the examples in the main description text for PApplet. */ public String sketchPath(String where) { if (sketchPath == null) { return where; // throw new RuntimeException("The applet was not inited properly, " + // "or security restrictions prevented " + // "it from determining its path."); } // isAbsolute() could throw an access exception, but so will writing // to the local disk using the sketch path, so this is safe here. // for 0120, added a try/catch anyways. try { if (new File(where).isAbsolute()) return where; } catch (Exception e) { } return sketchPath + File.separator + where; } /** * Takes a path and creates any in-between folders if they don't * already exist. Useful when trying to save to a subfolder that * may not actually exist. */ static public void createPath(String path) { createPath(new File(path)); } static public void createPath(File file) { try { String parent = file.getParent(); if (parent != null) { File unit = new File(parent); if (!unit.exists()) unit.mkdirs(); } } catch (SecurityException se) { System.err.println("You don't have permissions to create " + file.getAbsolutePath()); } } }