diff --git a/editor/processing.plugin.core/META-INF/MANIFEST.MF b/editor/processing.plugin.core/META-INF/MANIFEST.MF index c1f09b7de..f83e1d0a0 100644 --- a/editor/processing.plugin.core/META-INF/MANIFEST.MF +++ b/editor/processing.plugin.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Processing Plugin Core Bundle-SymbolicName: processing.plugin.core;singleton:=true -Bundle-Version: 0.2.2.0 +Bundle-Version: 0.2.3.0 Bundle-Activator: processing.plugin.core.ProcessingCore Bundle-Vendor: Processing.org Require-Bundle: org.eclipse.core.runtime, diff --git a/editor/processing.plugin.core/plugin.xml b/editor/processing.plugin.core/plugin.xml index f3717a8b3..dd596f567 100644 --- a/editor/processing.plugin.core/plugin.xml +++ b/editor/processing.plugin.core/plugin.xml @@ -25,18 +25,33 @@ + + + + + + + - + + + diff --git a/editor/processing.plugin.core/src/processing/plugin/core/builder/IncrementalChangeProcessor.java b/editor/processing.plugin.core/src/processing/plugin/core/builder/IncrementalChangeProcessor.java new file mode 100644 index 000000000..c0502ad0d --- /dev/null +++ b/editor/processing.plugin.core/src/processing/plugin/core/builder/IncrementalChangeProcessor.java @@ -0,0 +1,327 @@ +/** + * Copyright (c) 2010 Chris Lonnen. 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.opensource.org/licenses/eclipse-1.0.php + * + * Contributors: + * Chris Lonnen - initial API and implementation + */ +package processing.plugin.core.builder; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.runtime.CoreException; + +import processing.plugin.core.ProcessingCore; +import processing.plugin.core.ProcessingLog; + +/** + * Handle an IResourceDelta reported against a Sketch Project. + *

+ * Each instance of this class should only handle a single + * resource delta. Create a new one for each delta that needs + * to be processed. + *

+ * This class was inspired by the JyDT class of the same name + * implemented by Red Robin software. + *

+ * Right now only a full build is available, but the granularity + * of this class prevents unnecessary builds, which saves a lot + * of time, especially on workspaces with more than a handful + * of sketches. + *

+ * In the future this should be easy to integrate with a more + * incremental build process. + */ +public class IncrementalChangeProcessor { + + private boolean fullBuildRequired; + + private SketchProject sp; + + /** Give it a project as context */ + public IncrementalChangeProcessor(SketchProject sketchProject){ + sp = sketchProject; + fullBuildRequired = false; + } + + /** Indicate that a full build should be carried out */ + private void setFullBuildRequired(){ + fullBuildRequired = true; + } + + /** Process an IResourceChangeEvent */ + public boolean resourceChanged(IResourceChangeEvent event){ + printEvent(event); + IResourceDelta delta = event.getDelta(); + return this.resourceChanged(delta); + } + + /** Process an IResourceDelta */ + public boolean resourceChanged(IResourceDelta delta){ + if (delta == null) return false; + printResourceChanges(delta); + + IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() { + public boolean visit(IResourceDelta delta) throws CoreException { + switch (delta.getResource().getType()){ + case IResource.ROOT: + // do nothing + break; + case IResource.PROJECT: + IProject changedProject = (IProject) delta.getResource(); + if(changedProject != sp.getProject()) + return false; + processProjectChange(delta); + break; + case IResource.FILE: + processFileChange(delta); + break; + case IResource.FOLDER: + processFolderChange(delta); + break; + default: + break; + } + return true; // visit children + } + }; + try{ + delta.accept(visitor); + } catch (CoreException ex){ + ProcessingLog.logError(ex); + } + return fullBuildRequired; + } + + /** Handle a change against the Project itself */ + private void processProjectChange(IResourceDelta delta){ + // Do nothing for now. + switch (delta.getFlags()) { + case IResourceDelta.DESCRIPTION: + break; + case IResourceDelta.OPEN: + setFullBuildRequired(); + break; + case IResourceDelta.TYPE: + break; + case IResourceDelta.SYNC: + setFullBuildRequired(); + break; + case IResourceDelta.MARKERS: + break; + case IResourceDelta.REPLACED: + setFullBuildRequired(); + break; + case IResourceDelta.MOVED_TO: + setFullBuildRequired(); + break; + case IResourceDelta.MOVED_FROM: + break; + default: + break; + } + } + + /** Handle a resource change against a folder */ + private void processFolderChange(IResourceDelta delta){ + if (!sp.getProject().exists(delta.getProjectRelativePath())) + return; + switch(delta.getKind()){ + case IResourceDelta.ADDED: + break; + case IResourceDelta.REMOVED: + if (delta.getFlags() == IResourceDelta.MOVED_TO) + processInterestingFolderMove(delta); + else + processInterestingFolderRemoval(delta); + break; + case IResourceDelta.CHANGED: + processInterestingFolderChange(delta); + break; + default: + break; + } + } + + /** Handle the change of a folder in the project */ + private void processInterestingFolderChange(IResourceDelta delta){ + // we don't really care that the folder changed at the moment + + // switch(delta.getFlags()){ + // case IResourceDelta.CONTENT: + // case IResourceDelta.DESCRIPTION: + // case IResourceDelta.OPEN: + // case IResourceDelta.TYPE: + // case IResourceDelta.SYNC: + // case IResourceDelta.MARKERS: + // case IResourceDelta.REPLACED: + // case IResourceDelta.MOVED_TO: + // case IResourceDelta.MOVED_FROM: + // default: + // break; + // } + } + + /** Handle the removal of a folder in the project */ + private void processInterestingFolderRemoval(IResourceDelta delta){ + setFullBuildRequired(); + } + + /** Handle the move of a folder in the project */ + private void processInterestingFolderMove(IResourceDelta delta){ + setFullBuildRequired(); + } + + /** Handle the change of a file in the project */ + private void processFileChange(IResourceDelta delta){ + IFile resource = (IFile) delta.getResource(); + // we only care about .pde files + if(!ProcessingCore.isProcessingFile(resource)) return; + if (!sp.getProject().exists(resource.getProjectRelativePath())) return; + + // System.out.println(delta.getProjectRelativePath() + " exists in " + sp.getProject().getName()); + setFullBuildRequired(); + + //switch (delta.getKind()){ + //case IResourceDelta.ADDED: + //case IResourceDelta.REMOVED: + //case IResourceDelta.CHANGED: + // int deltaFlags = delta.getFlags(); + // switch(deltaFlags){ + // case IResourceDelta.CONTENT: + // case IResourceDelta.ENCODING: + // case IResourceDelta.TYPE: + // case IResourceDelta.SYNC: + // case IResourceDelta.MARKERS: + // case IResourceDelta.REPLACED: + // case IResourceDelta.MOVED_TO: + // case IResourceDelta.MOVED_FROM: + // default: + // break; + // } + // break; + // default: + // break; + // } + } + + /** Debug code */ + private static void printChange(IResourceDelta delta, StringBuffer buffer) { + buffer.append("CHANGED "); + buffer.append(delta.getFlags()); + switch (delta.getFlags()) { + case IResourceDelta.CONTENT: + buffer.append(" (CONTENT)"); + break; + case IResourceDelta.ENCODING: + buffer.append(" (ENCODING)"); + break; + case IResourceDelta.DESCRIPTION: + buffer.append(" (DESCRIPTION)"); + break; + case IResourceDelta.OPEN: + buffer.append(" (OPEN)"); + break; + case IResourceDelta.TYPE: + buffer.append(" (TYPE)"); + break; + case IResourceDelta.SYNC: + buffer.append(" (SYNC)"); + break; + case IResourceDelta.MARKERS: + buffer.append(" (MARKERS)"); + break; + case IResourceDelta.REPLACED: + buffer.append(" (REPLACED)"); + break; + case IResourceDelta.MOVED_TO: + buffer.append(" (MOVED_TO)"); + break; + case IResourceDelta.MOVED_FROM: + buffer.append(" (MOVED_FROM)"); + break; + default: + buffer.append(" ()"); + break; + } + } + + /** Debug code */ + private static void printOneResourceChanged(IResourceDelta delta, StringBuffer buffer, int indent) { + for (int i = 0; i < indent; i++) { + buffer.append(' '); + } + switch (delta.getKind()) { + case IResourceDelta.ADDED: + buffer.append("ADDED"); + break; + case IResourceDelta.REMOVED: + buffer.append("REMOVED"); + break; + case IResourceDelta.CHANGED: + printChange(delta, buffer); + break; + default: + buffer.append('['); + buffer.append(delta.getKind()); + buffer.append(']'); + break; + } + buffer.append(' '); + buffer.append(delta.getResource()); + buffer.append('\n'); + } + + /** Debug code */ + private static void printResourcesChanged(IResourceDelta delta, StringBuffer buffer, int indent) { + printOneResourceChanged(delta, buffer, indent); + IResourceDelta[] children = delta.getAffectedChildren(); + for (int i = 0; i < children.length; i++) { + printResourcesChanged(children[i], buffer, indent + 1); + } + } + + /** Debug code */ + public static void printEvent(IResourceChangeEvent event) { + StringBuffer buffer = new StringBuffer(80); + buffer.append("Resource change event received "); + switch (event.getType()) { + case IResourceChangeEvent.POST_BUILD: + buffer.append("[POST_BUILD]"); + break; + case IResourceChangeEvent.POST_CHANGE: + buffer.append("[POST_CHANGE]"); + break; + case IResourceChangeEvent.PRE_BUILD: + buffer.append("[PRE_BUILD]"); + break; + case IResourceChangeEvent.PRE_CLOSE: + buffer.append("[PRE_CLOSE]"); + break; + case IResourceChangeEvent.PRE_DELETE: + buffer.append("[PRE_DELETE]"); + break; + default: + break; + } + buffer.append(".\n"); + System.out.println(buffer); + } + + /** Debug code */ + public static void printResourceChanges(IResourceDelta delta) { + StringBuffer buffer = new StringBuffer(80); + if (delta != null) + printResourcesChanged(delta, buffer, 0); + System.out.println(buffer); + } + + +} diff --git a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java index 6ce20402b..6a5cc8ac2 100644 --- a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java +++ b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java @@ -51,24 +51,24 @@ import processing.plugin.core.ProcessingUtilities; * The SketchNature class is tightly integrated and manages the configuration so this builder * works together with the JDT, so woe be to those who would carelessly manipulate this builder * directly. - *

*

* The builder is compatible with the PDE, and it expects sketches to be laid out with the * same folder structure. It may store metadata and temporary build files in the sketch * file system but these will not change how the PDE interacts with it. Users should be * able to use the PDE interchangeably with this builder. - *

*

* Though this implements the Incremental Project Builder, the preprocessor is not incremental * and forces all builds to be full builds. To save a little bit of time, any build request is * treated as a full build without an inspection of the resource delta. - *

*/ public class SketchBuilder extends IncrementalProjectBuilder{ /** The identifier for the Processing builder (value "processing.plugin.core.processingbuilder"). */ public static final String BUILDER_ID = ProcessingCore.PLUGIN_ID + ".sketchBuilder"; + /** Parent marker for Processing created markers (value "processing.plugin.core.processingMarker"). */ + public static final String PROCESSINGMARKER = ProcessingCore.PLUGIN_ID + ".processingMarker"; + /** Problem marker for processing preprocessor issues (value "processing.plugin.core.preprocError"). */ public static final String PREPROCMARKER = ProcessingCore.PLUGIN_ID + ".preprocError"; @@ -111,9 +111,14 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** Full paths to jars required to compile the sketch */ private ArrayListlibraryJarPathList; - /** Clean any leftover state from previous builds. */ - protected void clean(SketchProject sketch, IProgressMonitor monitor) throws CoreException{ - deleteP5ProblemMarkers(sketch); + /** + * Triggered by platform Clean command. + *

+ * Reset any state from previous builds. + */ + protected void clean(IProgressMonitor monitor) throws CoreException{ + SketchProject sketch = SketchProject.forProject(this.getProject()); + deleteP5ProblemMarkers(sketch.getProject()); srcFolderPathList = new ArrayList(); libraryJarPathList = new ArrayList(); // if this is the first run of the builder the build folder will not be stored yet, @@ -127,7 +132,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{ // Eventually, a model (controlled by SketchProject) should manage the markers, // folders, etc. Cleaning the model should be in a method called beCleaned() // in the SketchProject. This method will then look something like: - // SketchProject sketch = SketchProject.forProject(this.getProject()); // sketch.beCleaned(); } @@ -137,47 +141,44 @@ public class SketchBuilder extends IncrementalProjectBuilder{ * This usually means grabbing all the Processing files and compiling them * to Java source files and moving them into a designated folder where the * JDT will grab them and build them. - *

*

* For now all builds are full builds because the preprocessor does not - * handle incremental builds. - *

- * + * handle incremental builds. */ protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException{ IProject project = this.getProject(); SketchProject sketch = SketchProject.forProject(project); - - switch (kind){ - case FULL_BUILD: - return this.fullBuild(sketch, monitor); - case AUTO_BUILD: - return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null; -// return this.autoBuild(sketch, monitor); - case INCREMENTAL_BUILD: - return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null; -// return this.incrementalBuild(sketch, monitor); - default: - return null; + switch (kind) { + case FULL_BUILD: + return this.fullBuild(sketch, monitor); + case AUTO_BUILD: + return this.autoBuild(sketch, monitor); + case INCREMENTAL_BUILD: + return this.incrementalBuild(sketch, monitor); + default: + return null; // everything falls through to return null } } - - /** - * Returns false if the resource change definitely didn't affect this project. - *

- * Runs simple checks to try and rule out a build. If it can't find a reason - * to rule out a build, it defaults to true. Hopefully this will spare a few - * CPU cycles, though there is a lot of room for improvement here. - */ - private boolean fullBuildRequired(IProject proj) { - if (proj == null) return false; - IResourceDelta delta = this.getDelta(proj); - if (delta == null) return false; - if (delta.getResource().getType() == IResource.PROJECT){ - IProject changedProject = (IProject) delta.getResource(); - if (changedProject != proj) return false; - } - return true; + + /** Handles platform auto builds */ + protected IProject[] autoBuild(SketchProject sketchProject, IProgressMonitor monitor) throws CoreException{ + //System.err.println("Auto Build"); + IResourceDelta delta = this.getDelta(this.getProject()); + IncrementalChangeProcessor changeProcessor = new IncrementalChangeProcessor(sketchProject); + boolean shouldBuild = changeProcessor.resourceChanged(delta); + if (shouldBuild) + fullBuild(sketchProject, monitor); + return null; + } + + /** Incremental builds are ignored. */ + protected IProject[] incrementalBuild(SketchProject sketchProject, IProgressMonitor monitor){ + //System.err.println("Incremental Build"); + + // triggered by launching a sketch or explicitly by a user iff auto build is off + // if auto build is on, launching a sketch triggers an auto build first + // save a few cycles by ignoring these + return null; } /** @@ -188,7 +189,9 @@ public class SketchBuilder extends IncrementalProjectBuilder{ * This can be a long running process, so we use a monitor. */ protected IProject[] fullBuild( SketchProject sketchProject, IProgressMonitor monitor) throws CoreException { - clean(sketchProject, monitor); // tabula rasa +// System.err.println("Full Build of " + sketchProject.getProject().getName()); + + clean(monitor); // tabula rasa IProject sketch = sketchProject.getProject(); if ( sketch == null || !sketch.isAccessible() ){ @@ -481,16 +484,8 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** Delete all of the existing P5 problem markers. */ protected static void deleteP5ProblemMarkers(IResource resource) throws CoreException{ - if(resource != null && resource.exists()){ - resource.deleteMarkers(SketchBuilder.PREPROCMARKER, true, IResource.DEPTH_INFINITE); - resource.deleteMarkers(SketchBuilder.COMPILEMARKER, true, IResource.DEPTH_INFINITE); - } - } - - /** Purge all the Processing set problem markers from the Processing sketch. */ - protected static void deleteP5ProblemMarkers(SketchProject sketch)throws CoreException{ - if(sketch != null) - deleteP5ProblemMarkers(sketch.getProject()); + if(resource != null && resource.exists()) + resource.deleteMarkers(SketchBuilder.PROCESSINGMARKER , true, IResource.DEPTH_INFINITE); } /** @@ -500,7 +495,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{ * to a specific file it will be marked against the project and the line will not be marked. */ private void reportProblem(String message, IResource problemFile, int lineNumber, boolean isError){ - // translate error messages to a friendlier form if (message.equals("expecting RCURLY, found 'null'")) message = "Found one too many { characters without a } to match it."; @@ -528,17 +522,18 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** * Check for interruptions. *

- * The build process is rather long so if the user cancels things toss an exception. + * The build process can run long, so if the user cancels things toss an exception. * Builds also tie up the workspace, so check to see if something is demanding to * interrupt it and let it. Usually these interruptions would force a rebuild of the - * system anyhow, so save some CPU cycles and time. + * system anyhow, nullifying the work being done now, so save some cycles and let + * it budge in line. *

* @return true if the build is hogging the resource thread * @throws OperationCanceledException is the user cancels */ private boolean checkCancel(IProgressMonitor monitor){ - if (monitor.isCanceled()){ throw new OperationCanceledException(); } - if (isInterrupted()){ return true; } + if (monitor.isCanceled()) throw new OperationCanceledException(); + if (isInterrupted()) return true; return false; } }