package net.sourceforge.pmd.eclipse.ui.views.dataflow;
import java.util.Iterator;
import name.herlin.command.CommandException;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants;
import net.sourceforge.pmd.eclipse.runtime.cmd.ReviewResourceForRuleCommand;
import net.sourceforge.pmd.eclipse.ui.model.FileRecord;
import net.sourceforge.pmd.eclipse.ui.nls.StringKeys;
import net.sourceforge.pmd.eclipse.ui.views.AbstractStructureInspectorPage;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.rule.controversial.DataflowAnomalyAnalysisRule;
import net.sourceforge.pmd.util.StringUtil;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Label;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPart;
/**
* A page for the dataflow - view.
*
* @author Sven Jacob
*
*/
public class DataflowViewPage extends AbstractStructureInspectorPage implements IPropertyListener {
private Composite dfaFrame;
private Button switchButton;
protected DataflowGraphViewer graphViewer;
protected DataflowAnomalyTableViewer tableViewer;
private boolean isTableShown;
private boolean isTableRefreshed;
/**
* Constructor
* @param part
* @param record the FileRecord
*/
public DataflowViewPage(IWorkbenchPart part, FileRecord record) {
super(part, record);
}
/* @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) */
@Override
public void createControl(Composite parent) {
dfaFrame = new Composite(parent, SWT.NONE);
// //////////////////////////////////////////////////
// upper title area
Composite titleArea = new Composite(dfaFrame, SWT.NONE);
GridData tableData = new GridData(GridData.FILL_HORIZONTAL);
tableData.horizontalSpan = 2;
titleArea.setLayoutData(tableData);
titleArea.setLayout(new GridLayout(4, false));
Label methodLabel = new Label(titleArea, 0);
methodLabel.setText("Method: ");
buildMethodSelector(titleArea);
// a label for the spacing
Label label = new Label(titleArea, SWT.NONE);
label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// the Button for showing or hiding the Anomaly-List
switchButton = new Button(titleArea, SWT.RIGHT);
switchButton.setLayoutData(new GridData(130, 25));
switchButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent se) {
isTableShown = !isTableShown;
showTableArea(isTableShown);
if (!isTableShown) {
if (graphViewer == null || graphViewer.getGraph() == null) {
return;
}
final DataflowGraph graph = graphViewer.getGraph();
if (graph.isMarked()) {
graph.demark();
}
}
}}
);
switchButton.setText(getString(StringKeys.VIEW_DATAFLOW_SWITCHBUTTON_SHOW));
// //////////////////////////////////////////////////
// the DataflowGraphViewer (left Part)
graphViewer = new DataflowGraphViewer(dfaFrame, SWT.NONE);
graphViewer.setVisible(false);
// //////////////////////////////////////////////////
// the DataflowAnomalyTable (right Part)
tableViewer = new DataflowAnomalyTableViewer(dfaFrame, SWT.BORDER);
tableViewer.addSelectionChangedListener(this);
tableViewer.setContentProvider(new DataflowAnomalyTableContentProvider());
tableViewer.setLabelProvider(new DataflowAnomalyTableLabelProvider());
isTableRefreshed = false;
GridLayout mainLayout = new GridLayout(2, true);
mainLayout.horizontalSpacing = mainLayout.verticalSpacing = 7;
mainLayout.marginWidth = 3;
mainLayout.marginHeight = 3;
dfaFrame.setLayout(mainLayout);
// hide the table
showTableArea(false);
showFirstMethod();
}
/* @see org.eclipse.ui.part.IPage#getControl() */
@Override
public Control getControl() {
return dfaFrame;
}
/**
* 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) {
String resourceString = getDocument().get();
// give the Data to the GraphViewer
graphViewer.setVisible(true);
graphViewer.setData(pmdMethod, resourceString);
graphViewer.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
int row = (int)((double)e.y / DataflowGraphViewer.ROW_HEIGHT);
graphViewer.getGraph().demark();
graphViewer.getGraph().markNode(row);
highlightLine( pmdMethod.getDataFlowNode().getFlow().get(row).getLine()-1 );
tableViewer.getTable().deselectAll();
}
});
showTableArea(isTableShown);
}
}
/**
* Shows or hides the DataflowAnomalyTable, set the right text for the button.
*
* @param isShown, true if the Table should be visible, false otherwise
*/
private void showTableArea(boolean isShown) {
tableViewer.setVisible(isShown);
// if the AnomalyTable is visible, an Area is 50% of the View
// set the new Size and update the SwitchButton's Label
if (isShown) {
((GridData) graphViewer.getLayoutData()).horizontalSpan = 1;
switchButton.setText(getString(StringKeys.VIEW_DATAFLOW_SWITCHBUTTON_HIDE));
// refresh the table if it isn't refreshed yet.
if (!isTableRefreshed) {
refreshDFATable(getResource());
}
} else {
((GridData) graphViewer.getLayoutData()).horizontalSpan = 2;
switchButton.setText(getString(StringKeys.VIEW_DATAFLOW_SWITCHBUTTON_SHOW));
}
// lay out to update the View
dfaFrame.layout(true, true);
}
/* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) */
public void selectionChanged(SelectionChangedEvent event) {
RuleViolation violation = selectedViolationFrom(event);
if (violation == null) return;
String varName = violation.getVariableName();
if (StringUtil.isEmpty(varName)) return;
int beginLine = violation.getBeginLine();
int endLine = violation.getEndLine();
if (beginLine != 0 && endLine != 0) {
try {
int offset = getDocument().getLineOffset(violation.getBeginLine() - 1);
int length = getDocument().getLineOffset(violation.getEndLine()) - offset;
highlight(offset, length);
} catch (BadLocationException ble) {
logError(StringKeys.ERROR_RUNTIME_EXCEPTION + "Exception when selecting a line in the editor", ble);
}
// showMethodToMarker(marker);
showMethodToViolation(violation);
// then we calculate and color _a possible_ Path
// for this Error in the Dataflow
DataflowGraph graph = graphViewer.getGraph();
if (!graphViewer.isDisposed() && graph != null) {
graph.markPath(beginLine, endLine, varName);
}
}
}
/**
* Refreshes the page with a new resource.
* @param newResource new resource for the page
*/
public void refresh(IResource newResource) {
super.refresh(newResource);
if (isTableShown) {
refreshDFATable(newResource);
} else {
isTableRefreshed = false;
}
refreshMethodSelector();
}
/**
* Executes a command to refresh the DFA table.
* After execution {@link #refresh(IResource)} will be called.
* @param newResource the new resource
*/
public void refreshDFATable(IResource newResource) {
isTableRefreshed = true;
try {
ReviewResourceForRuleCommand cmd = new ReviewResourceForRuleCommand();
DataflowAnomalyAnalysisRule rule = new DataflowAnomalyAnalysisRule();
rule.setUsesDFA();
cmd.setUserInitiated(false);
cmd.setRule(rule);
cmd.setResource(newResource);
cmd.addPropertyListener(this);
cmd.performExecute();
} catch (CommandException e) {
logErrorByKey(StringKeys.ERROR_PMD_EXCEPTION, e);
}
}
/**
* If the review is ready propertyChanged with the results will be called.
*/
public void propertyChanged(Object source, int propId) {
if (source instanceof Iterator<?>
&& propId == PMDRuntimeConstants.PROPERTY_REVIEW) {
tableViewer.setInput(source);
}
}
}