package net.sourceforge.pmd.eclipse.ui.views.ast; import java.util.HashSet; import java.util.List; import java.util.Set; import net.sourceforge.pmd.eclipse.ui.BasicTableLabelProvider; import net.sourceforge.pmd.eclipse.ui.editors.SyntaxManager; import net.sourceforge.pmd.eclipse.ui.model.FileRecord; import net.sourceforge.pmd.eclipse.ui.preferences.br.BasicTableManager; import net.sourceforge.pmd.eclipse.ui.views.AbstractStructureInspectorPage; import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ParseException; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.util.StringUtil; import org.eclipse.core.resources.IResource; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IWorkbenchPart; import org.jaxen.BaseXPath; import org.jaxen.JaxenException; import org.jaxen.XPathSyntaxException; /** * A combined abstract syntax tree viewer for a whole class or selected methods * and an XPath editor/evaluator on the right that works with it. * * @author Brian Remedios * */ public class ASTViewPage extends AbstractStructureInspectorPage { private SashForm sashForm; protected TreeViewer astViewer; private StyledText xpathField; private TableViewer resultsViewer; private Button goButton; private Node classNode; private ASTPainterHelper helper; private ASTContentProvider contentProvider; // private static Set<String> keywords = new HashSet<String>(); private static Set<Class<?>> HiddenNodeTypes; static { HiddenNodeTypes = new HashSet<Class<?>>(); HiddenNodeTypes.add(ASTImportDeclaration.class); } /** * * @param part * @param record the FileRecord */ public ASTViewPage(IWorkbenchPart part, FileRecord record) { super(part, record); } public TreeViewer astViewer() { return astViewer; } public void showImports(boolean flag) { contentProvider.includeImports(flag); astViewer.refresh(); } public void showComments(boolean flag) { contentProvider.includeComments(flag); astViewer.refresh(); } /** * TODO use an adjustable Sash to separate the two sections * TODO add an XPath version combo widget */ public void createControl(Composite parent) { sashForm = new SashForm(parent, SWT.HORIZONTAL); Composite astPanel = new Composite(sashForm, SWT.NONE); GridLayout mainLayout = new GridLayout(3, false); astPanel.setLayout(mainLayout); Composite titleArea = new Composite(astPanel, SWT.NONE); GridData gridData = new GridData(GridData.FILL_HORIZONTAL); gridData.horizontalSpan = 2; titleArea.setLayoutData(gridData); titleArea.setLayout(new GridLayout(4, false)); Label showLabel = new Label(titleArea, 0); showLabel.setText("Show: "); final Button classBtn = new Button(titleArea, SWT.RADIO); classBtn.setText("Class"); classBtn.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent se) { if (classBtn.getSelection()) showClass(); } } ); final Button methodBtn = new Button(titleArea, SWT.RADIO); methodBtn.setText("Method"); methodBtn.setSelection(true); methodBtn.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent se) { enableMethodSelector( methodBtn.getSelection() ); methodPicked(); } } ); buildMethodSelector(titleArea); astViewer = new TreeViewer(astPanel, SWT.MULTI | SWT.BORDER); contentProvider = new ASTContentProvider( true, true ); astViewer.setContentProvider(contentProvider); astViewer.setLabelProvider( new ASTLabelProvider() ); setupListeners(astViewer.getTree()); GridData data = new GridData(GridData.FILL_BOTH); data.horizontalSpan = 2; astViewer.getTree().setLayoutData(data); //================== Composite xpathTestPanel = new Composite(sashForm, SWT.NONE); data = new GridData(GridData.FILL_BOTH); data.horizontalSpan = 1; xpathTestPanel.setLayoutData(data); GridLayout playLayout = new GridLayout(2, false); xpathTestPanel.setLayout(playLayout); xpathField = new StyledText(xpathTestPanel, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); gridData = new GridData(GridData.FILL_BOTH); gridData.grabExcessHorizontalSpace = true; gridData.horizontalSpan = 1; xpathField.setLayoutData(gridData); SyntaxManager.adapt(xpathField, "xpath", null); addXPathValidator(); goButton = new Button(xpathTestPanel, SWT.PUSH); goButton.setText("GO"); gridData = new GridData(GridData.FILL_BOTH); gridData.grabExcessHorizontalSpace = false; gridData.horizontalSpan = 1; goButton.setLayoutData(gridData); goButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { evaluateXPath(); } } ); // outputField = new StyledText(xpathTestPanel, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); gridData = new GridData(GridData.FILL_BOTH); gridData.grabExcessHorizontalSpace = true; gridData.horizontalSpan = 2; // outputField.setLayoutData(gridData); // SyntaxManager.adapt(outputField, "xpath", null); BasicTableManager tableMgr = new BasicTableManager("ast", null, NodeColumnUI.VisibleColumns); resultsViewer = tableMgr.buildTableViewer(xpathTestPanel); tableMgr.setupColumns(NodeColumnUI.VisibleColumns); IStructuredContentProvider contentProvider = new IStructuredContentProvider() { public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } public Object[] getElements(Object inputElement) { return (Node[])inputElement; } }; BasicTableLabelProvider labelProvider = new BasicTableLabelProvider(NodeColumnUI.VisibleColumns); Table table = resultsViewer.getTable(); table.setLayoutData(gridData); resultsViewer.setLabelProvider(labelProvider); resultsViewer.setContentProvider(contentProvider); registerListeners(); showFirstMethod(); } private void addXPathValidator() { ModifyListener ml = new ModifyListener() { public void modifyText(ModifyEvent event) { validateXPath(xpathField.getText()); } }; xpathField.addModifyListener(ml); } private void validateXPath(String xpathString) { try { new BaseXPath(xpathString, null); } catch (XPathSyntaxException ex) { System.out.println(ex.getPosition() + " " + ex.getMessage()); // TODO add error marker to editor, red-underlining on offending text goButton.setEnabled(false); return; } catch (JaxenException ex) { goButton.setEnabled(false); return; } goButton.setEnabled(true); } private void evaluateXPath() { if (! setupTest() ) return; List<Node> results = null; try { results = XPathEvaluator.instance.evaluate( getDocument().get(), xpathField.getText(), XPathRuleQuery.XPATH_1_0 // TODO derive from future combo widget ); } catch (ParseException pe) { showError(pe.fillInStackTrace().getMessage()); return; } show(results); } private boolean setupTest() { // outputField.setText(""); resultsViewer.getTable().clearAll(); if (StringUtil.isEmpty(xpathField.getText())) { //outputField.setText("XPath query field is empty."); return false; } return true; } private void showError(String message) { //outputField.setText(message); } private void show(List<Node> results) { if (results.isEmpty()) { //outputField.setText("No matching nodes found"); return; } // StringBuilder sb = new StringBuilder(); // for (int i=0; i<results.size(); i++) { // displayOn(results.get(i), i+1, sb); // sb.append('\n'); // } // outputField.setText( sb.toString() ); resultsViewer.setInput( results.toArray(new Node[results.size()]) ); } private void setupListeners(Tree tree) { helper = new ASTPainterHelper(tree.getDisplay()); tree.addListener(SWT.PaintItem, new Listener() { public void handleEvent(Event event) { TextLayout layout = helper.layoutFor((TreeItem)event.item); layout.draw(event.gc, event.x+5, event.y ); // event.gc.drawLine(event.x - 55, event.y, event.x - 55, event.y + 20); } }); tree.addListener(SWT.MeasureItem, new Listener() { public void handleEvent(Event e) { Rectangle textLayoutBounds = helper.layoutFor((TreeItem)e.item).getBounds(); e.width = textLayoutBounds.width + 2; e.height = textLayoutBounds.height + 2; } }); tree.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent se) { highlightItem(se.item.getData()); } }); } private void highlightItem(Object item) { AbstractNode node = (AbstractNode)item; highlight( node.getBeginLine() - 1,node.getBeginColumn() - 1, node.getEndLine()- 1, node.getEndColumn() ); } public void dispose() { super.dispose(); helper.dispose(); } public Control getControl() { return sashForm; } protected void showClass() { if (classNode == null) { String source = getDocument().get(); classNode = XPathEvaluator.instance.getCompilationUnit(source); } astViewer.setInput(classNode); astViewer.expandAll(); } protected void showMethod(ASTMethodDeclaration pmdMethod) { if (pmdMethod == null) return; astViewer.setInput(pmdMethod); astViewer.expandAll(); } /** * Shows the DataflowGraph (and Dataflow-Anomalies) for a Method. * * @param pmdMethod Method to show in the graph */ // protected void showMethod(final ASTMethodDeclaration pmdMethod) { // if (pmdMethod != null) { // // final String resourceString = getDocument().get(); // give the Data to the GraphViewer // astViewer.setVisible(true); // astViewer.setData(pmdMethod, resourceString); // astViewer.addMouseListener(new MouseAdapter() { // // @Override // public void mouseDown(MouseEvent e) { // if (textEditor != null) { // final int row = (int)((double)e.y / DataflowGraphViewer.ROW_HEIGHT); // astViewer.getGraph().demark(); // astViewer.getGraph().markNode(row); // final int startLine = pmdMethod.getDataFlowNode().getFlow().get(row).getLine()-1; // int offset = 0; // int length = 0; // try { // offset = getDocument().getLineOffset(startLine); // length = getDocument().getLineLength(startLine); // } catch (BadLocationException ble) { // logError(StringKeys.MSGKEY_ERROR_RUNTIME_EXCEPTION + "Exception when selecting a line in the editor" , ble); // } // textEditor.selectAndReveal(offset, length); // astViewer.getTree().deselectAll(); // } // } // }); // showTableArea(isTableShown); // } // } /** * Refreshes the page with a new resource. * @param newResource new resource for the page */ public void refresh(IResource newResource) { super.refresh(newResource); classNode = null; // if (isTableShown) { // refreshDFATable(newResource); // } else { // this.isTableRefreshed = false; // } refreshMethodSelector(); } }