From 8c4cfab11ddd381398cf8dd963bc3ff606974178 Mon Sep 17 00:00:00 2001 From: GerardPaligot Date: Thu, 14 Jan 2016 11:39:45 +0100 Subject: [PATCH] feat(process): Allows interruption of a processor. Closes #478 --- doc/processor.md | 4 +- .../processing/AbstractManualProcessor.java | 9 +++- .../spoon/processing/AbstractProcessor.java | 18 +++++--- .../spoon/processing/ProcessInterruption.java | 37 ++++++++++++++++ src/main/java/spoon/processing/Processor.java | 10 ++++- .../spoon/support/QueueProcessingManager.java | 35 +++++++++------ .../support/RuntimeProcessingManager.java | 24 ++++++----- .../java/spoon/processing/ProcessingTest.java | 43 +++++++++++++++++++ .../processing/processors/MyProcessor.java | 35 +++++++++++++++ 9 files changed, 180 insertions(+), 35 deletions(-) create mode 100644 src/main/java/spoon/processing/ProcessInterruption.java create mode 100644 src/test/java/spoon/processing/ProcessingTest.java create mode 100644 src/test/java/spoon/processing/processors/MyProcessor.java diff --git a/doc/processor.md b/doc/processor.md index c33b53cdab5..cac756f6eb2 100644 --- a/doc/processor.md +++ b/doc/processor.md @@ -19,7 +19,9 @@ of the processor class. There is also an optional overridable method for queryin elements at a finer grain. The process method takes the requested element as input and does the analysis -(here detecting empty catch blocks). +(here detecting empty catch blocks). At any time, you can interrupt the processing +of the model with a call to `interrupt()` (this stops all processors, and proceeds +with the next step which is usually pretty-printing the code to disk). Since a real world analysis combines multiple queries, multiple processors can be used at the same time. The launcher applies them in the order they have been declared. diff --git a/src/main/java/spoon/processing/AbstractManualProcessor.java b/src/main/java/spoon/processing/AbstractManualProcessor.java index 987c9f607f8..44d5406d8c1 100644 --- a/src/main/java/spoon/processing/AbstractManualProcessor.java +++ b/src/main/java/spoon/processing/AbstractManualProcessor.java @@ -16,12 +16,12 @@ */ package spoon.processing; -import java.util.Set; - import spoon.compiler.Environment; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; +import java.util.Set; + /** * This class defines an abstract processor to be subclassed by the user for * defining new manual processors. A manual processor should override the init @@ -100,4 +100,9 @@ public final void setFactory(Factory factory) { public final void initProperties(ProcessorProperties properties) { AbstractProcessor.initProperties(this, properties); } + + @Override + public void interrupt() { + throw new ProcessInterruption(); + } } diff --git a/src/main/java/spoon/processing/AbstractProcessor.java b/src/main/java/spoon/processing/AbstractProcessor.java index 065c41cde4b..908f6fc6712 100644 --- a/src/main/java/spoon/processing/AbstractProcessor.java +++ b/src/main/java/spoon/processing/AbstractProcessor.java @@ -16,13 +16,6 @@ */ package spoon.processing; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Set; - import org.apache.log4j.Level; import spoon.Launcher; import spoon.compiler.Environment; @@ -30,6 +23,13 @@ import spoon.reflect.factory.Factory; import spoon.support.util.RtHelper; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + /** * This class defines an abstract processor to be subclassed by the user for * defining new processors. @@ -172,4 +172,8 @@ public void setFactory(Factory factory) { this.factory = factory; } + @Override + public void interrupt() { + throw new ProcessInterruption(); + } } diff --git a/src/main/java/spoon/processing/ProcessInterruption.java b/src/main/java/spoon/processing/ProcessInterruption.java new file mode 100644 index 00000000000..b309219adcf --- /dev/null +++ b/src/main/java/spoon/processing/ProcessInterruption.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ +package spoon.processing; + +/** + * This exception is used to interrupt a processor during its processing. + */ +public class ProcessInterruption extends RuntimeException { + public ProcessInterruption() { + } + + public ProcessInterruption(String message) { + super(message); + } + + public ProcessInterruption(String message, Throwable cause) { + super(message, cause); + } + + public ProcessInterruption(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/spoon/processing/Processor.java b/src/main/java/spoon/processing/Processor.java index a6020aed739..c2e4c236adb 100644 --- a/src/main/java/spoon/processing/Processor.java +++ b/src/main/java/spoon/processing/Processor.java @@ -16,11 +16,11 @@ */ package spoon.processing; -import java.util.Set; - import spoon.compiler.Environment; import spoon.reflect.declaration.CtElement; +import java.util.Set; + /** * This interface defines a generic code processor. To define a new processor, * the user should subclass {@link spoon.processing.AbstractProcessor}, the @@ -122,4 +122,10 @@ public interface Processor extends FactoryAccessor { */ void initProperties(ProcessorProperties properties); + /** + * Interrupts the processing of this processor but changes on your AST are kept + * and the invocation of this method doesn't interrupt the processing of all + * processors specified in the {@link ProcessingManager}. + */ + void interrupt(); } diff --git a/src/main/java/spoon/support/QueueProcessingManager.java b/src/main/java/spoon/support/QueueProcessingManager.java index 6ed69710c7b..964a0edcd59 100644 --- a/src/main/java/spoon/support/QueueProcessingManager.java +++ b/src/main/java/spoon/support/QueueProcessingManager.java @@ -19,6 +19,7 @@ import org.apache.log4j.Level; import spoon.SpoonException; import spoon.processing.AbstractProcessor; +import spoon.processing.ProcessInterruption; import spoon.processing.ProcessingManager; import spoon.processing.Processor; import spoon.reflect.declaration.CtElement; @@ -105,26 +106,34 @@ protected ProcessingVisitor getVisitor() { public void process(Collection elements) { Processor p; while ((p = getProcessors().poll()) != null) { - getFactory().getEnvironment().reportProgressMessage(p.getClass().getName()); - current = p; - p.initProperties(AbstractProcessor.loadProperties(p)); - p.init(); - p.process(); - for (CtElement e : new ArrayList(elements)) { - process(e, p); + try { + getFactory().getEnvironment().reportProgressMessage(p.getClass().getName()); + current = p; + p.initProperties(AbstractProcessor.loadProperties(p)); + p.init(); + p.process(); + for (CtElement e : new ArrayList(elements)) { + process(e, p); + } + } catch (ProcessInterruption ignore) { + } finally { + p.processingDone(); } - p.processingDone(); } } public void process(CtElement element) { Processor p; while ((p = getProcessors().poll()) != null) { - current = p; - p.init(); - p.process(); - process(element, p); - p.processingDone(); + try { + current = p; + p.init(); + p.process(); + process(element, p); + } catch (ProcessInterruption ignore) { + } finally { + p.processingDone(); + } } } diff --git a/src/main/java/spoon/support/RuntimeProcessingManager.java b/src/main/java/spoon/support/RuntimeProcessingManager.java index 33712dbff90..d1f676a1dca 100644 --- a/src/main/java/spoon/support/RuntimeProcessingManager.java +++ b/src/main/java/spoon/support/RuntimeProcessingManager.java @@ -16,11 +16,8 @@ */ package spoon.support; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; - import org.apache.log4j.Level; +import spoon.processing.ProcessInterruption; import spoon.processing.ProcessingManager; import spoon.processing.Processor; import spoon.reflect.declaration.CtElement; @@ -29,6 +26,10 @@ import spoon.support.util.Timer; import spoon.support.visitor.ProcessingVisitor; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + /** * This processing manager implements a blocking processing policy that consists * of applying the processors in a FIFO order until no processors remain to be @@ -114,13 +115,16 @@ public void process(Collection elements) { * Recursively processes elements and their children with a given processor. */ public void process(Collection elements, Processor processor) { - getFactory().getEnvironment().debugMessage("processing with '" + processor.getClass().getName() + "'..."); - current = processor; - Timer.start(processor.getClass().getName()); - for (CtElement e : elements) { - process(e, processor); + try { + getFactory().getEnvironment().debugMessage("processing with '" + processor.getClass().getName() + "'..."); + current = processor; + Timer.start(processor.getClass().getName()); + for (CtElement e : elements) { + process(e, processor); + } + Timer.stop(processor.getClass().getName()); + } catch (ProcessInterruption ignored) { } - Timer.stop(processor.getClass().getName()); } public void process(CtElement element) { diff --git a/src/test/java/spoon/processing/ProcessingTest.java b/src/test/java/spoon/processing/ProcessingTest.java new file mode 100644 index 00000000000..70e6f37b38b --- /dev/null +++ b/src/test/java/spoon/processing/ProcessingTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +package spoon.processing; + +import org.junit.Test; +import spoon.Launcher; +import spoon.processing.processors.MyProcessor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +public class ProcessingTest { + @Test + public void testInterruptAProcessor() throws Exception { + final Launcher launcher = new Launcher(); + launcher.getEnvironment().setNoClasspath(true); + launcher.addInputResource("./src/tets/java/spoon/processing/"); + launcher.setSourceOutputDirectory("./target/trash"); + final MyProcessor processor = new MyProcessor(); + launcher.addProcessor(processor); + try { + launcher.run(); + } catch (ProcessInterruption e) { + fail("ProcessInterrupt exception must be catch in the ProcessingManager."); + } + assertFalse(processor.isShouldStayAtFalse()); + } +} diff --git a/src/test/java/spoon/processing/processors/MyProcessor.java b/src/test/java/spoon/processing/processors/MyProcessor.java new file mode 100644 index 00000000000..b4babfcb8b0 --- /dev/null +++ b/src/test/java/spoon/processing/processors/MyProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +package spoon.processing.processors; + +import spoon.processing.AbstractProcessor; +import spoon.reflect.declaration.CtElement; + +public class MyProcessor extends AbstractProcessor { + private boolean shouldStayAtFalse; + + @Override + public void process(CtElement element) { + interrupt(); + shouldStayAtFalse = true; + } + + public boolean isShouldStayAtFalse() { + return shouldStayAtFalse; + } +}