/* * This file is part of the OpenJML plugin project. * Copyright (c) 2006-2013 David R. Cok */ package org.jmlspecs.openjml.eclipse; import java.io.PrintStream; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceRuleFactory; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IProblemRequestor; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.jmlspecs.annotation.Nullable; import org.jmlspecs.annotation.SpecPublic; import org.jmlspecs.openjml.Strings; import com.sun.tools.javac.code.Symbol; // TODO - The plugin tool reports problems by directly calling an instance // of this IProblemRequestor. The Eclipse framework actually uses // a combination of a ProblemReporter to which problems are reported // and requestors that can subscribe to problem reports. Perhaps we // should correct to that model. /** * This is an implementation of the Eclipse IProblemRequestor * interface as used in this application. Upon receiving problem * reports (by calls of acceptProblem), it creates and records an Eclipse * problem marker specific to this plugin. */ /* * It also adds this bit of functionality - the ability to provide a mapping from * IFile paths to working copies so that the right source text * can be associated with a given problem message without having * to be sure that potential changes are written to disk. * TODO - this is not used at present, but is left in for now in case we * implement this later. */ public class JmlProblemRequestor implements IProblemRequestor { /** * This is a map of filename to compilation unit. * The filename is gotten from IResource.getFullPath().toString(). * The compilation unit is typically a working copy, not necessarily * saved to disk. Its text information can be obtained from * getSource() after reconciliation. */ private Map<String,ICompilationUnit> map = null; /** The Eclipse project associated with this problem requestor */ @SpecPublic @Nullable protected IProject project; /** * Returns the ICompilationUnit corresponding to the given path, * or null if none found * @param p the path used as a key * @return the associated ICompilationUnit */ public @Nullable ICompilationUnit getICU(String p) { return map == null ? null : map.get(p); } /** Creates a problem requestor for a particular java project. */ public JmlProblemRequestor(@Nullable IJavaProject jp) { this.project = jp == null ? null : jp.getProject(); } /** Eclipse calls this when a problem is reported * @param p the reported problem * @see org.eclipse.jdt.core.IProblemRequestor#acceptProblem(org.eclipse.jdt.core.compiler.IProblem) */ public void acceptProblem(/*@ non_null */ IProblem p) { // JML checking produces CompilerProblems (in OpenJMLInterface); // Eclipse produces other kinds of problems; // Eclipse problems are already reported, so don't report them // over again if JML encounters it as part of getting the Java AST. // Except: We swallow silently two errors - that a method requires a // body and uninitialized blank final fields, both of which occur in // specification files by design. if (!(p instanceof JmlEclipseProblem)) { int id = p.getID(); if (!(id == IProblem.MethodRequiresBody || id == IProblem.UninitializedBlankFinalField)) return; String s = new String(p.getOriginatingFileName()); if (s.endsWith(Utils.dotJava)) return; // FIXME - we are not actually doing anything. SHould we? // Log.log("JAVA Problem: " + id + " " + new String(p.getOriginatingFileName()) + " " + p.getMessage()); // p.delete(); return; } if (p.getID() == 0 && p.getMessage().contains("is public, should be declared in a file named") && String.valueOf(p.getOriginatingFileName()).endsWith(Utils.dotJML)) { return; } // Ignore warnings if level is 2 if (p.isWarning() && level == 2) return; try { IResource f = null; char[] ch = p.getOriginatingFileName(); IWorkspace w = ResourcesPlugin.getWorkspace(); IWorkspaceRoot root = w.getRoot(); if (ch != null) { Path path = new Path(new String(ch)); f = root.findMember(path); } else { // No originating file name, so use the project or the workspace root f = project != null ? project : root; } // If this is a parsing problem, there will be no symbol Symbol sym = ((JmlEclipseProblem)p).sourceSymbol; // Use the following if you want problems printed to the console // as well as producing markers and annotations if (Options.uiverboseness) { JmlEclipseProblem.printProblem(new PrintStream(Log.log.listener().getStream()), p); } JmlEclipseProblem jmlproblem = (JmlEclipseProblem)p; final IResource r = f; final int finalLineNumber = p.getSourceLineNumber(); final int column = p.getSourceStart(); // FIXME - review 0-1-based: documentation says both getSourceStart() // and lineStart are 1-based which would make this computation off by one final int finalOffset = p.getSourceStart() + jmlproblem.lineStart; final int finalEnd = p.getSourceEnd() + 1 + jmlproblem.lineStart; String source = ""; if (sym != null) source = sym.owner.toString() + "." + sym.toString() + ": " + Strings.eol; final String finalErrorMessage = source + p.getMessage(); int severity = jmlproblem.severity; if (jmlproblem.lineStart < 0) { Log.log(p.getMessage()); if (Options.isOption(Options.showErrorPopupsKey)) { Activator.utils().showMessageInUI(null,"OpenJML ESC Error",finalErrorMessage); } } // FIXME - this looks like a hack - at least explain final boolean staticCheckWarning = // The 64 is ProblemSeverities.SecondaryError, which has discouraged access severity == 64 || finalErrorMessage.contains("The prover") || finalErrorMessage.contains("Associated declaration"); // The OpenJML note is translated to ProblemSeverities.Ignore, for // lack of a better mapping. The p.isWarning() includes the Ignore // category - so we can't use it directly. Actually(), p.isError() // does not include all error categories (TODO - clean this up?) final int finalSeverity = staticCheckWarning ? IMarker.SEVERITY_WARNING : p.isError() ? IMarker.SEVERITY_ERROR : severity == ProblemSeverities.Ignore ? IMarker.SEVERITY_INFO : severity > 0 ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING ; // Eclipse recommends that things that modify the resources // in a workspace be performed in a IWorkspaceRunnable IWorkspaceRunnable runnable = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { IMarker marker = r.createMarker( staticCheckWarning ? Env.ESC_MARKER_ID : Env.JML_MARKER_ID); marker.setAttribute(IMarker.LINE_NUMBER, finalLineNumber >= 1? finalLineNumber : 1); if (column >= 0) { marker.setAttribute(IMarker.CHAR_START, finalOffset); marker.setAttribute(IMarker.CHAR_END, finalEnd); // One beyond the last highlighted } // 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 // Note - if the marked characters are the line termination characters, // no highlighting happens, though the marker still appears as a problem. marker.setAttribute(IMarker.SEVERITY,finalSeverity); marker.setAttribute(IMarker.MESSAGE, finalErrorMessage); } }; IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory(); ISchedulingRule rule = ruleFactory.markerRule(r); r.getWorkspace().run(runnable, rule, IWorkspace.AVOID_UPDATE, (IProgressMonitor)null); } catch (Exception e) { Log.errorKey("openjml.ui.failed.to.create.marker",e); //$NON-NLS-1$ } } /** What to accept: level == 2 ==> errors only; level == 1; errors and * warnings */ protected int level = 1; /** What to accept: level == 2 ==> errors only; level == 1; errors and * warnings * @param level whether to print warnings and info messages */ public void setLevel(int level) { this.level = level; } /** This implementation does nothing in this call - all reported problems * are handled. * @see org.eclipse.jdt.core.IProblemRequestor#beginReporting() */ public void beginReporting() { // do nothing } /** This implementation does nothing in this call - all reported problems * are handled. * @see org.eclipse.jdt.core.IProblemRequestor#endReporting() */ public void endReporting() { // do nothing } /** This implementation is always active * @return always returns true * @see org.eclipse.jdt.core.IProblemRequestor#isActive() */ public boolean isActive() { return true; } }