diff --git a/js/rhino/build.xml b/js/rhino/build.xml index 76b9ec5e50b..c0247129167 100644 --- a/js/rhino/build.xml +++ b/js/rhino/build.xml @@ -16,6 +16,8 @@ Requires Ant version 1.2 + @@ -35,6 +37,8 @@ Requires Ant version 1.2 + @@ -50,7 +54,55 @@ Requires Ant version 1.2 - + + + + + + + + + import java.awt.Component; + + package org.mozilla.javascript.tools.debugger; + import java.awt.Component; + + + + import javax.swing.tree.*; + + package org.mozilla.javascript.tools.debugger; + import javax.swing.tree.*; + + + + import javax.swing.*; + + package org.mozilla.javascript.tools.debugger; + import javax.swing.*; + + + + import javax.swing.tree.TreeModel; + + package org.mozilla.javascript.tools.debugger; + import javax.swing.tree.TreeModel; + + + + import javax.swing.JTree; + + package org.mozilla.javascript.tools.debugger; + import javax.swing.JTree; + + + + + 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) { + } +}; + +class EvalWindow extends JInternalFrame +implements ActionListener { + + EvalTextArea evalTextArea; + + public void setEnabled(boolean b) { + super.setEnabled(b); + evalTextArea.setEnabled(b); + } + + public EvalWindow(String name, JSDebugger db) { + super(name, true, false, true, true); + evalTextArea = new EvalTextArea(db); + evalTextArea.setRows(24); + evalTextArea.setColumns(80); + JScrollPane scroller = new JScrollPane(evalTextArea); + setContentPane(scroller); + //scroller.setPreferredSize(new Dimension(600, 400)); + pack(); + setVisible(true); + } + + 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(); + } + } +}; + +class JSInternalConsole extends JInternalFrame +implements ActionListener { + + ConsoleTextArea consoleTextArea; + + public InputStream getIn() { + return consoleTextArea.getIn(); + } + + public PrintStream getOut() { + return consoleTextArea.getOut(); + } + + public PrintStream getErr() { + return consoleTextArea.getErr(); + } + + 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(); + setVisible(true); + } + + 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(); + } + } +}; + +class FilePopupMenu extends JPopupMenu { + FileTextArea w; + int x, y; + + FilePopupMenu(FileTextArea w) { + super(); + this.w = 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 to Cursor")); + item.addActionListener(w); + } + void show(JComponent comp, int x, int y) { + this.x = x; + this.y = y; + super.show(comp, x, y); + } +}; + +class FileTextArea extends JTextArea implements ActionListener, + PopupMenuListener, + KeyListener, + MouseListener { + FileWindow w; + FilePopupMenu popup; + + FileTextArea(FileWindow w) { + this.w = w; + popup = new FilePopupMenu(this); + popup.addPopupMenuListener(this); + addMouseListener(this); + addKeyListener(this); + setFont(new Font("Monospaced", 0, 12)); + } + + 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(); + } + } + } + + + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + public void mouseClicked(MouseEvent e) { + checkPopup(e); + } + public void mouseEntered(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + private void checkPopup(MouseEvent e) { + if(e.isPopupTrigger()) { + popup.show(this, e.getX(), e.getY()); + } + } + + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + public void popupMenuCanceled(PopupMenuEvent e) { + } + 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 to Cursor")) { + w.runToCursor(e); + } + } + public void keyPressed(KeyEvent e) { + switch(e.getKeyCode()) { + case KeyEvent.VK_BACK_SPACE: + case KeyEvent.VK_ENTER: + case KeyEvent.VK_DELETE: + e.consume(); + break; + } + } + public void keyTyped(KeyEvent e) { + e.consume(); + } + public void keyReleased(KeyEvent e) { + e.consume(); + } +} + +class MoreWindows extends JDialog implements ActionListener { + private String value = null; + private JList list; + Hashtable fileWindows; + JButton setButton; + JButton refreshButton; + JButton cancelButton; + + /** + * Show the initialized dialog. The first argument should + * be null if you want the dialog to come up in the center + * of the screen. Otherwise, the argument should be the + * component on top of which the dialog should appear. + */ + + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + private void setValue(String newValue) { + value = newValue; + list.setSelectedValue(value, true); + } + + 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); + JInternalFrame w = (JInternalFrame)fileWindows.get(value); + if(w != null) { + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } + } + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + }; + + MoreWindows(JFrame frame, Hashtable fileWindows, + String title, + String labelText) { + super(frame, title, true); + this.fileWindows = fileWindows; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //main 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(); + } +}; + +class FindFunction extends JDialog implements ActionListener { + private String value = null; + private JList list; + Hashtable functionMap; + JSDebugger db; + JButton setButton; + JButton refreshButton; + JButton cancelButton; + + /** + * Show the initialized dialog. The first argument should + * be null if you want the dialog to come up in the center + * of the screen. Otherwise, the argument should be the + * component on top of which the dialog should appear. + */ + + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + private void setValue(String newValue) { + value = newValue; + list.setSelectedValue(value, true); + } + + 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); + JSDebugger.SourceEntry sourceEntry = (JSDebugger.SourceEntry)functionMap.get(value); + DebuggableScript script = sourceEntry.fnOrScript; + if(script != null) { + String sourceName = script.getSourceName(); + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + Enumeration ee = script.getLineNumbers(); + int lineNumber = ((Integer)ee.nextElement()).intValue(); + FileWindow w = db.getFileWindow(sourceName); + if(w == null) { + (new CreateFileWindow(db, sourceName, sourceEntry.source.toString(), lineNumber)).run(); + w = db.getFileWindow(sourceName); + } + int start = w.getPosition(lineNumber-1); + w.select(start, start); + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } + } + + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + }; + + FindFunction(JSDebugger db, Hashtable functionMap, + String title, + String labelText) { + super(db, title, true); + this.functionMap = functionMap; + this.db = db; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //main part of the dialog + list = new JList(new DefaultListModel()); + DefaultListModel model = (DefaultListModel)list.getModel(); + model.clear(); + //model.fireIntervalRemoved(model, 0, size); + Enumeration e = functionMap.keys(); + String[] a = new String[functionMap.size()]; + int i = 0; + while(e.hasMoreElements()) { + a[i++] = e.nextElement().toString(); + } + java.util.Arrays.sort(a); + for(i = 0; i < a.length; i++) { + model.addElement(a[i]); + } + list.setSelectedIndex(0); + //model.fireIntervalAdded(model, 0, data.length); + 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)); + //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(); + } +}; + +class FileHeader extends JPanel implements MouseListener { + + FileWindow fileWindow; + + public void mouseEntered(MouseEvent e) { + } + public void mousePressed(MouseEvent e) { + if(e.getComponent() == this && + (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { + int x = e.getX(); + int y = e.getY(); + Font font = fileWindow.textArea.getFont(); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + int line = y/h; + fileWindow.toggleBreakPoint(line + 1); + } + } + public void mouseClicked(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mouseReleased(MouseEvent e) { + } + + FileHeader(FileWindow fileWindow) { + this.fileWindow = fileWindow; + addMouseListener(this); + update(); + } + + 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); + } + + 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 left = getX(); + 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 maxWidth = metrics.stringWidth(dummy); + 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 = false; + if(fileWindow.breakpoints.get(new Integer(pos)) != null) { + isBreakPoint = true; + } + text = Integer.toString(i + 1) + " "; + int w = metrics.stringWidth(text); + 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); + } + } + } +}; + +class FileWindow extends JInternalFrame { + + JSDebugger db; + FileTextArea textArea; + FileHeader fileHeader; + JScrollPane p; + int currentPos; + Hashtable breakpoints; + String url; + JLabel statusBar; + int lineNumChars; + String cursor = ">"; + String breakPoint = "*"; + + public void dispose() { + Enumeration e = breakpoints.keys(); + while(e.hasMoreElements()) { + Integer line = (Integer)breakpoints.get(e.nextElement()); + db.clearBreakPoint(url, line.intValue()); + } + db.removeWindow(this); + super.dispose(); + } + + void runToCursor(ActionEvent e) { + try { + db.runToCursor(url, + textArea.getLineOfOffset(textArea.getCaretPosition()) + 1, + e); + } catch(BadLocationException exc) { + } + } + + public int getPosition(int line) { + int result = -1; + try { + result = textArea.getLineStartOffset(line); + } catch(javax.swing.text.BadLocationException exc) { + } + return result; + } + + boolean isBreakPoint(int line) { + int pos = getPosition(line - 1); + return breakpoints.get(new Integer(pos)) != null; + } + + void toggleBreakPoint(int line) { + int pos = getPosition(line - 1); + Integer i = new Integer(pos); + if(breakpoints.get(i) == null) { + setBreakPoint(line); + } else { + clearBreakPoint(line); + } + } + + void setBreakPoint(int line) { + int actualLine = db.setBreakPoint(url, line); + if(actualLine != -1) { + int pos = getPosition(actualLine - 1); + breakpoints.put(new Integer(pos), new Integer(line)); + fileHeader.repaint(); + } + } + + void clearBreakPoint(int line) { + db.clearBreakPoint(url, line); + int pos = getPosition(line - 1); + Integer loc = new Integer(pos); + if(breakpoints.get(loc) != null) { + breakpoints.remove(loc); + fileHeader.repaint(); + } + } + + FileWindow(JSDebugger db, String fileName, String text) { + super(new File(fileName).getName(), true, true, true, true); + this.db = db; + breakpoints = (Hashtable)db.breakpointsMap.get(fileName); + if(breakpoints == null) { + breakpoints = new Hashtable(); + db.breakpointsMap.put(fileName, breakpoints); + } + setUrl(fileName); + 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); + setText(text); + textArea.select(0); + } + + public void setUrl(String fileName) { + // in case fileName is very long, try to set tool tip on frame + Component c = getComponent(1); + // this will work at least for Metal L&F + if(c != null && c instanceof JComponent) { + ((JComponent)c).setToolTipText(fileName); + } + url = fileName; + } + + public String getUrl() { + return url; + } + + void setText(String newText) { + if(!textArea.getText().equals(newText)) { + textArea.setText(newText); + int pos = 0; + if(currentPos != -1) { + pos = currentPos; + } + textArea.select(pos); + } + Enumeration e = breakpoints.keys(); + while(e.hasMoreElements()) { + Object key = e.nextElement(); + Integer line = (Integer)breakpoints.get(key); + if(db.setBreakPoint(url, line.intValue()) == -1) { + breakpoints.remove(key); + } + } + fileHeader.update(); + fileHeader.repaint(); + } + + void setPosition(int pos) { + textArea.select(pos); + currentPos = pos; + fileHeader.repaint(); + } + + void select(int start, int end) { + int docEnd = textArea.getDocument().getLength(); + textArea.select(docEnd, docEnd); + textArea.select(start, end); + } +}; + +class MyTableModel extends AbstractTableModel { + JSDebugger db; + Vector expressions; + Vector values; + MyTableModel(JSDebugger db) { + this.db = db; + expressions = new Vector(); + values = new Vector(); + expressions.addElement(""); + values.addElement(""); + } + + public int getColumnCount() { + return 2; + } + + public int getRowCount() { + return expressions.size(); + } + + public String getColumnName(int column) { + switch(column) { + case 0: + return "Expression"; + case 1: + return "Value"; + } + return null; + } + + public boolean isCellEditable(int row, int column) { + return true; + } + + public Object getValueAt(int row, int column) { + switch(column) { + case 0: + return expressions.elementAt(row); + case 1: + return values.elementAt(row); + } + return ""; + } + + 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 = db.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(); + } + } + + 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 = db.eval(expr); + if(result == null) result = ""; + } else { + result = ""; + } + result = result.replace('\n', ' '); + values.setElementAt(result, i); + } + fireTableDataChanged(); + } +}; + +class Evaluator extends JTable { + MyTableModel tableModel; + Evaluator(JSDebugger db) { + super(new MyTableModel(db)); + tableModel = (MyTableModel)getModel(); + } +} + + +class MyTreeTable extends JTreeTable { + + public MyTreeTable(TreeTableModel model) { + super(model); + } + + 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; + } +}; + +class ContextWindow extends JPanel implements ActionListener { + JComboBox context; + Vector toolTips; + JTabbedPane tabs; + JTabbedPane tabs2; + MyTreeTable thisTable; + MyTreeTable localsTable; + VariableModel model; + MyTableModel tableModel; + Evaluator evaluator; + EvalTextArea cmdLine; + JSplitPane split; + JSDebugger db; + boolean enabled; + ContextWindow(JSDebugger db) { + super(); + this.db = db; + enabled = false; + JPanel left = new JPanel(); + JToolBar t1 = new JToolBar("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(); + 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 AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + //return 3; + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + //return "Type"; + //case 2: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + JScrollPane jsp = new JScrollPane(thisTable); + jsp.getViewport().setViewSize(new Dimension(5,2)); + tabs.add("this", jsp); + localsTable = new MyTreeTable(new AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + 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(db); + cmdLine = new EvalTextArea(db); + cmdLine.requestFocus(); + tableModel = evaluator.tableModel; + jsp = new JScrollPane(evaluator); + JToolBar t2 = new JToolBar("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); + split.setResizeWeight(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; + HierarchyListener listener = new HierarchyListener() { + public void hierarchyChanged(HierarchyEvent e) { + Component thisParent = finalThis.getParent(); + if(thisParent == null) { + return; + } + Component parent = finalT1.getParent(); + boolean leftDocked = true; + boolean rightDocked = true; + if(parent != null) { + if(parent != finalP1) { + while(!(parent instanceof JFrame)) { + parent = parent.getParent(); + } + JFrame frame = (JFrame)parent; + frame.setResizable(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; + frame.setResizable(true); + rightDocked = false; + } else { + rightDocked = true; + } + } + JSplitPane split = (JSplitPane)thisParent; + if(leftDocked) { + if(rightDocked) { + finalSplit.setDividerLocation(0.5); + } else { + finalSplit.setDividerLocation(1.0); + } + split.setDividerLocation(0.66); + } else if(rightDocked) { + finalSplit.setDividerLocation(0.0); + split.setDividerLocation(0.66); + } else { + // both undocked + split.setDividerLocation(1.0); + } + } + }; + t1.addHierarchyListener(listener); + t2.addHierarchyListener(listener); + disable(); + } + + public void actionPerformed(ActionEvent e) { + if(!enabled) return; + if(e.getActionCommand().equals("ContextSwitch")) { + Context cx = Context.getCurrentContext(); + int frameIndex = context.getSelectedIndex(); + context.setToolTipText(toolTips.elementAt(frameIndex).toString()); + Scriptable obj; + int frameCount = cx.getFrameCount(); + if(frameIndex < frameCount) { + obj = cx.getFrame(frameIndex).getVariableObject(); + } else { + return; + } + NativeCall call = null; + if(obj instanceof NativeCall) { + call = (NativeCall)obj; + obj = call.getThisObj(); + } + JTree tree = thisTable.resetTree(model = new VariableModel(obj)); + if(call == null) { + tree = localsTable.resetTree(new AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + } else { + tree = localsTable.resetTree(model = new VariableModel(call)); + } + db.contextSwitch(frameIndex); + tableModel.updateModel(); + } + } + + public void disable() { + context.setEnabled(false); + thisTable.setEnabled(false); + localsTable.setEnabled(false); + evaluator.setEnabled(false); + cmdLine.setEnabled(false); + } + + public void enable() { + context.setEnabled(true); + thisTable.setEnabled(true); + localsTable.setEnabled(true); + evaluator.setEnabled(true); + cmdLine.setEnabled(true); + } + + public void disableUpdate() { + enabled = false; + } + + public void enableUpdate() { + enabled = true; + } +}; + +class CreateFileWindow implements Runnable { + + + JSDebugger db; + String url; + String text; + int line; + boolean activate; + + CreateFileWindow(JSDebugger db, + String url, String text, int line) { + this.db = db; + this.url = url; + this.text = text; + this.line = line; + this.activate = true; + } + + CreateFileWindow(JSDebugger db, + String url, String text, int line, boolean activate) { + this.db = db; + this.url = url; + this.text = text; + this.line = line; + this.activate = activate; + } + + public void run() { + FileWindow w = new FileWindow(db, url, text); + db.fileWindows.put(url, w); + if(db.currentWindow != null) { + db.currentWindow.setPosition(-1); + } + try { + w.setPosition(w.textArea.getLineStartOffset(line-1)); + } catch(BadLocationException exc) { + w.setPosition(-1); + } + w.setVisible(true); + db.desk.add(w); + db.currentWindow = w; + db.menubar.addFile(url); + if(activate) { + try { + w.setMaximum(true); + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } +} + +class SetFilePosition implements Runnable { + + JSDebugger db; + FileWindow w; + int line; + boolean activate; + + SetFilePosition(JSDebugger db, FileWindow w, int line) { + this.db = db; + this.w = w; + this.line = line; + activate = true; + } + + SetFilePosition(JSDebugger db, FileWindow w, int line, boolean activate) { + this.db = db; + this.w = w; + this.line = line; + this.activate = activate; + } + + public void run() { + JTextArea ta = w.textArea; + try { + int loc = ta.getLineStartOffset(line-1); + if(db.currentWindow != null && db.currentWindow != w) { + db.currentWindow.setPosition(-1); + } + w.setPosition(loc); + db.currentWindow = w; + } catch(BadLocationException exc) { + // fix me + } + if(activate) { + if(w.isIcon()) { + db.desk.getDesktopManager().deiconifyFrame(w); + } + db.desk.getDesktopManager().activateFrame(w); + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } +} + +class SetFileText implements Runnable { + + FileWindow w; + String text; + + SetFileText(FileWindow w, String text) { + this.w = w; this.text = text; + } + + public void run() { + w.setText(text); + } +} + +class UpdateContext implements Runnable { + JSDebugger db; + Context cx; + UpdateContext(JSDebugger db, Context cx) { + this.db = db; + this.cx = cx; + } + + public void run() { + db.context.enable(); + JComboBox ctx = db.context.context; + Vector toolTips = db.context.toolTips; + db.context.disableUpdate(); + int frameCount = cx.getFrameCount(); + ctx.removeAllItems(); + toolTips.clear(); + for(int i = 0; i < frameCount; i++) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(i); + String sourceName = frame.getSourceName(); + String location; + String shortName = sourceName; + int lineNumber = frame.getLineNumber(); + if(sourceName == null) { + if(lineNumber == -1) { + shortName = sourceName = ""; + } else { + shortName = sourceName = ""; + } + } else { + if(sourceName.length() > 20) { + shortName = new File(sourceName).getName(); + } + } + location = "\"" + shortName + "\", line " + lineNumber; + ctx.insertItemAt(location, i); + location = "\"" + sourceName + "\", line " + lineNumber; + toolTips.addElement(location); + } + db.context.enableUpdate(); + ctx.setSelectedIndex(0); + ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height)); + } +}; + +class Menubar extends JMenuBar implements ActionListener { + JMenu getDebugMenu() { + return getMenu(2); + } + Menubar(JSDebugger db) { + super(); + this.db = db; + String[] fileItems = {"Load...", "Exit"}; + String[] fileCmds = {"Load", "Exit"}; + char[] fileShortCuts = {'L', 'X'}; + 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) { + 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); + } + 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) { + item.setEnabled(false); + } + debugMenu.add(item); + } + 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); + + } + + 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 { + db.actionPerformed(e); + return; + } + try { + UIManager.setLookAndFeel(plaf_name); + SwingUtilities.updateComponentTreeUI(db); + SwingUtilities.updateComponentTreeUI(db.dlg); + } catch(Exception ignored) { + //ignored.printStackTrace(); + } + } + + public void addFile(String fileName) { + int count = windowMenu.getItemCount(); + JMenuItem item; + if(count == 4) { + windowMenu.addSeparator(); + count++; + } + if(count - 4 == 10) { + windowMenu.add(item = new JMenuItem("More Windows...", 'M')); + item.setActionCommand("More Windows..."); + item.addActionListener(this); + return; + } else if(count - 4 < 10) { + windowMenu.add(item = new JMenuItem((char)('0' + (count-4)) + " " + fileName, '0' + (count - 4))); + } else { + return; + } + item.setActionCommand(fileName); + item.addActionListener(this); + } + + JSDebugger db; + JMenu windowMenu; +}; + +class EnterInterrupt implements Runnable { + JSDebugger db; + Context cx; + EnterInterrupt(JSDebugger db, Context cx) { + this.db = db; + this.cx = cx; + } + public void run() { + Context newCx; + if((newCx = Context.enter(cx)) != cx) { + System.out.println("debugger error: Enter Interrupt: failed to obtain context: " + cx); + } + JMenu menu = db.getJMenuBar().getMenu(0); + menu.getItem(0).setEnabled(false); // File->Load + menu = db.getJMenuBar().getMenu(2); + menu.getItem(0).setEnabled(false); // Debug->Break + int count = menu.getItemCount(); + for(int i = 1; i < count; ++i) { + menu.getItem(i).setEnabled(true); + } + boolean b = false; + for(int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) { + db.toolBar.getComponent(ci).setEnabled(b); + b = true; + } + db.toolBar.setEnabled(true); + } +}; + +class ExitInterrupt implements Runnable { + JSDebugger db; + ExitInterrupt(JSDebugger db) { + this.db = db; + } + public void run() { + JMenu menu = db.getJMenuBar().getMenu(0); + menu.getItem(0).setEnabled(true); // File->Load + menu = db.getJMenuBar().getMenu(2); + menu.getItem(0).setEnabled(true); // Debug->Break + int count = menu.getItemCount(); + for(int i = 1; i < count; ++i) { + menu.getItem(i).setEnabled(false); + } + db.context.disable(); + boolean b = true; + for(int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) { + db.toolBar.getComponent(ci).setEnabled(b); + b = false; + } + db.console.consoleTextArea.requestFocus(); + } +}; + +class LoadFile implements Runnable { + Scriptable scope; + JSDebugger debugger; + String fileName; + LoadFile(JSDebugger debugger, Scriptable scope, String fileName) { + this.scope = scope; + this.fileName = fileName; + this.debugger = debugger; + } + public void run() { + Context cx = Context.enter(); + cx.setBreakNextLine(true); + try { + cx.evaluateReader(scope, new FileReader(fileName), + fileName, 1, null); + } catch(Exception exc) { + exc.printStackTrace(Main.getErr()); + } + cx.exit(); + } +} + + +public class JSDebugger extends JFrame implements Debugger, ContextListener { + + /* ContextListener interface */ + + java.util.HashSet contexts = new java.util.HashSet(); + + public void contextCreated(Context cx) { + cx.setDebugger(this); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + } + + public void contextEntered(Context cx) { + // If the debugger is attached to cx + // keep a reference to it even if it was detached + // from its thread (we cause that to happen below + // in interrupted) + if(!contexts.contains(cx)) { + if(cx.getDebugger() == this) { + contexts.add(cx); + } + } + } + + public void contextExited(Context cx) { + } + + public void contextReleased(Context cx) { + contexts.remove(cx); + } + + /* end ContextListener interface */ + + public void doBreak() { + synchronized(contexts) { + Iterator iter = contexts.iterator(); + while(iter.hasNext()) { + Context cx = (Context)iter.next(); + cx.setBreakNextLine(true); + } + } + } + + public void setVisible(boolean b) { + super.setVisible(b); + if(b) { + // this needs to be done after the window is visible + context.split.setDividerLocation(0.5); + } + } + + static final int STEP_OVER = 0; + static final int STEP_INTO = 1; + static final int STEP_OUT = 2; + static final int GO = 3; + static final int BREAK = 4; + static final int RUN_TO_CURSOR = 5; + + class ThreadState { + private int stopAtFrameDepth = -1; + }; + + private Hashtable threadState = new Hashtable(); + private Thread runToCursorThread; + private int runToCursorLine; + private String runToCursorFile; + private Hashtable sourceNames = new Hashtable(); + + class SourceEntry { + SourceEntry(StringBuffer source, DebuggableScript fnOrScript) { + this.source = source; + this.fnOrScript = fnOrScript; + } + StringBuffer source; + DebuggableScript fnOrScript; + }; + + Hashtable functionMap = new Hashtable(); + Hashtable breakpointsMap = new Hashtable(); + + public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, + StringBuffer source) { + String sourceName = fnOrScript.getSourceName(); + //System.out.println("sourceName = " + sourceName); + if (sourceName != null && !sourceName.equals("")) { + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + } else { + Enumeration e = fnOrScript.getLineNumbers(); + if(!e.hasMoreElements() || ((Integer)e.nextElement()).intValue() == -1) { + sourceName = ""; + } else { + sourceName = ""; + } + } + Vector v = (Vector) sourceNames.get(sourceName); + if (v == null) { + v = new Vector(); + sourceNames.put(sourceName, v); + } + SourceEntry entry = new SourceEntry(source, fnOrScript); + v.addElement(entry); + if(fnOrScript.getScriptable() instanceof NativeFunction) { + NativeFunction f = (NativeFunction)fnOrScript.getScriptable(); + String name = f.jsGet_name(); + if(name.length() > 0 && !name.equals("anonymous")) { + functionMap.put(name, entry); + } + } + loadedFile(sourceName, source.toString()); + } + + public void handleBreakpointHit(Context cx) { + interrupted(cx); + } + + public void handleExceptionThrown(Context cx, Object e) { + if(cx.getFrameCount() == 0) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String sourceName = frame.getSourceName(); + int lineNumber = frame.getLineNumber(); + FileWindow w = null; + if(sourceName == null) { + if(lineNumber == -1) { + sourceName = ""; + } else { + sourceName = ""; + } + } else { + w = getFileWindow(sourceName); + } + if(e instanceof NativeJavaObject) { + e = ((NativeJavaObject)e).unwrap(); + } + String msg = e.toString(); + if(msg == null || msg.length() == 0) { + msg = e.getClass().toString(); + } + msg += " (" + sourceName + ", line " + lineNumber + ")"; + if(w != null) { + //swingInvoke(new SetFilePosition(this, w, lineNumber)); + } + MessageDialogWrapper.showMessageDialog(this, + msg, + "Exception in Script", + JOptionPane.ERROR_MESSAGE); + } + } + + JDesktopPane desk; + ContextWindow context; + Menubar menubar; + JToolBar toolBar; + JSInternalConsole console; + EvalWindow evalWindow; + JSplitPane split1; + JLabel statusBar; + + public JSDebugger(String name) { + super(name); + setJMenuBar(menubar = new Menubar(this)); + toolBar = new JToolBar(); + JButton button; + toolBar.add(button = new JButton("Break")); + button.setToolTipText("Break"); + button.setActionCommand("Break"); + button.addActionListener(menubar); + button.setEnabled(true); + toolBar.add(button = new JButton("Go")); + button.setToolTipText("Go"); + button.setActionCommand("Go"); + button.addActionListener(menubar); + button.setEnabled(false); + toolBar.add(button = new JButton("Step Into")); + button.setToolTipText("Step Into"); + button.setActionCommand("Step Into"); + button.addActionListener(menubar); + button.setEnabled(false); + toolBar.add(button = new JButton("Step Over")); + button.setToolTipText("Step Over"); + button.setActionCommand("Step Over"); + button.setEnabled(false); + button.addActionListener(menubar); + toolBar.add(button = new JButton("Step Out")); + button.setToolTipText("Step Out"); + button.setActionCommand("Step Out"); + button.setEnabled(false); + button.addActionListener(menubar); + //toolBar.add(button = new JButton(new String("Run to Cursor"))); + //button.setToolTipText("Run to Cursor"); + //button.setActionCommand("Run to Cursor"); + //button.setEnabled(false); + //button.addActionListener(menubar); + // getContentPane().setLayout(new BorderLayout()); + getContentPane().add(toolBar, BorderLayout.NORTH); + desk = new JDesktopPane(); + desk.setPreferredSize(new Dimension(600, 300)); + desk.setMinimumSize(new Dimension(150, 50)); + desk.add(console = new JSInternalConsole("JavaScript Console")); + desk.getDesktopManager().activateFrame(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); + split1.setResizeWeight(0.66); + getContentPane().add(split1, BorderLayout.CENTER); + statusBar = new JLabel(); + statusBar.setText("Thread: "); + getContentPane().add(statusBar, BorderLayout.SOUTH); + dlg = new JFileChooser(); + dlg.setDialogTitle("Select a file to load"); + 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); + try { + console.setMaximum(true); + console.setSelected(true); + } catch(Exception exc) { + } + } + + public InputStream getIn() { + return console.getIn(); + } + + public PrintStream getOut() { + return console.getOut(); + } + + public PrintStream getErr() { + return console.getErr(); + } + + public static void main(String[] args) { + try { + final JSDebugger sdb = new JSDebugger("Rhino JavaScript Debugger"); + swingInvoke(new Runnable() { + public void run() { + sdb.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + sdb.pack(); + sdb.setSize(600, 460); + sdb.setVisible(true); + } + }); + System.setIn(sdb.getIn()); + System.setOut(sdb.getOut()); + System.setErr(sdb.getErr()); + Context.addContextListener(sdb); + String className = java.lang.System.getProperty("rhino.shell"); + if(className != null) { + try { + Class clazz = Class.forName(className); + Method mainMethod = + clazz.getMethod("main", new Class[] {String[].class}); + mainMethod.invoke(null, new Object[] {args}); + return; + } catch(Exception exc) { + } + } + Main.main(args); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + public FileWindow getFileWindow(String fileName) { + if(fileName == null || fileName.equals("") || fileName.equals("")) { + return null; + } + try { + // Can't do this if we're remote debugging + String file = new File(fileName).getCanonicalPath(); + Enumeration e = fileWindows.keys(); + for(; e.hasMoreElements(); ) { + String name = (String)e.nextElement(); + if(file.equals(new File(name).getCanonicalPath())) { + FileWindow w = (FileWindow)fileWindows.get(name); + w.setUrl(fileName); + return w; + } + } + } catch(IOException exc) { + } + return (FileWindow)fileWindows.get(fileName); + } + + public void loadedFile(String fileName, String text) { + FileWindow w = getFileWindow(fileName); + if(w != null) { + swingInvoke(new SetFileText(w, text)); + } + } + + static void swingInvoke(Runnable f) { + try { + SwingUtilities.invokeAndWait(f); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + static void swingInvokeLater(Runnable f) { + try { + SwingUtilities.invokeLater(f); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + int frameIndex = -1; + + void contextSwitch(int frameIndex) { + Context cx = Context.getCurrentContext(); + if(cx != null) { + int frameCount = cx.getFrameCount(); + if(frameIndex < 0 || frameIndex >= frameCount) { + return; + } + this.frameIndex = frameIndex; + org.mozilla.javascript.debug.Frame frame = cx.getFrame(frameIndex); + String sourceName = frame.getSourceName(); + if(sourceName == null || sourceName.equals("")) { + console.show(); + console.consoleTextArea.requestFocus(); + return; + } + if(sourceName == "") { + return; + } + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + int lineNumber = frame.getLineNumber(); + this.frameIndex = frameIndex; + FileWindow w = getFileWindow(sourceName); + if(w != null) { + SetFilePosition action = new SetFilePosition(this, w, lineNumber); + action.run(); + } else { + Vector v = (Vector)sourceNames.get(sourceName); + String source = ((SourceEntry)v.elementAt(0)).source.toString(); + CreateFileWindow action = new CreateFileWindow(this, + sourceName, + source, + lineNumber); + action.run(); + } + } + } + + + public static void start(Context cx, Scriptable scope) { + } + + public void interrupted(Context cx) { + synchronized(debuggerMonitor) { + Thread thread = Thread.currentThread(); + statusBar.setText("Thread: " + thread.toString()); + ThreadState state = (ThreadState)threadState.get(thread); + int stopAtFrameDepth = -1; + if(state != null) { + stopAtFrameDepth = state.stopAtFrameDepth; + } + if(runToCursorFile != null && thread == runToCursorThread) { + int frameCount = cx.getFrameCount(); + if(frameCount > 0) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String sourceName = frame.getSourceName(); + if(sourceName != null) { + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + if(sourceName.equals(runToCursorFile)) { + int lineNumber = frame.getLineNumber(); + if(lineNumber == runToCursorLine) { + stopAtFrameDepth = -1; + runToCursorFile = null; + } else { + FileWindow w = getFileWindow(sourceName); + if(w == null || + !w.isBreakPoint(lineNumber)) { + return; + } else { + runToCursorFile = null; + } + } + } + } + } else { + } + } + if(stopAtFrameDepth > 0) { + if (cx.getFrameCount() > stopAtFrameDepth) + return; + } + if(state != null) { + state.stopAtFrameDepth = -1; + } + int frameCount = cx.getFrameCount(); + this.frameIndex = frameCount -1; + int line = 0; + if(frameCount == 0) { + return; + } + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String fileName = frame.getSourceName(); + if(fileName != null && !fileName.equals("")) { + try { + fileName = new File(fileName).getCanonicalPath(); + } catch(IOException exc) { + } + } + cx.setBreakNextLine(false); + line = frame.getLineNumber(); + int enterCount = 0; + cx.exit(); + while(Context.getCurrentContext() != null) { + Context.exit(); + enterCount++; + } + if(fileName != null && !fileName.equals("")) { + FileWindow w = (FileWindow)getFileWindow(fileName); + if(w != null) { + SetFilePosition action = new SetFilePosition(this, w, line); + swingInvoke(action); + } else { + Vector v = (Vector)sourceNames.get(fileName); + String source = ((SourceEntry)v.elementAt(0)).source.toString(); + CreateFileWindow action = new CreateFileWindow(this, + fileName, + source, + line); + swingInvoke(action); + } + } else { + final JInternalFrame finalConsole = console; + swingInvoke(new Runnable() { + public void run() { + finalConsole.show(); + } + }); + } + swingInvoke(new EnterInterrupt(this, cx)); + swingInvoke(new UpdateContext(this, cx)); + int returnValue; + synchronized(monitor) { + this.returnValue = -1; + try { + while(this.returnValue == -1) { + monitor.wait(); + } + returnValue = this.returnValue; + } catch(InterruptedException exc) { + return; + } + } + final Context finalContext = cx; + swingInvoke(new Runnable() { + public void run() { + finalContext.exit(); + } + }); + swingInvoke(new ExitInterrupt(this)); + Context current; + if((current = Context.enter(cx)) != cx) { + System.out.println("debugger error: cx = " + cx + " current = " + current); + + } + while(enterCount > 0) { + Context.enter(); + enterCount--; + } + switch(returnValue) { + case STEP_OVER: + cx.setBreakNextLine(true); + stopAtFrameDepth = cx.getFrameCount(); + if(state == null) { + state = new ThreadState(); + threadState.put(thread, state); + } + state.stopAtFrameDepth = stopAtFrameDepth; + break; + case STEP_INTO: + cx.setBreakNextLine(true); + if(state != null) { + state.stopAtFrameDepth = -1; + } + break; + case STEP_OUT: + cx.setBreakNextLine(true); + if(state == null) { + state = new ThreadState(); + threadState.put(thread, state); + } + state.stopAtFrameDepth = cx.getFrameCount() - 1; + break; + case RUN_TO_CURSOR: + cx.setBreakNextLine(true); + if(state != null) { + state.stopAtFrameDepth = -1; + } + break; + } + } + } + + JFileChooser dlg; + + public String chooseFile() { + File CWD = null; + String dir = System.getProperty("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) { + String result = dlg.getSelectedFile().getPath(); + CWD = dlg.getSelectedFile().getParentFile(); + java.lang.System.setProperty("user.dir", CWD.getPath()); + return result; + } + return null; + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + int returnValue = -1; + if(cmd.equals("Step Over")) { + returnValue = STEP_OVER; + } else if(cmd.equals("Step Into")) { + returnValue = STEP_INTO; + } else if(cmd.equals("Step Out")) { + returnValue = STEP_OUT; + } else if(cmd.equals("Go")) { + returnValue = GO; + } else if(cmd.equals("Break")) { + doBreak(); + } else if(cmd.equals("Run to Cursor")) { + returnValue = RUN_TO_CURSOR; + } else if(cmd.equals("Exit")) { + System.exit(0); + } else if(cmd.equals("Load")) { + String fileName = chooseFile(); + if(fileName != null) { + new Thread(new LoadFile(this, Main.getScope(), + fileName)).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(); + try { + console.setSelected(true); + } catch(Exception exc) { + } + 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, functionMap, "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; + if(w.isIcon()) { + desk.getDesktopManager().deiconifyFrame(w); + } + w.show(); + try { + w.setSelected(true); + } catch(Exception exc) { + } + } + } + if(returnValue != -1) { + if(currentWindow != null) currentWindow.setPosition(-1); + synchronized(monitor) { + this.returnValue = returnValue; + monitor.notify(); + } + } + } + + void runToCursor(String fileName, + int lineNumber, + ActionEvent evt) { + Vector v = (Vector) sourceNames.get(fileName); + if(v == null) { + System.out.println("debugger error: Couldn't find source: " + fileName); + } + int i; + SourceEntry se = null; + for (i = v.size() -1; i >= 0; i--) { + se = (SourceEntry) v.elementAt(i); + if (se.fnOrScript.removeBreakpoint(lineNumber)) { + se.fnOrScript.placeBreakpoint(lineNumber); + break; + } else if(se.fnOrScript.placeBreakpoint(lineNumber)) { + se.fnOrScript.removeBreakpoint(lineNumber); + break; + } + } + if(i >= 0) { + runToCursorFile = fileName; + runToCursorLine = lineNumber; + actionPerformed(evt); + } + } + + int setBreakPoint(String sourceName, int lineNumber) { + Vector v = (Vector) sourceNames.get(sourceName); + if(v == null) return -1; + int i=0; + int result = -1; + for (i = v.size() -1; i >= 0; i--) { + SourceEntry se = (SourceEntry) v.elementAt(i); + if (se.fnOrScript.placeBreakpoint(lineNumber)) { + result = lineNumber; + } + } + return result; + } + + void clearBreakPoint(String sourceName, int lineNumber) { + Vector v = (Vector) sourceNames.get(sourceName); + if(v == null) return; + int i=0; + for (; i < v.size(); i++) { + SourceEntry se = (SourceEntry) v.elementAt(i); + se.fnOrScript.removeBreakpoint(lineNumber); + } + } + + JMenu getWindowMenu() { + return menubar.getMenu(3); + } + + public void removeWindow(FileWindow w) { + fileWindows.remove(w.getUrl()); + JMenu windowMenu = getWindowMenu(); + int count = windowMenu.getItemCount(); + JMenuItem lastItem = windowMenu.getItem(count -1); + 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(w.getUrl())) { + 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 -2; i++) { + JMenuItem thisItem = windowMenu.getItem(i); + if(thisItem != null) { + //1 D:\foo.js + //2 D:\bar.js + text = thisItem.getText(); + pos = text.indexOf(' '); + thisItem.setText((char)('0' + j) + " " + + text.substring(pos + 1)); + thisItem.setMnemonic('0' + j); + j++; + } + } + if(count - 6 < 10 && lastItem != item) { + if(lastItem.getText().equals("More Windows...")) { + windowMenu.remove(lastItem); + } + } + } + break; + } + } + windowMenu.revalidate(); + } + + + public String eval(String expr) { + Context cx = Context.getCurrentContext(); + if(cx == null) return "undefined"; + if(frameIndex >= cx.getFrameCount()) { + return "undefined"; + } + String resultString; + cx.setDebugger(null); + cx.setGeneratingDebug(false); + cx.setOptimizationLevel(-1); + boolean breakNextLine = cx.getBreakNextLine(); + cx.setBreakNextLine(false); + try { + Scriptable scope; + scope = cx.getFrame(frameIndex).getVariableObject(); + Object result; + if(scope instanceof NativeCall) { + NativeCall call = (NativeCall)scope; + result = NativeGlobal.evalSpecial(cx, call, + call.getThisObj(), + new Object[]{expr}, + "", 1); + } else { + result = cx.evaluateString(scope, + expr, + "", + 0, + null); + } + try { + resultString = ScriptRuntime.toString(result); + } catch(Exception exc) { + resultString = result.toString(); + } + } catch(Exception exc) { + resultString = exc.getMessage(); + } + cx.setDebugger(this); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + cx.setBreakNextLine(breakNextLine); + return resultString; + } + + java.util.Hashtable fileWindows = new java.util.Hashtable(); + FileWindow currentWindow; + Object monitor = new Object(); + Object swingMonitor = new Object(); + Object debuggerMonitor = new Object(); + int returnValue = -1; +}; + + + + + + + diff --git a/js/rhino/org/mozilla/javascript/tools/debugger/VariableModel.java b/js/rhino/org/mozilla/javascript/tools/debugger/VariableModel.java new file mode 100644 index 00000000000..e69de29bb2d diff --git a/js/rhino/org/mozilla/javascript/tools/shell/ConsoleTextArea.java b/js/rhino/org/mozilla/javascript/tools/shell/ConsoleTextArea.java new file mode 100644 index 00000000000..e69de29bb2d diff --git a/js/rhino/org/mozilla/javascript/tools/shell/JSConsole.java b/js/rhino/org/mozilla/javascript/tools/shell/JSConsole.java new file mode 100644 index 00000000000..2eff3b82807 --- /dev/null +++ b/js/rhino/org/mozilla/javascript/tools/shell/JSConsole.java @@ -0,0 +1,211 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * 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 See Beyond are + * Copyright (C) 2000 See Beyond Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (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 NPL, indicate your decision by + * deleting the provisions above and replace 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 NPL or the GPL. + */ +package org.mozilla.javascript.tools.shell; +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.util.*; +import javax.swing.text.Document; +import javax.swing.text.Segment; + + +public class JSConsole extends JFrame implements ActionListener { + + private File CWD; + private JFileChooser dlg; + private ConsoleTextArea consoleTextArea; + + public String chooseFile() { + if(CWD == null) { + String dir = System.getProperty("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 = dlg.getSelectedFile().getParentFile(); + return result; + } + return null; + } + + public static void main(String args[]) { + JSConsole console = 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/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/Main.java b/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/Main.java new file mode 100644 index 00000000000..9f2ba13f247 --- /dev/null +++ b/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/Main.java @@ -0,0 +1,2571 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * 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 See Beyond are + * Copyright (C) 2000 See Beyond Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (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 NPL, indicate your decision by + * deleting the provisions above and replace 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 NPL or the GPL. + */ + +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.StringTokenizer; + +import org.mozilla.javascript.*; +import org.mozilla.javascript.debug.*; +import org.mozilla.javascript.tools.shell.Main; +import org.mozilla.javascript.tools.shell.ConsoleTextArea; +import java.util.*; +import java.io.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import java.lang.reflect.Method; + +class MessageDialogWrapper { + static void showMessageDialog(Component parent, String msg, String title, + int flags) { + JOptionPane.showMessageDialog(parent, + msg, + title, + flags); + } +}; + +class EvalTextArea extends JTextArea implements KeyListener, +DocumentListener { + JSDebugger db; + 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 EvalTextArea(JSDebugger db) { + super(); + this.db = db; + 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(); + } + + 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(); + Context cx = Context.getCurrentContext(); + if(cx.stringIsCompilableUnit(text)) { + history.addElement(text); + historyIndex = history.size(); + String result = db.eval(text); + append("\n"); + append(result); + append("\n% "); + outputMark = doc.getLength(); + } else { + append("\n"); + } + } + + 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) { + } +}; + +class EvalWindow extends JInternalFrame +implements ActionListener { + + EvalTextArea evalTextArea; + + public void setEnabled(boolean b) { + super.setEnabled(b); + evalTextArea.setEnabled(b); + } + + public EvalWindow(String name, JSDebugger db) { + super(name, true, false, true, true); + evalTextArea = new EvalTextArea(db); + evalTextArea.setRows(24); + evalTextArea.setColumns(80); + JScrollPane scroller = new JScrollPane(evalTextArea); + setContentPane(scroller); + //scroller.setPreferredSize(new Dimension(600, 400)); + pack(); + setVisible(true); + } + + 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(); + } + } +}; + +class JSInternalConsole extends JInternalFrame +implements ActionListener { + + ConsoleTextArea consoleTextArea; + + public InputStream getIn() { + return consoleTextArea.getIn(); + } + + public PrintStream getOut() { + return consoleTextArea.getOut(); + } + + public PrintStream getErr() { + return consoleTextArea.getErr(); + } + + 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(); + setVisible(true); + } + + 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(); + } + } +}; + +class FilePopupMenu extends JPopupMenu { + FileTextArea w; + int x, y; + + FilePopupMenu(FileTextArea w) { + super(); + this.w = 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 to Cursor")); + item.addActionListener(w); + } + void show(JComponent comp, int x, int y) { + this.x = x; + this.y = y; + super.show(comp, x, y); + } +}; + +class FileTextArea extends JTextArea implements ActionListener, + PopupMenuListener, + KeyListener, + MouseListener { + FileWindow w; + FilePopupMenu popup; + + FileTextArea(FileWindow w) { + this.w = w; + popup = new FilePopupMenu(this); + popup.addPopupMenuListener(this); + addMouseListener(this); + addKeyListener(this); + setFont(new Font("Monospaced", 0, 12)); + } + + 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(); + } + } + } + + + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + public void mouseClicked(MouseEvent e) { + checkPopup(e); + } + public void mouseEntered(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + private void checkPopup(MouseEvent e) { + if(e.isPopupTrigger()) { + popup.show(this, e.getX(), e.getY()); + } + } + + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + } + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + public void popupMenuCanceled(PopupMenuEvent e) { + } + 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 to Cursor")) { + w.runToCursor(e); + } + } + public void keyPressed(KeyEvent e) { + switch(e.getKeyCode()) { + case KeyEvent.VK_BACK_SPACE: + case KeyEvent.VK_ENTER: + case KeyEvent.VK_DELETE: + e.consume(); + break; + } + } + public void keyTyped(KeyEvent e) { + e.consume(); + } + public void keyReleased(KeyEvent e) { + e.consume(); + } +} + +class MoreWindows extends JDialog implements ActionListener { + private String value = null; + private JList list; + Hashtable fileWindows; + JButton setButton; + JButton refreshButton; + JButton cancelButton; + + /** + * Show the initialized dialog. The first argument should + * be null if you want the dialog to come up in the center + * of the screen. Otherwise, the argument should be the + * component on top of which the dialog should appear. + */ + + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + private void setValue(String newValue) { + value = newValue; + list.setSelectedValue(value, true); + } + + 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); + JInternalFrame w = (JInternalFrame)fileWindows.get(value); + if(w != null) { + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } + } + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + }; + + MoreWindows(JFrame frame, Hashtable fileWindows, + String title, + String labelText) { + super(frame, title, true); + this.fileWindows = fileWindows; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //main 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(); + } +}; + +class FindFunction extends JDialog implements ActionListener { + private String value = null; + private JList list; + Hashtable functionMap; + JSDebugger db; + JButton setButton; + JButton refreshButton; + JButton cancelButton; + + /** + * Show the initialized dialog. The first argument should + * be null if you want the dialog to come up in the center + * of the screen. Otherwise, the argument should be the + * component on top of which the dialog should appear. + */ + + public String showDialog(Component comp) { + value = null; + setLocationRelativeTo(comp); + setVisible(true); + return value; + } + + private void setValue(String newValue) { + value = newValue; + list.setSelectedValue(value, true); + } + + 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); + JSDebugger.SourceEntry sourceEntry = (JSDebugger.SourceEntry)functionMap.get(value); + DebuggableScript script = sourceEntry.fnOrScript; + if(script != null) { + String sourceName = script.getSourceName(); + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + Enumeration ee = script.getLineNumbers(); + int lineNumber = ((Integer)ee.nextElement()).intValue(); + FileWindow w = db.getFileWindow(sourceName); + if(w == null) { + (new CreateFileWindow(db, sourceName, sourceEntry.source.toString(), lineNumber)).run(); + w = db.getFileWindow(sourceName); + } + int start = w.getPosition(lineNumber-1); + w.select(start, start); + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } + } + + class MouseHandler extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + setButton.doClick(); + } + } + }; + + FindFunction(JSDebugger db, Hashtable functionMap, + String title, + String labelText) { + super(db, title, true); + this.functionMap = functionMap; + this.db = db; + //buttons + cancelButton = new JButton("Cancel"); + setButton = new JButton("Select"); + cancelButton.addActionListener(this); + setButton.addActionListener(this); + getRootPane().setDefaultButton(setButton); + + //main part of the dialog + list = new JList(new DefaultListModel()); + DefaultListModel model = (DefaultListModel)list.getModel(); + model.clear(); + //model.fireIntervalRemoved(model, 0, size); + Enumeration e = functionMap.keys(); + String[] a = new String[functionMap.size()]; + int i = 0; + while(e.hasMoreElements()) { + a[i++] = e.nextElement().toString(); + } + java.util.Arrays.sort(a); + for(i = 0; i < a.length; i++) { + model.addElement(a[i]); + } + list.setSelectedIndex(0); + //model.fireIntervalAdded(model, 0, data.length); + 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)); + //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(); + } +}; + +class FileHeader extends JPanel implements MouseListener { + + FileWindow fileWindow; + + public void mouseEntered(MouseEvent e) { + } + public void mousePressed(MouseEvent e) { + if(e.getComponent() == this && + (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { + int x = e.getX(); + int y = e.getY(); + Font font = fileWindow.textArea.getFont(); + FontMetrics metrics = getFontMetrics(font); + int h = metrics.getHeight(); + int line = y/h; + fileWindow.toggleBreakPoint(line + 1); + } + } + public void mouseClicked(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mouseReleased(MouseEvent e) { + } + + FileHeader(FileWindow fileWindow) { + this.fileWindow = fileWindow; + addMouseListener(this); + update(); + } + + 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); + } + + 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 left = getX(); + 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 maxWidth = metrics.stringWidth(dummy); + 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 = false; + if(fileWindow.breakpoints.get(new Integer(pos)) != null) { + isBreakPoint = true; + } + text = Integer.toString(i + 1) + " "; + int w = metrics.stringWidth(text); + 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); + } + } + } +}; + +class FileWindow extends JInternalFrame { + + JSDebugger db; + FileTextArea textArea; + FileHeader fileHeader; + JScrollPane p; + int currentPos; + Hashtable breakpoints; + String url; + JLabel statusBar; + int lineNumChars; + String cursor = ">"; + String breakPoint = "*"; + + public void dispose() { + Enumeration e = breakpoints.keys(); + while(e.hasMoreElements()) { + Integer line = (Integer)breakpoints.get(e.nextElement()); + db.clearBreakPoint(url, line.intValue()); + } + db.removeWindow(this); + super.dispose(); + } + + void runToCursor(ActionEvent e) { + try { + db.runToCursor(url, + textArea.getLineOfOffset(textArea.getCaretPosition()) + 1, + e); + } catch(BadLocationException exc) { + } + } + + public int getPosition(int line) { + int result = -1; + try { + result = textArea.getLineStartOffset(line); + } catch(javax.swing.text.BadLocationException exc) { + } + return result; + } + + boolean isBreakPoint(int line) { + int pos = getPosition(line - 1); + return breakpoints.get(new Integer(pos)) != null; + } + + void toggleBreakPoint(int line) { + int pos = getPosition(line - 1); + Integer i = new Integer(pos); + if(breakpoints.get(i) == null) { + setBreakPoint(line); + } else { + clearBreakPoint(line); + } + } + + void setBreakPoint(int line) { + int actualLine = db.setBreakPoint(url, line); + if(actualLine != -1) { + int pos = getPosition(actualLine - 1); + breakpoints.put(new Integer(pos), new Integer(line)); + fileHeader.repaint(); + } + } + + void clearBreakPoint(int line) { + db.clearBreakPoint(url, line); + int pos = getPosition(line - 1); + Integer loc = new Integer(pos); + if(breakpoints.get(loc) != null) { + breakpoints.remove(loc); + fileHeader.repaint(); + } + } + + FileWindow(JSDebugger db, String fileName, String text) { + super(new File(fileName).getName(), true, true, true, true); + this.db = db; + breakpoints = (Hashtable)db.breakpointsMap.get(fileName); + if(breakpoints == null) { + breakpoints = new Hashtable(); + db.breakpointsMap.put(fileName, breakpoints); + } + setUrl(fileName); + 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); + setText(text); + textArea.select(0); + } + + public void setUrl(String fileName) { + // in case fileName is very long, try to set tool tip on frame + Component c = getComponent(1); + // this will work at least for Metal L&F + if(c != null && c instanceof JComponent) { + ((JComponent)c).setToolTipText(fileName); + } + url = fileName; + } + + public String getUrl() { + return url; + } + + void setText(String newText) { + if(!textArea.getText().equals(newText)) { + textArea.setText(newText); + int pos = 0; + if(currentPos != -1) { + pos = currentPos; + } + textArea.select(pos); + } + Enumeration e = breakpoints.keys(); + while(e.hasMoreElements()) { + Object key = e.nextElement(); + Integer line = (Integer)breakpoints.get(key); + if(db.setBreakPoint(url, line.intValue()) == -1) { + breakpoints.remove(key); + } + } + fileHeader.update(); + fileHeader.repaint(); + } + + void setPosition(int pos) { + textArea.select(pos); + currentPos = pos; + fileHeader.repaint(); + } + + void select(int start, int end) { + int docEnd = textArea.getDocument().getLength(); + textArea.select(docEnd, docEnd); + textArea.select(start, end); + } +}; + +class MyTableModel extends AbstractTableModel { + JSDebugger db; + Vector expressions; + Vector values; + MyTableModel(JSDebugger db) { + this.db = db; + expressions = new Vector(); + values = new Vector(); + expressions.addElement(""); + values.addElement(""); + } + + public int getColumnCount() { + return 2; + } + + public int getRowCount() { + return expressions.size(); + } + + public String getColumnName(int column) { + switch(column) { + case 0: + return "Expression"; + case 1: + return "Value"; + } + return null; + } + + public boolean isCellEditable(int row, int column) { + return true; + } + + public Object getValueAt(int row, int column) { + switch(column) { + case 0: + return expressions.elementAt(row); + case 1: + return values.elementAt(row); + } + return ""; + } + + 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 = db.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(); + } + } + + 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 = db.eval(expr); + if(result == null) result = ""; + } else { + result = ""; + } + result = result.replace('\n', ' '); + values.setElementAt(result, i); + } + fireTableDataChanged(); + } +}; + +class Evaluator extends JTable { + MyTableModel tableModel; + Evaluator(JSDebugger db) { + super(new MyTableModel(db)); + tableModel = (MyTableModel)getModel(); + } +} + + +class MyTreeTable extends JTreeTable { + + public MyTreeTable(TreeTableModel model) { + super(model); + } + + 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; + } +}; + +class ContextWindow extends JPanel implements ActionListener { + JComboBox context; + Vector toolTips; + JTabbedPane tabs; + JTabbedPane tabs2; + MyTreeTable thisTable; + MyTreeTable localsTable; + VariableModel model; + MyTableModel tableModel; + Evaluator evaluator; + EvalTextArea cmdLine; + JSplitPane split; + JSDebugger db; + boolean enabled; + ContextWindow(JSDebugger db) { + super(); + this.db = db; + enabled = false; + JPanel left = new JPanel(); + JToolBar t1 = new JToolBar("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(); + 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 AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + //return 3; + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + //return "Type"; + //case 2: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + JScrollPane jsp = new JScrollPane(thisTable); + jsp.getViewport().setViewSize(new Dimension(5,2)); + tabs.add("this", jsp); + localsTable = new MyTreeTable(new AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + 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(db); + cmdLine = new EvalTextArea(db); + cmdLine.requestFocus(); + tableModel = evaluator.tableModel; + jsp = new JScrollPane(evaluator); + JToolBar t2 = new JToolBar("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); + split.setResizeWeight(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; + HierarchyListener listener = new HierarchyListener() { + public void hierarchyChanged(HierarchyEvent e) { + Component thisParent = finalThis.getParent(); + if(thisParent == null) { + return; + } + Component parent = finalT1.getParent(); + boolean leftDocked = true; + boolean rightDocked = true; + if(parent != null) { + if(parent != finalP1) { + while(!(parent instanceof JFrame)) { + parent = parent.getParent(); + } + JFrame frame = (JFrame)parent; + frame.setResizable(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; + frame.setResizable(true); + rightDocked = false; + } else { + rightDocked = true; + } + } + JSplitPane split = (JSplitPane)thisParent; + if(leftDocked) { + if(rightDocked) { + finalSplit.setDividerLocation(0.5); + } else { + finalSplit.setDividerLocation(1.0); + } + split.setDividerLocation(0.66); + } else if(rightDocked) { + finalSplit.setDividerLocation(0.0); + split.setDividerLocation(0.66); + } else { + // both undocked + split.setDividerLocation(1.0); + } + } + }; + t1.addHierarchyListener(listener); + t2.addHierarchyListener(listener); + disable(); + } + + public void actionPerformed(ActionEvent e) { + if(!enabled) return; + if(e.getActionCommand().equals("ContextSwitch")) { + Context cx = Context.getCurrentContext(); + int frameIndex = context.getSelectedIndex(); + context.setToolTipText(toolTips.elementAt(frameIndex).toString()); + Scriptable obj; + int frameCount = cx.getFrameCount(); + if(frameIndex < frameCount) { + obj = cx.getFrame(frameIndex).getVariableObject(); + } else { + return; + } + NativeCall call = null; + if(obj instanceof NativeCall) { + call = (NativeCall)obj; + obj = call.getThisObj(); + } + JTree tree = thisTable.resetTree(model = new VariableModel(obj)); + if(call == null) { + tree = localsTable.resetTree(new AbstractTreeTableModel(new DefaultMutableTreeNode()) { + public Object getChild(Object parent, int index) { + return null; + } + public int getChildCount(Object parent) { + return 0; + } + public int getColumnCount() { + return 2; + } + public String getColumnName(int column) { + switch(column) { + case 0: + return " Name"; + case 1: + return " Value"; + } + return null; + } + public Object getValueAt(Object node, int column) { + return null; + } + }); + } else { + tree = localsTable.resetTree(model = new VariableModel(call)); + } + db.contextSwitch(frameIndex); + tableModel.updateModel(); + } + } + + public void disable() { + context.setEnabled(false); + thisTable.setEnabled(false); + localsTable.setEnabled(false); + evaluator.setEnabled(false); + cmdLine.setEnabled(false); + } + + public void enable() { + context.setEnabled(true); + thisTable.setEnabled(true); + localsTable.setEnabled(true); + evaluator.setEnabled(true); + cmdLine.setEnabled(true); + } + + public void disableUpdate() { + enabled = false; + } + + public void enableUpdate() { + enabled = true; + } +}; + +class CreateFileWindow implements Runnable { + + + JSDebugger db; + String url; + String text; + int line; + boolean activate; + + CreateFileWindow(JSDebugger db, + String url, String text, int line) { + this.db = db; + this.url = url; + this.text = text; + this.line = line; + this.activate = true; + } + + CreateFileWindow(JSDebugger db, + String url, String text, int line, boolean activate) { + this.db = db; + this.url = url; + this.text = text; + this.line = line; + this.activate = activate; + } + + public void run() { + FileWindow w = new FileWindow(db, url, text); + db.fileWindows.put(url, w); + if(db.currentWindow != null) { + db.currentWindow.setPosition(-1); + } + try { + w.setPosition(w.textArea.getLineStartOffset(line-1)); + } catch(BadLocationException exc) { + w.setPosition(-1); + } + w.setVisible(true); + db.desk.add(w); + db.currentWindow = w; + db.menubar.addFile(url); + if(activate) { + try { + w.setMaximum(true); + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } +} + +class SetFilePosition implements Runnable { + + JSDebugger db; + FileWindow w; + int line; + boolean activate; + + SetFilePosition(JSDebugger db, FileWindow w, int line) { + this.db = db; + this.w = w; + this.line = line; + activate = true; + } + + SetFilePosition(JSDebugger db, FileWindow w, int line, boolean activate) { + this.db = db; + this.w = w; + this.line = line; + this.activate = activate; + } + + public void run() { + JTextArea ta = w.textArea; + try { + int loc = ta.getLineStartOffset(line-1); + if(db.currentWindow != null && db.currentWindow != w) { + db.currentWindow.setPosition(-1); + } + w.setPosition(loc); + db.currentWindow = w; + } catch(BadLocationException exc) { + // fix me + } + if(activate) { + if(w.isIcon()) { + db.desk.getDesktopManager().deiconifyFrame(w); + } + db.desk.getDesktopManager().activateFrame(w); + try { + w.show(); + w.setSelected(true); + } catch(Exception exc) { + } + } + } +} + +class SetFileText implements Runnable { + + FileWindow w; + String text; + + SetFileText(FileWindow w, String text) { + this.w = w; this.text = text; + } + + public void run() { + w.setText(text); + } +} + +class UpdateContext implements Runnable { + JSDebugger db; + Context cx; + UpdateContext(JSDebugger db, Context cx) { + this.db = db; + this.cx = cx; + } + + public void run() { + db.context.enable(); + JComboBox ctx = db.context.context; + Vector toolTips = db.context.toolTips; + db.context.disableUpdate(); + int frameCount = cx.getFrameCount(); + ctx.removeAllItems(); + toolTips.clear(); + for(int i = 0; i < frameCount; i++) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(i); + String sourceName = frame.getSourceName(); + String location; + String shortName = sourceName; + int lineNumber = frame.getLineNumber(); + if(sourceName == null) { + if(lineNumber == -1) { + shortName = sourceName = ""; + } else { + shortName = sourceName = ""; + } + } else { + if(sourceName.length() > 20) { + shortName = new File(sourceName).getName(); + } + } + location = "\"" + shortName + "\", line " + lineNumber; + ctx.insertItemAt(location, i); + location = "\"" + sourceName + "\", line " + lineNumber; + toolTips.addElement(location); + } + db.context.enableUpdate(); + ctx.setSelectedIndex(0); + ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height)); + } +}; + +class Menubar extends JMenuBar implements ActionListener { + JMenu getDebugMenu() { + return getMenu(2); + } + Menubar(JSDebugger db) { + super(); + this.db = db; + String[] fileItems = {"Load...", "Exit"}; + String[] fileCmds = {"Load", "Exit"}; + char[] fileShortCuts = {'L', 'X'}; + 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) { + 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); + } + 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) { + item.setEnabled(false); + } + debugMenu.add(item); + } + 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); + + } + + 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 { + db.actionPerformed(e); + return; + } + try { + UIManager.setLookAndFeel(plaf_name); + SwingUtilities.updateComponentTreeUI(db); + SwingUtilities.updateComponentTreeUI(db.dlg); + } catch(Exception ignored) { + //ignored.printStackTrace(); + } + } + + public void addFile(String fileName) { + int count = windowMenu.getItemCount(); + JMenuItem item; + if(count == 4) { + windowMenu.addSeparator(); + count++; + } + if(count - 4 == 10) { + windowMenu.add(item = new JMenuItem("More Windows...", 'M')); + item.setActionCommand("More Windows..."); + item.addActionListener(this); + return; + } else if(count - 4 < 10) { + windowMenu.add(item = new JMenuItem((char)('0' + (count-4)) + " " + fileName, '0' + (count - 4))); + } else { + return; + } + item.setActionCommand(fileName); + item.addActionListener(this); + } + + JSDebugger db; + JMenu windowMenu; +}; + +class EnterInterrupt implements Runnable { + JSDebugger db; + Context cx; + EnterInterrupt(JSDebugger db, Context cx) { + this.db = db; + this.cx = cx; + } + public void run() { + Context newCx; + if((newCx = Context.enter(cx)) != cx) { + System.out.println("debugger error: Enter Interrupt: failed to obtain context: " + cx); + } + JMenu menu = db.getJMenuBar().getMenu(0); + menu.getItem(0).setEnabled(false); // File->Load + menu = db.getJMenuBar().getMenu(2); + menu.getItem(0).setEnabled(false); // Debug->Break + int count = menu.getItemCount(); + for(int i = 1; i < count; ++i) { + menu.getItem(i).setEnabled(true); + } + boolean b = false; + for(int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) { + db.toolBar.getComponent(ci).setEnabled(b); + b = true; + } + db.toolBar.setEnabled(true); + } +}; + +class ExitInterrupt implements Runnable { + JSDebugger db; + ExitInterrupt(JSDebugger db) { + this.db = db; + } + public void run() { + JMenu menu = db.getJMenuBar().getMenu(0); + menu.getItem(0).setEnabled(true); // File->Load + menu = db.getJMenuBar().getMenu(2); + menu.getItem(0).setEnabled(true); // Debug->Break + int count = menu.getItemCount(); + for(int i = 1; i < count; ++i) { + menu.getItem(i).setEnabled(false); + } + db.context.disable(); + boolean b = true; + for(int ci = 0, cc = db.toolBar.getComponentCount(); ci < cc; ci++) { + db.toolBar.getComponent(ci).setEnabled(b); + b = false; + } + db.console.consoleTextArea.requestFocus(); + } +}; + +class LoadFile implements Runnable { + Scriptable scope; + JSDebugger debugger; + String fileName; + LoadFile(JSDebugger debugger, Scriptable scope, String fileName) { + this.scope = scope; + this.fileName = fileName; + this.debugger = debugger; + } + public void run() { + Context cx = Context.enter(); + cx.setBreakNextLine(true); + try { + cx.evaluateReader(scope, new FileReader(fileName), + fileName, 1, null); + } catch(Exception exc) { + exc.printStackTrace(Main.getErr()); + } + cx.exit(); + } +} + + +public class JSDebugger extends JFrame implements Debugger, ContextListener { + + /* ContextListener interface */ + + java.util.HashSet contexts = new java.util.HashSet(); + + public void contextCreated(Context cx) { + cx.setDebugger(this); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + } + + public void contextEntered(Context cx) { + // If the debugger is attached to cx + // keep a reference to it even if it was detached + // from its thread (we cause that to happen below + // in interrupted) + if(!contexts.contains(cx)) { + if(cx.getDebugger() == this) { + contexts.add(cx); + } + } + } + + public void contextExited(Context cx) { + } + + public void contextReleased(Context cx) { + contexts.remove(cx); + } + + /* end ContextListener interface */ + + public void doBreak() { + synchronized(contexts) { + Iterator iter = contexts.iterator(); + while(iter.hasNext()) { + Context cx = (Context)iter.next(); + cx.setBreakNextLine(true); + } + } + } + + public void setVisible(boolean b) { + super.setVisible(b); + if(b) { + // this needs to be done after the window is visible + context.split.setDividerLocation(0.5); + } + } + + static final int STEP_OVER = 0; + static final int STEP_INTO = 1; + static final int STEP_OUT = 2; + static final int GO = 3; + static final int BREAK = 4; + static final int RUN_TO_CURSOR = 5; + + class ThreadState { + private int stopAtFrameDepth = -1; + }; + + private Hashtable threadState = new Hashtable(); + private Thread runToCursorThread; + private int runToCursorLine; + private String runToCursorFile; + private Hashtable sourceNames = new Hashtable(); + + class SourceEntry { + SourceEntry(StringBuffer source, DebuggableScript fnOrScript) { + this.source = source; + this.fnOrScript = fnOrScript; + } + StringBuffer source; + DebuggableScript fnOrScript; + }; + + Hashtable functionMap = new Hashtable(); + Hashtable breakpointsMap = new Hashtable(); + + public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, + StringBuffer source) { + String sourceName = fnOrScript.getSourceName(); + //System.out.println("sourceName = " + sourceName); + if (sourceName != null && !sourceName.equals("")) { + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + } else { + Enumeration e = fnOrScript.getLineNumbers(); + if(!e.hasMoreElements() || ((Integer)e.nextElement()).intValue() == -1) { + sourceName = ""; + } else { + sourceName = ""; + } + } + Vector v = (Vector) sourceNames.get(sourceName); + if (v == null) { + v = new Vector(); + sourceNames.put(sourceName, v); + } + SourceEntry entry = new SourceEntry(source, fnOrScript); + v.addElement(entry); + if(fnOrScript.getScriptable() instanceof NativeFunction) { + NativeFunction f = (NativeFunction)fnOrScript.getScriptable(); + String name = f.jsGet_name(); + if(name.length() > 0 && !name.equals("anonymous")) { + functionMap.put(name, entry); + } + } + loadedFile(sourceName, source.toString()); + } + + public void handleBreakpointHit(Context cx) { + interrupted(cx); + } + + public void handleExceptionThrown(Context cx, Object e) { + if(cx.getFrameCount() == 0) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String sourceName = frame.getSourceName(); + int lineNumber = frame.getLineNumber(); + FileWindow w = null; + if(sourceName == null) { + if(lineNumber == -1) { + sourceName = ""; + } else { + sourceName = ""; + } + } else { + w = getFileWindow(sourceName); + } + if(e instanceof NativeJavaObject) { + e = ((NativeJavaObject)e).unwrap(); + } + String msg = e.toString(); + if(msg == null || msg.length() == 0) { + msg = e.getClass().toString(); + } + msg += " (" + sourceName + ", line " + lineNumber + ")"; + if(w != null) { + //swingInvoke(new SetFilePosition(this, w, lineNumber)); + } + MessageDialogWrapper.showMessageDialog(this, + msg, + "Exception in Script", + JOptionPane.ERROR_MESSAGE); + } + } + + JDesktopPane desk; + ContextWindow context; + Menubar menubar; + JToolBar toolBar; + JSInternalConsole console; + EvalWindow evalWindow; + JSplitPane split1; + JLabel statusBar; + + public JSDebugger(String name) { + super(name); + setJMenuBar(menubar = new Menubar(this)); + toolBar = new JToolBar(); + JButton button; + toolBar.add(button = new JButton("Break")); + button.setToolTipText("Break"); + button.setActionCommand("Break"); + button.addActionListener(menubar); + button.setEnabled(true); + toolBar.add(button = new JButton("Go")); + button.setToolTipText("Go"); + button.setActionCommand("Go"); + button.addActionListener(menubar); + button.setEnabled(false); + toolBar.add(button = new JButton("Step Into")); + button.setToolTipText("Step Into"); + button.setActionCommand("Step Into"); + button.addActionListener(menubar); + button.setEnabled(false); + toolBar.add(button = new JButton("Step Over")); + button.setToolTipText("Step Over"); + button.setActionCommand("Step Over"); + button.setEnabled(false); + button.addActionListener(menubar); + toolBar.add(button = new JButton("Step Out")); + button.setToolTipText("Step Out"); + button.setActionCommand("Step Out"); + button.setEnabled(false); + button.addActionListener(menubar); + //toolBar.add(button = new JButton(new String("Run to Cursor"))); + //button.setToolTipText("Run to Cursor"); + //button.setActionCommand("Run to Cursor"); + //button.setEnabled(false); + //button.addActionListener(menubar); + // getContentPane().setLayout(new BorderLayout()); + getContentPane().add(toolBar, BorderLayout.NORTH); + desk = new JDesktopPane(); + desk.setPreferredSize(new Dimension(600, 300)); + desk.setMinimumSize(new Dimension(150, 50)); + desk.add(console = new JSInternalConsole("JavaScript Console")); + desk.getDesktopManager().activateFrame(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); + split1.setResizeWeight(0.66); + getContentPane().add(split1, BorderLayout.CENTER); + statusBar = new JLabel(); + statusBar.setText("Thread: "); + getContentPane().add(statusBar, BorderLayout.SOUTH); + dlg = new JFileChooser(); + dlg.setDialogTitle("Select a file to load"); + 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); + try { + console.setMaximum(true); + console.setSelected(true); + } catch(Exception exc) { + } + } + + public InputStream getIn() { + return console.getIn(); + } + + public PrintStream getOut() { + return console.getOut(); + } + + public PrintStream getErr() { + return console.getErr(); + } + + public static void main(String[] args) { + try { + final JSDebugger sdb = new JSDebugger("Rhino JavaScript Debugger"); + swingInvoke(new Runnable() { + public void run() { + sdb.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + sdb.pack(); + sdb.setSize(600, 460); + sdb.setVisible(true); + } + }); + System.setIn(sdb.getIn()); + System.setOut(sdb.getOut()); + System.setErr(sdb.getErr()); + Context.addContextListener(sdb); + String className = java.lang.System.getProperty("rhino.shell"); + if(className != null) { + try { + Class clazz = Class.forName(className); + Method mainMethod = + clazz.getMethod("main", new Class[] {String[].class}); + mainMethod.invoke(null, new Object[] {args}); + return; + } catch(Exception exc) { + } + } + Main.main(args); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + public FileWindow getFileWindow(String fileName) { + if(fileName == null || fileName.equals("") || fileName.equals("")) { + return null; + } + try { + // Can't do this if we're remote debugging + String file = new File(fileName).getCanonicalPath(); + Enumeration e = fileWindows.keys(); + for(; e.hasMoreElements(); ) { + String name = (String)e.nextElement(); + if(file.equals(new File(name).getCanonicalPath())) { + FileWindow w = (FileWindow)fileWindows.get(name); + w.setUrl(fileName); + return w; + } + } + } catch(IOException exc) { + } + return (FileWindow)fileWindows.get(fileName); + } + + public void loadedFile(String fileName, String text) { + FileWindow w = getFileWindow(fileName); + if(w != null) { + swingInvoke(new SetFileText(w, text)); + } + } + + static void swingInvoke(Runnable f) { + try { + SwingUtilities.invokeAndWait(f); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + static void swingInvokeLater(Runnable f) { + try { + SwingUtilities.invokeLater(f); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + + int frameIndex = -1; + + void contextSwitch(int frameIndex) { + Context cx = Context.getCurrentContext(); + if(cx != null) { + int frameCount = cx.getFrameCount(); + if(frameIndex < 0 || frameIndex >= frameCount) { + return; + } + this.frameIndex = frameIndex; + org.mozilla.javascript.debug.Frame frame = cx.getFrame(frameIndex); + String sourceName = frame.getSourceName(); + if(sourceName == null || sourceName.equals("")) { + console.show(); + console.consoleTextArea.requestFocus(); + return; + } + if(sourceName == "") { + return; + } + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + int lineNumber = frame.getLineNumber(); + this.frameIndex = frameIndex; + FileWindow w = getFileWindow(sourceName); + if(w != null) { + SetFilePosition action = new SetFilePosition(this, w, lineNumber); + action.run(); + } else { + Vector v = (Vector)sourceNames.get(sourceName); + String source = ((SourceEntry)v.elementAt(0)).source.toString(); + CreateFileWindow action = new CreateFileWindow(this, + sourceName, + source, + lineNumber); + action.run(); + } + } + } + + + public static void start(Context cx, Scriptable scope) { + } + + public void interrupted(Context cx) { + synchronized(debuggerMonitor) { + Thread thread = Thread.currentThread(); + statusBar.setText("Thread: " + thread.toString()); + ThreadState state = (ThreadState)threadState.get(thread); + int stopAtFrameDepth = -1; + if(state != null) { + stopAtFrameDepth = state.stopAtFrameDepth; + } + if(runToCursorFile != null && thread == runToCursorThread) { + int frameCount = cx.getFrameCount(); + if(frameCount > 0) { + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String sourceName = frame.getSourceName(); + if(sourceName != null) { + try { + sourceName = new File(sourceName).getCanonicalPath(); + } catch(IOException exc) { + } + if(sourceName.equals(runToCursorFile)) { + int lineNumber = frame.getLineNumber(); + if(lineNumber == runToCursorLine) { + stopAtFrameDepth = -1; + runToCursorFile = null; + } else { + FileWindow w = getFileWindow(sourceName); + if(w == null || + !w.isBreakPoint(lineNumber)) { + return; + } else { + runToCursorFile = null; + } + } + } + } + } else { + } + } + if(stopAtFrameDepth > 0) { + if (cx.getFrameCount() > stopAtFrameDepth) + return; + } + if(state != null) { + state.stopAtFrameDepth = -1; + } + int frameCount = cx.getFrameCount(); + this.frameIndex = frameCount -1; + int line = 0; + if(frameCount == 0) { + return; + } + org.mozilla.javascript.debug.Frame frame = cx.getFrame(0); + String fileName = frame.getSourceName(); + if(fileName != null && !fileName.equals("")) { + try { + fileName = new File(fileName).getCanonicalPath(); + } catch(IOException exc) { + } + } + cx.setBreakNextLine(false); + line = frame.getLineNumber(); + int enterCount = 0; + cx.exit(); + while(Context.getCurrentContext() != null) { + Context.exit(); + enterCount++; + } + if(fileName != null && !fileName.equals("")) { + FileWindow w = (FileWindow)getFileWindow(fileName); + if(w != null) { + SetFilePosition action = new SetFilePosition(this, w, line); + swingInvoke(action); + } else { + Vector v = (Vector)sourceNames.get(fileName); + String source = ((SourceEntry)v.elementAt(0)).source.toString(); + CreateFileWindow action = new CreateFileWindow(this, + fileName, + source, + line); + swingInvoke(action); + } + } else { + final JInternalFrame finalConsole = console; + swingInvoke(new Runnable() { + public void run() { + finalConsole.show(); + } + }); + } + swingInvoke(new EnterInterrupt(this, cx)); + swingInvoke(new UpdateContext(this, cx)); + int returnValue; + synchronized(monitor) { + this.returnValue = -1; + try { + while(this.returnValue == -1) { + monitor.wait(); + } + returnValue = this.returnValue; + } catch(InterruptedException exc) { + return; + } + } + final Context finalContext = cx; + swingInvoke(new Runnable() { + public void run() { + finalContext.exit(); + } + }); + swingInvoke(new ExitInterrupt(this)); + Context current; + if((current = Context.enter(cx)) != cx) { + System.out.println("debugger error: cx = " + cx + " current = " + current); + + } + while(enterCount > 0) { + Context.enter(); + enterCount--; + } + switch(returnValue) { + case STEP_OVER: + cx.setBreakNextLine(true); + stopAtFrameDepth = cx.getFrameCount(); + if(state == null) { + state = new ThreadState(); + threadState.put(thread, state); + } + state.stopAtFrameDepth = stopAtFrameDepth; + break; + case STEP_INTO: + cx.setBreakNextLine(true); + if(state != null) { + state.stopAtFrameDepth = -1; + } + break; + case STEP_OUT: + cx.setBreakNextLine(true); + if(state == null) { + state = new ThreadState(); + threadState.put(thread, state); + } + state.stopAtFrameDepth = cx.getFrameCount() - 1; + break; + case RUN_TO_CURSOR: + cx.setBreakNextLine(true); + if(state != null) { + state.stopAtFrameDepth = -1; + } + break; + } + } + } + + JFileChooser dlg; + + public String chooseFile() { + File CWD = null; + String dir = System.getProperty("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) { + String result = dlg.getSelectedFile().getPath(); + CWD = dlg.getSelectedFile().getParentFile(); + java.lang.System.setProperty("user.dir", CWD.getPath()); + return result; + } + return null; + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + int returnValue = -1; + if(cmd.equals("Step Over")) { + returnValue = STEP_OVER; + } else if(cmd.equals("Step Into")) { + returnValue = STEP_INTO; + } else if(cmd.equals("Step Out")) { + returnValue = STEP_OUT; + } else if(cmd.equals("Go")) { + returnValue = GO; + } else if(cmd.equals("Break")) { + doBreak(); + } else if(cmd.equals("Run to Cursor")) { + returnValue = RUN_TO_CURSOR; + } else if(cmd.equals("Exit")) { + System.exit(0); + } else if(cmd.equals("Load")) { + String fileName = chooseFile(); + if(fileName != null) { + new Thread(new LoadFile(this, Main.getScope(), + fileName)).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(); + try { + console.setSelected(true); + } catch(Exception exc) { + } + 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, functionMap, "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; + if(w.isIcon()) { + desk.getDesktopManager().deiconifyFrame(w); + } + w.show(); + try { + w.setSelected(true); + } catch(Exception exc) { + } + } + } + if(returnValue != -1) { + if(currentWindow != null) currentWindow.setPosition(-1); + synchronized(monitor) { + this.returnValue = returnValue; + monitor.notify(); + } + } + } + + void runToCursor(String fileName, + int lineNumber, + ActionEvent evt) { + Vector v = (Vector) sourceNames.get(fileName); + if(v == null) { + System.out.println("debugger error: Couldn't find source: " + fileName); + } + int i; + SourceEntry se = null; + for (i = v.size() -1; i >= 0; i--) { + se = (SourceEntry) v.elementAt(i); + if (se.fnOrScript.removeBreakpoint(lineNumber)) { + se.fnOrScript.placeBreakpoint(lineNumber); + break; + } else if(se.fnOrScript.placeBreakpoint(lineNumber)) { + se.fnOrScript.removeBreakpoint(lineNumber); + break; + } + } + if(i >= 0) { + runToCursorFile = fileName; + runToCursorLine = lineNumber; + actionPerformed(evt); + } + } + + int setBreakPoint(String sourceName, int lineNumber) { + Vector v = (Vector) sourceNames.get(sourceName); + if(v == null) return -1; + int i=0; + int result = -1; + for (i = v.size() -1; i >= 0; i--) { + SourceEntry se = (SourceEntry) v.elementAt(i); + if (se.fnOrScript.placeBreakpoint(lineNumber)) { + result = lineNumber; + } + } + return result; + } + + void clearBreakPoint(String sourceName, int lineNumber) { + Vector v = (Vector) sourceNames.get(sourceName); + if(v == null) return; + int i=0; + for (; i < v.size(); i++) { + SourceEntry se = (SourceEntry) v.elementAt(i); + se.fnOrScript.removeBreakpoint(lineNumber); + } + } + + JMenu getWindowMenu() { + return menubar.getMenu(3); + } + + public void removeWindow(FileWindow w) { + fileWindows.remove(w.getUrl()); + JMenu windowMenu = getWindowMenu(); + int count = windowMenu.getItemCount(); + JMenuItem lastItem = windowMenu.getItem(count -1); + 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(w.getUrl())) { + 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 -2; i++) { + JMenuItem thisItem = windowMenu.getItem(i); + if(thisItem != null) { + //1 D:\foo.js + //2 D:\bar.js + text = thisItem.getText(); + pos = text.indexOf(' '); + thisItem.setText((char)('0' + j) + " " + + text.substring(pos + 1)); + thisItem.setMnemonic('0' + j); + j++; + } + } + if(count - 6 < 10 && lastItem != item) { + if(lastItem.getText().equals("More Windows...")) { + windowMenu.remove(lastItem); + } + } + } + break; + } + } + windowMenu.revalidate(); + } + + + public String eval(String expr) { + Context cx = Context.getCurrentContext(); + if(cx == null) return "undefined"; + if(frameIndex >= cx.getFrameCount()) { + return "undefined"; + } + String resultString; + cx.setDebugger(null); + cx.setGeneratingDebug(false); + cx.setOptimizationLevel(-1); + boolean breakNextLine = cx.getBreakNextLine(); + cx.setBreakNextLine(false); + try { + Scriptable scope; + scope = cx.getFrame(frameIndex).getVariableObject(); + Object result; + if(scope instanceof NativeCall) { + NativeCall call = (NativeCall)scope; + result = NativeGlobal.evalSpecial(cx, call, + call.getThisObj(), + new Object[]{expr}, + "", 1); + } else { + result = cx.evaluateString(scope, + expr, + "", + 0, + null); + } + try { + resultString = ScriptRuntime.toString(result); + } catch(Exception exc) { + resultString = result.toString(); + } + } catch(Exception exc) { + resultString = exc.getMessage(); + } + cx.setDebugger(this); + cx.setGeneratingDebug(true); + cx.setOptimizationLevel(-1); + cx.setBreakNextLine(breakNextLine); + return resultString; + } + + java.util.Hashtable fileWindows = new java.util.Hashtable(); + FileWindow currentWindow; + Object monitor = new Object(); + Object swingMonitor = new Object(); + Object debuggerMonitor = new Object(); + int returnValue = -1; +}; + + + + + + + diff --git a/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/VariableModel.java b/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/VariableModel.java new file mode 100644 index 00000000000..fe223368c64 --- /dev/null +++ b/js/rhino/toolsrc/org/mozilla/javascript/tools/debugger/VariableModel.java @@ -0,0 +1,418 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * 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 See Beyond are + * Copyright (C) 2000 See Beyond Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (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 NPL, indicate your decision by + * deleting the provisions above and replace 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 NPL or the GPL. + */ + +package org.mozilla.javascript.tools.debugger; +import java.io.File; +import java.util.Date; +import org.mozilla.javascript.*; +import org.mozilla.javascript.tools.shell.Global; +import javax.swing.event.TableModelEvent; +import java.util.Hashtable; +import java.util.Enumeration; + +public class VariableModel extends AbstractTreeTableModel + implements TreeTableModel { + + // Names of the columns. + //static protected String[] cNames = {"Name", "Type", "Value"}; + static protected String[] cNames = { " Name", " Value"}; + + // Types of the columns. + //static protected Class[] cTypes = {TreeTableModel.class, String.class, String.class}; + static protected Class[] cTypes = {TreeTableModel.class, String.class}; + + public VariableModel(Scriptable scope) { + super(scope == null ? null : new VariableNode(scope, "this")); + } + + // + // Some convenience methods. + // + + protected Object getObject(Object node) { + VariableNode varNode = ((VariableNode)node); + if(varNode == null) return null; + return varNode.getObject(); + } + + protected Object[] getChildren(Object node) { + VariableNode varNode = ((VariableNode)node); + return varNode.getChildren(); + } + + // + // The TreeModel interface + // + + public int getChildCount(Object node) { + Object[] children = getChildren(node); + return (children == null) ? 0 : children.length; + } + + public Object getChild(Object node, int i) { + return getChildren(node)[i]; + } + + // The superclass's implementation would work, but this is more efficient. + public boolean isLeaf(Object node) { + if(node == null) return true; + VariableNode varNode = (VariableNode)node; + Object[] children = varNode.getChildren(); + if(children != null && children.length > 0) { + return false; + } + //Object value = getObject(node); + //if(value != null && value instanceof Scriptable) { + //if(value == Undefined.instance || + //value == ScriptableObject.NOT_FOUND) { + //return true; + //} + //Scriptable scrip = (Scriptable)value; + //Scriptable proto = scrip.getPrototype(); + //if(proto != null && + //proto != ScriptableObject.getObjectPrototype(scrip)) { + //return false; + //} + //} + // if(value == null) return true; + // if (value == Undefined.instance) + // return true; + // if (value == null) + // return true; + // if (value instanceof String) + // return true; + // if (value instanceof Number) + // return true; + // if (value instanceof Boolean) + // return true; + // if(value instanceof Scriptable) { + // //Scriptable scrip = (Scriptable)value; + //if(scrip.has(0, scrip)) { + //return false; + //} + //if(ScriptableObject.getPropertyIds(scrip).length > 0) { + //return false; + //} + //return true; + //} + return true; + } + + public boolean isCellEditable(Object node, int column) { + return column == 0 || column == 2; + } + + // + // The TreeTableNode interface. + // + + public int getColumnCount() { + return cNames.length; + } + + public String getColumnName(int column) { + return cNames[column]; + } + + public Class getColumnClass(int column) { + return cTypes[column]; + } + + public Object getValueAt(Object node, int column) { + Object value = getObject(node); + try { + switch(column) { + case 0: // Name + VariableNode varNode = (VariableNode)node; + String name = ""; + if(varNode.name != null) { + return name + varNode.name; + } + return name + "[" + varNode.index + "]"; + case -1: // Type + if(value == Undefined.instance || value == ScriptableObject.NOT_FOUND) { + return "undefined"; + } + if (value == null) + return "object"; + if (value instanceof Scriptable) + return (value instanceof Function) ? "function" : "object"; + if (value instanceof String) + return "string"; + if (value instanceof Number) + return "number"; + if (value instanceof Boolean) + return "boolean"; + return value.getClass().getName(); + case 1: // value + if(value == Undefined.instance || value == ScriptableObject.NOT_FOUND) { + return "undefined"; + } + if(value instanceof Scriptable) { + try { + Context cx = Context.enter(); + String result; + try { + result = ScriptRuntime.toString(value); + } catch(Exception exc) { + result = value.toString(); + } finally { + cx.exit(); + } + 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(); + } catch(Exception exc) { + exc.printStackTrace(); + } + } + if(value == null) { + return "null"; + } + return value.toString(); + } + } + catch (Exception exc) { + //exc.printStackTrace(); + } + return null; + } + + public void setScope(Scriptable scope) { + //System.out.println("setScope: " + scope); + VariableNode rootVar = (VariableNode)root; + rootVar.scope = scope; + fireTreeNodesChanged(this, + new Object[]{root}, + null, new Object[]{root}); + } + +} + + +class VariableNode { + Scriptable scope; + String name; + int index; + + public VariableNode(Scriptable scope, String name) { + this.scope = scope; + this.name = name; + } + + public VariableNode(Scriptable scope, int index) { + this.scope = scope; + this.name = null; + this.index = index; + } + + /** + * Returns the the string to be used to display this leaf in the JTree. + */ + public String toString() { + return (name != null ? name : "[" + index + "]"); + } + + public Object getObject() { + try { + if(scope == null) return null; + if(name != null) { + if(name.equals("this")) { + return scope; + } + Object result; + if(name.equals("__proto__")) { + result = scope.getPrototype(); + } else if(name.equals("__parent__")) { + result = scope.getParentScope(); + } else { + result = scope.get(name, scope); + } + if(result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + return result; + } + Object result = scope.get(index, scope); + if(result == ScriptableObject.NOT_FOUND) { + result = Undefined.instance; + } + return result; + } catch(Exception exc) { + return "undefined"; + } + } + + Object[] children; + + /** + * Loads the children, caching the results in the children ivar. + */ + static final Object[] empty = new Object[0]; + static Scriptable builtin[]; + protected Object[] getChildren() { + if(children != null) return children; + try { + Object value = getObject(); + if(value == null) return children = empty; + if(value == ScriptableObject.NOT_FOUND || + value == Undefined.instance) { + return children = empty; + } + if(value instanceof Scriptable) { + Scriptable scrip = (Scriptable)value; + Scriptable proto = scrip.getPrototype(); + Scriptable parent = scrip.getParentScope(); + if(scrip instanceof NativeCall) { + parent = proto = null; + } else if(parent instanceof NativeCall) { + parent = null; + } + if(proto != null) { + if(builtin == null) { + builtin = new Scriptable[6]; + builtin[0] = + ScriptableObject.getObjectPrototype(scrip); + builtin[1] = + ScriptableObject.getFunctionPrototype(scrip); + builtin[2] = + ScriptableObject.getClassPrototype(scrip, + "String"); + builtin[3] = + ScriptableObject.getClassPrototype(scrip, + "Boolean"); + builtin[4] = + ScriptableObject.getClassPrototype(scrip, + "Array"); + builtin[5] = + ScriptableObject.getClassPrototype(scrip, + "Number"); + } + for(int i = 0; i < builtin.length; i++) { + if(proto == builtin[i]) { + proto = null; + break; + } + } + } + if(scrip.has(0, scrip)) { + int len = 0; + try { + Scriptable start = scrip; + Scriptable obj = start; + Object result = Undefined.instance; + do { + if(obj.has("length", start)) { + result = obj.get("length", start); + if (result != Scriptable.NOT_FOUND) + break; + } + obj = obj.getPrototype(); + } while (obj != null); + if(result instanceof Number) { + len = ((Number)result).intValue(); + } + } catch(Exception exc) { + } + if(parent != null) { + len++; + } + if(proto != null) { + len++; + } + children = new VariableNode[len]; + int i = 0; + int j = 0; + if(proto != null) { + children[i++] = new VariableNode(scrip, "__proto__"); + j++; + } + if(parent != null) { + children[i++] = new VariableNode(scrip, "__parent__"); + j++; + } + for(; i < len; i++) { + children[i] = new VariableNode(scrip, i-j); + } + } else { + int len = 0; + Hashtable t = new Hashtable(); + Object[] ids = scrip.getIds(); + if(proto != null) t.put("__proto__", "__proto__"); + if(parent != null) t.put("__parent__", "__parent__"); + if(ids.length > 0) { + for(int j = 0; j < ids.length; j++) { + t.put(ids[j], ids[j]); + } + } + ids = new Object[t.size()]; + Enumeration e = t.keys(); + int j = 0; + while(e.hasMoreElements()) { + ids[j++] = e.nextElement().toString(); + } + if(ids != null && ids.length > 0) { + java.util.Arrays.sort(ids, new java.util.Comparator() { + public int compare(Object l, Object r) { + return l.toString().compareToIgnoreCase(r.toString()); + + } + }); + len = ids.length; + } + children = new VariableNode[len]; + for(int i = 0; i < len; i++) { + Object id = ids[i]; + ////System.out.println("id is " + id); + children[i] = + new VariableNode(scrip, id.toString()); + } + } + } + } catch (Exception exc) { + exc.printStackTrace(); + } + return children; + } +} + diff --git a/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java b/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java new file mode 100644 index 00000000000..a4c4adaa35e --- /dev/null +++ b/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/ConsoleTextArea.java @@ -0,0 +1,296 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * 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 See Beyond are + * Copyright (C) 2000 See Beyond Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (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 NPL, indicate your decision by + * deleting the provisions above and replace 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 NPL or the GPL. + */ +package org.mozilla.javascript.tools.shell; +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.util.*; +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 { + 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/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java b/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java new file mode 100644 index 00000000000..2eff3b82807 --- /dev/null +++ b/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/JSConsole.java @@ -0,0 +1,211 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * 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 See Beyond are + * Copyright (C) 2000 See Beyond Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Christopher Oliver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (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 NPL, indicate your decision by + * deleting the provisions above and replace 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 NPL or the GPL. + */ +package org.mozilla.javascript.tools.shell; +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.util.*; +import javax.swing.text.Document; +import javax.swing.text.Segment; + + +public class JSConsole extends JFrame implements ActionListener { + + private File CWD; + private JFileChooser dlg; + private ConsoleTextArea consoleTextArea; + + public String chooseFile() { + if(CWD == null) { + String dir = System.getProperty("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 = dlg.getSelectedFile().getParentFile(); + return result; + } + return null; + } + + public static void main(String args[]) { + JSConsole console = 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); + } + } + } + + } + +};