blob: de63cda4cea789e6e985ec4cb379318ec5b063f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2011 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;
import org.mozilla.javascript.debug.Debugger;
/**
* 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$
}
/**
* Clears the breakpoints from this script out of the given {@link Debugger}
*/
void clearBreakpoints(RhinoDebuggerImpl debugger) {
if(this.lines != null) {
for (int i = 0; i < this.lines.length; i++) {
Breakpoint bp = lines[i].breakpoint;
if(bp != null) {
debugger.clearBreakpoint(bp.breakpointId);
}
}
}
}
/**
* 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;
}
/**
* 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();
}
}