mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 13:49:18 +01:00
1045 lines
42 KiB
Java
1045 lines
42 KiB
Java
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-13 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 version 2
|
|
as published by the Free Software Foundation.
|
|
|
|
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.mode.java.runner;
|
|
|
|
import processing.app.*;
|
|
import processing.app.exec.StreamRedirectThread;
|
|
import processing.core.*;
|
|
import processing.mode.java.JavaBuild;
|
|
|
|
import java.awt.GraphicsDevice;
|
|
import java.awt.GraphicsEnvironment;
|
|
import java.awt.Point;
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
import com.sun.jdi.*;
|
|
import com.sun.jdi.connect.*;
|
|
import com.sun.jdi.event.*;
|
|
import com.sun.jdi.request.*;
|
|
|
|
|
|
/**
|
|
* Runs a compiled sketch. As of release 0136, all sketches are run externally
|
|
* to the environment so that a debugging interface can be used. This opens up
|
|
* future options for a decent debugger, but in the meantime fixes several
|
|
* problems with output and error streams, messages getting lost on Mac OS X,
|
|
* the run/stop buttons not working, libraries not shutting down, exceptions
|
|
* not coming through, exceptions being printed twice, having to force quit
|
|
* if you make a bad while() loop, and so on.
|
|
*/
|
|
public class Runner implements MessageConsumer {
|
|
// private boolean presenting;
|
|
|
|
// Object that listens for error messages or exceptions.
|
|
protected RunnerListener listener;
|
|
|
|
// Running remote VM
|
|
protected VirtualMachine vm;
|
|
|
|
// Thread transferring remote error stream to our error stream
|
|
protected Thread errThread = null;
|
|
|
|
// Thread transferring remote output stream to our output stream
|
|
protected Thread outThread = null;
|
|
|
|
// Mode for tracing the Trace program (default= 0 off)
|
|
// protected int debugTraceMode = 0;
|
|
|
|
// Do we want to watch assignments to fields
|
|
// protected boolean watchFields = false;
|
|
|
|
// // Class patterns for which we don't want events
|
|
// protected String[] excludes = {
|
|
// "java.*", "javax.*", "sun.*", "com.sun.*",
|
|
// "apple.*",
|
|
// "processing.*"
|
|
// };
|
|
|
|
protected SketchException exception;
|
|
protected Editor editor;
|
|
protected JavaBuild build;
|
|
protected Process process;
|
|
|
|
|
|
public Runner(JavaBuild build, RunnerListener listener) throws SketchException {
|
|
this.listener = listener;
|
|
// this.sketch = sketch;
|
|
this.build = build;
|
|
|
|
if (listener instanceof Editor) {
|
|
this.editor = (Editor) listener;
|
|
// } else {
|
|
// System.out.println("actually it's a " + listener.getClass().getName());
|
|
}
|
|
|
|
// Make sure all the imported libraries will actually run with this setup.
|
|
int bits = Base.getNativeBits();
|
|
for (Library library : build.getImportedLibraries()) {
|
|
if (!library.supportsArch(PApplet.platform, bits)) {
|
|
System.err.println(library.getName() + " does not run in " + bits + "-bit mode.");
|
|
int opposite = (bits == 32) ? 64 : 32;
|
|
if (Base.isMacOS()) {
|
|
//if (library.supportsArch(PConstants.MACOSX, opposite)) { // should always be true
|
|
throw new SketchException("To use " + library.getName() + ", " +
|
|
"switch to " + opposite + "-bit mode in Preferences.");
|
|
//}
|
|
} else {
|
|
throw new SketchException(library.getName() + " is only compatible " +
|
|
"with the " + opposite + "-bit download of Processing.");
|
|
//throw new SketchException(library.getName() + " does not run in " + bits + "-bit mode.");
|
|
// "To use this library, switch to 32-bit mode in Preferences." (OS X)
|
|
// "To use this library, you must use the 32-bit version of Processing."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void launch(boolean presenting) {
|
|
if (launchVirtualMachine(presenting)) {
|
|
generateTrace();
|
|
}
|
|
}
|
|
|
|
|
|
// public void launch(String appletClassName, boolean presenting) {
|
|
// this.appletClassName = appletClassName;
|
|
public boolean launchVirtualMachine(boolean presenting) {
|
|
// this.presenting = presenting;
|
|
|
|
String[] vmParams = getMachineParams();
|
|
String[] sketchParams = getSketchParams(presenting);
|
|
int port = 8000 + (int) (Math.random() * 1000);
|
|
String portStr = String.valueOf(port);
|
|
|
|
// Older (Java 1.5 and earlier) version, go figure
|
|
// String jdwpArg = "-Xrunjdwp:transport=dt_socket,address=" + portStr + ",server=y,suspend=y";
|
|
// String debugArg = "-Xdebug";
|
|
// Newer (Java 1.5+) version that uses JVMTI
|
|
String jdwpArg = "-agentlib:jdwp=transport=dt_socket,address=" + portStr + ",server=y,suspend=y";
|
|
|
|
String[] commandArgs = null;
|
|
if (!Base.isMacOS()) {
|
|
commandArgs = new String[] {
|
|
//"java",
|
|
Base.getJavaPath(),
|
|
jdwpArg
|
|
};
|
|
} else {
|
|
// Decided to just set this to 1.6 only, because otherwise it's gonna
|
|
// be a shitshow if folks are getting Apple's 1.6 with 32-bit and
|
|
// Oracle's 1.7 when run in 64-bit mode. ("Why does my sketch suck in
|
|
// 64-bit? Why is retina broken?)
|
|
// The --request flag will prompt to install Apple's 1.6 JVM if none is
|
|
// available. We're specifying 1.6 so that we can get support for both
|
|
// 32- and 64-bit, because Oracle won't be releasing Java 1.7 in 32-bit.
|
|
// Helpfully, the --request flag is not present on Mac OS X 10.6
|
|
// (luckily it is also not needed, because 1.6 is installed by default)
|
|
// but it requires an additional workaround to not use that flag,
|
|
// otherwise will see an error about an unsupported option. The flag is
|
|
// available with 10.7 and 10.8, the only other supported versions of
|
|
// OS X at this point, because we require 10.6.8 and higher. That also
|
|
// means we don't need to check for any other OS versions, the user is
|
|
// a douchebag and modifies Info.plist to get around the restriction.
|
|
if (false) {
|
|
if (System.getProperty("os.version").startsWith("10.6")) {
|
|
commandArgs = new String[] {
|
|
"/usr/libexec/java_home",
|
|
"--version", "1.6",
|
|
"--exec", "java",
|
|
"-d" + Base.getNativeBits(),
|
|
jdwpArg
|
|
};
|
|
} else { // for 10.7, 10.8, etc
|
|
commandArgs = new String[] {
|
|
"/usr/libexec/java_home",
|
|
"--request", // install on-demand
|
|
"--version", "1.6",
|
|
"--exec", "java",
|
|
"-d" + Base.getNativeBits(),
|
|
// debugArg,
|
|
jdwpArg
|
|
};
|
|
}
|
|
} else {
|
|
// testing jdk-7u40
|
|
commandArgs = new String[] {
|
|
//"/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/Contents/Home/bin/java",
|
|
Base.getJavaPath(),
|
|
jdwpArg
|
|
};
|
|
}
|
|
}
|
|
|
|
commandArgs = PApplet.concat(commandArgs, vmParams);
|
|
commandArgs = PApplet.concat(commandArgs, sketchParams);
|
|
// PApplet.println(commandArgs);
|
|
// commandArg.setValue(commandArgs);
|
|
launchJava(commandArgs);
|
|
// try {
|
|
// Thread.sleep(2000);
|
|
// } catch (InterruptedException e) {
|
|
// e.printStackTrace();
|
|
// }
|
|
|
|
// boolean available = false;
|
|
// while (!available) {
|
|
// try {
|
|
// Socket socket = new Socket((String) null, port);
|
|
//// socket.close();
|
|
// // this should mean we're all set?
|
|
// available = true;
|
|
//
|
|
// } catch (IOException e) {
|
|
// System.out.println("waiting");
|
|
// //e.printStackTrace();
|
|
// try {
|
|
// Thread.sleep(100);
|
|
// } catch (InterruptedException e1) {
|
|
// e1.printStackTrace();
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
AttachingConnector connector = (AttachingConnector)
|
|
findConnector("com.sun.jdi.SocketAttach");
|
|
//PApplet.println(connector); // gets the defaults
|
|
|
|
Map arguments = connector.defaultArguments();
|
|
|
|
// Connector.Argument addressArg =
|
|
// (Connector.Argument)arguments.get("address");
|
|
// addressArg.setValue(addr);
|
|
Connector.Argument portArg =
|
|
(Connector.Argument)arguments.get("port");
|
|
portArg.setValue(portStr);
|
|
|
|
// Connector.Argument timeoutArg =
|
|
// (Connector.Argument)arguments.get("timeout");
|
|
// timeoutArg.setValue("10000");
|
|
|
|
//PApplet.println(connector); // prints the current
|
|
//com.sun.tools.jdi.AbstractLauncher al;
|
|
//com.sun.tools.jdi.RawCommandLineLauncher rcll;
|
|
|
|
//System.out.println(PApplet.javaVersion);
|
|
// http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch
|
|
|
|
try {
|
|
// boolean available = false;
|
|
// while (!available) {
|
|
while (true) {
|
|
try {
|
|
vm = connector.attach(arguments);
|
|
// vm = connector.attach(arguments);
|
|
if (vm != null) {
|
|
// generateTrace();
|
|
// available = true;
|
|
return true;
|
|
}
|
|
} catch (IOException e) {
|
|
// System.out.println("waiting");
|
|
// e.printStackTrace();
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (InterruptedException e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
// } catch (IOException exc) {
|
|
// throw new Error("Unable to launch target VM: " + exc);
|
|
} catch (IllegalConnectorArgumentsException exc) {
|
|
throw new Error("Internal error: " + exc);
|
|
}
|
|
}
|
|
|
|
|
|
protected String[] getMachineParams() {
|
|
ArrayList<String> params = new ArrayList<String>();
|
|
|
|
//params.add("-Xint"); // interpreted mode
|
|
//params.add("-Xprof"); // profiler
|
|
//params.add("-Xaprof"); // allocation profiler
|
|
//params.add("-Xrunhprof:cpu=samples"); // old-style profiler
|
|
|
|
// TODO change this to use run.args = true, run.args.0, run.args.1, etc.
|
|
// so that spaces can be included in the arg names
|
|
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.ext.dirs=nuffing");
|
|
|
|
if (Preferences.getBoolean("run.options.memory")) {
|
|
params.add("-Xms" + Preferences.get("run.options.memory.initial") + "m");
|
|
params.add("-Xmx" + Preferences.get("run.options.memory.maximum") + "m");
|
|
}
|
|
|
|
if (Base.isMacOS()) {
|
|
params.add("-Xdock:name=" + build.getSketchClassName());
|
|
// params.add("-Dcom.apple.mrj.application.apple.menu.about.name=" +
|
|
// sketch.getMainClassName());
|
|
}
|
|
// sketch.libraryPath might be ""
|
|
// librariesClassPath will always have sep char prepended
|
|
params.add("-Djava.library.path=" +
|
|
build.getJavaLibraryPath() +
|
|
File.pathSeparator +
|
|
System.getProperty("java.library.path"));
|
|
|
|
params.add("-cp");
|
|
params.add(build.getClassPath());
|
|
// params.add(sketch.getClassPath() +
|
|
// File.pathSeparator +
|
|
// Base.librariesClassPath);
|
|
|
|
// enable assertions
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=1188
|
|
params.add("-ea");
|
|
//PApplet.println(PApplet.split(sketch.classPath, ':'));
|
|
|
|
String outgoing[] = new String[params.size()];
|
|
params.toArray(outgoing);
|
|
|
|
// PApplet.println(outgoing);
|
|
// PApplet.println(PApplet.split(outgoing[0], ":"));
|
|
// PApplet.println();
|
|
// PApplet.println("class path");
|
|
// PApplet.println(PApplet.split(outgoing[2], ":"));
|
|
|
|
return outgoing;
|
|
//return (String[]) params.toArray();
|
|
|
|
// System.out.println("sketch class path");
|
|
// PApplet.println(PApplet.split(sketch.classPath, ';'));
|
|
// System.out.println();
|
|
// System.out.println("libraries class path");
|
|
// PApplet.println(PApplet.split(Base.librariesClassPath, ';'));
|
|
// System.out.println();
|
|
}
|
|
|
|
|
|
protected String[] getSketchParams(boolean presenting) {
|
|
ArrayList<String> params = new ArrayList<String>();
|
|
|
|
// It's dangerous to add your own main() to your code,
|
|
// but if you've done it, we'll respect your right to hang yourself.
|
|
// http://processing.org/bugs/bugzilla/1446.html
|
|
if (build.getFoundMain()) {
|
|
params.add(build.getSketchClassName());
|
|
|
|
} else {
|
|
params.add("processing.core.PApplet");
|
|
|
|
// get the stored device index (starts at 0)
|
|
int runDisplay = Preferences.getInteger("run.display");
|
|
|
|
// If there was a saved location (this guy has been run more than once)
|
|
// then the location will be set to the last position of the sketch window.
|
|
// This will be passed to the PApplet runner using something like
|
|
// --location=30,20
|
|
// Otherwise, the editor location will be passed, and the applet will
|
|
// figure out where to place itself based on the editor location.
|
|
// --editor-location=150,20
|
|
if (editor != null) { // if running processing-cmd, don't do placement
|
|
GraphicsDevice editorDevice =
|
|
editor.getGraphicsConfiguration().getDevice();
|
|
GraphicsEnvironment ge =
|
|
GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
GraphicsDevice[] devices = ge.getScreenDevices();
|
|
|
|
// Make sure the display set in Preferences actually exists
|
|
GraphicsDevice runDevice = editorDevice;
|
|
if (runDisplay >= 0 && runDisplay < devices.length) {
|
|
runDevice = devices[runDisplay];
|
|
} else {
|
|
runDevice = editorDevice;
|
|
for (int i = 0; i < devices.length; i++) {
|
|
if (devices[i] == runDevice) {
|
|
runDisplay = i;
|
|
break;
|
|
// Don't set the pref, might be a temporary thing. Users can
|
|
// open/close Preferences to reset the device themselves.
|
|
// Preferences.setInteger("run.display", runDisplay);
|
|
}
|
|
}
|
|
}
|
|
|
|
Point windowLocation = editor.getSketchLocation();
|
|
// if (windowLocation != null) {
|
|
// // could check to make sure the sketch location is on the device
|
|
// // that's specified in Preferences, but that's going to be annoying
|
|
// // if you move a sketch to another window, then it keeps jumping
|
|
// // back to the specified window.
|
|
//// Rectangle screenRect =
|
|
//// runDevice.getDefaultConfiguration().getBounds();
|
|
// }
|
|
if (windowLocation == null) {
|
|
if (editorDevice == runDevice) {
|
|
// If sketches are to be shown on the same display as the editor,
|
|
// provide the editor location so the sketch's main() can place it.
|
|
Point editorLocation = editor.getLocation();
|
|
params.add(PApplet.ARGS_EDITOR_LOCATION + "=" +
|
|
editorLocation.x + "," + editorLocation.y);
|
|
} else {
|
|
// The sketch's main() will set a location centered on the new
|
|
// display. It has to happen in main() because the width/height
|
|
// of the sketch are not known here.
|
|
// Set a location centered on the other display
|
|
// Rectangle screenRect =
|
|
// runDevice.getDefaultConfiguration().getBounds();
|
|
// int runX =
|
|
// params.add(PApplet.ARGS_LOCATION + "=" + runX + "," + runY);
|
|
}
|
|
} else {
|
|
params.add(PApplet.ARGS_LOCATION + "=" +
|
|
windowLocation.x + "," + windowLocation.y);
|
|
}
|
|
params.add(PApplet.ARGS_EXTERNAL);
|
|
}
|
|
|
|
params.add(PApplet.ARGS_DISPLAY + "=" + runDisplay);
|
|
|
|
|
|
if (presenting) {
|
|
params.add(PApplet.ARGS_FULL_SCREEN);
|
|
// if (Preferences.getBoolean("run.present.exclusive")) {
|
|
// params.add(PApplet.ARGS_EXCLUSIVE);
|
|
// }
|
|
params.add(PApplet.ARGS_STOP_COLOR + "=" +
|
|
Preferences.get("run.present.stop.color"));
|
|
params.add(PApplet.ARGS_BGCOLOR + "=" +
|
|
Preferences.get("run.present.bgcolor"));
|
|
}
|
|
|
|
params.add(build.getSketchClassName());
|
|
params.add(PApplet.ARGS_SKETCH_FOLDER + "=" + build.getSketchPath());
|
|
// Adding sketch path in the end coz it's likely to
|
|
// contain spaces and things go wrong on UNIX systems.
|
|
}
|
|
|
|
// String outgoing[] = new String[params.size()];
|
|
// params.toArray(outgoing);
|
|
// return outgoing;
|
|
return params.toArray(new String[0]);
|
|
}
|
|
|
|
|
|
protected void launchJava(final String[] args) {
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
// PApplet.println("java starting");
|
|
process = PApplet.exec(args);
|
|
try {
|
|
// PApplet.println("java waiting");
|
|
int result = process.waitFor();
|
|
// PApplet.println("java done waiting");
|
|
if (result != 0) {
|
|
String[] errorStrings = PApplet.loadStrings(process.getErrorStream());
|
|
String[] inputStrings = PApplet.loadStrings(process.getInputStream());
|
|
|
|
// PApplet.println("launchJava stderr:");
|
|
// PApplet.println(errorStrings);
|
|
// PApplet.println("launchJava stdout:");
|
|
PApplet.printArray(inputStrings);
|
|
|
|
if (errorStrings != null && errorStrings.length > 1) {
|
|
if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) {
|
|
Base.showWarning("Way Too High",
|
|
"Please lower the value for \u201Cmaximum available memory\u201D in the\n" +
|
|
"Preferences window. For more information, read Help \u2192 Troubleshooting.", null);
|
|
} else {
|
|
for (String err : errorStrings) {
|
|
System.err.println(err);
|
|
}
|
|
System.err.println("Using startup command: " + PApplet.join(args, " "));
|
|
}
|
|
} else {
|
|
//exc.printStackTrace();
|
|
System.err.println("Could not run the sketch (Target VM failed to initialize).");
|
|
if (Preferences.getBoolean("run.options.memory")) {
|
|
// Only mention this if they've even altered the memory setup
|
|
System.err.println("Make sure that you haven't set the maximum available memory too high.");
|
|
}
|
|
System.err.println("For more information, read revisions.txt and Help \u2192 Troubleshooting.");
|
|
}
|
|
// changing this to separate editor and listener [091124]
|
|
//if (editor != null) {
|
|
listener.statusError("Could not run the sketch.");
|
|
//}
|
|
// return null;
|
|
}
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
|
|
/*
|
|
protected VirtualMachine launchVirtualMachine(String[] vmParams,
|
|
String[] classParams) {
|
|
//vm = launchTarget(sb.toString());
|
|
LaunchingConnector connector = (LaunchingConnector)
|
|
findConnector("com.sun.jdi.RawCommandLineLaunch");
|
|
//PApplet.println(connector); // gets the defaults
|
|
|
|
//Map arguments = connectorArguments(connector, mainArgs);
|
|
Map arguments = connector.defaultArguments();
|
|
|
|
Connector.Argument commandArg =
|
|
(Connector.Argument)arguments.get("command");
|
|
// Using localhost instead of 127.0.0.1 sometimes causes a
|
|
// "Transport Error 202" error message when trying to run.
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=895
|
|
// String addr = "127.0.0.1:" + (8000 + (int) (Math.random() * 1000));
|
|
//String addr = "localhost:" + (8000 + (int) (Math.random() * 1000));
|
|
// Better yet, host is not needed, so using just the port for the address
|
|
String addr = "" + (8000 + (int) (Math.random() * 1000));
|
|
|
|
String commandArgs =
|
|
"java -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
|
|
if (Base.isMacOS()) {
|
|
// Decided to just set this to 1.6 only, because otherwise it's gonna
|
|
// be a shitshow if folks are getting Apple's 1.6 with 32-bit and
|
|
// Oracle's 1.7 when run in 64-bit mode. ("Why does my sketch suck in
|
|
// 64-bit? Why is retina broken?)
|
|
// The --request flag will prompt to install Apple's 1.6 JVM if none is
|
|
// available. We're specifying 1.6 so that we can get support for both
|
|
// 32- and 64-bit, because Oracle won't be releasing Java 1.7 in 32-bit.
|
|
// Helpfully, the --request flag is not present on Mac OS X 10.6
|
|
// (luckily it is also not needed, because 1.6 is installed by default)
|
|
// but it requires an additional workaround to not use that flag,
|
|
// otherwise will see an error about an unsupported option. The flag is
|
|
// available with 10.7 and 10.8, the only other supported versions of
|
|
// OS X at this point, because we require 10.6.8 and higher. That also
|
|
// means we don't need to check for any other OS versions, unless
|
|
// is a douchebag and modifies Info.plist to get around the restriction.
|
|
addr = "" + (8000 + (int) (Math.random() * 1000));
|
|
commandArgs =
|
|
"/usr/libexec/java_home " +
|
|
(System.getProperty("os.version").startsWith("10.6") ? "" : "--request ") +
|
|
"--version 1.6 " +
|
|
"--exec java " +
|
|
"-d" + Base.getNativeBits() + " " +
|
|
"-Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y ";
|
|
}
|
|
|
|
for (int i = 0; i < vmParams.length; i++) {
|
|
commandArgs = addArgument(commandArgs, vmParams[i], ' ');
|
|
}
|
|
if (classParams != null) {
|
|
for (int i = 0; i < classParams.length; i++) {
|
|
commandArgs = addArgument(commandArgs, classParams[i], ' ');
|
|
}
|
|
}
|
|
System.out.println("commandArgs is " + commandArgs);
|
|
commandArg.setValue(commandArgs);
|
|
|
|
Connector.Argument addressArg =
|
|
(Connector.Argument)arguments.get("address");
|
|
addressArg.setValue(addr);
|
|
|
|
//PApplet.println(connector); // prints the current
|
|
//com.sun.tools.jdi.AbstractLauncher al;
|
|
//com.sun.tools.jdi.RawCommandLineLauncher rcll;
|
|
|
|
//System.out.println(PApplet.javaVersion);
|
|
// http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch
|
|
try {
|
|
return connector.launch(arguments);
|
|
} catch (IOException exc) {
|
|
throw new Error("Unable to launch target VM: " + exc);
|
|
} catch (IllegalConnectorArgumentsException exc) {
|
|
throw new Error("Internal error: " + exc);
|
|
} catch (VMStartException exc) {
|
|
Process p = exc.process();
|
|
//System.out.println(p);
|
|
String[] errorStrings = PApplet.loadStrings(p.getErrorStream());
|
|
//String[] inputStrings =
|
|
PApplet.loadStrings(p.getInputStream());
|
|
|
|
if (errorStrings != null && errorStrings.length > 1) {
|
|
if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) {
|
|
Base.showWarning("Way Too High",
|
|
"Please lower the value for \u201Cmaximum available memory\u201D in the\n" +
|
|
"Preferences window. For more information, read Help \u2192 Troubleshooting.",
|
|
exc);
|
|
} else {
|
|
PApplet.println(errorStrings);
|
|
System.err.println("Using startup command:");
|
|
PApplet.println(arguments);
|
|
}
|
|
} else {
|
|
exc.printStackTrace();
|
|
System.err.println("Could not run the sketch (Target VM failed to initialize).");
|
|
if (Preferences.getBoolean("run.options.memory")) {
|
|
// Only mention this if they've even altered the memory setup
|
|
System.err.println("Make sure that you haven't set the maximum available memory too high.");
|
|
}
|
|
System.err.println("For more information, read revisions.txt and Help \u2192 Troubleshooting.");
|
|
}
|
|
// changing this to separate editor and listener [091124]
|
|
//if (editor != null) {
|
|
listener.statusError("Could not run the sketch.");
|
|
//}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
private static boolean hasWhitespace(String string) {
|
|
int length = string.length();
|
|
for (int i = 0; i < length; i++) {
|
|
if (Character.isWhitespace(string.charAt(i))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
private static String addArgument(String string, String argument, char sep) {
|
|
if (hasWhitespace(argument) || argument.indexOf(',') != -1) {
|
|
// Quotes were stripped out for this argument, add 'em back.
|
|
StringBuffer buffer = new StringBuffer(string);
|
|
buffer.append('"');
|
|
for (int i = 0; i < argument.length(); i++) {
|
|
char c = argument.charAt(i);
|
|
if (c == '"') {
|
|
buffer.append('\\');
|
|
// buffer.append("\\\\");
|
|
}
|
|
buffer.append(c);
|
|
}
|
|
buffer.append('"');
|
|
buffer.append(sep);
|
|
return buffer.toString();
|
|
|
|
} else {
|
|
return string + argument + String.valueOf(sep);
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
/**
|
|
* Generate the trace.
|
|
* Enable events, start thread to display events,
|
|
* start threads to forward remote error and output streams,
|
|
* resume the remote VM, wait for the final event, and shutdown.
|
|
*/
|
|
protected void generateTrace() {
|
|
//vm.setDebugTraceMode(debugTraceMode);
|
|
// vm.setDebugTraceMode(VirtualMachine.TRACE_ALL);
|
|
// vm.setDebugTraceMode(VirtualMachine.TRACE_NONE); // formerly, seems to have no effect
|
|
|
|
// For internal debugging
|
|
PrintWriter writer = null;
|
|
|
|
// Calling this seems to set something internally to make the
|
|
// Eclipse JDI wake up. Without it, an ObjectCollectedException
|
|
// is thrown on excReq.enable(). No idea why this works,
|
|
// but at least exception handling has returned. (Suspect that it may
|
|
// block until all or at least some threads are available, meaning
|
|
// that the app has launched and we have legit objects to talk to).
|
|
vm.allThreads();
|
|
// The bug may not have been noticed because the test suite waits for
|
|
// a thread to be available, and queries it by calling allThreads().
|
|
// See org.eclipse.debug.jdi.tests.AbstractJDITest for the example.
|
|
|
|
EventRequestManager mgr = vm.eventRequestManager();
|
|
// get only the uncaught exceptions
|
|
ExceptionRequest excReq = mgr.createExceptionRequest(null, false, true);
|
|
// System.out.println(excReq);
|
|
// this version reports all exceptions, caught or uncaught
|
|
// ExceptionRequest excReq = mgr.createExceptionRequest(null, true, true);
|
|
// suspend so we can step
|
|
excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
// excReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
|
|
// excReq.setSuspendPolicy(EventRequest.SUSPEND_NONE); // another option?
|
|
excReq.enable();
|
|
|
|
Thread eventThread = new Thread() {
|
|
public void run() {
|
|
try {
|
|
boolean connected = true;
|
|
while (connected) {
|
|
EventQueue eventQueue = vm.eventQueue();
|
|
// remove() blocks until event(s) available
|
|
EventSet eventSet = eventQueue.remove();
|
|
// listener.vmEvent(eventSet);
|
|
|
|
for (Event event : eventSet) {
|
|
// System.out.println("EventThread.handleEvent -> " + event);
|
|
if (event instanceof ExceptionEvent) {
|
|
// for (ThreadReference thread : vm.allThreads()) {
|
|
// System.out.println("thread : " + thread);
|
|
//// thread.suspend();
|
|
// }
|
|
exceptionEvent((ExceptionEvent) event);
|
|
} else if (event instanceof VMDisconnectEvent) {
|
|
connected = false;
|
|
}
|
|
}
|
|
}
|
|
// } catch (VMDisconnectedException e) {
|
|
// Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect");
|
|
} catch (Exception e) {
|
|
System.err.println("crashed in event thread due to " + e.getMessage());
|
|
// Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e);
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
};
|
|
eventThread.start();
|
|
|
|
|
|
errThread =
|
|
new MessageSiphon(process.getErrorStream(), this).getThread();
|
|
|
|
outThread = new StreamRedirectThread("JVM stdout Reader",
|
|
process.getInputStream(),
|
|
System.out);
|
|
errThread.start();
|
|
outThread.start();
|
|
|
|
vm.resume();
|
|
|
|
// Shutdown begins when event thread terminates
|
|
try {
|
|
if (eventThread != null) eventThread.join(); // is this the problem?
|
|
|
|
// System.out.println("in here");
|
|
// Bug #852 tracked to this next line in the code.
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=852
|
|
errThread.join(); // Make sure output is forwarded
|
|
// System.out.println("and then");
|
|
outThread.join(); // before we exit
|
|
// System.out.println("finished join for errThread and outThread");
|
|
|
|
// At this point, disable the run button.
|
|
// This happens when the sketch is exited by hitting ESC,
|
|
// or the user manually closes the sketch window.
|
|
// TODO this should be handled better, should it not?
|
|
if (editor != null) {
|
|
editor.deactivateRun();
|
|
}
|
|
} catch (InterruptedException exc) {
|
|
// we don't interrupt
|
|
}
|
|
//System.out.println("and leaving");
|
|
if (writer != null) writer.close();
|
|
}
|
|
|
|
|
|
protected Connector findConnector(String connectorName) {
|
|
// List connectors =
|
|
// com.sun.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
|
List connectors =
|
|
org.eclipse.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
|
|
|
// // debug: code to list available connectors
|
|
// Iterator iter2 = connectors.iterator();
|
|
// while (iter2.hasNext()) {
|
|
// Connector connector = (Connector)iter2.next();
|
|
// System.out.println("connector name is " + connector.name());
|
|
// }
|
|
|
|
for (Object c : connectors) {
|
|
Connector connector = (Connector) c;
|
|
// System.out.println(connector.name());
|
|
// }
|
|
// Iterator iter = connectors.iterator();
|
|
// while (iter.hasNext()) {
|
|
// Connector connector = (Connector)iter.next();
|
|
if (connector.name().equals(connectorName)) {
|
|
return connector;
|
|
}
|
|
}
|
|
Base.showError("Compiler Error",
|
|
"findConnector() failed to find " +
|
|
connectorName + " inside Runner", null);
|
|
return null; // Not reachable
|
|
}
|
|
|
|
|
|
public void exceptionEvent(ExceptionEvent event) {
|
|
ObjectReference or = event.exception();
|
|
ReferenceType rt = or.referenceType();
|
|
String exceptionName = rt.name();
|
|
//Field messageField = Throwable.class.getField("detailMessage");
|
|
Field messageField = rt.fieldByName("detailMessage");
|
|
// System.out.println("field " + messageField);
|
|
Value messageValue = or.getValue(messageField);
|
|
// System.out.println("mess val " + messageValue);
|
|
|
|
//"java.lang.ArrayIndexOutOfBoundsException"
|
|
int last = exceptionName.lastIndexOf('.');
|
|
String message = exceptionName.substring(last + 1);
|
|
if (messageValue != null) {
|
|
String messageStr = messageValue.toString();
|
|
if (messageStr.startsWith("\"")) {
|
|
messageStr = messageStr.substring(1, messageStr.length() - 1);
|
|
}
|
|
message += ": " + messageStr;
|
|
}
|
|
// System.out.println("mess type " + messageValue.type());
|
|
//StringReference messageReference = (StringReference) messageValue.type();
|
|
|
|
// First just report the exception and its placement
|
|
reportException(message, or, event.thread());
|
|
// Then try to pretty it up with a better message
|
|
handleCommonErrors(exceptionName, message, listener);
|
|
|
|
if (editor != null) {
|
|
editor.deactivateRun();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Provide more useful explanations of common error messages, perhaps with
|
|
* a short message in the status area, and (if necessary) a longer message
|
|
* in the console.
|
|
*
|
|
* @param exceptionClass Class name causing the error (with full package name)
|
|
* @param message The message from the exception
|
|
* @param listener The Editor or command line interface that's listening for errors
|
|
* @return true if the error was purtified, false otherwise
|
|
*/
|
|
public static boolean handleCommonErrors(final String exceptionClass,
|
|
final String message,
|
|
final RunnerListener listener) {
|
|
if (exceptionClass.equals("java.lang.OutOfMemoryError")) {
|
|
if (message.contains("exceeds VM budget")) {
|
|
// TODO this is a kludge for Android, since there's no memory preference
|
|
listener.statusError("OutOfMemoryError: This code attempts to use more memory than available.");
|
|
System.err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
|
System.err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
|
System.err.println("loading thousands of images), or simply that it's trying to use more memory");
|
|
System.err.println("than what is supported by the current device.");
|
|
} else {
|
|
listener.statusError("OutOfMemoryError: You may need to increase the memory setting in Preferences.");
|
|
System.err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
|
System.err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
|
System.err.println("loading thousands of images), or that your sketch may need more memory to run.");
|
|
System.err.println("If your sketch uses a lot of memory (for instance if it loads a lot of data files)");
|
|
System.err.println("you can increase the memory available to your sketch using the Preferences window.");
|
|
}
|
|
} else if (exceptionClass.equals("java.lang.UnsatisfiedLinkError")) {
|
|
listener.statusError("A library used by this sketch is not installed properly.");
|
|
System.err.println("A library relies on native code that's not available.");
|
|
System.err.println("Or only works properly when the sketch is run as a " +
|
|
((Base.getNativeBits() == 32) ? "64-bit " : "32-bit ") + " application.");
|
|
|
|
} else if (exceptionClass.equals("java.lang.StackOverflowError")) {
|
|
listener.statusError("StackOverflowError: This sketch is attempting too much recursion.");
|
|
System.err.println("A StackOverflowError means that you have a bug that's causing a function");
|
|
System.err.println("to be called recursively (it's calling itself and going in circles),");
|
|
System.err.println("or you're intentionally calling a recursive function too much,");
|
|
System.err.println("and your code should be rewritten in a more efficient manner.");
|
|
|
|
} else if (exceptionClass.equals("java.lang.UnsupportedClassVersionError")) {
|
|
listener.statusError("UnsupportedClassVersionError: A library is using code compiled with an unsupported version of Java.");
|
|
System.err.println("This version of Processing only supports libraries and JAR files compiled for Java 1.6 or earlier.");
|
|
System.err.println("A library used by this sketch was compiled for Java 1.7 or later, ");
|
|
System.err.println("and needs to be recompiled to be compatible with Java 1.6.");
|
|
|
|
} else if (exceptionClass.equals("java.lang.NoSuchMethodError") ||
|
|
exceptionClass.equals("java.lang.NoSuchFieldError")) {
|
|
listener.statusError(exceptionClass.substring(10) + ": " +
|
|
"You may be using a library that's incompatible " +
|
|
"with this version of Processing.");
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// TODO: This may be called more than one time per error in the VM,
|
|
// presumably because exceptions might be wrapped inside others,
|
|
// and this will fire for both.
|
|
protected void reportException(String message, ObjectReference or, ThreadReference thread) {
|
|
listener.statusError(findException(message, or, thread));
|
|
}
|
|
|
|
|
|
/**
|
|
* Move through a list of stack frames, searching for references to code
|
|
* found in the current sketch. Return with a RunnerException that contains
|
|
* the location of the error, or if nothing is found, just return with a
|
|
* RunnerException that wraps the error message itself.
|
|
*/
|
|
protected SketchException findException(String message, ObjectReference or, ThreadReference thread) {
|
|
try {
|
|
// use to dump the stack for debugging
|
|
// for (StackFrame frame : thread.frames()) {
|
|
// System.out.println("frame: " + frame);
|
|
// }
|
|
|
|
List<StackFrame> frames = thread.frames();
|
|
for (StackFrame frame : frames) {
|
|
try {
|
|
Location location = frame.location();
|
|
String filename = null;
|
|
filename = location.sourceName();
|
|
int lineNumber = location.lineNumber() - 1;
|
|
SketchException rex =
|
|
build.placeException(message, filename, lineNumber);
|
|
if (rex != null) {
|
|
return rex;
|
|
}
|
|
} catch (AbsentInformationException e) {
|
|
// Any of the thread.blah() methods can throw an AbsentInformationEx
|
|
// if that bit of data is missing. If so, just write out the error
|
|
// message to the console.
|
|
//e.printStackTrace(); // not useful
|
|
exception = new SketchException(message);
|
|
exception.hideStackTrace();
|
|
listener.statusError(exception);
|
|
}
|
|
}
|
|
} catch (IncompatibleThreadStateException e) {
|
|
// This shouldn't happen, but if it does, print the exception in case
|
|
// it's something that needs to be debugged separately.
|
|
e.printStackTrace();
|
|
}
|
|
// before giving up, try to extract from the throwable object itself
|
|
// since sometimes exceptions are re-thrown from a different context
|
|
try {
|
|
// assume object reference is Throwable, get stack trace
|
|
Method method = ((ClassType) or.referenceType()).concreteMethodByName("getStackTrace", "()[Ljava/lang/StackTraceElement;");
|
|
ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
|
// iterate through stack frames and pull filename and line number for each
|
|
for (Value val: result.getValues()) {
|
|
ObjectReference ref = (ObjectReference)val;
|
|
method = ((ClassType) ref.referenceType()).concreteMethodByName("getFileName", "()Ljava/lang/String;");
|
|
StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
|
String filename = strref.value();
|
|
method = ((ClassType) ref.referenceType()).concreteMethodByName("getLineNumber", "()I");
|
|
IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
|
int lineNumber = intval.intValue() - 1;
|
|
SketchException rex =
|
|
build.placeException(message, filename, lineNumber);
|
|
if (rex != null) {
|
|
return rex;
|
|
}
|
|
}
|
|
// for (Method m : ((ClassType) or.referenceType()).allMethods()) {
|
|
// System.out.println(m + " | " + m.signature() + " | " + m.genericSignature());
|
|
// }
|
|
// Implemented for 2.0b9, writes a stack trace when there's an internal error inside core.
|
|
method = ((ClassType) or.referenceType()).concreteMethodByName("printStackTrace", "()V");
|
|
// System.err.println("got method " + method);
|
|
or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
|
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
// Give up, nothing found inside the pile of stack frames
|
|
SketchException rex = new SketchException(message);
|
|
// exception is being created /here/, so stack trace is not useful
|
|
rex.hideStackTrace();
|
|
return rex;
|
|
}
|
|
|
|
|
|
public void close() {
|
|
// TODO make sure stop() has already been called to exit the sketch
|
|
|
|
// TODO actually kill off the vm here
|
|
if (vm != null) {
|
|
try {
|
|
vm.exit(0);
|
|
|
|
} catch (com.sun.jdi.VMDisconnectedException vmde) {
|
|
// if the vm has disconnected on its own, ignore message
|
|
//System.out.println("harmless disconnect " + vmde.getMessage());
|
|
// TODO shouldn't need to do this, need to do more cleanup
|
|
}
|
|
vm = null;
|
|
}
|
|
}
|
|
|
|
|
|
// made synchronized for 0087
|
|
// attempted to remove synchronized for 0136 to fix bug #775 (no luck tho)
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=775
|
|
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_STOP) == 0) {
|
|
//System.out.println("external: quit");
|
|
if (editor != null) {
|
|
// editor.internalCloseRunner(); // [091124]
|
|
// editor.handleStop(); // prior to 0192
|
|
editor.internalCloseRunner(); // 0192
|
|
}
|
|
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));
|
|
// this is only fired when connected to an editor
|
|
editor.setSketchLocation(new Point(left, top));
|
|
//System.out.println("external: move to " + left + " " + top);
|
|
return;
|
|
}
|
|
|
|
// 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 message, 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();
|
|
}
|
|
}
|