/* * This file is part of the OpenJML plugin project. * Copyright (c) 2006-2013 David R. Cok */ package org.jmlspecs.openjml.eclipse; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.*; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.*; import org.eclipse.ui.part.FileEditorInput; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.annotation.Pure; import org.jmlspecs.annotation.Query; import org.jmlspecs.openjml.Main.Cmd; import org.jmlspecs.openjml.Strings; import org.jmlspecs.openjml.eclipse.PathItem.ProjectPath; import org.jmlspecs.openjml.esc.MethodProverSMT.Counterexample; import org.jmlspecs.openjml.proverinterface.IProverResult; import org.jmlspecs.openjml.proverinterface.IProverResult.ICounterexample; import org.osgi.framework.Bundle; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; // FIXME - review UI Utils class /** * This class holds utility values and methods to support the Eclipse plugin for * OpenJML. * * @author David Cok * */ public class Utils { /** This class is used to wrap arbitrary exceptions coming from OpenJML */ public static class OpenJMLException extends RuntimeException { /** Default serial version ID */ private static final long serialVersionUID = 1L; /** * Used to signal some unexpected error situation in doing JML * processing. */ public OpenJMLException(@NonNull String error) { super(error); } /** * Used to signal some unexpected error situation in doing JML * processing. */ public OpenJMLException(@NonNull String error, @NonNull Exception e) { super(error, e); } } /** An empty string */ final public static @NonNull String emptyString = ""; //$NON-NLS-1$ /** A single comma */ final public static @NonNull String comma = ","; //$NON-NLS-1$ /** An single space */ final public static @NonNull String space = " "; //$NON-NLS-1$ /** The .java suffix */ final public static @NonNull String dotJava = ".java"; //$NON-NLS-1$ /** The .jml suffix */ final public static @NonNull String dotJML = ".jml"; //$NON-NLS-1$ /** * A map relating java projects to the instance of OpenJMLInterface that * handles openjml stuff for that project. We have a separate instance for * each project since options can be different by project. */ final protected @NonNull Map<IJavaProject, OpenJMLInterface> projectMap = new HashMap<IJavaProject, OpenJMLInterface>(); /** * Returns the unique OpenJMLInterface for a given project * * @param jproject * the Java project whose interface is desired * @return the OpenJMLInterface for that project */ public @NonNull OpenJMLInterface getInterface(@NonNull IJavaProject jproject) { OpenJMLInterface i = projectMap.get(jproject); if (i == null) { projectMap.put(jproject, i = new OpenJMLInterface(jproject)); } return i; } protected String getInternalSystemSpecs() { String filesep = "/"; // Not File.separator I think //$NON-NLS-1$ StringBuilder ss = new StringBuilder(); try { boolean somethingPresent = false; String versionString = System.getProperty("java.version"); //$NON-NLS-1$ int version = 7; // the current default if (versionString.startsWith("1.") && versionString.length() > 3 //$NON-NLS-1$ && (version = (versionString.charAt(2) - '0')) >= 4 && version <= 9) { // found OK version number } else { Log.log("Unrecognized version: " + versionString); //$NON-NLS-1$ version = 7; // default, if the version string is in an // unexpected format } String[] defspecs = new String[8]; // null entries OK Bundle specsBundle = Platform.getBundle(Env.SPECS_PLUGIN_ID); if (specsBundle == null) { if (Options.uiverboseness) Log.log("No specification plugin " + Env.SPECS_PLUGIN_ID); //$NON-NLS-1$ } else { String loc = null; URL url = FileLocator.toFileURL(specsBundle.getResource(emptyString)); File root = new File(url.toURI()); loc = root.getAbsolutePath(); loc = loc.replace("\\", "/"); //$NON-NLS-1$//$NON-NLS-2$ if (Options.uiverboseness) Log.log("JMLSpecs plugin found " + root + space //$NON-NLS-1$ + root.exists()); if (root.isFile()) { // Presume it is a jar or zip file JarFile j = new JarFile(root); int i = 0; for (int v = version; v >= 4; --v) { JarEntry f = j.getJarEntry("java" + v); //$NON-NLS-1$ if (f != null) defspecs[i++] = loc + "!java" + v; //$NON-NLS-1$ } j.close(); } else if (root.isDirectory()) { // Normal file system directory int i = 0; for (int v = version; v >= 4; --v) { File f = new File(root, "java" + v); //$NON-NLS-1$ if (f.exists()) defspecs[i++] = root.getAbsolutePath().replace( '\\', '/') + filesep + "java" + v; //$NON-NLS-1$ } } else { if (Options.uiverboseness) Log.log("Expected contents (javaN subdirectories) not found in specs bundle at " + root); } for (String z : defspecs) { if (z != null) { somethingPresent = true; if (Options.uiverboseness) Log.log("Set library specspath defaults from the Specs plugin"); break; } } } if (!somethingPresent) { Bundle selfBundle = Platform.getBundle(Env.PLUGIN_ID); if (selfBundle == null) { if (Options.uiverboseness) Log.log("No self plugin"); //$NON-NLS-1$ } else { URL url = FileLocator.toFileURL(selfBundle.getResource(Strings.slash)); if (url != null) { File root = new File(url.toURI()); if (Options.uiverboseness) Log.log("Self bundle found " + root + Strings.space //$NON-NLS-1$ + root.exists()); int i = 0; if (root.isDirectory()) { for (int v = version; v >= 4; --v) { File f = new File(root, ".." + filesep //$NON-NLS-1$ + "specs" + filesep + "java" + v); //$NON-NLS-1$//$NON-NLS-2$ if (f.exists()) defspecs[i++] = f.toString(); } } else { JarFile j = new JarFile(root); for (int v = version; v >= 4; --v) { JarEntry f = j.getJarEntry("specs" + filesep + "java" + v); if (f != null) defspecs[i++] = root + "!specs" + filesep + "java" + v; } j.close(); } if (i > 0) somethingPresent = true; } } } if (!somethingPresent) Log.errorlog("No internal library specs found", null); for (String z : defspecs) if (z != null) { ss.append(z); ss.append(File.pathSeparator); } if (ss.length() > 0) ss.setLength(ss.length() - File.pathSeparator.length()); } catch (Exception e) { Log.log("Failure finding internal specs: " + e); //$NON-NLS-1$ } return ss.toString(); } /** Checks for dirty editors; pops up a dialog to ask the user what * to do. Returns false if the operation is to be canceled. * @return */ public boolean checkForDirtyEditors() { return PlatformUI.getWorkbench().saveAllEditors(true); } /** * This routine initiates (as a Job) checking the JML of all the Java files * in the selection; if any containers (including working sets) are * selected, the operation applies to the contents of the container ; if any * Java elements are selected (e.g. a method), the operation applies to the * containing file. * * @param selection * the current selection (ignored unless it is an * IStructuredSelection) * @param window * null or the currently active IWorkbenchWindow * @param shell * the current shell */ public void checkSelection(@Nullable final ISelection selection, @Nullable final IWorkbenchWindow window, @NonNull final Shell shell) { if (!checkForDirtyEditors()) return; if (selection == null) { showMessage(shell, "JML Check", "Nothing selected to check"); return; } List<IResource> res = getSelectedResources(selection, window, shell); if (res.size() == 0) { showMessage(shell, "JML Check", "Nothing appropriate to check"); return; } deleteMarkers(res, shell); final Map<IJavaProject, List<IResource>> sorted = sortByProject(res); Job j = new Job("JML Manual Check") { public IStatus run(IProgressMonitor monitor) { monitor.beginTask("JML type-checking", sorted.size()); boolean c = false; for (final IJavaProject jp : sorted.keySet()) { // FIXME - should impose an order on the projects final List<IResource> ores = sorted.get(jp); try { getInterface(jp).executeExternalCommand(Cmd.CHECK, ores, monitor,false); } catch (Exception e) { showExceptionInUI(shell, null, e); c = true; } monitor.worked(1); } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } }; IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory(); // FIXME ISchedulingRule rule = ruleFactory.markerRule(r); if (sorted.keySet().size() == 1) { j.setRule(sorted.keySet().iterator().next().getProject()); } else { j.setRule(ResourcesPlugin.getWorkspace().getRoot()); } j.setUser(true); // true since the job has been initiated by an end-user j.schedule(); } /** * This routine initiates (as a Job) executing ESC on all the Java files in * the selection; if any containers are selected, the operation applies the * contents of the container (including working sets). If a Type or Method * is selected, the operation is applied to that element only. (FIXME - not * yet) * * @param selection * the current selection (ignored unless it is an * IStructuredSelection) * @param window * null or the currently active IWorkbenchWindow * @param shell * the current shell */ public void checkESCSelection(ISelection selection, @Nullable IWorkbenchWindow window, @Nullable final Shell shell) { if (!checkForDirtyEditors()) return; if (selection == null) { showMessage(shell, "ESC", "Nothing selected to check"); return; } final List<Object> res = getSelectedElements(selection, window, shell); if (res.size() == 0) { showMessage(shell, "ESC", "Nothing selected or applicable to check"); return; } final Map<IJavaProject, List<Object>> sorted = sortByProject(res); deleteMarkers(res, shell); // FIXME - does this trigger a rebuild? for (final IJavaProject jp : sorted.keySet()) { checkESCProject(jp,sorted.get(jp),shell,"Static Checks - Manual"); } } public void checkESCProject(final IJavaProject jp, final List<?> ores, /*@ nullable */Shell shell, String reason) { Job j = new Job(reason) { public IStatus run(IProgressMonitor monitor) { monitor.beginTask("Static checking of " + jp.getElementName(), 1); boolean c = false; try { if (ores == null) { LinkedList<Object> list = new LinkedList<Object>(); list.add(jp); final List<Object> res = list; getInterface(jp).executeESCCommand(Cmd.ESC, res, monitor); } else if (ores.size() != 0){ getInterface(jp).executeESCCommand(Cmd.ESC, ores, monitor); } } catch (Exception e) { // FIXME - this will block, preventing progress on the rest of the projects Log.errorlog("Exception during Static Checking - " + jp.getElementName(), e); showExceptionInUI(null, "Exception during Static Checking - " + jp.getElementName(), e); c = true; } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } }; IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory(); j.setRule(jp.getProject()); j.setUser(true); // true since the job has been initiated by an end-user j.schedule(); } static public java.util.Properties getProperties() { return org.jmlspecs.openjml.Utils.findProperties(null); } static public java.util.Properties readProperties() { // FIXME - as different projects are processed, they continually // overwrite each other's properties // Log.log("About to read properties"); java.util.Properties properties = new java.util.Properties(); { // Note: It appears that a file in the workspace root is not seen as // a member of the workspace - just projects - because findMember on // the workspace root returns null. So we find the file directly in // the local file system. IPath path = ResourcesPlugin.getWorkspace().getRoot().getLocation() .append(org.jmlspecs.openjml.Strings.propertiesFileName); try { boolean found = org.jmlspecs.openjml.Utils.readProps( properties, path.toFile().getAbsolutePath()); if (found && Options.uiverboseness) Log.log("Properties read from the workspace: " + path.toOSString()); } catch (java.io.IOException e) { Log.errorlog("Failed to read a properties file", e); } } return properties; } public java.util.Properties readProjectProperties(IProject project) { // FIXME - as different projects are processed, they continually // overwrite each other's properties // Log.log("About to read properties"); readProperties(); java.util.Properties properties = new java.util.Properties(); { // Log.log("Project location: " + project.getLocation()); IResource res = project .findMember(org.jmlspecs.openjml.Strings.propertiesFileName); if (res != null) { try { boolean found = org.jmlspecs.openjml.Utils.readProps( properties, res.getLocation().toOSString()); if (found && Options.uiverboseness) Log.log("Properties read from the project directory: " + res.getLocation().toOSString()); } catch (java.io.IOException e) { Log.errorlog("Failed to read a properties file", e); } } } return properties; } /** * This routine initiates (as a Job) compiling RAC for all the Java files in * the selection; if any containers are selected, the operation applies the * contents of the container (including working sets); if any Java elements * are selected (e.g. a method), the operation applies to the containing * file. * * @param selection * the current selection (ignored unless it is an * IStructuredSelection) * @param window * null or the currently active IWorkbenchWindow * @param shell * the current shell */ public void racSelection(final @NonNull ISelection selection, @Nullable final IWorkbenchWindow window, final Shell shell) { if (!checkForDirtyEditors()) return; // For now at least, only IResources are accepted for selection final @NonNull List<IResource> res = getSelectedResources(selection, window, shell); if (res.size() == 0) { showMessage(shell, "JML RAC Selected", "Nothing appropriate to check"); return; } final @NonNull Map<IJavaProject, List<IResource>> sorted = sortByProject(res); for (final IJavaProject jp : sorted.keySet()) { Job j = new Job("Compiling Runtime Assertions on selected resources") { public IStatus run(IProgressMonitor monitor) { boolean c = false; try { getInterface(jp).executeExternalCommand(Cmd.RAC, sorted.get(jp), monitor,false); } catch (Exception e) { showExceptionInUI(shell, "Failure while compiling runtime assertions", e); c = true; } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } }; IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory(); // FIXME ISchedulingRule rule = ruleFactory.markerRule(r); j.setRule(jp.getProject()); j.setUser(true); // true since the job has been initiated by an // end-user j.schedule(); } } /** * This routine initiates (as a Job) compiling RAC for all the Java files in * the selection; if any containers are selected, the operation applies the * contents of the container (including working sets); if any Java elements * are selected (e.g. a method), the operation applies to the containing * file. * * @param selection * the current selection (ignored unless it is an * IStructuredSelection) * @param window * null or the currently active IWorkbenchWindow * @param shell * the current shell */ public void racMarked(final @NonNull ISelection selection, @Nullable final IWorkbenchWindow window, final Shell shell) { if (!checkForDirtyEditors()) return; // For now at least, only IResources are accepted for selection final @NonNull Collection<IJavaProject> projects = getSelectedProjects(true,selection, window, shell); if (projects.size() == 0) { showMessage(shell, "JML RAC Marked", "No projects selected"); return; } for (final IJavaProject jp : projects) { racMarked(jp); } } public void racMarked(final IJavaProject jp) { Job j = new Job("Compiling Runtime Assertions on marked resources") { public IStatus run(IProgressMonitor monitor) { boolean c = false; try { Set<IResource> resources = getRacFiles(jp); getInterface(jp).executeExternalCommand(Cmd.RAC, resources, monitor,false); } catch (Exception e) { showExceptionInUI(null, "Failure while compiling runtime assertions", e); c = true; } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } }; IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory(); // FIXME ISchedulingRule rule = ruleFactory.markerRule(r); j.setRule(jp.getProject()); j.setUser(true); // true since the job has been initiated by an // end-user j.schedule(); } // TODO - document doBuildRac - put in a Job - or not - because called from // a computation thread anyway protected void doBuildRac(IJavaProject jproject, List<IResource> resourcesToBuild, IProgressMonitor monitor) { Set<IResource> enabledForRac = getRacFiles(jproject); List<IResource> newlist = new ArrayList<IResource>(enabledForRac.size()); for (IResource r : resourcesToBuild) { if (enabledForRac.contains(r)) newlist.add(r); } if (newlist.size() != 0) { try { if (Options.uiverboseness) Log.log("Starting RAC " + newlist.size() + " files"); getInterface(jproject).executeExternalCommand(Cmd.RAC, newlist, monitor,false); if (Options.uiverboseness) Log.log("Completed RAC"); } catch (Exception e) { showExceptionInUI(null, null, e); } } else { if (Options.uiverboseness) Log.log("Nothing to RAC"); } } /** * This routine initiates (as a Job) generating jmldoc pages for each * project in the selection. If non-projects are selected, the containing * project is used. For each project, the contents of the source folders are * documented. * * @param selection * the current selection (ignored unless it is an * IStructuredSelection) * @param window * null or the currently active IWorkbenchWindow * @param shell * the current shell */ public void jmldocSelection(final ISelection selection, @Nullable final IWorkbenchWindow window, final Shell shell) { // For now at least, only IResources are accepted for selection final List<IResource> res = getSelectedResources(selection, window, shell); if (res.size() == 0) { showMessage(shell, "JML - jmldoc", "Nothing appropriate to check"); return; } Job j = new Job("Generating jmldoc") { public IStatus run(IProgressMonitor monitor) { boolean c = false; try { Collection<IJavaProject> projects = getSelectedProjects( false, selection, window, shell); if (projects.size() == 0) projects = getSelectedProjects(true, selection, window, shell); for (IJavaProject p : projects) { getInterface(p).generateJmldoc(p); } } catch (Exception e) { showExceptionInUI(shell, null, e); c = true; } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } }; // FIXME - use some proper scheduling rule? j.setRule(ResourcesPlugin.getWorkspace().getRoot()); j.setUser(true); j.schedule(); } /** * This method pops up an information window to show the specifications of * each selected type, method, or field. Executed entirely in the UI thread. * * @param selection * the selection (multiple items may be selected) * @param window * the current window * @param shell * the current shell */ public void showSpecsForSelection(ISelection selection, @Nullable IWorkbenchWindow window, Shell shell) { List<Object> list = getSelectedElements(selection, window, shell); if (list.isEmpty()) { showMessage(shell, "OpenJML - Show specifications for Java element", "Choose a type, method or field whose specifications are to be shown"); return; } String sn = emptyString; for (Object o : list) { try { String s = null; if (o instanceof IType) { IType t = (IType) o; s = getInterface(t.getJavaProject()).getAllSpecs(t); if (s != null) s = s.replace('\r', ' '); sn = "type " + t.getFullyQualifiedName(); } else if (o instanceof IMethod) { IMethod m = (IMethod) o; s = getInterface(m.getJavaProject()).getAllSpecs(m); if (s != null) s = s.replace('\r', ' '); sn = "method " + m.getDeclaringType().getFullyQualifiedName() + "." + m.getElementName(); } else if (o instanceof IField) { IField f = (IField) o; s = getInterface(f.getJavaProject()).getSpecs(f); if (s != null) s = s.replace('\r', ' '); sn = "field " + f.getDeclaringType().getFullyQualifiedName() + "." + f.getElementName(); } else if (o instanceof ICompilationUnit) { ICompilationUnit cu = (ICompilationUnit)o; IType[] types = cu.getTypes(); StringBuilder ss = new StringBuilder(); for (IType t: types) { s = getInterface(t.getJavaProject()).getAllSpecs(t); if (s != null) s = s.replace('\r', ' '); if (s.length() == 0) s = "<no specifications>"; ss.append(s); ss.append("\n"); } sn = "classes in file " + cu.getPath().toString(); s = ss.toString(); } else if (o instanceof IFile) { IFile f = (IFile) o; ICompilationUnit cu = JavaCore.createCompilationUnitFrom(f); IType[] types = cu.getTypes(); StringBuilder ss = new StringBuilder(); for (IType t: types) { s = getInterface(t.getJavaProject()).getAllSpecs(t); if (s != null) s = s.replace('\r', ' '); if (s.length() == 0) s = "<no specifications>"; ss.append(s); ss.append("\n"); } sn = "classes in file " + f.toString(); s = ss.toString(); } else { showMessageInUINM(shell, "Specifications", "Cannot show specifications for a " + o.getClass()); return; } if (s != null) { if (s.length() == 0) s = "<no specifications>"; showMessageInUINM(shell, "Specifications for " + sn, s); } else if (sn != null) { showMessageInUINM(shell, "Specifications", "No JML check has been run"); return; } } catch (Exception e) { showMessage(shell, "OpenJML - Exception", e.getMessage()); } } } public List<IType> findMatchingClassNames(IJavaProject jp, String text) throws JavaModelException { String classname = Strings.slash + text.replace('.', '/') + ".class"; //$NON-NLS-1$ String dotText = Strings.dot + text; String dollarText = Strings.dollar + text; List<IType> matches = new LinkedList<IType>(); for (IClasspathEntry cpe : jp.getResolvedClasspath(true)) { // cpe is SOURCE, PROJECT, or LIBRARY if (cpe.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { // findPackageFragmentRoots does not work for // library entries try { ZipFile z = new ZipFile(cpe.getPath() .toString()); Enumeration<? extends ZipEntry> en = z .entries(); while (en.hasMoreElements()) { ZipEntry ze = en.nextElement(); String zs = ze.getName(); if (zs.endsWith(classname)) { zs = zs.replace('/', '.'); zs = zs.substring(0, zs.length() - ".class".length()); //$NON-NLS-1$ matches.add(jp.findType(zs, (IProgressMonitor) null)); } } z.close(); } catch (java.io.IOException ex) { Log.errorlog("Failed to open jar file " + cpe.getPath().toString(), ex); // Pretend there is no match } } else { for (IPackageFragmentRoot pfr : jp .findPackageFragmentRoots(cpe)) { if (!pfr.isOpen()) continue; for (IJavaElement element : pfr.getChildren()) { // element is a IPackageFragment for (IJavaElement je : ((IPackageFragment) element) .getChildren()) { // je is aICompilationUnit if (je instanceof ICompilationUnit) { for (IType ee : ((ICompilationUnit) je).getAllTypes()) { if (ee.getFullyQualifiedName().endsWith(dotText)) { matches.add(ee); } else { matchNestedTypes(ee,dollarText,matches); } } } } } } } } return matches; } protected void matchNestedTypes(IType ee, String dotText, List<IType> matches) throws JavaModelException { for (IJavaElement e: ee.getChildren()) { if (e instanceof IType) { IType t = (IType)e; if (t.getFullyQualifiedName().endsWith(dotText)) { matches.add(t); } else { matchNestedTypes(t,dotText,matches); } } } } /** * This method opens an editor on the specs corresponding to a selected * class; if text is selected, an attempt is made to interpret the text as * the name (possibly fully qualified) of a class; otherwise if a type is * selected that type is used; if a file or compilation unit is selected, * the primary type of that unit is used. Then we search for the * specifications file corresponding to that type. Executed entirely in the * UI thread. * * @param selection * the selection (multiple items may be selected) * @param window * the current window * @param shell * the current shell */ public void openSpecEditorForSelection(ISelection selection, @Nullable IWorkbenchWindow window, Shell shell) { // Note that we could get the specs file through IAPI. However, that // requires that the project be type-checked. Here we duplicate the // logic for looking up specification files, which is not good, but // there is less dependence on what is already typechecked, which is // good. ITextSelection textSelection = getSelectedText(selection); List<Object> list = new LinkedList<Object>(); String text; if (textSelection != null && window != null && (text = textSelection.getText()).length() != 0) { // We have some selected text - try to interpret it as a class name if (Options.uiverboseness) Log.log("Selected text: " + text); //$NON-NLS-1$ // Get the string of text, with .class at the end String classname = "/" + text.replace('.', '/') + ".class"; //$NON-NLS-1$ //$NON-NLS-2$ // Get the Java project corresponding to the file the text is in IEditorPart p = window.getActivePage().getActiveEditor(); IEditorInput e = p == null ? null : p.getEditorInput(); IFile o = e == null ? null : (IFile) e.getAdapter(IFile.class); IJavaProject jp = o == null ? null : JavaCore.create(o) .getJavaProject(); List<IType> matches = new LinkedList<IType>(); if (jp == null) { showMessageInUI(shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "Could not determine the containing Java project"); return; } try { // FIXME - check that nested and secondary types work and // resolve eventually to the primary type // First try fully-qualified names IType t = jp.findType(text, (IProgressMonitor) null); if (t != null) { matches.add(t); } else { // Look for partial matches in the classpath of the project matches = findMatchingClassNames(jp,text); } } catch (JavaModelException ex) { Log.errorlog( "Failed to match text to a type name because of an exception", ex); // Pretend there is no match } if (matches.size() == 1) { list.add(matches.get(0)); } else if (matches.size() > 1) { StringBuilder sb = new StringBuilder(); sb.append("More than one match of type names to "); sb.append(text); sb.append(" with no complete match. Specify the text more completely:"); for (IType t : matches) { sb.append(space); sb.append(t.getFullyQualifiedName()); } showMessageInUI(shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, sb.toString()); return; } else { showMessageInUI(shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "Could not find a type that matches \"" + text + "\""); return; } } else { list = getSelectedElements(selection, window, shell); if (list.isEmpty()) { showMessageInUI(shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "Nothing selected to open editors for"); return; } } String kinds = emptyString; IResource firstEditableLocation = null; // FIXME - perhaps put the Dialog boxes outside the loop and accumulate all the information outer: for (Object o : list) { try { IType t = null; IType origType = null; if (o instanceof IType) { t = (IType) o; origType = t; t = t.getCompilationUnit().findPrimaryType(); } else if (o instanceof ICompilationUnit) { t = ((ICompilationUnit) o).findPrimaryType(); } else if (o instanceof IFile) { ICompilationUnit cu = JavaCore.createCompilationUnitFrom((IFile) o); t = cu.findPrimaryType(); } else if (o instanceof IAdaptable) { ICompilationUnit cu = (ICompilationUnit) ((IAdaptable) o) .getAdapter(ICompilationUnit.class); if (cu != null) t = cu.findPrimaryType(); if (t == null) t = (IType) ((IAdaptable) o).getAdapter(IType.class); } if (t == null) { kinds = kinds + o.getClass() + space; continue; } if (origType == null) origType = t; String name = t.getFullyQualifiedName().replace('.', '/'); String pname = t.getPackageFragment().getElementName(); if (pname.isEmpty()) { showMessageInUI(shell, "OpenJML", "It does not appear that the target file is in a source folder, so I can't search for a specifications file."); return; } pname = pname.replace('.', '/'); String[] fullnames = new String[suffixes.length]; for (int i = 0; i < suffixes.length; i++) fullnames[i] = name + suffixes[i]; String[] absolutePaths = PathItem.getAbsolutePath( t.getJavaProject(), Utils.SPECSPATH_ID).split( File.pathSeparator); List<File> roots = new LinkedList<File>(); for (String p : absolutePaths) { File f = new File(p); if (f.exists()) roots.add(f); else { // FIXME - warn? } } for (String fname : fullnames) { for (File root : roots) { if (root.isDirectory()) { if (firstEditableLocation == null) { firstEditableLocation = ResourcesPlugin .getWorkspace() .getRoot() .getFileForLocation( new Path(root.getAbsolutePath())); } File ff = new File(root, fname); if (!ff.exists()) continue; IResource r = ResourcesPlugin .getWorkspace() .getRoot() .getFileForLocation( new Path(ff.getAbsolutePath())); if (r == null) { showMessageInUI( shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "The specifications for type " + origType.getFullyQualifiedName() + " are in " + Env.eol + ff.getAbsolutePath() + Env.eol + "but an editor can not be opened since the location is not in the workspace." + Env.eol + "Try linking to the package root from within the project."); continue outer; } if (r.exists() && r instanceof IFile) { launchJavaEditor((IFile) r); showMessageInUI( shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "The specifications for type " + origType.getFullyQualifiedName() + " are in " + Env.eol + r.getLocation().toString()); return; } } else { ZipFile jarfile = new ZipFile(root); ZipEntry jarentry = jarfile.getEntry(fname); if (jarentry != null) { // FIXME - this will launch duplicate editors InputStream is = jarfile .getInputStream(jarentry); int size = (int) jarentry.getSize(); byte[] bytes = new byte[size]; is.read(bytes, 0, size); String s = new String(bytes); showMessage( shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, "Specification file for " + t.getFullyQualifiedName() + " in " + Env.eol + jarfile.getName() + Env.eol + " is read-only"); String nm = jarentry.getName(); int k = nm.lastIndexOf('/'); if (k >= 0) nm = nm.substring(k + 1); launchJavaEditor(s, nm); return; } jarfile.close(); } } } showMessage( shell, Messages.OpenJMLUI_OpenSpecsEditor_DialogTitle, kinds.length() == 0 ? "Nothing found for which to open an editor" : "Cannot show specification files for " + kinds); } catch (Exception e) { showExceptionInUI(shell, "Failure while finding specifications file", e); return; } } } // FIXME - add default content - document public void generateDefaultSpecs(PrintWriter ww, IType t) { StringWriter sw = new StringWriter(); PrintWriter w = new PrintWriter(sw); Set<String> imports = new HashSet<String>(); try { // No extending Object // No importing java.lang // No duplicate imports // type parameters names and bounds not handled correctly // Need methods and fields and nested classes // Need secondary types printClass(w, t, imports); } catch (JavaModelException e) { w.println("<Error in generating default content>"); } w.close(); ww.println("package " + t.getPackageFragment().getElementName() + Strings.semicolon); //$NON-NLS-1$ for (String s : imports) { ww.println("import " + s + Strings.semicolon); //$NON-NLS-1$ } ww.println(); ww.println(sw.toString()); ww.close(); } // FIXME - document protected void printClass(PrintWriter w, IType t, Set<String> imports) throws JavaModelException { ITypeHierarchy th = t.newSupertypeHierarchy(null); IType sup = th.getSuperclass(t); if (sup.getFullyQualifiedName().equals("java.lang.Object")) //$NON-NLS-1$ sup = null; IType[] ifaces = th.getSuperInterfaces(t); if (sup != null) imports.add(sup.getPackageFragment().getElementName()); for (IType i : ifaces) imports.add(i.getPackageFragment().getElementName()); w.println(); // FIXME - annotations w.print(Flags.toString(t.getFlags())); w.print(" class "); //$NON-NLS-1$ printType(w, t, imports); if (sup != null) { w.print(" extends "); //$NON-NLS-1$ printType(w, sup, imports); } if (ifaces.length > 0) w.print(" implements"); //$NON-NLS-1$ boolean isFirst = true; for (IType i : ifaces) { if (isFirst) isFirst = false; else w.print(Strings.comma); w.print(Strings.space); printType(w, i, imports); } w.println(" {"); w.println(); // w.println(" //@ requires true;"); // w.println(" //@ ensures true;"); // w.println(" //@ static_initalizer;"); // w.println(); // w.println(" //@ requires true;"); // w.println(" //@ ensures true;"); // w.println(" //@ initalizer;"); // // for (IMethod m : t.getMethods()) { // w.println(); // w.print(" "); // // FIXME - annotations // w.print(Flags.toString(m.getFlags())); // w.print(" "); // w.print(m.getReturnType()); // w.print(" "); // w.print(m.getElementName()); // w.print("("); // boolean isFirst2 = true; // String[] pn = m.getParameterNames(); // String[] pt = m.getParameterTypes(); // for (int i=0; i<pn.length; i++) { // if (isFirst2) isFirst2 = false; else w.print(", "); // // FIXME _ modifierse // w.print(pt[i]); // w.print(" "); // w.print(pn[i]); // } // w.print(")"); // // FIXME - exceptions // w.print(";"); // } w.println(); w.println("}"); //$NON-NLS-1$ } // FIXME - document protected void printType(PrintWriter w, IType t, Set<String> imports) throws JavaModelException { w.print(t.getElementName()); ITypeParameter[] tparams = t.getTypeParameters(); if (tparams.length != 0) { w.print("<"); //$NON-NLS-1$ boolean isFirst = true; for (ITypeParameter tp : tparams) { if (isFirst) isFirst = false; else w.print(Strings.comma); w.print(tp.getElementName()); String[] bounds = tp.getBounds(); if (bounds.length > 0) { w.print(" extends"); //$NON-NLS-1$ boolean isFirst2 = true; for (String s : bounds) { if (isFirst2) isFirst2 = false; else w.print(" &"); //$NON-NLS-1$ w.print(Strings.space); w.print(s); } } } w.print(">"); //$NON-NLS-1$ } } /** * This method pops up an information window to show the proof result for * each selected method. Executed entirely in the UI thread. * * @param selection * the selection (multiple items may be selected) * @param window * the current window * @param shell * the current shell */ public void showProofInfoForSelection(ISelection selection, @Nullable IWorkbenchWindow window, Shell shell, boolean showDetails) { List<Object> olist = getSelectedElements(selection, window, shell); List<IMethod> list = new LinkedList<IMethod>(); for (Object o : olist) { if (o instanceof IMethod) list.add((IMethod) o); // Ignore anything that does not match. Other types of items can be // selected, particularly with a MenuAction. } if (list.isEmpty()) { showMessage(shell, "JML", //$NON-NLS-1$ "No methods were selected for the 'show proof info' operation"); } else { for (IMethod m : list) { OpenJMLInterface jml = getInterface(m.getJavaProject()); jml.showProofInfo(m, shell, showDetails); } } } /** * This method pops up an information window to show the value of an * expression in the current counterexample. Uses the computational thread. * * @param selection * the selection (multiple items may be selected) * @param window * the current window * @param shell * the current shell */ public void showCEValueForTextSelection(ISelection selection, @Nullable IWorkbenchWindow window, Shell shell) { if (!checkForDirtyEditors()) return; ITextSelection selectedText = getSelectedText(selection); if (selectedText == null) { showMessage(shell, "JML", "No text is selected"); return; } int pos = selectedText.getOffset(); int end = pos + selectedText.getLength() - 1; String s = selectedText.getText(); IResource r = getSelectedResources(selection, window, shell).get(0); // FIXME - need to do this in another thread. ? String result = getInterface(JavaCore.create(r.getProject())) .getCEValue(pos, end, s, r); showMessage(shell, "Counterexample value", result); } /** * This method interprets the selection returning a List of IResources or * IJavaElements, and ignoring things it does not know how to handle. The * selection is ignored if it is not an IStructuredSelection (e.g. * ITextSelections are ignored). If the selection is empty or the resulting * list is empty, and 'window' is non-null, then the routine attempts to * find a resource that corresponds to the currently active editor. * <UL> * <LI>IJavaElement - returned in the list * <LI>IResource - added directly to list, whether a file or a container * <LI>working set - adds the elements of the working set if they can be * converted (through IAdaptor) to an IResource * <LI>otherwise - attempts to be converted to an IResource, and added to * list if successful, ignored otherwise * </UL> * * @param selection * The selection to inspect * @param window * The window in which a selected editor exists, or null * @param shell * the shell to use in displaying information dialogs, or null to * use a default shell * @return A List of IResource or IJavaElement */ public List<Object> getSelectedElements(@NonNull ISelection selection, @NonNull IWorkbenchWindow window, @Nullable Shell shell) { List<Object> list = new LinkedList<Object>(); if (!selection.isEmpty() && selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = (IStructuredSelection) selection; for (Iterator<?> iter = structuredSelection.iterator(); iter .hasNext();) { Object element = iter.next(); if (element instanceof IJavaElement) { list.add(element); } else if (element instanceof IResource) { // Log.log("Selected " + ((IResource)element).getName()); list.add(element); } else if (element instanceof IWorkingSet) { for (IAdaptable a : ((IWorkingSet) element).getElements()) { IResource r = (IResource) a.getAdapter(IResource.class); if (r != null) list.add(r); } continue; } else if (element instanceof IAdaptable) { // TODO: curious as to just what might fall into this // category IResource r = ((IResource) ((IAdaptable) element) .getAdapter(IResource.class)); if (r != null) list.add(r); } } } else { // We had nothing selected // Look for the active editor instead try { IEditorPart p = window.getActivePage().getActiveEditor(); IEditorInput e = p == null ? null : p.getEditorInput(); Object o = e == null ? null : e.getAdapter(ICompilationUnit.class); o = o != null ? o : e != null ? e.getAdapter(IFile.class) : null; o = o == null ? e : o; if (o != null) { // Log.log("Selected " + o); list.add(o); } } catch (Exception ee) { Log.errorlog("Exception when finding selected targets: " + ee, ee); showMessage(window.getShell(), "JML Plugin Exception", "Exception occurred when finding selected targets: " + ee); } } return list; } // TODO - document public ITextSelection getSelectedText(@NonNull ISelection selection) { if (selection instanceof ITextSelection) { return (ITextSelection) selection; } else { return null; } } /** * This method interprets the selection returning a List of IResources or * IJavaElements, and ignoring things it does not know how to handle. The * selection is ignored if it is not an IStructuredSelection (e.g. * ITextSelections are ignored). If the selection is empty or the resulting * list is empty, and 'window' is non-null, then the routine attempts to * find a resource that corresponds to the currently active editor. * <UL> * <LI>IJavaElement - adds the containing java project * <LI>IResource - adds the containing project, as a Java project, if it is * one * <LI>working set - adds the elements of the working set if they are Java * projects * <LI>otherwise - attempts to be converted to a IJavaProject or an * IResource, and added to list if successful, ignored otherwise * </UL> * * @param convert * if false, then returned elements were precisely Java Projects * in the selection; if true, the returned projects may also be * the containing projects of selected non-project elements * @param selection * The selection to inspect * @param window * The window in which a selected editor exists, or null * @param shell * the shell to use in displaying information dialogs, or null to * use a default shell * @return A List of IResource or IJavaElement */ public Collection<IJavaProject> getSelectedProjects(boolean convert, @NonNull ISelection selection, @NonNull IWorkbenchWindow window, @Nullable Shell shell) { Set<IJavaProject> list = new HashSet<IJavaProject>(); if (!selection.isEmpty()) { if (selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = (IStructuredSelection) selection; for (Iterator<?> iter = structuredSelection.iterator(); iter .hasNext();) { Object element = iter.next(); if (!convert) { if (element instanceof IJavaProject) list.add((IJavaProject) element); } else if (element instanceof IJavaElement) { list.add(((IJavaElement) element).getJavaProject()); } else if (element instanceof IResource) { IJavaProject jp = JavaCore.create(((IResource) element) .getProject()); if (jp != null) list.add(jp); } else if (element instanceof IWorkingSet) { for (IAdaptable a : ((IWorkingSet) element) .getElements()) { // IJavaProject jp = // JavaCore.create(((IResource)element).getProject()); IJavaProject jp = (IJavaProject) a .getAdapter(IJavaProject.class); if (jp != null) list.add(jp); } continue; } else if (element instanceof IAdaptable) { // TODO: curious as to just what might fall into this // category IJavaProject jp = (IJavaProject) ((IAdaptable) element) .getAdapter(IJavaProject.class); if (jp != null) list.add(jp); else { IResource r = ((IResource) ((IAdaptable) element) .getAdapter(IResource.class)); jp = JavaCore.create(((IResource) element) .getProject()); if (r != null) list.add(jp); } } } // } else { // showMessage(shell,"Unknown selection",selection.getClass().toString()); } } if (convert && list.size() == 0) { // We had nothing selected or it was not a structured selection // Look for the active editor instead try { IEditorPart p = window.getActivePage().getActiveEditor(); IEditorInput e = p == null ? null : p.getEditorInput(); IFile o = e == null ? null : (IFile) e.getAdapter(IFile.class); if (o != null) { IJavaProject jp = JavaCore.create(o.getProject()); if (jp != null) list.add(jp); } } catch (Exception ee) { Log.errorlog("Exception when finding selected targets: " + ee, ee); showMessage(window.getShell(), Messages.OpenJMLUI_ExceptionTitle, "Exception occurred when finding selected targets: " + ee); } } return list; } /** * This method interprets the selection returning a List of IResources, and * ignoring things it does not know how to handle; note that the resources * in the returned list are not necessarily Java files. The selection is * ignored if it is not an IStructuredSelection (e.g. ITextSelections are * ignored). If the selection is empty and 'window' is non-null, then the * routine attempts to find a resource that corresponds to the currently * active editor. * <UL> * <LI>IResource - added directly to list, whether a file or a container * <LI>working set - adds the elements of the working set if they can be * converted (through IAdaptor) to an IResource * <LI>IJavaElement - adds the IResource that contains the element * <LI>otherwise - ignored * </UL> * * @param selection * The selection to inspect * @param window * The window in which a selected editor exists * @param shell * the shell to use in displaying information dialogs * @return A List of IResources found in the selection */ public List<IResource> getSelectedResources(@NonNull ISelection selection, @Nullable IWorkbenchWindow window, @Nullable Shell shell) { List<IResource> list = new LinkedList<IResource>(); if (selection instanceof IStructuredSelection && !selection.isEmpty() ) { IStructuredSelection structuredSelection = (IStructuredSelection) selection; for (Iterator<?> iter = structuredSelection.iterator(); iter .hasNext();) { Object element = iter.next(); if (element instanceof IResource) { list.add((IResource) element); } else if (element instanceof IWorkingSet) { for (IAdaptable a : ((IWorkingSet) element).getElements()) { IResource r = (IResource) a.getAdapter(IResource.class); if (r != null) list.add(r); } continue; } else if (element instanceof IJavaElement) { // try { IResource r = ((IJavaElement) element).getResource(); if (r != null) list.add(r); else if (element instanceof IAdaptable && (r = (IResource) ((IAdaptable) element) .getAdapter(IResource.class)) != null) { list.add(r); } else { if (Options.uiverboseness) Log.log("No resource for " + ((IJavaElement) element).getElementName()); } } } } else { // We had nothing selected // Look for the active editor instead try { IEditorPart p = window.getActivePage().getActiveEditor(); IEditorInput e = p == null ? null : p.getEditorInput(); IResource o = e == null ? null : (IFile) e .getAdapter(IFile.class); if (o != null) { // Log.log("Selected " + o); list.add(o); // This is an IFile } } catch (Exception ee) { Log.errorlog("Exception when finding selected targets: " + ee, ee); showMessage(shell, "JML Plugin Exception", "Exception occurred when finding selected targets: " + ee); } } return list; } /** * Alters whether the JML nature is enabled or disabled for the given * selected objects. The operation makes sense only for IJavaProject * objects; if other types of objects are selected, the enclosing * IJavaProject is used; if there is none, the selected object is ignored. * The operation is performed entirely in the UI thread (and should be * called from the UI thread). * * @param enable * if true, the JML nature is enabled; if false, it is disabled * @param selection * the objects selected in the UI * @param window * the current window * @param shell * the current shell (for any dialogs) */ public void changeJmlNatureSelection(boolean enable, ISelection selection, IWorkbenchWindow window, Shell shell) { Collection<IJavaProject> list = Activator.utils() .getSelectedProjects(true, selection, window, shell); Iterator<IJavaProject> i = list.iterator(); if (!i.hasNext()) { Utils.showMessage(shell, "JML Plugin", "No Java projects selected"); return; } int maxdialogs = 5; while (i.hasNext()) { IJavaProject p = i.next(); try { if (enable) JMLNature.enableJMLNature(p.getProject()); else JMLNature.disableJMLNature(p.getProject()); PlatformUI.getWorkbench().getDecoratorManager() .update(Env.JML_DECORATOR_ID); } catch (Exception e) { if (window != null && (maxdialogs--) > 0) { Utils.showMessage( shell, "JML Plugin Exception", "Exception while " + (enable ? "enabling" : "disabling") + " JML " + (p != null ? "on " + p.getElementName() : "") + e.toString()); } Log.errorlog( "Failed to " + (enable ? "enable" : "disable") + " JML nature" + (p != null ? " on project " + p.getElementName() : ""), e); } } if (Options.uiverboseness) Log.log("Completed JML Nature operation "); } // Do this right here in the UI thread // FIXME - should resource things be happening in another thread? /** * Deletes all JML markers from the items selected, right within the UI * thread, without a progress dialog. The resources for which markers are * deleted are those returned by Utils.getSelectedResources. This should be * called from the UI thread. * * @param selection * the IStructuredSelection whose markers are to be deleted * @param window * the current workbench window, or null (used in * getSelectedResources) * @param shell * the current Shell, or null for the default shell (for message * dialogs) */ public void deleteMarkersInSelection(ISelection selection, IWorkbenchWindow window, Shell shell) { if (selection == null) { showMessage(shell, "JML Plugin", "Nothing selected to delete markers of"); return; } List<IResource> list = getSelectedResources(selection, window, shell); if (list.isEmpty()) { showMessage(shell, "JML Plugin", "Nothing appropriate to delete markers of"); return; } deleteMarkers(list, shell); return; } public @Nullable PathItem toPathItem(IJavaProject jp, Object r) { IResource f; if (r instanceof IPackageFragmentRoot) { IPackageFragmentRoot pfr = (IPackageFragmentRoot) r; f = pfr.getResource(); } else if (r instanceof IFile && "jar".equals(((IFile) r).getFileExtension())) { f = (IFile) r; } else if (!(r instanceof IFolder)) { return null; } else { f = (IFolder) r; } PathItem p; if (f.getProject().equals(jp.getProject())) { // Same project - use a project relative path p = new PathItem.ProjectPath(f.getProjectRelativePath() .toString()); } else { p = new PathItem.WorkspacePath(f.getFullPath().toString()); } return p; } // FIXME - add/remove - a source folder that is in the ALL_SOURCE_FOLDERs // is not recognized as already present /** * Expects the selection to hold exactly one Java project, plus one or more * folders or jar files; those folders and jar files are added to the * beginning of the specs path for the given project. * * @param selection the current selection in the UI * @param window the currently active window * @param shell the current shell (for dialogs) */ public void addSelectionToSpecsPath(ISelection selection, IWorkbenchWindow window, @Nullable Shell shell) { Collection<IJavaProject> projects = getSelectedProjects(false, selection, window, shell); if (projects.size() != 1) { showMessage(shell, "OpenJML - Add to Specs Path", "Select exactly one Java Project along with the desired folders"); return; } IJavaProject jp = projects.iterator().next(); List<Object> list = getSelectedElements(selection, window, shell); String notadded = emptyString; boolean added = false; for (Object r : list) { if (r instanceof IJavaProject || r instanceof IProject) continue; PathItem p = toPathItem(jp,r); if (p == null) { if (r instanceof IResource) notadded = notadded + ((IResource) r).getName() + space; continue; } try { if (PathItem.add(jp, Env.specsKey, p)) { added = true; } else { notadded = notadded + space + p.display(); } } catch (CoreException e) { showMessage(shell, "OpenJML - Remove from Specs Path", "Exception on reading or writing persistent property: " + e); } } if (notadded.length() != 0) { showMessage(shell, "JML - Add to Specs Path", "These were already present and not added:" + notadded); } else if (!added) { showMessage(shell, "JML - Add to Specs Path", "Nothing was added"); } } /** * Expects the selection to hold exactly one Java project, plus one or more * folders or jar files; those folders and jar files are removed from the * the specs path of the given project. * * @param selection * the current selection in the UI * @param window * the currently active window * @param shell * the current shell (for dialogs) */ public void removeSelectionFromSpecsPath(ISelection selection, @Nullable IWorkbenchWindow window, @Nullable Shell shell) { Collection<IJavaProject> projects = getSelectedProjects(false, selection, window, shell); if (projects.size() != 1) { showMessage(shell, "JML - Remove from Specs Path", "Select exactly one Java Project along with the desired folders"); return; } IJavaProject jp = projects.iterator().next(); List<Object> list = getSelectedElements(selection, window, shell); String notremoved = emptyString; for (Object r : list) { IResource f; String n; if (r instanceof IJavaProject || r instanceof IProject) continue; PathItem p = toPathItem(jp,r); try { if (p == null) { notremoved = notremoved + r + space; } else if (!PathItem.remove(jp, Env.specsKey, p)) { notremoved = notremoved + p.display() + space; } } catch (CoreException e) { showMessage(shell, "OpenJML - Remove from Specs Path", "Exception on reading or writing persistent property: " + e); } } if (notremoved.length() != 0) { showMessage(shell, "OpenJML - Remove from Specs Path", "These were not found: " + notremoved); } } /** Puts up dialogs to edit each the paths of each selected Java project. */ public void manipulateSpecsPath(ISelection selection, IWorkbenchWindow window, Shell shell) { Collection<IJavaProject> projects = getSelectedProjects(true, selection, window, shell); if (projects.isEmpty()) { showMessageInUI(shell,"OpenJML Paths Editor", "No projects selected"); return; } final Shell finalShell = shell; for (IJavaProject jproject : projects) { final IJavaProject jp = jproject; // FIXME - none of these implementations lets the dialogs come up // simultaneously // Even if we inherit PathsEditor from ModelessDialog // Display d = finalShell == null ? Display.getDefault() : // finalShell.getDisplay(); // d.asyncExec(new Runnable() { // public void run() { // Dialog dialog = new PathsEditor(finalShell, // "OpenJML Paths Editor - " + jp.getElementName(), jp); // dialog.create(); // dialog.open(); // OK actions are handled in the dialog // } // }); try { jproject.getProject().getWorkspace() .run(new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { Dialog dialog = new PathsEditor(finalShell, "OpenJML Paths Editor - " + jp.getElementName(), jp); dialog.create(); dialog.open(); // OK actions are handled in the // dialog } }, null); } catch (CoreException e) { showExceptionInUI(shell, "Failure while editing paths", e); } // Dialog dialog = new PathsEditor(finalShell, // "OpenJML Paths Editor - " + jp.getElementName(), jp); // dialog.create(); // dialog.open(); // OK actions are handled in the dialog } } /** * Shows the classpath for selected projects. SHould be called from the UI * thread; is executed entirely in the calling thread. * * @param selection * the current selection in the UI * @param window * the currently active window * @param shell * the currently active shell (or null for default) */ public void showPaths(ISelection selection, IWorkbenchWindow window, Shell shell) { Collection<IJavaProject> projects = getSelectedProjects(true, selection, window, shell); if (projects.isEmpty()) { showMessage(shell, "Show JML Paths", "No projects selected"); } for (IJavaProject jp : projects) { List<String> list = getClasspath(jp); StringBuilder ss = new StringBuilder(); ss.append("Classpath:"); ss.append(Env.eol); for (String s : list) { ss.append(s); ss.append(Env.eol); } ss.append(Env.eol); // source path ss.append("Source path:"); ss.append(Env.eol); String sp = PathItem.getAbsolutePath(jp, Env.sourceKey); sp = sp.replace(File.pathSeparator, Env.eol); ss.append(sp); ss.append(Env.eol); ss.append(Env.eol); // specs path ss.append("Specs path:"); ss.append(Env.eol); sp = PathItem.getAbsolutePath(jp, Env.specsKey); sp = sp.replace(File.pathSeparator, Env.eol); ss.append(sp); ss.append(Env.eol); showMessage(shell, "JML paths for project " + jp.getElementName(), "Edit the paths in the JML preferences or use the" + Env.eol + "Add to/Remove from JML Specs Path menu items" + Env.eol + Env.eol + ss.toString()); } } private boolean changingClasspath = false; /** * Adds a Library classpath entry holding the internal run-time library to * the end of the given project's classpath, if the library is not already * on the classpath. This will trigger a build, if auto-building is turned * on. * * @param jproject * the project whose classpath is to be adjusted */ public IClasspathEntry addRuntimeToProjectClasspath(final IJavaProject jproject) { if (changingClasspath) return null; try { IClasspathEntry[] entries = jproject.getRawClasspath(); for (IClasspathEntry i : entries) { if (i.getPath().lastSegment().equals(ClasspathVariableInitializer.OPENJML_RUNTIME_LIBRARY)) { showMessageInUI(null,"OpenJML","Internal runtime library is already on the classpath"); return i; } if (i.getPath().lastSegment().equals(RUNTIME_LIBRARY)) { showMessageInUI(null,"OpenJML","Classpath already contains a manually added jmlruntime.jar library"); return i; } } IClasspathEntry libentry = makeRuntimeLibEntry(); final IClasspathEntry[] newentries = new IClasspathEntry[entries.length + 1]; System.arraycopy(entries, 0, newentries, 0, entries.length); newentries[entries.length] = libentry; try { changingClasspath = true; if (Options.uiverboseness) Log.log("Internal runtime being added to classpath: " + libentry); try { jproject.getProject().getWorkspace() .run(new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { try { jproject.setRawClasspath(newentries, monitor); } catch (Exception e) { showMessageInUI(null, "Error Dialog", "Exception while changing classpath: " + e); } } }, null); } catch (CoreException e) { Log.errorlog("Core Exception while changing classpath", e); // just continue } } finally { changingClasspath = false; } return libentry; } catch (JavaModelException e) { throw new Utils.OpenJMLException( "Failed in adding internal runtime library to classpath: " + e.getMessage(), e); } } protected void removeFromClasspath(final IJavaProject jproject, IClasspathEntry entry) { if (changingClasspath) return; try { IClasspathEntry[] entries = jproject.getRawClasspath(); final IClasspathEntry[] newentries = new IClasspathEntry[entries.length]; int j = 0; if (entry == null) { for (IClasspathEntry i : entries) { if (!i.getPath().lastSegment().equals(RUNTIME_LIBRARY) && !i.getPath().lastSegment().equals(ClasspathVariableInitializer.OPENJML_RUNTIME_LIBRARY)) { newentries[j++] = i; } } } else { for (IClasspathEntry i : entries) { if (!i.equals(entry)) { newentries[j++] = i; } } } if (j < entries.length) { final IClasspathEntry[] newerentries = new IClasspathEntry[j]; System.arraycopy(newentries, 0, newerentries, 0, j); try { changingClasspath = true; if (Options.uiverboseness) Log.log("Internal runtime being removed from classpath: " + entry); try { jproject.getProject().getWorkspace() .run(new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { try { jproject.setRawClasspath(newerentries, monitor); } catch (Exception e) { showMessageInUI(null, "Error Dialog", "Exception while changing classpath: " + e); } } }, null); } catch (CoreException e) { Log.errorlog("Core Exception while changing classpath", e); // just continue } } finally { changingClasspath = false; } } return ; } catch (JavaModelException e) { throw new Utils.OpenJMLException( "Failed in removing internal runtime library from classpath: " + e.getMessage(), e); } } /** * Gets the classpath of the given project, interpreting all Eclipse entries * and converting them into file system paths to directories or jars. * * @param jproject * the Java project whose class path is wanted * @return a List of Strings giving the paths to the files and directories * on the class path */ @NonNull protected List<String> getClasspath(@NonNull IJavaProject jproject) { return getClasspath(jproject, false); } /** * Gets the classpath of the given project, interpreting all Eclipse entries * and converting them into file system paths to directories or jars. * * @param jproject * the Java project whose class path is wanted * @param onlyExported * if true, only classpath entries that are marked as exported * are added to the output list * @return a List of Strings giving the paths to the files and directories * on the class path */ @NonNull protected List<String> getClasspath(@NonNull IJavaProject jproject, boolean onlyExported) { try { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IClasspathEntry[] entries = jproject.getResolvedClasspath(true); List<String> cpes = new LinkedList<String>(); for (IClasspathEntry i : entries) { if (onlyExported && !i.isExported()) continue; // Log.log("ENTRY " + i); switch (i.getEntryKind()) { case IClasspathEntry.CPE_CONTAINER: case IClasspathEntry.CPE_SOURCE: IResource p = root.getFolder(i.getPath()); String s = p.getLocation().toString(); cpes.add(s); break; case IClasspathEntry.CPE_LIBRARY: IFile f = root.getFile(i.getPath()); if (f == null || f.getLocation() == null) { cpes.add(i.getPath().toString()); } else { cpes.add(f.getLocation().toString()); } break; case IClasspathEntry.CPE_PROJECT: // FIXME - this has not been tested - pay attention to // isExported? IProject proj = (IProject) root.getProject(i.getPath() .toString()); List<String> plist = getClasspath(JavaCore.create(proj), true); cpes.addAll(plist); break; case IClasspathEntry.CPE_VARIABLE: // Variables and containers are already resolved default: Log.errorlog( "An unexpected kind of ClassPathEntry was ignored (project " + jproject.getElementName() + "): " + i, null); break; } } // Bundle b = Platform.getBundle("org.jmlspecs.OpenJMLUI"); // URL url = b.getEntry(""); // URI uri = url.toURI(); // String s = uri.getPath(); // String ss = url.toExternalForm(); // FIXME - should this be used? // We are trying to include the contents of OpenJMLUI on the // classpath // Why? Don't we already have annotations, specs, and the runtime // library? FIXME // This just ends up as a / // cpes.add(s); return cpes; // } catch (URISyntaxException e) { // throw new // Utils.OpenJMLException("Failed in determining classpath",e); } catch (JavaModelException e) { throw new Utils.OpenJMLException("Failed in determining classpath", e); } } public void refreshView() { Display d = Display.getDefault(); d.asyncExec(new Runnable() { public void run() { try { IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(OpenJMLView.ID); ((OpenJMLView)view).refresh(); } catch (PartInitException e) { // FIXME - report error? } } }); } public void refreshView(final String symname) { Display d = Display.getDefault(); d.asyncExec(new Runnable() { public void run() { try { IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(OpenJMLView.ID); ((OpenJMLView)view).refresh(symname); } catch (PartInitException e) { // FIXME - report error? } catch (RuntimeException e) { // FIXME - report error? } } }); } /** Returns the ProofView, if it exists, null otherwise */ static public OpenJMLView findView() { IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(OpenJMLView.ID); return ((OpenJMLView)view); } /** Creates (if needed) and returns the Proof View */ static public OpenJMLView showView() { try { IWorkbench w = PlatformUI.getWorkbench(); IWorkbenchWindow ww = w.getActiveWorkbenchWindow(); IWorkbenchPage wp = ww.getActivePage(); IViewPart view = wp.showView(OpenJMLView.ID); return ((OpenJMLView)view); } catch (PartInitException e) { // FIXME - report error? return null; } } /** Creates (if needed) and returns the trace view, setting the given data */ public void setTraceView(final String methodName, final String text) { Display d = Display.getDefault(); d.asyncExec(new Runnable() { public void run() { setTraceViewUI(null, methodName,text); } }); } /** Creates (if needed) and returns the trace view, setting the given data; MUST be called from the UI thread */ public void setTraceViewUI(/*@ nullable*/ TraceView tview, final String methodName, final String text) { try { if (tview == null) { IViewPart view = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(TraceView.ID); if (!(view instanceof TraceView)) return; // FIXME - this would be an internal error tview = (TraceView)view; } tview.setText(methodName, text); } catch (PartInitException e) { // FIXME - report error? } } /** Creates (if needed) and returns the trace view, setting it to data for the most recent selection in the Proof View. */ public void setTraceViewUI(/*@ nullable*/ TraceView tview, IJavaProject currentProject) { TreeItem ti = findView().selected; if (ti == null) return; OpenJMLView.Info info = (OpenJMLView.Info)ti.getData(); if (info == null) return; IProverResult res = info.proofResult; String methodName = ti.getText(); ICounterexample ce = res == null ? null : res.counterexample(); String text = ce instanceof Counterexample ? ((Counterexample)ce).traceText : null; setTraceViewUI(tview, methodName, text); } public void setTraceView(final IJavaProject currentProject) { Display d = Display.getDefault(); d.asyncExec(new Runnable() { public void run() { setTraceViewUI(null, currentProject); } }); } public void setTraceView(final String key, final IJavaProject currentProject) { Display d = Display.getDefault(); d.asyncExec(new Runnable() { public void run() { OpenJMLView view = Activator.utils().findView(); final TreeItem ti = view.treeitems.get(key); view.selected = ti; setTraceViewUI(null, currentProject); // FIXME - highlight also OpenJMLView.Info info = (OpenJMLView.Info)ti.getData(); if (info != null && info.javaElement instanceof IMethod) { Activator.utils().getInterface(currentProject).highlightCounterexamplePath((IMethod)info.javaElement,info.proofResult,view.treece.get(ti)); } } }); } /** * This class is an implementation of the interfaces needed to provide input * to and launch editors in the workspace. * * @author David R. Cok */ public static class StringStorage implements IStorage, IStorageEditorInput { /** The initial content of the editor */ private @NonNull String content; /** The name of storage unit (e.g. the file name) */ private @NonNull String name; /** A constructor for a new storage unit */ // @ assignable this.*; public StringStorage(@NonNull String content, @NonNull String name) { this.content = content; this.name = name; } /** Interface method that returns the contents of the storage unit */ @Override public InputStream getContents() throws CoreException { return new ByteArrayInputStream(content.getBytes()); } /** * Returns the path to the underlying resource * * @return null (not needed for readonly Strings) */ @Override public IPath getFullPath() { return null; } /** * Returns the name of the storage object * * @return the name of the storage unit */ @Override @Query public @NonNull String getName() { return name; } /** * Returns whether the storage object is read only * * @return always true */ @Override public boolean isReadOnly() { return true; } /** * Returns the object adapted to the given class. It appears we can * ignore this and always return null. * * @return null */ @Override @SuppressWarnings("rawtypes") // Can't add type parameters because the parent interface does not have // them public @Nullable Object getAdapter(@NonNull Class arg0) { return null; } /** * Returns self * * @return this object */ // @ ensures \return == this; @Override public IStorage getStorage() throws CoreException { return (IStorage) this; } /** * Returns whether the underlying storage object exists * * @return always true */ @Override public boolean exists() { return true; } /** * Returns an ImageDescriptor, here ignored * * @return always null */ @Override public ImageDescriptor getImageDescriptor() { return null; } /** * Returns a corresponding Persistable object, here ignored * * @return always null */ @Override public IPersistableElement getPersistable() { return null; } /** * Return the text desired in a tool tip, here the name of the storage * unit */ @NonNull @Override public String getToolTipText() { return name; } } /** * Launches a read-only text editor with the given content and name * * @param content * the content of the editor * @param name * the name (as in the title) of the editor */ public void launchEditor(String content, String name) { try { IEditorInput editorInput = new StringStorage(content, name); IWorkbenchWindow window = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); page.openEditor(editorInput, "org.eclipse.ui.DefaultTextEditor"); } catch (Exception e) { showExceptionInUI(null, "Failure while launching an editor", e); } } /** * Launches a read-only text editor with the given content and name * * @param content * the content of the editor * @param name * the name (as in the title) of the editor */ public void launchJavaEditor(String content, String name) { try { IEditorInput editorInput = new StringStorage(content, name); IWorkbenchWindow window = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); page.openEditor(editorInput, org.eclipse.jdt.ui.JavaUI.ID_CU_EDITOR); } catch (Exception e) { showExceptionInUI(null, "Failure while launching an editor", e); } } /** * Launches a editable Java editor with the given file * * @param file * the file to edit */ public void launchJavaEditor(IFile file) { try { IFileEditorInput editorInput = new FileEditorInput(file); IWorkbenchWindow window = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); page.openEditor(editorInput, org.eclipse.jdt.ui.JavaUI.ID_CU_EDITOR); } catch (Exception e) { showExceptionInUI(null, "Failure while launching an editor", e); } } // public void launchJavaEditor(File file) { // try { // IFileStore fileStore = EFS.getLocalFileSystem().getStore();//new // Path(filterPath)); // fileStore = fileStore.getChild(file.getAbsolutePath()); // IFileEditorInput editorInput = new FileEditorInput(file); // IWorkbenchWindow window = // PlatformUI.getWorkbench().getActiveWorkbenchWindow(); // IWorkbenchPage page = window.getActivePage(); // page.openEditor(editorInput, org.eclipse.jdt.ui.JavaUI.ID_CU_EDITOR ); // } catch (Exception e) { // showExceptionInUI(null,"Failure while launching an editor", e); // } // } /** * Deletes the markers in any of the objects in the List that are IResource * objects; if the object is a container, markers are deleted for any * resources in the container; other kinds of objects are ignored. * * @param <T> * just the type of the list * @param list * a list of objects whose markers are to be deleted * @param shell * the current shell for dialogs (or null for default) */ public <T> void deleteMarkers(List<T> list, @Nullable Shell shell) { int maxdialogs = 5; for (T t : list) { if (!(t instanceof IResource)) continue; IResource resource = (IResource) t; try { deleteMarkers(resource, shell); } catch (Exception e) { Log.errorlog("Exception while deleting markers: " + e, e); if ((maxdialogs--) > 0) { showMessage( shell, "JML Plugin Exception", "Exception while deleting markers " + (resource != null ? "on " + resource.getName() : "") + e.toString()); } } } } /** * Deletes the markers (and highlighting) in the given resource; if the * object is a container, markers are deleted for any resources in the * container; other kinds of objects are ignored. * * @param resource * the resource whose markers are to be deleted * @param shell * the current shell for dialogs (or null for default) */ public void deleteMarkers(IResource resource, @Nullable Shell shell) { try { if (Options.uiverboseness) Log.log("Deleting markers in " + resource.getName()); resource.deleteMarkers(Env.JML_MARKER_ID, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.ESC_MARKER_ID, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_TRUE, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_FALSE, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_EXCEPTION, false, IResource.DEPTH_INFINITE); } catch (CoreException e) { String msg = "Failed to delete markers on " + resource.getProject(); Log.errorlog(msg, e); Utils.showMessage(shell, "JML Plugin Exception", msg + " - " + e); } } public void deleteHighlights(IResource resource, @Nullable Shell shell) { try { if (Options.uiverboseness) Log.log("Deleting highlights in " + resource.getName()); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_TRUE, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_FALSE, false, IResource.DEPTH_INFINITE); resource.deleteMarkers(Env.JML_HIGHLIGHT_ID_EXCEPTION, false, IResource.DEPTH_INFINITE); } catch (CoreException e) { String msg = "Failed to delete highlights on " + resource.getProject(); Log.errorlog(msg, e); Utils.showMessage(shell, "JML Plugin Exception", msg + " - " + e); } } // TODO _ needs more documentation // public Map<IJavaProject, Set<IResource>> enabledMaps = new HashMap<IJavaProject, Set<IResource>>(); // // public Set<IResource> getSet(IJavaProject jp) { // Set<IResource> set = enabledMaps.get(jp); // if (set == null) { // enabledMaps.put(jp, set = new HashSet<IResource>()); // } // return set; // } Set<IResource> getRacFiles(IJavaProject jp) { String encoded = PathItem.getEncodedPath(jp,Env.racKey); Set<IResource> items = new HashSet<IResource>(); for (PathItem item: PathItem.parseAll(encoded)) { if (item instanceof ProjectPath) { IResource r = jp.getProject().findMember(((ProjectPath)item).projectRelativeLocation); items.add(r); } } return items; } void setRacFiles(IJavaProject jp, Set<IResource> items) { List<PathItem> paths = new LinkedList<PathItem>(); for (IResource r: items) { paths.add(new ProjectPath(r.getProjectRelativePath().toString())); } String encoded = PathItem.concat(paths); PathItem.setEncodedPath(jp,Env.racKey,encoded); } public void racMark(boolean enable, ISelection selection, IWorkbenchWindow window, @Nullable Shell shell) { List<IResource> res = getSelectedResources(selection, window, shell); final Map<IJavaProject, List<IResource>> sorted = sortByProject(res); for (final IJavaProject jp : sorted.keySet()) { Set<IResource> items = getRacFiles(jp); for (IResource r : sorted.get(jp)) { mark(enable, r, items); } setRacFiles(jp,items); } } protected void mark(boolean enable, IResource r, Set<IResource> set) { try { if (r instanceof IFolder) { if (enable) set.add(r); else { set.remove(r); IPath p = new Path(getRacDir()).append(r.getProjectRelativePath()); p = p.removeFileExtension().addFileExtension(".class"); r.getProject().findMember(p).delete(true,null); } // for (IResource rr : ((IFolder) r).members()) { // mark(enable, rr, set); // } } else if (r instanceof IFile) { if (r.getName().endsWith(Strings.javaSuffix)) { if (enable) set.add(r); else { set.remove(r); IPath p = r.getProject().getProjectRelativePath().append(getRacDir()).append(r.getProjectRelativePath().removeFirstSegments(1)); p = p.removeFileExtension().addFileExtension("class"); IProject pr = r.getProject(); IResource rr = pr.findMember(p); if (rr != null) rr.delete(true,null); } } } else { // FIXME - needs an error message Log.log("Not handling " + r.getClass()); } } catch (CoreException e) { Log.errorlog( "Core Exception while traversing Resource tree (mark for RAC)", e); // just continue } } public void highlight(final IResource r, final int finalOffset, final int finalEnd, final int type) { // FIXME - I think this should be in the UI, not in a batch operation IWorkspaceRunnable runnable = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { String name = type == IProverResult.Span.NORMAL ? Env.JML_HIGHLIGHT_ID : type == IProverResult.Span.TRUE ? Env.JML_HIGHLIGHT_ID_TRUE : type == IProverResult.Span.FALSE ? Env.JML_HIGHLIGHT_ID_FALSE : type == IProverResult.Span.EXCEPTION ? Env.JML_HIGHLIGHT_ID_EXCEPTION : Env.JML_HIGHLIGHT_ID; IMarker marker = r.createMarker(name); // marker.setAttribute(IMarker.LINE_NUMBER, // finalLineNumber >= 1? finalLineNumber : 1); // if (column >= 0) { marker.setAttribute(IMarker.CHAR_START, finalOffset); marker.setAttribute(IMarker.CHAR_END, finalEnd); // } // Note - it appears that CHAR_START is measured from the // beginning of the // file and overrides the line number when drawing the squiggly // The line number is used in the information about the problem // in // the Problem View // marker.setAttribute(IMarker.SEVERITY,b == null ? 0 : b ? 2 : // 1); // marker.setAttribute(IMarker.MESSAGE, finalErrorMessage); } }; try { r.getWorkspace().run(runnable, r, IWorkspace.AVOID_UPDATE, null); } catch (CoreException e) { Log.errorlog("Core Exception while highlighting", e); // just continue } } public void racClear(ISelection selection, IWorkbenchWindow window, final @Nullable Shell shell) { Collection<IJavaProject> res = getSelectedProjects(true,selection, window, shell); for (final IJavaProject jp : res) { Job j = new Job("JML Clear RAC") { public IStatus run(IProgressMonitor monitor) { return racClear(jp,shell,monitor); } }; // FIXME - use some proper scheduling rule? j.setRule(jp.getProject()); j.setUser(true); j.schedule(); } } public IStatus racClear(IJavaProject jp, final @Nullable Shell shell, IProgressMonitor monitor) { boolean c = false; String racFolder = getRacDir(); IFolder dir = jp.getProject().getFolder(racFolder); StringBuilder sb = new StringBuilder(); try { for (IResource r : dir.members()) { try { r.delete(IResource.FORCE,monitor); } catch (Exception e) { sb.append(r.getName()); sb.append(" - "); sb.append(e.getMessage()); sb.append(Env.eol); } if (monitor != null && monitor.isCanceled()) { c = true; break; } } if (sb.length() != 0) { showMessageInUI(shell, "OpenJML Exception", sb.toString()); } } catch (CoreException e) { showMessageInUI(shell, "OpenJML Exception", e.getMessage()); } return c ? Status.CANCEL_STATUS : Status.OK_STATUS; } public void racChoose(ISelection selection, IWorkbenchWindow window, final @Nullable Shell shell) { Collection<IJavaProject> res = getSelectedProjects(true,selection, window, shell); for (IJavaProject jp : res) { Dialog d = new RACDialog(shell,"Select files in " + jp.getElementName() + " for Runtime Assertion Checking",jp); d.create(); d.open(); } } // protected void clear(IResource r, IFolder dir) { // try { // if (r instanceof IFolder) { // for (IResource rr : ((IFolder) r).members()) { // clear(rr, dir); // } // } else if (r instanceof IFile) { // if (r.getName().endsWith(".java")) { // ICompilationUnit cu = (ICompilationUnit) r // .getAdapter(ICompilationUnit.class); // if (cu != null) { // for (IType t : cu.getTypes()) { // String s = t.getFullyQualifiedName(); // s = s.replace('.', '/'); // s = s.substring(0, s.length() - (".java").length()); // s = s + ".class"; // IFile f = dir.getFile(s); // f.delete(IResource.FORCE, null); // } // } // } // } else { // if (Options.uiverboseness) // Log.log("Not handling " + r.getClass()); // } // } catch (CoreException e) { // Log.errorlog( // "Core Exception while traversing Resource tree (clear for RAC)", // e); // // just continue // } // } /** * Creates a map indexed by IJavaProject, with the value for each * IJavaProject being a Collection consisting of the subset of the argument * that belongs to the Java project. * * @param elements * The set of elements to sort * @return The resulting Map of IJavaProject to Collection */ /* * @ requires elements != null; requires elements.elementType <: IResource * || elements.elementType <: IJavaElement; ensures \result != null; */ public static @NonNull <T> Map<IJavaProject, List<T>> sortByProject(@NonNull Collection<T> elements) { Map<IJavaProject, List<T>> map = new HashMap<IJavaProject, List<T>>(); Iterator<T> i = elements.iterator(); while (i.hasNext()) { T o = i.next(); IJavaProject jp; if (o instanceof IResource) { jp = JavaCore.create(((IResource) o).getProject()); } else if (o instanceof IJavaElement) { jp = ((IJavaElement) o).getJavaProject(); } else { Log.errorlog( "INTERNAL ERROR: Unexpected content for a selection List - " + o.getClass(), null); continue; } if (jp != null && jp.exists()) addToMap(map, jp, o); } return map; } /** * If key is not a key in the map, it is added, with an empty Collection for * its value; then the given object is added to the Collection for that key. * * @param map * A map of key values to Collections * @param key * A key value to add to the map, if it is not already present * @param object * An item to add to the Collection for the given key */ private static <T> void addToMap(@NonNull Map<IJavaProject, List<T>> map, @NonNull IJavaProject key, @NonNull T object) { List<T> list = map.get(key); if (list == null) map.put(key, list = new LinkedList<T>()); list.add(object); } /** * Displays a message in a dialog in the UI thread - this may be called from * other threads. * * @param shell * The shell to use to display the dialog, or a top-level shell * if the parameter is null * @param title * The title of the dialog window * @param msg * The message to display in the dialog */ public void showMessageInUI(@Nullable Shell shell, @NonNull final String title, @NonNull final String msg) { final Shell fshell = shell; Display d = fshell == null ? Display.getDefault() : fshell.getDisplay(); d.asyncExec(new Runnable() { public void run() { MessageDialog.openInformation(fshell, title, msg); } }); } /** Pops up a dialog showing the given message and thte stack trace of the * given exception; may be called from the computational thread. */ public void showExceptionInUI(@Nullable Shell shell, String message, Throwable e) { String emsg = e == null ? null : e.getMessage(); if (emsg != null && !emsg.isEmpty()) message = message + " (" + emsg + ")"; //$NON-NLS-1$ //$NON-NLS-2$ if (e != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println(); e.printStackTrace(pw); message = message + Env.eol + sw.toString(); } showMessageInUI(shell, "OpenJML Exception", message); } // FIXME - fix non-modal dialog /** * Displays a message in a non-modal dialog in the UI thread - this may be * called from other threads. * * @param shell * The shell to use to display the dialog, or a top-level shell * if the parameter is null * @param title * The title of the dialog window * @param msg * The message to display in the dialog */ public void showMessageInUINM(@Nullable Shell shell, @NonNull final String title, @NonNull final String msg) { final Shell fshell = shell; Display d = fshell == null ? Display.getDefault() : fshell.getDisplay(); d.asyncExec(new Runnable() { public void run() { Dialog d = new NonModalDialog(fshell, title, msg); d.open(); } }); } // FIXME this does not seem to be working static public class NonModalDialog extends MessageDialog { final static String[] buttons = { "OK" }; public NonModalDialog(Shell shell, String title, String message) { super(new Shell(), title, null, message, INFORMATION, buttons, 0); setShellStyle(getShellStyle() | SWT.MODELESS); setBlockOnOpen(false); } } public final static QualifiedName SPECSPATH_ID = new QualifiedName( Env.PLUGIN_ID, "specspath"); public final static QualifiedName SOURCEPATH_ID = new QualifiedName( Env.PLUGIN_ID, "sourcepath"); /** * Displays a message in a information dialog; must be called from the UI * thread. * * @param shell * Either the parent shell * @param title * A title for the dialog window * @param msg * The message to display in the dialog window */ // @ requires shell != null; // @ requires title != null; // @ requires msg != null; static public void showMessage(Shell shell, String title, String msg) { MessageDialog.openInformation(shell, title, msg); } // FIXME -document /** * Shows a dialog regarding an exception that has been thrown; this must be * called within the UI thread. */ public void topLevelException(Shell shell, String title, Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); String s = "Please report this internal bug, along with information about the context that caused the problem:" + Env.eol + Env.eol + sw.toString(); final int maxlength = 2000; // Just a limit to keep from overfilling a // dialog box if (s.length() > maxlength) { s = s.substring(0, maxlength) + " ..."; } showMessage(shell, "JML Top-level Exception: " + title, s); } /** The suffixes allowed for JML files. */ static public final String[] suffixes = { ".jml", ".java" }; /** * These constants should be the same as in org.jmlspecs.openjml.Utils, but * we don't reference them directly to avoid a dependency (in case we reuse * this plugin) */ static public final int QUIET = 0; static public final int NORMAL = 1; static public final int PROGRESS = 2; static public final int VERBOSE = 3; static public final int DEBUG = 4; public int openjmlVerbose() { try { return Integer.valueOf(Options.value(Options.verbosityKey)); } catch (NumberFormatException e) { return NORMAL; } } /** * This method returns an int giving the precedence of the suffix of the * file name: -1 indicates not a JML file; 0 is the preferred suffix; * increasing positive numbers indicate decreasing precedence of suffixes. * * @param name * the file name to be assessed * @return the precedence of the suffix (0 highest, more positive lower, -1 * is not JML) */ @Pure static public int suffixOK(/* @ non_null */String name) { int i = 0; for (String s : suffixes) { if (name.endsWith(s)) return i; i++; } return -1; } public String getRacDir() { String racdir = Options.value(Options.racbinKey); if (racdir == null || racdir.isEmpty()) racdir = "bin"; return racdir; } /** Deletes the contents of the RAC binary directory (at the location defined in the * options) of the given project and refreshing it * in the workspace. All done in the UI thread. * @param p the project whose RAC directory is to be cleaned */ // FIXME - should this be done in a computational thread, with a progress monitor? public void cleanRacbin(final IProject project) { Job job = new Job("Clean") { //$NON-NLS-1$ public IStatus run(IProgressMonitor monitor) { try { // FIXME - should we be using the preferences or the option? String racbin = Activator.utils().getRacDir(); IResource rf = project.findMember(Options.value(Options.racbinKey)); if (rf instanceof IFolder) { IFolder f = (IFolder)rf; for (IResource r: f.members()) { r.delete(IResource.FORCE,null); } f.refreshLocal(IResource.DEPTH_INFINITE,null); } else { // FIXME - error - show message } } catch (CoreException e) { Log.errorKey("openjml.ui.cleaning.rac",e); //$NON-NLS-1$ } return Status.OK_STATUS; } }; job.setUser(true); job.setRule(project); job.schedule(); } public static final String RUNTIME_LIBRARY = Strings.runtimeJarName; /** Makes a classpath entry corresponding to the jmlruntime.jar library */ public IClasspathEntry makeRuntimeLibEntry() { String envname = ClasspathVariableInitializer.OPENJML_RUNTIME_LIBRARY; IPath p = new Path(envname); IClasspathEntry libe = JavaCore.newVariableEntry(p,null,null); return libe; } /** Returns the value set against the runtime library classpath entry */ public String fetchRuntimeLibEntry() { IPath p = JavaCore.getResolvedVariablePath(new Path(ClasspathVariableInitializer.OPENJML_RUNTIME_LIBRARY)); String runtime = p == null ? null : p.toString(); return runtime; } public int countMethods(IJavaProject jp) throws Exception { int count = 0; for (IPackageFragmentRoot pfr: jp.getPackageFragmentRoots()) { count += countMethods(pfr); } return count; } public int countMethods(IProject jp) throws Exception { return countMethods(JavaCore.create(jp)); } public int countMethods(IPackageFragmentRoot pfr) throws Exception { int count = 0; for (IJavaElement pf: pfr.getChildren()) { if (pf instanceof IPackageFragment) { count += countMethods((IPackageFragment)pf); } } return count; } public int countMethods(IPackageFragment pf) throws Exception { int count = 0; for (ICompilationUnit cu: pf.getCompilationUnits()) { // getCompilationUnits() returns .jml and maybe other files as well if (cu.getPath().toString().endsWith(".java")) { count += countMethods(cu); } } return count; } public int countMethods(ICompilationUnit cu) throws Exception { int count = 1; // For the file for (IType t: cu.getTypes()) { count += countMethods(t); } return count; } public int countMethods(IResource f) throws Exception { int count = 0; if (f instanceof IFolder) { for (IResource r: ((IFolder)f).members()) { count += countMethods(r); } } else if (f instanceof IFile) { ICompilationUnit cu = (ICompilationUnit) f.getAdapter(ICompilationUnit.class); if (cu != null) count += countMethods(cu); } else { Log.log("Can't count a " + f.getClass()); } return count; } public int countMethods(IType t) throws Exception { // FIXME - this does not count methods in anonymous or local types int count = 0; IType[] types = t.getTypes(); for (IType tt: types) { count += countMethods(tt); } IMethod[] methods = t.getMethods(); boolean hasDeclaredConstructor = false; for (IMethod m : methods) { if (m.isConstructor()) { hasDeclaredConstructor = true; break; } } if (!hasDeclaredConstructor) count++; return methods.length + count; } public int countMethods(IMethod m) throws Exception { return 1; } public int countMethods(IJavaElement f) { Log.log("Can't count a " + f.getClass()); return 0; } // FIXME - have not been able to get this to work as I would like public static class ModelessDialog extends Dialog { public ModelessDialog(Shell parentShell) { super(parentShell); setBlockOnOpen(false); } protected void setShellStyle(int newShellStyle) { int newstyle = newShellStyle & ~SWT.APPLICATION_MODAL; /* * turn off * APPLICATION_MODAL */ newstyle |= SWT.MODELESS; /* turn on MODELESS */ super.setShellStyle(newstyle); } public int open() { int retVal = super.open(); pumpMessages(); /* * this will let the caller wait till OK, Cancel is * pressed, but will let the other GUI responsive */ return retVal; } protected void pumpMessages() { Shell sh = getShell(); Display disp = sh.getDisplay(); while (!sh.isDisposed()) { if (!disp.readAndDispatch()) disp.sleep(); } disp.update(); } } }