mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-10 21:24:12 +01:00
647 lines
21 KiB
Java
647 lines
21 KiB
Java
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-05 Ben Fry and Casey Reas
|
|
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
package processing.app;
|
|
|
|
import processing.core.*;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.io.*;
|
|
import java.lang.reflect.*;
|
|
import java.util.*;
|
|
|
|
import com.oroinc.text.regex.*;
|
|
|
|
|
|
/**
|
|
* Runs a compiled java applet, whether as an external application
|
|
* or internally as an applet object in a window.
|
|
*/
|
|
public class Runner implements MessageConsumer {
|
|
|
|
PApplet applet;
|
|
RunnerException exception;
|
|
Window window;
|
|
PrintStream leechErr;
|
|
//String className;
|
|
|
|
Editor editor;
|
|
Sketch sketch;
|
|
|
|
boolean newMessage;
|
|
int messageLineCount;
|
|
boolean foundMessageSource;
|
|
|
|
Process process;
|
|
SystemOutSiphon processInput;
|
|
OutputStream processOutput;
|
|
MessageSiphon processError;
|
|
|
|
|
|
public Runner(Sketch sketch, Editor editor) {
|
|
this.sketch = sketch;
|
|
this.editor = editor;
|
|
}
|
|
|
|
|
|
public void start(Point windowLocation) throws RunnerException {
|
|
//System.out.println(" externalRuntime is " + sketch.externalRuntime);
|
|
/* this.leechErr = new PrintStream(new MessageStream(this));
|
|
|
|
try {
|
|
if (editor.presenting) {
|
|
startPresenting();
|
|
|
|
} else if (sketch.externalRuntime) {
|
|
startExternalRuntime(windowLocation);
|
|
|
|
} else {
|
|
startInternal(windowLocation);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
// this will pass through to the first part of message
|
|
// this handles errors that happen inside setup()
|
|
e.printStackTrace();
|
|
|
|
// make sure applet is in use
|
|
if (applet != null) applet.finished = true;
|
|
|
|
leechErr.println(PApplet.LEECH_WAKEUP);
|
|
e.printStackTrace(this.leechErr);
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
public void startPresenting() throws Exception {
|
|
Vector params = new Vector();
|
|
|
|
params.add("java");
|
|
|
|
String options = Preferences.get("run.options");
|
|
/* if (options.length() > 0) {
|
|
String pieces[] = PApplet.split(options, ' ');
|
|
for (int i = 0; i < pieces.length; i++) {
|
|
String p = pieces[i].trim();
|
|
if (p.length() > 0) {
|
|
params.add(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
params.add("-Djava.library.path=" +
|
|
sketch.libraryPath +
|
|
File.pathSeparator +
|
|
System.getProperty("java.library.path"));
|
|
|
|
params.add("-cp");
|
|
params.add(sketch.classPath + Sketchbook.librariesClassPath);
|
|
|
|
params.add("processing.core.PApplet");
|
|
|
|
params.add(PApplet.ARGS_EXTERNAL);
|
|
params.add(PApplet.ARGS_PRESENT);
|
|
params.add(PApplet.ARGS_PRESENT_STOP_COLOR + "=" +
|
|
Preferences.get("run.present.stop.color"));
|
|
params.add(PApplet.ARGS_BGCOLOR + "=" +
|
|
Preferences.get("run.present.bgcolor"));
|
|
params.add(PApplet.ARGS_DISPLAY + "=" +
|
|
Preferences.get("run.display"));
|
|
params.add(PApplet.ARGS_SKETCH_FOLDER + "=" +
|
|
sketch.folder.getAbsolutePath());
|
|
params.add(sketch.mainClassName);
|
|
|
|
String command[] = new String[params.size()];
|
|
params.copyInto(command);
|
|
//PApplet.println(command);
|
|
|
|
process = Runtime.getRuntime().exec(command);
|
|
processInput = new SystemOutSiphon(process.getInputStream());
|
|
processError = new MessageSiphon(process.getErrorStream(), this);
|
|
processOutput = process.getOutputStream();
|
|
*/
|
|
}
|
|
|
|
|
|
public void startExternalRuntime(Point windowLocation) throws Exception {
|
|
// if there was a saved location (this guy has been run more than
|
|
// once) then windowLocation will be set to the last position of
|
|
// the sketch window. this will be passed to the PApplet runner
|
|
// using something like --external=e30,20 where the e stands for
|
|
// exact. otherwise --external=x,y for just the regular positioning.
|
|
/* Point editorLocation = editor.getLocation();
|
|
String location =
|
|
(windowLocation != null) ?
|
|
(PApplet.ARGS_LOCATION + "=" +
|
|
windowLocation.x + "," + windowLocation.y) :
|
|
(PApplet.ARGS_EDITOR_LOCATION + "=" +
|
|
editorLocation.x + "," + editorLocation.y);
|
|
|
|
// this as prefix made the code folder bug go away, but killed stdio
|
|
//"cmd", "/c", "start",
|
|
|
|
// all params have to be stored as separate items,
|
|
// so a growable array needs to be used. i.e. -Xms128m -Xmx1024m
|
|
// will throw an error if it's shoved into a single array element
|
|
Vector params = new Vector();
|
|
|
|
params.add("java");
|
|
|
|
String options = Preferences.get("run.options");
|
|
if (options.length() > 0) {
|
|
String pieces[] = PApplet.split(options, ' ');
|
|
for (int i = 0; i < pieces.length; i++) {
|
|
String p = pieces[i].trim();
|
|
if (p.length() > 0) {
|
|
params.add(p);
|
|
}
|
|
}
|
|
}
|
|
// sketch.libraryPath might be ""
|
|
// librariesClassPath will always have sep char prepended
|
|
params.add("-Djava.library.path=" +
|
|
sketch.libraryPath +
|
|
File.pathSeparator +
|
|
System.getProperty("java.library.path"));
|
|
|
|
params.add("-cp");
|
|
params.add(sketch.classPath + Sketchbook.librariesClassPath);
|
|
|
|
params.add("processing.core.PApplet");
|
|
|
|
params.add(location);
|
|
params.add(PApplet.ARGS_EXTERNAL);
|
|
params.add(PApplet.ARGS_DISPLAY + "=" +
|
|
Preferences.get("run.display"));
|
|
params.add(PApplet.ARGS_SKETCH_FOLDER + "=" +
|
|
sketch.folder.getAbsolutePath());
|
|
params.add(sketch.mainClassName);
|
|
|
|
String command[] = new String[params.size()];
|
|
params.copyInto(command);
|
|
//PApplet.println(command);
|
|
|
|
process = Runtime.getRuntime().exec(command);
|
|
processInput = new SystemOutSiphon(process.getInputStream());
|
|
processError = new MessageSiphon(process.getErrorStream(), this);
|
|
processOutput = process.getOutputStream();
|
|
*/ }
|
|
|
|
|
|
public void startInternal(Point windowLocation) throws Exception {
|
|
/* Point editorLocation = editor.getLocation();
|
|
//Insets editorInsets = editor.getInsets();
|
|
|
|
int windowX = editorLocation.x;
|
|
int windowY = editorLocation.y + editor.getInsets().top;
|
|
|
|
RunnerClassLoader loader = new RunnerClassLoader();
|
|
Class c = loader.loadClass(sketch.mainClassName);
|
|
applet = (PApplet) c.newInstance();
|
|
|
|
window = new Frame(sketch.name); // use ugly window
|
|
((Frame)window).setResizable(false);
|
|
if (editor.icon != null) {
|
|
((Frame)window).setIconImage(editor.icon);
|
|
}
|
|
window.pack(); // to get a peer, size set later, need for insets
|
|
|
|
applet.leechErr = leechErr;
|
|
applet.folder = sketch.folder.getAbsolutePath();
|
|
applet.frame = (Frame) window;
|
|
|
|
applet.init();
|
|
//applet.start();
|
|
|
|
while ((applet.width == 0) && !applet.finished) {
|
|
try {
|
|
if (applet.exception != null) {
|
|
throw new RunnerException(applet.exception.getMessage());
|
|
}
|
|
Thread.sleep(5);
|
|
} catch (InterruptedException e) { }
|
|
}
|
|
|
|
window.addWindowListener(new WindowAdapter() {
|
|
public void windowClosing(WindowEvent e) {
|
|
stop();
|
|
editor.doClose();
|
|
}
|
|
});
|
|
|
|
applet.addKeyListener(new KeyAdapter() {
|
|
public void keyPressed(KeyEvent e) {
|
|
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
|
stop();
|
|
editor.doClose();
|
|
}
|
|
}
|
|
});
|
|
|
|
window.add(applet);
|
|
|
|
Dimension screen =
|
|
Toolkit.getDefaultToolkit().getScreenSize();
|
|
|
|
window.setLayout(null);
|
|
Insets insets = window.getInsets();
|
|
|
|
int minW = Preferences.getInteger("run.window.width.minimum");
|
|
int minH = Preferences.getInteger("run.window.height.minimum");
|
|
int windowW =
|
|
Math.max(applet.width, minW) + insets.left + insets.right;
|
|
int windowH =
|
|
Math.max(applet.height, minH) + insets.top + insets.bottom;
|
|
|
|
if (windowX - windowW > 10) { // if it fits to the left of the window
|
|
window.setBounds(windowX - windowW, windowY, windowW, windowH);
|
|
|
|
} else { // if it fits inside the editor window
|
|
windowX = editorLocation.x + Preferences.GRID_SIZE * 2; // 66
|
|
windowY = editorLocation.y + Preferences.GRID_SIZE * 2; // 66
|
|
|
|
if ((windowX + windowW > screen.width - Preferences.GRID_SIZE) ||
|
|
(windowY + windowH > screen.height - Preferences.GRID_SIZE)) {
|
|
// otherwise center on screen
|
|
windowX = (screen.width - windowW) / 2;
|
|
windowY = (screen.height - windowH) / 2;
|
|
}
|
|
window.setBounds(windowX, windowY, windowW, windowH); //ww, wh);
|
|
}
|
|
|
|
Color windowBgColor = Preferences.getColor("run.window.bgcolor");
|
|
window.setBackground(windowBgColor);
|
|
|
|
int usableH = windowH - insets.top - insets.bottom;
|
|
applet.setBounds((windowW - applet.width)/2,
|
|
insets.top + (usableH - applet.height) / 2,
|
|
windowW, windowH);
|
|
|
|
applet.setVisible(true); // no effect
|
|
if (windowLocation != null) {
|
|
window.setLocation(windowLocation);
|
|
}
|
|
window.show();
|
|
applet.requestFocus(); // necessary for key events
|
|
*/
|
|
}
|
|
|
|
|
|
public void stop() {
|
|
/* // check for null in case stop is called during compilation
|
|
if (applet != null) {
|
|
applet.stop();
|
|
|
|
// above avoids NullPointerExceptions
|
|
// but still threading is too complex, and so
|
|
// some boogers are being left behind
|
|
applet = null;
|
|
|
|
} else if (process != null) { // running externally
|
|
try {
|
|
processOutput.write(PApplet.EXTERNAL_STOP);
|
|
processOutput.flush();
|
|
|
|
} catch (IOException e) {
|
|
close();
|
|
}
|
|
}
|
|
|
|
*/
|
|
}
|
|
|
|
|
|
public void close() {
|
|
/* //if (window != null) window.hide();
|
|
if (window != null) {
|
|
//System.err.println("disposing window");
|
|
window.dispose();
|
|
window = null;
|
|
}
|
|
|
|
if (process != null) {
|
|
try {
|
|
process.destroy();
|
|
} catch (Exception e) {
|
|
//System.err.println("(ignored) error while destroying");
|
|
//e.printStackTrace();
|
|
}
|
|
process = null;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
// made synchronized for rev 87
|
|
synchronized public void message(String s) {
|
|
//System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length());
|
|
|
|
// this eats the CRLFs on the lines.. oops.. do it later
|
|
//if (s.trim().length() == 0) return;
|
|
|
|
// this is PApplet sending a message (via System.out.println)
|
|
// that signals that the applet has been quit.
|
|
// if (s.indexOf(PApplet.EXTERNAL_QUIT) == 0) {
|
|
//System.out.println("external: quit");
|
|
// editor.doClose();
|
|
// return;
|
|
// }
|
|
|
|
// this is the PApplet sending us a message that the applet
|
|
// is being moved to a new window location
|
|
// if (s.indexOf(PApplet.EXTERNAL_MOVE) == 0) {
|
|
// String nums = s.substring(s.indexOf(' ') + 1).trim();
|
|
// int space = nums.indexOf(' ');
|
|
// int left = Integer.parseInt(nums.substring(0, space));
|
|
// int top = Integer.parseInt(nums.substring(space + 1));
|
|
// editor.appletLocation = new Point(left, top);
|
|
// //System.out.println("external: move to " + left + " " + top);
|
|
//// return;
|
|
// }
|
|
|
|
// this is PApplet sending a message saying "i'm about to spew
|
|
// a stack trace because an error occurred during PApplet.run()"
|
|
// if (s.indexOf(PApplet.LEECH_WAKEUP) == 0) {
|
|
// newMessage being set to 'true' means that the next time
|
|
// message() is called, expect the first line of the actual
|
|
// // error message & stack trace to be sent from the applet.
|
|
// newMessage = true;
|
|
// return; // this line ignored
|
|
// }
|
|
|
|
// these are used for debugging, in case there are concerns
|
|
// that some errors aren't coming through properly
|
|
/*
|
|
if (s.length() > 2) {
|
|
System.err.println(newMessage);
|
|
System.err.println("message " + s.length() + ":" + s);
|
|
}
|
|
*/
|
|
// always shove out the mesage, since it might not fall under
|
|
// the same setup as we're expecting
|
|
System.err.print(s);
|
|
//System.err.println("[" + s.length() + "] " + s);
|
|
System.err.flush();
|
|
|
|
// exit here because otherwise the exception name
|
|
// may be titled with a blank string
|
|
if (s.trim().length() == 0) return;
|
|
|
|
// annoying, because it seems as though the terminators
|
|
// aren't being sent properly
|
|
//System.err.println(s);
|
|
|
|
//if (newMessage && s.length() > 2) {
|
|
if (newMessage) {
|
|
exception = new RunnerException(s); // type of java ex
|
|
exception.hideStackTrace = true;
|
|
//System.out.println("setting ex type to " + s);
|
|
newMessage = false;
|
|
foundMessageSource = false;
|
|
messageLineCount = 0;
|
|
|
|
} else {
|
|
messageLineCount++;
|
|
|
|
/*
|
|
java.lang.NullPointerException
|
|
at javatest.<init>(javatest.java:5)
|
|
at Temporary_2425_1153.draw(Temporary_2425_1153.java:11)
|
|
at PApplet.nextFrame(PApplet.java:481)
|
|
at PApplet.run(PApplet.java:428)
|
|
at java.lang.Thread.run(Unknown Source)
|
|
*/
|
|
|
|
if (!foundMessageSource) {
|
|
// " at javatest.<init>(javatest.java:5)"
|
|
// -> "javatest.<init>(javatest.java:5)"
|
|
int afterAt = s.indexOf("at") + 3;
|
|
//if (afterAt == -1) {
|
|
if (afterAt == 2) { // means indexOf was -1
|
|
//System.err.println(s); // stop double-printing exceptions
|
|
return;
|
|
}
|
|
s = s.substring(afterAt + 1);
|
|
|
|
// "javatest.<init>(javatest.java:5)"
|
|
// -> "javatest.<init>" and "(javatest.java:5)"
|
|
int startParen = s.indexOf('(');
|
|
// at javatest.<init>(javatest.java:5)
|
|
String pkgClassFxn = null;
|
|
//String fileLine = null;
|
|
int codeIndex = -1;
|
|
int lineNumber = -1;
|
|
|
|
if (startParen == -1) {
|
|
pkgClassFxn = s;
|
|
|
|
} else {
|
|
pkgClassFxn = s.substring(0, startParen);
|
|
// "(javatest.java:5)"
|
|
String fileAndLine = s.substring(startParen + 1);
|
|
int stopParen = fileAndLine.indexOf(')');
|
|
//fileAndLine = fileAndLine.substring(0, fileAndLine.length() - 1);
|
|
fileAndLine = fileAndLine.substring(0, stopParen);
|
|
//System.out.println("file 'n line " + fileAndLine);
|
|
|
|
//if (!fileAndLine.equals("Unknown Source")) {
|
|
// "javatest.java:5"
|
|
int colonIndex = fileAndLine.indexOf(':');
|
|
if (colonIndex != -1) {
|
|
String filename = fileAndLine.substring(0, colonIndex);
|
|
// "javatest.java" and "5"
|
|
//System.out.println("filename = " + filename);
|
|
//System.out.println("pre0 = " + sketch.code[0].preprocName);
|
|
//for (int i = 0; i < sketch.codeCount; i++) {
|
|
//System.out.println(i + " " + sketch.code[i].lineOffset + " " +
|
|
// sketch.code[i].preprocName);
|
|
//}
|
|
lineNumber =
|
|
Integer.parseInt(fileAndLine.substring(colonIndex + 1)) - 1;
|
|
|
|
for (int i = 0; i < sketch.codeCount; i++) {
|
|
SketchCode code = sketch.code[i];
|
|
//System.out.println(code.preprocName + " " + lineNumber + " " +
|
|
// code.preprocOffset);
|
|
if (((code.preprocName == null) &&
|
|
(lineNumber >= code.preprocOffset)) ||
|
|
((code.preprocName != null) &&
|
|
code.preprocName.equals(filename))) {
|
|
codeIndex = i;
|
|
//System.out.println("got codeindex " + codeIndex);
|
|
//break;
|
|
//} else if (
|
|
}
|
|
}
|
|
|
|
if (codeIndex != -1) {
|
|
//System.out.println("got line num " + lineNumber);
|
|
// in case this was a tab that got embedded into the main .java
|
|
lineNumber -= sketch.code[codeIndex].preprocOffset;
|
|
|
|
// this may have a paren on the end, if so need to strip
|
|
// down to just the digits
|
|
/*
|
|
int lastNumberIndex = colonIndex + 1;
|
|
while ((lastNumberIndex < fileAndLine.length()) &&
|
|
Character.isDigit(fileAndLine.charAt(lastNumberIndex))) {
|
|
lastNumberIndex++;
|
|
}
|
|
*/
|
|
|
|
// lineNumber is 1-indexed, but editor wants zero-indexed
|
|
// getMessage() will be what's shown in the editor
|
|
exception =
|
|
new RunnerException(exception.getMessage(),
|
|
codeIndex, lineNumber, -1);
|
|
exception.hideStackTrace = true;
|
|
foundMessageSource = true;
|
|
}
|
|
}
|
|
}
|
|
editor.error(exception);
|
|
|
|
/*
|
|
int index = s.indexOf(className + ".java");
|
|
if (index != -1) {
|
|
int len = (className + ".java").length();
|
|
String lineNumberStr = s.substring(index + len + 1);
|
|
index = lineNumberStr.indexOf(')');
|
|
lineNumberStr = lineNumberStr.substring(0, index);
|
|
try {
|
|
exception.line = Integer.parseInt(lineNumberStr) - 1; //2;
|
|
} catch (NumberFormatException e) { }
|
|
//e.printStackTrace(); // a recursive error waiting to happen?
|
|
// if nfe occurs, who cares, still send the error on up
|
|
editor.error(exception);
|
|
*/
|
|
|
|
/*
|
|
// WARNING THESE ARE DISABLED!!
|
|
} else if ((index = s.indexOf(className + ".class")) != -1) {
|
|
// code to check for:
|
|
// at Temporary_484_3845.loop(Compiled Code)
|
|
// would also probably get:
|
|
// at Temporary_484_3845.loop
|
|
// which (i believe) is used by the mac and/or jview
|
|
String functionStr = s.substring(index +
|
|
(className + ".class").length() + 1);
|
|
index = functionStr.indexOf('(');
|
|
if (index != -1) {
|
|
functionStr = functionStr.substring(0, index);
|
|
}
|
|
exception = new RunnerException(//"inside \"" + functionStr + "()\": " +
|
|
exception.getMessage() +
|
|
" inside " + functionStr + "() " +
|
|
"[add Compiler.disable() to setup()]");
|
|
editor.error(exception);
|
|
// this will fall through in tihs example:
|
|
// at Temporary_4636_9696.pootie(Compiled Code)
|
|
// at Temporary_4636_9696.loop(Temporary_4636_9696.java:24)
|
|
// because pootie() (re)sets the exception title
|
|
// and throws it, but then the line number gets set
|
|
// because of the line that comes after
|
|
*/
|
|
|
|
} else if (messageLineCount > 10) { // 5 -> 10 for 0088
|
|
// this means the class name may not be mentioned
|
|
// in the stack trace.. this is just a general purpose
|
|
// error, but needs to make it through anyway.
|
|
// so if five lines have gone past, might as well signal
|
|
messageLineCount = -100;
|
|
exception = new RunnerException(exception.getMessage());
|
|
exception.hideStackTrace = true;
|
|
editor.error(exception);
|
|
|
|
} else {
|
|
//System.err.print(s);
|
|
}
|
|
//System.out.println("got it " + s);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Siphons from an InputStream of System.out (from a Process)
|
|
* and sends it to the real System.out.
|
|
*/
|
|
class SystemOutSiphon implements Runnable {
|
|
InputStream input;
|
|
Thread thread;
|
|
|
|
public SystemOutSiphon(InputStream input) {
|
|
this.input = input;
|
|
|
|
thread = new Thread(this);
|
|
// unless this is set to min, it seems to hork the app
|
|
// since it's in charge of stuffing the editor console with strings
|
|
// maybe it's time to get rid of/fix that friggin console
|
|
// ...disabled for 0075, with 0074's fix for code folder hanging
|
|
// this only seems to make the console unresponsive
|
|
//thread.setPriority(Thread.MIN_PRIORITY);
|
|
thread.start();
|
|
}
|
|
|
|
public void run() {
|
|
byte boofer[] = new byte[256];
|
|
|
|
while (Thread.currentThread() == thread) {
|
|
try {
|
|
// can't use a buffered reader here because incremental
|
|
// print statements are interesting too.. causes some
|
|
// disparity with how System.err gets spewed, oh well.
|
|
int count = input.read(boofer, 0, boofer.length);
|
|
if (count == -1) {
|
|
thread = null;
|
|
|
|
} else {
|
|
System.out.print(new String(boofer, 0, count));
|
|
//System.out.flush();
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
// this is prolly because the app was quit & the stream broken
|
|
//e.printStackTrace(System.out);
|
|
//e.printStackTrace();
|
|
thread = null;
|
|
|
|
} catch (Exception e) {
|
|
//System.out.println("SystemOutSiphon: i just died in your arms tonight");
|
|
// on mac os x, this will spew a "Bad File Descriptor" ex
|
|
// each time an external app is shut down.
|
|
//e.printStackTrace();
|
|
thread = null;
|
|
//System.out.println("");
|
|
}
|
|
//System.out.println("SystemOutSiphon: out");
|
|
//thread = null;
|
|
}
|
|
}
|
|
}
|
|
}
|