From d7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Tue, 8 Jun 2010 08:22:05 +0200 Subject: reverted folder structure change for better mergeing with upstream --- .../javascript/tools/ToolErrorReporter.java | 225 ++ .../org/mozilla/javascript/tools/debugger/Dim.java | 1560 +++++++++ .../javascript/tools/debugger/GuiCallback.java | 71 + .../mozilla/javascript/tools/debugger/Main.java | 431 +++ .../javascript/tools/debugger/ScopeProvider.java | 52 + .../javascript/tools/debugger/SwingGui.java | 3547 ++++++++++++++++++++ .../mozilla/javascript/tools/debugger/build.xml | 126 + .../javascript/tools/idswitch/CodePrinter.java | 212 ++ .../javascript/tools/idswitch/FileBody.java | 191 ++ .../javascript/tools/idswitch/IdValuePair.java | 58 + .../mozilla/javascript/tools/idswitch/Main.java | 612 ++++ .../javascript/tools/idswitch/SwitchGenerator.java | 491 +++ .../org/mozilla/javascript/tools/jsc/Main.java | 395 +++ .../javascript/tools/resources/Messages.properties | 268 ++ .../javascript/tools/shell/ConsoleTextArea.java | 300 ++ .../javascript/tools/shell/Environment.java | 141 + .../org/mozilla/javascript/tools/shell/Global.java | 1038 ++++++ .../mozilla/javascript/tools/shell/JSConsole.java | 225 ++ .../javascript/tools/shell/JavaPolicySecurity.java | 240 ++ .../org/mozilla/javascript/tools/shell/Main.java | 638 ++++ .../mozilla/javascript/tools/shell/QuitAction.java | 50 + .../javascript/tools/shell/SecurityProxy.java | 48 + .../tools/shell/ShellContextFactory.java | 114 + 23 files changed, 11033 insertions(+) create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/ToolErrorReporter.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/GuiCallback.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Main.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/ScopeProvider.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/build.xml create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/CodePrinter.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/FileBody.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/IdValuePair.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/Main.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/SwitchGenerator.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/jsc/Main.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/resources/Messages.properties create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Environment.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Global.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Main.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/QuitAction.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/SecurityProxy.java create mode 100644 trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ShellContextFactory.java (limited to 'trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools') diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/ToolErrorReporter.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/ToolErrorReporter.java new file mode 100644 index 0000000..938f5f2 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/ToolErrorReporter.java @@ -0,0 +1,225 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Kurt Westerfeld + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools; + +import org.mozilla.javascript.*; + +import java.text.MessageFormat; +import java.io.*; +import java.util.*; + +/** + * Error reporter for tools. + * + * Currently used by both the shell and the compiler. + */ +public class ToolErrorReporter implements ErrorReporter { + + public ToolErrorReporter(boolean reportWarnings) { + this(reportWarnings, System.err); + } + + public ToolErrorReporter(boolean reportWarnings, PrintStream err) { + this.reportWarnings = reportWarnings; + this.err = err; + } + + /** + * Look up the message corresponding to messageId in the + * org.mozilla.javascript.tools.shell.resources.Messages property file. + * For internationalization support. + */ + public static String getMessage(String messageId) { + return getMessage(messageId, (Object []) null); + } + + public static String getMessage(String messageId, String argument) { + Object[] args = { argument }; + return getMessage(messageId, args); + } + + public static String getMessage(String messageId, Object arg1, Object arg2) + { + Object[] args = { arg1, arg2 }; + return getMessage(messageId, args); + } + + public static String getMessage(String messageId, Object[] args) { + Context cx = Context.getCurrentContext(); + Locale locale = cx == null ? Locale.getDefault() : cx.getLocale(); + + // ResourceBundle does cacheing. + ResourceBundle rb = ResourceBundle.getBundle + ("org.mozilla.javascript.tools.resources.Messages", locale); + + String formatString; + try { + formatString = rb.getString(messageId); + } catch (java.util.MissingResourceException mre) { + throw new RuntimeException("no message resource found for message property " + + messageId); + } + + if (args == null) { + return formatString; + } else { + MessageFormat formatter = new MessageFormat(formatString); + return formatter.format(args); + } + } + + private static String getExceptionMessage(RhinoException ex) + { + String msg; + if (ex instanceof JavaScriptException) { + msg = getMessage("msg.uncaughtJSException", ex.details()); + } else if (ex instanceof EcmaError) { + msg = getMessage("msg.uncaughtEcmaError", ex.details()); + } else if (ex instanceof EvaluatorException) { + msg = ex.details(); + } else { + msg = ex.toString(); + } + return msg; + } + + public void warning(String message, String sourceName, int line, + String lineSource, int lineOffset) + { + if (!reportWarnings) + return; + reportErrorMessage(message, sourceName, line, lineSource, lineOffset, + true); + } + + public void error(String message, String sourceName, int line, + String lineSource, int lineOffset) + { + hasReportedErrorFlag = true; + reportErrorMessage(message, sourceName, line, lineSource, lineOffset, + false); + } + + public EvaluatorException runtimeError(String message, String sourceName, + int line, String lineSource, + int lineOffset) + { + return new EvaluatorException(message, sourceName, line, + lineSource, lineOffset); + } + + public boolean hasReportedError() { + return hasReportedErrorFlag; + } + + public boolean isReportingWarnings() { + return this.reportWarnings; + } + + public void setIsReportingWarnings(boolean reportWarnings) { + this.reportWarnings = reportWarnings; + } + + public static void reportException(ErrorReporter er, RhinoException ex) + { + if (er instanceof ToolErrorReporter) { + ((ToolErrorReporter)er).reportException(ex); + } else { + String msg = getExceptionMessage(ex); + er.error(msg, ex.sourceName(), ex.lineNumber(), + ex.lineSource(), ex.columnNumber()); + } + } + + public void reportException(RhinoException ex) + { + if (ex instanceof WrappedException) { + WrappedException we = (WrappedException)ex; + we.printStackTrace(err); + } else { + String lineSeparator = + SecurityUtilities.getSystemProperty("line.separator"); + String msg = getExceptionMessage(ex) + lineSeparator + + ex.getScriptStackTrace(); + reportErrorMessage(msg, ex.sourceName(), ex.lineNumber(), + ex.lineSource(), ex.columnNumber(), false); + } + } + + private void reportErrorMessage(String message, String sourceName, int line, + String lineSource, int lineOffset, + boolean justWarning) + { + if (line > 0) { + String lineStr = String.valueOf(line); + if (sourceName != null) { + Object[] args = { sourceName, lineStr, message }; + message = getMessage("msg.format3", args); + } else { + Object[] args = { lineStr, message }; + message = getMessage("msg.format2", args); + } + } else { + Object[] args = { message }; + message = getMessage("msg.format1", args); + } + if (justWarning) { + message = getMessage("msg.warning", message); + } + err.println(messagePrefix + message); + if (null != lineSource) { + err.println(messagePrefix + lineSource); + err.println(messagePrefix + buildIndicator(lineOffset)); + } + } + + private String buildIndicator(int offset){ + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < offset-1; i++) + sb.append("."); + sb.append("^"); + return sb.toString(); + } + + private final String messagePrefix = "js: "; + private boolean hasReportedErrorFlag; + private boolean reportWarnings; + private PrintStream err; +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java new file mode 100644 index 0000000..de8fcde --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Dim.java @@ -0,0 +1,1560 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * SeeBeyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Matt Gould + * Christopher Oliver + * Cameron McCormack + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.debugger; + +import org.mozilla.javascript.*; +import org.mozilla.javascript.debug.*; +import java.util.*; +import java.io.*; +import java.net.URL; + +/** + * Dim or Debugger Implementation for Rhino. + */ +public class Dim { + + // Constants for instructing the debugger what action to perform + // to end interruption. Used by 'returnValue'. + public static final int STEP_OVER = 0; + public static final int STEP_INTO = 1; + public static final int STEP_OUT = 2; + public static final int GO = 3; + public static final int BREAK = 4; + public static final int EXIT = 5; + + // Constants for the DimIProxy interface implementation class. + private static final int IPROXY_DEBUG = 0; + private static final int IPROXY_LISTEN = 1; + private static final int IPROXY_COMPILE_SCRIPT = 2; + private static final int IPROXY_EVAL_SCRIPT = 3; + private static final int IPROXY_STRING_IS_COMPILABLE = 4; + private static final int IPROXY_OBJECT_TO_STRING = 5; + private static final int IPROXY_OBJECT_PROPERTY = 6; + private static final int IPROXY_OBJECT_IDS = 7; + + /** + * Interface to the debugger GUI. + */ + private GuiCallback callback; + + /** + * Whether the debugger should break. + */ + private boolean breakFlag; + + /** + * The ScopeProvider object that provides the scope in which to + * evaluate script. + */ + private ScopeProvider scopeProvider; + + /** + * The index of the current stack frame. + */ + private int frameIndex = -1; + + /** + * Information about the current stack at the point of interruption. + */ + private volatile ContextData interruptedContextData; + + /** + * The ContextFactory to listen to for debugging information. + */ + private ContextFactory contextFactory; + + /** + * Synchronization object used to allow script evaluations to + * happen when a thread is resumed. + */ + private Object monitor = new Object(); + + /** + * Synchronization object used to wait for valid + * {@link #interruptedContextData}. + */ + private Object eventThreadMonitor = new Object(); + + /** + * The action to perform to end the interruption loop. + */ + private volatile int returnValue = -1; + + /** + * Whether the debugger is inside the interruption loop. + */ + private boolean insideInterruptLoop; + + /** + * The requested script string to be evaluated when the thread + * has been resumed. + */ + private String evalRequest; + + /** + * The stack frame in which to evaluate {@link #evalRequest}. + */ + private StackFrame evalFrame; + + /** + * The result of evaluating {@link #evalRequest}. + */ + private String evalResult; + + /** + * Whether the debugger should break when a script exception is thrown. + */ + private boolean breakOnExceptions; + + /** + * Whether the debugger should break when a script function is entered. + */ + private boolean breakOnEnter; + + /** + * Whether the debugger should break when a script function is returned + * from. + */ + private boolean breakOnReturn; + + /** + * Table mapping URLs to information about the script source. + */ + private final Hashtable urlToSourceInfo = new Hashtable(); + + /** + * Table mapping function names to information about the function. + */ + private final Hashtable functionNames = new Hashtable(); + + /** + * Table mapping functions to information about the function. + */ + private final Hashtable functionToSource = new Hashtable(); + + /** + * ContextFactory.Listener instance attached to {@link #contextFactory}. + */ + private DimIProxy listener; + + /** + * Sets the GuiCallback object to use. + */ + public void setGuiCallback(GuiCallback callback) { + this.callback = callback; + } + + /** + * Tells the debugger to break at the next opportunity. + */ + public void setBreak() { + this.breakFlag = true; + } + + /** + * Sets the ScopeProvider to be used. + */ + public void setScopeProvider(ScopeProvider scopeProvider) { + this.scopeProvider = scopeProvider; + } + + /** + * Switches context to the stack frame with the given index. + */ + public void contextSwitch(int frameIndex) { + this.frameIndex = frameIndex; + } + + /** + * Sets whether the debugger should break on exceptions. + */ + public void setBreakOnExceptions(boolean breakOnExceptions) { + this.breakOnExceptions = breakOnExceptions; + } + + /** + * Sets whether the debugger should break on function entering. + */ + public void setBreakOnEnter(boolean breakOnEnter) { + this.breakOnEnter = breakOnEnter; + } + + /** + * Sets whether the debugger should break on function return. + */ + public void setBreakOnReturn(boolean breakOnReturn) { + this.breakOnReturn = breakOnReturn; + } + + /** + * Attaches the debugger to the given ContextFactory. + */ + public void attachTo(ContextFactory factory) { + detach(); + this.contextFactory = factory; + this.listener = new DimIProxy(this, IPROXY_LISTEN); + factory.addListener(this.listener); + } + + /** + * Detaches the debugger from the current ContextFactory. + */ + public void detach() { + if (listener != null) { + contextFactory.removeListener(listener); + contextFactory = null; + listener = null; + } + } + + /** + * Releases resources associated with this debugger. + */ + public void dispose() { + detach(); + } + + /** + * Returns the FunctionSource object for the given script or function. + */ + private FunctionSource getFunctionSource(DebuggableScript fnOrScript) { + FunctionSource fsource = functionSource(fnOrScript); + if (fsource == null) { + String url = getNormalizedUrl(fnOrScript); + SourceInfo si = sourceInfo(url); + if (si == null) { + if (!fnOrScript.isGeneratedScript()) { + // Not eval or Function, try to load it from URL + String source = loadSource(url); + if (source != null) { + DebuggableScript top = fnOrScript; + for (;;) { + DebuggableScript parent = top.getParent(); + if (parent == null) { + break; + } + top = parent; + } + registerTopScript(top, source); + fsource = functionSource(fnOrScript); + } + } + } + } + return fsource; + } + + /** + * Loads the script at the given URL. + */ + private String loadSource(String sourceUrl) { + String source = null; + int hash = sourceUrl.indexOf('#'); + if (hash >= 0) { + sourceUrl = sourceUrl.substring(0, hash); + } + try { + InputStream is; + openStream: + { + if (sourceUrl.indexOf(':') < 0) { + // Can be a file name + try { + if (sourceUrl.startsWith("~/")) { + String home = SecurityUtilities.getSystemProperty("user.home"); + if (home != null) { + String pathFromHome = sourceUrl.substring(2); + File f = new File(new File(home), pathFromHome); + if (f.exists()) { + is = new FileInputStream(f); + break openStream; + } + } + } + File f = new File(sourceUrl); + if (f.exists()) { + is = new FileInputStream(f); + break openStream; + } + } catch (SecurityException ex) { } + // No existing file, assume missed http:// + if (sourceUrl.startsWith("//")) { + sourceUrl = "http:" + sourceUrl; + } else if (sourceUrl.startsWith("/")) { + sourceUrl = "http://127.0.0.1" + sourceUrl; + } else { + sourceUrl = "http://" + sourceUrl; + } + } + + is = (new URL(sourceUrl)).openStream(); + } + + try { + source = Kit.readReader(new InputStreamReader(is)); + } finally { + is.close(); + } + } catch (IOException ex) { + System.err.println + ("Failed to load source from "+sourceUrl+": "+ ex); + } + return source; + } + + /** + * Registers the given script as a top-level script in the debugger. + */ + private void registerTopScript(DebuggableScript topScript, String source) { + if (!topScript.isTopLevel()) { + throw new IllegalArgumentException(); + } + String url = getNormalizedUrl(topScript); + DebuggableScript[] functions = getAllFunctions(topScript); + final SourceInfo sourceInfo = new SourceInfo(source, functions, url); + + synchronized (urlToSourceInfo) { + SourceInfo old = (SourceInfo)urlToSourceInfo.get(url); + if (old != null) { + sourceInfo.copyBreakpointsFrom(old); + } + urlToSourceInfo.put(url, sourceInfo); + for (int i = 0; i != sourceInfo.functionSourcesTop(); ++i) { + FunctionSource fsource = sourceInfo.functionSource(i); + String name = fsource.name(); + if (name.length() != 0) { + functionNames.put(name, fsource); + } + } + } + + synchronized (functionToSource) { + for (int i = 0; i != functions.length; ++i) { + FunctionSource fsource = sourceInfo.functionSource(i); + functionToSource.put(functions[i], fsource); + } + } + + callback.updateSourceText(sourceInfo); + } + + /** + * Returns the FunctionSource object for the given function or script. + */ + private FunctionSource functionSource(DebuggableScript fnOrScript) { + return (FunctionSource)functionToSource.get(fnOrScript); + } + + /** + * Returns an array of all function names. + */ + public String[] functionNames() { + String[] a; + synchronized (urlToSourceInfo) { + Enumeration e = functionNames.keys(); + a = new String[functionNames.size()]; + int i = 0; + while (e.hasMoreElements()) { + a[i++] = (String)e.nextElement(); + } + } + return a; + } + + /** + * Returns the FunctionSource object for the function with the given name. + */ + public FunctionSource functionSourceByName(String functionName) { + return (FunctionSource)functionNames.get(functionName); + } + + /** + * Returns the SourceInfo object for the given URL. + */ + public SourceInfo sourceInfo(String url) { + return (SourceInfo)urlToSourceInfo.get(url); + } + + /** + * Returns the source URL for the given script or function. + */ + private String getNormalizedUrl(DebuggableScript fnOrScript) { + String url = fnOrScript.getSourceName(); + if (url == null) { url = ""; } + else { + // Not to produce window for eval from different lines, + // strip line numbers, i.e. replace all #[0-9]+\(eval\) by + // (eval) + // Option: similar teatment for Function? + char evalSeparator = '#'; + StringBuffer sb = null; + int urlLength = url.length(); + int cursor = 0; + for (;;) { + int searchStart = url.indexOf(evalSeparator, cursor); + if (searchStart < 0) { + break; + } + String replace = null; + int i = searchStart + 1; + while (i != urlLength) { + int c = url.charAt(i); + if (!('0' <= c && c <= '9')) { + break; + } + ++i; + } + if (i != searchStart + 1) { + // i points after #[0-9]+ + if ("(eval)".regionMatches(0, url, i, 6)) { + cursor = i + 6; + replace = "(eval)"; + } + } + if (replace == null) { + break; + } + if (sb == null) { + sb = new StringBuffer(); + sb.append(url.substring(0, searchStart)); + } + sb.append(replace); + } + if (sb != null) { + if (cursor != urlLength) { + sb.append(url.substring(cursor)); + } + url = sb.toString(); + } + } + return url; + } + + /** + * Returns an array of all functions in the given script. + */ + private static DebuggableScript[] getAllFunctions + (DebuggableScript function) { + ObjArray functions = new ObjArray(); + collectFunctions_r(function, functions); + DebuggableScript[] result = new DebuggableScript[functions.size()]; + functions.toArray(result); + return result; + } + + /** + * Helper function for {@link #getAllFunctions(DebuggableScript)}. + */ + private static void collectFunctions_r(DebuggableScript function, + ObjArray array) { + array.add(function); + for (int i = 0; i != function.getFunctionCount(); ++i) { + collectFunctions_r(function.getFunction(i), array); + } + } + + /** + * Clears all breakpoints. + */ + public void clearAllBreakpoints() { + Enumeration e = urlToSourceInfo.elements(); + while (e.hasMoreElements()) { + SourceInfo si = (SourceInfo)e.nextElement(); + si.removeAllBreakpoints(); + } + } + + /** + * Called when a breakpoint has been hit. + */ + private void handleBreakpointHit(StackFrame frame, Context cx) { + breakFlag = false; + interrupted(cx, frame, null); + } + + /** + * Called when a script exception has been thrown. + */ + private void handleExceptionThrown(Context cx, Throwable ex, + StackFrame frame) { + if (breakOnExceptions) { + ContextData cd = frame.contextData(); + if (cd.lastProcessedException != ex) { + interrupted(cx, frame, ex); + cd.lastProcessedException = ex; + } + } + } + + /** + * Returns the current ContextData object. + */ + public ContextData currentContextData() { + return interruptedContextData; + } + + /** + * Sets the action to perform to end interruption. + */ + public void setReturnValue(int returnValue) { + synchronized (monitor) { + this.returnValue = returnValue; + monitor.notify(); + } + } + + /** + * Resumes execution of script. + */ + public void go() { + synchronized (monitor) { + this.returnValue = GO; + monitor.notifyAll(); + } + } + + /** + * Evaluates the given script. + */ + public String eval(String expr) { + String result = "undefined"; + if (expr == null) { + return result; + } + ContextData contextData = currentContextData(); + if (contextData == null || frameIndex >= contextData.frameCount()) { + return result; + } + StackFrame frame = contextData.getFrame(frameIndex); + if (contextData.eventThreadFlag) { + Context cx = Context.getCurrentContext(); + result = do_eval(cx, frame, expr); + } else { + synchronized (monitor) { + if (insideInterruptLoop) { + evalRequest = expr; + evalFrame = frame; + monitor.notify(); + do { + try { + monitor.wait(); + } catch (InterruptedException exc) { + Thread.currentThread().interrupt(); + break; + } + } while (evalRequest != null); + result = evalResult; + } + } + } + return result; + } + + /** + * Compiles the given script. + */ + public void compileScript(String url, String text) { + DimIProxy action = new DimIProxy(this, IPROXY_COMPILE_SCRIPT); + action.url = url; + action.text = text; + action.withContext(); + } + + /** + * Evaluates the given script. + */ + public void evalScript(final String url, final String text) { + DimIProxy action = new DimIProxy(this, IPROXY_EVAL_SCRIPT); + action.url = url; + action.text = text; + action.withContext(); + } + + /** + * Converts the given script object to a string. + */ + public String objectToString(Object object) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_TO_STRING); + action.object = object; + action.withContext(); + return action.stringResult; + } + + /** + * Returns whether the given string is syntactically valid script. + */ + public boolean stringIsCompilableUnit(String str) { + DimIProxy action = new DimIProxy(this, IPROXY_STRING_IS_COMPILABLE); + action.text = str; + action.withContext(); + return action.booleanResult; + } + + /** + * Returns the value of a property on the given script object. + */ + public Object getObjectProperty(Object object, Object id) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_PROPERTY); + action.object = object; + action.id = id; + action.withContext(); + return action.objectResult; + } + + /** + * Returns an array of the property names on the given script object. + */ + public Object[] getObjectIds(Object object) { + DimIProxy action = new DimIProxy(this, IPROXY_OBJECT_IDS); + action.object = object; + action.withContext(); + return action.objectArrayResult; + } + + /** + * Returns the value of a property on the given script object. + */ + private Object getObjectPropertyImpl(Context cx, Object object, + Object id) { + Scriptable scriptable = (Scriptable)object; + Object result; + if (id instanceof String) { + String name = (String)id; + if (name.equals("this")) { + result = scriptable; + } else if (name.equals("__proto__")) { + result = scriptable.getPrototype(); + } else if (name.equals("__parent__")) { + result = scriptable.getParentScope(); + } else { + result = ScriptableObject.getProperty(scriptable, name); + if (result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + } + } else { + int index = ((Integer)id).intValue(); + result = ScriptableObject.getProperty(scriptable, index); + if (result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + } + return result; + } + + /** + * Returns an array of the property names on the given script object. + */ + private Object[] getObjectIdsImpl(Context cx, Object object) { + if (!(object instanceof Scriptable) || object == Undefined.instance) { + return Context.emptyArgs; + } + + Object[] ids; + Scriptable scriptable = (Scriptable)object; + if (scriptable instanceof DebuggableObject) { + ids = ((DebuggableObject)scriptable).getAllIds(); + } else { + ids = scriptable.getIds(); + } + + Scriptable proto = scriptable.getPrototype(); + Scriptable parent = scriptable.getParentScope(); + int extra = 0; + if (proto != null) { + ++extra; + } + if (parent != null) { + ++extra; + } + if (extra != 0) { + Object[] tmp = new Object[extra + ids.length]; + System.arraycopy(ids, 0, tmp, extra, ids.length); + ids = tmp; + extra = 0; + if (proto != null) { + ids[extra++] = "__proto__"; + } + if (parent != null) { + ids[extra++] = "__parent__"; + } + } + + return ids; + } + + /** + * Interrupts script execution. + */ + private void interrupted(Context cx, final StackFrame frame, + Throwable scriptException) { + ContextData contextData = frame.contextData(); + boolean eventThreadFlag = callback.isGuiEventThread(); + contextData.eventThreadFlag = eventThreadFlag; + + boolean recursiveEventThreadCall = false; + +interruptedCheck: + synchronized (eventThreadMonitor) { + if (eventThreadFlag) { + if (interruptedContextData != null) { + recursiveEventThreadCall = true; + break interruptedCheck; + } + } else { + while (interruptedContextData != null) { + try { + eventThreadMonitor.wait(); + } catch (InterruptedException exc) { + return; + } + } + } + interruptedContextData = contextData; + } + + if (recursiveEventThreadCall) { + // XXX: For now the following is commented out as on Linux + // too deep recursion of dispatchNextGuiEvent causes GUI lockout. + // Note: it can make GUI unresponsive if long-running script + // will be called on GUI thread while processing another interrupt + if (false) { + // Run event dispatch until gui sets a flag to exit the initial + // call to interrupted. + while (this.returnValue == -1) { + try { + callback.dispatchNextGuiEvent(); + } catch (InterruptedException exc) { + } + } + } + return; + } + + if (interruptedContextData == null) Kit.codeBug(); + + try { + do { + int frameCount = contextData.frameCount(); + this.frameIndex = frameCount -1; + + final String threadTitle = Thread.currentThread().toString(); + final String alertMessage; + if (scriptException == null) { + alertMessage = null; + } else { + alertMessage = scriptException.toString(); + } + + int returnValue = -1; + if (!eventThreadFlag) { + synchronized (monitor) { + if (insideInterruptLoop) Kit.codeBug(); + this.insideInterruptLoop = true; + this.evalRequest = null; + this.returnValue = -1; + callback.enterInterrupt(frame, threadTitle, + alertMessage); + try { + for (;;) { + try { + monitor.wait(); + } catch (InterruptedException exc) { + Thread.currentThread().interrupt(); + break; + } + if (evalRequest != null) { + this.evalResult = null; + try { + evalResult = do_eval(cx, evalFrame, + evalRequest); + } finally { + evalRequest = null; + evalFrame = null; + monitor.notify(); + } + continue; + } + if (this.returnValue != -1) { + returnValue = this.returnValue; + break; + } + } + } finally { + insideInterruptLoop = false; + } + } + } else { + this.returnValue = -1; + callback.enterInterrupt(frame, threadTitle, alertMessage); + while (this.returnValue == -1) { + try { + callback.dispatchNextGuiEvent(); + } catch (InterruptedException exc) { + } + } + returnValue = this.returnValue; + } + switch (returnValue) { + case STEP_OVER: + contextData.breakNextLine = true; + contextData.stopAtFrameDepth = contextData.frameCount(); + break; + case STEP_INTO: + contextData.breakNextLine = true; + contextData.stopAtFrameDepth = -1; + break; + case STEP_OUT: + if (contextData.frameCount() > 1) { + contextData.breakNextLine = true; + contextData.stopAtFrameDepth + = contextData.frameCount() -1; + } + break; + } + } while (false); + } finally { + synchronized (eventThreadMonitor) { + interruptedContextData = null; + eventThreadMonitor.notifyAll(); + } + } + + } + + /** + * Evaluates script in the given stack frame. + */ + private static String do_eval(Context cx, StackFrame frame, String expr) { + String resultString; + Debugger saved_debugger = cx.getDebugger(); + Object saved_data = cx.getDebuggerContextData(); + int saved_level = cx.getOptimizationLevel(); + + cx.setDebugger(null, null); + cx.setOptimizationLevel(-1); + cx.setGeneratingDebug(false); + try { + Callable script = (Callable)cx.compileString(expr, "", 0, null); + Object result = script.call(cx, frame.scope, frame.thisObj, + ScriptRuntime.emptyArgs); + if (result == Undefined.instance) { + resultString = ""; + } else { + resultString = ScriptRuntime.toString(result); + } + } catch (Exception exc) { + resultString = exc.getMessage(); + } finally { + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(saved_level); + cx.setDebugger(saved_debugger, saved_data); + } + if (resultString == null) { + resultString = "null"; + } + return resultString; + } + + /** + * Proxy class to implement debug interfaces without bloat of class + * files. + */ + private static class DimIProxy + implements ContextAction, ContextFactory.Listener, Debugger { + + /** + * The debugger. + */ + private Dim dim; + + /** + * The interface implementation type. One of the IPROXY_* constants + * defined in {@link Dim}. + */ + private int type; + + /** + * The URL origin of the script to compile or evaluate. + */ + private String url; + + /** + * The text of the script to compile, evaluate or test for compilation. + */ + private String text; + + /** + * The object to convert, get a property from or enumerate. + */ + private Object object; + + /** + * The property to look up in {@link #object}. + */ + private Object id; + + /** + * The boolean result of the action. + */ + private boolean booleanResult; + + /** + * The String result of the action. + */ + private String stringResult; + + /** + * The Object result of the action. + */ + private Object objectResult; + + /** + * The Object[] result of the action. + */ + private Object[] objectArrayResult; + + /** + * Creates a new DimIProxy. + */ + private DimIProxy(Dim dim, int type) { + this.dim = dim; + this.type = type; + } + + // ContextAction + + /** + * Performs the action given by {@link #type}. + */ + public Object run(Context cx) { + switch (type) { + case IPROXY_COMPILE_SCRIPT: + cx.compileString(text, url, 1, null); + break; + + case IPROXY_EVAL_SCRIPT: + { + Scriptable scope = null; + if (dim.scopeProvider != null) { + scope = dim.scopeProvider.getScope(); + } + if (scope == null) { + scope = new ImporterTopLevel(cx); + } + cx.evaluateString(scope, text, url, 1, null); + } + break; + + case IPROXY_STRING_IS_COMPILABLE: + booleanResult = cx.stringIsCompilableUnit(text); + break; + + case IPROXY_OBJECT_TO_STRING: + if (object == Undefined.instance) { + stringResult = "undefined"; + } else if (object == null) { + stringResult = "null"; + } else if (object instanceof NativeCall) { + stringResult = "[object Call]"; + } else { + stringResult = Context.toString(object); + } + break; + + case IPROXY_OBJECT_PROPERTY: + objectResult = dim.getObjectPropertyImpl(cx, object, id); + break; + + case IPROXY_OBJECT_IDS: + objectArrayResult = dim.getObjectIdsImpl(cx, object); + break; + + default: + throw Kit.codeBug(); + } + return null; + } + + /** + * Performs the action given by {@link #type} with the attached + * {@link ContextFactory}. + */ + private void withContext() { + dim.contextFactory.call(this); + } + + // ContextFactory.Listener + + /** + * Called when a Context is created. + */ + public void contextCreated(Context cx) { + if (type != IPROXY_LISTEN) Kit.codeBug(); + ContextData contextData = new ContextData(); + Debugger debugger = new DimIProxy(dim, IPROXY_DEBUG); + cx.setDebugger(debugger, contextData); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + } + + /** + * Called when a Context is destroyed. + */ + public void contextReleased(Context cx) { + if (type != IPROXY_LISTEN) Kit.codeBug(); + } + + // Debugger + + /** + * Returns a StackFrame for the given function or script. + */ + public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) { + if (type != IPROXY_DEBUG) Kit.codeBug(); + + FunctionSource item = dim.getFunctionSource(fnOrScript); + if (item == null) { + // Can not debug if source is not available + return null; + } + return new StackFrame(cx, dim, item); + } + + /** + * Called when compilation is finished. + */ + public void handleCompilationDone(Context cx, + DebuggableScript fnOrScript, + String source) { + if (type != IPROXY_DEBUG) Kit.codeBug(); + + if (!fnOrScript.isTopLevel()) { + return; + } + dim.registerTopScript(fnOrScript, source); + } + } + + /** + * Class to store information about a stack. + */ + public static class ContextData { + + /** + * The stack frames. + */ + private ObjArray frameStack = new ObjArray(); + + /** + * Whether the debugger should break at the next line in this context. + */ + private boolean breakNextLine; + + /** + * The frame depth the debugger should stop at. Used to implement + * "step over" and "step out". + */ + private int stopAtFrameDepth = -1; + + /** + * Whether this context is in the event thread. + */ + private boolean eventThreadFlag; + + /** + * The last exception that was processed. + */ + private Throwable lastProcessedException; + + /** + * Returns the ContextData for the given Context. + */ + public static ContextData get(Context cx) { + return (ContextData) cx.getDebuggerContextData(); + } + + /** + * Returns the number of stack frames. + */ + public int frameCount() { + return frameStack.size(); + } + + /** + * Returns the stack frame with the given index. + */ + public StackFrame getFrame(int frameNumber) { + int num = frameStack.size() - frameNumber - 1; + return (StackFrame) frameStack.get(num); + } + + /** + * Pushes a stack frame on to the stack. + */ + private void pushFrame(StackFrame frame) { + frameStack.push(frame); + } + + /** + * Pops a stack frame from the stack. + */ + private void popFrame() { + frameStack.pop(); + } + } + + /** + * Object to represent one stack frame. + */ + public static class StackFrame implements DebugFrame { + + /** + * The debugger. + */ + private Dim dim; + + /** + * The ContextData for the Context being debugged. + */ + private ContextData contextData; + + /** + * The scope. + */ + private Scriptable scope; + + /** + * The 'this' object. + */ + private Scriptable thisObj; + + /** + * Information about the function. + */ + private FunctionSource fsource; + + /** + * Array of breakpoint state for each source line. + */ + private boolean[] breakpoints; + + /** + * Current line number. + */ + private int lineNumber; + + /** + * Creates a new StackFrame. + */ + private StackFrame(Context cx, Dim dim, FunctionSource fsource) { + this.dim = dim; + this.contextData = ContextData.get(cx); + this.fsource = fsource; + this.breakpoints = fsource.sourceInfo().breakpoints; + this.lineNumber = fsource.firstLine(); + } + + /** + * Called when the stack frame is entered. + */ + public void onEnter(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + contextData.pushFrame(this); + this.scope = scope; + this.thisObj = thisObj; + if (dim.breakOnEnter) { + dim.handleBreakpointHit(this, cx); + } + } + + /** + * Called when the current position has changed. + */ + public void onLineChange(Context cx, int lineno) { + this.lineNumber = lineno; + + if (!breakpoints[lineno] && !dim.breakFlag) { + boolean lineBreak = contextData.breakNextLine; + if (lineBreak && contextData.stopAtFrameDepth >= 0) { + lineBreak = (contextData.frameCount() + <= contextData.stopAtFrameDepth); + } + if (!lineBreak) { + return; + } + contextData.stopAtFrameDepth = -1; + contextData.breakNextLine = false; + } + + dim.handleBreakpointHit(this, cx); + } + + /** + * Called when an exception has been thrown. + */ + public void onExceptionThrown(Context cx, Throwable exception) { + dim.handleExceptionThrown(cx, exception, this); + } + + /** + * Called when the stack frame has been left. + */ + public void onExit(Context cx, boolean byThrow, + Object resultOrException) { + if (dim.breakOnReturn && !byThrow) { + dim.handleBreakpointHit(this, cx); + } + contextData.popFrame(); + } + + /** + * Called when a 'debugger' statement is executed. + */ + public void onDebuggerStatement(Context cx) { + dim.handleBreakpointHit(this, cx); + } + + /** + * Returns the SourceInfo object for the function. + */ + public SourceInfo sourceInfo() { + return fsource.sourceInfo(); + } + + /** + * Returns the ContextData object for the Context. + */ + public ContextData contextData() { + return contextData; + } + + /** + * Returns the scope object for this frame. + */ + public Object scope() { + return scope; + } + + /** + * Returns the 'this' object for this frame. + */ + public Object thisObj() { + return thisObj; + } + + /** + * Returns the source URL. + */ + public String getUrl() { + return fsource.sourceInfo().url(); + } + + /** + * Returns the current line number. + */ + public int getLineNumber() { + return lineNumber; + } + } + + /** + * Class to store information about a function. + */ + public static class FunctionSource { + + /** + * Information about the source of the function. + */ + private SourceInfo sourceInfo; + + /** + * Line number of the first line of the function. + */ + private int firstLine; + + /** + * The function name. + */ + private String name; + + /** + * Creates a new FunctionSource. + */ + private FunctionSource(SourceInfo sourceInfo, int firstLine, + String name) { + if (name == null) throw new IllegalArgumentException(); + this.sourceInfo = sourceInfo; + this.firstLine = firstLine; + this.name = name; + } + + /** + * Returns the SourceInfo object that describes the source of the + * function. + */ + public SourceInfo sourceInfo() { + return sourceInfo; + } + + /** + * Returns the line number of the first line of the function. + */ + public int firstLine() { + return firstLine; + } + + /** + * Returns the name of the function. + */ + public String name() { + return name; + } + } + + /** + * Class to store information about a script source. + */ + public static class SourceInfo { + + /** + * An empty array of booleans. + */ + private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; + + /** + * The script. + */ + private String source; + + /** + * The URL of the script. + */ + private String url; + + /** + * Array indicating which lines can have breakpoints set. + */ + private boolean[] breakableLines; + + /** + * Array indicating whether a breakpoint is set on the line. + */ + private boolean[] breakpoints; + + /** + * Array of FunctionSource objects for the functions in the script. + */ + private FunctionSource[] functionSources; + + /** + * Creates a new SourceInfo object. + */ + private SourceInfo(String source, DebuggableScript[] functions, + String normilizedUrl) { + this.source = source; + this.url = normilizedUrl; + + int N = functions.length; + int[][] lineArrays = new int[N][]; + for (int i = 0; i != N; ++i) { + lineArrays[i] = functions[i].getLineNumbers(); + } + + int minAll = 0, maxAll = -1; + int[] firstLines = new int[N]; + for (int i = 0; i != N; ++i) { + int[] lines = lineArrays[i]; + if (lines == null || lines.length == 0) { + firstLines[i] = -1; + } else { + int min, max; + min = max = lines[0]; + for (int j = 1; j != lines.length; ++j) { + int line = lines[j]; + if (line < min) { + min = line; + } else if (line > max) { + max = line; + } + } + firstLines[i] = min; + if (minAll > maxAll) { + minAll = min; + maxAll = max; + } else { + if (min < minAll) { + minAll = min; + } + if (max > maxAll) { + maxAll = max; + } + } + } + } + + if (minAll > maxAll) { + // No line information + this.breakableLines = EMPTY_BOOLEAN_ARRAY; + this.breakpoints = EMPTY_BOOLEAN_ARRAY; + } else { + if (minAll < 0) { + // Line numbers can not be negative + throw new IllegalStateException(String.valueOf(minAll)); + } + int linesTop = maxAll + 1; + this.breakableLines = new boolean[linesTop]; + this.breakpoints = new boolean[linesTop]; + for (int i = 0; i != N; ++i) { + int[] lines = lineArrays[i]; + if (lines != null && lines.length != 0) { + for (int j = 0; j != lines.length; ++j) { + int line = lines[j]; + this.breakableLines[line] = true; + } + } + } + } + this.functionSources = new FunctionSource[N]; + for (int i = 0; i != N; ++i) { + String name = functions[i].getFunctionName(); + if (name == null) { + name = ""; + } + this.functionSources[i] + = new FunctionSource(this, firstLines[i], name); + } + } + + /** + * Returns the source text. + */ + public String source() { + return this.source; + } + + /** + * Returns the script's origin URL. + */ + public String url() { + return this.url; + } + + /** + * Returns the number of FunctionSource objects stored in this object. + */ + public int functionSourcesTop() { + return functionSources.length; + } + + /** + * Returns the FunctionSource object with the given index. + */ + public FunctionSource functionSource(int i) { + return functionSources[i]; + } + + /** + * Copies the breakpoints from the given SourceInfo object into this + * one. + */ + private void copyBreakpointsFrom(SourceInfo old) { + int end = old.breakpoints.length; + if (end > this.breakpoints.length) { + end = this.breakpoints.length; + } + for (int line = 0; line != end; ++line) { + if (old.breakpoints[line]) { + this.breakpoints[line] = true; + } + } + } + + /** + * Returns whether the given line number can have a breakpoint set on + * it. + */ + public boolean breakableLine(int line) { + return (line < this.breakableLines.length) + && this.breakableLines[line]; + } + + /** + * Returns whether there is a breakpoint set on the given line. + */ + public boolean breakpoint(int line) { + if (!breakableLine(line)) { + throw new IllegalArgumentException(String.valueOf(line)); + } + return line < this.breakpoints.length && this.breakpoints[line]; + } + + /** + * Sets or clears the breakpoint flag for the given line. + */ + public boolean breakpoint(int line, boolean value) { + if (!breakableLine(line)) { + throw new IllegalArgumentException(String.valueOf(line)); + } + boolean changed; + synchronized (breakpoints) { + if (breakpoints[line] != value) { + breakpoints[line] = value; + changed = true; + } else { + changed = false; + } + } + return changed; + } + + /** + * Removes all breakpoints from the script. + */ + public void removeAllBreakpoints() { + synchronized (breakpoints) { + for (int line = 0; line != breakpoints.length; ++line) { + breakpoints[line] = false; + } + } + } + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/GuiCallback.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/GuiCallback.java new file mode 100644 index 0000000..f9762ec --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/GuiCallback.java @@ -0,0 +1,71 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * Cameron McCormack + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.debugger; + +/** + * Interface for communication between the debugger and its GUI. This + * should be implemented by the GUI. + */ +public interface GuiCallback { + + /** + * Called when the source text of some script has been changed. + */ + void updateSourceText(Dim.SourceInfo sourceInfo); + + /** + * Called when the interrupt loop has been entered. + */ + void enterInterrupt(Dim.StackFrame lastFrame, + String threadTitle, + String alertMessage); + + /** + * Returns whether the current thread is the GUI's event thread. + * This information is required to avoid blocking the event thread + * from the debugger. + */ + boolean isGuiEventThread(); + + /** + * Processes the next GUI event. This manual pumping of GUI events + * is necessary when the GUI event thread itself has been stopped. + */ + void dispatchNextGuiEvent() throws InterruptedException; +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Main.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Main.java new file mode 100644 index 0000000..3f90915 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/Main.java @@ -0,0 +1,431 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * SeeBeyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Matt Gould + * Christopher Oliver + * Cameron McCormack + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.debugger; + +import java.io.InputStream; +import java.io.PrintStream; + +import javax.swing.JFrame; + +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.shell.Global; + +/** + * Rhino script debugger main class. This class links together a + * debugger object ({@link Dim}) and a debugger GUI object ({@link SwingGui}). + */ +public class Main { + + /** + * The debugger. + */ + private Dim dim; + + /** + * The debugger frame. + */ + private SwingGui debugGui; + + /** + * Creates a new Main. + */ + public Main(String title) { + dim = new Dim(); + debugGui = new SwingGui(dim, title); + } + + /** + * Returns the debugger window {@link JFrame}. + */ + public JFrame getDebugFrame() { + return debugGui; + } + + /** + * Breaks execution of the script. + */ + public void doBreak() { + dim.setBreak(); + } + + /** + * Sets whether execution should break when a script exception is thrown. + */ + public void setBreakOnExceptions(boolean value) { + dim.setBreakOnExceptions(value); + debugGui.getMenubar().getBreakOnExceptions().setSelected(value); + } + + /** + * Sets whether execution should break when a function is entered. + */ + public void setBreakOnEnter(boolean value) { + dim.setBreakOnEnter(value); + debugGui.getMenubar().getBreakOnEnter().setSelected(value); + } + + /** + * Sets whether execution should break when a function is left. + */ + public void setBreakOnReturn(boolean value) { + dim.setBreakOnReturn(value); + debugGui.getMenubar().getBreakOnReturn().setSelected(value); + } + + /** + * Removes all breakpoints. + */ + public void clearAllBreakpoints() { + dim.clearAllBreakpoints(); + } + + /** + * Resumes execution of the script. + */ + public void go() { + dim.go(); + } + + /** + * Sets the scope to be used for script evaluation. + */ + public void setScope(Scriptable scope) { + setScopeProvider(IProxy.newScopeProvider(scope)); + } + + /** + * Sets the {@link ScopeProvider} that provides a scope to be used + * for script evaluation. + */ + public void setScopeProvider(ScopeProvider p) { + dim.setScopeProvider(p); + } + + /** + * Assign a Runnable object that will be invoked when the user + * selects "Exit..." or closes the Debugger main window. + */ + public void setExitAction(Runnable r) { + debugGui.setExitAction(r); + } + + /** + * Returns an {@link InputStream} for stdin from the debugger's internal + * Console window. + */ + public InputStream getIn() { + return debugGui.getConsole().getIn(); + } + + /** + * Returns a {@link PrintStream} for stdout to the debugger's internal + * Console window. + */ + public PrintStream getOut() { + return debugGui.getConsole().getOut(); + } + + /** + * Returns a {@link PrintStream} for stderr in the Debugger's internal + * Console window. + */ + public PrintStream getErr() { + return debugGui.getConsole().getErr(); + } + + /** + * Packs the debugger GUI frame. + */ + public void pack() { + debugGui.pack(); + } + + /** + * Sets the debugger GUI frame dimensions. + */ + public void setSize(int w, int h) { + debugGui.setSize(w, h); + } + + /** + * Sets the visibility of the debugger GUI frame. + */ + public void setVisible(boolean flag) { + debugGui.setVisible(flag); + } + + /** + * Returns whether the debugger GUI frame is visible. + */ + public boolean isVisible() { + return debugGui.isVisible(); + } + + /** + * Frees any resources held by the debugger. + */ + public void dispose() { + clearAllBreakpoints(); + dim.go(); + debugGui.dispose(); + dim = null; + } + + /** + * Attaches the debugger to the given {@link ContextFactory}. + */ + public void attachTo(ContextFactory factory) { + dim.attachTo(factory); + } + + /** + * Detaches from the current {@link ContextFactory}. + */ + public void detach() { + dim.detach(); + } + + /** + * Main entry point. Creates a debugger attached to a Rhino + * {@link org.mozilla.javascript.tools.shell.Main} shell session. + */ + public static void main(String[] args) { + Main main = new Main("Rhino JavaScript Debugger"); + main.doBreak(); + main.setExitAction(new IProxy(IProxy.EXIT_ACTION)); + + System.setIn(main.getIn()); + System.setOut(main.getOut()); + System.setErr(main.getErr()); + + Global global = org.mozilla.javascript.tools.shell.Main.getGlobal(); + global.setIn(main.getIn()); + global.setOut(main.getOut()); + global.setErr(main.getErr()); + + main.attachTo( + org.mozilla.javascript.tools.shell.Main.shellContextFactory); + + main.setScope(global); + + main.pack(); + main.setSize(600, 460); + main.setVisible(true); + + org.mozilla.javascript.tools.shell.Main.exec(args); + } + + /** + * Entry point for embedded applications. This method attaches + * to the global {@link ContextFactory} with a scope of a newly + * created {@link Global} object. No I/O redirection is performed + * as with {@link #main(String[])}. + */ + public static void mainEmbedded(String title) { + ContextFactory factory = ContextFactory.getGlobal(); + Global global = new Global(); + global.init(factory); + mainEmbedded(factory, global, title); + } + + /** + * Entry point for embedded applications. This method attaches + * to the given {@link ContextFactory} with the given scope. No + * I/O redirection is performed as with {@link #main(String[])}. + */ + public static void mainEmbedded(ContextFactory factory, + Scriptable scope, + String title) { + mainEmbeddedImpl(factory, scope, title); + } + + /** + * Entry point for embedded applications. This method attaches + * to the given {@link ContextFactory} with the given scope. No + * I/O redirection is performed as with {@link #main(String[])}. + */ + public static void mainEmbedded(ContextFactory factory, + ScopeProvider scopeProvider, + String title) { + mainEmbeddedImpl(factory, scopeProvider, title); + } + + /** + * Helper method for {@link #mainEmbedded(String)}, etc. + */ + private static void mainEmbeddedImpl(ContextFactory factory, + Object scopeProvider, + String title) { + if (title == null) { + title = "Rhino JavaScript Debugger (embedded usage)"; + } + Main main = new Main(title); + main.doBreak(); + main.setExitAction(new IProxy(IProxy.EXIT_ACTION)); + + main.attachTo(factory); + if (scopeProvider instanceof ScopeProvider) { + main.setScopeProvider((ScopeProvider)scopeProvider); + } else { + Scriptable scope = (Scriptable)scopeProvider; + if (scope instanceof Global) { + Global global = (Global)scope; + global.setIn(main.getIn()); + global.setOut(main.getOut()); + global.setErr(main.getErr()); + } + main.setScope(scope); + } + + main.pack(); + main.setSize(600, 460); + main.setVisible(true); + } + + // Deprecated methods + + /** + * @deprecated Use {@link #setSize(int, int)} instead. + */ + public void setSize(java.awt.Dimension dimension) { + debugGui.setSize(dimension.width, dimension.height); + } + + /** + * @deprecated + * The method does nothing and is only present for compatibility. + */ + public void setOptimizationLevel(int level) { + } + + /** + * @deprecated + * The method is only present for compatibility and should not be called. + */ + public void contextEntered(Context cx) { + throw new IllegalStateException(); + } + + /** + * @deprecated + * The method is only present for compatibility and should not be called. + */ + public void contextExited(Context cx) { + throw new IllegalStateException(); + } + + /** + * @deprecated + * The method is only present for compatibility and should not be called. + */ + public void contextCreated(Context cx) { + throw new IllegalStateException(); + } + + /** + * @deprecated + * The method is only present for compatibility and should not be called. + */ + public void contextReleased(Context cx) + { + throw new IllegalStateException(); + } + + /** + * Class to consolidate all internal implementations of interfaces + * to avoid class generation bloat. + */ + private static class IProxy implements Runnable, ScopeProvider { + + // Constants for 'type'. + public static final int EXIT_ACTION = 1; + public static final int SCOPE_PROVIDER = 2; + + /** + * The type of interface. + */ + private final int type; + + /** + * The scope object to expose when {@link #type} = + * {@link #SCOPE_PROVIDER}. + */ + private Scriptable scope; + + /** + * Creates a new IProxy. + */ + public IProxy(int type) { + this.type = type; + } + + /** + * Creates a new IProxy that acts as a {@link ScopeProvider}. + */ + public static ScopeProvider newScopeProvider(Scriptable scope) { + IProxy scopeProvider = new IProxy(SCOPE_PROVIDER); + scopeProvider.scope = scope; + return scopeProvider; + } + + // ContextAction + + /** + * Exit action. + */ + public void run() { + if (type != EXIT_ACTION) Kit.codeBug(); + System.exit(0); + } + + // ScopeProvider + + /** + * Returns the scope for script evaluations. + */ + public Scriptable getScope() { + if (type != SCOPE_PROVIDER) Kit.codeBug(); + if (scope == null) Kit.codeBug(); + return scope; + } + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/ScopeProvider.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/ScopeProvider.java new file mode 100644 index 0000000..d8f65b9 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/ScopeProvider.java @@ -0,0 +1,52 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * See Beyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * Cameron McCormack + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.debugger; + +import org.mozilla.javascript.Scriptable; + +/** + * Interface to provide a scope object for script evaluation to the debugger. + */ +public interface ScopeProvider { + + /** + * Returns the scope object to be used for script evaluation. + */ + Scriptable getScope(); +} \ No newline at end of file diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java new file mode 100644 index 0000000..61dc065 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/SwingGui.java @@ -0,0 +1,3547 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * SeeBeyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Matt Gould + * Cameron McCormack + * Christopher Oliver + * Hannes Wallnoefer + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.debugger; + +import javax.swing.*; +import javax.swing.text.*; +import javax.swing.event.*; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.*; + +import java.util.*; +import java.io.*; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreePath; +import java.lang.reflect.Method; + +import org.mozilla.javascript.Kit; +import org.mozilla.javascript.SecurityUtilities; + +import org.mozilla.javascript.tools.shell.ConsoleTextArea; + +import org.mozilla.javascript.tools.debugger.downloaded.JTreeTable; +import org.mozilla.javascript.tools.debugger.downloaded.TreeTableModel; +import org.mozilla.javascript.tools.debugger.downloaded.TreeTableModelAdapter; + +/** + * GUI for the Rhino debugger. + */ +public class SwingGui extends JFrame implements GuiCallback { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -8217029773456711621L; + + /** + * The debugger. + */ + Dim dim; + + /** + * The action to run when the 'Exit' menu item is chosen or the + * frame is closed. + */ + private Runnable exitAction; + + /** + * The {@link JDesktopPane} that holds the script windows. + */ + private JDesktopPane desk; + + /** + * The {@link JPanel} that shows information about the context. + */ + private ContextWindow context; + + /** + * The menu bar. + */ + private Menubar menubar; + + /** + * The tool bar. + */ + private JToolBar toolBar; + + /** + * The console that displays I/O from the script. + */ + private JSInternalConsole console; + + /** + * The {@link JSplitPane} that separates {@link #desk} from + * {@link org.mozilla.javascript.Context}. + */ + private JSplitPane split1; + + /** + * The status bar. + */ + private JLabel statusBar; + + /** + * Hash table of internal frame names to the internal frames themselves. + */ + private Hashtable toplevels = new Hashtable(); + + /** + * Hash table of script URLs to their internal frames. + */ + private Hashtable fileWindows = new Hashtable(); + + /** + * The {@link FileWindow} that last had the focus. + */ + private FileWindow currentWindow; + + /** + * File choose dialog for loading a script. + */ + JFileChooser dlg; + + /** + * The AWT EventQueue. Used for manually pumping AWT events from + * {@link #dispatchNextGuiEvent()}. + */ + private EventQueue awtEventQueue; + + /** + * Creates a new SwingGui. + */ + public SwingGui(Dim dim, String title) { + super(title); + this.dim = dim; + init(); + dim.setGuiCallback(this); + } + + /** + * Returns the Menubar of this debugger frame. + */ + public Menubar getMenubar() { + return menubar; + } + + /** + * Sets the {@link Runnable} that will be run when the "Exit" menu + * item is chosen. + */ + public void setExitAction(Runnable r) { + exitAction = r; + } + + /** + * Returns the debugger console component. + */ + public JSInternalConsole getConsole() { + return console; + } + + /** + * Sets the visibility of the debugger GUI. + */ + public void setVisible(boolean b) { + super.setVisible(b); + if (b) { + // this needs to be done after the window is visible + console.consoleTextArea.requestFocus(); + context.split.setDividerLocation(0.5); + try { + console.setMaximum(true); + console.setSelected(true); + console.show(); + console.consoleTextArea.requestFocus(); + } catch (Exception exc) { + } + } + } + + /** + * Records a new internal frame. + */ + void addTopLevel(String key, JFrame frame) { + if (frame != this) { + toplevels.put(key, frame); + } + } + + /** + * Constructs the debugger GUI. + */ + private void init() { + menubar = new Menubar(this); + setJMenuBar(menubar); + toolBar = new JToolBar(); + JButton button; + JButton breakButton, goButton, stepIntoButton, + stepOverButton, stepOutButton; + String [] toolTips = {"Break (Pause)", + "Go (F5)", + "Step Into (F11)", + "Step Over (F7)", + "Step Out (F8)"}; + int count = 0; + button = breakButton = new JButton("Break"); + button.setToolTipText("Break"); + button.setActionCommand("Break"); + button.addActionListener(menubar); + button.setEnabled(true); + button.setToolTipText(toolTips[count++]); + + button = goButton = new JButton("Go"); + button.setToolTipText("Go"); + button.setActionCommand("Go"); + button.addActionListener(menubar); + button.setEnabled(false); + button.setToolTipText(toolTips[count++]); + + button = stepIntoButton = new JButton("Step Into"); + button.setToolTipText("Step Into"); + button.setActionCommand("Step Into"); + button.addActionListener(menubar); + button.setEnabled(false); + button.setToolTipText(toolTips[count++]); + + button = stepOverButton = new JButton("Step Over"); + button.setToolTipText("Step Over"); + button.setActionCommand("Step Over"); + button.setEnabled(false); + button.addActionListener(menubar); + button.setToolTipText(toolTips[count++]); + + button = stepOutButton = new JButton("Step Out"); + button.setToolTipText("Step Out"); + button.setActionCommand("Step Out"); + button.setEnabled(false); + button.addActionListener(menubar); + button.setToolTipText(toolTips[count++]); + + Dimension dim = stepOverButton.getPreferredSize(); + breakButton.setPreferredSize(dim); + breakButton.setMinimumSize(dim); + breakButton.setMaximumSize(dim); + breakButton.setSize(dim); + goButton.setPreferredSize(dim); + goButton.setMinimumSize(dim); + goButton.setMaximumSize(dim); + stepIntoButton.setPreferredSize(dim); + stepIntoButton.setMinimumSize(dim); + stepIntoButton.setMaximumSize(dim); + stepOverButton.setPreferredSize(dim); + stepOverButton.setMinimumSize(dim); + stepOverButton.setMaximumSize(dim); + stepOutButton.setPreferredSize(dim); + stepOutButton.setMinimumSize(dim); + stepOutButton.setMaximumSize(dim); + toolBar.add(breakButton); + toolBar.add(goButton); + toolBar.add(stepIntoButton); + toolBar.add(stepOverButton); + toolBar.add(stepOutButton); + + JPanel contentPane = new JPanel(); + contentPane.setLayout(new BorderLayout()); + getContentPane().add(toolBar, BorderLayout.NORTH); + getContentPane().add(contentPane, BorderLayout.CENTER); + desk = new JDesktopPane(); + desk.setPreferredSize(new Dimension(600, 300)); + desk.setMinimumSize(new Dimension(150, 50)); + desk.add(console = new JSInternalConsole("JavaScript Console")); + context = new ContextWindow(this); + context.setPreferredSize(new Dimension(600, 120)); + context.setMinimumSize(new Dimension(50, 50)); + + split1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, desk, + context); + split1.setOneTouchExpandable(true); + SwingGui.setResizeWeight(split1, 0.66); + contentPane.add(split1, BorderLayout.CENTER); + statusBar = new JLabel(); + statusBar.setText("Thread: "); + contentPane.add(statusBar, BorderLayout.SOUTH); + dlg = new JFileChooser(); + + javax.swing.filechooser.FileFilter filter = + new javax.swing.filechooser.FileFilter() { + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + String n = f.getName(); + int i = n.lastIndexOf('.'); + if (i > 0 && i < n.length() -1) { + String ext = n.substring(i + 1).toLowerCase(); + if (ext.equals("js")) { + return true; + } + } + return false; + } + + public String getDescription() { + return "JavaScript Files (*.js)"; + } + }; + dlg.addChoosableFileFilter(filter); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + exit(); + } + }); + } + + /** + * Runs the {@link #exitAction}. + */ + private void exit() { + if (exitAction != null) { + SwingUtilities.invokeLater(exitAction); + } + dim.setReturnValue(Dim.EXIT); + } + + /** + * Returns the {@link FileWindow} for the given URL. + */ + FileWindow getFileWindow(String url) { + if (url == null || url.equals("")) { + return null; + } + return (FileWindow)fileWindows.get(url); + } + + /** + * Returns a short version of the given URL. + */ + static String getShortName(String url) { + int lastSlash = url.lastIndexOf('/'); + if (lastSlash < 0) { + lastSlash = url.lastIndexOf('\\'); + } + String shortName = url; + if (lastSlash >= 0 && lastSlash + 1 < url.length()) { + shortName = url.substring(lastSlash + 1); + } + return shortName; + } + + /** + * Closes the given {@link FileWindow}. + */ + void removeWindow(FileWindow w) { + fileWindows.remove(w.getUrl()); + JMenu windowMenu = getWindowMenu(); + int count = windowMenu.getItemCount(); + JMenuItem lastItem = windowMenu.getItem(count -1); + String name = getShortName(w.getUrl()); + for (int i = 5; i < count; i++) { + JMenuItem item = windowMenu.getItem(i); + if (item == null) continue; // separator + String text = item.getText(); + //1 D:\foo.js + //2 D:\bar.js + int pos = text.indexOf(' '); + if (text.substring(pos + 1).equals(name)) { + windowMenu.remove(item); + // Cascade [0] + // Tile [1] + // ------- [2] + // Console [3] + // ------- [4] + if (count == 6) { + // remove the final separator + windowMenu.remove(4); + } else { + int j = i - 4; + for (;i < count -1; i++) { + JMenuItem thisItem = windowMenu.getItem(i); + if (thisItem != null) { + //1 D:\foo.js + //2 D:\bar.js + text = thisItem.getText(); + if (text.equals("More Windows...")) { + break; + } else { + pos = text.indexOf(' '); + thisItem.setText((char)('0' + j) + " " + + text.substring(pos + 1)); + thisItem.setMnemonic('0' + j); + j++; + } + } + } + if (count - 6 == 0 && lastItem != item) { + if (lastItem.getText().equals("More Windows...")) { + windowMenu.remove(lastItem); + } + } + } + break; + } + } + windowMenu.revalidate(); + } + + /** + * Shows the line at which execution in the given stack frame just stopped. + */ + void showStopLine(Dim.StackFrame frame) { + String sourceName = frame.getUrl(); + if (sourceName == null || sourceName.equals("")) { + if (console.isVisible()) { + console.show(); + } + } else { + showFileWindow(sourceName, -1); + int lineNumber = frame.getLineNumber(); + FileWindow w = getFileWindow(sourceName); + if (w != null) { + setFilePosition(w, lineNumber); + } + } + } + + /** + * Shows a {@link FileWindow} for the given source, creating it + * if it doesn't exist yet. if lineNumber is greater + * than -1, it indicates the line number to select and display. + * @param sourceUrl the source URL + * @param lineNumber the line number to select, or -1 + */ + protected void showFileWindow(String sourceUrl, int lineNumber) { + FileWindow w = getFileWindow(sourceUrl); + if (w == null) { + Dim.SourceInfo si = dim.sourceInfo(sourceUrl); + createFileWindow(si, -1); + w = getFileWindow(sourceUrl); + } + if (lineNumber > -1) { + int start = w.getPosition(lineNumber-1); + int end = w.getPosition(lineNumber)-1; + w.textArea.select(start); + w.textArea.setCaretPosition(start); + w.textArea.moveCaretPosition(end); + } + try { + if (w.isIcon()) { + w.setIcon(false); + } + w.setVisible(true); + w.moveToFront(); + w.setSelected(true); + requestFocus(); + w.requestFocus(); + w.textArea.requestFocus(); + } catch (Exception exc) { + } + } + + /** + * Creates and shows a new {@link FileWindow} for the given source. + */ + protected void createFileWindow(Dim.SourceInfo sourceInfo, int line) { + boolean activate = true; + + String url = sourceInfo.url(); + FileWindow w = new FileWindow(this, sourceInfo); + fileWindows.put(url, w); + if (line != -1) { + if (currentWindow != null) { + currentWindow.setPosition(-1); + } + try { + w.setPosition(w.textArea.getLineStartOffset(line-1)); + } catch (BadLocationException exc) { + try { + w.setPosition(w.textArea.getLineStartOffset(0)); + } catch (BadLocationException ee) { + w.setPosition(-1); + } + } + } + desk.add(w); + if (line != -1) { + currentWindow = w; + } + menubar.addFile(url); + w.setVisible(true); + + if (activate) { + try { + w.setMaximum(true); + w.setSelected(true); + w.moveToFront(); + } catch (Exception exc) { + } + } + } + + /** + * Update the source text for sourceInfo. This returns true + * if a {@link FileWindow} for the given source exists and could be updated. + * Otherwise, this does nothing and returns false. + * @param sourceInfo the source info + * @return true if a {@link FileWindow} for the given source exists + * and could be updated, false otherwise. + */ + protected boolean updateFileWindow(Dim.SourceInfo sourceInfo) { + String fileName = sourceInfo.url(); + FileWindow w = getFileWindow(fileName); + if (w != null) { + w.updateText(sourceInfo); + w.show(); + return true; + } + return false; + } + + /** + * Moves the current position in the given {@link FileWindow} to the + * given line. + */ + private void setFilePosition(FileWindow w, int line) { + boolean activate = true; + JTextArea ta = w.textArea; + try { + if (line == -1) { + w.setPosition(-1); + if (currentWindow == w) { + currentWindow = null; + } + } else { + int loc = ta.getLineStartOffset(line-1); + if (currentWindow != null && currentWindow != w) { + currentWindow.setPosition(-1); + } + w.setPosition(loc); + currentWindow = w; + } + } catch (BadLocationException exc) { + // fix me + } + if (activate) { + if (w.isIcon()) { + desk.getDesktopManager().deiconifyFrame(w); + } + desk.getDesktopManager().activateFrame(w); + try { + w.show(); + w.toFront(); // required for correct frame layering (JDK 1.4.1) + w.setSelected(true); + } catch (Exception exc) { + } + } + } + + /** + * Handles script interruption. + */ + void enterInterruptImpl(Dim.StackFrame lastFrame, + String threadTitle, String alertMessage) { + statusBar.setText("Thread: " + threadTitle); + + showStopLine(lastFrame); + + if (alertMessage != null) { + MessageDialogWrapper.showMessageDialog(this, + alertMessage, + "Exception in Script", + JOptionPane.ERROR_MESSAGE); + } + + updateEnabled(true); + + Dim.ContextData contextData = lastFrame.contextData(); + + JComboBox ctx = context.context; + Vector toolTips = context.toolTips; + context.disableUpdate(); + int frameCount = contextData.frameCount(); + ctx.removeAllItems(); + // workaround for JDK 1.4 bug that caches selected value even after + // removeAllItems() is called + ctx.setSelectedItem(null); + toolTips.removeAllElements(); + for (int i = 0; i < frameCount; i++) { + Dim.StackFrame frame = contextData.getFrame(i); + String url = frame.getUrl(); + int lineNumber = frame.getLineNumber(); + String shortName = url; + if (url.length() > 20) { + shortName = "..." + url.substring(url.length() - 17); + } + String location = "\"" + shortName + "\", line " + lineNumber; + ctx.insertItemAt(location, i); + location = "\"" + url + "\", line " + lineNumber; + toolTips.addElement(location); + } + context.enableUpdate(); + ctx.setSelectedIndex(0); + ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height)); + } + + /** + * Returns the 'Window' menu. + */ + private JMenu getWindowMenu() { + return menubar.getMenu(3); + } + + /** + * Displays a {@link JFileChooser} and returns the selected filename. + */ + private String chooseFile(String title) { + dlg.setDialogTitle(title); + File CWD = null; + String dir = SecurityUtilities.getSystemProperty("user.dir"); + if (dir != null) { + CWD = new File(dir); + } + if (CWD != null) { + dlg.setCurrentDirectory(CWD); + } + int returnVal = dlg.showOpenDialog(this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + try { + String result = dlg.getSelectedFile().getCanonicalPath(); + CWD = dlg.getSelectedFile().getParentFile(); + Properties props = System.getProperties(); + props.put("user.dir", CWD.getPath()); + System.setProperties(props); + return result; + } catch (IOException ignored) { + } catch (SecurityException ignored) { + } + } + return null; + } + + /** + * Returns the current selected internal frame. + */ + private JInternalFrame getSelectedFrame() { + JInternalFrame[] frames = desk.getAllFrames(); + for (int i = 0; i < frames.length; i++) { + if (frames[i].isShowing()) { + return frames[i]; + } + } + return frames[frames.length - 1]; + } + + /** + * Enables or disables the menu and tool bars with respect to the + * state of script execution. + */ + private void updateEnabled(boolean interrupted) { + ((Menubar)getJMenuBar()).updateEnabled(interrupted); + for (int ci = 0, cc = toolBar.getComponentCount(); ci < cc; ci++) { + boolean enableButton; + if (ci == 0) { + // Break + enableButton = !interrupted; + } else { + enableButton = interrupted; + } + toolBar.getComponent(ci).setEnabled(enableButton); + } + if (interrupted) { + toolBar.setEnabled(true); + // raise the debugger window + int state = getExtendedState(); + if (state == Frame.ICONIFIED) { + setExtendedState(Frame.NORMAL); + } + toFront(); + context.enable(); + } else { + if (currentWindow != null) currentWindow.setPosition(-1); + context.disable(); + } + } + + /** + * Calls {@link JSplitPane#setResizeWeight} via reflection. + * For compatibility, since JDK < 1.3 does not have this method. + */ + static void setResizeWeight(JSplitPane pane, double weight) { + try { + Method m = JSplitPane.class.getMethod("setResizeWeight", + new Class[]{double.class}); + m.invoke(pane, new Object[]{new Double(weight)}); + } catch (NoSuchMethodException exc) { + } catch (IllegalAccessException exc) { + } catch (java.lang.reflect.InvocationTargetException exc) { + } + } + + /** + * Reads the file with the given name and returns its contents as a String. + */ + private String readFile(String fileName) { + String text; + try { + Reader r = new FileReader(fileName); + try { + text = Kit.readReader(r); + } finally { + r.close(); + } + } catch (IOException ex) { + MessageDialogWrapper.showMessageDialog(this, + ex.getMessage(), + "Error reading "+fileName, + JOptionPane.ERROR_MESSAGE); + text = null; + } + return text; + } + + // GuiCallback + + /** + * Called when the source text for a script has been updated. + */ + public void updateSourceText(Dim.SourceInfo sourceInfo) { + RunProxy proxy = new RunProxy(this, RunProxy.UPDATE_SOURCE_TEXT); + proxy.sourceInfo = sourceInfo; + SwingUtilities.invokeLater(proxy); + } + + /** + * Called when the interrupt loop has been entered. + */ + public void enterInterrupt(Dim.StackFrame lastFrame, + String threadTitle, + String alertMessage) { + if (SwingUtilities.isEventDispatchThread()) { + enterInterruptImpl(lastFrame, threadTitle, alertMessage); + } else { + RunProxy proxy = new RunProxy(this, RunProxy.ENTER_INTERRUPT); + proxy.lastFrame = lastFrame; + proxy.threadTitle = threadTitle; + proxy.alertMessage = alertMessage; + SwingUtilities.invokeLater(proxy); + } + } + + /** + * Returns whether the current thread is the GUI event thread. + */ + public boolean isGuiEventThread() { + return SwingUtilities.isEventDispatchThread(); + } + + /** + * Processes the next GUI event. + */ + public void dispatchNextGuiEvent() throws InterruptedException { + EventQueue queue = awtEventQueue; + if (queue == null) { + queue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + awtEventQueue = queue; + } + AWTEvent event = queue.getNextEvent(); + if (event instanceof ActiveEvent) { + ((ActiveEvent)event).dispatch(); + } else { + Object source = event.getSource(); + if (source instanceof Component) { + Component comp = (Component)source; + comp.dispatchEvent(event); + } else if (source instanceof MenuComponent) { + ((MenuComponent)source).dispatchEvent(event); + } + } + } + + // ActionListener + + /** + * Performs an action from the menu or toolbar. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + int returnValue = -1; + if (cmd.equals("Cut") || cmd.equals("Copy") || cmd.equals("Paste")) { + JInternalFrame f = getSelectedFrame(); + if (f != null && f instanceof ActionListener) { + ((ActionListener)f).actionPerformed(e); + } + } else if (cmd.equals("Step Over")) { + returnValue = Dim.STEP_OVER; + } else if (cmd.equals("Step Into")) { + returnValue = Dim.STEP_INTO; + } else if (cmd.equals("Step Out")) { + returnValue = Dim.STEP_OUT; + } else if (cmd.equals("Go")) { + returnValue = Dim.GO; + } else if (cmd.equals("Break")) { + dim.setBreak(); + } else if (cmd.equals("Exit")) { + exit(); + } else if (cmd.equals("Open")) { + String fileName = chooseFile("Select a file to compile"); + if (fileName != null) { + String text = readFile(fileName); + if (text != null) { + RunProxy proxy = new RunProxy(this, RunProxy.OPEN_FILE); + proxy.fileName = fileName; + proxy.text = text; + new Thread(proxy).start(); + } + } + } else if (cmd.equals("Load")) { + String fileName = chooseFile("Select a file to execute"); + if (fileName != null) { + String text = readFile(fileName); + if (text != null) { + RunProxy proxy = new RunProxy(this, RunProxy.LOAD_FILE); + proxy.fileName = fileName; + proxy.text = text; + new Thread(proxy).start(); + } + } + } else if (cmd.equals("More Windows...")) { + MoreWindows dlg = new MoreWindows(this, fileWindows, + "Window", "Files"); + dlg.showDialog(this); + } else if (cmd.equals("Console")) { + if (console.isIcon()) { + desk.getDesktopManager().deiconifyFrame(console); + } + console.show(); + desk.getDesktopManager().activateFrame(console); + console.consoleTextArea.requestFocus(); + } else if (cmd.equals("Cut")) { + } else if (cmd.equals("Copy")) { + } else if (cmd.equals("Paste")) { + } else if (cmd.equals("Go to function...")) { + FindFunction dlg = new FindFunction(this, "Go to function", + "Function"); + dlg.showDialog(this); + } else if (cmd.equals("Tile")) { + JInternalFrame[] frames = desk.getAllFrames(); + int count = frames.length; + int rows, cols; + rows = cols = (int)Math.sqrt(count); + if (rows*cols < count) { + cols++; + if (rows * cols < count) { + rows++; + } + } + Dimension size = desk.getSize(); + int w = size.width/cols; + int h = size.height/rows; + int x = 0; + int y = 0; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + int index = (i*cols) + j; + if (index >= frames.length) { + break; + } + JInternalFrame f = frames[index]; + try { + f.setIcon(false); + f.setMaximum(false); + } catch (Exception exc) { + } + desk.getDesktopManager().setBoundsForFrame(f, x, y, + w, h); + x += w; + } + y += h; + x = 0; + } + } else if (cmd.equals("Cascade")) { + JInternalFrame[] frames = desk.getAllFrames(); + int count = frames.length; + int x, y, w, h; + x = y = 0; + h = desk.getHeight(); + int d = h / count; + if (d > 30) d = 30; + for (int i = count -1; i >= 0; i--, x += d, y += d) { + JInternalFrame f = frames[i]; + try { + f.setIcon(false); + f.setMaximum(false); + } catch (Exception exc) { + } + Dimension dimen = f.getPreferredSize(); + w = dimen.width; + h = dimen.height; + desk.getDesktopManager().setBoundsForFrame(f, x, y, w, h); + } + } else { + Object obj = getFileWindow(cmd); + if (obj != null) { + FileWindow w = (FileWindow)obj; + try { + if (w.isIcon()) { + w.setIcon(false); + } + w.setVisible(true); + w.moveToFront(); + w.setSelected(true); + } catch (Exception exc) { + } + } + } + if (returnValue != -1) { + updateEnabled(false); + dim.setReturnValue(returnValue); + } + } +} + +/** + * Helper class for showing a message dialog. + */ +class MessageDialogWrapper { + + /** + * Shows a message dialog, wrapping the msg at 60 + * columns. + */ + public static void showMessageDialog(Component parent, String msg, + String title, int flags) { + if (msg.length() > 60) { + StringBuffer buf = new StringBuffer(); + int len = msg.length(); + int j = 0; + int i; + for (i = 0; i < len; i++, j++) { + char c = msg.charAt(i); + buf.append(c); + if (Character.isWhitespace(c)) { + int k; + for (k = i + 1; k < len; k++) { + if (Character.isWhitespace(msg.charAt(k))) { + break; + } + } + if (k < len) { + int nextWordLen = k - i; + if (j + nextWordLen > 60) { + buf.append('\n'); + j = 0; + } + } + } + } + msg = buf.toString(); + } + JOptionPane.showMessageDialog(parent, msg, title, flags); + } +} + +/** + * Extension of JTextArea for script evaluation input. + */ +class EvalTextArea + extends JTextArea + implements KeyListener, DocumentListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -3918033649601064194L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * History of expressions that have been evaluated + */ + private Vector history; + + /** + * Index of the selected history item. + */ + private int historyIndex = -1; + + /** + * Position in the display where output should go. + */ + private int outputMark; + + /** + * Creates a new EvalTextArea. + */ + public EvalTextArea(SwingGui debugGui) { + this.debugGui = debugGui; + history = new java.util.Vector(); + Document doc = getDocument(); + doc.addDocumentListener(this); + addKeyListener(this); + setLineWrap(true); + setFont(new Font("Monospaced", 0, 12)); + append("% "); + outputMark = doc.getLength(); + } + + /** + * Selects a subrange of the text. + */ + public void select(int start, int end) { + //requestFocus(); + super.select(start, end); + } + + /** + * Called when Enter is pressed. + */ + private synchronized void returnPressed() { + Document doc = getDocument(); + int len = doc.getLength(); + Segment segment = new Segment(); + try { + doc.getText(outputMark, len - outputMark, segment); + } catch (javax.swing.text.BadLocationException ignored) { + ignored.printStackTrace(); + } + String text = segment.toString(); + if (debugGui.dim.stringIsCompilableUnit(text)) { + if (text.trim().length() > 0) { + history.addElement(text); + historyIndex = history.size(); + } + append("\n"); + String result = debugGui.dim.eval(text); + if (result.length() > 0) { + append(result); + append("\n"); + } + append("% "); + outputMark = doc.getLength(); + } else { + append("\n"); + } + } + + /** + * Writes output into the text area. + */ + public synchronized void write(String str) { + insert(str, outputMark); + int len = str.length(); + outputMark += len; + select(outputMark, outputMark); + } + + // KeyListener + + /** + * Called when a key is pressed. + */ + public void keyPressed(KeyEvent e) { + int code = e.getKeyCode(); + if (code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_LEFT) { + if (outputMark == getCaretPosition()) { + e.consume(); + } + } else if (code == KeyEvent.VK_HOME) { + int caretPos = getCaretPosition(); + if (caretPos == outputMark) { + e.consume(); + } else if (caretPos > outputMark) { + if (!e.isControlDown()) { + if (e.isShiftDown()) { + moveCaretPosition(outputMark); + } else { + setCaretPosition(outputMark); + } + e.consume(); + } + } + } else if (code == KeyEvent.VK_ENTER) { + returnPressed(); + e.consume(); + } else if (code == KeyEvent.VK_UP) { + historyIndex--; + if (historyIndex >= 0) { + if (historyIndex >= history.size()) { + historyIndex = history.size() -1; + } + if (historyIndex >= 0) { + String str = (String)history.elementAt(historyIndex); + int len = getDocument().getLength(); + replaceRange(str, outputMark, len); + int caretPos = outputMark + str.length(); + select(caretPos, caretPos); + } else { + historyIndex++; + } + } else { + historyIndex++; + } + e.consume(); + } else if (code == KeyEvent.VK_DOWN) { + int caretPos = outputMark; + if (history.size() > 0) { + historyIndex++; + if (historyIndex < 0) {historyIndex = 0;} + int len = getDocument().getLength(); + if (historyIndex < history.size()) { + String str = (String)history.elementAt(historyIndex); + replaceRange(str, outputMark, len); + caretPos = outputMark + str.length(); + } else { + historyIndex = history.size(); + replaceRange("", outputMark, len); + } + } + select(caretPos, caretPos); + e.consume(); + } + } + + /** + * Called when a key is typed. + */ + public void keyTyped(KeyEvent e) { + int keyChar = e.getKeyChar(); + if (keyChar == 0x8 /* KeyEvent.VK_BACK_SPACE */) { + if (outputMark == getCaretPosition()) { + e.consume(); + } + } else if (getCaretPosition() < outputMark) { + setCaretPosition(outputMark); + } + } + + /** + * Called when a key is released. + */ + public synchronized void keyReleased(KeyEvent e) { + } + + // DocumentListener + + /** + * Called when text was inserted into the text area. + */ + public synchronized void insertUpdate(DocumentEvent e) { + int len = e.getLength(); + int off = e.getOffset(); + if (outputMark > off) { + outputMark += len; + } + } + + /** + * Called when text was removed from the text area. + */ + public synchronized void removeUpdate(DocumentEvent e) { + int len = e.getLength(); + int off = e.getOffset(); + if (outputMark > off) { + if (outputMark >= off + len) { + outputMark -= len; + } else { + outputMark = off; + } + } + } + + /** + * Attempts to clean up the damage done by {@link #updateUI()}. + */ + public synchronized void postUpdateUI() { + //requestFocus(); + setCaret(getCaret()); + select(outputMark, outputMark); + } + + /** + * Called when text has changed in the text area. + */ + public synchronized void changedUpdate(DocumentEvent e) { + } +} + +/** + * An internal frame for evaluating script. + */ +class EvalWindow extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -2860585845212160176L; + + /** + * The text area into which expressions can be typed. + */ + private EvalTextArea evalTextArea; + + /** + * Creates a new EvalWindow. + */ + public EvalWindow(String name, SwingGui debugGui) { + super(name, true, false, true, true); + evalTextArea = new EvalTextArea(debugGui); + evalTextArea.setRows(24); + evalTextArea.setColumns(80); + JScrollPane scroller = new JScrollPane(evalTextArea); + setContentPane(scroller); + //scroller.setPreferredSize(new Dimension(600, 400)); + pack(); + setVisible(true); + } + + /** + * Sets whether the text area is enabled. + */ + public void setEnabled(boolean b) { + super.setEnabled(b); + evalTextArea.setEnabled(b); + } + + // ActionListener + + /** + * Performs an action on the text area. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Cut")) { + evalTextArea.cut(); + } else if (cmd.equals("Copy")) { + evalTextArea.copy(); + } else if (cmd.equals("Paste")) { + evalTextArea.paste(); + } + } +} + +/** + * Internal frame for the console. + */ +class JSInternalConsole extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -5523468828771087292L; + + /** + * Creates a new JSInternalConsole. + */ + public JSInternalConsole(String name) { + super(name, true, false, true, true); + consoleTextArea = new ConsoleTextArea(null); + consoleTextArea.setRows(24); + consoleTextArea.setColumns(80); + JScrollPane scroller = new JScrollPane(consoleTextArea); + setContentPane(scroller); + pack(); + addInternalFrameListener(new InternalFrameAdapter() { + public void internalFrameActivated(InternalFrameEvent e) { + // hack + if (consoleTextArea.hasFocus()) { + consoleTextArea.getCaret().setVisible(false); + consoleTextArea.getCaret().setVisible(true); + } + } + }); + } + + /** + * The console text area. + */ + ConsoleTextArea consoleTextArea; + + /** + * Returns the input stream of the console text area. + */ + public InputStream getIn() { + return consoleTextArea.getIn(); + } + + /** + * Returns the output stream of the console text area. + */ + public PrintStream getOut() { + return consoleTextArea.getOut(); + } + + /** + * Returns the error stream of the console text area. + */ + public PrintStream getErr() { + return consoleTextArea.getErr(); + } + + // ActionListener + + /** + * Performs an action on the text area. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Cut")) { + consoleTextArea.cut(); + } else if (cmd.equals("Copy")) { + consoleTextArea.copy(); + } else if (cmd.equals("Paste")) { + consoleTextArea.paste(); + } + } +} + +/** + * Popup menu class for right-clicking on {@link FileTextArea}s. + */ +class FilePopupMenu extends JPopupMenu { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3589525009546013565L; + + /** + * The popup x position. + */ + int x; + + /** + * The popup y position. + */ + int y; + + /** + * Creates a new FilePopupMenu. + */ + public FilePopupMenu(FileTextArea w) { + JMenuItem item; + add(item = new JMenuItem("Set Breakpoint")); + item.addActionListener(w); + add(item = new JMenuItem("Clear Breakpoint")); + item.addActionListener(w); + add(item = new JMenuItem("Run")); + item.addActionListener(w); + } + + /** + * Displays the menu at the given coordinates. + */ + public void show(JComponent comp, int x, int y) { + this.x = x; + this.y = y; + super.show(comp, x, y); + } +} + +/** + * Text area to display script source. + */ +class FileTextArea + extends JTextArea + implements ActionListener, PopupMenuListener, KeyListener, MouseListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -25032065448563720L; + + /** + * The owning {@link FileWindow}. + */ + private FileWindow w; + + /** + * The popup menu. + */ + private FilePopupMenu popup; + + /** + * Creates a new FileTextArea. + */ + public FileTextArea(FileWindow w) { + this.w = w; + popup = new FilePopupMenu(this); + popup.addPopupMenuListener(this); + addMouseListener(this); + addKeyListener(this); + setFont(new Font("Monospaced", 0, 12)); + } + + /** + * Moves the selection to the given offset. + */ + public void select(int pos) { + if (pos >= 0) { + try { + int line = getLineOfOffset(pos); + Rectangle rect = modelToView(pos); + if (rect == null) { + select(pos, pos); + } else { + try { + Rectangle nrect = + modelToView(getLineStartOffset(line + 1)); + if (nrect != null) { + rect = nrect; + } + } catch (Exception exc) { + } + JViewport vp = (JViewport)getParent(); + Rectangle viewRect = vp.getViewRect(); + if (viewRect.y + viewRect.height > rect.y) { + // need to scroll up + select(pos, pos); + } else { + // need to scroll down + rect.y += (viewRect.height - rect.height)/2; + scrollRectToVisible(rect); + select(pos, pos); + } + } + } catch (BadLocationException exc) { + select(pos, pos); + //exc.printStackTrace(); + } + } + } + + /** + * Checks if the popup menu should be shown. + */ + private void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popup.show(this, e.getX(), e.getY()); + } + } + + // MouseListener + + /** + * Called when a mouse button is pressed. + */ + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + + /** + * Called when the mouse is clicked. + */ + public void mouseClicked(MouseEvent e) { + checkPopup(e); + requestFocus(); + getCaret().setVisible(true); + } + + /** + * Called when the mouse enters the component. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Called when the mouse exits the component. + */ + public void mouseExited(MouseEvent e) { + } + + /** + * Called when a mouse button is released. + */ + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + // PopupMenuListener + + /** + * Called before the popup menu will become visible. + */ + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + + /** + * Called before the popup menu will become invisible. + */ + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + /** + * Called when the popup menu is cancelled. + */ + public void popupMenuCanceled(PopupMenuEvent e) { + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + int pos = viewToModel(new Point(popup.x, popup.y)); + popup.setVisible(false); + String cmd = e.getActionCommand(); + int line = -1; + try { + line = getLineOfOffset(pos); + } catch (Exception exc) { + } + if (cmd.equals("Set Breakpoint")) { + w.setBreakPoint(line + 1); + } else if (cmd.equals("Clear Breakpoint")) { + w.clearBreakPoint(line + 1); + } else if (cmd.equals("Run")) { + w.load(); + } + } + + // KeyListener + + /** + * Called when a key is pressed. + */ + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_BACK_SPACE: + case KeyEvent.VK_ENTER: + case KeyEvent.VK_DELETE: + case KeyEvent.VK_TAB: + e.consume(); + break; + } + } + + /** + * Called when a key is typed. + */ + public void keyTyped(KeyEvent e) { + e.consume(); + } + + /** + * Called when a key is released. + */ + public void keyReleased(KeyEvent e) { + e.consume(); + } +} + +/** + * Dialog to list the available windows. + */ +class MoreWindows extends JDialog implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 5177066296457377546L; + + /** + * Last selected value. + */ + private String value; + + /** + * The list component. + */ + private JList list; + + /** + * Our parent frame. + */ + private SwingGui swingGui; + + /** + * The "Select" button. + */ + private JButton setButton; + + /** + * The "Cancel" button. + */ + private JButton cancelButton; + + /** + * Creates a new MoreWindows. + */ + MoreWindows(SwingGui frame, Hashtable fileWindows, String title, + String labelText) { + super(frame, title, true); + this.swingGui = frame; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //dim part of the dialog + list = new JList(new DefaultListModel()); + DefaultListModel model = (DefaultListModel)list.getModel(); + model.clear(); + //model.fireIntervalRemoved(model, 0, size); + Enumeration e = fileWindows.keys(); + while (e.hasMoreElements()) { + String data = e.nextElement().toString(); + model.addElement(data); + } + list.setSelectedIndex(0); + //model.fireIntervalAdded(model, 0, data.length); + setButton.setEnabled(true); + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + list.addMouseListener(new MouseHandler()); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(320, 240)); + //XXX: Must do the following, too, or else the scroller thinks + //XXX: it's taller than it is: + listScroller.setMinimumSize(new Dimension(250, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to button. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS)); + JLabel label = new JLabel(labelText); + label.setLabelFor (list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + //Lay out the buttons from left to right. + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(cancelButton); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(setButton); + + //Put everything together, using the content pane's BorderLayout. + Container contentPane = getContentPane(); + contentPane.add(listPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.SOUTH); + pack(); + addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent ke) { + int code = ke.getKeyCode(); + if (code == KeyEvent.VK_ESCAPE) { + ke.consume(); + value = null; + setVisible(false); + } + } + }); + } + + /** + * Shows the dialog. + */ + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Cancel")) { + setVisible(false); + value = null; + } else if (cmd.equals("Select")) { + value = (String)list.getSelectedValue(); + setVisible(false); + swingGui.showFileWindow(value, -1); + } + } + + /** + * MouseListener implementation for {@link #list}. + */ + private class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + } +} + +/** + * Find function dialog. + */ +class FindFunction extends JDialog implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 559491015232880916L; + + /** + * Last selected function. + */ + private String value; + + /** + * List of functions. + */ + private JList list; + + /** + * The debug GUI frame. + */ + private SwingGui debugGui; + + /** + * The "Select" button. + */ + private JButton setButton; + + /** + * The "Cancel" button. + */ + private JButton cancelButton; + + /** + * Creates a new FindFunction. + */ + public FindFunction(SwingGui debugGui, String title, String labelText) { + super(debugGui, title, true); + this.debugGui = debugGui; + + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + list = new JList(new DefaultListModel()); + DefaultListModel model = (DefaultListModel)list.getModel(); + model.clear(); + + String[] a = debugGui.dim.functionNames(); + java.util.Arrays.sort(a); + for (int i = 0; i < a.length; i++) { + model.addElement(a[i]); + } + list.setSelectedIndex(0); + + setButton.setEnabled(a.length > 0); + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + list.addMouseListener(new MouseHandler()); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(320, 240)); + listScroller.setMinimumSize(new Dimension(250, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to button. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS)); + JLabel label = new JLabel(labelText); + label.setLabelFor (list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + //Lay out the buttons from left to right. + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(cancelButton); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(setButton); + + //Put everything together, using the content pane's BorderLayout. + Container contentPane = getContentPane(); + contentPane.add(listPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.SOUTH); + pack(); + addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent ke) { + int code = ke.getKeyCode(); + if (code == KeyEvent.VK_ESCAPE) { + ke.consume(); + value = null; + setVisible(false); + } + } + }); + } + + /** + * Shows the dialog. + */ + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Cancel")) { + setVisible(false); + value = null; + } else if (cmd.equals("Select")) { + if (list.getSelectedIndex() < 0) { + return; + } + try { + value = (String)list.getSelectedValue(); + } catch (ArrayIndexOutOfBoundsException exc) { + return; + } + setVisible(false); + Dim.FunctionSource item = debugGui.dim.functionSourceByName(value); + if (item != null) { + Dim.SourceInfo si = item.sourceInfo(); + String url = si.url(); + int lineNumber = item.firstLine(); + debugGui.showFileWindow(url, lineNumber); + } + } + } + + /** + * MouseListener implementation for {@link #list}. + */ + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + } +} + +/** + * Gutter for FileWindows. + */ +class FileHeader extends JPanel implements MouseListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -2858905404778259127L; + + /** + * The line that the mouse was pressed on. + */ + private int pressLine = -1; + + /** + * The owning FileWindow. + */ + private FileWindow fileWindow; + + /** + * Creates a new FileHeader. + */ + public FileHeader(FileWindow fileWindow) { + this.fileWindow = fileWindow; + addMouseListener(this); + update(); + } + + /** + * Updates the gutter. + */ + public void update() { + FileTextArea textArea = fileWindow.textArea; + Font font = textArea.getFont(); + setFont(font); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + int lineCount = textArea.getLineCount() + 1; + String dummy = Integer.toString(lineCount); + if (dummy.length() < 2) { + dummy = "99"; + } + Dimension d = new Dimension(); + d.width = metrics.stringWidth(dummy) + 16; + d.height = lineCount * h + 100; + setPreferredSize(d); + setSize(d); + } + + /** + * Paints the component. + */ + public void paint(Graphics g) { + super.paint(g); + FileTextArea textArea = fileWindow.textArea; + Font font = textArea.getFont(); + g.setFont(font); + FontMetrics metrics = getFontMetrics(font); + Rectangle clip = g.getClipBounds(); + g.setColor(getBackground()); + g.fillRect(clip.x, clip.y, clip.width, clip.height); + int ascent = metrics.getMaxAscent(); + int h = metrics.getHeight(); + int lineCount = textArea.getLineCount() + 1; + String dummy = Integer.toString(lineCount); + if (dummy.length() < 2) { + dummy = "99"; + } + int startLine = clip.y / h; + int endLine = (clip.y + clip.height) / h + 1; + int width = getWidth(); + if (endLine > lineCount) endLine = lineCount; + for (int i = startLine; i < endLine; i++) { + String text; + int pos = -2; + try { + pos = textArea.getLineStartOffset(i); + } catch (BadLocationException ignored) { + } + boolean isBreakPoint = fileWindow.isBreakPoint(i + 1); + text = Integer.toString(i + 1) + " "; + int y = i * h; + g.setColor(Color.blue); + g.drawString(text, 0, y + ascent); + int x = width - ascent; + if (isBreakPoint) { + g.setColor(new Color(0x80, 0x00, 0x00)); + int dy = y + ascent - 9; + g.fillOval(x, dy, 9, 9); + g.drawOval(x, dy, 8, 8); + g.drawOval(x, dy, 9, 9); + } + if (pos == fileWindow.currentPos) { + Polygon arrow = new Polygon(); + int dx = x; + y += ascent - 10; + int dy = y; + arrow.addPoint(dx, dy + 3); + arrow.addPoint(dx + 5, dy + 3); + for (x = dx + 5; x <= dx + 10; x++, y++) { + arrow.addPoint(x, y); + } + for (x = dx + 9; x >= dx + 5; x--, y++) { + arrow.addPoint(x, y); + } + arrow.addPoint(dx + 5, dy + 7); + arrow.addPoint(dx, dy + 7); + g.setColor(Color.yellow); + g.fillPolygon(arrow); + g.setColor(Color.black); + g.drawPolygon(arrow); + } + } + } + + // MouseListener + + /** + * Called when the mouse enters the component. + */ + public void mouseEntered(MouseEvent e) { + } + + /** + * Called when a mouse button is pressed. + */ + public void mousePressed(MouseEvent e) { + Font font = fileWindow.textArea.getFont(); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + pressLine = e.getY() / h; + } + + /** + * Called when the mouse is clicked. + */ + public void mouseClicked(MouseEvent e) { + } + + /** + * Called when the mouse exits the component. + */ + public void mouseExited(MouseEvent e) { + } + + /** + * Called when a mouse button is released. + */ + public void mouseReleased(MouseEvent e) { + if (e.getComponent() == this + && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { + int y = e.getY(); + Font font = fileWindow.textArea.getFont(); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + int line = y/h; + if (line == pressLine) { + fileWindow.toggleBreakPoint(line + 1); + } else { + pressLine = -1; + } + } + } +} + +/** + * An internal frame for script files. + */ +class FileWindow extends JInternalFrame implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = -6212382604952082370L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The SourceInfo object that describes the file. + */ + private Dim.SourceInfo sourceInfo; + + /** + * The FileTextArea that displays the file. + */ + FileTextArea textArea; + + /** + * The FileHeader that is the gutter for {@link #textArea}. + */ + private FileHeader fileHeader; + + /** + * Scroll pane for containing {@link #textArea}. + */ + private JScrollPane p; + + /** + * The current offset position. + */ + int currentPos; + + /** + * Loads the file. + */ + void load() { + String url = getUrl(); + if (url != null) { + RunProxy proxy = new RunProxy(debugGui, RunProxy.LOAD_FILE); + proxy.fileName = url; + proxy.text = sourceInfo.source(); + new Thread(proxy).start(); + } + } + + /** + * Returns the offset position for the given line. + */ + public int getPosition(int line) { + int result = -1; + try { + result = textArea.getLineStartOffset(line); + } catch (javax.swing.text.BadLocationException exc) { + } + return result; + } + + /** + * Returns whether the given line has a breakpoint. + */ + public boolean isBreakPoint(int line) { + return sourceInfo.breakableLine(line) && sourceInfo.breakpoint(line); + } + + /** + * Toggles the breakpoint on the given line. + */ + public void toggleBreakPoint(int line) { + if (!isBreakPoint(line)) { + setBreakPoint(line); + } else { + clearBreakPoint(line); + } + } + + /** + * Sets a breakpoint on the given line. + */ + public void setBreakPoint(int line) { + if (sourceInfo.breakableLine(line)) { + boolean changed = sourceInfo.breakpoint(line, true); + if (changed) { + fileHeader.repaint(); + } + } + } + + /** + * Clears a breakpoint from the given line. + */ + public void clearBreakPoint(int line) { + if (sourceInfo.breakableLine(line)) { + boolean changed = sourceInfo.breakpoint(line, false); + if (changed) { + fileHeader.repaint(); + } + } + } + + /** + * Creates a new FileWindow. + */ + public FileWindow(SwingGui debugGui, Dim.SourceInfo sourceInfo) { + super(SwingGui.getShortName(sourceInfo.url()), + true, true, true, true); + this.debugGui = debugGui; + this.sourceInfo = sourceInfo; + updateToolTip(); + currentPos = -1; + textArea = new FileTextArea(this); + textArea.setRows(24); + textArea.setColumns(80); + p = new JScrollPane(); + fileHeader = new FileHeader(this); + p.setViewportView(textArea); + p.setRowHeaderView(fileHeader); + setContentPane(p); + pack(); + updateText(sourceInfo); + textArea.select(0); + } + + /** + * Updates the tool tip contents. + */ + private void updateToolTip() { + // Try to set tool tip on frame. On Mac OS X 10.5, + // the number of components is different, so try to be safe. + int n = getComponentCount() - 1; + if (n > 1) { + n = 1; + } else if (n < 0) { + return; + } + Component c = getComponent(n); + // this will work at least for Metal L&F + if (c != null && c instanceof JComponent) { + ((JComponent)c).setToolTipText(getUrl()); + } + } + + /** + * Returns the URL of the source. + */ + public String getUrl() { + return sourceInfo.url(); + } + + /** + * Called when the text of the script has changed. + */ + public void updateText(Dim.SourceInfo sourceInfo) { + this.sourceInfo = sourceInfo; + String newText = sourceInfo.source(); + if (!textArea.getText().equals(newText)) { + textArea.setText(newText); + int pos = 0; + if (currentPos != -1) { + pos = currentPos; + } + textArea.select(pos); + } + fileHeader.update(); + fileHeader.repaint(); + } + + /** + * Sets the cursor position. + */ + public void setPosition(int pos) { + textArea.select(pos); + currentPos = pos; + fileHeader.repaint(); + } + + /** + * Selects a range of characters. + */ + public void select(int start, int end) { + int docEnd = textArea.getDocument().getLength(); + textArea.select(docEnd, docEnd); + textArea.select(start, end); + } + + /** + * Disposes this FileWindow. + */ + public void dispose() { + debugGui.removeWindow(this); + super.dispose(); + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Cut")) { + // textArea.cut(); + } else if (cmd.equals("Copy")) { + textArea.copy(); + } else if (cmd.equals("Paste")) { + // textArea.paste(); + } + } +} + +/** + * Table model class for watched expressions. + */ +class MyTableModel extends AbstractTableModel { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 2971618907207577000L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * Vector of watched expressions. + */ + private Vector expressions; + + /** + * Vector of values from evaluated from {@link #expressions}. + */ + private Vector values; + + /** + * Creates a new MyTableModel. + */ + public MyTableModel(SwingGui debugGui) { + this.debugGui = debugGui; + expressions = new Vector(); + values = new Vector(); + expressions.addElement(""); + values.addElement(""); + } + + /** + * Returns the number of columns in the table (2). + */ + public int getColumnCount() { + return 2; + } + + /** + * Returns the number of rows in the table. + */ + public int getRowCount() { + return expressions.size(); + } + + /** + * Returns the name of the given column. + */ + public String getColumnName(int column) { + switch (column) { + case 0: + return "Expression"; + case 1: + return "Value"; + } + return null; + } + + /** + * Returns whether the given cell is editable. + */ + public boolean isCellEditable(int row, int column) { + return true; + } + + /** + * Returns the value in the given cell. + */ + public Object getValueAt(int row, int column) { + switch (column) { + case 0: + return expressions.elementAt(row); + case 1: + return values.elementAt(row); + } + return ""; + } + + /** + * Sets the value in the given cell. + */ + public void setValueAt(Object value, int row, int column) { + switch (column) { + case 0: + String expr = value.toString(); + expressions.setElementAt(expr, row); + String result = ""; + if (expr.length() > 0) { + result = debugGui.dim.eval(expr); + if (result == null) result = ""; + } + values.setElementAt(result, row); + updateModel(); + if (row + 1 == expressions.size()) { + expressions.addElement(""); + values.addElement(""); + fireTableRowsInserted(row + 1, row + 1); + } + break; + case 1: + // just reset column 2; ignore edits + fireTableDataChanged(); + } + } + + /** + * Re-evaluates the expressions in the table. + */ + void updateModel() { + for (int i = 0; i < expressions.size(); ++i) { + Object value = expressions.elementAt(i); + String expr = value.toString(); + String result = ""; + if (expr.length() > 0) { + result = debugGui.dim.eval(expr); + if (result == null) result = ""; + } else { + result = ""; + } + result = result.replace('\n', ' '); + values.setElementAt(result, i); + } + fireTableDataChanged(); + } +} + +/** + * A table for evaluated expressions. + */ +class Evaluator extends JTable { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 8133672432982594256L; + + /** + * The {@link TableModel} for this table. + */ + MyTableModel tableModel; + + /** + * Creates a new Evaluator. + */ + public Evaluator(SwingGui debugGui) { + super(new MyTableModel(debugGui)); + tableModel = (MyTableModel)getModel(); + } +} + +/** + * Tree model for script object inspection. + */ +class VariableModel implements TreeTableModel { + + /** + * Serializable magic number. + */ + private static final String[] cNames = { " Name", " Value" }; + + /** + * Tree column types. + */ + private static final Class[] cTypes = + { TreeTableModel.class, String.class }; + + /** + * Empty {@link VariableNode} array. + */ + private static final VariableNode[] CHILDLESS = new VariableNode[0]; + + /** + * The debugger. + */ + private Dim debugger; + + /** + * The root node. + */ + private VariableNode root; + + /** + * Creates a new VariableModel. + */ + public VariableModel() { + } + + /** + * Creates a new VariableModel. + */ + public VariableModel(Dim debugger, Object scope) { + this.debugger = debugger; + this.root = new VariableNode(scope, "this"); + } + + // TreeTableModel + + /** + * Returns the root node of the tree. + */ + public Object getRoot() { + if (debugger == null) { + return null; + } + return root; + } + + /** + * Returns the number of children of the given node. + */ + public int getChildCount(Object nodeObj) { + if (debugger == null) { + return 0; + } + VariableNode node = (VariableNode) nodeObj; + return children(node).length; + } + + /** + * Returns a child of the given node. + */ + public Object getChild(Object nodeObj, int i) { + if (debugger == null) { + return null; + } + VariableNode node = (VariableNode) nodeObj; + return children(node)[i]; + } + + /** + * Returns whether the given node is a leaf node. + */ + public boolean isLeaf(Object nodeObj) { + if (debugger == null) { + return true; + } + VariableNode node = (VariableNode) nodeObj; + return children(node).length == 0; + } + + /** + * Returns the index of a node under its parent. + */ + public int getIndexOfChild(Object parentObj, Object childObj) { + if (debugger == null) { + return -1; + } + VariableNode parent = (VariableNode) parentObj; + VariableNode child = (VariableNode) childObj; + VariableNode[] children = children(parent); + for (int i = 0; i != children.length; i++) { + if (children[i] == child) { + return i; + } + } + return -1; + } + + /** + * Returns whether the given cell is editable. + */ + public boolean isCellEditable(Object node, int column) { + return column == 0; + } + + /** + * Sets the value at the given cell. + */ + public void setValueAt(Object value, Object node, int column) { } + + /** + * Adds a TreeModelListener to this tree. + */ + public void addTreeModelListener(TreeModelListener l) { } + + /** + * Removes a TreeModelListener from this tree. + */ + public void removeTreeModelListener(TreeModelListener l) { } + + public void valueForPathChanged(TreePath path, Object newValue) { } + + // TreeTableNode + + /** + * Returns the number of columns. + */ + public int getColumnCount() { + return cNames.length; + } + + /** + * Returns the name of the given column. + */ + public String getColumnName(int column) { + return cNames[column]; + } + + /** + * Returns the type of value stored in the given column. + */ + public Class getColumnClass(int column) { + return cTypes[column]; + } + + /** + * Returns the value at the given cell. + */ + public Object getValueAt(Object nodeObj, int column) { + if (debugger == null) { return null; } + VariableNode node = (VariableNode)nodeObj; + switch (column) { + case 0: // Name + return node.toString(); + case 1: // Value + String result; + try { + result = debugger.objectToString(getValue(node)); + } catch (RuntimeException exc) { + result = exc.getMessage(); + } + StringBuffer buf = new StringBuffer(); + int len = result.length(); + for (int i = 0; i < len; i++) { + char ch = result.charAt(i); + if (Character.isISOControl(ch)) { + ch = ' '; + } + buf.append(ch); + } + return buf.toString(); + } + return null; + } + + /** + * Returns an array of the children of the given node. + */ + private VariableNode[] children(VariableNode node) { + if (node.children != null) { + return node.children; + } + + VariableNode[] children; + + Object value = getValue(node); + Object[] ids = debugger.getObjectIds(value); + if (ids == null || ids.length == 0) { + children = CHILDLESS; + } else { + Arrays.sort(ids, new Comparator() { + public int compare(Object l, Object r) + { + if (l instanceof String) { + if (r instanceof Integer) { + return -1; + } + return ((String)l).compareToIgnoreCase((String)r); + } else { + if (r instanceof String) { + return 1; + } + int lint = ((Integer)l).intValue(); + int rint = ((Integer)r).intValue(); + return lint - rint; + } + } + }); + children = new VariableNode[ids.length]; + for (int i = 0; i != ids.length; ++i) { + children[i] = new VariableNode(value, ids[i]); + } + } + node.children = children; + return children; + } + + /** + * Returns the value of the given node. + */ + public Object getValue(VariableNode node) { + try { + return debugger.getObjectProperty(node.object, node.id); + } catch (Exception exc) { + return "undefined"; + } + } + + /** + * A variable node in the tree. + */ + private static class VariableNode { + + /** + * The script object. + */ + private Object object; + + /** + * The object name. Either a String or an Integer. + */ + private Object id; + + /** + * Array of child nodes. This is filled with the properties of + * the object. + */ + private VariableNode[] children; + + /** + * Creates a new VariableNode. + */ + public VariableNode(Object object, Object id) { + this.object = object; + this.id = id; + } + + /** + * Returns a string representation of this node. + */ + public String toString() { + return id instanceof String + ? (String) id : "[" + ((Integer) id).intValue() + "]"; + } + } +} + +/** + * A tree table for browsing script objects. + */ +class MyTreeTable extends JTreeTable { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3457265548184453049L; + + /** + * Creates a new MyTreeTable. + */ + public MyTreeTable(VariableModel model) { + super(model); + } + + /** + * Initializes a tree for this tree table. + */ + public JTree resetTree(TreeTableModel treeTableModel) { + tree = new TreeTableCellRenderer(treeTableModel); + + // Install a tableModel representing the visible rows in the tree. + super.setModel(new TreeTableModelAdapter(treeTableModel, tree)); + + // Force the JTable and JTree to share their row selection models. + ListToTreeSelectionModelWrapper selectionWrapper = new + ListToTreeSelectionModelWrapper(); + tree.setSelectionModel(selectionWrapper); + setSelectionModel(selectionWrapper.getListSelectionModel()); + + // Make the tree and table row heights the same. + if (tree.getRowHeight() < 1) { + // Metal looks better like this. + setRowHeight(18); + } + + // Install the tree editor renderer and editor. + setDefaultRenderer(TreeTableModel.class, tree); + setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); + setShowGrid(true); + setIntercellSpacing(new Dimension(1,1)); + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + DefaultTreeCellRenderer r = (DefaultTreeCellRenderer)tree.getCellRenderer(); + r.setOpenIcon(null); + r.setClosedIcon(null); + r.setLeafIcon(null); + return tree; + } + + /** + * Returns whether the cell under the coordinates of the mouse + * in the {@link EventObject} is editable. + */ + public boolean isCellEditable(EventObject e) { + if (e instanceof MouseEvent) { + MouseEvent me = (MouseEvent)e; + // If the modifiers are not 0 (or the left mouse button), + // tree may try and toggle the selection, and table + // will then try and toggle, resulting in the + // selection remaining the same. To avoid this, we + // only dispatch when the modifiers are 0 (or the left mouse + // button). + if (me.getModifiers() == 0 || + ((me.getModifiers() & (InputEvent.BUTTON1_MASK|1024)) != 0 && + (me.getModifiers() & + (InputEvent.SHIFT_MASK | + InputEvent.CTRL_MASK | + InputEvent.ALT_MASK | + InputEvent.BUTTON2_MASK | + InputEvent.BUTTON3_MASK | + 64 | //SHIFT_DOWN_MASK + 128 | //CTRL_DOWN_MASK + 512 | // ALT_DOWN_MASK + 2048 | //BUTTON2_DOWN_MASK + 4096 //BUTTON3_DOWN_MASK + )) == 0)) { + int row = rowAtPoint(me.getPoint()); + for (int counter = getColumnCount() - 1; counter >= 0; + counter--) { + if (TreeTableModel.class == getColumnClass(counter)) { + MouseEvent newME = new MouseEvent + (MyTreeTable.this.tree, me.getID(), + me.getWhen(), me.getModifiers(), + me.getX() - getCellRect(row, counter, true).x, + me.getY(), me.getClickCount(), + me.isPopupTrigger()); + MyTreeTable.this.tree.dispatchEvent(newME); + break; + } + } + } + if (me.getClickCount() >= 3) { + return true; + } + return false; + } + if (e == null) { + return true; + } + return false; + } +} + +/** + * Panel that shows information about the context. + */ +class ContextWindow extends JPanel implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 2306040975490228051L; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The combo box that holds the stack frames. + */ + JComboBox context; + + /** + * Tool tips for the stack frames. + */ + Vector toolTips; + + /** + * Tabbed pane for "this" and "locals". + */ + private JTabbedPane tabs; + + /** + * Tabbed pane for "watch" and "evaluate". + */ + private JTabbedPane tabs2; + + /** + * The table showing the "this" object. + */ + private MyTreeTable thisTable; + + /** + * The table showing the stack local variables. + */ + private MyTreeTable localsTable; + + /** + * The {@link #evaluator}'s table model. + */ + private MyTableModel tableModel; + + /** + * The script evaluator table. + */ + private Evaluator evaluator; + + /** + * The script evaluation text area. + */ + private EvalTextArea cmdLine; + + /** + * The split pane. + */ + JSplitPane split; + + /** + * Whether the ContextWindow is enabled. + */ + private boolean enabled; + + /** + * Creates a new ContextWindow. + */ + public ContextWindow(final SwingGui debugGui) { + this.debugGui = debugGui; + enabled = false; + JPanel left = new JPanel(); + JToolBar t1 = new JToolBar(); + t1.setName("Variables"); + t1.setLayout(new GridLayout()); + t1.add(left); + JPanel p1 = new JPanel(); + p1.setLayout(new GridLayout()); + JPanel p2 = new JPanel(); + p2.setLayout(new GridLayout()); + p1.add(t1); + JLabel label = new JLabel("Context:"); + context = new JComboBox(); + context.setLightWeightPopupEnabled(false); + toolTips = new java.util.Vector(); + label.setBorder(context.getBorder()); + context.addActionListener(this); + context.setActionCommand("ContextSwitch"); + GridBagLayout layout = new GridBagLayout(); + left.setLayout(layout); + GridBagConstraints lc = new GridBagConstraints(); + lc.insets.left = 5; + lc.anchor = GridBagConstraints.WEST; + lc.ipadx = 5; + layout.setConstraints(label, lc); + left.add(label); + GridBagConstraints c = new GridBagConstraints(); + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.WEST; + layout.setConstraints(context, c); + left.add(context); + tabs = new JTabbedPane(SwingConstants.BOTTOM); + tabs.setPreferredSize(new Dimension(500,300)); + thisTable = new MyTreeTable(new VariableModel()); + JScrollPane jsp = new JScrollPane(thisTable); + jsp.getViewport().setViewSize(new Dimension(5,2)); + tabs.add("this", jsp); + localsTable = new MyTreeTable(new VariableModel()); + localsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + localsTable.setPreferredSize(null); + jsp = new JScrollPane(localsTable); + tabs.add("Locals", jsp); + c.weightx = c.weighty = 1; + c.gridheight = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + layout.setConstraints(tabs, c); + left.add(tabs); + evaluator = new Evaluator(debugGui); + cmdLine = new EvalTextArea(debugGui); + //cmdLine.requestFocus(); + tableModel = evaluator.tableModel; + jsp = new JScrollPane(evaluator); + JToolBar t2 = new JToolBar(); + t2.setName("Evaluate"); + tabs2 = new JTabbedPane(SwingConstants.BOTTOM); + tabs2.add("Watch", jsp); + tabs2.add("Evaluate", new JScrollPane(cmdLine)); + tabs2.setPreferredSize(new Dimension(500,300)); + t2.setLayout(new GridLayout()); + t2.add(tabs2); + p2.add(t2); + evaluator.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, + p1, p2); + split.setOneTouchExpandable(true); + SwingGui.setResizeWeight(split, 0.5); + setLayout(new BorderLayout()); + add(split, BorderLayout.CENTER); + + final JToolBar finalT1 = t1; + final JToolBar finalT2 = t2; + final JPanel finalP1 = p1; + final JPanel finalP2 = p2; + final JSplitPane finalSplit = split; + final JPanel finalThis = this; + + ComponentListener clistener = new ComponentListener() { + boolean t2Docked = true; + void check(Component comp) { + Component thisParent = finalThis.getParent(); + if (thisParent == null) { + return; + } + Component parent = finalT1.getParent(); + boolean leftDocked = true; + boolean rightDocked = true; + boolean adjustVerticalSplit = false; + if (parent != null) { + if (parent != finalP1) { + while (!(parent instanceof JFrame)) { + parent = parent.getParent(); + } + JFrame frame = (JFrame)parent; + debugGui.addTopLevel("Variables", frame); + + // We need the following hacks because: + // - We want an undocked toolbar to be + // resizable. + // - We are using JToolbar as a container of a + // JComboBox. Without this JComboBox's popup + // can get left floating when the toolbar is + // re-docked. + // + // We make the frame resizable and then + // remove JToolbar's window listener + // and insert one of our own that first ensures + // the JComboBox's popup window is closed + // and then calls JToolbar's window listener. + if (!frame.isResizable()) { + frame.setResizable(true); + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + final EventListener[] l = + frame.getListeners(WindowListener.class); + frame.removeWindowListener((WindowListener)l[0]); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + context.hidePopup(); + ((WindowListener)l[0]).windowClosing(e); + } + }); + //adjustVerticalSplit = true; + } + leftDocked = false; + } else { + leftDocked = true; + } + } + parent = finalT2.getParent(); + if (parent != null) { + if (parent != finalP2) { + while (!(parent instanceof JFrame)) { + parent = parent.getParent(); + } + JFrame frame = (JFrame)parent; + debugGui.addTopLevel("Evaluate", frame); + frame.setResizable(true); + rightDocked = false; + } else { + rightDocked = true; + } + } + if (leftDocked && t2Docked && rightDocked && t2Docked) { + // no change + return; + } + t2Docked = rightDocked; + JSplitPane split = (JSplitPane)thisParent; + if (leftDocked) { + if (rightDocked) { + finalSplit.setDividerLocation(0.5); + } else { + finalSplit.setDividerLocation(1.0); + } + if (adjustVerticalSplit) { + split.setDividerLocation(0.66); + } + + } else if (rightDocked) { + finalSplit.setDividerLocation(0.0); + split.setDividerLocation(0.66); + } else { + // both undocked + split.setDividerLocation(1.0); + } + } + public void componentHidden(ComponentEvent e) { + check(e.getComponent()); + } + public void componentMoved(ComponentEvent e) { + check(e.getComponent()); + } + public void componentResized(ComponentEvent e) { + check(e.getComponent()); + } + public void componentShown(ComponentEvent e) { + check(e.getComponent()); + } + }; + p1.addContainerListener(new ContainerListener() { + public void componentAdded(ContainerEvent e) { + Component thisParent = finalThis.getParent(); + JSplitPane split = (JSplitPane)thisParent; + if (e.getChild() == finalT1) { + if (finalT2.getParent() == finalP2) { + // both docked + finalSplit.setDividerLocation(0.5); + } else { + // left docked only + finalSplit.setDividerLocation(1.0); + } + split.setDividerLocation(0.66); + } + } + public void componentRemoved(ContainerEvent e) { + Component thisParent = finalThis.getParent(); + JSplitPane split = (JSplitPane)thisParent; + if (e.getChild() == finalT1) { + if (finalT2.getParent() == finalP2) { + // right docked only + finalSplit.setDividerLocation(0.0); + split.setDividerLocation(0.66); + } else { + // both undocked + split.setDividerLocation(1.0); + } + } + } + }); + t1.addComponentListener(clistener); + t2.addComponentListener(clistener); + disable(); + } + + /** + * Disables the component. + */ + public void disable() { + context.setEnabled(false); + thisTable.setEnabled(false); + localsTable.setEnabled(false); + evaluator.setEnabled(false); + cmdLine.setEnabled(false); + } + + /** + * Enables the component. + */ + public void enable() { + context.setEnabled(true); + thisTable.setEnabled(true); + localsTable.setEnabled(true); + evaluator.setEnabled(true); + cmdLine.setEnabled(true); + } + + /** + * Disables updating of the component. + */ + public void disableUpdate() { + enabled = false; + } + + /** + * Enables updating of the component. + */ + public void enableUpdate() { + enabled = true; + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + if (!enabled) return; + if (e.getActionCommand().equals("ContextSwitch")) { + Dim.ContextData contextData = debugGui.dim.currentContextData(); + if (contextData == null) { return; } + int frameIndex = context.getSelectedIndex(); + context.setToolTipText(toolTips.elementAt(frameIndex).toString()); + int frameCount = contextData.frameCount(); + if (frameIndex >= frameCount) { + return; + } + Dim.StackFrame frame = contextData.getFrame(frameIndex); + Object scope = frame.scope(); + Object thisObj = frame.thisObj(); + thisTable.resetTree(new VariableModel(debugGui.dim, thisObj)); + VariableModel scopeModel; + if (scope != thisObj) { + scopeModel = new VariableModel(debugGui.dim, scope); + } else { + scopeModel = new VariableModel(); + } + localsTable.resetTree(scopeModel); + debugGui.dim.contextSwitch(frameIndex); + debugGui.showStopLine(frame); + tableModel.updateModel(); + } + } +} + +/** + * The debugger frame menu bar. + */ +class Menubar extends JMenuBar implements ActionListener { + + /** + * Serializable magic number. + */ + private static final long serialVersionUID = 3217170497245911461L; + + /** + * Items that are enabled only when interrupted. + */ + private Vector interruptOnlyItems = new Vector(); + + /** + * Items that are enabled only when running. + */ + private Vector runOnlyItems = new Vector(); + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The menu listing the internal frames. + */ + private JMenu windowMenu; + + /** + * The "Break on exceptions" menu item. + */ + private JCheckBoxMenuItem breakOnExceptions; + + /** + * The "Break on enter" menu item. + */ + private JCheckBoxMenuItem breakOnEnter; + + /** + * The "Break on return" menu item. + */ + private JCheckBoxMenuItem breakOnReturn; + + /** + * Creates a new Menubar. + */ + Menubar(SwingGui debugGui) { + super(); + this.debugGui = debugGui; + String[] fileItems = {"Open...", "Run...", "", "Exit"}; + String[] fileCmds = {"Open", "Load", "", "Exit"}; + char[] fileShortCuts = {'0', 'N', 0, 'X'}; + int[] fileAccelerators = {KeyEvent.VK_O, + KeyEvent.VK_N, + 0, + KeyEvent.VK_Q}; + String[] editItems = {"Cut", "Copy", "Paste", "Go to function..."}; + char[] editShortCuts = {'T', 'C', 'P', 'F'}; + String[] debugItems = {"Break", "Go", "Step Into", "Step Over", "Step Out"}; + char[] debugShortCuts = {'B', 'G', 'I', 'O', 'T'}; + String[] plafItems = {"Metal", "Windows", "Motif"}; + char [] plafShortCuts = {'M', 'W', 'F'}; + int[] debugAccelerators = {KeyEvent.VK_PAUSE, + KeyEvent.VK_F5, + KeyEvent.VK_F11, + KeyEvent.VK_F7, + KeyEvent.VK_F8, + 0, 0}; + + JMenu fileMenu = new JMenu("File"); + fileMenu.setMnemonic('F'); + JMenu editMenu = new JMenu("Edit"); + editMenu.setMnemonic('E'); + JMenu plafMenu = new JMenu("Platform"); + plafMenu.setMnemonic('P'); + JMenu debugMenu = new JMenu("Debug"); + debugMenu.setMnemonic('D'); + windowMenu = new JMenu("Window"); + windowMenu.setMnemonic('W'); + for (int i = 0; i < fileItems.length; ++i) { + if (fileItems[i].length() == 0) { + fileMenu.addSeparator(); + } else { + JMenuItem item = new JMenuItem(fileItems[i], + fileShortCuts[i]); + item.setActionCommand(fileCmds[i]); + item.addActionListener(this); + fileMenu.add(item); + if (fileAccelerators[i] != 0) { + KeyStroke k = KeyStroke.getKeyStroke(fileAccelerators[i], Event.CTRL_MASK); + item.setAccelerator(k); + } + } + } + for (int i = 0; i < editItems.length; ++i) { + JMenuItem item = new JMenuItem(editItems[i], + editShortCuts[i]); + item.addActionListener(this); + editMenu.add(item); + } + for (int i = 0; i < plafItems.length; ++i) { + JMenuItem item = new JMenuItem(plafItems[i], + plafShortCuts[i]); + item.addActionListener(this); + plafMenu.add(item); + } + for (int i = 0; i < debugItems.length; ++i) { + JMenuItem item = new JMenuItem(debugItems[i], + debugShortCuts[i]); + item.addActionListener(this); + if (debugAccelerators[i] != 0) { + KeyStroke k = KeyStroke.getKeyStroke(debugAccelerators[i], 0); + item.setAccelerator(k); + } + if (i != 0) { + interruptOnlyItems.add(item); + } else { + runOnlyItems.add(item); + } + debugMenu.add(item); + } + breakOnExceptions = new JCheckBoxMenuItem("Break on Exceptions"); + breakOnExceptions.setMnemonic('X'); + breakOnExceptions.addActionListener(this); + breakOnExceptions.setSelected(false); + debugMenu.add(breakOnExceptions); + + breakOnEnter = new JCheckBoxMenuItem("Break on Function Enter"); + breakOnEnter.setMnemonic('E'); + breakOnEnter.addActionListener(this); + breakOnEnter.setSelected(false); + debugMenu.add(breakOnEnter); + + breakOnReturn = new JCheckBoxMenuItem("Break on Function Return"); + breakOnReturn.setMnemonic('R'); + breakOnReturn.addActionListener(this); + breakOnReturn.setSelected(false); + debugMenu.add(breakOnReturn); + + add(fileMenu); + add(editMenu); + //add(plafMenu); + add(debugMenu); + JMenuItem item; + windowMenu.add(item = new JMenuItem("Cascade", 'A')); + item.addActionListener(this); + windowMenu.add(item = new JMenuItem("Tile", 'T')); + item.addActionListener(this); + windowMenu.addSeparator(); + windowMenu.add(item = new JMenuItem("Console", 'C')); + item.addActionListener(this); + add(windowMenu); + + updateEnabled(false); + } + + /** + * Returns the "Break on exceptions" menu item. + */ + public JCheckBoxMenuItem getBreakOnExceptions() { + return breakOnExceptions; + } + + /** + * Returns the "Break on enter" menu item. + */ + public JCheckBoxMenuItem getBreakOnEnter() { + return breakOnEnter; + } + + /** + * Returns the "Break on return" menu item. + */ + public JCheckBoxMenuItem getBreakOnReturn() { + return breakOnReturn; + } + + /** + * Returns the "Debug" menu. + */ + public JMenu getDebugMenu() { + return getMenu(2); + } + + // ActionListener + + /** + * Performs an action. + */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + String plaf_name = null; + if (cmd.equals("Metal")) { + plaf_name = "javax.swing.plaf.metal.MetalLookAndFeel"; + } else if (cmd.equals("Windows")) { + plaf_name = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; + } else if (cmd.equals("Motif")) { + plaf_name = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; + } else { + Object source = e.getSource(); + if (source == breakOnExceptions) { + debugGui.dim.setBreakOnExceptions(breakOnExceptions.isSelected()); + } else if (source == breakOnEnter) { + debugGui.dim.setBreakOnEnter(breakOnEnter.isSelected()); + } else if (source == breakOnReturn) { + debugGui.dim.setBreakOnReturn(breakOnReturn.isSelected()); + } else { + debugGui.actionPerformed(e); + } + return; + } + try { + UIManager.setLookAndFeel(plaf_name); + SwingUtilities.updateComponentTreeUI(debugGui); + SwingUtilities.updateComponentTreeUI(debugGui.dlg); + } catch (Exception ignored) { + //ignored.printStackTrace(); + } + } + + /** + * Adds a file to the window menu. + */ + public void addFile(String url) { + int count = windowMenu.getItemCount(); + JMenuItem item; + if (count == 4) { + windowMenu.addSeparator(); + count++; + } + JMenuItem lastItem = windowMenu.getItem(count -1); + boolean hasMoreWin = false; + int maxWin = 5; + if (lastItem != null && + lastItem.getText().equals("More Windows...")) { + hasMoreWin = true; + maxWin++; + } + if (!hasMoreWin && count - 4 == 5) { + windowMenu.add(item = new JMenuItem("More Windows...", 'M')); + item.setActionCommand("More Windows..."); + item.addActionListener(this); + return; + } else if (count - 4 <= maxWin) { + if (hasMoreWin) { + count--; + windowMenu.remove(lastItem); + } + String shortName = SwingGui.getShortName(url); + + windowMenu.add(item = new JMenuItem((char)('0' + (count-4)) + " " + shortName, '0' + (count - 4))); + if (hasMoreWin) { + windowMenu.add(lastItem); + } + } else { + return; + } + item.setActionCommand(url); + item.addActionListener(this); + } + + /** + * Updates the enabledness of menu items. + */ + public void updateEnabled(boolean interrupted) { + for (int i = 0; i != interruptOnlyItems.size(); ++i) { + JMenuItem item = (JMenuItem)interruptOnlyItems.elementAt(i); + item.setEnabled(interrupted); + } + + for (int i = 0; i != runOnlyItems.size(); ++i) { + JMenuItem item = (JMenuItem)runOnlyItems.elementAt(i); + item.setEnabled(!interrupted); + } + } +} + +/** + * Class to consolidate all cases that require to implement Runnable + * to avoid class generation bloat. + */ +class RunProxy implements Runnable { + + // Constants for 'type'. + static final int OPEN_FILE = 1; + static final int LOAD_FILE = 2; + static final int UPDATE_SOURCE_TEXT = 3; + static final int ENTER_INTERRUPT = 4; + + /** + * The debugger GUI. + */ + private SwingGui debugGui; + + /** + * The type of Runnable this object is. Takes one of the constants + * defined in this class. + */ + private int type; + + /** + * The name of the file to open or load. + */ + String fileName; + + /** + * The source text to update. + */ + String text; + + /** + * The source for which to update the text. + */ + Dim.SourceInfo sourceInfo; + + /** + * The frame to interrupt in. + */ + Dim.StackFrame lastFrame; + + /** + * The name of the interrupted thread. + */ + String threadTitle; + + /** + * The message of the exception thrown that caused the thread + * interruption, if any. + */ + String alertMessage; + + /** + * Creates a new RunProxy. + */ + public RunProxy(SwingGui debugGui, int type) { + this.debugGui = debugGui; + this.type = type; + } + + /** + * Runs this Runnable. + */ + public void run() { + switch (type) { + case OPEN_FILE: + try { + debugGui.dim.compileScript(fileName, text); + } catch (RuntimeException ex) { + MessageDialogWrapper.showMessageDialog( + debugGui, ex.getMessage(), "Error Compiling "+fileName, + JOptionPane.ERROR_MESSAGE); + } + break; + + case LOAD_FILE: + try { + debugGui.dim.evalScript(fileName, text); + } catch (RuntimeException ex) { + MessageDialogWrapper.showMessageDialog( + debugGui, ex.getMessage(), "Run error for "+fileName, + JOptionPane.ERROR_MESSAGE); + } + break; + + case UPDATE_SOURCE_TEXT: + { + String fileName = sourceInfo.url(); + if (!debugGui.updateFileWindow(sourceInfo) && + !fileName.equals("")) { + debugGui.createFileWindow(sourceInfo, -1); + } + } + break; + + case ENTER_INTERRUPT: + debugGui.enterInterruptImpl(lastFrame, threadTitle, alertMessage); + break; + + default: + throw new IllegalArgumentException(String.valueOf(type)); + + } + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/build.xml b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/build.xml new file mode 100644 index 0000000..c35fd40 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/debugger/build.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + import java.awt.Component; + +package org.mozilla.javascript.tools.debugger.downloaded; + + + + import java.awt.event.*; + + + + import java.awt.AWTEvent; + + + + import java.io.Serializable; + + + + import javax.swing.*; + + package org.mozilla.javascript.tools.debugger.downloaded; + import javax.swing.*; + + + + class ListToTreeSelectionModelWrapper + public class ListToTreeSelectionModelWrapper + + + ListSelectionModel getListSelectionModel + public ListSelectionModel getListSelectionModel + + + import java.awt.Rectangle; + + + + import javax.swing.tree.TreeModel; + + package org.mozilla.javascript.tools.debugger.downloaded; + import javax.swing.tree.TreeModel; + + + + import javax.swing.JTree; + + package org.mozilla.javascript.tools.debugger.downloaded; + import javax.swing.JTree; + + + + + + + + +The following targets are available with this build file: + + download Download ${swing-ex-url} + and extract the necessary files from it. + + help Print this help. + + + + + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/CodePrinter.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/CodePrinter.java new file mode 100644 index 0000000..dd4f689 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/CodePrinter.java @@ -0,0 +1,212 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.idswitch; + +class CodePrinter { + +// length of u-type escape like \u12AB + private static final int LITERAL_CHAR_MAX_SIZE = 6; + + private String lineTerminator = "\n"; + + private int indentStep = 4; + private int indentTabSize = 8; + + private char[] buffer = new char[1 << 12]; // 4K + private int offset; + + public String getLineTerminator() { return lineTerminator; } + public void setLineTerminator(String value) { lineTerminator = value; } + + public int getIndentStep() { return indentStep; } + public void setIndentStep(int char_count) { indentStep = char_count; } + + public int getIndentTabSize() { return indentTabSize; } + public void setIndentTabSize(int tab_size) { indentTabSize = tab_size; } + + public void clear() { + offset = 0; + } + + private int ensure_area(int area_size) { + int begin = offset; + int end = begin + area_size; + if (end > buffer.length) { + int new_capacity = buffer.length * 2; + if (end > new_capacity) { new_capacity = end; } + char[] tmp = new char[new_capacity]; + System.arraycopy(buffer, 0, tmp, 0, begin); + buffer = tmp; + } + return begin; + } + + private int add_area(int area_size) { + int pos = ensure_area(area_size); + offset = pos + area_size; + return pos; + } + + public int getOffset() { + return offset; + } + + public int getLastChar() { + return offset == 0 ? -1 : buffer[offset - 1]; + } + + public void p(char c) { + int pos = add_area(1); + buffer[pos] = c; + } + + public void p(String s) { + int l = s.length(); + int pos = add_area(l); + s.getChars(0, l, buffer, pos); + } + + public final void p(char[] array) { + p(array, 0, array.length); + } + + public void p(char[] array, int begin, int end) { + int l = end - begin; + int pos = add_area(l); + System.arraycopy(array, begin, buffer, pos, l); + } + + public void p(int i) { + p(Integer.toString(i)); + } + + public void qchar(int c) { + int pos = ensure_area(2 + LITERAL_CHAR_MAX_SIZE); + buffer[pos] = '\''; + pos = put_string_literal_char(pos + 1, c, false); + buffer[pos] = '\''; + offset = pos + 1; + } + + public void qstring(String s) { + int l = s.length(); + int pos = ensure_area(2 + LITERAL_CHAR_MAX_SIZE * l); + buffer[pos] = '"'; + ++pos; + for (int i = 0; i != l; ++i) { + pos = put_string_literal_char(pos, s.charAt(i), true); + } + buffer[pos] = '"'; + offset = pos + 1; + } + + private int put_string_literal_char(int pos, int c, boolean in_string) { + boolean backslash_symbol = true; + switch (c) { + case '\b': c = 'b'; break; + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\f': c = 'f'; break; + case '\r': c = 'r'; break; + case '\'': backslash_symbol = !in_string; break; + case '"': backslash_symbol = in_string; break; + default: backslash_symbol = false; + } + + if (backslash_symbol) { + buffer[pos] = '\\'; + buffer[pos + 1] = (char)c; + pos += 2; + } + else if (' ' <= c && c <= 126) { + buffer[pos] = (char)c; + ++pos; + } + else { + buffer[pos] = '\\'; + buffer[pos + 1] = 'u'; + buffer[pos + 2] = digit_to_hex_letter(0xF & (c >> 12)); + buffer[pos + 3] = digit_to_hex_letter(0xF & (c >> 8)); + buffer[pos + 4] = digit_to_hex_letter(0xF & (c >> 4)); + buffer[pos + 5] = digit_to_hex_letter(0xF & c); + pos += 6; + } + return pos; + } + + private static char digit_to_hex_letter(int d) { + return (char)((d < 10) ? '0' + d : 'A' - 10 + d); + } + + public void indent(int level) { + int visible_size = indentStep * level; + int indent_size, tab_count; + if (indentTabSize <= 0) { + tab_count = 0; indent_size = visible_size; + } + else { + tab_count = visible_size / indentTabSize; + indent_size = tab_count + visible_size % indentTabSize; + } + int pos = add_area(indent_size); + int tab_end = pos + tab_count; + int indent_end = pos + indent_size; + for (; pos != tab_end; ++pos) { buffer[pos] = '\t'; } + for (; pos != indent_end; ++pos) { buffer[pos] = ' '; } + } + + public void nl() { + p('\n'); + } + + public void line(int indent_level, String s) { + indent(indent_level); p(s); nl(); + } + + public void erase(int begin, int end) { + System.arraycopy(buffer, end, buffer, begin, offset - end); + offset -= end - begin; + } + + public String toString() { + return new String(buffer, 0, offset); + } + + + +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/FileBody.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/FileBody.java new file mode 100644 index 0000000..60bdfb4 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/FileBody.java @@ -0,0 +1,191 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.idswitch; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; + +public class FileBody { + + private static class ReplaceItem { + ReplaceItem next; + int begin; + int end; + String replacement; + + ReplaceItem(int begin, int end, String text) { + this.begin = begin; + this.end = end; + this.replacement = text; + } + } + + private char[] buffer = new char[1 << 14]; // 16K + private int bufferEnd; + private int lineBegin; + private int lineEnd; + private int nextLineStart; + + private int lineNumber; + + ReplaceItem firstReplace; + ReplaceItem lastReplace; + + + public char[] getBuffer() { return buffer; } + + public void readData(Reader r) throws IOException { + int capacity = buffer.length; + int offset = 0; + for (;;) { + int n_read = r.read(buffer, offset, capacity - offset); + if (n_read < 0) { break; } + offset += n_read; + if (capacity == offset) { + capacity *= 2; + char[] tmp = new char[capacity]; + System.arraycopy(buffer, 0, tmp, 0, offset); + buffer = tmp; + } + } + bufferEnd = offset; + } + + public void writeInitialData(Writer w) throws IOException { + w.write(buffer, 0, bufferEnd); + } + + public void writeData(Writer w) throws IOException { + int offset = 0; + for (ReplaceItem x = firstReplace; x != null; x = x.next) { + int before_replace = x.begin - offset; + if (before_replace > 0) { + w.write(buffer, offset, before_replace); + } + w.write(x.replacement); + offset = x.end; + } + int tail = bufferEnd - offset; + if (tail != 0) { + w.write(buffer, offset, tail); + } + } + + public boolean wasModified() { return firstReplace != null; } + + public boolean setReplacement(int begin, int end, String text) { + if (equals(text, buffer, begin, end)) { return false; } + + ReplaceItem item = new ReplaceItem(begin, end, text); + if (firstReplace == null) { + firstReplace = lastReplace = item; + } + else if (begin < firstReplace.begin) { + item.next = firstReplace; + firstReplace = item; + } + else { + ReplaceItem cursor = firstReplace; + ReplaceItem next = cursor.next; + while (next != null) { + if (begin < next.begin) { + item.next = next; + cursor.next = item; + break; + } + cursor = next; + next = next.next; + } + if (next == null) { + lastReplace.next = item; + } + } + + return true; + } + + public int getLineNumber() { return lineNumber; } + + public int getLineBegin() { return lineBegin; } + + public int getLineEnd() { return lineEnd; } + + public void startLineLoop() { + lineNumber = 0; + lineBegin = lineEnd = nextLineStart = 0; + } + + public boolean nextLine() { + if (nextLineStart == bufferEnd) { + lineNumber = 0; return false; + } + int i; int c = 0; + for (i = nextLineStart; i != bufferEnd; ++i) { + c = buffer[i]; + if (c == '\n' || c == '\r') { break; } + } + lineBegin = nextLineStart; + lineEnd = i; + if (i == bufferEnd) { + nextLineStart = i; + } + else if (c == '\r' && i + 1 != bufferEnd && buffer[i + 1] == '\n') { + nextLineStart = i + 2; + } + else { + nextLineStart = i + 1; + } + ++lineNumber; + return true; + } + + private static boolean equals(String str, char[] array, int begin, int end) + { + if (str.length() == end - begin) { + for (int i = begin, j = 0; i != end; ++i, ++j) { + if (array[i] != str.charAt(j)) { return false; } + } + return true; + } + return false; + } + +} + + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/IdValuePair.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/IdValuePair.java new file mode 100644 index 0000000..69d5065 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/IdValuePair.java @@ -0,0 +1,58 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.idswitch; + +public class IdValuePair +{ + public final int idLength; + public final String id; + public final String value; + + private int lineNumber; + + public IdValuePair(String id, String value) { + this.idLength = id.length(); + this.id = id; + this.value = value; + } + + public int getLineNumber() { return lineNumber; } + + public void setLineNumber(int value) { lineNumber = value; } +} + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/Main.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/Main.java new file mode 100644 index 0000000..ae1d038 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/Main.java @@ -0,0 +1,612 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.idswitch; + +import java.io.*; +import java.util.*; +import java.text.SimpleDateFormat; + +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.tools.ToolErrorReporter; + +public class Main { + + private static final String SWITCH_TAG_STR = "string_id_map"; + private static final String GENERATED_TAG_STR = "generated"; + private static final String STRING_TAG_STR = "string"; + + private static final int + NORMAL_LINE = 0, + SWITCH_TAG = 1, + GENERATED_TAG = 2, + STRING_TAG = 3; + + private final Vector all_pairs = new Vector(); + + private ToolErrorReporter R; + private CodePrinter P; + private FileBody body; + private String source_file; + + private int tag_definition_end; + + private int tag_value_start; + private int tag_value_end; + + private static boolean is_value_type(int id) { + if (id == STRING_TAG) { return true; } + return false; + } + + private static String tag_name(int id) { + switch (id) { + case SWITCH_TAG: return SWITCH_TAG_STR; + case -SWITCH_TAG: return "/" + SWITCH_TAG_STR; + case GENERATED_TAG: return GENERATED_TAG_STR; + case -GENERATED_TAG: return "/" + GENERATED_TAG_STR; + } + return ""; + } + + void process_file(String file_path) throws IOException { + source_file = file_path; + + body = new FileBody(); + + InputStream is; + if (file_path.equals("-")) { + is = System.in; + } + else { + is = new FileInputStream(file_path); + } + try { + Reader r = new InputStreamReader(is, "ASCII"); + body.readData(r); + } + finally { is.close(); } + + process_file(); + + if (body.wasModified()) { + OutputStream os; + if (file_path.equals("-")) { + os = System.out; + } + else { + os = new FileOutputStream(file_path); + } + + try { + Writer w = new OutputStreamWriter(os); + body.writeData(w); + w.flush(); + } + finally { os.close(); } + } + } + + private void process_file() { + int cur_state = 0; + char[] buffer = body.getBuffer(); + + int generated_begin = -1, generated_end = -1; + int time_stamp_begin = -1, time_stamp_end = -1; + + body.startLineLoop(); + while (body.nextLine()) { + int begin = body.getLineBegin(); + int end = body.getLineEnd(); + + int tag_id = extract_line_tag_id(buffer, begin, end); + boolean bad_tag = false; + switch (cur_state) { + case NORMAL_LINE: + if (tag_id == SWITCH_TAG) { + cur_state = SWITCH_TAG; + all_pairs.removeAllElements(); + generated_begin = -1; + } + else if (tag_id == -SWITCH_TAG) { + bad_tag = true; + } + break; + case SWITCH_TAG: + if (tag_id == 0) { + look_for_id_definitions(buffer, begin, end, false); + } + else if (tag_id == STRING_TAG) { + look_for_id_definitions(buffer, begin, end, true); + } + else if (tag_id == GENERATED_TAG) { + if (generated_begin >= 0) { bad_tag = true; } + else { + cur_state = GENERATED_TAG; + time_stamp_begin = tag_definition_end; + time_stamp_end = end; + } + } + else if (tag_id == -SWITCH_TAG) { + cur_state = 0; + if (generated_begin >= 0 && !all_pairs.isEmpty()) { + generate_java_code(); + String code = P.toString(); + boolean different = body.setReplacement + (generated_begin, generated_end, code); + if (different) { + String stamp = get_time_stamp(); + body.setReplacement + (time_stamp_begin, time_stamp_end, stamp); + } + } + + break; + } + else { + bad_tag = true; + } + break; + case GENERATED_TAG: + if (tag_id == 0) { + if (generated_begin < 0) { generated_begin = begin; } + } + else if (tag_id == -GENERATED_TAG) { + if (generated_begin < 0) { generated_begin = begin; } + cur_state = SWITCH_TAG; + generated_end = begin; + } + else { + bad_tag = true; + } + break; + } + if (bad_tag) { + String text = ToolErrorReporter.getMessage( + "msg.idswitch.bad_tag_order", tag_name(tag_id)); + throw R.runtimeError + (text, source_file, body.getLineNumber(), null, 0); + } + } + + if (cur_state != 0) { + String text = ToolErrorReporter.getMessage( + "msg.idswitch.file_end_in_switch", tag_name(cur_state)); + throw R.runtimeError + (text, source_file, body.getLineNumber(), null, 0); + } + } + + private String get_time_stamp() { + SimpleDateFormat f = new SimpleDateFormat + (" 'Last update:' yyyy-MM-dd HH:mm:ss z"); + return f.format(new Date()); + } + + private void generate_java_code() { + + P.clear(); + + IdValuePair[] pairs = new IdValuePair[all_pairs.size()]; + all_pairs.copyInto(pairs); + + SwitchGenerator g = new SwitchGenerator(); + g.char_tail_test_threshold = 2; + g.setReporter(R); + g.setCodePrinter(P); + + g.generateSwitch(pairs, "0"); + } + + private int extract_line_tag_id(char[] array, int cursor, int end) { + int id = 0; + cursor = skip_white_space(array, cursor, end); + int after_leading_white_space = cursor; + cursor = look_for_slash_slash(array, cursor, end); + if (cursor != end) { + boolean at_line_start = (after_leading_white_space + 2 == cursor); + cursor = skip_white_space(array, cursor, end); + if (cursor != end && array[cursor] == '#') { + ++cursor; + + boolean end_tag = false; + if (cursor != end && array[cursor] == '/') { + ++cursor; end_tag = true; + } + + int tag_start = cursor; + + for (; cursor != end; ++cursor) { + int c = array[cursor]; + if (c == '#' || c == '=' ||is_white_space(c)) { break; } + } + + if (cursor != end) { + int tag_end = cursor; + cursor = skip_white_space(array, cursor, end); + if (cursor != end) { + int c = array[cursor]; + if (c == '=' || c == '#') { + id = get_tag_id + (array, tag_start, tag_end, at_line_start); + if (id != 0) { + String bad = null; + if (c == '#') { + if (end_tag) { + id = -id; + if (is_value_type(id)) { + bad = "msg.idswitch.no_end_usage"; + } + } + tag_definition_end = cursor + 1; + } + else { + if (end_tag) { + bad = "msg.idswitch.no_end_with_value"; + } + else if (!is_value_type(id)) { + bad = "msg.idswitch.no_value_allowed"; + } + id = extract_tag_value + (array, cursor + 1, end, id); + } + if (bad != null) { + String s = ToolErrorReporter.getMessage( + bad, tag_name(id)); + throw R.runtimeError + (s, source_file, body.getLineNumber(), + null, 0); + } + } + } + } + } + } + } + return id; + } + +// Return position after first of // or end if not found + private int look_for_slash_slash(char[] array, int cursor, int end) { + while (cursor + 2 <= end) { + int c = array[cursor++]; + if (c == '/') { + c = array[cursor++]; + if (c == '/') { + return cursor; + } + } + } + return end; + } + + private int extract_tag_value(char[] array, int cursor, int end, int id) { + // cursor points after #[^#=]+= + // ALERT: implement support for quoted strings + boolean found = false; + cursor = skip_white_space(array, cursor, end); + if (cursor != end) { + int value_start = cursor; + int value_end = cursor; + while (cursor != end) { + int c = array[cursor]; + if (is_white_space(c)) { + int after_space = skip_white_space(array, cursor + 1, end); + if (after_space != end && array[after_space] == '#') { + value_end = cursor; + cursor = after_space; + break; + } + cursor = after_space + 1; + } + else if (c == '#') { + value_end = cursor; + break; + } + else { + ++cursor; + } + } + if (cursor != end) { + // array[cursor] is '#' here + found = true; + tag_value_start = value_start; + tag_value_end = value_end; + tag_definition_end = cursor + 1; + } + } + return (found) ? id : 0; + } + + private int get_tag_id + (char[] array, int begin, int end, boolean at_line_start) + { + if (at_line_start) { + if (equals(SWITCH_TAG_STR, array, begin, end)) { + return SWITCH_TAG; + } + if (equals(GENERATED_TAG_STR, array, begin, end)) { + return GENERATED_TAG; + } + } + if (equals(STRING_TAG_STR, array, begin, end)) { + return STRING_TAG; + } + return 0; + } + + private void look_for_id_definitions + (char[] array, int begin, int end, boolean use_tag_value_as_string) + { + // Look for the pattern + // '^[ \t]+Id_([a-zA-Z0-9_]+)[ \t]*=.*$' + // where \1 gives field or method name + int cursor = begin; + // Skip tab and spaces at the beginning + cursor = skip_white_space(array, cursor, end); + int id_start = cursor; + int name_start = skip_matched_prefix("Id_", array, cursor, end); + if (name_start >= 0) { + // Found Id_ prefix + cursor = name_start; + cursor = skip_name_char(array, cursor, end); + int name_end = cursor; + if (name_start != name_end) { + cursor = skip_white_space(array, cursor, end); + if (cursor != end) { + if (array[cursor] == '=') { + int id_end = name_end; + if (use_tag_value_as_string) { + name_start = tag_value_start; + name_end = tag_value_end; + } + // Got the match + add_id(array, id_start, id_end, name_start, name_end); + } + } + } + } + } + + private void add_id + (char[] array, int id_start, int id_end, int name_start, int name_end) + { + String name = new String(array, name_start, name_end - name_start); + String value = new String(array, id_start, id_end - id_start); + + IdValuePair pair = new IdValuePair(name, value); + + pair.setLineNumber(body.getLineNumber()); + + all_pairs.addElement(pair); + } + + private static boolean is_white_space(int c) { + return c == ' ' || c == '\t'; + } + + private static int skip_white_space(char[] array, int begin, int end) { + int cursor = begin; + for (; cursor != end; ++cursor) { + int c = array[cursor]; + if (!is_white_space(c)) { break; } + } + return cursor; + } + + private static int skip_matched_prefix + (String prefix, char[] array, int begin, int end) + { + int cursor = -1; + int prefix_length = prefix.length(); + if (prefix_length <= end - begin) { + cursor = begin; + for (int i = 0; i != prefix_length; ++i, ++cursor) { + if (prefix.charAt(i) != array[cursor]) { + cursor = -1; break; + } + } + } + return cursor; + } + + private static boolean equals(String str, char[] array, int begin, int end) + { + if (str.length() == end - begin) { + for (int i = begin, j = 0; i != end; ++i, ++j) { + if (array[i] != str.charAt(j)) { return false; } + } + return true; + } + return false; + } + + private static int skip_name_char(char[] array, int begin, int end) { + int cursor = begin; + for (; cursor != end; ++cursor) { + int c = array[cursor]; + if (!('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z')) { + if (!('0' <= c && c <= '9')) { + if (c != '_') { + break; + } + } + } + } + return cursor; + } + + public static void main(String[] args) { + Main self = new Main(); + int status = self.exec(args); + System.exit(status); + } + + private int exec(String[] args) { + R = new ToolErrorReporter(true, System.err); + + int arg_count = process_options(args); + + if (arg_count == 0) { + option_error(ToolErrorReporter.getMessage( + "msg.idswitch.no_file_argument")); + return -1; + } + if (arg_count > 1) { + option_error(ToolErrorReporter.getMessage( + "msg.idswitch.too_many_arguments")); + return -1; + } + + P = new CodePrinter(); + P.setIndentStep(4); + P.setIndentTabSize(0); + + try { + process_file(args[0]); + } + catch (IOException ex) { + print_error(ToolErrorReporter.getMessage( + "msg.idswitch.io_error", ex.toString())); + return -1; + } + catch (EvaluatorException ex) { + return -1; + } + return 0; + } + + private int process_options(String[] args) { + + int status = 1; + + boolean show_usage = false; + boolean show_version = false; + + int N = args.length; + L: for (int i = 0; i != N; ++i) { + String arg = args[i]; + int arg_length = arg.length(); + if (arg_length >= 2) { + if (arg.charAt(0) == '-') { + if (arg.charAt(1) == '-') { + if (arg_length == 2) { + args[i] = null; break; + } + if (arg.equals("--help")) { + show_usage = true; + } + else if (arg.equals("--version")) { + show_version = true; + } + else { + option_error(ToolErrorReporter.getMessage( + "msg.idswitch.bad_option", arg)); + status = -1; break L; + } + } + else { + for (int j = 1; j != arg_length; ++j) { + char c = arg.charAt(j); + switch (c) { + case 'h': show_usage = true; break; + default: + option_error( + ToolErrorReporter.getMessage( + "msg.idswitch.bad_option_char", + String.valueOf(c))); + status = -1; + break L; + } + + } + } + args[i] = null; + } + } + } + + if (status == 1) { + if (show_usage) { show_usage(); status = 0; } + if (show_version) { show_version(); status = 0; } + } + + if (status != 1) { System.exit(status); } + + return remove_nulls(args); + } + + private void show_usage() { + System.out.println( + ToolErrorReporter.getMessage("msg.idswitch.usage")); + System.out.println(); + } + + private void show_version() { + System.out.println( + ToolErrorReporter.getMessage("msg.idswitch.version")); + } + + private void option_error(String str) { + print_error( + ToolErrorReporter.getMessage("msg.idswitch.bad_invocation", str)); + } + + private void print_error(String text) { + System.err.println(text); + } + + private int remove_nulls(String[] array) { + int N = array.length; + int cursor = 0; + for (; cursor != N; ++cursor) { + if (array[cursor] == null) { break; } + } + int destination = cursor; + if (cursor != N) { + ++cursor; + for (; cursor != N; ++cursor) { + String elem = array[cursor]; + if (elem != null) { + array[destination] = elem; ++destination; + } + } + } + return destination; + } +} + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/SwitchGenerator.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/SwitchGenerator.java new file mode 100644 index 0000000..3db99d6 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/idswitch/SwitchGenerator.java @@ -0,0 +1,491 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.idswitch; + +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.tools.ToolErrorReporter; + +public class SwitchGenerator { + + String v_switch_label = "L0"; + String v_label = "L"; + String v_s = "s"; + String v_c = "c"; + String v_guess = "X"; + String v_id = "id"; + String v_length_suffix = "_length"; + + int use_if_threshold = 3; + int char_tail_test_threshold = 2; + + private IdValuePair[] pairs; + private String default_value; + private int[] columns; + private boolean c_was_defined; + + private CodePrinter P; + private ToolErrorReporter R; + private String source_file; + + public CodePrinter getCodePrinter() { return P; } + public void setCodePrinter(CodePrinter value) { P = value; } + + public ToolErrorReporter getReporter() { return R; } + public void setReporter(ToolErrorReporter value) { R = value; } + + public String getSourceFileName() { return source_file; } + public void setSourceFileName(String value) { source_file = value; } + + public void generateSwitch(String[] pairs, String default_value) { + int N = pairs.length / 2; + IdValuePair[] id_pairs = new IdValuePair[N]; + for (int i = 0; i != N; ++i) { + id_pairs[i] = new IdValuePair(pairs[2 * i], pairs[2 * i + 1]); + } + generateSwitch(id_pairs, default_value); + + } + + public void generateSwitch(IdValuePair[] pairs, String default_value) { + int begin = 0; + int end = pairs.length; + if (begin == end) { return; } + this.pairs = pairs; + this.default_value = default_value; + + generate_body(begin, end, 2); + } + + private void generate_body(int begin, int end, int indent_level) { + P.indent(indent_level); + P.p(v_switch_label); P.p(": { "); + P.p(v_id); P.p(" = "); P.p(default_value); + P.p("; String "); P.p(v_guess); P.p(" = null;"); + + c_was_defined = false; + int c_def_begin = P.getOffset(); + P.p(" int "); P.p(v_c); P.p(';'); + int c_def_end = P.getOffset(); + P.nl(); + + generate_length_switch(begin, end, indent_level + 1); + + if (!c_was_defined) { + P.erase(c_def_begin, c_def_end); + } + + P.indent(indent_level + 1); + P.p("if ("); P.p(v_guess); P.p("!=null && "); + P.p(v_guess); P.p("!="); P.p(v_s); + P.p(" && !"); P.p(v_guess); P.p(".equals("); P.p(v_s); P.p(")) "); + P.p(v_id); P.p(" = "); P.p(default_value); P.p(";"); P.nl(); + + // Add break at end of block to suppress warning for unused label + P.indent(indent_level + 1); + P.p("break "); P.p(v_switch_label); P.p(";"); P.nl(); + + P.line(indent_level, "}"); + } + + private void generate_length_switch(int begin, int end, int indent_level) { + + sort_pairs(begin, end, -1); + + check_all_is_different(begin, end); + + int lengths_count = count_different_lengths(begin, end); + + columns = new int[pairs[end - 1].idLength]; + + boolean use_if; + if (lengths_count <= use_if_threshold) { + use_if = true; + if (lengths_count != 1) { + P.indent(indent_level); + P.p("int "); P.p(v_s); P.p(v_length_suffix); + P.p(" = "); P.p(v_s); P.p(".length();"); + P.nl(); + } + } + else { + use_if = false; + P.indent(indent_level); + P.p(v_label); P.p(": switch ("); + P.p(v_s); P.p(".length()) {"); + P.nl(); + } + + int same_length_begin = begin; + int cur_l = pairs[begin].idLength, l = 0; + for (int i = begin;;) { + ++i; + if (i == end || (l = pairs[i].idLength) != cur_l) { + int next_indent; + if (use_if) { + P.indent(indent_level); + if (same_length_begin != begin) { P.p("else "); } + P.p("if ("); + if (lengths_count == 1) { + P.p(v_s); P.p(".length()=="); + } + else { + P.p(v_s); P.p(v_length_suffix); P.p("=="); + } + P.p(cur_l); + P.p(") {"); + next_indent = indent_level + 1; + } + else { + P.indent(indent_level); + P.p("case "); P.p(cur_l); P.p(":"); + next_indent = indent_level + 1; + } + generate_letter_switch + (same_length_begin, i, next_indent, !use_if, use_if); + if (use_if) { + P.p("}"); P.nl(); + } + else { + P.p("break "); P.p(v_label); P.p(";"); P.nl(); + } + + if (i == end) { break; } + same_length_begin = i; + cur_l = l; + } + } + + if (!use_if) { + P.indent(indent_level); P.p("}"); P.nl(); + } + + } + + private void generate_letter_switch + (int begin, int end, + int indent_level, boolean label_was_defined, boolean inside_if) + { + int L = pairs[begin].idLength; + + for (int i = 0; i != L; ++i) { + columns[i] = i; + } + + generate_letter_switch_r + (begin, end, L, indent_level, label_was_defined, inside_if); + } + + + private boolean generate_letter_switch_r + (int begin, int end, int L, + int indent_level, boolean label_was_defined, boolean inside_if) + { + boolean next_is_unreachable = false; + if (begin + 1 == end) { + P.p(' '); + IdValuePair pair = pairs[begin]; + if (L > char_tail_test_threshold) { + P.p(v_guess); P.p("="); P.qstring(pair.id); P.p(";"); + P.p(v_id); P.p("="); P.p(pair.value); P.p(";"); + } + else { + if (L == 0) { + next_is_unreachable = true; + P.p(v_id); P.p("="); P.p(pair.value); + P.p("; break "); P.p(v_switch_label); P.p(";"); + } + else { + P.p("if ("); + int column = columns[0]; + P.p(v_s); P.p(".charAt("); P.p(column); P.p(")=="); + P.qchar(pair.id.charAt(column)); + for (int i = 1; i != L; ++i) { + P.p(" && "); + column = columns[i]; + P.p(v_s); P.p(".charAt("); P.p(column); P.p(")=="); + P.qchar(pair.id.charAt(column)); + } + P.p(") {"); + P.p(v_id); P.p("="); P.p(pair.value); + P.p("; break "); P.p(v_switch_label); P.p(";}"); + } + } + P.p(' '); + return next_is_unreachable; + } + + int max_column_index = find_max_different_column(begin, end, L); + int max_column = columns[max_column_index]; + int count = count_different_chars(begin, end, max_column); + + columns[max_column_index] = columns[L - 1]; + + if (inside_if) { P.nl(); P.indent(indent_level); } + else { P.p(' '); } + + boolean use_if; + if (count <= use_if_threshold) { + use_if = true; + c_was_defined = true; + P.p(v_c); P.p("="); P.p(v_s); + P.p(".charAt("); P.p(max_column); P.p(");"); + } + else { + use_if = false; + if (!label_was_defined) { + label_was_defined = true; + P.p(v_label); P.p(": "); + } + P.p("switch ("); P.p(v_s); + P.p(".charAt("); P.p(max_column); P.p(")) {"); + } + + int same_char_begin = begin; + int cur_ch = pairs[begin].id.charAt(max_column), ch = 0; + for (int i = begin;;) { + ++i; + if (i == end || (ch = pairs[i].id.charAt(max_column)) != cur_ch) { + int next_indent; + if (use_if) { + P.nl(); P.indent(indent_level); + if (same_char_begin != begin) { P.p("else "); } + P.p("if ("); P.p(v_c); P.p("=="); + P.qchar(cur_ch); P.p(") {"); + next_indent = indent_level + 1; + } + else { + P.nl(); P.indent(indent_level); + P.p("case "); P.qchar(cur_ch); P.p(":"); + next_indent = indent_level + 1; + } + boolean after_unreachable = generate_letter_switch_r + (same_char_begin, i, L - 1, + next_indent, label_was_defined, use_if); + if (use_if) { + P.p("}"); + } + else { + if (!after_unreachable) { + P.p("break "); P.p(v_label); P.p(";"); + } + } + if (i == end) { break; } + same_char_begin = i; + cur_ch = ch; + } + } + + if (use_if) { + P.nl(); + if (inside_if) { P.indent(indent_level - 1); } + else { P.indent(indent_level); } + } + else { + P.nl(); P.indent(indent_level); P.p("}"); + if (inside_if) { P.nl(); P.indent(indent_level - 1);} + else { P.p(' '); } + } + + columns[max_column_index] = max_column; + + return next_is_unreachable; + } + + + private int count_different_lengths(int begin, int end) { + int lengths_count = 0; + int cur_l = -1; + for (; begin != end; ++begin) { + int l = pairs[begin].idLength; + if (cur_l != l) { + ++lengths_count; cur_l = l; + } + } + return lengths_count; + } + + private int find_max_different_column(int begin, int end, int L) { + int max_count = 0; + int max_index = 0; + + for (int i = 0; i != L; ++i) { + int column = columns[i]; + sort_pairs(begin, end, column); + int count = count_different_chars(begin, end, column); + if (count == end - begin) { return i; } + if (max_count < count) { + max_count = count; + max_index = i; + } + } + + if (max_index != L - 1) { + sort_pairs(begin, end, columns[max_index]); + } + + return max_index; + } + + private int count_different_chars(int begin, int end, int column) { + int chars_count = 0; + int cur_ch = -1; + for (; begin != end; ++begin) { + int ch = pairs[begin].id.charAt(column); + if (ch != cur_ch) { + ++chars_count; cur_ch = ch; + } + } + return chars_count; + } + + private void check_all_is_different(int begin, int end) { + if (begin != end) { + IdValuePair prev = pairs[begin]; + while (++begin != end) { + IdValuePair current = pairs[begin]; + if (prev.id.equals(current.id)) { + throw on_same_pair_fail(prev, current); + } + prev = current; + } + } + } + + private EvaluatorException on_same_pair_fail(IdValuePair a, IdValuePair b) { + int line1 = a.getLineNumber(), line2 = b.getLineNumber(); + if (line2 > line1) { int tmp = line1; line1 = line2; line2 = tmp; } + String error_text = ToolErrorReporter.getMessage( + "msg.idswitch.same_string", a.id, new Integer(line2)); + return R.runtimeError(error_text, source_file, line1, null, 0); + } + + private void sort_pairs(int begin, int end, int comparator) { + heap4Sort(pairs, begin, end - begin, comparator); + } + + private static boolean bigger + (IdValuePair a, IdValuePair b, int comparator) + { + if (comparator < 0) { + // For length selection switch it is enough to compare just length, + // but to detect same strings full comparison is essential + //return a.idLength > b.idLength; + int diff = a.idLength - b.idLength; + if (diff != 0) { return diff > 0; } + return a.id.compareTo(b.id) > 0; + } + else { + return a.id.charAt(comparator) > b.id.charAt(comparator); + } + } + + private static void heap4Sort + (IdValuePair[] array, int offset, int size, int comparator) + { + if (size <= 1) { return; } + makeHeap4(array, offset, size, comparator); + while (size > 1) { + --size; + IdValuePair v1 = array[offset + size]; + IdValuePair v2 = array[offset + 0]; + array[offset + size] = v2; + array[offset + 0] = v1; + heapify4(array, offset, size, 0, comparator); + } + } + + private static void makeHeap4 + (IdValuePair[] array, int offset, int size, int comparator) + { + for (int i = ((size + 2) >> 2); i != 0;) { + --i; + heapify4(array, offset, size, i, comparator); + } + } + + private static void heapify4 + (IdValuePair[] array, int offset, int size, int i, int comparator) + { + int new_i1, new_i2, new_i3; + IdValuePair i_val = array[offset + i]; + for (;;) { + int base = (i << 2); + new_i1 = base | 1; + new_i2 = base | 2; + new_i3 = base | 3; + int new_i4 = base + 4; + if (new_i4 >= size) { break; } + IdValuePair val1 = array[offset + new_i1]; + IdValuePair val2 = array[offset + new_i2]; + IdValuePair val3 = array[offset + new_i3]; + IdValuePair val4 = array[offset + new_i4]; + if (bigger(val2, val1, comparator)) { + val1 = val2; new_i1 = new_i2; + } + if (bigger(val4, val3, comparator)) { + val3 = val4; new_i3 = new_i4; + } + if (bigger(val3, val1, comparator)) { + val1 = val3; new_i1 = new_i3; + } + if (bigger(i_val, val1, comparator)) { return; } + array[offset + i] = val1; + array[offset + new_i1] = i_val; + i = new_i1; + } + if (new_i1 < size) { + IdValuePair val1 = array[offset + new_i1]; + if (new_i2 != size) { + IdValuePair val2 = array[offset + new_i2]; + if (bigger(val2, val1, comparator)) { + val1 = val2; new_i1 = new_i2; + } + if (new_i3 != size) { + IdValuePair val3 = array[offset + new_i3]; + if (bigger(val3, val1, comparator)) { + val1 = val3; new_i1 = new_i3; + } + } + } + if (bigger(val1, i_val, comparator)) { + array[offset + i] = val1; + array[offset + new_i1] = i_val; + } + } + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/jsc/Main.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/jsc/Main.java new file mode 100644 index 0000000..2da4f2f --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/jsc/Main.java @@ -0,0 +1,395 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Christine Begle + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.jsc; + +import java.io.*; +import java.util.*; +import org.mozilla.javascript.*; +import org.mozilla.javascript.optimizer.ClassCompiler; +import org.mozilla.javascript.tools.ToolErrorReporter; + +/** + * @author Norris Boyd + */ +public class Main { + + /** + * Main entry point. + * + * Process arguments as would a normal Java program. + * Then set up the execution environment and begin to + * compile scripts. + */ + public static void main(String args[]) + { + Main main = new Main(); + args = main.processOptions(args); + if (args == null) { + if (main.printHelp) { + System.out.println(ToolErrorReporter.getMessage( + "msg.jsc.usage", Main.class.getName())); + System.exit(0); + } + System.exit(1); + } + if (!main.reporter.hasReportedError()) { + main.processSource(args); + } + } + + public Main() + { + reporter = new ToolErrorReporter(true); + compilerEnv = new CompilerEnvirons(); + compilerEnv.setErrorReporter(reporter); + compiler = new ClassCompiler(compilerEnv); + } + + /** + * Parse arguments. + * + */ + public String[] processOptions(String args[]) + { + targetPackage = ""; // default to no package + compilerEnv.setGenerateDebugInfo(false); // default to no symbols + for (int i=0; i < args.length; i++) { + String arg = args[i]; + if (!arg.startsWith("-")) { + int tail = args.length - i; + if (targetName != null && tail > 1) { + addError("msg.multiple.js.to.file", targetName); + return null; + } + String[] result = new String[tail]; + for (int j = 0; j != tail; ++j) { + result[j] = args[i + j]; + } + return result; + } + if (arg.equals("-help") || arg.equals("-h") + || arg.equals("--help")) + { + printHelp = true; + return null; + } + + try { + if (arg.equals("-version") && ++i < args.length) { + int version = Integer.parseInt(args[i]); + compilerEnv.setLanguageVersion(version); + continue; + } + if ((arg.equals("-opt") || arg.equals("-O")) && + ++i < args.length) + { + int optLevel = Integer.parseInt(args[i]); + compilerEnv.setOptimizationLevel(optLevel); + continue; + } + } + catch (NumberFormatException e) { + badUsage(args[i]); + return null; + } + if (arg.equals("-nosource")) { + compilerEnv.setGeneratingSource(false); + continue; + } + if (arg.equals("-debug") || arg.equals("-g")) { + compilerEnv.setGenerateDebugInfo(true); + continue; + } + if (arg.equals("-main-method-class") && ++i < args.length) { + compiler.setMainMethodClass(args[i]); + continue; + } + if (arg.equals("-o") && ++i < args.length) { + String name = args[i]; + int end = name.length(); + if (end == 0 + || !Character.isJavaIdentifierStart(name.charAt(0))) + { + addError("msg.invalid.classfile.name", name); + continue; + } + for (int j = 1; j < end; j++) { + char c = name.charAt(j); + if (!Character.isJavaIdentifierPart(c)) { + if (c == '.') { + // check if it is the dot in .class + if (j == end - 6 && name.endsWith(".class")) { + name = name.substring(0, j); + break; + } + } + addError("msg.invalid.classfile.name", name); + break; + } + } + targetName = name; + continue; + } + if (arg.equals("-observe-instruction-count")) { + compilerEnv.setGenerateObserverCount(true); + } + if (arg.equals("-package") && ++i < args.length) { + String pkg = args[i]; + int end = pkg.length(); + for (int j = 0; j != end; ++j) { + char c = pkg.charAt(j); + if (Character.isJavaIdentifierStart(c)) { + for (++j; j != end; ++j) { + c = pkg.charAt(j); + if (!Character.isJavaIdentifierPart(c)) { + break; + } + } + if (j == end) { + break; + } + if (c == '.' && j != end - 1) { + continue; + } + } + addError("msg.package.name", targetPackage); + return null; + } + targetPackage = pkg; + continue; + } + if (arg.equals("-extends") && ++i < args.length) { + String targetExtends = args[i]; + Class superClass; + try { + superClass = Class.forName(targetExtends); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + compiler.setTargetExtends(superClass); + continue; + } + if (arg.equals("-implements") && ++i < args.length) { + // TODO: allow for multiple comma-separated interfaces. + String targetImplements = args[i]; + StringTokenizer st = new StringTokenizer(targetImplements, + ","); + Vector v = new Vector(); + while (st.hasMoreTokens()) { + String className = st.nextToken(); + try { + v.addElement(Class.forName(className)); + } catch (ClassNotFoundException e) { + throw new Error(e.toString()); // TODO: better error + } + } + Class[] implementsClasses = new Class[v.size()]; + v.copyInto(implementsClasses); + compiler.setTargetImplements(implementsClasses); + continue; + } + if (arg.equals("-d") && ++i < args.length) { + destinationDir = args[i]; + continue; + } + badUsage(arg); + return null; + } + // no file name + p(ToolErrorReporter.getMessage("msg.no.file")); + return null; + } + /** + * Print a usage message. + */ + private static void badUsage(String s) { + System.err.println(ToolErrorReporter.getMessage( + "msg.jsc.bad.usage", Main.class.getName(), s)); + } + + /** + * Compile JavaScript source. + * + */ + public void processSource(String[] filenames) + { + for (int i = 0; i != filenames.length; ++i) { + String filename = filenames[i]; + if (!filename.endsWith(".js")) { + addError("msg.extension.not.js", filename); + return; + } + File f = new File(filename); + String source = readSource(f); + if (source == null) return; + + String mainClassName = targetName; + if (mainClassName == null) { + String name = f.getName(); + String nojs = name.substring(0, name.length() - 3); + mainClassName = getClassName(nojs); + } + if (targetPackage.length() != 0) { + mainClassName = targetPackage+"."+mainClassName; + } + + Object[] compiled + = compiler.compileToClassFiles(source, filename, 1, + mainClassName); + if (compiled == null || compiled.length == 0) { + return; + } + + File targetTopDir = null; + if (destinationDir != null) { + targetTopDir = new File(destinationDir); + } else { + String parent = f.getParent(); + if (parent != null) { + targetTopDir = new File(parent); + } + } + for (int j = 0; j != compiled.length; j += 2) { + String className = (String)compiled[j]; + byte[] bytes = (byte[])compiled[j + 1]; + File outfile = getOutputFile(targetTopDir, className); + try { + FileOutputStream os = new FileOutputStream(outfile); + try { + os.write(bytes); + } finally { + os.close(); + } + } catch (IOException ioe) { + addFormatedError(ioe.toString()); + } + } + } + } + + private String readSource(File f) + { + if (!f.exists()) { + addError("msg.jsfile.not.found", f.getAbsolutePath()); + return null; + } + try { + Reader in = new FileReader(f); + try { + return Kit.readReader(in); + } finally { + in.close(); + } + } catch (FileNotFoundException ex) { + addError("msg.couldnt.open", f.getAbsolutePath()); + } catch (IOException ioe) { + addFormatedError(ioe.toString()); + } + return null; + } + + private File getOutputFile(File parentDir, String className) + { + String path = className.replace('.', File.separatorChar); + path = path.concat(".class"); + File f = new File(parentDir, path); + String dirPath = f.getParent(); + if (dirPath != null) { + File dir = new File(dirPath); + if (!dir.exists()) { + dir.mkdirs(); + } + } + return f; + } + + /** + * Verify that class file names are legal Java identifiers. Substitute + * illegal characters with underscores, and prepend the name with an + * underscore if the file name does not begin with a JavaLetter. + */ + + String getClassName(String name) { + char[] s = new char[name.length()+1]; + char c; + int j = 0; + + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + s[j++] = '_'; + } + for (int i=0; i < name.length(); i++, j++) { + c = name.charAt(i); + if ( Character.isJavaIdentifierPart(c) ) { + s[j] = c; + } else { + s[j] = '_'; + } + } + return (new String(s)).trim(); + } + + private static void p(String s) { + System.out.println(s); + } + + private void addError(String messageId, String arg) + { + String msg; + if (arg == null) { + msg = ToolErrorReporter.getMessage(messageId); + } else { + msg = ToolErrorReporter.getMessage(messageId, arg); + } + addFormatedError(msg); + } + + private void addFormatedError(String message) + { + reporter.error(message, null, -1, null, -1); + } + + private boolean printHelp; + private ToolErrorReporter reporter; + private CompilerEnvirons compilerEnv; + private ClassCompiler compiler; + private String targetName; + private String targetPackage; + private String destinationDir; +} + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/resources/Messages.properties b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/resources/Messages.properties new file mode 100644 index 0000000..76615e9 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/resources/Messages.properties @@ -0,0 +1,268 @@ +# +# JavaScript tools messages file. +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Rhino code, released +# May 6, 1999. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1997-1999 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# the GNU General Public License Version 2 or later (the "GPL"), in which +# case the provisions of the GPL are applicable instead of those above. If +# you wish to allow use of your version of this file only under the terms of +# the GPL and not to allow others to use your version of this file under the +# MPL, indicate your decision by deleting the provisions above and replacing +# them with the notice and other provisions required by the GPL. If you do +# not delete the provisions above, a recipient may use your version of this +# file under either the MPL or the GPL. +# +# ***** END LICENSE BLOCK ***** + +msg.expected.string.arg =\ + Expected a string argument. + +msg.class.not.found =\ + Class "{0}" not found. + +msg.couldnt.open =\ + Couldn''t open file "{0}". + +msg.couldnt.open.url =\ + Couldn''t open URL "{0}: {1}". + +msg.no-opt =\ + Must have the org.mozilla.javascript.optimizer package available \ + to compile to class files. + +msg.shell.invalid =\ + Invalid option "{0}" + +msg.shell.usage =\ + Usage: java {0} [options...] [files]\n\ + Valid options are:\n\ + \ -?, -help Displays help messages.\n\ + \ -w Enable warnings.\n\ + \ -version 100|110|120|130|140|150|160|170\n\ + \ Set a specific language version.\n\ + \ -opt [-1|0-9] Set optimization level.\n\ + \ -f script-filename Execute script file.\n\ + \ -e script-source Evaluate inline script.\n\ + \ -debug Generate debug code.\n\ + \ -strict Enable strict mode warnings.\n\ + \ -fatal-warnings Treat warnings as errors. + +msg.help =\ + \n\ + Command Description \n\ + ======= =========== \n\ + help() Display usage and help messages. \n\ + defineClass(className) Define an extension using the Java class \n\ + \ named with the string argument. \n\ + \ Uses ScriptableObject.defineClass(). \n\ + load(["foo.js", ...]) Load JavaScript source files named by \n\ + \ string arguments. \n\ + loadClass(className) Load a class named by a string argument. \n\ + \ The class must be a script compiled to a \n\ + \ class file. \n\ + print([expr ...]) Evaluate and print expressions. \n\ + quit() Quit the shell. \n\ + version([number]) Get or set the JavaScript version number. \n\ + gc() Runs the garbage collector.\n\ + spawn(arg) Evaluate function or scriptname on a new thread \n\ + sync(function) Creates a synchronized version of the function, \n\ + \ where the synchronization object is "this" \n\ + readFile(fileName [, encoding])\n\ + \ Returns the content of the file as a string. \n\ + \ Encoding of the string can be optionally specified. \n\ + readUrl(url [, encoding]) \n\ + \ Similar to readFile, reads the contents of the url.\n\ + runCommand(name ...) Runs a specified shell command. Additional arguments are \n\ + \ passed to the command \n\ + seal(args ...) Seals the supplied objects \n\ + toint32(arg) Converts the argument into a 32-bit integer \n\ + serialize(obj, fileName) \n\ + \ Serializes an object and saves it to a file \n\ + deserialise(fileName) Reconstructs a serialized object \n\ + environment Returns the current environment object \n\ + history Displays the shell command history \n\ + \n\ + For full description of all available commands see shell.html in\n\ + the docs directory of Rhino distribution.\n\ + +msg.warning =\ + warning: {0} + +msg.format1 =\ + {0} + +msg.format2 =\ + line {0}: {1} + +msg.format3 =\ + "{0}", line {1}: {2} + +msg.uncaughtJSException =\ + exception from uncaught JavaScript throw: {0} + +msg.uncaughtEcmaError =\ + uncaught JavaScript runtime exception: {0} + +msg.jsc.bad.usage =\ + Didn''t understand "{1}". \n\ + For more information, try java {0} -h + +msg.jsc.usage =\ +Usage: java {0} [OPTION]... SOURCE...\n\ +Valid options are: \n\ +\ -version VERSION Use the specified language version.\n\ +\ VERSION should be one of 100|110|120|130|140|150|160|170.\n\ +\ -opt LEVEL Use optimization with the specified level.\n\ +\ LEVEL should be one of 0..9.\n\ +\ -debug, -g Include debug information.\n\ +\ -nosource Do not include source to function objects.\n\ +\ It makes f.toString() useless and violates ECMAScript\n\ +\ standard but makes generated classes smaller and\n\ +\ saves memory.\n\ +\ -o CLASSNAME Use specified name as the last component of the main\n\ +\ generated class name. When specified, only one script\n\ +\ SOURCE is allowed. If omitted, it defaults to source\n\ +\ name with stripped .js suffix.\n\ +\ -package PACKAGE Place generated classes in the specified package.\n\ +\ -d DIRECTORY Use DIRECTORY as destination directory for generated\n\ +\ classes. If omitted, it defaults to parent directory\n\ +\ of SOURCE.\n\ +\ -extends CLASS The main generated class will extend the specified\n\ +\ class CLASS.\n\ +\ -implements INTERFACE1,INTERFACE2,... The main generated class will\n\ +\ implement the specified list of interfaces.\n\ +\ -main-method-class CLASS Specify the class name used for main method \n\ +\ implementation. The class must have a method matching\n\ +\ "public static void main(Script sc, String[] args)"\n\ +\ -observe-instruction-count Generate code that contains callbacks to \n\ +\ accumulate counts of executed instructions. Code \n\ +\ compiled with this flag can be monitored using \n\ +\ Context.setInstructionObserverThreshold. \n\ +\ -help, --help, -h Print this help and exit.\n\ + + +msg.no.file =\ + A file name must be specified to compile. + +msg.invalid.classfile.name =\ + File "{0}" is not a valid class file name. + +msg.extension.not.js =\ + File "{0}" is not a valid js file name. + +msg.jsfile.not.found=\ + File "{0}" not found. + +msg.multiple.js.to.file =\ + Cannot compile multiple js files to "{0}". + +msg.package.name =\ + "{0}" is not a valid package name. + +msg.spawn.args =\ + Argument to spawn() must be a function or script. + +msg.must.implement.Script =\ + Argument to loadClass() must be the name of a class that implements \ + the Script interface. Class files generated by compiling scripts \ + will implement Script. + +msg.runCommand.bad.args =\ + The first argument to runCommand must be a command name. + +msg.runCommand.bad.env =\ + A value of the env property of option object for runCommnad must be an \ + object. + +msg.shell.seal.not.object =\ + seal function can only be applied to objects + +msg.shell.seal.not.scriptable =\ + seal function supports only sealing of ScriptableObject instances + +msg.shell.readFile.bad.args =\ + readFile require at least file path to be specified + +msg.shell.readUrl.bad.args =\ + readUrl require at least file path to be specified + +msg.shell.bad.function.scope =\ + Wrong scope object for shell function: {0} + +msg.idswitch.same_string =\ + The string {0} is used second time in the switch code. \ + Previous occurrence was at line {1} + +msg.idswitch.file_end_in_switch =\ + End of file inside tag {0} + +msg.idswitch.bad_tag_order =\ + String switch tag {0} is not allowed here + +msg.idswitch.no_end_with_value =\ + End for tag {0} can not contain value + +msg.idswitch.no_value_allowed =\ + Tag {0} can not contain value + +msg.idswitch.no_end_usage =\ + Tag {0} can not be used as end tag + +msg.idswitch.no_file_argument =\ + File argument should be given + +msg.idswitch.too_many_arguments =\ + Too many arguments are given + +msg.idswitch.bad_option =\ + Invalid option {0} + +msg.idswitch.bad_option_char =\ + Invalid option letter {0} + +msg.idswitch.bad_invocation =\ +StringIdMap: {0}\n\ +For more information, try\n\ +java org.mozilla.javascript.tools.idswitch.StringIdMap --help + +msg.idswitch.io_error =\ +StringIdMap: IO error, {0} + +msg.idswitch.usage = \ +Usage: java org.mozilla.javascript.tools.idswitch.StringIdMap [OPTIONS] JAVA_SOURCE_FILE\n\ +Generates efficient string dispatch code in JAVA_SOURCE_FILE.\n\ +The resulting Java source fragment replaces the old dispatch code.\n\ +If JAVA_SOURCE_FILE is -, standard input is used for Java source and the\n\ +result is sent to standard output.\n\ +\n\ +\ -h, --help display this help and exit\n\ +\ --version display version information and exit\n\ +\n\ +Note: the original file will be overwritten without any backup actions\n\ +\ and all code inside #generated# tag will be replaced by new one. + +msg.idswitch.version = \ +org.mozilla.javascript.tools.idswitch.StringIdMap version 0.2 + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java new file mode 100644 index 0000000..08cac62 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java @@ -0,0 +1,300 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * See Beyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.shell; +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.Document; +import javax.swing.text.Segment; + +class ConsoleWrite implements Runnable { + private ConsoleTextArea textArea; + private String str; + + public ConsoleWrite(ConsoleTextArea textArea, String str) { + this.textArea = textArea; + this.str = str; + } + + public void run() { + textArea.write(str); + } +} + +class ConsoleWriter extends java.io.OutputStream { + + private ConsoleTextArea textArea; + private StringBuffer buffer; + + public ConsoleWriter(ConsoleTextArea textArea) { + this.textArea = textArea; + buffer = new StringBuffer(); + } + + public synchronized void write(int ch) { + buffer.append((char)ch); + if(ch == '\n') { + flushBuffer(); + } + } + + public synchronized void write (char[] data, int off, int len) { + for(int i = off; i < len; i++) { + buffer.append(data[i]); + if(data[i] == '\n') { + flushBuffer(); + } + } + } + + public synchronized void flush() { + if (buffer.length() > 0) { + flushBuffer(); + } + } + + public void close () { + flush(); + } + + private void flushBuffer() { + String str = buffer.toString(); + buffer.setLength(0); + SwingUtilities.invokeLater(new ConsoleWrite(textArea, str)); + } +} + +public class ConsoleTextArea + extends JTextArea implements KeyListener, DocumentListener +{ + static final long serialVersionUID = 8557083244830872961L; + + private ConsoleWriter console1; + private ConsoleWriter console2; + private PrintStream out; + private PrintStream err; + private PrintWriter inPipe; + private PipedInputStream in; + private java.util.Vector history; + private int historyIndex = -1; + private int outputMark = 0; + + public void select(int start, int end) { + requestFocus(); + super.select(start, end); + } + + public ConsoleTextArea(String[] argv) { + super(); + history = new java.util.Vector(); + console1 = new ConsoleWriter(this); + console2 = new ConsoleWriter(this); + out = new PrintStream(console1); + err = new PrintStream(console2); + PipedOutputStream outPipe = new PipedOutputStream(); + inPipe = new PrintWriter(outPipe); + in = new PipedInputStream(); + try { + outPipe.connect(in); + } catch(IOException exc) { + exc.printStackTrace(); + } + getDocument().addDocumentListener(this); + addKeyListener(this); + setLineWrap(true); + setFont(new Font("Monospaced", 0, 12)); + } + + + synchronized void returnPressed() { + Document doc = getDocument(); + int len = doc.getLength(); + Segment segment = new Segment(); + try { + doc.getText(outputMark, len - outputMark, segment); + } catch(javax.swing.text.BadLocationException ignored) { + ignored.printStackTrace(); + } + if(segment.count > 0) { + history.addElement(segment.toString()); + } + historyIndex = history.size(); + inPipe.write(segment.array, segment.offset, segment.count); + append("\n"); + outputMark = doc.getLength(); + inPipe.write("\n"); + inPipe.flush(); + console1.flush(); + } + + public void eval(String str) { + inPipe.write(str); + inPipe.write("\n"); + inPipe.flush(); + console1.flush(); + } + + public void keyPressed(KeyEvent e) { + int code = e.getKeyCode(); + if(code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_LEFT) { + if(outputMark == getCaretPosition()) { + e.consume(); + } + } else if(code == KeyEvent.VK_HOME) { + int caretPos = getCaretPosition(); + if(caretPos == outputMark) { + e.consume(); + } else if(caretPos > outputMark) { + if(!e.isControlDown()) { + if(e.isShiftDown()) { + moveCaretPosition(outputMark); + } else { + setCaretPosition(outputMark); + } + e.consume(); + } + } + } else if(code == KeyEvent.VK_ENTER) { + returnPressed(); + e.consume(); + } else if(code == KeyEvent.VK_UP) { + historyIndex--; + if(historyIndex >= 0) { + if(historyIndex >= history.size()) { + historyIndex = history.size() -1; + } + if(historyIndex >= 0) { + String str = (String)history.elementAt(historyIndex); + int len = getDocument().getLength(); + replaceRange(str, outputMark, len); + int caretPos = outputMark + str.length(); + select(caretPos, caretPos); + } else { + historyIndex++; + } + } else { + historyIndex++; + } + e.consume(); + } else if(code == KeyEvent.VK_DOWN) { + int caretPos = outputMark; + if(history.size() > 0) { + historyIndex++; + if(historyIndex < 0) {historyIndex = 0;} + int len = getDocument().getLength(); + if(historyIndex < history.size()) { + String str = (String)history.elementAt(historyIndex); + replaceRange(str, outputMark, len); + caretPos = outputMark + str.length(); + } else { + historyIndex = history.size(); + replaceRange("", outputMark, len); + } + } + select(caretPos, caretPos); + e.consume(); + } + } + + public void keyTyped(KeyEvent e) { + int keyChar = e.getKeyChar(); + if(keyChar == 0x8 /* KeyEvent.VK_BACK_SPACE */) { + if(outputMark == getCaretPosition()) { + e.consume(); + } + } else if(getCaretPosition() < outputMark) { + setCaretPosition(outputMark); + } + } + + public synchronized void keyReleased(KeyEvent e) { + } + + public synchronized void write(String str) { + insert(str, outputMark); + int len = str.length(); + outputMark += len; + select(outputMark, outputMark); + } + + public synchronized void insertUpdate(DocumentEvent e) { + int len = e.getLength(); + int off = e.getOffset(); + if(outputMark > off) { + outputMark += len; + } + } + + public synchronized void removeUpdate(DocumentEvent e) { + int len = e.getLength(); + int off = e.getOffset(); + if(outputMark > off) { + if(outputMark >= off + len) { + outputMark -= len; + } else { + outputMark = off; + } + } + } + + public synchronized void postUpdateUI() { + // this attempts to cleanup the damage done by updateComponentTreeUI + requestFocus(); + setCaret(getCaret()); + select(outputMark, outputMark); + } + + public synchronized void changedUpdate(DocumentEvent e) { + } + + + public InputStream getIn() { + return in; + } + + public PrintStream getOut() { + return out; + } + + public PrintStream getErr() { + return err; + } + +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Environment.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Environment.java new file mode 100644 index 0000000..19904b9 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Environment.java @@ -0,0 +1,141 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + Environment.java + + Wraps java.lang.System properties. + + by Patrick C. Beard + */ + +package org.mozilla.javascript.tools.shell; + +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.ScriptableObject; + +import java.util.Vector; +import java.util.Enumeration; +import java.util.Properties; + +/** + * Environment, intended to be instantiated at global scope, provides + * a natural way to access System properties from JavaScript. + * + * @author Patrick C. Beard + */ +public class Environment extends ScriptableObject +{ + static final long serialVersionUID = -430727378460177065L; + + private Environment thePrototypeInstance = null; + + public static void defineClass(ScriptableObject scope) { + try { + ScriptableObject.defineClass(scope, Environment.class); + } catch (Exception e) { + throw new Error(e.getMessage()); + } + } + + public String getClassName() { + return "Environment"; + } + + public Environment() { + if (thePrototypeInstance == null) + thePrototypeInstance = this; + } + + public Environment(ScriptableObject scope) { + setParentScope(scope); + Object ctor = ScriptRuntime.getTopLevelProp(scope, "Environment"); + if (ctor != null && ctor instanceof Scriptable) { + Scriptable s = (Scriptable) ctor; + setPrototype((Scriptable) s.get("prototype", s)); + } + } + + public boolean has(String name, Scriptable start) { + if (this == thePrototypeInstance) + return super.has(name, start); + + return (System.getProperty(name) != null); + } + + public Object get(String name, Scriptable start) { + if (this == thePrototypeInstance) + return super.get(name, start); + + String result = System.getProperty(name); + if (result != null) + return ScriptRuntime.toObject(getParentScope(), result); + else + return Scriptable.NOT_FOUND; + } + + public void put(String name, Scriptable start, Object value) { + if (this == thePrototypeInstance) + super.put(name, start, value); + else + System.getProperties().put(name, ScriptRuntime.toString(value)); + } + + private Object[] collectIds() { + Properties props = System.getProperties(); + Enumeration names = props.propertyNames(); + Vector keys = new Vector(); + while (names.hasMoreElements()) + keys.addElement(names.nextElement()); + Object[] ids = new Object[keys.size()]; + keys.copyInto(ids); + return ids; + } + + public Object[] getIds() { + if (this == thePrototypeInstance) + return super.getIds(); + return collectIds(); + } + + public Object[] getAllIds() { + if (this == thePrototypeInstance) + return super.getAllIds(); + return collectIds(); + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Global.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Global.java new file mode 100644 index 0000000..fdb8f4e --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Global.java @@ -0,0 +1,1038 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Beard + * Igor Bukanov + * Norris Boyd + * Rob Ginda + * Kurt Westerfeld + * Matthias Radestock + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import java.io.*; +import java.net.*; +import java.lang.reflect.*; +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.ToolErrorReporter; +import org.mozilla.javascript.serialize.*; + +/** + * This class provides for sharing functions across multiple threads. + * This is of particular interest to server applications. + * + * @author Norris Boyd + */ +public class Global extends ImporterTopLevel +{ + static final long serialVersionUID = 4029130780977538005L; + + NativeArray history; + private InputStream inStream; + private PrintStream outStream; + private PrintStream errStream; + private boolean sealedStdLib = false; + boolean initialized; + private QuitAction quitAction; + private String[] prompts = { "js> ", " > " }; + + public Global() + { + } + + public Global(Context cx) + { + init(cx); + } + + public boolean isInitialized() { + return initialized; + } + + /** + * Set the action to call from quit(). + */ + public void initQuitAction(QuitAction quitAction) + { + if (quitAction == null) + throw new IllegalArgumentException("quitAction is null"); + if (this.quitAction != null) + throw new IllegalArgumentException("The method is once-call."); + + this.quitAction = quitAction; + } + + public void init(ContextFactory factory) + { + factory.call(new ContextAction() { + public Object run(Context cx) + { + init(cx); + return null; + } + }); + } + + public void init(Context cx) + { + // Define some global functions particular to the shell. Note + // that these functions are not part of ECMA. + initStandardObjects(cx, sealedStdLib); + String[] names = { + "defineClass", + "deserialize", + "gc", + "help", + "load", + "loadClass", + "print", + "quit", + "readFile", + "readUrl", + "runCommand", + "seal", + "serialize", + "spawn", + "sync", + "toint32", + "version", + }; + defineFunctionProperties(names, Global.class, + ScriptableObject.DONTENUM); + + // Set up "environment" in the global scope to provide access to the + // System environment variables. + Environment.defineClass(this); + Environment environment = new Environment(this); + defineProperty("environment", environment, + ScriptableObject.DONTENUM); + + history = (NativeArray) cx.newArray(this, 0); + defineProperty("history", history, ScriptableObject.DONTENUM); + initialized = true; + } + + /** + * Print a help message. + * + * This method is defined as a JavaScript function. + */ + public static void help(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + PrintStream out = getInstance(funObj).getOut(); + out.println(ToolErrorReporter.getMessage("msg.help")); + } + + public static void gc(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + System.gc(); + } + + + /** + * Print the string values of its arguments. + * + * This method is defined as a JavaScript function. + * Note that its arguments are of the "varargs" form, which + * allows it to handle an arbitrary number of arguments + * supplied to the JavaScript function. + * + */ + public static Object print(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + PrintStream out = getInstance(funObj).getOut(); + for (int i=0; i < args.length; i++) { + if (i > 0) + out.print(" "); + + // Convert the arbitrary JavaScript value into a string form. + String s = Context.toString(args[i]); + + out.print(s); + } + out.println(); + return Context.getUndefinedValue(); + } + + /** + * Call embedding-specific quit action passing its argument as + * int32 exit code. + * + * This method is defined as a JavaScript function. + */ + public static void quit(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + Global global = getInstance(funObj); + if (global.quitAction != null) { + int exitCode = (args.length == 0 ? 0 + : ScriptRuntime.toInt32(args[0])); + global.quitAction.quit(cx, exitCode); + } + } + + /** + * Get and set the language version. + * + * This method is defined as a JavaScript function. + */ + public static double version(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + double result = cx.getLanguageVersion(); + if (args.length > 0) { + double d = Context.toNumber(args[0]); + cx.setLanguageVersion((int) d); + } + return result; + } + + /** + * Load and execute a set of JavaScript source files. + * + * This method is defined as a JavaScript function. + * + */ + public static void load(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + { + for (int i = 0; i < args.length; i++) { + Main.processFile(cx, thisObj, Context.toString(args[i])); + } + } + + /** + * Load a Java class that defines a JavaScript object using the + * conventions outlined in ScriptableObject.defineClass. + *

+ * This method is defined as a JavaScript function. + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @see org.mozilla.javascript.ScriptableObject#defineClass(Scriptable,Class) + */ + public static void defineClass(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + Class clazz = getClass(args); + ScriptableObject.defineClass(thisObj, clazz); + } + + /** + * Load and execute a script compiled to a class file. + *

+ * This method is defined as a JavaScript function. + * When called as a JavaScript function, a single argument is + * expected. This argument should be the name of a class that + * implements the Script interface, as will any script + * compiled by jsc. + * + * @exception IllegalAccessException if access is not available + * to the class + * @exception InstantiationException if unable to instantiate + * the named class + */ + public static void loadClass(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + throws IllegalAccessException, InstantiationException + { + Class clazz = getClass(args); + if (!Script.class.isAssignableFrom(clazz)) { + throw reportRuntimeError("msg.must.implement.Script"); + } + Script script = (Script) clazz.newInstance(); + script.exec(cx, thisObj); + } + + private static Class getClass(Object[] args) { + if (args.length == 0) { + throw reportRuntimeError("msg.expected.string.arg"); + } + Object arg0 = args[0]; + if (arg0 instanceof Wrapper) { + Object wrapped = ((Wrapper)arg0).unwrap(); + if (wrapped instanceof Class) + return (Class)wrapped; + } + String className = Context.toString(args[0]); + try { + return Class.forName(className); + } + catch (ClassNotFoundException cnfe) { + throw reportRuntimeError("msg.class.not.found", className); + } + } + + public static void serialize(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + throws IOException + { + if (args.length < 2) { + throw Context.reportRuntimeError( + "Expected an object to serialize and a filename to write " + + "the serialization to"); + } + Object obj = args[0]; + String filename = Context.toString(args[1]); + FileOutputStream fos = new FileOutputStream(filename); + Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); + ScriptableOutputStream out = new ScriptableOutputStream(fos, scope); + out.writeObject(obj); + out.close(); + } + + public static Object deserialize(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + throws IOException, ClassNotFoundException + { + if (args.length < 1) { + throw Context.reportRuntimeError( + "Expected a filename to read the serialization from"); + } + String filename = Context.toString(args[0]); + FileInputStream fis = new FileInputStream(filename); + Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); + ObjectInputStream in = new ScriptableInputStream(fis, scope); + Object deserialized = in.readObject(); + in.close(); + return Context.toObject(deserialized, scope); + } + + public String[] getPrompts(Context cx) { + if (ScriptableObject.hasProperty(this, "prompts")) { + Object promptsJS = ScriptableObject.getProperty(this, + "prompts"); + if (promptsJS instanceof Scriptable) { + Scriptable s = (Scriptable) promptsJS; + if (ScriptableObject.hasProperty(s, 0) && + ScriptableObject.hasProperty(s, 1)) + { + Object elem0 = ScriptableObject.getProperty(s, 0); + if (elem0 instanceof Function) { + elem0 = ((Function) elem0).call(cx, this, s, + new Object[0]); + } + prompts[0] = Context.toString(elem0); + Object elem1 = ScriptableObject.getProperty(s, 1); + if (elem1 instanceof Function) { + elem1 = ((Function) elem1).call(cx, this, s, + new Object[0]); + } + prompts[1] = Context.toString(elem1); + } + } + } + return prompts; + } + + /** + * The spawn function runs a given function or script in a different + * thread. + * + * js> function g() { a = 7; } + * js> a = 3; + * 3 + * js> spawn(g) + * Thread[Thread-1,5,main] + * js> a + * 3 + */ + public static Object spawn(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + { + Scriptable scope = funObj.getParentScope(); + Runner runner; + if (args.length != 0 && args[0] instanceof Function) { + Object[] newArgs = null; + if (args.length > 1 && args[1] instanceof Scriptable) { + newArgs = cx.getElements((Scriptable) args[1]); + } + if (newArgs == null) { newArgs = ScriptRuntime.emptyArgs; } + runner = new Runner(scope, (Function) args[0], newArgs); + } else if (args.length != 0 && args[0] instanceof Script) { + runner = new Runner(scope, (Script) args[0]); + } else { + throw reportRuntimeError("msg.spawn.args"); + } + runner.factory = cx.getFactory(); + Thread thread = new Thread(runner); + thread.start(); + return thread; + } + + /** + * The sync function creates a synchronized function (in the sense + * of a Java synchronized method) from an existing function. The + * new function synchronizes on the this object of + * its invocation. + * js> var o = { f : sync(function(x) { + * print("entry"); + * Packages.java.lang.Thread.sleep(x*1000); + * print("exit"); + * })}; + * js> spawn(function() {o.f(5);}); + * Thread[Thread-0,5,main] + * entry + * js> spawn(function() {o.f(5);}); + * Thread[Thread-1,5,main] + * js> + * exit + * entry + * exit + */ + public static Object sync(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + { + if (args.length == 1 && args[0] instanceof Function) { + return new Synchronizer((Function)args[0]); + } + else { + throw reportRuntimeError("msg.sync.args"); + } + } + + /** + * Execute the specified command with the given argument and options + * as a separate process and return the exit status of the process. + *

+ * Usage: + *

+     * runCommand(command)
+     * runCommand(command, arg1, ..., argN)
+     * runCommand(command, arg1, ..., argN, options)
+     * 
+ * All except the last arguments to runCommand are converted to strings + * and denote command name and its arguments. If the last argument is a + * JavaScript object, it is an option object. Otherwise it is converted to + * string denoting the last argument and options objects assumed to be + * empty. + * Te following properties of the option object are processed: + *
    + *
  • args - provides an array of additional command arguments + *
  • env - explicit environment object. All its enumeratable + * properties define the corresponding environment variable names. + *
  • input - the process input. If it is not + * java.io.InputStream, it is converted to string and sent to the process + * as its input. If not specified, no input is provided to the process. + *
  • output - the process output instead of + * java.lang.System.out. If it is not instance of java.io.OutputStream, + * the process output is read, converted to a string, appended to the + * output property value converted to string and put as the new value of + * the output property. + *
  • err - the process error output instead of + * java.lang.System.err. If it is not instance of java.io.OutputStream, + * the process error output is read, converted to a string, appended to + * the err property value converted to string and put as the new + * value of the err property. + *
+ */ + public static Object runCommand(Context cx, Scriptable thisObj, + Object[] args, Function funObj) + throws IOException + { + int L = args.length; + if (L == 0 || (L == 1 && args[0] instanceof Scriptable)) { + throw reportRuntimeError("msg.runCommand.bad.args"); + } + + InputStream in = null; + OutputStream out = null, err = null; + ByteArrayOutputStream outBytes = null, errBytes = null; + Object outObj = null, errObj = null; + String[] environment = null; + Scriptable params = null; + Object[] addArgs = null; + if (args[L - 1] instanceof Scriptable) { + params = (Scriptable)args[L - 1]; + --L; + Object envObj = ScriptableObject.getProperty(params, "env"); + if (envObj != Scriptable.NOT_FOUND) { + if (envObj == null) { + environment = new String[0]; + } else { + if (!(envObj instanceof Scriptable)) { + throw reportRuntimeError("msg.runCommand.bad.env"); + } + Scriptable envHash = (Scriptable)envObj; + Object[] ids = ScriptableObject.getPropertyIds(envHash); + environment = new String[ids.length]; + for (int i = 0; i != ids.length; ++i) { + Object keyObj = ids[i], val; + String key; + if (keyObj instanceof String) { + key = (String)keyObj; + val = ScriptableObject.getProperty(envHash, key); + } else { + int ikey = ((Number)keyObj).intValue(); + key = Integer.toString(ikey); + val = ScriptableObject.getProperty(envHash, ikey); + } + if (val == ScriptableObject.NOT_FOUND) { + val = Undefined.instance; + } + environment[i] = key+'='+ScriptRuntime.toString(val); + } + } + } + Object inObj = ScriptableObject.getProperty(params, "input"); + if (inObj != Scriptable.NOT_FOUND) { + in = toInputStream(inObj); + } + outObj = ScriptableObject.getProperty(params, "output"); + if (outObj != Scriptable.NOT_FOUND) { + out = toOutputStream(outObj); + if (out == null) { + outBytes = new ByteArrayOutputStream(); + out = outBytes; + } + } + errObj = ScriptableObject.getProperty(params, "err"); + if (errObj != Scriptable.NOT_FOUND) { + err = toOutputStream(errObj); + if (err == null) { + errBytes = new ByteArrayOutputStream(); + err = errBytes; + } + } + Object addArgsObj = ScriptableObject.getProperty(params, "args"); + if (addArgsObj != Scriptable.NOT_FOUND) { + Scriptable s = Context.toObject(addArgsObj, + getTopLevelScope(thisObj)); + addArgs = cx.getElements(s); + } + } + Global global = getInstance(funObj); + if (out == null) { + out = (global != null) ? global.getOut() : System.out; + } + if (err == null) { + err = (global != null) ? global.getErr() : System.err; + } + // If no explicit input stream, do not send any input to process, + // in particular, do not use System.in to avoid deadlocks + // when waiting for user input to send to process which is already + // terminated as it is not always possible to interrupt read method. + + String[] cmd = new String[(addArgs == null) ? L : L + addArgs.length]; + for (int i = 0; i != L; ++i) { + cmd[i] = ScriptRuntime.toString(args[i]); + } + if (addArgs != null) { + for (int i = 0; i != addArgs.length; ++i) { + cmd[L + i] = ScriptRuntime.toString(addArgs[i]); + } + } + + int exitCode = runProcess(cmd, environment, in, out, err); + if (outBytes != null) { + String s = ScriptRuntime.toString(outObj) + outBytes.toString(); + ScriptableObject.putProperty(params, "output", s); + } + if (errBytes != null) { + String s = ScriptRuntime.toString(errObj) + errBytes.toString(); + ScriptableObject.putProperty(params, "err", s); + } + + return new Integer(exitCode); + } + + /** + * The seal function seals all supplied arguments. + */ + public static void seal(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + { + for (int i = 0; i != args.length; ++i) { + Object arg = args[i]; + if (!(arg instanceof ScriptableObject) || arg == Undefined.instance) + { + if (!(arg instanceof Scriptable) || arg == Undefined.instance) + { + throw reportRuntimeError("msg.shell.seal.not.object"); + } else { + throw reportRuntimeError("msg.shell.seal.not.scriptable"); + } + } + } + + for (int i = 0; i != args.length; ++i) { + Object arg = args[i]; + ((ScriptableObject)arg).sealObject(); + } + } + + /** + * The readFile reads the given file content and convert it to a string + * using the specified character coding or default character coding if + * explicit coding argument is not given. + *

+ * Usage: + *

+     * readFile(filePath)
+     * readFile(filePath, charCoding)
+     * 
+ * The first form converts file's context to string using the default + * character coding. + */ + public static Object readFile(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + throws IOException + { + if (args.length == 0) { + throw reportRuntimeError("msg.shell.readFile.bad.args"); + } + String path = ScriptRuntime.toString(args[0]); + String charCoding = null; + if (args.length >= 2) { + charCoding = ScriptRuntime.toString(args[1]); + } + + return readUrl(path, charCoding, true); + } + + /** + * The readUrl opens connection to the given URL, read all its data + * and converts them to a string + * using the specified character coding or default character coding if + * explicit coding argument is not given. + *

+ * Usage: + *

+     * readUrl(url)
+     * readUrl(url, charCoding)
+     * 
+ * The first form converts file's context to string using the default + * charCoding. + */ + public static Object readUrl(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + throws IOException + { + if (args.length == 0) { + throw reportRuntimeError("msg.shell.readUrl.bad.args"); + } + String url = ScriptRuntime.toString(args[0]); + String charCoding = null; + if (args.length >= 2) { + charCoding = ScriptRuntime.toString(args[1]); + } + + return readUrl(url, charCoding, false); + } + + /** + * Convert the argumnet to int32 number. + */ + public static Object toint32(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + { + Object arg = (args.length != 0 ? args[0] : Undefined.instance); + if (arg instanceof Integer) + return arg; + return ScriptRuntime.wrapInt(ScriptRuntime.toInt32(arg)); + } + + public InputStream getIn() { + return inStream == null ? System.in : inStream; + } + + public void setIn(InputStream in) { + inStream = in; + } + + public PrintStream getOut() { + return outStream == null ? System.out : outStream; + } + + public void setOut(PrintStream out) { + outStream = out; + } + + public PrintStream getErr() { + return errStream == null ? System.err : errStream; + } + + public void setErr(PrintStream err) { + errStream = err; + } + + public void setSealedStdLib(boolean value) + { + sealedStdLib = value; + } + + private static Global getInstance(Function function) + { + Scriptable scope = function.getParentScope(); + if (!(scope instanceof Global)) + throw reportRuntimeError("msg.bad.shell.function.scope", + String.valueOf(scope)); + return (Global)scope; + } + + /** + * Runs the given process using Runtime.exec(). + * If any of in, out, err is null, the corresponding process stream will + * be closed immediately, otherwise it will be closed as soon as + * all data will be read from/written to process + * + * @return Exit value of process. + * @throws IOException If there was an error executing the process. + */ + private static int runProcess(String[] cmd, String[] environment, + InputStream in, OutputStream out, + OutputStream err) + throws IOException + { + Process p; + if (environment == null) { + p = Runtime.getRuntime().exec(cmd); + } else { + p = Runtime.getRuntime().exec(cmd, environment); + } + + try { + PipeThread inThread = null; + if (in != null) { + inThread = new PipeThread(false, in, p.getOutputStream()); + inThread.start(); + } else { + p.getOutputStream().close(); + } + + PipeThread outThread = null; + if (out != null) { + outThread = new PipeThread(true, p.getInputStream(), out); + outThread.start(); + } else { + p.getInputStream().close(); + } + + PipeThread errThread = null; + if (err != null) { + errThread = new PipeThread(true, p.getErrorStream(), err); + errThread.start(); + } else { + p.getErrorStream().close(); + } + + // wait for process completion + for (;;) { + try { + p.waitFor(); + if (outThread != null) { + outThread.join(); + } + if (inThread != null) { + inThread.join(); + } + if (errThread != null) { + errThread.join(); + } + break; + } catch (InterruptedException ignore) { + } + } + + return p.exitValue(); + } finally { + p.destroy(); + } + } + + static void pipe(boolean fromProcess, InputStream from, OutputStream to) + throws IOException + { + try { + final int SIZE = 4096; + byte[] buffer = new byte[SIZE]; + for (;;) { + int n; + if (!fromProcess) { + n = from.read(buffer, 0, SIZE); + } else { + try { + n = from.read(buffer, 0, SIZE); + } catch (IOException ex) { + // Ignore exception as it can be cause by closed pipe + break; + } + } + if (n < 0) { break; } + if (fromProcess) { + to.write(buffer, 0, n); + to.flush(); + } else { + try { + to.write(buffer, 0, n); + to.flush(); + } catch (IOException ex) { + // Ignore exception as it can be cause by closed pipe + break; + } + } + } + } finally { + try { + if (fromProcess) { + from.close(); + } else { + to.close(); + } + } catch (IOException ex) { + // Ignore errors on close. On Windows JVM may throw invalid + // refrence exception if process terminates too fast. + } + } + } + + private static InputStream toInputStream(Object value) + throws IOException + { + InputStream is = null; + String s = null; + if (value instanceof Wrapper) { + Object unwrapped = ((Wrapper)value).unwrap(); + if (unwrapped instanceof InputStream) { + is = (InputStream)unwrapped; + } else if (unwrapped instanceof byte[]) { + is = new ByteArrayInputStream((byte[])unwrapped); + } else if (unwrapped instanceof Reader) { + s = readReader((Reader)unwrapped); + } else if (unwrapped instanceof char[]) { + s = new String((char[])unwrapped); + } + } + if (is == null) { + if (s == null) { s = ScriptRuntime.toString(value); } + is = new ByteArrayInputStream(s.getBytes()); + } + return is; + } + + private static OutputStream toOutputStream(Object value) { + OutputStream os = null; + if (value instanceof Wrapper) { + Object unwrapped = ((Wrapper)value).unwrap(); + if (unwrapped instanceof OutputStream) { + os = (OutputStream)unwrapped; + } + } + return os; + } + + private static String readUrl(String filePath, String charCoding, + boolean urlIsFile) + throws IOException + { + int chunkLength; + InputStream is = null; + try { + if (!urlIsFile) { + URL urlObj = new URL(filePath); + URLConnection uc = urlObj.openConnection(); + is = uc.getInputStream(); + chunkLength = uc.getContentLength(); + if (chunkLength <= 0) + chunkLength = 1024; + if (charCoding == null) { + String type = uc.getContentType(); + if (type != null) { + charCoding = getCharCodingFromType(type); + } + } + } else { + File f = new File(filePath); + + long length = f.length(); + chunkLength = (int)length; + if (chunkLength != length) + throw new IOException("Too big file size: "+length); + + if (chunkLength == 0) { return ""; } + + is = new FileInputStream(f); + } + + Reader r; + if (charCoding == null) { + r = new InputStreamReader(is); + } else { + r = new InputStreamReader(is, charCoding); + } + return readReader(r, chunkLength); + + } finally { + if (is != null) + is.close(); + } + } + + private static String getCharCodingFromType(String type) + { + int i = type.indexOf(';'); + if (i >= 0) { + int end = type.length(); + ++i; + while (i != end && type.charAt(i) <= ' ') { + ++i; + } + String charset = "charset"; + if (charset.regionMatches(true, 0, type, i, charset.length())) + { + i += charset.length(); + while (i != end && type.charAt(i) <= ' ') { + ++i; + } + if (i != end && type.charAt(i) == '=') { + ++i; + while (i != end && type.charAt(i) <= ' ') { + ++i; + } + if (i != end) { + // i is at the start of non-empty + // charCoding spec + while (type.charAt(end -1) <= ' ') { + --end; + } + return type.substring(i, end); + } + } + } + } + return null; + } + + private static String readReader(Reader reader) + throws IOException + { + return readReader(reader, 4096); + } + + private static String readReader(Reader reader, int initialBufferSize) + throws IOException + { + char[] buffer = new char[initialBufferSize]; + int offset = 0; + for (;;) { + int n = reader.read(buffer, offset, buffer.length - offset); + if (n < 0) { break; } + offset += n; + if (offset == buffer.length) { + char[] tmp = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, tmp, 0, offset); + buffer = tmp; + } + } + return new String(buffer, 0, offset); + } + + static RuntimeException reportRuntimeError(String msgId) { + String message = ToolErrorReporter.getMessage(msgId); + return Context.reportRuntimeError(message); + } + + static RuntimeException reportRuntimeError(String msgId, String msgArg) + { + String message = ToolErrorReporter.getMessage(msgId, msgArg); + return Context.reportRuntimeError(message); + } +} + + +class Runner implements Runnable, ContextAction { + + Runner(Scriptable scope, Function func, Object[] args) { + this.scope = scope; + f = func; + this.args = args; + } + + Runner(Scriptable scope, Script script) { + this.scope = scope; + s = script; + } + + public void run() + { + factory.call(this); + } + + public Object run(Context cx) + { + if (f != null) + return f.call(cx, scope, scope, args); + else + return s.exec(cx, scope); + } + + ContextFactory factory; + private Scriptable scope; + private Function f; + private Script s; + private Object[] args; +} + +class PipeThread extends Thread { + + PipeThread(boolean fromProcess, InputStream from, OutputStream to) { + setDaemon(true); + this.fromProcess = fromProcess; + this.from = from; + this.to = to; + } + + public void run() { + try { + Global.pipe(fromProcess, from, to); + } catch (IOException ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + + private boolean fromProcess; + private InputStream from; + private OutputStream to; +} + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java new file mode 100644 index 0000000..f6fe3a1 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java @@ -0,0 +1,225 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino JavaScript Debugger code, released + * November 21, 2000. + * + * The Initial Developer of the Original Code is + * See Beyond Corporation. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ +package org.mozilla.javascript.tools.shell; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; + +import javax.swing.ButtonGroup; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import org.mozilla.javascript.SecurityUtilities; + +public class JSConsole extends JFrame implements ActionListener +{ + static final long serialVersionUID = 2551225560631876300L; + + private File CWD; + private JFileChooser dlg; + private ConsoleTextArea consoleTextArea; + + public String chooseFile() { + if(CWD == null) { + String dir = SecurityUtilities.getSystemProperty("user.dir"); + if(dir != null) { + CWD = new File(dir); + } + } + if(CWD != null) { + dlg.setCurrentDirectory(CWD); + } + dlg.setDialogTitle("Select a file to load"); + int returnVal = dlg.showOpenDialog(this); + if(returnVal == JFileChooser.APPROVE_OPTION) { + String result = dlg.getSelectedFile().getPath(); + CWD = new File(dlg.getSelectedFile().getParent()); + return result; + } + return null; + } + + public static void main(String args[]) { + new JSConsole(args); + } + + public void createFileChooser() { + dlg = new JFileChooser(); + javax.swing.filechooser.FileFilter filter = + new javax.swing.filechooser.FileFilter() { + public boolean accept(File f) { + if(f.isDirectory()) { + return true; + } + String name = f.getName(); + int i = name.lastIndexOf('.'); + if(i > 0 && i < name.length() -1) { + String ext = name.substring(i + 1).toLowerCase(); + if(ext.equals("js")) { + return true; + } + } + return false; + } + + public String getDescription() { + return "JavaScript Files (*.js)"; + } + }; + dlg.addChoosableFileFilter(filter); + + } + + public JSConsole(String[] args) { + super("Rhino JavaScript Console"); + JMenuBar menubar = new JMenuBar(); + createFileChooser(); + String[] fileItems = {"Load...", "Exit"}; + String[] fileCmds = {"Load", "Exit"}; + char[] fileShortCuts = {'L', 'X'}; + String[] editItems = {"Cut", "Copy", "Paste"}; + char[] editShortCuts = {'T', 'C', 'P'}; + String[] plafItems = {"Metal", "Windows", "Motif"}; + boolean [] plafState = {true, false, false}; + JMenu fileMenu = new JMenu("File"); + fileMenu.setMnemonic('F'); + JMenu editMenu = new JMenu("Edit"); + editMenu.setMnemonic('E'); + JMenu plafMenu = new JMenu("Platform"); + plafMenu.setMnemonic('P'); + for(int i = 0; i < fileItems.length; ++i) { + JMenuItem item = new JMenuItem(fileItems[i], + fileShortCuts[i]); + item.setActionCommand(fileCmds[i]); + item.addActionListener(this); + fileMenu.add(item); + } + for(int i = 0; i < editItems.length; ++i) { + JMenuItem item = new JMenuItem(editItems[i], + editShortCuts[i]); + item.addActionListener(this); + editMenu.add(item); + } + ButtonGroup group = new ButtonGroup(); + for(int i = 0; i < plafItems.length; ++i) { + JRadioButtonMenuItem item = new JRadioButtonMenuItem(plafItems[i], + plafState[i]); + group.add(item); + item.addActionListener(this); + plafMenu.add(item); + } + menubar.add(fileMenu); + menubar.add(editMenu); + menubar.add(plafMenu); + setJMenuBar(menubar); + consoleTextArea = new ConsoleTextArea(args); + JScrollPane scroller = new JScrollPane(consoleTextArea); + setContentPane(scroller); + consoleTextArea.setRows(24); + consoleTextArea.setColumns(80); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + pack(); + setVisible(true); + // System.setIn(consoleTextArea.getIn()); + // System.setOut(consoleTextArea.getOut()); + // System.setErr(consoleTextArea.getErr()); + Main.setIn(consoleTextArea.getIn()); + Main.setOut(consoleTextArea.getOut()); + Main.setErr(consoleTextArea.getErr()); + Main.main(args); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + String plaf_name = null; + if(cmd.equals("Load")) { + String f = chooseFile(); + if(f != null) { + f = f.replace('\\', '/'); + consoleTextArea.eval("load(\"" + f + "\");"); + } + } else if(cmd.equals("Exit")) { + System.exit(0); + } else if(cmd.equals("Cut")) { + consoleTextArea.cut(); + } else if(cmd.equals("Copy")) { + consoleTextArea.copy(); + } else if(cmd.equals("Paste")) { + consoleTextArea.paste(); + } else { + if(cmd.equals("Metal")) { + plaf_name = "javax.swing.plaf.metal.MetalLookAndFeel"; + } else if(cmd.equals("Windows")) { + plaf_name = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; + } else if(cmd.equals("Motif")) { + plaf_name = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; + } + if(plaf_name != null) { + try { + UIManager.setLookAndFeel(plaf_name); + SwingUtilities.updateComponentTreeUI(this); + consoleTextArea.postUpdateUI(); + // updateComponentTreeUI seems to mess up the file + // chooser dialog, so just create a new one + createFileChooser(); + } catch(Exception exc) { + JOptionPane.showMessageDialog(this, + exc.getMessage(), + "Platform", + JOptionPane.ERROR_MESSAGE); + } + } + } + + } + +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java new file mode 100644 index 0000000..de39a5e --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/JavaPolicySecurity.java @@ -0,0 +1,240 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import java.security.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; + +import org.mozilla.javascript.*; + +public class JavaPolicySecurity extends SecurityProxy +{ + + public Class getStaticSecurityDomainClassInternal() { + return ProtectionDomain.class; + } + + private static class Loader extends ClassLoader + implements GeneratedClassLoader + { + private ProtectionDomain domain; + + Loader(ClassLoader parent, ProtectionDomain domain) { + super(parent != null ? parent : getSystemClassLoader()); + this.domain = domain; + } + + public Class defineClass(String name, byte[] data) { + return super.defineClass(name, data, 0, data.length, domain); + } + + public void linkClass(Class cl) { + resolveClass(cl); + } + } + + private static class ContextPermissions extends PermissionCollection + { + static final long serialVersionUID = -1721494496320750721L; + +// Construct PermissionCollection that permits an action only +// if it is permitted by staticDomain and by security context of Java stack on +// the moment of constructor invocation + ContextPermissions(ProtectionDomain staticDomain) { + _context = AccessController.getContext(); + if (staticDomain != null) { + _statisPermissions = staticDomain.getPermissions(); + } + setReadOnly(); + } + + public void add(Permission permission) { + throw new RuntimeException("NOT IMPLEMENTED"); + } + + public boolean implies(Permission permission) { + if (_statisPermissions != null) { + if (!_statisPermissions.implies(permission)) { + return false; + } + } + try { + _context.checkPermission(permission); + return true; + }catch (AccessControlException ex) { + return false; + } + } + + public Enumeration elements() + { + return new Enumeration() { + public boolean hasMoreElements() { return false; } + public Object nextElement() { return null; } + }; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()); + sb.append('@'); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" (context="); + sb.append(_context); + sb.append(", static_permitions="); + sb.append(_statisPermissions); + sb.append(')'); + return sb.toString(); + } + + AccessControlContext _context; + PermissionCollection _statisPermissions; + } + + public JavaPolicySecurity() + { + // To trigger error on jdk-1.1 with lazy load + new CodeSource(null, (java.security.cert.Certificate[])null); + } + + protected void callProcessFileSecure(final Context cx, + final Scriptable scope, + final String filename) + { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + URL url = getUrlObj(filename); + ProtectionDomain staticDomain = getUrlDomain(url); + Main.processFileSecure(cx, scope, url.toExternalForm(), + staticDomain); + return null; + } + }); + } + + private URL getUrlObj(String url) + { + URL urlObj; + try { + urlObj = new URL(url); + } catch (MalformedURLException ex) { + // Assume as Main.processFileSecure it is file, need to build its + // URL + String curDir = System.getProperty("user.dir"); + curDir = curDir.replace('\\', '/'); + if (!curDir.endsWith("/")) { + curDir = curDir+'/'; + } + try { + URL curDirURL = new URL("file:"+curDir); + urlObj = new URL(curDirURL, url); + } catch (MalformedURLException ex2) { + throw new RuntimeException + ("Can not construct file URL for '"+url+"':" + +ex2.getMessage()); + } + } + return urlObj; + } + + private ProtectionDomain getUrlDomain(URL url) + { + CodeSource cs; + cs = new CodeSource(url, (java.security.cert.Certificate[])null); + PermissionCollection pc = Policy.getPolicy().getPermissions(cs); + return new ProtectionDomain(cs, pc); + } + + public GeneratedClassLoader + createClassLoader(ClassLoader parentLoader, Object securityDomain) + { + ProtectionDomain domain = (ProtectionDomain)securityDomain; + return new Loader(parentLoader, domain); + } + + public Object getDynamicSecurityDomain(Object securityDomain) + { + ProtectionDomain staticDomain = (ProtectionDomain)securityDomain; + return getDynamicDomain(staticDomain); + } + + private ProtectionDomain getDynamicDomain(ProtectionDomain staticDomain) { + ContextPermissions p = new ContextPermissions(staticDomain); + ProtectionDomain contextDomain = new ProtectionDomain(null, p); + return contextDomain; + } + + public Object callWithDomain(Object securityDomain, + final Context cx, + final Callable callable, + final Scriptable scope, + final Scriptable thisObj, + final Object[] args) + { + ProtectionDomain staticDomain = (ProtectionDomain)securityDomain; + // There is no direct way in Java to intersect permitions according + // stack context with additional domain. + // The following implementation first constructs ProtectionDomain + // that allows actions only allowed by both staticDomain and current + // stack context, and then constructs AccessController for this dynamic + // domain. + // If this is too slow, alternative solution would be to generate + // class per domain with a proxy method to call to infect + // java stack. + // Another optimization in case of scripts coming from "world" domain, + // that is having minimal default privileges is to construct + // one AccessControlContext based on ProtectionDomain + // with least possible privileges and simply call + // AccessController.doPrivileged with this untrusted context + + ProtectionDomain dynamicDomain = getDynamicDomain(staticDomain); + ProtectionDomain[] tmp = { dynamicDomain }; + AccessControlContext restricted = new AccessControlContext(tmp); + + PrivilegedAction action = new PrivilegedAction() { + public Object run() { + return callable.call(cx, scope, thisObj, args); + } + }; + + return AccessController.doPrivileged(action, restricted); + } +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Main.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Main.java new file mode 100644 index 0000000..9120892 --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/Main.java @@ -0,0 +1,638 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Beard + * Norris Boyd + * Igor Bukanov + * Rob Ginda + * Kurt Westerfeld + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.net.MalformedURLException; +import java.util.*; +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.ToolErrorReporter; + +/** + * The shell program. + * + * Can execute scripts interactively or in batch mode at the command line. + * An example of controlling the JavaScript engine. + * + * @author Norris Boyd + */ +public class Main +{ + public static ShellContextFactory + shellContextFactory = new ShellContextFactory(); + + public static Global global = new Global(); + static protected ToolErrorReporter errorReporter; + static protected int exitCode = 0; + static private final int EXITCODE_RUNTIME_ERROR = 3; + static private final int EXITCODE_FILE_NOT_FOUND = 4; + static boolean processStdin = true; + static Vector fileList = new Vector(5); + private static SecurityProxy securityImpl; + + static { + global.initQuitAction(new IProxy(IProxy.SYSTEM_EXIT)); + } + + /** + * Proxy class to avoid proliferation of anonymous classes. + */ + private static class IProxy implements ContextAction, QuitAction + { + private static final int PROCESS_FILES = 1; + private static final int EVAL_INLINE_SCRIPT = 2; + private static final int SYSTEM_EXIT = 3; + + private int type; + String[] args; + String scriptText; + + IProxy(int type) + { + this.type = type; + } + + public Object run(Context cx) + { + if (type == PROCESS_FILES) { + processFiles(cx, args); + } else if (type == EVAL_INLINE_SCRIPT) { + Script script = loadScriptFromSource(cx, scriptText, + "", 1, null); + if (script != null) { + evaluateScript(script, cx, getGlobal()); + } + } else { + throw Kit.codeBug(); + } + return null; + } + + public void quit(Context cx, int exitCode) + { + if (type == SYSTEM_EXIT) { + System.exit(exitCode); + return; + } + throw Kit.codeBug(); + } + } + + /** + * Main entry point. + * + * Process arguments as would a normal Java program. Also + * create a new Context and associate it with the current thread. + * Then set up the execution environment and begin to + * execute scripts. + */ + public static void main(String args[]) { + try { + if (Boolean.getBoolean("rhino.use_java_policy_security")) { + initJavaPolicySecuritySupport(); + } + } catch (SecurityException ex) { + ex.printStackTrace(System.err); + } + + int result = exec(args); + if (result != 0) { + System.exit(result); + } + } + + /** + * Execute the given arguments, but don't System.exit at the end. + */ + public static int exec(String origArgs[]) + { + errorReporter = new ToolErrorReporter(false, global.getErr()); + shellContextFactory.setErrorReporter(errorReporter); + String[] args = processOptions(origArgs); + if (processStdin) + fileList.addElement(null); + + if (!global.initialized) { + global.init(shellContextFactory); + } + IProxy iproxy = new IProxy(IProxy.PROCESS_FILES); + iproxy.args = args; + shellContextFactory.call(iproxy); + + return exitCode; + } + + static void processFiles(Context cx, String[] args) + { + // define "arguments" array in the top-level object: + // need to allocate new array since newArray requires instances + // of exactly Object[], not ObjectSubclass[] + Object[] array = new Object[args.length]; + System.arraycopy(args, 0, array, 0, args.length); + Scriptable argsObj = cx.newArray(global, array); + global.defineProperty("arguments", argsObj, + ScriptableObject.DONTENUM); + + for (int i=0; i < fileList.size(); i++) { + processSource(cx, (String) fileList.elementAt(i)); + } + + } + + public static Global getGlobal() + { + return global; + } + + /** + * Parse arguments. + */ + public static String[] processOptions(String args[]) + { + String usageError; + goodUsage: for (int i = 0; ; ++i) { + if (i == args.length) { + return new String[0]; + } + String arg = args[i]; + if (!arg.startsWith("-")) { + processStdin = false; + fileList.addElement(arg); + String[] result = new String[args.length - i - 1]; + System.arraycopy(args, i+1, result, 0, args.length - i - 1); + return result; + } + if (arg.equals("-version")) { + if (++i == args.length) { + usageError = arg; + break goodUsage; + } + int version; + try { + version = Integer.parseInt(args[i]); + } catch (NumberFormatException ex) { + usageError = args[i]; + break goodUsage; + } + if (!Context.isValidLanguageVersion(version)) { + usageError = args[i]; + break goodUsage; + } + shellContextFactory.setLanguageVersion(version); + continue; + } + if (arg.equals("-opt") || arg.equals("-O")) { + if (++i == args.length) { + usageError = arg; + break goodUsage; + } + int opt; + try { + opt = Integer.parseInt(args[i]); + } catch (NumberFormatException ex) { + usageError = args[i]; + break goodUsage; + } + if (opt == -2) { + // Compatibility with Cocoon Rhino fork + opt = -1; + } else if (!Context.isValidOptimizationLevel(opt)) { + usageError = args[i]; + break goodUsage; + } + shellContextFactory.setOptimizationLevel(opt); + continue; + } + if (arg.equals("-strict")) { + shellContextFactory.setStrictMode(true); + errorReporter.setIsReportingWarnings(true); + continue; + } + if (arg.equals("-fatal-warnings")) { + shellContextFactory.setWarningAsError(true); + continue; + } + if (arg.equals("-e")) { + processStdin = false; + if (++i == args.length) { + usageError = arg; + break goodUsage; + } + if (!global.initialized) { + global.init(shellContextFactory); + } + IProxy iproxy = new IProxy(IProxy.EVAL_INLINE_SCRIPT); + iproxy.scriptText = args[i]; + shellContextFactory.call(iproxy); + continue; + } + if (arg.equals("-w")) { + errorReporter.setIsReportingWarnings(true); + continue; + } + if (arg.equals("-f")) { + processStdin = false; + if (++i == args.length) { + usageError = arg; + break goodUsage; + } + fileList.addElement(args[i].equals("-") ? null : args[i]); + continue; + } + if (arg.equals("-sealedlib")) { + global.setSealedStdLib(true); + continue; + } + if (arg.equals("-debug")) { + shellContextFactory.setGeneratingDebug(true); + continue; + } + if (arg.equals("-?") || + arg.equals("-help")) { + // print usage message + global.getOut().println( + ToolErrorReporter.getMessage("msg.shell.usage", Main.class.getName())); + System.exit(1); + } + usageError = arg; + break goodUsage; + } + // print error and usage message + global.getOut().println( + ToolErrorReporter.getMessage("msg.shell.invalid", usageError)); + global.getOut().println( + ToolErrorReporter.getMessage("msg.shell.usage", Main.class.getName())); + System.exit(1); + return null; + } + + private static void initJavaPolicySecuritySupport() + { + Throwable exObj; + try { + Class cl = Class.forName + ("org.mozilla.javascript.tools.shell.JavaPolicySecurity"); + securityImpl = (SecurityProxy)cl.newInstance(); + SecurityController.initGlobal(securityImpl); + return; + } catch (ClassNotFoundException ex) { + exObj = ex; + } catch (IllegalAccessException ex) { + exObj = ex; + } catch (InstantiationException ex) { + exObj = ex; + } catch (LinkageError ex) { + exObj = ex; + } + throw Kit.initCause(new IllegalStateException( + "Can not load security support: "+exObj), exObj); + } + + /** + * Evaluate JavaScript source. + * + * @param cx the current context + * @param filename the name of the file to compile, or null + * for interactive mode. + */ + public static void processSource(Context cx, String filename) + { + if (filename == null || filename.equals("-")) { + PrintStream ps = global.getErr(); + if (filename == null) { + // print implementation version + ps.println(cx.getImplementationVersion()); + } + + // Use the interpreter for interactive input + cx.setOptimizationLevel(-1); + + BufferedReader in = new BufferedReader + (new InputStreamReader(global.getIn())); + int lineno = 1; + boolean hitEOF = false; + while (!hitEOF) { + String[] prompts = global.getPrompts(cx); + if (filename == null) + ps.print(prompts[0]); + ps.flush(); + String source = ""; + + // Collect lines of source to compile. + while (true) { + String newline; + try { + newline = in.readLine(); + } + catch (IOException ioe) { + ps.println(ioe.toString()); + break; + } + if (newline == null) { + hitEOF = true; + break; + } + source = source + newline + "\n"; + lineno++; + if (cx.stringIsCompilableUnit(source)) + break; + ps.print(prompts[1]); + } + Script script = loadScriptFromSource(cx, source, "", + lineno, null); + if (script != null) { + Object result = evaluateScript(script, cx, global); + // Avoid printing out undefined or function definitions. + if (result != Context.getUndefinedValue() && + !(result instanceof Function && + source.trim().startsWith("function"))) + { + try { + ps.println(Context.toString(result)); + } catch (RhinoException rex) { + ToolErrorReporter.reportException( + cx.getErrorReporter(), rex); + } + } + NativeArray h = global.history; + h.put((int)h.getLength(), h, source); + } + } + ps.println(); + } else { + processFile(cx, global, filename); + } + System.gc(); + } + + public static void processFile(Context cx, Scriptable scope, + String filename) + { + if (securityImpl == null) { + processFileSecure(cx, scope, filename, null); + } else { + securityImpl.callProcessFileSecure(cx, scope, filename); + } + } + + static void processFileSecure(Context cx, Scriptable scope, + String path, Object securityDomain) + { + Script script; + if (path.endsWith(".class")) { + script = loadCompiledScript(cx, path, securityDomain); + } else { + String source = (String)readFileOrUrl(path, true); + if (source == null) { + exitCode = EXITCODE_FILE_NOT_FOUND; + return; + } + + // Support the executable script #! syntax: If + // the first line begins with a '#', treat the whole + // line as a comment. + if (source.length() > 0 && source.charAt(0) == '#') { + for (int i = 1; i != source.length(); ++i) { + int c = source.charAt(i); + if (c == '\n' || c == '\r') { + source = source.substring(i); + break; + } + } + } + script = loadScriptFromSource(cx, source, path, 1, securityDomain); + } + if (script != null) { + evaluateScript(script, cx, scope); + } + } + + public static Script loadScriptFromSource(Context cx, String scriptSource, + String path, int lineno, + Object securityDomain) + { + try { + return cx.compileString(scriptSource, path, lineno, + securityDomain); + } catch (EvaluatorException ee) { + // Already printed message. + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (RhinoException rex) { + ToolErrorReporter.reportException( + cx.getErrorReporter(), rex); + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (VirtualMachineError ex) { + // Treat StackOverflow and OutOfMemory as runtime errors + ex.printStackTrace(); + String msg = ToolErrorReporter.getMessage( + "msg.uncaughtJSException", ex.toString()); + exitCode = EXITCODE_RUNTIME_ERROR; + Context.reportError(msg); + } + return null; + } + + private static Script loadCompiledScript(Context cx, String path, + Object securityDomain) + { + byte[] data = (byte[])readFileOrUrl(path, false); + if (data == null) { + exitCode = EXITCODE_FILE_NOT_FOUND; + return null; + } + // XXX: For now extract class name of compiled Script from path + // instead of parsing class bytes + int nameStart = path.lastIndexOf('/'); + if (nameStart < 0) { + nameStart = 0; + } else { + ++nameStart; + } + int nameEnd = path.lastIndexOf('.'); + if (nameEnd < nameStart) { + // '.' does not exist in path (nameEnd < 0) + // or it comes before nameStart + nameEnd = path.length(); + } + String name = path.substring(nameStart, nameEnd); + try { + GeneratedClassLoader loader = SecurityController.createLoader(cx.getApplicationClassLoader(), securityDomain); + Class clazz = loader.defineClass(name, data); + loader.linkClass(clazz); + if (!Script.class.isAssignableFrom(clazz)) { + throw Context.reportRuntimeError("msg.must.implement.Script"); + } + return (Script) clazz.newInstance(); + } catch (RhinoException rex) { + ToolErrorReporter.reportException( + cx.getErrorReporter(), rex); + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (IllegalAccessException iaex) { + exitCode = EXITCODE_RUNTIME_ERROR; + Context.reportError(iaex.toString()); + } catch (InstantiationException inex) { + exitCode = EXITCODE_RUNTIME_ERROR; + Context.reportError(inex.toString()); + } + return null; + } + + public static Object evaluateScript(Script script, Context cx, + Scriptable scope) + { + try { + return script.exec(cx, scope); + } catch (RhinoException rex) { + ToolErrorReporter.reportException( + cx.getErrorReporter(), rex); + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (VirtualMachineError ex) { + // Treat StackOverflow and OutOfMemory as runtime errors + ex.printStackTrace(); + String msg = ToolErrorReporter.getMessage( + "msg.uncaughtJSException", ex.toString()); + exitCode = EXITCODE_RUNTIME_ERROR; + Context.reportError(msg); + } + return Context.getUndefinedValue(); + } + + public static InputStream getIn() { + return getGlobal().getIn(); + } + + public static void setIn(InputStream in) { + getGlobal().setIn(in); + } + + public static PrintStream getOut() { + return getGlobal().getOut(); + } + + public static void setOut(PrintStream out) { + getGlobal().setOut(out); + } + + public static PrintStream getErr() { + return getGlobal().getErr(); + } + + public static void setErr(PrintStream err) { + getGlobal().setErr(err); + } + + /** + * Read file or url specified by path. + * @return file or url content as byte[] or as String if + * convertToString is true. + */ + private static Object readFileOrUrl(String path, boolean convertToString) + { + URL url = null; + // Assume path is URL if it contains dot and there are at least + // 2 characters in the protocol part. The later allows under Windows + // to interpret paths with driver letter as file, not URL. + if (path.indexOf(':') >= 2) { + try { + url = new URL(path); + } catch (MalformedURLException ex) { + } + } + + InputStream is = null; + int capacityHint = 0; + if (url == null) { + File file = new File(path); + capacityHint = (int)file.length(); + try { + is = new FileInputStream(file); + } catch (IOException ex) { + Context.reportError(ToolErrorReporter.getMessage( + "msg.couldnt.open", path)); + return null; + } + } else { + try { + URLConnection uc = url.openConnection(); + is = uc.getInputStream(); + capacityHint = uc.getContentLength(); + // Ignore insane values for Content-Length + if (capacityHint > (1 << 20)) { + capacityHint = -1; + } + } catch (IOException ex) { + Context.reportError(ToolErrorReporter.getMessage( + "msg.couldnt.open.url", url.toString(), ex.toString())); + return null; + } + } + if (capacityHint <= 0) { + capacityHint = 4096; + } + + byte[] data; + try { + try { + data = Kit.readStream(is, capacityHint); + } finally { + is.close(); + } + } catch (IOException ex) { + Context.reportError(ex.toString()); + return null; + } + + Object result; + if (!convertToString) { + result = data; + } else { + // Convert to String using the default encoding + // XXX: Use 'charset=' argument of Content-Type if URL? + result = new String(data); + } + return result; + } + +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/QuitAction.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/QuitAction.java new file mode 100644 index 0000000..dcad90e --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/QuitAction.java @@ -0,0 +1,50 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import org.mozilla.javascript.Context; + +/** + * Defines action to perform in response to quit command. + */ +public interface QuitAction +{ + public void quit(Context cx, int exitCode); +} + diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/SecurityProxy.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/SecurityProxy.java new file mode 100644 index 0000000..8f029ea --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/SecurityProxy.java @@ -0,0 +1,48 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import org.mozilla.javascript.*; + +public abstract class SecurityProxy extends SecurityController +{ + protected abstract void callProcessFileSecure(Context cx, Scriptable scope, + String filename); + +} diff --git a/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ShellContextFactory.java b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ShellContextFactory.java new file mode 100644 index 0000000..ba7e62c --- /dev/null +++ b/trunk/infrastructure/rhino1_7R1/toolsrc/org/mozilla/javascript/tools/shell/ShellContextFactory.java @@ -0,0 +1,114 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Bob Jervis + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript.tools.shell; + +import org.mozilla.javascript.*; + +public class ShellContextFactory extends ContextFactory +{ + private boolean strictMode; + private boolean warningAsError; + private int languageVersion; + private int optimizationLevel; + private boolean generatingDebug; + private ErrorReporter errorReporter; + + protected boolean hasFeature(Context cx, int featureIndex) + { + switch (featureIndex) { + case Context.FEATURE_STRICT_VARS: + case Context.FEATURE_STRICT_EVAL: + case Context.FEATURE_STRICT_MODE: + return strictMode; + + case Context.FEATURE_WARNING_AS_ERROR: + return warningAsError; + } + return super.hasFeature(cx, featureIndex); + } + + protected void onContextCreated(Context cx) + { + cx.setLanguageVersion(languageVersion); + cx.setOptimizationLevel(optimizationLevel); + if (errorReporter != null) { + cx.setErrorReporter(errorReporter); + } + cx.setGeneratingDebug(generatingDebug); + super.onContextCreated(cx); + } + + public void setStrictMode(boolean flag) + { + checkNotSealed(); + this.strictMode = flag; + } + + public void setWarningAsError(boolean flag) + { + checkNotSealed(); + this.warningAsError = flag; + } + + public void setLanguageVersion(int version) + { + Context.checkLanguageVersion(version); + checkNotSealed(); + this.languageVersion = version; + } + + public void setOptimizationLevel(int optimizationLevel) + { + Context.checkOptimizationLevel(optimizationLevel); + checkNotSealed(); + this.optimizationLevel = optimizationLevel; + } + + public void setErrorReporter(ErrorReporter errorReporter) + { + if (errorReporter == null) throw new IllegalArgumentException(); + this.errorReporter = errorReporter; + } + + public void setGeneratingDebug(boolean generatingDebug) + { + this.generatingDebug = generatingDebug; + } +} -- cgit v1.2.3