-
-
Notifications
You must be signed in to change notification settings - Fork 7k
/
Copy pathPApplet.java
698 lines (619 loc) · 22.4 KB
/
PApplet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
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;
/**
* 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;
}
}
/**
* Split the provided String at wherever whitespace occurs. Multiple
* whitespace (extra spaces or tabs or whatever) between items will count as a
* single break.
* <P>
* 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).
*
* <PRE>
* i.e. splitTokens("a b") -> { "a", "b" }
* splitTokens("a b") -> { "a", "b" }
* splitTokens("a\tb") -> { "a", "b" }
* splitTokens("a \t b ") -> { "a", "b" }
* </PRE>
*/
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.
*
* <PRE>
* i.e. splitTokens("a, b", " ,") -> { "a", "b" }
* </PRE>
*
* To include all the whitespace possibilities, use the variable WHITESPACE,
* found in PConstants:
*
* <PRE>
* i.e. splitTokens("a | b", WHITESPACE + "|"); -> { "a", "b" }
* </PRE>
*/
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.
* <P>
* 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.
* <P>
* 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.
* <PRE>
* e.g. String stuff[] = { "apple", "bear", "cat" };
* String list = join(stuff, ", ");
* // list is now "apple, bear, cat"</PRE>
*/
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 <b>file</b> 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).
* <br><br>
* The <b>args</b> 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.
* <br><br>
* 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.
* <br><br>
* 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.
* <br><br>
* For users familiar with Java, this is not quite the same as Runtime.exec(), because the launcher command is prepended. Instead, the <b>exec(String[])</b> 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<String[]> 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] = 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);
return match(what, p);
}
static public String[] match(String what, Pattern pattern) {
Matcher m = pattern.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.
* @throws IOException
*/
static public PrintWriter createWriter(File file) throws IOException {
createPath(file); // make sure in-between folders exist
OutputStream output = new FileOutputStream(file);
try {
if (file.getName().toLowerCase().endsWith(".gz")) {
output = new GZIPOutputStream(output);
}
} catch (IOException e) {
output.close();
throw e;
}
return createWriter(output);
}
/**
* 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.
* <p/>
* 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 <TT>saveXxxx("data/blah.dat")</TT>.
*/
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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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());
}
}
}