diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 7fdfa5451..d9d5508be 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -29,10 +29,10 @@ import processing.app.ui.Toolkit; import processing.mode.java.debug.LineBreakpoint; import processing.mode.java.debug.LineHighlight; import processing.mode.java.debug.LineID; -import processing.mode.java.pdex.ASTGenerator; import processing.mode.java.pdex.ErrorCheckerService; import processing.mode.java.pdex.ImportStatement; import processing.mode.java.pdex.JavaTextArea; +import processing.mode.java.pdex.PDEX; import processing.mode.java.pdex.Problem; import processing.mode.java.pdex.SourceUtils; import processing.mode.java.preproc.PdePreprocessor; @@ -71,6 +71,7 @@ public class JavaEditor extends Editor { private boolean javaTabWarned; protected ErrorCheckerService errorCheckerService; + protected PDEX pdex; protected List problems = Collections.emptyList(); @@ -183,6 +184,21 @@ public class JavaEditor extends Editor { } errorCheckerService.start(); errorCheckerService.notifySketchChanged(); + + pdex = new PDEX(this, errorCheckerService); + + // Add ctrl+click listener + getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent evt) { + if (evt.getButton() == MouseEvent.BUTTON1) { + if ((evt.isControlDown() && !Platform.isMacOS()) || evt.isMetaDown()) { + handleCtrlClick(evt); + } + } else if (evt.getButton() == MouseEvent.BUTTON2) { + handleCtrlClick(evt); + } + } + }); } @@ -212,6 +228,7 @@ public class JavaEditor extends Editor { if (errorCheckerService != null) { if (hasJavaTabsChanged) { errorCheckerService.handleHasJavaTabsChange(hasJavaTabs); + pdex.handleHasJavaTabsChange(hasJavaTabs); if (hasJavaTabs) { setProblemList(Collections.emptyList()); } @@ -1358,6 +1375,7 @@ public class JavaEditor extends Editor { inspector.dispose(); } errorCheckerService.stop(); + pdex.dispose(); super.dispose(); } @@ -2795,25 +2813,31 @@ public class JavaEditor extends Editor { /** Handle refactor operation */ private void handleRefactor() { - Messages.log("Caret at:" + textarea.getLineText(textarea.getCaretLine())); - ASTGenerator astGenerator = errorCheckerService.getASTGenerator(); int startOffset = getSelectionStart(); int stopOffset = getSelectionStop(); int tabIndex = sketch.getCurrentCodeIndex(); - astGenerator.handleRename(tabIndex, startOffset, stopOffset); + pdex.handleRename(tabIndex, startOffset, stopOffset); } /** Handle show usage operation */ private void handleShowUsage() { - Messages.log("Caret at:" + textarea.getLineText(textarea.getCaretLine())); - ASTGenerator astGenerator = errorCheckerService.getASTGenerator(); int startOffset = getSelectionStart(); int stopOffset = getSelectionStop(); int tabIndex = sketch.getCurrentCodeIndex(); - astGenerator.handleShowUsage(tabIndex, startOffset, stopOffset); + pdex.handleShowUsage(tabIndex, startOffset, stopOffset); + } + + + /** Handle ctrl+click */ + private void handleCtrlClick(MouseEvent evt) { + int off = getJavaTextArea().xyToOffset(evt.getX(), evt.getY()); + if (off < 0) return; + int tabIndex = sketch.getCurrentCodeIndex(); + + pdex.handleCtrlClick(tabIndex, off); } diff --git a/java/src/processing/mode/java/pdex/ASTGenerator.java b/java/src/processing/mode/java/pdex/ASTGenerator.java index f98d271ec..eefdb5972 100644 --- a/java/src/processing/mode/java/pdex/ASTGenerator.java +++ b/java/src/processing/mode/java/pdex/ASTGenerator.java @@ -20,74 +20,33 @@ along with this program; if not, write to the Free Software Foundation, Inc. package processing.mode.java.pdex; -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Rectangle; -import java.awt.Window; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Consumer; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.JTree; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.ArrayAccess; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.Block; -import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; -import org.eclipse.jdt.core.dom.IBinding; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Name; -import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.QualifiedName; @@ -97,20 +56,11 @@ import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.select.Elements; import processing.app.Messages; -import processing.app.Sketch; -import processing.app.ui.EditorStatus; -import processing.app.ui.Toolkit; -import processing.mode.java.JavaEditor; -import processing.mode.java.pdex.PreprocessedSketch.SketchInterval; import com.google.classpath.ClassPath; import com.google.classpath.RegExpResourceFilter; @@ -118,18 +68,7 @@ import com.google.classpath.RegExpResourceFilter; @SuppressWarnings({ "unchecked" }) public class ASTGenerator { - public static final boolean SHOW_DEBUG_TREE = false; - - protected final ErrorCheckerService ecs; - protected final JavaEditor editor; - - protected final GUI gui; - - - public ASTGenerator(JavaEditor editor, ErrorCheckerService ecs) { - this.editor = editor; - this.ecs = ecs; - gui = new GUI(editor, this); + public ASTGenerator() { //addCompletionPopupListner(); //loadJavaDoc(); } @@ -964,142 +903,6 @@ public class ASTGenerator { } - public static SimpleName getSimpleNameAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { - Messages.log("* getSimpleNameAt"); - - // Find node at offset - ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset); - - SimpleName result = null; - - if (node == null) { - result = null; - } else if (node.getNodeType() == ASTNode.SIMPLE_NAME) { - result = (SimpleName) node; - } else { - // Return SimpleName with highest coverage - List simpleNames = getSimpleNameChildren(node); - if (!simpleNames.isEmpty()) { - // Compute coverage - int[] coverages = simpleNames.stream() - .mapToInt(name -> { - int start = name.getStartPosition(); - int stop = start + name.getLength(); - return Math.min(stop, stopJavaOffset) - - Math.max(startJavaOffset, start); - }) - .toArray(); - // Select node with highest coverage - int maxIndex = IntStream.range(0, simpleNames.size()) - .filter(i -> coverages[i] >= 0) - .reduce((i, j) -> coverages[i] > coverages[j] ? i : j) - .orElse(-1); - if (maxIndex == -1) return null; - result = simpleNames.get(maxIndex); - } - } - - if (node == null) { - Messages.log("no simple name found"); - } else { - Messages.log("found " + node.toString()); - } - return result; - } - - - public static ASTNode getASTNodeAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { - Messages.log("* getASTNodeAt"); - - int length = stopJavaOffset - startJavaOffset; - - NodeFinder f = new NodeFinder(root, startJavaOffset, length); - ASTNode node = f.getCoveredNode(); - if (node == null) { - node = f.getCoveringNode(); - } - if (node == null) { - Messages.log("no node found"); - } else { - Messages.log("found " + node.getClass().getSimpleName()); - } - return node; - } - - - public static List getSimpleNameChildren(ASTNode node) { - List simpleNames = new ArrayList<>(); - node.accept(new ASTVisitor() { - @Override - public boolean visit(SimpleName simpleName) { - simpleNames.add(simpleName); - return super.visit(simpleName); - } - }); - return simpleNames; - } - - - public static IBinding resolveBinding(SimpleName node) { - IBinding binding = node.resolveBinding(); - if (binding == null) return null; - - // Fix constructor call/declaration being resolved as type - if (binding.getKind() == IBinding.TYPE) { - ASTNode context = node; - while (isNameOrType(context) && - !context.getLocationInParent().getId().equals("typeArguments")) { - context = context.getParent(); - } - switch (context.getNodeType()) { - case ASTNode.METHOD_DECLARATION: - MethodDeclaration decl = (MethodDeclaration) context; - if (decl.isConstructor()) { - binding = decl.resolveBinding(); - } - break; - case ASTNode.CLASS_INSTANCE_CREATION: - ClassInstanceCreation cic = (ClassInstanceCreation) context; - binding = cic.resolveConstructorBinding(); - break; - } - } - - // Normalize parametrized and raw bindings into generic bindings - switch (binding.getKind()) { - case IBinding.TYPE: - ITypeBinding type = (ITypeBinding) binding; - if (type.isParameterizedType() || type.isRawType()) { - binding = type.getErasure(); - } - break; - case IBinding.METHOD: - IMethodBinding method = (IMethodBinding) binding; - ITypeBinding declaringClass = method.getDeclaringClass(); - if (declaringClass.isParameterizedType() || - declaringClass.isRawType()) { - IMethodBinding[] methods = declaringClass.getErasure().getDeclaredMethods(); - IMethodBinding generic = Arrays.stream(methods) - .filter(method::overrides) - .findAny().orElse(null); - if (generic != null) method = generic; - } - if (method.isParameterizedMethod() || method.isRawMethod()) { - method = method.getMethodDeclaration(); - } - binding = method; - break; - } - - return binding; - } - - - public static boolean isNameOrType(ASTNode node) { - return node instanceof Name || node instanceof Type; - } - - /** * Fetches line number of the node in its CompilationUnit. * @param node @@ -1111,143 +914,6 @@ public class ASTGenerator { } - protected void handleRename(PreprocessedSketch ps, IBinding binding, String newName) { - CompilationUnit root = ps.compilationUnit; - - // Renaming constructor should rename class - if (binding.getKind() == IBinding.METHOD) { - IMethodBinding method = (IMethodBinding) binding; - if (method.isConstructor()) { - binding = method.getDeclaringClass(); - } - } - - ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey()); - if (decl == null) return; - - List occurrences = new ArrayList<>(); - occurrences.addAll(findAllOccurrences(root, binding.getKey())); - - // Renaming class should rename all constructors - if (binding.getKind() == IBinding.TYPE) { - ITypeBinding type = (ITypeBinding) binding; - //type = type.getErasure(); - IMethodBinding[] methods = type.getDeclaredMethods(); - Arrays.stream(methods) - .filter(IMethodBinding::isConstructor) - .flatMap(c -> findAllOccurrences(root, c.getKey()).stream()) - .forEach(occurrences::add); - } - - Map> mappedNodes = occurrences.stream() - .map(ps::mapJavaToSketch) - .collect(Collectors.groupingBy(interval -> interval.tabIndex)); - - Sketch sketch = ps.sketch; - - editor.startCompoundEdit(); - - int currentTabIndex = sketch.getCurrentCodeIndex(); - final int currentOffset = editor.getCaretOffset(); - mappedNodes.entrySet().forEach(entry -> { - int tabIndex = entry.getKey(); - sketch.setCurrentCode(tabIndex); - - List nodes = entry.getValue(); - nodes.stream() - // Replace from the end so all unprocess offsets stay valid - .sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed()) - .forEach(si -> { - // Make sure offsets are in bounds - int length = editor.getTextArea().getDocumentLength(); - if (si.startTabOffset >= 0 && si.startTabOffset <= length && - si.stopTabOffset >= 0 && si.stopTabOffset <= length) { - // Replace the code - editor.getTextArea().select(si.startTabOffset, si.stopTabOffset); - editor.getTextArea().setSelectedText(newName); - } - }); - - sketch.setModified(true); - }); - - int precedingIntervals = - (int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList()) - .stream() - .filter(interval -> interval.stopTabOffset < currentOffset) - .count(); - int intervalLengthDiff = newName.length() - binding.getName().length(); - int offsetDiff = precedingIntervals * intervalLengthDiff; - - sketch.setCurrentCode(currentTabIndex); - editor.getTextArea().setCaretPosition(currentOffset + offsetDiff); - - editor.stopCompoundEdit(); - - if (gui.showUsageBinding != null) { - gui.showUsageWindow.setVisible(false); - } - } - - - // Thread: EDT - public void handleShowUsage(int tabIndex, int startTabOffset, int stopTabOffset) { - Messages.log("* handleShowUsage"); - - if (editor.hasJavaTabs()) return; // show usage disabled if java tabs - - ecs.acceptWhenDone(ps -> { - - // Map offsets - int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset); - int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset); - - // Find the node - SimpleName name = getSimpleNameAt(ps.compilationUnit, startJavaOffset, stopJavaOffset); - if (name == null) return; - - // Find binding - IBinding binding = resolveBinding(name); - if (binding == null) return; - - handleShowUsage(ps, binding); - }); - } - - - public void handleShowUsage(PreprocessedSketch ps, IBinding binding) { - String bindingKey = binding.getKey(); - - // Find occurrences of the node - List occurrences = findAllOccurrences(ps.compilationUnit, bindingKey); - if (occurrences == null) return; - - List occurrenceIntervals = occurrences.stream() - .map(ps::mapJavaToSketch) - .collect(Collectors.toList()); - - // Send to gui - EventQueue.invokeLater(() -> gui.handleShowUsage(ps, binding, occurrenceIntervals)); - } - - - protected static List findAllOccurrences(ASTNode root, String bindingKey) { - List occurences = new ArrayList<>(); - root.getRoot().accept(new ASTVisitor() { - @Override - public boolean visit(SimpleName name) { - IBinding binding = resolveBinding(name); - if (binding != null && bindingKey.equals(binding.getKey())) { - occurences.add(name); - } - return super.visit(name); - } - }); - - return occurences; - } - - /* protected SketchOutline sketchOutline; @@ -1265,52 +931,6 @@ public class ASTGenerator { */ - // Thread: EDT - public void handleRename(int tabIndex, int startTabOffset, int stopTabOffset) { - Messages.log("* handleRename"); - if (editor.hasJavaTabs()) return; // refactoring disabled w/ java tabs - - ecs.acceptWhenDone(ps -> { - if (ps.hasSyntaxErrors) { - editor.statusMessage("Can't perform action until syntax errors are fixed :(", - EditorStatus.WARNING); - return; - } - - ASTNode root = ps.compilationUnit; - - // Map offsets - int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset); - int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset); - - // Find the node - SimpleName name = getSimpleNameAt(root, startJavaOffset, stopJavaOffset); - if (name == null) { - editor.statusMessage("Highlight the class/function/variable name first", - EditorStatus.NOTICE); - return; - } - - // Find binding - IBinding binding = resolveBinding(name); - if (binding == null) { - editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " + - "so it cannot be renamed", EditorStatus.ERROR); - return; - } - - ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey()); - if (decl == null) { - editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " + - "so it cannot be renamed", EditorStatus.ERROR); - return; - } - - EventQueue.invokeLater(() -> gui.handleRename(ps, binding)); - }); - } - - /** * Give this thing a {@link Name} instance - a {@link SimpleName} from the * ASTNode for ex, and it tries its level best to locate its declaration in @@ -1698,10 +1318,6 @@ public class ASTGenerator { return null; } - public GUI getGui() { - return gui; - } - /** * A wrapper for java.lang.reflect types. @@ -2329,7 +1945,8 @@ public class ASTGenerator { /// JavaDocs ----------------------------------------------------------------- - + //TODO: Work on this later. +/* protected TreeMap jdocMap; @@ -2391,10 +2008,7 @@ public class ASTGenerator { } -/* public void updateJavaDoc(final CompletionCandidate candidate) { - //TODO: Work on this later. - return; String methodmatch = candidate.toString(); if (methodmatch.indexOf('(') != -1) { methodmatch = methodmatch.substring(0, methodmatch.indexOf('(')); @@ -2434,592 +2048,4 @@ public class ASTGenerator { } */ - - - /// Error checker ------------------------------------------------------------ - - - protected void updateDebugTree(PreprocessedSketch ps) { - CompilationUnit cu = ps.compilationUnit; - if (cu.types().isEmpty()){ - Messages.loge("No Type found in CU"); - return; - } - - ASTNode type0 = (ASTNode) cu.types().get(0); - DefaultMutableTreeNode codeTree = new DefaultMutableTreeNode(type0); - visitRecur(type0, codeTree); - EventQueue.invokeLater(() -> gui.updateDebugTree(codeTree)); - - // TODO: figure out what this is -// if (codeTree != null) { -// if (!frameAutoComp.isVisible()) { -// -// frameAutoComp.setVisible(true); -// -// } -// if (!frmJavaDoc.isVisible()) { -// long t = System.currentTimeMillis(); -// loadJavaDoc(); -// log("Time taken: " -// + (System.currentTimeMillis() - t)); -// frmJavaDoc.setBounds(new Rectangle(ecs.getEditor() -// .getX() + ecs.getEditor().getWidth(), -// ecs.getEditor() -// .getY(), 450, 600)); -// frmJavaDoc.setVisible(true); -// } -// } - } - - - /** - * Generates AST Swing component - * @param node - * @param tnode - */ - public static void visitRecur(ASTNode node, DefaultMutableTreeNode tnode) { - Iterator it = - node.structuralPropertiesForType().iterator(); - //Base.loge("Props of " + node.getClass().getName()); - DefaultMutableTreeNode ctnode; - while (it.hasNext()) { - StructuralPropertyDescriptor prop = it.next(); - - if (prop.isChildProperty() || prop.isSimpleProperty()) { - if (node.getStructuralProperty(prop) != null) { -// System.out -// .println(node.getStructuralProperty(prop) + " -> " + (prop)); - if (node.getStructuralProperty(prop) instanceof ASTNode) { - ASTNode cnode = (ASTNode) node.getStructuralProperty(prop); - if (isAddableASTNode(cnode)) { - ctnode = new DefaultMutableTreeNode(node.getStructuralProperty(prop)); - tnode.add(ctnode); - visitRecur(cnode, ctnode); - } - } else { - tnode.add(new DefaultMutableTreeNode(node - .getStructuralProperty(prop))); - } - } - } else if (prop.isChildListProperty()) { - List nodelist = (List) - node.getStructuralProperty(prop); - for (ASTNode cnode : nodelist) { - if (isAddableASTNode(cnode)) { - ctnode = new DefaultMutableTreeNode(cnode); - tnode.add(ctnode); - visitRecur(cnode, ctnode); - } else { - visitRecur(cnode, tnode); - } - } - } - } - } - - - public static boolean isAddableASTNode(ASTNode node) { - switch (node.getNodeType()) { -// case ASTNode.STRING_LITERAL: -// case ASTNode.NUMBER_LITERAL: -// case ASTNode.BOOLEAN_LITERAL: -// case ASTNode.NULL_LITERAL: -// return false; - default: - return true; - } - } - - - - /// Editor stuff ------------------------------------------------------------- - - - // Thread: EDT - public void handleCtrlClick(int tabIndex, int offset) { - Messages.log("* handleCtrlClick"); - - // TODO: don't run the heavy lifting on EDT - - ecs.acceptWhenDone(ps -> { - ASTNode root = ps.compilationUnit; - - int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset); - - SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset); - - if (simpleName == null) { - Messages.log("nothing found"); - return; - } - - IBinding binding = resolveBinding(simpleName); - if (binding == null) { - Messages.log("binding not resolved"); - return; - } - - String key = binding.getKey(); - ASTNode decl = ps.compilationUnit.findDeclaringNode(key); - if (decl == null) { - Messages.log("decl not found, showing usage instead"); - handleShowUsage(ps, binding); - return; - } - - SimpleName declName = null; - switch (binding.getKind()) { - case IBinding.TYPE: declName = ((TypeDeclaration) decl).getName(); break; - case IBinding.METHOD: declName = ((MethodDeclaration) decl).getName(); break; - case IBinding.VARIABLE: declName = ((VariableDeclaration) decl).getName(); break; - } - if (declName == null) { - Messages.log("decl name not found " + decl); - return; - } - - if (declName.equals(simpleName)) { - handleShowUsage(ps, binding); - } else { - Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName); - highlightNode(ps, declName); - } - }); - } - - - public void highlightNode(PreprocessedSketch ps, ASTNode node) { - SketchInterval si = ps.mapJavaToSketch(node); - EventQueue.invokeLater(() -> { - editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset); - }); - } - - - - /// GUI ---------------------------------------------------------------------- - - protected static class GUI { - - // Rename window - protected JDialog renameWindow; - protected JTextField renameTextField; - protected JLabel renameOldNameLabel; - protected JButton showUsageButton; - protected JButton renameButton; - protected IBinding renameBinding; - protected PreprocessedSketch ps; - - // Show usage window - protected JDialog showUsageWindow; - protected JTree showUsageTree; - protected IBinding showUsageBinding; - - protected final JavaEditor editor; - protected final ASTGenerator astGen; - - protected Consumer updateDebugTreeListener; - protected Consumer reloadShowUsageListener; - - - protected GUI(JavaEditor editor, ASTGenerator astGen) { - this.editor = editor; - this.astGen = astGen; - - updateDebugTreeListener = astGen::updateDebugTree; - reloadShowUsageListener = this::reloadShowUsage; - - setupGUI(); - addListeners(); - - } - - protected void setupGUI() { - - if (SHOW_DEBUG_TREE) initDebugWindow(); - - { // Rename window - renameWindow = new JDialog(editor); - renameWindow.setTitle("Enter new name:"); - renameWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - renameWindow.setModal(true); - renameWindow.setResizable(false); - renameWindow.addComponentListener(new ComponentAdapter() { - @Override - public void componentHidden(ComponentEvent e) { - renameBinding = null; - ps = null; - } - }); - renameWindow.setSize(250, 130); - renameWindow.setLayout(new BoxLayout(renameWindow.getContentPane(), BoxLayout.Y_AXIS)); - Toolkit.setIcon(renameWindow); - - { // Top panel - - // Text field - renameTextField = new JTextField(); - renameTextField.setPreferredSize(new Dimension(150, 60)); - - // Old name label - renameOldNameLabel = new JLabel(); - renameOldNameLabel.setText("Old Name: "); - - // Top panel - JPanel panelTop = new JPanel(); - panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS)); - panelTop.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - panelTop.add(renameTextField); - panelTop.add(Box.createRigidArea(new Dimension(0, 10))); - panelTop.add(renameOldNameLabel); - renameWindow.add(panelTop); - } - - { // Bottom panel - showUsageButton = new JButton("Show Usage"); - renameButton = new JButton("Rename"); - - JPanel panelBottom = new JPanel(); - panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS)); - panelBottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - panelBottom.add(Box.createHorizontalGlue()); - panelBottom.add(showUsageButton); - panelBottom.add(Box.createRigidArea(new Dimension(15, 0))); - panelBottom.add(renameButton); - renameWindow.add(panelBottom); - } - - //renameWindow.setVisible(true); - renameWindow.setMinimumSize(renameWindow.getSize()); - renameWindow.setLocation(editor.getX() - + (editor.getWidth() - renameWindow.getWidth()) / 2, - editor.getY() - + (editor.getHeight() - renameWindow.getHeight()) - / 2); - } - - { // Show Usage window - showUsageWindow = new JDialog(editor); - showUsageWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - showUsageWindow.setAutoRequestFocus(false); - showUsageWindow.addComponentListener(new ComponentAdapter() { - @Override - public void componentHidden(ComponentEvent e) { - // Delete references to ASTNodes so that whole AST can be GC'd - showUsageBinding = null; - showUsageTree.setModel(null); - astGen.ecs.unregisterDoneListener(reloadShowUsageListener); - } - - @Override - public void componentShown(ComponentEvent e) { - astGen.ecs.registerDoneListener(reloadShowUsageListener); - } - }); - showUsageWindow.setSize(300, 400); - Toolkit.setIcon(showUsageWindow); - JScrollPane sp2 = new JScrollPane(); - showUsageTree = new JTree(); - DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) showUsageTree.getCellRenderer(); - renderer.setLeafIcon(null); - renderer.setClosedIcon(null); - renderer.setOpenIcon(null); - sp2.setViewportView(showUsageTree); - showUsageWindow.add(sp2); - } - } - - - protected void addListeners() { - - if (SHOW_DEBUG_TREE) { - addDebugTreeListener(); - } - - renameButton.addActionListener(e -> { - if (renameTextField.getText().length() == 0) { - return; - } - String newName = renameTextField.getText().trim(); - boolean isNewNameValid = newName.length() >= 1 && - newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) && - newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart); - if (!isNewNameValid) { - JOptionPane.showMessageDialog(new JFrame(), "'" + newName - + "' isn't a valid name.", "Uh oh..", JOptionPane.PLAIN_MESSAGE); - } else { - astGen.handleRename(ps, renameBinding, newName); - renameWindow.setVisible(false); - } - }); - - showUsageButton.addActionListener(e -> { - astGen.handleShowUsage(ps, renameBinding); - renameWindow.setVisible(false); - }); - - showUsageTree.addTreeSelectionListener(e -> { - if (showUsageTree.getLastSelectedPathComponent() == null) { - return; - } - DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) showUsageTree - .getLastSelectedPathComponent(); - - if (tnode.getUserObject() instanceof ShowUsageTreeNode) { - ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject(); - editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset); - } - }); - } - - - public void handleRename(PreprocessedSketch ps, IBinding binding) { - if (!renameWindow.isVisible()){ - this.ps = ps; - renameBinding = binding; - renameWindow.setLocation(editor.getX() - + (editor.getWidth() - renameWindow.getWidth()) / 2, - editor.getY() - + (editor.getHeight() - renameWindow.getHeight()) - / 2); - renameOldNameLabel.setText("Current name: " + binding.getName()); - renameTextField.setText(binding.getName()); - renameTextField.requestFocus(); - renameTextField.selectAll(); - renameWindow.setVisible(true); - renameWindow.toFront(); - } - } - - - public void handleShowUsage(PreprocessedSketch ps, IBinding binding, List occurrences) { - showUsageBinding = binding; - - String bindingType = ""; - switch (binding.getKind()) { - case IBinding.METHOD: - IMethodBinding method = (IMethodBinding) binding; - if (method.isConstructor()) bindingType = "Constructor"; - else bindingType = "Method"; - break; - case IBinding.TYPE: bindingType = "Type"; break; - case IBinding.VARIABLE: - IVariableBinding variable = (IVariableBinding) binding; - if (variable.isField()) bindingType = "Field"; - else if (variable.isParameter()) bindingType = "Parameter"; - else if (variable.isEnumConstant()) bindingType = "Enum constant"; - else bindingType = "Local variable"; - break; - } - - DefaultMutableTreeNode rootNode = - new DefaultMutableTreeNode(bindingType + ": " + binding.getName()); - - Map> tabGroupedTreeNodes = occurrences.stream() - // TODO: this has to be fixed with better token mapping - // remove occurrences which fall into generated header - .filter(in -> in.tabIndex != 0 || - (in.startTabOffset >= 0 && in.stopTabOffset > 0)) - // Convert to TreeNodes - .map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in)) - // Group by tab - .collect(Collectors.groupingBy(node -> node.tabIndex)); - - tabGroupedTreeNodes.entrySet().stream() - // Sort by tab index - .sorted(Comparator.comparing(Map.Entry::getKey)) - .map(entry -> { - Integer tabIndex = entry.getKey(); - List nodes = entry.getValue(); - - int count = nodes.size(); - String usageLabel = count == 1 ? "usage" : "usages"; - - // Create new DefaultMutableTreeNode for this tab - String tabLabel = "" + - ps.sketch.getCode(tabIndex).getPrettyName() + - " " + count + " " + usageLabel + ""; - DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel); - - nodes.stream() - // Convert TreeNodes to DefaultMutableTreeNodes - .map(DefaultMutableTreeNode::new) - // Add all as children of tab node - .forEach(tabNode::add); - return tabNode; - }) - // Add all tab nodes as children of root node - .forEach(rootNode::add); - - DefaultTreeModel model = new DefaultTreeModel(rootNode); - showUsageTree.setModel(model); - - // Expand all nodes - for (int i = 0; i < showUsageTree.getRowCount(); i++) { - showUsageTree.expandRow(i); - } - - showUsageTree.setRootVisible(true); - - if (!showUsageWindow.isVisible()) { - showUsageWindow.setVisible(true); - GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice defaultScreen = ge.getDefaultScreenDevice(); - Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds(); - int maxX = (int) rect.getMaxX() - showUsageWindow.getWidth(); - int x = Math.min(editor.getX() + editor.getWidth(), maxX); - int y = (x == maxX) ? 10 : editor.getY(); - showUsageWindow.setLocation(x, y); - } - showUsageWindow.toFront(); - showUsageWindow.setTitle("Usage of \"" + binding.getName() + "\" : " - + occurrences.size() + " time(s)"); - } - - - // Thread: worker - public void reloadShowUsage(PreprocessedSketch ps) { - if (showUsageBinding != null) { - astGen.handleShowUsage(ps, showUsageBinding); - } - } - - - public void disposeAllWindows() { - Messages.log("* disposeAllWindows"); - disposeWindow(showUsageWindow, renameWindow); - - if (debugTreeWindow != null) disposeWindow(debugTreeWindow); - } - - - public static void disposeWindow(Window... w) { - for (Window window : w) { - if (window != null) - window.dispose(); - } - } - - - /// DEBUG -------------------------------------------------------------------- - - - protected JFrame debugTreeWindow; - - /** Swing component wrapper for AST, used for internal testing */ - protected JTree debugTree; - - - protected void initDebugWindow() { - debugTreeWindow = new JFrame(); - - debugTree = new JTree() { - @Override - public String convertValueToText(Object value, boolean selected, - boolean expanded, boolean leaf, - int row, boolean hasFocus) { - if (value instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value; - Object o = treeNode.getUserObject(); - if (o instanceof ASTNode) { - ASTNode node = (ASTNode) o; - return getNodeAsString(node); - } - } - return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus); - } - }; - debugTreeWindow.addComponentListener(new ComponentAdapter() { - @Override - public void componentHidden(ComponentEvent e) { - astGen.ecs.unregisterDoneListener(updateDebugTreeListener); - } - }); - debugTreeWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - debugTreeWindow.setBounds(new Rectangle(680, 100, 460, 620)); - debugTreeWindow.setTitle("AST View - " + editor.getSketch().getName()); - JScrollPane sp = new JScrollPane(); - sp.setViewportView(debugTree); - debugTreeWindow.add(sp); - astGen.ecs.registerDoneListener(updateDebugTreeListener); - } - - - protected void updateDebugTree(DefaultMutableTreeNode codeTree) { - if (debugTree.hasFocus() || debugTreeWindow.hasFocus()) { - return; - } - debugTree.setModel(new DefaultTreeModel(codeTree)); - ((DefaultTreeModel) debugTree.getModel()).reload(); - debugTree.validate(); - if (!debugTreeWindow.isVisible()) { - debugTreeWindow.setVisible(true); - } - } - - - protected void addDebugTreeListener() { - debugTree.addTreeSelectionListener(e -> { - if (debugTree.getLastSelectedPathComponent() == null) { - return; - } - DefaultMutableTreeNode tnode = - (DefaultMutableTreeNode) debugTree.getLastSelectedPathComponent(); - if (tnode.getUserObject() instanceof ASTNode) { - ASTNode node = (ASTNode) tnode.getUserObject(); - - astGen.ecs.acceptWhenDone(ps1 -> astGen.highlightNode(ps1, node)); - } - }); - } - - } - - protected static class ShowUsageTreeNode { - - int tabIndex; - int startTabOffset; - int stopTabOffset; - - String text; - - public static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) { - int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1; - int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset); - if (lineStopPdeOffset == -1) lineStopPdeOffset = ps.pdeCode.length(); - - int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset; - int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset; - - int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); - - // TODO: what a mess - String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset); - String pre = line.substring(0, highlightStartOffset) - .replace("&", "&").replace(">", ">").replace("<", "<"); - String highlight = line.substring(highlightStartOffset, highlightStopOffset) - .replace("&", "&").replace(">", ">").replace("<", "<"); - String post = line.substring(highlightStopOffset) - .replace("&", "&").replace(">", ">").replace("<", "<"); - line = pre + "" + highlight + "" + post; - line = line.trim(); - - ShowUsageTreeNode node = new ShowUsageTreeNode(); - node.tabIndex = in.tabIndex; - node.startTabOffset = in.startTabOffset; - node.stopTabOffset = in.stopTabOffset; - - node.text = "" + - (tabLine + 1) + " " + line + ""; - - return node; - } - - @Override - public String toString() { - return text; - } - } - } diff --git a/java/src/processing/mode/java/pdex/ASTUtils.java b/java/src/processing/mode/java/pdex/ASTUtils.java new file mode 100644 index 000000000..0a8c03e3b --- /dev/null +++ b/java/src/processing/mode/java/pdex/ASTUtils.java @@ -0,0 +1,183 @@ +package processing.mode.java.pdex; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.Type; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +import processing.app.Messages; + + +public class ASTUtils { + + public static ASTNode getASTNodeAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { + Messages.log("* getASTNodeAt"); + + int length = stopJavaOffset - startJavaOffset; + + NodeFinder f = new NodeFinder(root, startJavaOffset, length); + ASTNode node = f.getCoveredNode(); + if (node == null) { + node = f.getCoveringNode(); + } + if (node == null) { + Messages.log("no node found"); + } else { + Messages.log("found " + node.getClass().getSimpleName()); + } + return node; + } + + + public static SimpleName getSimpleNameAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { + Messages.log("* getSimpleNameAt"); + + // Find node at offset + ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset); + + SimpleName result = null; + + if (node == null) { + result = null; + } else if (node.getNodeType() == ASTNode.SIMPLE_NAME) { + result = (SimpleName) node; + } else { + // Return SimpleName with highest coverage + List simpleNames = getSimpleNameChildren(node); + if (!simpleNames.isEmpty()) { + // Compute coverage + int[] coverages = simpleNames.stream() + .mapToInt(name -> { + int start = name.getStartPosition(); + int stop = start + name.getLength(); + return Math.min(stop, stopJavaOffset) - + Math.max(startJavaOffset, start); + }) + .toArray(); + // Select node with highest coverage + int maxIndex = IntStream.range(0, simpleNames.size()) + .filter(i -> coverages[i] >= 0) + .reduce((i, j) -> coverages[i] > coverages[j] ? i : j) + .orElse(-1); + if (maxIndex == -1) return null; + result = simpleNames.get(maxIndex); + } + } + + if (result == null) { + Messages.log("no simple name found"); + } else { + Messages.log("found " + node.toString()); + } + return result; + } + + + public static List getSimpleNameChildren(ASTNode node) { + List simpleNames = new ArrayList<>(); + node.accept(new ASTVisitor() { + @Override + public boolean visit(SimpleName simpleName) { + simpleNames.add(simpleName); + return super.visit(simpleName); + } + }); + return simpleNames; + } + + + public static IBinding resolveBinding(SimpleName node) { + IBinding binding = node.resolveBinding(); + if (binding == null) return null; + + // Fix constructor call/declaration being resolved as type + if (binding.getKind() == IBinding.TYPE) { + ASTNode context = node; + + // Go up until we find non Name or Type node + // stop if context is type argument (parent is also Name/Type, but unrelated) + while (isNameOrType(context) && + !context.getLocationInParent().getId().equals("typeArguments")) { + context = context.getParent(); + } + + switch (context.getNodeType()) { + case ASTNode.METHOD_DECLARATION: + MethodDeclaration decl = (MethodDeclaration) context; + if (decl.isConstructor()) { + binding = decl.resolveBinding(); + } + break; + case ASTNode.CLASS_INSTANCE_CREATION: + ClassInstanceCreation cic = (ClassInstanceCreation) context; + binding = cic.resolveConstructorBinding(); + break; + } + } + + if (binding == null) return null; + + // Normalize parametrized and raw bindings into generic bindings + switch (binding.getKind()) { + case IBinding.TYPE: + ITypeBinding type = (ITypeBinding) binding; + if (type.isParameterizedType() || type.isRawType()) { + binding = type.getErasure(); + } + break; + case IBinding.METHOD: + IMethodBinding method = (IMethodBinding) binding; + ITypeBinding declaringClass = method.getDeclaringClass(); + if (declaringClass.isParameterizedType() || + declaringClass.isRawType()) { + IMethodBinding[] methods = declaringClass.getErasure().getDeclaredMethods(); + IMethodBinding generic = Arrays.stream(methods) + .filter(method::overrides) + .findAny().orElse(null); + if (generic != null) method = generic; + } + if (method.isParameterizedMethod() || method.isRawMethod()) { + method = method.getMethodDeclaration(); + } + binding = method; + break; + } + + return binding; + } + + + public static boolean isNameOrType(ASTNode node) { + return node instanceof Name || node instanceof Type; + } + + + protected static List findAllOccurrences(ASTNode root, String bindingKey) { + List occurences = new ArrayList<>(); + root.getRoot().accept(new ASTVisitor() { + @Override + public boolean visit(SimpleName name) { + IBinding binding = resolveBinding(name); + if (binding != null && bindingKey.equals(binding.getKey())) { + occurences.add(name); + } + return super.visit(name); + } + }); + + return occurences; + } + +} diff --git a/java/src/processing/mode/java/pdex/ErrorCheckerService.java b/java/src/processing/mode/java/pdex/ErrorCheckerService.java index 6fa7c6b30..df5c0e764 100644 --- a/java/src/processing/mode/java/pdex/ErrorCheckerService.java +++ b/java/src/processing/mode/java/pdex/ErrorCheckerService.java @@ -99,11 +99,6 @@ public class ErrorCheckerService { */ private volatile boolean running; - /** - * ASTGenerator for operations on AST - */ - protected final ASTGenerator astGenerator; - /** * Error checking doesn't happen before this interval has ellapsed since the * last request() call. @@ -137,7 +132,6 @@ public class ErrorCheckerService { public ErrorCheckerService(JavaEditor editor) { this.editor = editor; - astGenerator = new ASTGenerator(editor, this); isEnabled = !editor.hasJavaTabs(); isContinuousCheckEnabled = JavaMode.errorCheckEnabled; registerDoneListener(errorHandlerListener); @@ -170,8 +164,6 @@ public class ErrorCheckerService { Messages.loge("problem in error checker loop", e); } } - - astGenerator.getGui().disposeAllWindows(); } @@ -303,11 +295,6 @@ public class ErrorCheckerService { } - public ASTGenerator getASTGenerator() { - return astGenerator; - } - - protected final DocumentListener sketchChangedListener = new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { @@ -929,9 +916,6 @@ public class ErrorCheckerService { notifySketchChanged(); } else { preprocessingTask.cancel(false); - if (astGenerator.getGui().showUsageBinding != null) { - astGenerator.getGui().showUsageWindow.setVisible(false); - } } } diff --git a/java/src/processing/mode/java/pdex/JavaTextArea.java b/java/src/processing/mode/java/pdex/JavaTextArea.java index ec8167124..b1cebfa8e 100644 --- a/java/src/processing/mode/java/pdex/JavaTextArea.java +++ b/java/src/processing/mode/java/pdex/JavaTextArea.java @@ -90,6 +90,8 @@ public class JavaTextArea extends JEditTextArea { prevMMotionListeners = painter.getMouseMotionListeners(); prevKeyListeners = editor.getKeyListeners(); + suggestionGenerator = new ASTGenerator(); + tweakMode = false; } @@ -254,6 +256,8 @@ public class JavaTextArea extends JEditTextArea { } + ASTGenerator suggestionGenerator; + SwingWorker suggestionWorker = null; volatile boolean suggestionRunning = false; @@ -331,8 +335,7 @@ public class JavaTextArea extends JEditTextArea { if (phrase != null) { List candidates; - ASTGenerator astGenerator = editor.getErrorChecker().getASTGenerator(); - candidates = astGenerator.preparePredictions(ps, phrase, lineNumber); + candidates = suggestionGenerator.preparePredictions(ps, phrase, lineNumber); if (!suggestionRequested) { diff --git a/java/src/processing/mode/java/pdex/JavaTextAreaPainter.java b/java/src/processing/mode/java/pdex/JavaTextAreaPainter.java index 1602946e3..286b9a79a 100644 --- a/java/src/processing/mode/java/pdex/JavaTextAreaPainter.java +++ b/java/src/processing/mode/java/pdex/JavaTextAreaPainter.java @@ -47,7 +47,6 @@ import javax.swing.text.Utilities; import processing.app.Messages; import processing.app.Mode; -import processing.app.Platform; import processing.app.SketchCode; import processing.app.syntax.SyntaxDocument; import processing.app.syntax.TextAreaDefaults; @@ -77,20 +76,6 @@ public class JavaTextAreaPainter extends TextAreaPainter public JavaTextAreaPainter(final JavaTextArea textArea, TextAreaDefaults defaults) { super(textArea, defaults); - addMouseListener(new MouseAdapter() { - public void mouseReleased(MouseEvent evt) { - if (!getJavaEditor().hasJavaTabs()) { // Ctrl + Click disabled for java tabs - if (evt.getButton() == MouseEvent.BUTTON1) { - if ((evt.isControlDown() && !Platform.isMacOS()) || evt.isMetaDown()) { - handleCtrlClick(evt); - } - } else if (evt.getButton() == MouseEvent.BUTTON2) { - handleCtrlClick(evt); - } - } - } - }); - // Handle mouse clicks to toggle breakpoints addMouseListener(new MouseAdapter() { long lastTime; // OS X seems to be firing multiple mouse events @@ -120,19 +105,6 @@ public class JavaTextAreaPainter extends TextAreaPainter cursorType = Cursor.DEFAULT_CURSOR; } - - void handleCtrlClick(MouseEvent evt) { - int off = textArea.xyToOffset(evt.getX(), evt.getY()); - if (off < 0) return; - - int tabIndex = getEditor().getSketch().getCurrentCodeIndex(); - - ASTGenerator astGenerator = getJavaEditor().getErrorChecker().getASTGenerator(); - - astGenerator.handleCtrlClick(tabIndex, off); - } - - /** * Paint a line. Paints the gutter (with background color and text) then the * line (background color and text). diff --git a/java/src/processing/mode/java/pdex/PDEX.java b/java/src/processing/mode/java/pdex/PDEX.java new file mode 100644 index 000000000..43ec20753 --- /dev/null +++ b/java/src/processing/mode/java/pdex/PDEX.java @@ -0,0 +1,843 @@ +package processing.mode.java.pdex; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclaration; + +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Rectangle; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; + +import processing.app.Messages; +import processing.app.Sketch; +import processing.app.ui.EditorStatus; +import processing.app.ui.Toolkit; +import processing.mode.java.JavaEditor; +import processing.mode.java.pdex.PreprocessedSketch.SketchInterval; + +import static processing.mode.java.pdex.ASTUtils.*; + +public class PDEX { + + private static final boolean SHOW_DEBUG_TREE = true; + + private boolean enabled = true; + + private ShowUsage showUsage; + private Rename rename; + private DebugTree debugTree; + + private JavaEditor editor; + private ErrorCheckerService ecs; + + + public PDEX(JavaEditor editor, ErrorCheckerService ecs) { + this.editor = editor; + this.ecs = ecs; + + this.enabled = !editor.hasJavaTabs(); + + showUsage = new ShowUsage(editor, ecs); + rename = new Rename(editor); + if (SHOW_DEBUG_TREE) { + debugTree = new DebugTree(editor, ecs); + } + } + + + public void handleShowUsage(int tabIndex, int startTabOffset, int stopTabOffset) { + Messages.log("* handleShowUsage"); + if (!enabled) return; // show usage disabled if java tabs + ecs.acceptWhenDone(ps -> showUsage.findUsageAndUpdateTree(ps, tabIndex, startTabOffset, stopTabOffset)); + } + + + public void handleRename(int tabIndex, int startTabOffset, int stopTabOffset) { + Messages.log("* handleRename"); + if (!enabled) return; // refactoring disabled w/ java tabs + ecs.acceptWhenDone(ps -> rename.handleRename(ps, tabIndex, startTabOffset, stopTabOffset)); + } + + + public void handleCtrlClick(int tabIndex, int offset) { + Messages.log("* handleCtrlClick"); + if (!enabled) return; // disabled w/ java tabs + ecs.acceptWhenDone(ps -> handleCtrlClick(ps, tabIndex, offset)); + } + + + public void handleHasJavaTabsChange(boolean hasJavaTabs) { + enabled = !hasJavaTabs; + if (!enabled) { + showUsage.hide(); + } + } + + + public void dispose() { + showUsage.dispose(); + rename.dispose(); + if (debugTree != null) { + debugTree.dispose(); + } + } + + + // Thread: worker + private void handleCtrlClick(PreprocessedSketch ps, int tabIndex, int offset) { + ASTNode root = ps.compilationUnit; + int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset); + + SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset); + + if (simpleName == null) { + Messages.log("no simple name found at click location"); + return; + } + + IBinding binding = resolveBinding(simpleName); + if (binding == null) { + Messages.log("binding not resolved"); + return; + } + + String key = binding.getKey(); + ASTNode decl = ps.compilationUnit.findDeclaringNode(key); + if (decl == null) { + Messages.log("decl not found, showing usage instead"); + showUsage.findUsageAndUpdateTree(ps, binding); + return; + } + + SimpleName declName = null; + switch (binding.getKind()) { + case IBinding.TYPE: declName = ((TypeDeclaration) decl).getName(); break; + case IBinding.METHOD: declName = ((MethodDeclaration) decl).getName(); break; + case IBinding.VARIABLE: declName = ((VariableDeclaration) decl).getName(); break; + } + if (declName == null) { + Messages.log("decl name not found " + decl); + return; + } + + if (declName.equals(simpleName)) { + showUsage.findUsageAndUpdateTree(ps, binding); + } else { + Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName); + SketchInterval si = ps.mapJavaToSketch(declName); + EventQueue.invokeLater(() -> { + editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset); + }); + } + } + + + + private class ShowUsage { + + final JDialog window; + final JTree tree; + + final JavaEditor editor; + final ErrorCheckerService ecs; + + final Consumer reloadListener; + + IBinding binding; + + + ShowUsage(JavaEditor editor, ErrorCheckerService ecs) { + this.editor = editor; + this.ecs = ecs; + + reloadListener = this::reloadShowUsage; + + { // Show Usage window + window = new JDialog(editor); + window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + window.setAutoRequestFocus(false); + window.addComponentListener(new ComponentAdapter() { + @Override + public void componentHidden(ComponentEvent e) { + // Delete references to ASTNodes so that whole AST can be GC'd + binding = null; + tree.setModel(null); + ecs.unregisterDoneListener(reloadListener); + } + + @Override + public void componentShown(ComponentEvent e) { + ecs.registerDoneListener(reloadListener); + } + }); + window.setSize(300, 400); + Toolkit.setIcon(window); + JScrollPane sp2 = new JScrollPane(); + tree = new JTree(); + DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tree.getCellRenderer(); + renderer.setLeafIcon(null); + renderer.setClosedIcon(null); + renderer.setOpenIcon(null); + sp2.setViewportView(tree); + window.add(sp2); + } + + tree.addTreeSelectionListener(e -> { + if (tree.getLastSelectedPathComponent() == null) { + return; + } + DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) tree + .getLastSelectedPathComponent(); + + if (tnode.getUserObject() instanceof ShowUsageTreeNode) { + ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject(); + editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset); + } + }); + + } + + + // Thread: worker + void findUsageAndUpdateTree(PreprocessedSketch ps, int tabIndex, + int startTabOffset, int stopTabOffset) { + // Map offsets + int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset); + int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset); + + // Find the node + SimpleName name = ASTUtils.getSimpleNameAt(ps.compilationUnit, startJavaOffset, stopJavaOffset); + if (name == null) return; + + // Find binding + IBinding binding = ASTUtils.resolveBinding(name); + if (binding == null) return; + + this.binding = binding; + + findUsageAndUpdateTree(ps, binding); + } + + + // Thread: worker + void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding binding) { + + // Get label + String bindingType = ""; + switch (binding.getKind()) { + case IBinding.METHOD: + IMethodBinding method = (IMethodBinding) binding; + if (method.isConstructor()) bindingType = "Constructor"; + else bindingType = "Method"; + break; + case IBinding.TYPE: + bindingType = "Type"; + break; + case IBinding.VARIABLE: + IVariableBinding variable = (IVariableBinding) binding; + if (variable.isField()) bindingType = "Field"; + else if (variable.isParameter()) bindingType = "Parameter"; + else if (variable.isEnumConstant()) bindingType = "Enum constant"; + else bindingType = "Local variable"; + break; + } + + String elementName = binding.getName(); + + // Create root node + DefaultMutableTreeNode rootNode = + new DefaultMutableTreeNode(bindingType + ": " + elementName); + + int usageCount; + + { // Find usages, map to tree nodes, add to root node + String bindingKey = binding.getKey(); + List intervals = + findAllOccurrences(ps.compilationUnit, bindingKey).stream() + .map(ps::mapJavaToSketch) + // TODO: this has to be fixed with better token mapping + // remove occurrences which fall into generated header + .filter(in -> in.tabIndex != 0 || + (in.startTabOffset >= 0 && in.stopTabOffset > 0)) + .collect(Collectors.toList()); + + usageCount = intervals.size(); + + Map> tabGroupedTreeNodes = intervals.stream() + // Convert to TreeNodes + .map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in)) + // Group by tab + .collect(Collectors.groupingBy(node -> node.tabIndex)); + + tabGroupedTreeNodes.entrySet().stream() + // Sort by tab index + .sorted(Comparator.comparing(Map.Entry::getKey)) + .map(entry -> { + Integer tabIndex = entry.getKey(); + List nodes = entry.getValue(); + + int count = nodes.size(); + String usageLabel = count == 1 ? "usage" : "usages"; + + // Create new DefaultMutableTreeNode for this tab + String tabLabel = "" + + ps.sketch.getCode(tabIndex).getPrettyName() + + " " + count + " " + usageLabel + ""; + DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel); + + nodes.stream() + // Convert TreeNodes to DefaultMutableTreeNodes + .map(DefaultMutableTreeNode::new) + // Add all as children of tab node + .forEach(tabNode::add); + return tabNode; + }) + // Add all tab nodes as children of root node + .forEach(rootNode::add); + } + + TreeModel treeModel = new DefaultTreeModel(rootNode); + + // Update tree + EventQueue.invokeLater(() -> { + tree.setModel(treeModel); + + // Expand all nodes + for (int i = 0; i < tree.getRowCount(); i++) { + tree.expandRow(i); + } + + tree.setRootVisible(true); + + if (!window.isVisible()) { + window.setVisible(true); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice defaultScreen = ge.getDefaultScreenDevice(); + Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds(); + int maxX = (int) rect.getMaxX() - window.getWidth(); + int x = Math.min(editor.getX() + editor.getWidth(), maxX); + int y = (x == maxX) ? 10 : editor.getY(); + window.setLocation(x, y); + } + window.toFront(); + window.setTitle("Usage of \"" + elementName + "\" : " + + usageCount + " time(s)"); + }); + } + + + // Thread: worker + void reloadShowUsage(PreprocessedSketch ps) { + if (binding != null) { + findUsageAndUpdateTree(ps, binding); + } + } + + + void hide() { + window.setVisible(false); + } + + + void dispose() { + if (window != null) { + window.dispose(); + } + } + + } + + + private static class ShowUsageTreeNode { + + final int tabIndex; + final int startTabOffset; + final int stopTabOffset; + + final String text; + + + ShowUsageTreeNode(int tabIndex, int startTabOffset, int stopTabOffset, String text) { + this.tabIndex = tabIndex; + this.startTabOffset = startTabOffset; + this.stopTabOffset = stopTabOffset; + this.text = text; + } + + + static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) { + int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1; + int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset); + if (lineStopPdeOffset == -1) lineStopPdeOffset = ps.pdeCode.length(); + + int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset; + int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset; + + int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); + + // TODO: what a mess + String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset); + String pre = line.substring(0, highlightStartOffset) + .replace("&", "&").replace(">", ">").replace("<", "<"); + String highlight = line.substring(highlightStartOffset, highlightStopOffset) + .replace("&", "&").replace(">", ">").replace("<", "<"); + String post = line.substring(highlightStopOffset) + .replace("&", "&").replace(">", ">").replace("<", "<"); + line = pre + "" + highlight + "" + post; + line = line.trim(); + + + String text = "" + + (tabLine + 1) + " " + line + ""; + + return new ShowUsageTreeNode(in.tabIndex, in.startTabOffset, in.stopTabOffset, text); + } + + @Override + public String toString() { + return text; + } + } + + + + private class Rename { + + final JDialog window; + final JTextField textField; + final JLabel oldNameLabel; + + final JavaEditor editor; + + IBinding binding; + PreprocessedSketch ps; + + + Rename(JavaEditor editor) { + this.editor = editor; + + window = new JDialog(editor); + window.setTitle("Enter new name:"); + window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + window.setModal(true); + window.setResizable(false); + window.addComponentListener(new ComponentAdapter() { + @Override + public void componentHidden(ComponentEvent e) { + binding = null; + ps = null; + } + }); + window.setSize(250, 130); + window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS)); + Toolkit.setIcon(window); + + { // Top panel + + // Text field + textField = new JTextField(); + textField.setPreferredSize(new Dimension(150, 60)); + + // Old name label + oldNameLabel = new JLabel(); + oldNameLabel.setText("Old Name: "); + + // Top panel + JPanel panelTop = new JPanel(); + panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS)); + panelTop.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + panelTop.add(textField); + panelTop.add(Box.createRigidArea(new Dimension(0, 10))); + panelTop.add(oldNameLabel); + window.add(panelTop); + } + + { // Bottom panel + JButton showUsageButton = new JButton("Show Usage"); + showUsageButton.addActionListener(e -> { + showUsage.findUsageAndUpdateTree(ps, binding); + window.setVisible(false); + }); + + JButton renameButton = new JButton("Rename"); + renameButton.addActionListener(e -> { + if (textField.getText().length() == 0) { + return; + } + String newName = textField.getText().trim(); + boolean isNewNameValid = newName.length() >= 1 && + newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) && + newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart); + if (!isNewNameValid) { + JOptionPane.showMessageDialog(new JFrame(), "'" + newName + + "' isn't a valid name.", "Uh oh..", JOptionPane.PLAIN_MESSAGE); + } else { + rename(ps, binding, newName); + window.setVisible(false); + } + }); + + JPanel panelBottom = new JPanel(); + panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS)); + panelBottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + panelBottom.add(Box.createHorizontalGlue()); + panelBottom.add(showUsageButton); + panelBottom.add(Box.createRigidArea(new Dimension(15, 0))); + panelBottom.add(renameButton); + window.add(panelBottom); + } + + window.setMinimumSize(window.getSize()); + } + + + + // Thread: worker + void handleRename(PreprocessedSketch ps, int tabIndex, int startTabOffset, int stopTabOffset) { + if (ps.hasSyntaxErrors) { + editor.statusMessage("Can't perform action until syntax errors are fixed :(", + EditorStatus.WARNING); + return; + } + + ASTNode root = ps.compilationUnit; + + // Map offsets + int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset); + int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset); + + // Find the node + SimpleName name = getSimpleNameAt(root, startJavaOffset, stopJavaOffset); + if (name == null) { + editor.statusMessage("Highlight the class/function/variable name first", + EditorStatus.NOTICE); + return; + } + + // Find binding + IBinding binding = resolveBinding(name); + if (binding == null) { + editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " + + "so it cannot be renamed", EditorStatus.ERROR); + return; + } + + ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey()); + if (decl == null) { + editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " + + "so it cannot be renamed", EditorStatus.ERROR); + return; + } + + // Display the rename dialog + EventQueue.invokeLater(() -> { + if (!window.isVisible()) { + this.ps = ps; + this.binding = binding; + window.setLocation(editor.getX() + + (editor.getWidth() - window.getWidth()) / 2, + editor.getY() + + (editor.getHeight() - window.getHeight()) + / 2); + oldNameLabel.setText("Current name: " + binding.getName()); + textField.setText(binding.getName()); + textField.requestFocus(); + textField.selectAll(); + window.setVisible(true); + window.toFront(); + + int x = editor.getX() + (editor.getWidth() - window.getWidth()) / 2; + int y = editor.getY() + (editor.getHeight() - window.getHeight()) / 2; + window.setLocation(x, y); + } + }); + } + + + // Thread: EDT (we can't allow user to mess with sketch while renaming) + void rename(PreprocessedSketch ps, IBinding binding, String newName) { + CompilationUnit root = ps.compilationUnit; + + // Renaming constructor should rename class + if (binding.getKind() == IBinding.METHOD) { + IMethodBinding method = (IMethodBinding) binding; + if (method.isConstructor()) { + binding = method.getDeclaringClass(); + } + } + + ASTNode decl = root.findDeclaringNode(binding.getKey()); + if (decl == null) return; + + showUsage.hide(); + + List occurrences = new ArrayList<>(); + occurrences.addAll(findAllOccurrences(root, binding.getKey())); + + // Renaming class should rename all constructors + if (binding.getKind() == IBinding.TYPE) { + ITypeBinding type = (ITypeBinding) binding; + //type = type.getErasure(); + IMethodBinding[] methods = type.getDeclaredMethods(); + Arrays.stream(methods) + .filter(IMethodBinding::isConstructor) + .flatMap(c -> findAllOccurrences(root, c.getKey()).stream()) + .forEach(occurrences::add); + } + + Map> mappedNodes = occurrences.stream() + .map(ps::mapJavaToSketch) + .collect(Collectors.groupingBy(interval -> interval.tabIndex)); + + Sketch sketch = ps.sketch; + + editor.startCompoundEdit(); + + int currentTabIndex = sketch.getCurrentCodeIndex(); + final int currentOffset = editor.getCaretOffset(); + mappedNodes.entrySet().forEach(entry -> { + int tabIndex = entry.getKey(); + sketch.setCurrentCode(tabIndex); + + List nodes = entry.getValue(); + nodes.stream() + // Replace from the end so all unprocess offsets stay valid + .sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed()) + .forEach(si -> { + // Make sure offsets are in bounds + int length = editor.getTextArea().getDocumentLength(); + if (si.startTabOffset >= 0 && si.startTabOffset <= length && + si.stopTabOffset >= 0 && si.stopTabOffset <= length) { + // Replace the code + editor.getTextArea().select(si.startTabOffset, si.stopTabOffset); + editor.getTextArea().setSelectedText(newName); + } + }); + + sketch.setModified(true); + }); + + int precedingIntervals = + (int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList()) + .stream() + .filter(interval -> interval.stopTabOffset < currentOffset) + .count(); + int intervalLengthDiff = newName.length() - binding.getName().length(); + int offsetDiff = precedingIntervals * intervalLengthDiff; + + sketch.setCurrentCode(currentTabIndex); + editor.getTextArea().setCaretPosition(currentOffset + offsetDiff); + + editor.stopCompoundEdit(); + } + + + void dispose() { + if (window != null) { + window.dispose(); + } + } + + } + + + + private static class DebugTree { + + final JDialog window; + final JTree tree; + + final Consumer updateListener; + + + DebugTree(JavaEditor editor, ErrorCheckerService ecs) { + updateListener = this::buildAndUpdateTree; + + window = new JDialog(editor); + + tree = new JTree() { + @Override + public String convertValueToText(Object value, boolean selected, + boolean expanded, boolean leaf, + int row, boolean hasFocus) { + if (value instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value; + Object o = treeNode.getUserObject(); + if (o instanceof ASTNode) { + ASTNode node = (ASTNode) o; + return ASTGenerator.getNodeAsString(node); + } + } + return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus); + } + }; + window.addComponentListener(new ComponentAdapter() { + @Override + public void componentHidden(ComponentEvent e) { + ecs.unregisterDoneListener(updateListener); + tree.setModel(null); + } + }); + window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + window.setBounds(new Rectangle(680, 100, 460, 620)); + window.setTitle("AST View - " + editor.getSketch().getName()); + JScrollPane sp = new JScrollPane(); + sp.setViewportView(tree); + window.add(sp); + ecs.acceptWhenDone(updateListener); + ecs.registerDoneListener(updateListener); + + + tree.addTreeSelectionListener(e -> { + if (tree.getLastSelectedPathComponent() == null) { + return; + } + DefaultMutableTreeNode tnode = + (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + if (tnode.getUserObject() instanceof ASTNode) { + ASTNode node = (ASTNode) tnode.getUserObject(); + ecs.acceptWhenDone(ps -> { + SketchInterval si = ps.mapJavaToSketch(node); + EventQueue.invokeLater(() -> { + editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset); + }); + }); + } + }); + } + + + void dispose() { + if (window != null) { + window.dispose(); + } + } + + + // Thread: worker + void buildAndUpdateTree(PreprocessedSketch ps) { + CompilationUnit cu = ps.compilationUnit; + if (cu.types().isEmpty()){ + Messages.loge("No Type found in CU"); + return; + } + + ASTNode type0 = (ASTNode) cu.types().get(0); + DefaultMutableTreeNode codeTree = new DefaultMutableTreeNode(type0); + visitRecur(type0, codeTree); + EventQueue.invokeLater(() -> { + if (tree.hasFocus() || window.hasFocus()) { + return; + } + tree.setModel(new DefaultTreeModel(codeTree)); + ((DefaultTreeModel) tree.getModel()).reload(); + tree.validate(); + if (!window.isVisible()) { + window.setVisible(true); + } + }); + } + + + // Thread: worker + /** + * Generates AST Swing component + * @param node + * @param tnode + */ + void visitRecur(ASTNode node, DefaultMutableTreeNode tnode) { + + // TODO: nuke this, use ASTVisitor + + Iterator it = + node.structuralPropertiesForType().iterator(); + //Base.loge("Props of " + node.getClass().getName()); + DefaultMutableTreeNode ctnode; + while (it.hasNext()) { + StructuralPropertyDescriptor prop = it.next(); + + if (prop.isChildProperty() || prop.isSimpleProperty()) { + if (node.getStructuralProperty(prop) != null) { +// System.out +// .println(node.getStructuralProperty(prop) + " -> " + (prop)); + if (node.getStructuralProperty(prop) instanceof ASTNode) { + ASTNode cnode = (ASTNode) node.getStructuralProperty(prop); + if (isAddableASTNode(cnode)) { + ctnode = new DefaultMutableTreeNode(node.getStructuralProperty(prop)); + tnode.add(ctnode); + visitRecur(cnode, ctnode); + } + } else { + tnode.add(new DefaultMutableTreeNode(node + .getStructuralProperty(prop))); + } + } + } else if (prop.isChildListProperty()) { + List nodelist = (List) node.getStructuralProperty(prop); + for (ASTNode cnode : nodelist) { + if (isAddableASTNode(cnode)) { + ctnode = new DefaultMutableTreeNode(cnode); + tnode.add(ctnode); + visitRecur(cnode, ctnode); + } else { + visitRecur(cnode, tnode); + } + } + } + } + } + + + boolean isAddableASTNode(ASTNode node) { + switch (node.getNodeType()) { +// case ASTNode.STRING_LITERAL: +// case ASTNode.NUMBER_LITERAL: +// case ASTNode.BOOLEAN_LITERAL: +// case ASTNode.NULL_LITERAL: +// return false; + default: + return true; + } + } + + } + +}