package org.jmlspecs.openjml.eclipse;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.internal.dialogs.ViewContentProvider;
import org.eclipse.ui.internal.dialogs.ViewLabelProvider;
import org.eclipse.ui.part.ViewPart;
import org.jmlspecs.openjml.JmlOption;
import org.jmlspecs.openjml.Strings;
import org.jmlspecs.openjml.esc.MethodProverSMT.Counterexample;
import org.jmlspecs.openjml.proverinterface.IProverResult;
import org.jmlspecs.openjml.proverinterface.IProverResult.ICounterexample;
import org.jmlspecs.openjml.proverinterface.IProverResult.Kind;
import org.jmlspecs.openjml.proverinterface.ProverResult;
import org.w3c.dom.Document;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.util.Context;
public class OpenJMLView extends ViewPart implements SelectionListener, MouseListener {
/** Eclipse ID of the viewer - must match the ID defined in the plugin.xml file */
public static final String ID = "org.openjml.proofview"; //$NON-NLS-1$
Document doc;
IJavaProject currentProject;
IPartListener aListener;
TreeViewer viewer;
//Table tree;
Tree tree;
TreeItem treeroot;
public OpenJMLView() {
}
/**
* Create contents of the view part.
* @param parent
*/
@Override
public void createPartControl(Composite parent) {
// FIXME - to get a context menu we need to use a TreeViewer, but that means some complex coding of
// a content and label provider - do it later.
ScrolledComposite sc = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
sc.setExpandHorizontal(true);
sc.setExpandVertical(true);
// viewer = new TreeViewer(parent, SWT.MULTI|SWT.V_SCROLL|SWT.H_SCROLL);
// viewer.setContentProvider(new ViewContentProvider());
//// viewer.setLabelProvider(new ViewLabelProvider());
// viewer.setInput(getViewSite());
tree = new Tree(sc, SWT.NONE);
sc.setContent(tree);
// tree = (Tree)viewer.getControl();
treeroot = new TreeItem(tree, SWT.NONE);
tree.addSelectionListener(this);
tree.addMouseListener(this);
aListener = new IPartListener(){
@Override
public void partActivated(IWorkbenchPart part) {
if(part instanceof IEditorInput){
if(getCurrentFileData()) {
refresh();
}
}
}
@Override
public void partBroughtToTop(IWorkbenchPart part) {
if(part instanceof IEditorInput){
refresh();
}
}
@Override
public void partClosed(IWorkbenchPart part) {}
@Override
public void partDeactivated(IWorkbenchPart part) {}
@Override
public void partOpened(IWorkbenchPart part) {}
};
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getPartService().addPartListener(aListener);
initializeToolBar();
// initializeMenu();
if (viewer != null) createContextMenu(viewer);
refresh();
}
private void createContextMenu(TreeViewer viewer) {
// Create menu manager.
MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager mgr) {
mgr.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
}
});
// Create menu.
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
// Register menu for extension.
getSite().registerContextMenu(menuMgr, viewer);
}
public final static Color white = new Color(null,255,255,255);
public final static Color red = new Color(null,255,0,0);
public final static Color green = new Color(null,0,255,0);
public final static Color orange = new Color(null,255,128,0);
public final static Color yellow = new Color(null,255,255,0);
public final static Color blue = new Color(null,128,128,255);
Map<String,TreeItem> treeitems = new HashMap<String,TreeItem>();
Map<TreeItem,ICounterexample> treece = new HashMap<TreeItem,ICounterexample>();
// FIXME: Would like to update incrementally
// FIXME: Would like to have summary colors on class names. On pacakges as well?
// FIXME: Leave closed if all OK
// FIXME: Select - opens editor to correct method; opens trace view if available
// FIXME: what about multiple counterexamples (update trace and path in editor)
// FIXME: right click menu items - open trace, rerun action on method, clear contents
// FIXME: clearing contents
// FIXME: do TreeItem objects need to be disposed? YES
public void refresh() {
getCurrentFileData();
if (currentProject == null) {
this.setPartName("OpenJML Checks");
treeroot.setText("<No project set>");
} else {
OpenJMLInterface iface = Activator.utils().getInterface(currentProject);
Map<String,IProverResult> results = iface.getProofResults();
this.setPartName("OpenJML: " + currentProject.getElementName());
treeroot.setText("Static Checks for: " + currentProject.getElementName());
// FIXME - would like to sort these; remember the tree is built incrementally
for (String key : results.keySet()) {
refresh(key);
}
treeroot.setExpanded(true);
}
}
public void refresh(String key) {
OpenJMLInterface iface = Activator.utils().getInterface(currentProject);
Map<String,IProverResult> results = iface.getProofResults();
IProverResult result = results.get(key);
Symbol sym = result.methodSymbol();
PackageSymbol p = sym.packge();
String pname = p.getQualifiedName().toString();
if (pname.isEmpty()) pname = "<default package>";
TreeItem ti = treeitems.get(pname);
if (ti == null) {
ti = new TreeItem(treeroot, SWT.NONE);
ti.setText(pname);
treeitems.put(pname, ti);
}
Symbol classSym = sym.owner;
String scname = iface.keyForSym(classSym);
String cname = classSym.getSimpleName().toString();
TreeItem tii = treeitems.get(scname);
if (tii == null) {
tii = new TreeItem(ti,SWT.NONE);
tii.setText(scname); // FIXME - what about nested classes
treeitems.put(scname, tii);
{
Info iteminfo = new Info();
iteminfo.key = scname;
iteminfo.proofResult = null;
iteminfo.javaElement = iface.convertType((Symbol.ClassSymbol)classSym);
iteminfo.signature = classSym.getSimpleName().toString();
tii.setData(iteminfo);
}
}
String text = sym.toString();
TreeItem tiii = treeitems.get(key);
if (tiii == null) {
tiii = new TreeItem(tii,SWT.NONE);
treeitems.put(key, tiii);
}
Kind k = result == null ? null : result.result();
String info = result == null ? "" : (" ["
+ (org.jmlspecs.openjml.Utils.testingMode ? "TIME" : result.duration())
+ " " + result.prover() + "]");
tiii.removeAll();
String name = k == null ? "" : k.toString();
String padding = " ";
padding = padding.substring(0,name.length() <= padding.length() ? padding.length()-name.length() : 0);
String alltext = k == null ? text : ("[" + k.toString() + "] " + text + info);
Color color = white;
{
Info iteminfo = new Info();
iteminfo.key = key;
iteminfo.proofResult = result;
iteminfo.javaElement = iface.convertMethod((MethodSymbol)sym);
iteminfo.signature = key;
tiii.setData(iteminfo);
}
if (k == IProverResult.SAT || k == IProverResult.POSSIBLY_SAT) {
alltext = ("[INVALID] " + text + info);
color = orange;
List<IProverResult.Item> presults = ((org.jmlspecs.openjml.proverinterface.ProverResult)result).details();
if (presults == null) {
// Put nothing
} else if (presults.size() == 1) {
treece.put(tiii, result.counterexample());
} else if (presults.size() == 0) {
treece.put(tiii, null);
// FIXME - no counterexample
} else {
int i = 0;
for (IProverResult.Item ce : presults) {
if (i == 0) treece.put(tiii, (ICounterexample)ce);
if (ce instanceof IProverResult.ICounterexample) {
TreeItem tiiii = new TreeItem(tiii,SWT.NONE);
// FIXME - say more about the failed assertion
tiiii.setText("CE#" + (++i));
tiiii.setBackground(orange);
treece.put(tiiii, (ICounterexample)ce);
}
}
tiii.setExpanded(true);
}
} else if (k == IProverResult.UNSAT) {
alltext = ("[VALID] " + text + info);
color = green;
} else if (k == IProverResult.ERROR) {
color = red;
} else if (k == IProverResult.TIMEOUT) {
tiii.setBackground(yellow);
} else if (k == IProverResult.UNKNOWN) {
color = red;
} else if (k == IProverResult.INFEASIBLE) {
color = yellow;
} else if (k == IProverResult.SKIPPED) {
color = blue;
} else if (k == IProverResult.RUNNING) {
color = white;
} else if (k == IProverResult.CANCELLED) {
color = red;
} else if (k == IProverResult.COMPLETED) {
return; // No change
} else if (k == null) {
alltext = text;
color = white;
} else {
color = red;
}
tiii.setText(alltext);
tiii.setBackground(color);
// treeroot.setExpanded(true);
// ti.setExpanded(true);
// tii.setExpanded(true);
treeroot.getParent().showItem(tiii);
}
private boolean getCurrentFileData() {
try {
IWorkbenchPage page = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getActivePage();
if(page == null) return false;
IEditorPart activeEditor = page.getActiveEditor();
if(activeEditor == null) return false;
IEditorInput einput = activeEditor.getEditorInput();
if(!(einput instanceof IFileEditorInput)) return false;
IFileEditorInput input = (IFileEditorInput) einput;
// Get project name
IProject p = input.getFile().getProject();
currentProject = p.hasNature(JavaCore.NATURE_ID) ? JavaCore.create(p) : null;
return true;
} catch (Exception e) {
return false;
}
}
public void clearProofResults() {
OpenJMLInterface iface = Activator.utils().getInterface(currentProject);
iface.clearProofResults(currentProject);
treeitems.clear();
treece.clear();
treeroot.removeAll();
refresh();
selected = null;
}
static public void exportProofResults(FileWriter output) {
try {
String spaces = " ";
final OpenJMLView view = Utils.findView();
Map<String,Integer> counts = new HashMap<String,Integer>();
OpenJMLInterface iface = Activator.utils().getInterface(view.currentProject);
Map<String,IProverResult> proofResults = iface.getProofResults();
TreeSet<String> t = new TreeSet<String>(proofResults.keySet());
for (String s: t) {
String result = proofResults.get(s).result().toString();
output.append(result + spaces.substring(0,12-result.length()) + s + Strings.eol);
Integer i = counts.get(result);
if (i == null) i = 1; else i = i + 1;
counts.put(result, i);
}
output.append(Strings.eol);
for (String result: new TreeSet<>(counts.keySet())) {
String number = counts.get(result).toString();
output.append(number + spaces.substring(0,6-number.length()) + result + Strings.eol);
}
} catch (IOException e) {
}
}
public void clearSelectedProofResults() {
if (selected == null) return;
OpenJMLInterface iface = Activator.utils().getInterface(currentProject);
clearResults(iface,selected);
treeroot.removeAll();
treeitems.clear();
treece.clear();
refresh();
selected = null;
}
private void clearResults(OpenJMLInterface iface, TreeItem ti) {
Info info = (Info)ti.getData();
ti.setData(null);
if (info != null) iface.getProofResults().remove(info.key);
for (TreeItem t: ti.getItems()) {
clearResults(iface,t);
}
}
/**
* Initialize the toolbar.
*/
private void initializeToolBar() { // FIXME - does this really do anything?
getViewSite().getActionBars().getToolBarManager();
}
/**
* Initialize the menu.
*/
private void initializeMenu() {
IMenuManager menuManager = getViewSite().getActionBars()
.getMenuManager();
// MenuManager menu = new MenuManager("Get New Auto-Generated Expressions");
//
// menuManager.add(menu);
// menu.add(updateExpressionsAll);
// menu.add(updateExpressionsCS);
// menu.add(updateExpressionsDaikon);
// menuManager.add(clearProofResults);
// menuManager.add(reloadExpressions);
// menuManager.add(processExpressions);
// menuManager.add(processExpressionsWP);
// menuManager.add(saveExpressions);
}
@Override
public void setFocus() {
// Set the focus
}
public void dispose(){
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getPartService()
.removePartListener(aListener);
}
TreeItem selected;
@Override
public void widgetSelected(SelectionEvent e) {
TreeItem ti = selected = (TreeItem)e.item;
if (ti == null) return;
Info info = (Info)ti.getData();
TreeItem pi = ti;
if (info == null) {
pi = ti.getParentItem();
if (pi != null) info = (Info)pi.getData();
}
if (info != null) {
OpenJMLInterface iface = Activator.utils().getInterface(currentProject);
IProverResult pr = info.proofResult;
IJavaElement je = info.javaElement; // This can be null for default constructors that have no code
IResource r = je == null ? null : je.getResource();
if (r != null) Activator.utils().deleteHighlights(r,null); // FIXME - would like to clear just the java element, not the whole compilation unit
if (pr != null) {
ICounterexample ce = treece.get(ti);
if (ce instanceof Counterexample) {
String text = ((Counterexample)ce).traceText; // FIXME - change to method on interface
Activator.utils().setTraceViewUI(null,info.signature,text);
if (info.javaElement instanceof IMethod) {
iface.highlightCounterexamplePath((IMethod)info.javaElement,info.proofResult,ce);
}
if (pi != ti) treece.put(pi, ce);
} else {
Kind k = pr.result();
String desc = k == IProverResult.UNSAT ? "consistent"
: k == IProverResult.SAT ? "inconsistent"
: k == IProverResult.POSSIBLY_SAT ? "probably inconsistent"
: k.toString().toLowerCase();
Activator.utils().setTraceViewUI(null, info.signature,
(k == IProverResult.ERROR ? "Error occurred while checking method: "
: "Method and its specifications are " + desc + ": ")
+ info.key);
}
}
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}
/** Launches a Java editor for the method or class that was clicked */
@Override
public void mouseDoubleClick(MouseEvent e) {
// Presumes a selection happened as part of the double click
Widget w = e.widget;
if (!(w instanceof Tree)) return;
TreeItem ti = selected;
Info info = ti != null ? (Info)ti.getData() : null;
IJavaElement je = info == null ? null : info.javaElement;
if (je != null) {
IFile f = (IFile)je.getResource();
Activator.utils().launchJavaEditor(f);
}
}
@Override
public void mouseDown(MouseEvent e) {
}
@Override
public void mouseUp(MouseEvent e) {
}
public static class Info {
public IProverResult proofResult;
public String key;
public IJavaElement javaElement;
public String signature;
}
// private MessageConsole findConsole(String name) {
// ConsolePlugin plugin = ConsolePlugin.getDefault();
// IConsoleManager conMan = plugin.getConsoleManager();
// IConsole[] existing = conMan.getConsoles();
// for (int i = 0; i < existing.length; i++)
// if (name.equals(existing[i].getName()))
// return (MessageConsole) existing[i];
// //no console found, so create a new one
// MessageConsole myConsole = new MessageConsole(name, null);
// conMan.addConsoles(new IConsole[]{myConsole});
// return myConsole;
// }
}