/*
* This file is part of the OpenJML plugin project.
* Copyright (c) 2006-2013 David R. Cok
* @author David R. Cok
*/
package org.jmlspecs.openjml.eclipse;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.jmlspecs.annotation.NonNull;
import org.jmlspecs.annotation.Nullable;
import org.jmlspecs.openjml.Main;
import org.jmlspecs.openjml.Main.Cmd;
// FIXME - we need to handle dependencies when doing incremental compilation
// FIXME - needs review - JMLBuilder
// FIXME - if there are multiple projects being run, they need to
// be run in the right order
// FIXME - review how timers are used
/** This class implements a builder for JML tools. The builder is
* run as part of the compilation cycle and appears in the list of
* builders under project properties. It is enabled when the project
* has a JML Nature.
*/
public class JMLBuilder extends IncrementalProjectBuilder {
/** This class is used to walk the tree of incremental changes */
static class DeltaVisitor implements IResourceDeltaVisitor {
/** Local variable to store the resources to be built. This list is
* accumulated while walking the tree, and then the JML tools are activated
* on the entire list at once.
*/
private @NonNull List<IResource> resourcesToBuild = new LinkedList<IResource>();
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit(@NonNull IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
//Log.log("Visiting (delta) " + resource);
switch (delta.getKind()) {
case IResourceDelta.ADDED:
// handle added resource
accumulate(resourcesToBuild,resource);
break;
case IResourceDelta.REMOVED:
// handle removed resource
break;
case IResourceDelta.CHANGED:
// handle changed resource
accumulate(resourcesToBuild,resource);
break;
}
//return true to continue visiting children.
return true;
}
}
/** This class is used to walk the tree of full build changes; collects all
* files, recursively; does not delete markers. It ignores directories
* because the contents of the directory are automatically walked. */
static class ResourceVisitor implements IResourceVisitor {
/** Local variable to store the resources to be built. This list is
* accumulated while walking the tree, and then the JML tools are activated
* on the entire list at once.
*/
public @NonNull List<IResource> resourcesToBuild = new LinkedList<IResource>();
public boolean visit(@NonNull IResource resource) {
accumulate(resourcesToBuild,resource);
//return true to continue visiting children.
return true;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.internal.events.InternalBuilder#build(int,
* java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
*/
// The return value is used to indicate which other projects this project
// would like delta data for the next time the builder is called. A null value
// for nothing other than one's own project is OK.
@Override @Nullable
protected IProject[] build(int kind, @Nullable Map<String,String> args, /*@ nullable */ IProgressMonitor monitor)
throws CoreException {
// kind can be
// FULL_BUILD - e.g. an automatic build after a request to clean
// or a manual request for a build after a manual clean
// AUTO_BUILD - e.g. after a edit and save with auto building on
// INCREMENTAL_BUILD - e.g. after an edit and save with auto building off,
// and then a manual request to build
// CLEAN_BUILD - does not call this method (calls clean(IProgressMonitor) instead)
if (kind == FULL_BUILD || kind == CLEAN_BUILD) {
fullBuild(monitor);
} else { // INCREMENTAL_BUILD, as long as we have incremental information
// AUTO_BUILD also (requires good dependency information)
IResourceDelta delta = getDelta(getProject());
if (delta == null) { // no delta available, do a full build
fullBuild(monitor);
} else {
incrementalBuild(delta, monitor);
}
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.core.resources.IncrementalProjectBuilder#clean(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected void clean(/*@ nullable */ IProgressMonitor monitor) throws CoreException {
if (Options.uiverboseness) Log.log("Cleaning: " + getProject().getName()); //$NON-NLS-1$
IProject p = getProject();
Activator.utils().deleteMarkers(p,null);
Activator.utils().cleanRacbin(p);
}
/** Called during tree walking; it records the java files visited.
* @param resourcesToBuild the list accumulated so far
* @param resource the resource visited
*/
static void accumulate(List<IResource> resourcesToBuild, IResource resource) {
if (resource instanceof IFile) {
String name = resource.getName();
if (Utils.suffixOK(name) >= 0) {
IFile file = (IFile) resource;
resourcesToBuild.add(file);
} else if (".classpath".equals(name)) { //$NON-NLS-1$
resourcesToBuild.add(resource.getProject());
}
}
}
/** Perform actions on all identified items - called in UI thread
* @param jproject the Java project all the resources are in
* @param resourcesToBuild the resources to build
* @param monitor the monitor to record progress and cancellation
* @param full true if this is a full build, false if incremental
*/
protected static void doAction(final IJavaProject jproject, final List<IResource> resourcesToBuild, IProgressMonitor monitor, boolean full) {
// We've already checked that this is a Java and a JML project
// Also all the resources should be from this project, because the
// builders work project by project
// These operations create new compilation contexts. Presumably this is
// what is desired because either we are doing a full build from
// scratch or we are doing an incremental build in response to a
// file save. In the latter case we will eventually want incremental
// compilation, but since we don't have it yet, we have to do a new
// compilation context.
// Make a copy of the list because the list is used on a separate thread and
// might be cleared before it is finished processing.
List<IResource> list = new LinkedList<IResource>();
list.addAll(resourcesToBuild);
boolean done = false;
if (Options.isOption(Options.enableRacKey)) {
Activator.utils().racMarked(jproject);
done = true;
}
if (Options.isOption(Options.enableESCKey)) {
// FIXME - use monitor, be incremental?
Activator.utils().checkESCProject(jproject,list,null,"Static Checks - Auto"); //$NON-NLS-1$
done = true;
}
if (!done) {
// If we did not already type-check because of RAC or ESC, do it now
Activator.utils().getInterface(jproject).executeExternalCommand(Main.Cmd.CHECK,resourcesToBuild, monitor,true);
// Job j = new Job("OpenJML Auto Build") {
// public IStatus run(IProgressMonitor monitor) {
// monitor.beginTask("Static checking of " + jproject.getElementName(), 1);
// boolean c = false;
// try {
// Activator.utils().getInterface(jproject).executeExternalCommand(Main.Cmd.CHECK,resourcesToBuild, monitor,true);
// } catch (Exception e) {
// // FIXME - this will block, preventing progress on the rest of the projects
// Log.errorlog("Exception during Static Checking - " + jproject.getElementName(), e);
// Activator.utils().showExceptionInUI(null, "Exception during Static Checking - " + jproject.getElementName(), e);
// c = true;
// }
// return c ? Status.CANCEL_STATUS : Status.OK_STATUS;
// }
// };
// IResourceRuleFactory ruleFactory =
// ResourcesPlugin.getWorkspace().getRuleFactory();
// j.setRule(jproject.getProject());
// j.setUser(true); // true since the job has been initiated by an end-user
// j.schedule();
}
}
/** Called when a full build is requested on the current project.
* @param monitor the progress monitor to use
* @throws CoreException if something is wrong with the Eclipse resources
*/
protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
IJavaProject jproject = JavaCore.create(project);
if (jproject == null || !jproject.exists()) {
// It should not be possible to call the builder on a non-Java project.
Log.errorKey("openjml.ui.building.non.java.project",null,project.getName()); //$NON-NLS-1$
return;
}
if (Options.uiverboseness) Log.log("Full build " + project.getName()); //$NON-NLS-1$
Timer.timer.markTime();
Activator.utils().deleteMarkers(project,null);
if (monitor.isCanceled() || isInterrupted()) {
if (Options.uiverboseness) Log.log("Build interrupted"); //$NON-NLS-1$
return;
}
ResourceVisitor v = new ResourceVisitor();
for (IPackageFragmentRoot root : jproject.getAllPackageFragmentRoots()) {
IResource res = root.getCorrespondingResource();
if (res != null) res.accept(v);
}
//project.accept(v); - Calling accept on the project includes all of the files not on the source path - we don't want that
// Don't clear if the rac directory is the bin directory
String rd = Activator.utils().getRacDir();
IPath rdd = jproject.getProject().findMember(rd).getFullPath(); // relative to workspace
IPath output = jproject.getOutputLocation(); // also relative to workspace
if (!rdd.equals(output)) {
Activator.utils().racClear(jproject,null,monitor);
}
doAction(jproject,v.resourcesToBuild,monitor,true);
v.resourcesToBuild.clear();
if (Options.uiverboseness) Log.log(Timer.timer.getTimeString() + " Build complete " + project.getName()); //$NON-NLS-1$
}
/** Called when an incremental build is requested.
* @param delta the system supplied tree of changes
* @param monitor the progress monitor to use
* @throws CoreException if something is wrong with the Eclipse resources
*/
protected void incrementalBuild(IResourceDelta delta,
IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
IJavaProject jproject = JavaCore.create(project);
if (jproject == null || !jproject.exists()) {
// It should not be possible to call the builder on a non-Java project.
Log.errorKey("openjml.ui.building.non.java.project",null,project.getName()); //$NON-NLS-1$
return;
}
if (Options.uiverboseness) Log.log("Incremental build " + project.getName()); //$NON-NLS-1$
Timer.timer.markTime();
DeltaVisitor v = new DeltaVisitor();
delta.accept(v); // collects all changed files
Activator.utils().deleteMarkers(v.resourcesToBuild,null);
doAction(jproject,v.resourcesToBuild,monitor,false);
v.resourcesToBuild.clear(); // Empties the list
if (Options.uiverboseness) Log.log(Timer.timer.getTimeString() + " Build complete " + project.getName()); //$NON-NLS-1$
}
}