/*
* Copyright (C) 2012 Martin Leopold <m@martinleopold.com>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package processing.mode.experimental;
import com.sun.jdi.Value;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultCellEditor;
import javax.swing.GrayFilter;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.ExtTreeWillExpandListener;
import org.netbeans.swing.outline.OutlineModel;
import org.netbeans.swing.outline.RenderDataProvider;
import org.netbeans.swing.outline.RowModel;
/**
* Variable Inspector window.
*
* @author Martin Leopold <m@martinleopold.com>
*/
public class VariableInspector extends javax.swing.JFrame {
protected DefaultMutableTreeNode rootNode; // the root node (invisible)
protected DefaultMutableTreeNode builtins; // node for Processing built-in variables
protected DefaultTreeModel treeModel; // data model for the tree column
protected OutlineModel model; // data model for the whole Outline (tree and other columns)
protected List<DefaultMutableTreeNode> callStack; // the call stack
protected List<VariableNode> locals; // current local variables
protected List<VariableNode> thisFields; // all fields of the current this-object
protected List<VariableNode> declaredThisFields; // declared i.e. non-inherited fields of this
protected DebugEditor editor; // the editor
protected Debugger dbg; // the debugger
protected List<TreePath> expandedNodes = new ArrayList(); // list of expanded tree paths. (using list to maintain the order of expansion)
protected boolean p5mode = true; // processing / "advanced" mode flag (currently not used
/**
* Creates new form VariableInspector
*/
public VariableInspector(DebugEditor editor) {
this.editor = editor;
this.dbg = editor.dbg();
initComponents();
// setup Outline
rootNode = new DefaultMutableTreeNode("root");
builtins = new DefaultMutableTreeNode("Processing");
treeModel = new DefaultTreeModel(rootNode); // model for the tree column
model = DefaultOutlineModel.createOutlineModel(treeModel, new VariableRowModel(), true, "Name"); // model for all columns
ExpansionHandler expansionHandler = new ExpansionHandler();
model.getTreePathSupport().addTreeWillExpandListener(expansionHandler);
model.getTreePathSupport().addTreeExpansionListener(expansionHandler);
tree.setModel(model);
tree.setRootVisible(false);
tree.setRenderDataProvider(new OutlineRenderer());
tree.setColumnHidingAllowed(false); // disable visible columns button (shows by default when right scroll bar is visible)
tree.setAutoscrolls(false);
// set custom renderer and editor for value column, since we are using a custom class for values (VariableNode)
TableColumn valueColumn = tree.getColumnModel().getColumn(1);
valueColumn.setCellRenderer(new ValueCellRenderer());
valueColumn.setCellEditor(new ValueCellEditor());
//System.out.println("renderer: " + tree.getDefaultRenderer(String.class).getClass());
//System.out.println("editor: " + tree.getDefaultEditor(String.class).getClass());
callStack = new ArrayList();
locals = new ArrayList();
thisFields = new ArrayList();
declaredThisFields = new ArrayList();
this.setTitle(editor.getSketch().getName());
// for (Entry<Object, Object> entry : UIManager.getDefaults().entrySet()) {
// System.out.println(entry.getKey());
// }
}
@Override
public void setTitle(String title) {
super.setTitle(title + " | Variable Inspector");
}
/**
* Model for a Outline Row (excluding the tree column). Column 0 is "Value".
* Column 1 is "Type". Handles setting and getting values. TODO: Maybe use a
* TableCellRenderer instead of this to also have a different icon based on
* expanded state. See:
* http://kickjava.com/src/org/netbeans/swing/outline/DefaultOutlineCellRenderer.java.htm
*/
protected class VariableRowModel implements RowModel {
protected String[] columnNames = {"Value", "Type"};
protected int[] editableTypes = {VariableNode.TYPE_BOOLEAN, VariableNode.TYPE_FLOAT, VariableNode.TYPE_INTEGER, VariableNode.TYPE_STRING, VariableNode.TYPE_FLOAT, VariableNode.TYPE_DOUBLE, VariableNode.TYPE_LONG, VariableNode.TYPE_SHORT, VariableNode.TYPE_CHAR};
@Override
public int getColumnCount() {
if (p5mode) {
return 1; // only show value in p5 mode
} else {
return 2;
}
}
@Override
public Object getValueFor(Object o, int i) {
if (o instanceof VariableNode) {
VariableNode var = (VariableNode) o;
switch (i) {
case 0:
return var; // will be converted to an appropriate String by ValueCellRenderer
case 1:
return var.getTypeName();
default:
return "";
}
} else {
return "";
}
}
@Override
public Class getColumnClass(int i) {
if (i == 0) {
return VariableNode.class;
}
return String.class;
}
@Override
public boolean isCellEditable(Object o, int i) {
if (i == 0 && o instanceof VariableNode) {
VariableNode var = (VariableNode) o;
//System.out.println("type: " + var.getTypeName());
for (int type : editableTypes) {
if (var.getType() == type) {
return true;
}
}
}
return false;
}
@Override
public void setValueFor(Object o, int i, Object o1) {
VariableNode var = (VariableNode) o;
String stringValue = (String) o1;
Value value = null;
try {
switch (var.getType()) {
case VariableNode.TYPE_INTEGER:
value = dbg.vm().mirrorOf(Integer.parseInt(stringValue));
break;
case VariableNode.TYPE_BOOLEAN:
value = dbg.vm().mirrorOf(Boolean.parseBoolean(stringValue));
break;
case VariableNode.TYPE_FLOAT:
value = dbg.vm().mirrorOf(Float.parseFloat(stringValue));
break;
case VariableNode.TYPE_STRING:
value = dbg.vm().mirrorOf(stringValue);
break;
case VariableNode.TYPE_LONG:
value = dbg.vm().mirrorOf(Long.parseLong(stringValue));
break;
case VariableNode.TYPE_BYTE:
value = dbg.vm().mirrorOf(Byte.parseByte(stringValue));
break;
case VariableNode.TYPE_DOUBLE:
value = dbg.vm().mirrorOf(Double.parseDouble(stringValue));
break;
case VariableNode.TYPE_SHORT:
value = dbg.vm().mirrorOf(Short.parseShort(stringValue));
break;
case VariableNode.TYPE_CHAR:
// TODO: better char support
if (stringValue.length() > 0) {
value = dbg.vm().mirrorOf(stringValue.charAt(0));
}
break;
}
} catch (NumberFormatException ex) {
Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "invalid value entered for {0}: {1}", new Object[]{var.getName(), stringValue});
}
if (value != null) {
var.setValue(value);
Logger.getLogger(VariableRowModel.class.getName()).log(Level.INFO, "new value set: {0}", var.getStringValue());
}
}
@Override
public String getColumnName(int i) {
return columnNames[i];
}
}
/**
* Renderer for the tree portion of the outline component. Handles icons,
* text color and tool tips.
*/
protected class OutlineRenderer implements RenderDataProvider {
protected Icon[][] icons;
protected static final int ICON_SIZE = 16; // icon size (square, size=width=height)
public OutlineRenderer() {
// load icons
icons = loadIcons("theme/var-icons.gif");
}
/**
* Load multiple icons (horizotal) with multiple states (vertical) from
* a single file.
*
* @param fileName file path in the mode folder.
* @return a nested array (first index: icon, second index: state) or
* null if the file wasn't found.
*/
protected ImageIcon[][] loadIcons(String fileName) {
ExperimentalMode mode = editor.mode();
File file = mode.getContentFile(fileName);
if (!file.exists()) {
Logger.getLogger(OutlineRenderer.class.getName()).log(Level.SEVERE, "icon file not found: {0}", file.getAbsolutePath());
return null;
}
Image allIcons = mode.loadImage(fileName);
int cols = allIcons.getWidth(null) / ICON_SIZE;
int rows = allIcons.getHeight(null) / ICON_SIZE;
ImageIcon[][] iconImages = new ImageIcon[cols][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
//Image image = createImage(ICON_SIZE, ICON_SIZE);
Image image = new BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.drawImage(allIcons, -i * ICON_SIZE, -j * ICON_SIZE, null);
iconImages[i][j] = new ImageIcon(image);
}
}
return iconImages;
}
protected Icon getIcon(int type, int state) {
if (type < 0 || type > icons.length - 1) {
return null;
}
return icons[type][state];
}
protected VariableNode toVariableNode(Object o) {
if (o instanceof VariableNode) {
return (VariableNode) o;
} else {
return null;
}
}
protected Icon toGray(Icon icon) {
if (icon instanceof ImageIcon) {
Image grayImage = GrayFilter.createDisabledImage(((ImageIcon) icon).getImage());
return new ImageIcon(grayImage);
}
// Cannot convert
return icon;
}
@Override
public String getDisplayName(Object o) {
return o.toString(); // VariableNode.toString() returns name; (for sorting)
// VariableNode var = toVariableNode(o);
// if (var != null) {
// return var.getName();
// } else {
// return o.toString();
// }
}
@Override
public boolean isHtmlDisplayName(Object o) {
return false;
}
@Override
public Color getBackground(Object o) {
return null;
}
@Override
public Color getForeground(Object o) {
if (tree.isEnabled()) {
return null; // default
} else {
return Color.GRAY;
}
}
@Override
public String getTooltipText(Object o) {
VariableNode var = toVariableNode(o);
if (var != null) {
return var.getDescription();
} else {
return "";
}
}
@Override
public Icon getIcon(Object o) {
VariableNode var = toVariableNode(o);
if (var != null) {
if (tree.isEnabled()) {
return getIcon(var.getType(), 0);
} else {
return getIcon(var.getType(), 1);
}
} else {
if (o instanceof TreeNode) {
// TreeNode node = (TreeNode) o;
// AbstractLayoutCache layout = tree.getLayoutCache();
UIDefaults defaults = UIManager.getDefaults();
boolean isLeaf = model.isLeaf(o);
Icon icon;
if (isLeaf) {
icon = defaults.getIcon("Tree.leafIcon");
} else {
icon = defaults.getIcon("Tree.closedIcon");
}
if (!tree.isEnabled()) {
return toGray(icon);
}
return icon;
}
}
return null; // use standard icon
//UIManager.getIcon(o);
}
}
// TODO: could probably extend the simpler javax.swing.table.DefaultTableCellRenderer here
/**
* Renderer for the value column. Uses an italic font for null values and
* Object values ("instance of ..."). Uses a gray color when tree is not
* enabled.
*/
protected class ValueCellRenderer extends DefaultOutlineCellRenderer {
public ValueCellRenderer() {
super();
}
protected void setItalic(boolean on) {
if (on) {
setFont(new Font(getFont().getName(), Font.ITALIC, getFont().getSize()));
} else {
setFont(new Font(getFont().getName(), Font.PLAIN, getFont().getSize()));
}
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (!tree.isEnabled()) {
setForeground(Color.GRAY);
} else {
setForeground(Color.BLACK);
}
if (value instanceof VariableNode) {
VariableNode var = (VariableNode) value;
if (var.getValue() == null || var.getType() == VariableNode.TYPE_OBJECT) {
setItalic(true);
} else {
setItalic(false);
}
value = var.getStringValue();
}
setValue(value);
return c;
}
}
/**
* Editor for the value column. Will show an empty string when editing
* String values that are null.
*/
protected class ValueCellEditor extends DefaultCellEditor {
public ValueCellEditor() {
super(new JTextField());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (!(value instanceof VariableNode)) {
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
VariableNode var = (VariableNode) value;
if (var.getType() == VariableNode.TYPE_STRING && var.getValue() == null) {
return super.getTableCellEditorComponent(table, "", isSelected, row, column);
} else {
return super.getTableCellEditorComponent(table, var.getStringValue(), isSelected, row, column);
}
}
}
/**
* Handler for expanding and collapsing tree nodes. Implements lazy loading
* of tree data (on expand).
*/
protected class ExpansionHandler implements ExtTreeWillExpandListener, TreeExpansionListener {
@Override
public void treeWillExpand(TreeExpansionEvent tee) throws ExpandVetoException {
//System.out.println("will expand");
Object last = tee.getPath().getLastPathComponent();
if (!(last instanceof VariableNode)) {
return;
}
VariableNode var = (VariableNode) last;
// load children
// if (!dbg.isPaused()) {
// System.out.println("throwing veto");
// //throw new ExpandVetoException(tee, "Debugger busy");
// } else {
var.removeAllChildren(); // TODO: should we only load it once?
// TODO: don't filter in advanced mode
//System.out.println("loading children for: " + var);
// true means include inherited
var.addChildren(filterNodes(dbg.getFields(var.getValue(), 0, true), new ThisFilter()));
// }
}
@Override
public void treeWillCollapse(TreeExpansionEvent tee) throws ExpandVetoException {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void treeExpanded(TreeExpansionEvent tee) {
//System.out.println("expanded: " + tee.getPath());
if (!expandedNodes.contains(tee.getPath())) {
expandedNodes.add(tee.getPath());
}
// TreePath newPath = tee.getPath();
// if (expandedLast != null) {
// // test each node of the path for equality
// for (int i = 0; i < expandedLast.getPathCount(); i++) {
// if (i < newPath.getPathCount()) {
// Object last = expandedLast.getPathComponent(i);
// Object cur = newPath.getPathComponent(i);
// System.out.println(last + " =? " + cur + ": " + last.equals(cur) + "/" + (last.hashCode() == cur.hashCode()));
// }
// }
// }
// System.out.println("path equality: " + newPath.equals(expandedLast));
// expandedLast = newPath;
}
@Override
public void treeCollapsed(TreeExpansionEvent tee) {
//System.out.println("collapsed: " + tee.getPath());
// first remove all children of collapsed path
// this makes sure children do not appear before parents in the list.
// (children can't be expanded before their parents)
List<TreePath> removalList = new ArrayList();
for (TreePath path : expandedNodes) {
if (path.getParentPath().equals(tee.getPath())) {
removalList.add(path);
}
}
for (TreePath path : removalList) {
expandedNodes.remove(path);
}
// remove collapsed path
expandedNodes.remove(tee.getPath());
}
@Override
public void treeExpansionVetoed(TreeExpansionEvent tee, ExpandVetoException eve) {
//System.out.println("expansion vetoed");
// nop
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
scrollPane = new javax.swing.JScrollPane();
tree = new org.netbeans.swing.outline.Outline();
scrollPane.setViewportView(tree);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE))
);
pack();
}// </editor-fold>//GEN-END:initComponents
// /**
// * @param args the command line arguments
// */
// public static void main(String args[]) {
// /*
// * Set the Nimbus look and feel
// */
// //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
// /*
// * If Nimbus (introduced in Java SE 6) is not available, stay with the
// * default look and feel. For details see
// * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
// */
// try {
// javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
// } catch (ClassNotFoundException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (InstantiationException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (IllegalAccessException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// } catch (javax.swing.UnsupportedLookAndFeelException ex) {
// java.util.logging.Logger.getLogger(VariableInspector.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
// }
// //</editor-fold>
//
// /*
// * Create and display the form
// */
// run(new VariableInspector());
// }
protected static void run(final VariableInspector vi) {
/*
* Create and display the form
*/
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
vi.setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane scrollPane;
protected org.netbeans.swing.outline.Outline tree;
// End of variables declaration//GEN-END:variables
/**
* Access the root node of the tree.
*
* @return the root node
*/
public DefaultMutableTreeNode getRootNode() {
return rootNode;
}
/**
* Unlock the inspector window. Rebuild after this to avoid ... dots in the
* trees labels
*/
public void unlock() {
tree.setEnabled(true);
}
/**
* Lock the inspector window. Cancels open edits.
*/
public void lock() {
if (tree.getCellEditor() != null) {
//tree.getCellEditor().stopCellEditing(); // force quit open edit
tree.getCellEditor().cancelCellEditing(); // cancel an open edit
}
tree.setEnabled(false);
}
/**
* Reset the inspector windows data. Rebuild after this to make changes
* visible.
*/
public void reset() {
rootNode.removeAllChildren();
// clear local data for good measure (in case someone rebuilds)
callStack.clear();
locals.clear();
thisFields.clear();
declaredThisFields.clear();
expandedNodes.clear();
// update
treeModel.nodeStructureChanged(rootNode);
}
// public void setAdvancedMode() {
// p5mode = false;
// }
//
// public void setP5Mode() {
// p5mode = true;
// }
//
// public void toggleMode() {
// if (p5mode) {
// setAdvancedMode();
// } else {
// setP5Mode();
// }
// }
/**
* Update call stack data.
*
* @param nodes a list of nodes that represent the call stack.
* @param title the title to be used when labeling or otherwise grouping
* call stack data.
*/
public void updateCallStack(List<DefaultMutableTreeNode> nodes, String title) {
callStack = nodes;
}
/**
* Update locals data.
*
* @param nodes a list of {@link VariableNode} to be shown as local
* variables in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* locals data.
*/
public void updateLocals(List<VariableNode> nodes, String title) {
locals = nodes;
}
/**
* Update this-fields data.
*
* @param nodes a list of {@link VariableNode}s to be shown as this-fields
* in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* this-fields data.
*/
public void updateThisFields(List<VariableNode> nodes, String title) {
thisFields = nodes;
}
/**
* Update declared (non-inherited) this-fields data.
*
* @param nodes a list of {@link VariableNode}s to be shown as declared
* this-fields in the inspector.
* @param title the title to be used when labeling or otherwise grouping
* declared this-fields data.
*/
public void updateDeclaredThisFields(List<VariableNode> nodes, String title) {
declaredThisFields = nodes;
}
/**
* Rebuild the outline tree from current data. Uses the data provided by
* {@link #updateCallStack}, {@link #updateLocals}, {@link #updateThisFields}
* and {@link #updateDeclaredThisFields}
*/
public void rebuild() {
rootNode.removeAllChildren();
if (p5mode) {
// add all locals to root
addAllNodes(rootNode, locals);
// add non-inherited this fields
addAllNodes(rootNode, filterNodes(declaredThisFields, new LocalHidesThisFilter(locals, LocalHidesThisFilter.MODE_PREFIX)));
// add p5 builtins in a new folder
builtins.removeAllChildren();
addAllNodes(builtins, filterNodes(thisFields, new P5BuiltinsFilter()));
if (builtins.getChildCount() > 0) { // skip builtins in certain situations e.g. in pure java tabs.
rootNode.add(builtins);
}
// notify tree (using model) changed a node and its children
// http://stackoverflow.com/questions/2730851/how-to-update-jtree-elements
// needs to be done before expanding paths!
treeModel.nodeStructureChanged(rootNode);
// handle node expansions
for (TreePath path : expandedNodes) {
//System.out.println("re-expanding: " + path);
path = synthesizePath(path);
if (path != null) {
tree.expandPath(path);
} else {
//System.out.println("couldn't synthesize path");
}
}
// this expansion causes problems when sorted and stepping
//tree.expandPath(new TreePath(new Object[]{rootNode, builtins}));
} else {
// TODO: implement advanced mode here
}
}
/**
* Re-build a {@link TreePath} from a previous path using equals-checks
* starting at the root node. This is used to use paths from previous trees
* for use on the current tree.
*
* @param path the path to synthesize.
* @return the rebuilt path, usable on the current tree.
*/
protected TreePath synthesizePath(TreePath path) {
//System.out.println("synthesizing: " + path);
if (path.getPathCount() == 0 || !rootNode.equals(path.getPathComponent(0))) {
return null;
}
Object[] newPath = new Object[path.getPathCount()];
newPath[0] = rootNode;
TreeNode currentNode = rootNode;
for (int i = 0; i < path.getPathCount() - 1; i++) {
// get next node
for (int j = 0; j < currentNode.getChildCount(); j++) {
TreeNode nextNode = currentNode.getChildAt(j);
if (nextNode.equals(path.getPathComponent(i + 1))) {
currentNode = nextNode;
newPath[i + 1] = nextNode;
//System.out.println("found node " + (i+1) + ": " + nextNode);
break;
}
}
if (newPath[i + 1] == null) {
//System.out.println("didn't find node");
return null;
}
}
return new TreePath(newPath);
}
/**
* Filter a list of nodes using a {@link VariableNodeFilter}.
*
* @param nodes the list of nodes to filter.
* @param filter the filter to be used.
* @return the filtered list.
*/
protected List<VariableNode> filterNodes(List<VariableNode> nodes, VariableNodeFilter filter) {
List<VariableNode> filtered = new ArrayList();
for (VariableNode node : nodes) {
if (filter.accept(node)) {
filtered.add(node);
}
}
return filtered;
}
/**
* Add all nodes in a list to a root node.
*
* @param root the root node to add to.
* @param nodes the list of nodes to add.
*/
protected void addAllNodes(DefaultMutableTreeNode root, List<? extends MutableTreeNode> nodes) {
for (MutableTreeNode node : nodes) {
root.add(node);
}
}
/**
* A filter for {@link VariableNode}s.
*/
public interface VariableNodeFilter {
/**
* Check whether the filter accepts a {@link VariableNode}.
*
* @param var the input node
* @return true when the filter accepts the input node otherwise false.
*/
public boolean accept(VariableNode var);
}
/**
* A {@link VariableNodeFilter} that accepts Processing built-in variable
* names.
*/
public class P5BuiltinsFilter implements VariableNodeFilter {
protected String[] p5Builtins = {
"focused",
"frameCount",
"frameRate",
"height",
"online",
"screen",
"width",
"mouseX",
"mouseY",
"pmouseX",
"pmouseY",
"key",
"keyCode",
"keyPressed"
};
@Override
public boolean accept(VariableNode var) {
return Arrays.asList(p5Builtins).contains(var.getName());
}
}
/**
* A {@link VariableNodeFilter} that rejects implicit this references.
* (Names starting with "this$")
*/
public class ThisFilter implements VariableNodeFilter {
@Override
public boolean accept(VariableNode var) {
return !var.getName().startsWith("this$");
}
}
/**
* A {@link VariableNodeFilter} that either rejects this-fields if hidden by
* a local, or prefixes its name with "this."
*/
public class LocalHidesThisFilter implements VariableNodeFilter {
/**
* Reject a this-field if hidden by a local.
*/
public static final int MODE_HIDE = 0; // don't show hidden this fields
/**
* Prefix a this-fields name with "this." if hidden by a local.
*/
public static final int MODE_PREFIX = 1; // prefix hidden this fields with "this."
protected List<VariableNode> locals;
protected int mode;
/**
* Construct a {@link LocalHidesThisFilter}.
*
* @param locals a list of locals to check against
* @param mode either {@link #MODE_HIDE} or {@link #MODE_PREFIX}
*/
public LocalHidesThisFilter(List<VariableNode> locals, int mode) {
this.locals = locals;
this.mode = mode;
}
@Override
public boolean accept(VariableNode var) {
// check if the same name appears in the list of locals i.e. the local hides the field
for (VariableNode local : locals) {
if (var.getName().equals(local.getName())) {
switch (mode) {
case MODE_PREFIX:
var.setName("this." + var.getName());
return true;
case MODE_HIDE:
return false;
}
}
}
return true;
}
}
}