001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.flow.javascript;
018:
019: import java.util.ArrayList;
020: import java.util.List;
021:
022: import org.apache.cocoon.ProcessingException;
023: import org.apache.cocoon.util.location.Location;
024: import org.apache.cocoon.util.location.LocationImpl;
025: import org.apache.cocoon.util.location.LocationUtils;
026: import org.apache.commons.lang.exception.ExceptionUtils;
027: import org.mozilla.javascript.Context;
028: import org.mozilla.javascript.EcmaError;
029: import org.mozilla.javascript.JavaScriptException;
030: import org.mozilla.javascript.Scriptable;
031: import org.mozilla.javascript.debug.DebugFrame;
032: import org.mozilla.javascript.debug.DebuggableScript;
033: import org.mozilla.javascript.debug.Debugger;
034:
035: /**
036: * A Rhino debugger that tracks location information when an exception is raised in some JavaScript code.
037: * It's purpose is to build a {@link org.apache.cocoon.ProcessingException} that holds the stacktrace
038: * in the JavaScript code.
039: * <p>
040: * This debugger implementation is designed to be as lightweight and fast as possible, in order to have a
041: * negligible impact on the performances of the Rhino interpreter.
042: *
043: * @since 2.1.8
044: * @version $Id: LocationTrackingDebugger.java 433543 2006-08-22 06:22:54Z crossley $
045: */
046: public class LocationTrackingDebugger implements Debugger {
047:
048: // Strong reference to the location finder
049: private static final LocationUtils.LocationFinder rhinoLocFinder = new LocationUtils.LocationFinder() {
050:
051: public Location getLocation(Object obj, String description) {
052: if (obj instanceof EcmaError) {
053: EcmaError ex = (EcmaError) obj;
054: if (ex.getSourceName() != null) {
055: return new LocationImpl(ex.getName(), ex
056: .getSourceName(), ex.getLineNumber(), ex
057: .getColumnNumber());
058: } else {
059: return Location.UNKNOWN;
060: }
061:
062: } else if (obj instanceof JavaScriptException) {
063: JavaScriptException ex = (JavaScriptException) obj;
064: if (ex.sourceName() != null) {
065: return new LocationImpl(description, ex
066: .sourceName(), ex.lineNumber(), -1);
067: } else {
068: return Location.UNKNOWN;
069: }
070: }
071:
072: return null;
073: }
074: };
075:
076: static {
077: // Register what's needed to analyze exceptions produced by Rhino
078: ExceptionUtils.addCauseMethodName("getWrappedException");
079: LocationUtils.addFinder(rhinoLocFinder);
080: }
081:
082: protected List locations;
083: protected Throwable throwable;
084:
085: /**
086: * Rhino+cont API
087: */
088: public void handleCompilationDone(Context cx,
089: DebuggableScript fnOrScript, StringBuffer source) {
090: // ignore
091: }
092:
093: /**
094: * Rhino+cont API
095: */
096: public DebugFrame enterFrame(Context cx, Scriptable scope,
097: Scriptable this Obj, Object[] args,
098: DebuggableScript fnOrScript) {
099: return new StackTrackingFrame(fnOrScript);
100: }
101:
102: /**
103: * Rhino 1.6 API
104: */
105: public void handleCompilationDone(Context cx,
106: DebuggableScript fnOrScript, String source) {
107: // nothing
108: }
109:
110: /**
111: * Rhino 1.6 API
112: */
113: public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) {
114: // Rhino 1.6 API
115: return new StackTrackingFrame(fnOrScript);
116: }
117:
118: /**
119: * Get an exception that reflects the known location stack
120: *
121: * @param description a description for the exception
122: * @param originalException the original exception
123: *
124: * @return a suitable exception to throw
125: * @see ProcessingException#throwLocated(String, Throwable, Location)
126: */
127: public Exception getException(String description,
128: Exception originalException) throws ProcessingException {
129: if (throwable == null || locations == null) {
130: // Cannot to better for now
131: return originalException;
132: }
133:
134: // Unwrap original exception, if any, wrapped by Rhino (the wrapping
135: // class is different in Rhino+cont and Rhino 1.6)
136: Throwable cause = ExceptionUtils.getCause(throwable);
137: if (cause != null)
138: throwable = cause;
139:
140: return ProcessingException.throwLocated(description, throwable,
141: locations);
142: }
143:
144: private class StackTrackingFrame implements DebugFrame {
145:
146: DebuggableScript script;
147: int line;
148:
149: public StackTrackingFrame(DebuggableScript script) {
150: this .script = script;
151: }
152:
153: public void onEnter(Context cx, Scriptable activation,
154: Scriptable this Obj, Object[] args) {
155: // Rhino 1.6 specific
156: }
157:
158: // Rhino+cont API
159: public void onLineChange(Context cx, int lineNumber,
160: boolean breakpoint) {
161: line = lineNumber;
162: }
163:
164: // Rhino 1.6 API
165: public void onLineChange(Context cx, int lineNumber) {
166: line = lineNumber;
167: }
168:
169: public void onExceptionThrown(Context cx, Throwable ex) {
170: throwable = ex;
171: }
172:
173: public void onExit(Context cx, boolean byThrow,
174: Object resultOrException) {
175: if (byThrow) {
176: String name = null;
177: // Revisit: Rhino+cont and Rhino 1.6 have different debugger APIs, and we currently don't use this information
178: // Scriptable obj = script.getScriptable();
179: // name = obj instanceof NativeFunction ? ((NativeFunction)obj).getFunctionName() : "Top-level script";
180: // if (name == null || name.length() == 0) {
181: // name = "[unnamed]";
182: // }
183:
184: if (locations == null) {
185: locations = new ArrayList(1); // start small
186: }
187:
188: locations.add(new LocationImpl(name, script
189: .getSourceName(), line, -1));
190:
191: } else if (locations != null) {
192: // The exception was handled by the script: clear any recorded locations
193: locations = null;
194: }
195: }
196: }
197: }
|