| /******************************************************************************* |
| * Copyright (c) 2009 IBM Corporation and others All rights reserved. This |
| * program and the accompanying materials are made available under the terms of |
| * the Eclipse Public License v1.0 which accompanies this distribution, and is |
| * available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wst.jsdt.debug.internal.rhino.debugger; |
| |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.wst.jsdt.debug.internal.rhino.transport.Constants; |
| import org.eclipse.wst.jsdt.debug.internal.rhino.transport.JSONConstants; |
| import org.mozilla.javascript.debug.DebuggableScript; |
| |
| /** |
| * Rhino script implementation |
| * |
| * @since 1.0 |
| */ |
| public class ScriptSource { |
| |
| /** |
| * No line information |
| */ |
| static final LineData[] NO_LINES = new LineData[0]; |
| |
| /** |
| * The id of the script |
| */ |
| private Long scriptId; |
| /** |
| * Any special source properties to consider |
| */ |
| private final Map properties; |
| /** |
| * The backing source String that was compiled to give us this proxy |
| */ |
| private final String source; |
| /** |
| * If this {@link ScriptSource} was generated |
| */ |
| private final boolean generated; |
| /** |
| * The ordered list of function names |
| */ |
| private ArrayList functionNames = null; |
| |
| private Integer firstLine = null; |
| |
| /** |
| * The location of the script |
| */ |
| private URI uri = null; |
| /** |
| * The array of cached {@link FunctionSource}s |
| */ |
| private FunctionSource[] functionSources = null; |
| /** |
| * Sparse array of line information. This array only has entries present for |
| * lines that are returned |
| */ |
| private LineData[] lines = null; |
| /** |
| * Mapping of the {@link DebuggableScript} backing the FunctionSource to the FunctionSource handle |
| */ |
| private HashMap/*<DebuggableScript, FunctionSource>*/ scriptToFunction = new HashMap(); |
| |
| /** |
| * Constructor |
| * |
| * @param scriptId |
| * @param debuggableScript |
| * @param source |
| */ |
| public ScriptSource(DebuggableScript script, String source, URI uri, boolean generated, Map properties) { |
| this.uri = uri; |
| this.source = source; |
| this.properties = properties; |
| this.generated = generated; |
| |
| if(isStdIn()) { |
| //do not compute line data |
| return; |
| } |
| int[] rootlines = script.getLineNumbers(); |
| DebuggableScript[] functions = collectFunctions(script); |
| int flength = functions.length; |
| int max = 0; |
| int min = Integer.MAX_VALUE; |
| HashSet lineNumbers = new HashSet(flength+rootlines.length+1); |
| //dump in the line #'s from the root script |
| for (int i = 0; i < rootlines.length; i++) { |
| int line = rootlines[i]; |
| if(line > max) { |
| max = line; |
| } |
| if(line < min) { |
| min = line; |
| } |
| lineNumbers.add(new Integer(line)); |
| } |
| //dump in the line numbers from the expanded set of functions |
| if(flength > 0) { |
| functionSources = new FunctionSource[flength]; |
| functionNames = new ArrayList(flength); |
| |
| int start = 0, end = 0;; |
| for (int i = 0; i < functions.length; i++) { |
| int[] lines = functions[i].getLineNumbers(); |
| if(lines != null && lines.length > 0) { |
| start = lines[0] + 1; |
| for (int j = 0; j < lines.length; j++) { |
| int currentLine = lines[j]; |
| if(currentLine > max) { |
| max = currentLine; |
| } |
| if(currentLine < min) { |
| min = currentLine; |
| } |
| if (currentLine < start) { |
| start = currentLine; |
| } |
| else if(currentLine > end) { |
| end = currentLine; |
| } |
| lineNumbers.add(new Integer(currentLine)); |
| } |
| } |
| String name = functions[i].getFunctionName(); |
| name = (name == null ? Constants.EMPTY_STRING : name); |
| functionSources[i] = new FunctionSource(this, name, computeFunctionSource(0, 0, source), start); |
| functionNames.add(name); |
| scriptToFunction.put(functions[i], functionSources[i]); |
| start = 0; |
| } |
| } |
| //create the sparse array and populate the valid line numbers |
| if(max == 0) { |
| this.lines = NO_LINES; |
| } |
| else { |
| this.lines = new LineData[max+1]; |
| for (Iterator iter = lineNumbers.iterator(); iter.hasNext();) { |
| Integer value = (Integer) iter.next(); |
| this.lines[value.intValue()] = new LineData(); |
| iter.remove(); |
| } |
| lineNumbers = null; |
| } |
| this.firstLine = new Integer(min); |
| } |
| |
| /** |
| * Returns the first executable line in the script |
| * |
| * @return the first executable line in the script |
| */ |
| public Integer firstLine() { |
| return this.firstLine; |
| } |
| |
| /** |
| * @return if this script represents the stdin script |
| */ |
| public boolean isStdIn() { |
| return this.uri.toString().endsWith("stdin"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Collects all of the {@link DebuggableScript} objects for the functions |
| * @param root the root script |
| * @return the collected array of functions - {@link DebuggableScript} - objects or an empty array |
| */ |
| DebuggableScript[] collectFunctions(DebuggableScript root) { |
| ArrayList functions = new ArrayList(); |
| collectFunctions(root, functions); |
| int size = functions.size(); |
| if(size < 1) { |
| return RhinoDebuggerImpl.NO_SCRIPTS; |
| } |
| DebuggableScript[] funcs = new DebuggableScript[size]; |
| functions.toArray(funcs); |
| return funcs; |
| } |
| |
| /** |
| * Recursively collects function {@link DebuggableScript}s |
| * |
| * @param root |
| * @param collector |
| */ |
| void collectFunctions(DebuggableScript root, List collector) { |
| if(root.isFunction()) { |
| collector.add(root); |
| } |
| for (int i = 0; i < root.getFunctionCount(); i++) { |
| collectFunctions(root.getFunction(i), collector); |
| } |
| } |
| |
| /** |
| * Computes the functions' source from the given compiled buffer |
| * |
| * @param start |
| * @param end |
| * @param source |
| * @return the string for the source or <code>null</code> if it could not be computed |
| */ |
| String computeFunctionSource(int start, int end, String source) { |
| if(start > -1 && end <= source.length()) { |
| return source.substring(start, end); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the id for the script, <code>null</code> will throw and {@link IllegalArgumentException} |
| * @param id |
| * @throws IllegalArgumentException if <code>null</code> is specified as the new id |
| */ |
| public void setId(Long id) throws IllegalArgumentException { |
| if(id == null) { |
| throw new IllegalArgumentException(); |
| } |
| this.scriptId = id; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object obj) { |
| if(obj instanceof ScriptSource) { |
| return this.uri.toString().equals(((ScriptSource)obj).uri.toString()); |
| } |
| return false; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#hashCode() |
| */ |
| public int hashCode() { |
| return this.uri.toString().hashCode(); |
| } |
| |
| /** |
| * @return a new JSON map |
| */ |
| public Object toJSON() { |
| HashMap result = new HashMap(); |
| result.put(JSONConstants.SCRIPT_ID, this.scriptId); |
| result.put(JSONConstants.LOCATION, this.uri.toString()); |
| result.put(JSONConstants.PROPERTIES, properties); |
| result.put(JSONConstants.SOURCE, source); |
| result.put(JSONConstants.GENERATED, Boolean.valueOf(generated)); |
| if(lines != null) { |
| HashSet lineNumbers = new HashSet(); |
| for (int i = 0; i < lines.length; i++) { |
| if(lines[i] != null) { |
| lineNumbers.add(new Integer(i)); |
| } |
| } |
| result.put(JSONConstants.LINES, (lineNumbers == null ? Collections.EMPTY_SET : lineNumbers)); |
| } |
| else { |
| result.put(JSONConstants.LINES, Collections.EMPTY_SET); |
| } |
| result.put(JSONConstants.FUNCTIONS, (functionNames == null ? Collections.EMPTY_LIST : functionNames)); |
| return result; |
| } |
| |
| /** |
| * @return the id for this script object |
| */ |
| public Long getId() { |
| return scriptId; |
| } |
| |
| /** |
| * Returns the {@link Breakpoint} at the given line number or <code>null</code> |
| * if there isn't one |
| * @param lineNumber |
| * @return |
| */ |
| public Breakpoint getBreakpoint(Integer line, String functionName) { |
| synchronized (lines) { |
| if(line != null) { |
| int value = line.intValue(); |
| if (value > -1 && value < lines.length && lines[value] != null) { |
| return lines[value].breakpoint; |
| } |
| } |
| if(functionNames != null) { |
| int index = functionNames.indexOf(functionName); |
| FunctionSource func = functionAt(index); |
| if(func != null && lines[func.linenumber()] != null) { |
| return lines[func.linenumber()].breakpoint; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Returns if the line number is valid wrt this script |
| * |
| * @param lineNumber |
| * @param functionName |
| * @return true if the line number is valid wrt this script |
| */ |
| public boolean isValid(Integer line, String functionName) { |
| synchronized (lines) { |
| if(line != null) { |
| int value = line.intValue(); |
| if (value > -1 && value < lines.length) { |
| return lines[value] != null; |
| } |
| } |
| if(functionNames != null) { |
| int index = functionNames.indexOf(functionName); |
| FunctionSource func = functionAt(index); |
| if(func != null) { |
| return lines[func.linenumber()] != null; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Copies items from the given object to this one |
| * @param script |
| */ |
| public void clone(ScriptSource script) { |
| this.scriptId = script.scriptId; |
| this.lines = script.lines; |
| } |
| |
| /** |
| * Adds a breakpoint to this script |
| * |
| * @param breakpoint |
| */ |
| public void addBreakpoint(Breakpoint breakpoint) { |
| synchronized (lines) { |
| Integer lineNumber = breakpoint.lineNumber; |
| if(lineNumber != null) { |
| int value = lineNumber.intValue(); |
| if(lines[value] != null) { |
| lines[value].breakpoint = breakpoint; |
| } |
| } |
| else if(functionNames != null) { |
| int index = functionNames.indexOf(breakpoint.functionName); |
| FunctionSource func = functionAt(index); |
| if(func != null) { |
| if(lines[func.linenumber] != null) { |
| lines[func.linenumber].breakpoint = breakpoint; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes a breakpoint from this script |
| * |
| * @param breakpoint |
| */ |
| public void removeBreakpoint(Breakpoint breakpoint) { |
| synchronized (lines) { |
| Integer lineNumber = breakpoint.lineNumber; |
| if(lineNumber != null) { |
| int value = lineNumber.intValue(); |
| if(lines[value] != null) { |
| lines[value].breakpoint = null; |
| } |
| } |
| else if(functionNames != null) { |
| int index = functionNames.indexOf(breakpoint.functionName); |
| FunctionSource func = functionAt(index); |
| if(func != null) { |
| if(lines[func.linenumber] != null) { |
| lines[func.linenumber].breakpoint = null; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return the string location of this script |
| */ |
| public String getLocation() { |
| return this.uri.toString(); |
| } |
| |
| /** |
| * Returns the {@link FunctionSource} at the given position iff the given index |
| * is within the bounds of the sources array. |
| * |
| * @param index |
| * @return the {@link FunctionSource} at the given index or <code>null</code> |
| */ |
| public FunctionSource functionAt(int index) { |
| synchronized (functionSources) { |
| if(functionSources != null && (index < functionSources.length && index > -1)) { |
| return functionSources[index]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the {@link FunctionSource} for the given {@link DebuggableScript} |
| * |
| * @param script |
| * @return the {@link FunctionSource} for the given {@link DebuggableScript} or <code>null</code> |
| */ |
| public FunctionSource getFunction(DebuggableScript script) { |
| synchronized (scriptToFunction) { |
| return (FunctionSource) scriptToFunction.get(script); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#toString() |
| */ |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("ScriptSource: [id - ").append(scriptId).append("] [uri - ").append(uri.toString()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| buffer.append("[generated - ").append(generated).append("]\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| if(lines != null) { |
| buffer.append("\tline numbers: {"); //$NON-NLS-1$ |
| for (int i = 0; i < lines.length; i++) { |
| if(lines[i] != null) { |
| buffer.append(i); |
| if(i < lines.length-1) { |
| buffer.append(", "); //$NON-NLS-1$ |
| } |
| } |
| } |
| buffer.append("}\n"); //$NON-NLS-1$ |
| } |
| if(functionNames != null) { |
| buffer.append("\tfunction names: {"); //$NON-NLS-1$ |
| for (int i = 0; i < functionNames.size(); i++) { |
| buffer.append(functionNames.get(i)); |
| if(i < functionNames.size()-1) { |
| buffer.append(", "); //$NON-NLS-1$ |
| } |
| } |
| buffer.append("}\n"); //$NON-NLS-1$ |
| } |
| if(functionSources != null) { |
| buffer.append("\tfunction sources:\n"); //$NON-NLS-1$ |
| for (int i = 0; i < functionSources.length; i++) { |
| buffer.append(functionSources[i]).append("\n"); //$NON-NLS-1$ |
| } |
| } |
| return buffer.toString(); |
| } |
| } |