package net.sourceforge.floggy.eclipse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
/**
* Decides if a build is really needed.
* <p>
* This is not as trivial as it might seem as eclipse automatic builds have a
* habit of seemingly happening without any good reason (e.g. somtimes it can
* just happen even if you don't change anything)
* <p>
* This has resulted in infinite loops in the past (when using Pulsar / MTJ).
* This class assesses the resources which have changed and make a more informed
* decision.
*
* @author <a href="mailto:dan.murphy@floggy.org">Dan Murphy</a>
*
*/
public class BuildRequestAssessor {
private static final Log LOG = LogFactory.getLog(BuildRequestAssessor.class);
private static final String MJT_TEMP_DIR = ".mtj.tmp/";
private static final String CLASS_SUFFIX = ".class";
private IJavaProject javaProject;
/**
* Determines if a build of the project is required. If we're being asked be
* involved in a {@link IncrementalProjectBuilder.FULL_BUILD} or a
* {@link IncrementalProjectBuilder.CLEAN_BUILD} then a build is required.
* <p>
* Things are a little more complex in the case of an
* {@link IncrementalProjectBuilder.INCREMENTAL_BUILD} or a
* {@link IncrementalProjectBuilder.AUTO_BUILD}, in which case it is
* necessary to look at the resource delta (what has changed since the last
* build) in order to decide if a Floggy build is necessary. The main reason
* for this is that Mobile Java Tools or Pulsar can, when in combination
* with Floggy, result in an infinite build loop because both believe that a
* change made by the other builder means a build is required.
*
* @param project
* @param buildKind
* what type of build has been requested
* {@link IncrementalProjectBuilder}
* @param resouceDelta
* if null then it is assumed that the changes cannot be
* determined and so a build is needed
* @return whether a build is required
* @throws CoreException
* if something goes badly wrong when determining what natures
* the given project has
*/
public boolean isBuildRequired(IProject project, int buildKind,
IResourceDelta resouceDelta) throws CoreException {
boolean result = false;
if (!project.hasNature(JavaCore.NATURE_ID)
|| !project.hasNature(FloggyNature.NATURE_ID)) {
LOG.warn(project.getName() + " isn't a java floggy project");
return false;
}
switch (buildKind) {
case IncrementalProjectBuilder.INCREMENTAL_BUILD:
LOG.debug("Incremental build of " + resouceDelta);
break;
case IncrementalProjectBuilder.AUTO_BUILD:
LOG.debug("Automatic build of " + resouceDelta);
break;
case IncrementalProjectBuilder.FULL_BUILD:
LOG.debug("Full build invoked");
result = true;
break;
case IncrementalProjectBuilder.CLEAN_BUILD:
LOG.debug("Clean build invoked");
result = true;
break;
default:
return false;
}
if (resouceDelta == null) {
LOG.debug("Cannot determine which resources have changed");
result = true;
} else if (result == false) {
if (javaProject == null) {
javaProject = JavaCore.create(project);
}
logDelta(resouceDelta);
result = isBuildRequired(project, resouceDelta);
}
LOG.info((result ? "Floggy build required"
: "Floggy build not required"));
return result;
}
/**
* Determines if, given a resource delta a build is required.
* <p>
* By now we only care if the resource delta contains a .class file that is
* not in the MTJ temporary directory (MJT_TEMP_DIR) as this is where it
* places pre-verified classes.
*
* @param delta
* @return
* @throws CoreException
*/
private boolean isBuildRequired(IProject project, IResourceDelta delta)
throws CoreException {
boolean buildNeeded = false;
IResourceDelta[] changes = delta.getAffectedChildren();
IResource resource;
String pathAsString;
for (int i = 0; i < changes.length; i++) {
resource = changes[i].getResource();
if (resource.getType() == IResource.FILE) {
pathAsString = resource.getProjectRelativePath()
.toPortableString();
if (pathAsString.endsWith(CLASS_SUFFIX)
&& !pathAsString.startsWith(MJT_TEMP_DIR)) {
// currently don't look any deeper than this, in future will see if
// the class extends a persistable... if we can get this to work
// by calling doesClassExtendPersistable(resource);
buildNeeded = true;
if (buildNeeded) {
LOG.debug("Build required due to change of "
+ resource.getFullPath().toPortableString());
break;
}
}
} else if (resource.getType() == IResource.FOLDER) {
buildNeeded = isBuildRequired(project, changes[i]);
if (buildNeeded) {
break;
}
}
}
return buildNeeded;
}
private boolean doesClassExtendPersistable(IResource resource) throws JavaModelException {
boolean result = true;
IJavaElement javaElement = JavaCore.create(resource);
IPath outputLocation = javaProject.getOutputLocation();
if (javaElement.getElementType() == IJavaElement.CLASS_FILE) {
IClassFile classFile = (IClassFile) javaElement;
IType roType = classFile.getType();
String className = roType.getFullyQualifiedName();
IPackageFragment fragment = roType.getPackageFragment();
System.out.println();
//javaProject.findType(classFile.getElementName());
System.out.println();
}
return result;
}
private void logDelta(IResourceDelta projectDelta) {
StringBuffer buffer = new StringBuffer();
logDelta(projectDelta, buffer);
LOG.debug(buffer.toString());
}
private void logDelta(IResourceDelta delta, StringBuffer buf) {
if (delta != null) {
int flags = delta.getFlags();
if (flags != 0) {
appendToBuffer(buf, delta);
}
IResourceDelta[] children = delta.getAffectedChildren();
if (children != null) {
for (int i = 0; i < children.length; i++) {
logDelta(children[i], buf);
}
}
}
}
private void appendToBuffer(StringBuffer buf, IResourceDelta delta) {
buf.append("Resource: " + delta.getResource().getFullPath());
buf.append(' ');
appendType(buf, delta.getResource().getType());
buf.append(" ( ");
appendKind(buf, delta.getKind());
buf.append(' ');
appendFlags(buf, delta.getFlags());
buf.append(")\n");
}
private void appendType(StringBuffer buf, int type) {
buf.append("Type: ");
switch (type) {
case IResource.FILE:
buf.append("file");
break;
case IResource.FOLDER:
buf.append("folder");
break;
case IResource.PROJECT:
buf.append("project");
break;
case IResource.ROOT:
buf.append("root");
break;
}
}
private void appendKind(StringBuffer buf, int kind) {
buf.append("delta kind: ");
switch (kind) {
case IResourceDelta.ADDED:
buf.append("added");
break;
case IResourceDelta.CHANGED:
buf.append("changed");
break;
case IResourceDelta.REMOVED:
buf.append("removed");
break;
case IResourceDelta.ADDED_PHANTOM:
buf.append("added phantom");
break;
case IResourceDelta.REMOVED_PHANTOM:
buf.append("removed phantom");
break;
default:
break;
}
}
private void appendFlags(StringBuffer buf, int flags) {
buf.append("flags: ");
if ((flags & IResourceDelta.CONTENT) != 0)
buf.append("content ");
if ((flags & IResourceDelta.ENCODING) != 0)
buf.append("encoding ");
if ((flags & IResourceDelta.DESCRIPTION) != 0)
buf.append("description ");
if ((flags & IResourceDelta.OPEN) != 0)
buf.append("open ");
if ((flags & IResourceDelta.SYNC) != 0)
buf.append("sync ");
if ((flags & IResourceDelta.MARKERS) != 0)
buf.append("markers ");
if ((flags & IResourceDelta.REPLACED) != 0)
buf.append("replaced ");
if ((flags & IResourceDelta.MOVED_TO) != 0)
buf.append("moved_to ");
if ((flags & IResourceDelta.MOVED_FROM) != 0)
buf.append("moved_from ");
if ((flags & IResourceDelta.COPIED_FROM) != 0)
buf.append("copied_from ");
}
}