/*******************************************************************************
* Copyright (c) 2009 Anyware Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Gaetan Morice (Anyware Technologies) - initial implementation
* Laurent Petit (ccw) - adaptation to ccw
*******************************************************************************/
package ccw.builder;
import java.util.ArrayList;
import java.util.HashMap;
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.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import ccw.CCWPlugin;
import ccw.launching.ClojureLaunchDelegate;
import ccw.repl.REPLView;
/*
* gaetan.morice:
* "
* Note that this code is just a prototype and there is still lots of problems to fix. Among then :
* Incremental build : I only implement full build.
* Synchronization with JDT build : as clojure and java files could depend on each others, the two builders
* need to be launch several time to resolve all the dependencies.
*/
public class ClojureBuilder extends IncrementalProjectBuilder {
public static final String CLOJURE_COMPILER_PROBLEM_MARKER_TYPE = "ccw.markers.problemmarkers.compilation";
static public final String BUILDER_ID = "ccw.builder";
@SuppressWarnings("unchecked")
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
if (!CCWPlugin.isAutoReloadOnStartupSaveEnabled()) {
return null;
}
// System.out.println("clojure build required");
if (getProject()==null) {
return null;
}
if (kind == AUTO_BUILD || kind == INCREMENTAL_BUILD) {
if (onlyClassesOrOutputFolderRelatedDelta() && !onlyProjectTouched() ) {
// System.out.println("nothing to build (onlyClassesOrOutputFolderRelatedDelta()=" + onlyClassesOrOutputFolderRelatedDelta()
// + ", !onlyProjectTouched()=" + !onlyProjectTouched());
return null;
}
}
fullBuild(getProject(), monitor);
return null;
}
/** Only project touch is treated similarly to a full build request */
private boolean onlyProjectTouched() {
IResourceDelta delta = getDelta(getProject());
return delta.getResource().equals(getProject()) && delta.getAffectedChildren().length == 0;
}
private boolean onlyClassesOrOutputFolderRelatedDelta() throws CoreException {
if (getDelta(getProject()) == null) {
return false;
}
List<IPath> folders = new ArrayList<IPath>();
folders.add(getClassesFolder(getProject()).getFullPath());
for (IFolder outputPath: getSrcFolders(getProject()).values()) {
folders.add(outputPath.getFullPath());
}
folders.add(getProject().getFolder(".settings").getFullPath());
delta_loop: for (IResourceDelta d: getDelta(getProject()).getAffectedChildren()) {
for (IPath folder: folders) {
if (folder.isPrefixOf(d.getFullPath())) {
continue delta_loop;
}
}
// System.out.println("affected children for build:" + d.getFullPath());
return false;
}
return true;
}
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) {
// TODO Auto-generated method stub
}
public static void fullBuild(IProject project, IProgressMonitor monitor) throws CoreException{
if(monitor == null) {
monitor = new NullProgressMonitor();
}
createClassesFolder(project, new SubProgressMonitor(monitor, 0));
// Issue #203 is probably related to the following way of getting a REPLView.
// A race condition between the builder and the Eclipse machinery creating the views, etc.
// We will probably have to refactor stuff to separate things a little bit more, but for the time
// being, as an experiment and hopefully a temporary patch for the problem, I'll just add a little bit
// delay in the build:
// TODO see if we can do something more clever than having to use the UI thread and get a View object ...
REPLView repl = CCWPlugin.getDefault().getProjectREPL(project);
if (repl == null) {
// Another chance, to fight with a hack, temporarily at least, the race condition
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
repl = CCWPlugin.getDefault().getProjectREPL(project);
}
if (repl == null || repl.isDisposed() || !ClojureLaunchDelegate.isAutoReloadEnabled(repl.getLaunch())) {
// System.out.println("REPL not found, or disposed, or autoReloadEnabled not found on launch configuration");
return;
}
deleteMarkers(project);
ClojureVisitor visitor = new ClojureVisitor(repl.getSafeToolingConnection());
visitor.visit(getSrcFolders(project));
getClassesFolder(project).refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 0));
}
private static IFolder getClassesFolder(IProject project) {
return project.getFolder("classes");
}
private static Map<IFolder, IFolder> getSrcFolders(IProject project) throws CoreException {
Map<IFolder, IFolder> srcFolders = new HashMap<IFolder, IFolder>();
IJavaProject jProject = JavaCore.create(project);
IClasspathEntry[] entries = jProject.getResolvedClasspath(true);
IPath defaultOutputFolder = jProject.getOutputLocation();
for(IClasspathEntry entry : entries){
switch (entry.getEntryKind()) {
case IClasspathEntry.CPE_SOURCE:
IFolder folder = project.getWorkspace().getRoot().getFolder(entry.getPath());
IFolder outputFolder = project.getWorkspace().getRoot().getFolder(
(entry.getOutputLocation()==null) ? defaultOutputFolder : entry.getOutputLocation());
if (folder.exists())
srcFolders.put(folder, outputFolder);
break;
case IClasspathEntry.CPE_LIBRARY:
break;
case IClasspathEntry.CPE_PROJECT:
// TODO should compile here ?
break;
case IClasspathEntry.CPE_CONTAINER:
case IClasspathEntry.CPE_VARIABLE:
// Impossible cases, since entries are resolved
default:
break;
}
}
return srcFolders;
}
private static void createClassesFolder(IProject project, IProgressMonitor monitor) throws CoreException {
if (!getClassesFolder(project).exists()) {
getClassesFolder(project).create(true, true, monitor);
}
}
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
if (monitor==null) {
monitor = new NullProgressMonitor();
}
try {
if (CCWPlugin.isAutoReloadOnStartupSaveEnabled()) {
// Only when autoReloadOnStartupSave is enabled do we have AOT compilation
//active and a classes/ folder to manage
getClassesFolder(getProject()).delete(true, new SubProgressMonitor(monitor, 0));
createClassesFolder(getProject(), new SubProgressMonitor(monitor, 0));
getClassesFolder(getProject()).refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 0));
getClassesFolder(getProject()).setDerived(true); //, monitor); removed monitor argument, probably a 3.5/3.6 only stuff
}
} catch (CoreException e) {
CCWPlugin.logError("Unable to correctly clean classes folder", e);
}
deleteMarkers(getProject());
}
private static void deleteMarkers(IProject project) throws CoreException {
for (IFolder srcFolder: getSrcFolders(project).keySet()) {
srcFolder.deleteMarkers(CLOJURE_COMPILER_PROBLEM_MARKER_TYPE, true, IFile.DEPTH_INFINITE);
}
}
}