Java tutorial
/******************************************************************************* * 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); } } }