You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2019/12/17 02:40:27 UTC

[groovy] 01/02: add functional interfaces for phase operations to support lambdas

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 33bbc38e7a443929e3be380741a7186a1244437f
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 16 16:12:03 2019 -0600

    add functional interfaces for phase operations to support lambdas
    
    - retain abstract classes and method overloads for binary compatibility
---
 .../groovy/tools/ast/TransformTestHelper.groovy    |   8 +-
 src/main/java/groovy/lang/GroovyClassLoader.java   |   2 +-
 src/main/java/groovy/util/GroovyScriptEngine.java  |  17 +-
 .../codehaus/groovy/control/CompilationUnit.java   | 667 ++++++++++-----------
 .../groovy/control/CompilerConfiguration.java      |   2 +-
 .../groovy/control/StaticImportVisitor.java        |  22 +-
 .../control/customizers/CompilationCustomizer.java |   4 +-
 .../tools/javac/JavaAwareCompilationUnit.java      |  40 +-
 .../tools/javac/JavaStubCompilationUnit.java       |  26 +-
 .../groovy/transform/ASTTransformationVisitor.java |  35 +-
 .../transform/sc/StaticCompilationVisitor.java     |  14 +-
 .../transform/trait/TraitASTTransformation.java    |  23 +-
 src/test/groovy/lang/GroovyClassLoaderTest.groovy  |   4 +-
 .../ClosureAndInnerClassNodeStructureTest.groovy   |   4 +-
 .../codehaus/groovy/tools/gse/DependencyTest.java  |  41 +-
 .../console/ui/AstNodeToScriptAdapter.groovy       |   3 +-
 .../console/ui/ScriptToTreeNodeAdapter.groovy      |   3 +-
 .../groovysh/util/ScriptVariableAnalyzer.groovy    |   4 +-
 .../codehaus/groovy/jsr223/JSR223SecurityTest.java |   9 +-
 .../groovy/parser/antlr4/util/AstDumper.groovy     |   3 +-
 20 files changed, 430 insertions(+), 501 deletions(-)

diff --git a/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy b/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
index d4005e6..8a073bf 100644
--- a/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
+++ b/src/main/groovy/org/codehaus/groovy/tools/ast/TransformTestHelper.groovy
@@ -22,7 +22,6 @@ import groovy.transform.PackageScope
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
@@ -104,15 +103,16 @@ class TestHarnessClassLoader extends GroovyClassLoader {
  * Operation exists so that an AstTransformation can be run against the SourceUnit.
  */
 @PackageScope
-class TestHarnessOperation extends PrimaryClassNodeOperation {
+class TestHarnessOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
     private final ASTTransformation transform
 
-    TestHarnessOperation(transform) {
+    TestHarnessOperation(ASTTransformation transform) {
         this.transform = transform
     }
 
+    @Override
     void call(SourceUnit source, GeneratorContext ignoredContext, ClassNode ignoredNode) {
-        transform.visit(null, source)
+        this.transform.visit(null, source)
     }
 }
diff --git a/src/main/java/groovy/lang/GroovyClassLoader.java b/src/main/java/groovy/lang/GroovyClassLoader.java
index 27e7038..e4c8aae 100644
--- a/src/main/java/groovy/lang/GroovyClassLoader.java
+++ b/src/main/java/groovy/lang/GroovyClassLoader.java
@@ -1157,7 +1157,7 @@ public class GroovyClassLoader extends URLClassLoader {
         clearCache();
     }
 
-    private static class TimestampAdder extends CompilationUnit.PrimaryClassNodeOperation implements Opcodes {
+    private static class TimestampAdder implements CompilationUnit.IPrimaryClassNodeOperation, Opcodes {
         private static final TimestampAdder INSTANCE = new TimestampAdder();
 
         private TimestampAdder() {}
diff --git a/src/main/java/groovy/util/GroovyScriptEngine.java b/src/main/java/groovy/util/GroovyScriptEngine.java
index fa813a6..7afd183 100644
--- a/src/main/java/groovy/util/GroovyScriptEngine.java
+++ b/src/main/java/groovy/util/GroovyScriptEngine.java
@@ -25,7 +25,6 @@ import groovy.lang.GroovyResourceLoader;
 import groovy.lang.Script;
 import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.ClassNodeResolver;
 import org.codehaus.groovy.control.CompilationFailedException;
@@ -166,16 +165,12 @@ public class GroovyScriptEngine implements ResourceConnector {
             // remove all old entries including the "." entry
             cache.clear();
 
-            cu.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-                @Override
-                public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode)
-                        throws CompilationFailedException {
-                    // GROOVY-4013: If it is an inner class, tracking its dependencies doesn't really
-                    // serve any purpose and also interferes with the caching done to track dependencies
-                    if (classNode instanceof InnerClassNode) return;
-                    DependencyTracker dt = new DependencyTracker(source, cache, precompiledEntries);
-                    dt.visitClass(classNode);
-                }
+            cu.addPhaseOperation((final SourceUnit sourceUnit, final GeneratorContext context, final ClassNode classNode) -> {
+               // GROOVY-4013: If it is an inner class, tracking its dependencies doesn't really
+               // serve any purpose and also interferes with the caching done to track dependencies
+               if (classNode.getOuterClass() != null) return;
+               DependencyTracker dt = new DependencyTracker(sourceUnit, cache, precompiledEntries);
+               dt.visitClass(classNode);
             }, Phases.CLASS_GENERATION);
 
             cu.setClassNodeResolver(new ClassNodeResolver() {
diff --git a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
index 210ec3f..5b81391 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
@@ -72,8 +72,8 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
-import java.util.stream.Collectors;
 
+import static java.util.stream.Collectors.toList;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
 import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK;
@@ -123,8 +123,6 @@ public class CompilationUnit extends ProcessingUnit {
 
     protected ClassNodeResolver classNodeResolver = new ClassNodeResolver();
     protected ResolveVisitor resolveVisitor = new ResolveVisitor(this);
-    protected OptimizerVisitor optimizer = new OptimizerVisitor(this);
-    protected Verifier verifier = new Verifier();
 
     /** The AST transformations state data. */
     protected ASTTransformationsContext astTransformationsContext;
@@ -164,9 +162,10 @@ public class CompilationUnit extends ProcessingUnit {
      * Initializes the CompilationUnit with a CodeSource for controlling
      * security stuff, a class loader for loading classes, and a class
      * loader for loading AST transformations.
-     * <b>Note</b> The transform loader must be
-     * able to load compiler classes. That means CompilationUnit.class.classLoader
-     * must be at last a parent to transformLoader. The other loader has no such constraint.
+     * <p>
+     * <b>Note</b>: The transform loader must be able to load compiler classes.
+     * That means {@link #classLoader} must be at last a parent to {@code transformLoader}.
+     * The other loader has no such constraint.
      *
      * @param transformLoader - the loader for transforms
      * @param loader          - loader used to resolve classes against during compilation
@@ -178,188 +177,165 @@ public class CompilationUnit extends ProcessingUnit {
         super(configuration, loader, null);
 
         this.astTransformationsContext = new ASTTransformationsContext(this, transformLoader);
-        this.ast = new CompileUnit(this.classLoader, codeSource, this.configuration);
+        this.ast = new CompileUnit(getClassLoader(), codeSource, getConfiguration());
 
         addPhaseOperations();
         applyCompilationCustomizers();
     }
 
     private void addPhaseOperations() {
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                source.parse();
-            }
-        }, Phases.PARSING);
-
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                source.convert();
-                // add module to compile unit
-                getAST().addModule(source.getAST());
-
-                if (progressCallback != null) {
-                    progressCallback.call(source, phase);
-                }
-            }
+        addPhaseOperation(SourceUnit::parse, Phases.PARSING);
+
+        addPhaseOperation(source -> {
+            source.convert();
+            // add module to compile unit
+            getAST().addModule(source.getAST());
+            Optional.ofNullable(getProgressCallback())
+                .ifPresent(callback -> callback.call(source, getPhase()));
         }, Phases.CONVERSION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new EnumVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new EnumVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.CONVERSION);
 
         addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                StaticImportVisitor visitor = new StaticImportVisitor();
-                visitor.visitClass(classNode, source);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new StaticImportVisitor(classNode, source);
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new InnerClassVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new InnerClassVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                if (!classNode.isSynthetic()) {
-                    GroovyClassVisitor visitor = new GenericsVisitor(source);
-                    visitor.visitClass(classNode);
-                }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (!classNode.isSynthetic()) {
+                GroovyClassVisitor visitor = new GenericsVisitor(source);
+                visitor.visitClass(classNode);
             }
         }, Phases.SEMANTIC_ANALYSIS);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                TraitComposer.doExtendTraits(classNode, source, CompilationUnit.this);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            TraitComposer.doExtendTraits(classNode, source, this);
         }, Phases.CANONICALIZATION);
-        addPhaseOperation(new SourceUnitOperation() {
-            @Override
-            public void call(final SourceUnit source) throws CompilationFailedException {
-                List<ClassNode> classes = source.ast.getClasses();
-                for (ClassNode node : classes) {
-                    CompileUnit cu = node.getCompileUnit();
-                    for (Iterator<String> it = cu.iterateClassNodeToCompile(); it.hasNext(); ) {
-                        String name = it.next();
-                        StringBuilder message = new StringBuilder();
-                        message
-                                .append("Compilation incomplete: expected to find the class ")
-                                .append(name)
-                                .append(" in ")
-                                .append(source.getName());
-                        if (classes.isEmpty()) {
-                            message.append(", but the file seems not to contain any classes");
-                        } else {
-                            message.append(", but the file contains the classes: ");
-                            boolean first = true;
-                            for (ClassNode cn : classes) {
-                                if (!first) {
-                                    message.append(", ");
-                                } else {
-                                    first = false;
-                                }
-                                message.append(cn.getName());
+
+        addPhaseOperation(source -> {
+            List<ClassNode> classes = source.getAST().getClasses();
+            for (ClassNode node : classes) {
+                CompileUnit cu = node.getCompileUnit();
+                for (Iterator<String> it = cu.iterateClassNodeToCompile(); it.hasNext(); ) {
+                    String name = it.next();
+                    StringBuilder message = new StringBuilder();
+                    message
+                            .append("Compilation incomplete: expected to find the class ")
+                            .append(name)
+                            .append(" in ")
+                            .append(source.getName());
+                    if (classes.isEmpty()) {
+                        message.append(", but the file seems not to contain any classes");
+                    } else {
+                        message.append(", but the file contains the classes: ");
+                        boolean first = true;
+                        for (ClassNode cn : classes) {
+                            if (first) {
+                                first = false;
+                            } else {
+                                message.append(", ");
                             }
+                            message.append(cn.getName());
                         }
-
-                        getErrorCollector().addErrorAndContinue(
-                                new SimpleMessage(message.toString(), CompilationUnit.this)
-                        );
-                        it.remove();
                     }
+
+                    getErrorCollector().addErrorAndContinue(
+                            new SimpleMessage(message.toString(), this)
+                    );
+                    it.remove();
                 }
             }
         }, Phases.CANONICALIZATION);
 
         addPhaseOperation(classgen, Phases.CLASS_GENERATION);
 
-        addPhaseOperation(output);
+        addPhaseOperation(groovyClass -> {
+            String name = groovyClass.getName().replace('.', File.separatorChar) + ".class";
+            File path = new File(getConfiguration().getTargetDirectory(), name);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger();
-                xformer.transformClass(classNode);
+            // ensure the path is ready for the file
+            File directory = path.getParentFile();
+            if (directory != null && !directory.exists()) {
+                directory.mkdirs();
             }
+
+            // create the file and write out the data
+            try (FileOutputStream stream = new FileOutputStream(path)) {
+                byte[] bytes = groovyClass.getBytes();
+                stream.write(bytes, 0, bytes.length);
+            } catch (IOException e) {
+                getErrorCollector().addError(Message.create(e.getMessage(), this));
+            }
+        });
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger();
+            xformer.transformClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
         ASTTransformationVisitor.addPhaseOperations(this);
 
         // post-transform operations:
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                StaticVerifier verifier = new StaticVerifier();
-                verifier.visitClass(classNode, source);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            StaticVerifier verifier = new StaticVerifier();
+            verifier.visitClass(classNode, source);
         }, Phases.SEMANTIC_ANALYSIS);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new InnerClassCompletionVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
-        }, Phases.CANONICALIZATION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                GroovyClassVisitor visitor = new EnumCompletionVisitor(CompilationUnit.this, source);
-                visitor.visitClass(classNode);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new InnerClassCompletionVisitor(this, source);
+            visitor.visitClass(classNode);
+
+            visitor = new EnumCompletionVisitor(this, source);
+            visitor.visitClass(classNode);
         }, Phases.CANONICALIZATION);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
-                if (callback instanceof PrimaryClassNodeOperation) {
-                    ((PrimaryClassNodeOperation) callback).call(source, context, classNode);
-                    classNode.removeNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
+            if (callback instanceof IPrimaryClassNodeOperation) {
+                ((IPrimaryClassNodeOperation) callback).call(source, context, classNode);
+                classNode.removeNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
             }
         }, Phases.INSTRUCTION_SELECTION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                // TODO: Could this be moved into org.codehaus.groovy.transform.sc.transformers.VariableExpressionTransformer?
-                new ClassCodeExpressionTransformer() {
-                    @Override
-                    protected SourceUnit getSourceUnit() {
-                        return source;
-                    }
 
-                    @Override
-                    public Expression transform(final Expression expression) {
-                        if (expression instanceof VariableExpression) {
-                            // check for "switch(enumType) { case CONST: ... }"
-                            ClassNode enumType = expression.getNodeMetaData(SWITCH_CONDITION_EXPRESSION_TYPE);
-                            if (enumType != null) {
-                                // replace "CONST" variable expression with "EnumType.CONST" property expression
-                                Expression propertyExpression = propX(classX(enumType), expression.getText());
-                                setSourcePosition(propertyExpression, expression);
-                                return propertyExpression;
-                            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            // TODO: Can this be moved into org.codehaus.groovy.transform.sc.transformers.VariableExpressionTransformer?
+            GroovyClassVisitor visitor = new ClassCodeExpressionTransformer() {
+                @Override
+                protected SourceUnit getSourceUnit() {
+                    return source;
+                }
+
+                @Override
+                public Expression transform(final Expression expression) {
+                    if (expression instanceof VariableExpression) {
+                        // check for "switch(enumType) { case CONST: ... }"
+                        ClassNode enumType = expression.getNodeMetaData(SWITCH_CONDITION_EXPRESSION_TYPE);
+                        if (enumType != null) {
+                            // replace "CONST" variable expression with "EnumType.CONST" property expression
+                            Expression propertyExpression = propX(classX(enumType), expression.getText());
+                            setSourcePosition(propertyExpression, expression);
+                            return propertyExpression;
                         }
-                        return expression;
                     }
-                }.visitClass(classNode);
-            }
+                    return expression;
+                }
+            };
+            visitor.visitClass(classNode);
         }, Phases.INSTRUCTION_SELECTION);
     }
 
     private void applyCompilationCustomizers() {
-        for (CompilationCustomizer customizer : configuration.getCompilationCustomizers()) {
+        for (CompilationCustomizer customizer : getConfiguration().getCompilationCustomizers()) {
             if (customizer instanceof CompilationUnitAware) {
                 ((CompilationUnitAware) customizer).setCompilationUnit(this);
             }
@@ -367,26 +343,26 @@ public class CompilationUnit extends ProcessingUnit {
         }
     }
 
-    public void addPhaseOperation(final GroovyClassOperation op) {
+    public void addPhaseOperation(final IGroovyClassOperation op) {
         phaseOperations[Phases.OUTPUT].addFirst(op);
     }
 
-    public void addPhaseOperation(final SourceUnitOperation op, final int phase) {
+    public void addPhaseOperation(final ISourceUnitOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].add(op);
     }
 
-    public void addPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+    public void addPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].add(op);
     }
 
-    public void addFirstPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+    public void addFirstPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) {
         validatePhase(phase);
         phaseOperations[phase].addFirst(op);
     }
 
-    public void addNewPhaseOperation(final SourceUnitOperation op, final int phase) {
+    public void addNewPhaseOperation(final ISourceUnitOperation op, final int phase) {
         validatePhase(phase);
         newPhaseOperations[phase].add(op);
     }
@@ -404,7 +380,7 @@ public class CompilationUnit extends ProcessingUnit {
     @Override
     public void configure(final CompilerConfiguration configuration) {
         super.configure(configuration);
-        this.debug = this.configuration.getDebug();
+        this.debug = getConfiguration().getDebug();
         this.configured = true;
     }
 
@@ -435,16 +411,13 @@ public class CompilationUnit extends ProcessingUnit {
      */
     public ClassNode getClassNode(final String name) {
         ClassNode[] result = new ClassNode[1];
-        PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
-                if (classNode.getName().equals(name)) {
-                    result[0] = classNode;
-                }
+        IPrimaryClassNodeOperation handler = (source, context, classNode) -> {
+            if (classNode.getName().equals(name)) {
+                result[0] = classNode;
             }
         };
         try {
-            applyToPrimaryClassNodes(handler);
+            handler.doPhaseOperation(this);
         } catch (CompilationFailedException e) {
             if (debug) e.printStackTrace();
         }
@@ -506,26 +479,26 @@ public class CompilationUnit extends ProcessingUnit {
      * Adds a source file to the unit.
      */
     public SourceUnit addSource(final File file) {
-        return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(file, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
      * Adds a source file to the unit.
      */
     public SourceUnit addSource(final URL url) {
-        return addSource(new SourceUnit(url, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(url, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
      * Adds a InputStream source to the unit.
      */
     public SourceUnit addSource(final String name, final InputStream stream) {
-        ReaderSource source = new InputStreamReaderSource(stream, configuration);
-        return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector()));
+        ReaderSource source = new InputStreamReaderSource(stream, getConfiguration());
+        return addSource(new SourceUnit(name, source, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     public SourceUnit addSource(final String name, final String scriptText) {
-        return addSource(new SourceUnit(name, scriptText, configuration, classLoader, getErrorCollector()));
+        return addSource(new SourceUnit(name, scriptText, getConfiguration(), getClassLoader(), getErrorCollector()));
     }
 
     /**
@@ -533,7 +506,7 @@ public class CompilationUnit extends ProcessingUnit {
      */
     public SourceUnit addSource(final SourceUnit source) {
         String name = source.getName();
-        source.setClassLoader(this.classLoader);
+        source.setClassLoader(getClassLoader());
         for (SourceUnit su : queuedSources) {
             if (name.equals(su.getName())) return su;
         }
@@ -589,6 +562,10 @@ public class CompilationUnit extends ProcessingUnit {
         void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException;
     }
 
+    public ClassgenCallback getClassgenCallback() {
+        return classgenCallback;
+    }
+
     /**
      * Sets a ClassgenCallback.  You can have only one, and setting
      * it to {@code null} removes any existing setting.
@@ -597,10 +574,6 @@ public class CompilationUnit extends ProcessingUnit {
         this.classgenCallback = visitor;
     }
 
-    public ClassgenCallback getClassgenCallback() {
-        return classgenCallback;
-    }
-
     /**
      * A callback interface you can use to get a callback after every
      * unit of the compile process.  You will be called-back with a
@@ -612,6 +585,10 @@ public class CompilationUnit extends ProcessingUnit {
         void call(ProcessingUnit context, int phase) throws CompilationFailedException;
     }
 
+    public ProgressCallback getProgressCallback() {
+        return progressCallback;
+    }
+
     /**
      * Sets a ProgressCallback.  You can have only one, and setting
      * it to {@code null} removes any existing setting.
@@ -620,15 +597,11 @@ public class CompilationUnit extends ProcessingUnit {
         this.progressCallback = callback;
     }
 
-    public ProgressCallback getProgressCallback() {
-        return progressCallback;
-    }
-
     //---------------------------------------------------------------------------
     // ACTIONS
 
     /**
-     * Synonym for compile(Phases.ALL).
+     * Synonym for {@code compile(Phases.ALL)}.
      */
     public void compile() throws CompilationFailedException {
         compile(Phases.ALL);
@@ -656,9 +629,10 @@ public class CompilationUnit extends ProcessingUnit {
             // Grab processing may have brought in new AST transforms into various phases, process them as well
             processNewPhaseOperations(phase);
 
-            if (progressCallback != null) progressCallback.call(this, phase);
+            Optional.ofNullable(getProgressCallback())
+                .ifPresent(callback -> callback.call(this, phase));
             completePhase();
-            applyToSourceUnits(mark);
+            mark();
 
             if (dequeued()) continue;
 
@@ -669,7 +643,7 @@ public class CompilationUnit extends ProcessingUnit {
             }
         }
 
-        errorCollector.failIfErrors();
+        getErrorCollector().failIfErrors();
     }
 
     private void processPhaseOperations(final int phase) {
@@ -735,56 +709,28 @@ public class CompilationUnit extends ProcessingUnit {
     /**
      * Resolves all types.
      */
-    private final SourceUnitOperation resolve = new SourceUnitOperation() {
-        @Override
-        public void call(final SourceUnit source) throws CompilationFailedException {
-            List<ClassNode> classes = source.ast.getClasses();
-            for (ClassNode node : classes) {
-                VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                scopeVisitor.visitClass(node);
-
-                resolveVisitor.setClassNodeResolver(classNodeResolver);
-                resolveVisitor.startResolving(node, source);
-            }
-
-        }
-    };
-
-    private final GroovyClassOperation output = new GroovyClassOperation() {
-        @Override
-        public void call(final GroovyClass groovyClass) throws CompilationFailedException {
-            String name = groovyClass.getName().replace('.', File.separatorChar) + ".class";
-            File path = new File(configuration.getTargetDirectory(), name);
-
-            // ensure the path is ready for the file
-            File directory = path.getParentFile();
-            if (directory != null && !directory.exists()) {
-                directory.mkdirs();
-            }
-
-            // create the file and write out the data
-            byte[] bytes = groovyClass.getBytes();
+    private final ISourceUnitOperation resolve = (final SourceUnit source) -> {
+        for (ClassNode classNode : source.getAST().getClasses()) {
+            GroovyClassVisitor visitor = new VariableScopeVisitor(source);
+            visitor.visitClass(classNode);
 
-            try (FileOutputStream stream = new FileOutputStream(path)) {
-                stream.write(bytes, 0, bytes.length);
-            } catch (IOException e) {
-                getErrorCollector().addError(Message.create(e.getMessage(), CompilationUnit.this));
-            }
+            resolveVisitor.setClassNodeResolver(classNodeResolver);
+            resolveVisitor.startResolving(classNode, source);
         }
     };
 
     /**
-     * Runs classgen() on a single ClassNode.
+     * Runs {@link #classgen()} on a single {@code ClassNode}.
      */
-    private final PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
+    private final IPrimaryClassNodeOperation classgen = new IPrimaryClassNodeOperation() {
         @Override
         public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-            optimizer.visitClass(classNode, source); // GROOVY-4272: repositioned it here from static import visitor
+            new OptimizerVisitor(CompilationUnit.this).visitClass(classNode, source); // GROOVY-4272: repositioned from static import visitor
 
             //
             // Run the Verifier on the outer class
             //
-            GroovyClassVisitor visitor = verifier;
+            GroovyClassVisitor visitor = new Verifier();
             try {
                 visitor.visitClass(classNode);
             } catch (RuntimeParserException rpe) {
@@ -808,8 +754,6 @@ public class CompilationUnit extends ProcessingUnit {
             visitor = new ExtendedVerifier(source);
             visitor.visitClass(classNode);
 
-            visitor = null;
-
             // because the class may be generated even if a error was found
             // and that class may have an invalid format we fail here if needed
             getErrorCollector().failIfErrors();
@@ -822,33 +766,34 @@ public class CompilationUnit extends ProcessingUnit {
             String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName());
             // only show the file name and its extension like javac does in its stacktraces rather than the full path
             // also takes care of both \ and / depending on the host compiling environment
-            if (sourceName != null)
+            if (sourceName != null) {
                 sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1);
-            AsmClassGenerator generator = new AsmClassGenerator(source, context, classVisitor, sourceName);
+            }
 
             //
             // Run the generation and create the class (if required)
             //
-            generator.visitClass(classNode);
+            visitor = new AsmClassGenerator(source, context, classVisitor, sourceName);
+            visitor.visitClass(classNode);
 
             byte[] bytes = ((ClassWriter) classVisitor).toByteArray();
-            generatedClasses.add(new GroovyClass(classNode.getName(), bytes));
+            getClasses().add(new GroovyClass(classNode.getName(), bytes));
 
             //
             // Handle any callback that's been set
             //
-            if (CompilationUnit.this.classgenCallback != null) {
-                classgenCallback.call(classVisitor, classNode);
-            }
+            Optional.ofNullable(getClassgenCallback())
+                .ifPresent(callback -> callback.call(classVisitor, classNode));
 
             //
             // Recurse for inner classes
             //
-            LinkedList<ClassNode> innerClasses = generator.getInnerClasses();
+            LinkedList<ClassNode> innerClasses = ((AsmClassGenerator) visitor).getInnerClasses();
             while (!innerClasses.isEmpty()) {
                 classgen.call(source, context, innerClasses.removeFirst());
             }
         }
+
         @Override
         public boolean needSortedInput() {
             return true;
@@ -866,10 +811,10 @@ public class CompilationUnit extends ProcessingUnit {
                 // try classes under compilation
                 CompileUnit cu = getAST();
                 ClassNode cn = cu.getClass(name);
-                if (cn!=null) return cn;
+                if (cn != null) return cn;
                 // try inner classes
                 cn = cu.getGeneratedInnerClass(name);
-                if (cn!=null) return cn;
+                if (cn != null) return cn;
                 ClassNodeResolver.LookupResult lookupResult = getClassNodeResolver().resolveName(name, CompilationUnit.this);
                 return lookupResult == null ? null : lookupResult.getClassNode();
             }
@@ -880,8 +825,8 @@ public class CompilationUnit extends ProcessingUnit {
                 if (c.isInterface() || d.isInterface()) return ClassHelper.OBJECT_TYPE;
                 do {
                     c = c.getSuperClass();
-                } while (c!=null && !d.isDerivedFrom(c));
-                if (c==null) return ClassHelper.OBJECT_TYPE;
+                } while (c != null && !d.isDerivedFrom(c));
+                if (c == null) return ClassHelper.OBJECT_TYPE;
                 return c;
             }
             @Override
@@ -900,115 +845,153 @@ public class CompilationUnit extends ProcessingUnit {
      * Updates the phase marker on all sources.
      */
     protected void mark() throws CompilationFailedException {
-        applyToSourceUnits(mark);
-    }
-
-    /**
-     * Marks a single SourceUnit with the current phase,
-     * if it isn't already there yet.
-     */
-    private final SourceUnitOperation mark = new SourceUnitOperation() {
-        @Override
-        public void call(final SourceUnit source) throws CompilationFailedException {
+        ISourceUnitOperation mark = (final SourceUnit source) -> {
             if (source.phase < phase) {
                 source.gotoPhase(phase);
             }
             if (source.phase == phase && phaseComplete && !source.phaseComplete) {
                 source.completePhase();
             }
-        }
-    };
+        };
+        mark.doPhaseOperation(this);
+    }
 
     //---------------------------------------------------------------------------
     // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
 
     private interface PhaseOperation {
-        default void doPhaseOperation(final CompilationUnit unit) {
-            if (this instanceof SourceUnitOperation) {
-                unit.applyToSourceUnits((SourceUnitOperation) this);
-            } else if (this instanceof PrimaryClassNodeOperation) {
-                unit.applyToPrimaryClassNodes((PrimaryClassNodeOperation) this);
-            } else {
-                unit.applyToGeneratedGroovyClasses((GroovyClassOperation) this);
+        void doPhaseOperation(CompilationUnit unit);
+    }
+
+    @FunctionalInterface
+    public interface ISourceUnitOperation extends PhaseOperation {
+        void call(SourceUnit source) throws CompilationFailedException;
+
+        /**
+         * A loop driver for applying operations to all SourceUnits.
+         * Automatically skips units that have already been processed
+         * through the current phase.
+         */
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            for (String name : unit.sources.keySet()) {
+                SourceUnit source = unit.sources.get(name);
+                if (source.phase < unit.phase || (source.phase == unit.phase && !source.phaseComplete)) {
+                    try {
+                        this.call(source);
+                    } catch (CompilationFailedException e) {
+                        throw e;
+                    } catch (Exception e) {
+                        GroovyBugError gbe = new GroovyBugError(e);
+                        unit.changeBugText(gbe, source);
+                        throw gbe;
+                    } catch (GroovyBugError e) {
+                        unit.changeBugText(e, source);
+                        throw e;
+                    }
+                }
             }
+            unit.getErrorCollector().failIfErrors();
         }
     }
 
-    /**
-     * A callback interface for use in the applyToSourceUnits loop driver.
-     */
-    // TODO: convert to functional interface
-    public abstract static class SourceUnitOperation implements PhaseOperation {
-        public abstract void call(SourceUnit source) throws CompilationFailedException;
-    }
+    //---------------------------------------------------------------------------
+    // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
 
-    /**
-     * A loop driver for applying operations to all SourceUnits.
-     * Automatically skips units that have already been processed
-     * through the current phase.
-     */
-    public void applyToSourceUnits(final SourceUnitOperation body) throws CompilationFailedException {
-        for (String name : sources.keySet()) {
-            SourceUnit source = sources.get(name);
-            if ((source.phase < phase) || (source.phase == phase && !source.phaseComplete)) {
+    @FunctionalInterface
+    public interface IPrimaryClassNodeOperation extends PhaseOperation {
+        void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
+
+        /**
+         * A loop driver for applying operations to all primary ClassNodes in
+         * our AST.  Automatically skips units that have already been processed
+         * through the current phase.
+         */
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            for (ClassNode classNode : unit.getPrimaryClassNodes(this.needSortedInput())) {
+                SourceUnit context = null;
                 try {
-                    body.call(source);
+                    context = classNode.getModule().getContext();
+                    if (context == null || context.phase < unit.phase || (context.phase == unit.phase && !context.phaseComplete)) {
+                        int offset = 1;
+                        for (Iterator<InnerClassNode> it = classNode.getInnerClasses(); it.hasNext(); ) {
+                            it.next();
+                            offset += 1;
+                        }
+                        this.call(context, new GeneratorContext(unit.getAST(), offset), classNode);
+                    }
                 } catch (CompilationFailedException e) {
-                    throw e;
-                } catch (Exception e) {
-                    GroovyBugError gbe = new GroovyBugError(e);
-                    changeBugText(gbe, source);
+                    // fall through
+                } catch (NullPointerException npe) {
+                    GroovyBugError gbe = new GroovyBugError("unexpected NullPointerException", npe);
+                    unit.changeBugText(gbe, context);
                     throw gbe;
                 } catch (GroovyBugError e) {
-                    changeBugText(e, source);
+                    unit.changeBugText(e, context);
                     throw e;
+                } catch (NoClassDefFoundError | Exception e) {
+                    // effort to get more logging in case a dependency of a class is loaded
+                    // although it shouldn't have
+                    unit.convertUncaughtExceptionToCompilationError(e);
                 }
             }
+            unit.getErrorCollector().failIfErrors();
         }
-        getErrorCollector().failIfErrors();
-    }
 
-    //---------------------------------------------------------------------------
-    // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
-
-    /**
-     * An callback interface for use in the applyToPrimaryClassNodes loop driver.
-     */
-    // TODO: convert to functional interface
-    public abstract static class PrimaryClassNodeOperation implements PhaseOperation {
-        public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
-
-        public boolean needSortedInput() {
+        default boolean needSortedInput() {
             return false;
         }
     }
 
-    // TODO: convert to functional interface
-    public abstract static class GroovyClassOperation implements PhaseOperation {
-        public abstract void call(GroovyClass groovyClass) throws CompilationFailedException;
+    @FunctionalInterface
+    public interface IGroovyClassOperation extends PhaseOperation {
+        void call(GroovyClass groovyClass) throws CompilationFailedException;
+
+        @Override
+        default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException {
+            if (unit.phase != Phases.OUTPUT && !(unit.phase == Phases.CLASS_GENERATION && unit.phaseComplete)) {
+                throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + unit.getPhaseDescription());
+            }
+
+            for (GroovyClass groovyClass : unit.getClasses()) {
+                try {
+                    this.call(groovyClass);
+                } catch (CompilationFailedException e) {
+                    // fall through
+                } catch (NullPointerException npe) {
+                    throw npe;
+                } catch (GroovyBugError e) {
+                    unit.changeBugText(e, null);
+                    throw e;
+                } catch (Exception e) {
+                    throw new GroovyBugError(e);
+                }
+            }
+            unit.getErrorCollector().failIfErrors();
+        }
     }
 
-    private static int getSuperClassCount(ClassNode element) {
+    private static int getSuperClassCount(ClassNode classNode) {
         int count = 0;
-        while (element != null) {
+        while (classNode != null) {
             count += 1;
-            element = element.getSuperClass();
+            classNode = classNode.getSuperClass();
         }
         return count;
     }
 
-    private int getSuperInterfaceCount(final ClassNode element) {
+    private static int getSuperInterfaceCount(final ClassNode classNode) {
         int count = 1;
-        ClassNode[] interfaces = element.getInterfaces();
-        for (ClassNode anInterface : interfaces) {
-            count = Math.max(count, getSuperInterfaceCount(anInterface) + 1);
+        for (ClassNode face : classNode.getInterfaces()) {
+            count = Math.max(count, getSuperInterfaceCount(face) + 1);
         }
         return count;
     }
 
     private List<ClassNode> getPrimaryClassNodes(final boolean sort) {
         List<ClassNode> unsorted = getAST().getModules().stream()
-            .flatMap(module -> module.getClasses().stream()).collect(Collectors.toList());
+            .flatMap(module -> module.getClasses().stream()).collect(toList());
 
         if (!sort) return unsorted;
 
@@ -1052,46 +1035,9 @@ public class CompilationUnit extends ProcessingUnit {
         return sorted;
     }
 
-    /**
-     * A loop driver for applying operations to all primary ClassNodes in
-     * our AST.  Automatically skips units that have already been processed
-     * through the current phase.
-     */
-    public void applyToPrimaryClassNodes(final PrimaryClassNodeOperation body) throws CompilationFailedException {
-        for (ClassNode classNode : getPrimaryClassNodes(body.needSortedInput())) {
-            SourceUnit context = null;
-            try {
-                context = classNode.getModule().getContext();
-                if (context == null || context.phase < phase || (context.phase == phase && !context.phaseComplete)) {
-                    int offset = 1;
-                    for (Iterator<InnerClassNode> it = classNode.getInnerClasses(); it.hasNext(); ) {
-                        it.next();
-                        offset += 1;
-                    }
-                    body.call(context, new GeneratorContext(getAST(), offset), classNode);
-                }
-            } catch (CompilationFailedException e) {
-                // fall through, getErrorReporter().failIfErrors() will trigger
-            } catch (NullPointerException npe) {
-                GroovyBugError gbe = new GroovyBugError("unexpected NullPointerException", npe);
-                changeBugText(gbe, context);
-                throw gbe;
-            } catch (GroovyBugError e) {
-                changeBugText(e, context);
-                throw e;
-            } catch (NoClassDefFoundError | Exception e) {
-                // effort to get more logging in case a dependency of a class is loaded
-                // although it shouldn't have
-                convertUncaughtExceptionToCompilationError(e);
-            }
-        }
-
-        getErrorCollector().failIfErrors();
-    }
-
     private void convertUncaughtExceptionToCompilationError(final Throwable e) {
-        // check the exception for a nested compilation exception
         ErrorCollector nestedCollector = null;
+        // check the exception for a nested compilation exception
         for (Throwable next = e.getCause(); next != e && next != null; next = next.getCause()) {
             if (!(next instanceof MultipleCompilationErrorsException)) continue;
             MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
@@ -1103,37 +1049,50 @@ public class CompilationUnit extends ProcessingUnit {
             getErrorCollector().addCollectorContents(nestedCollector);
         } else {
             Exception err = e instanceof Exception?((Exception)e):new RuntimeException(e);
-            getErrorCollector().addError(new ExceptionMessage(err, configuration.getDebug(), this));
+            getErrorCollector().addError(new ExceptionMessage(err, debug, this));
         }
     }
 
-    public void applyToGeneratedGroovyClasses(final GroovyClassOperation body) throws CompilationFailedException {
-        if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
-            throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + getPhaseDescription());
-        }
+    private void changeBugText(final GroovyBugError e, final SourceUnit context) {
+        e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText());
+    }
 
-        for (GroovyClass gclass : this.generatedClasses) {
-            //
-            // Get the class and calculate its filesystem name
-            //
-            try {
-                body.call(gclass);
-            } catch (CompilationFailedException e) {
-                // fall through, getErrorReporter().failIfErrors() will trigger
-            } catch (NullPointerException npe) {
-                throw npe;
-            } catch (GroovyBugError e) {
-                changeBugText(e, null);
-                throw e;
-            } catch (Exception e) {
-                throw new GroovyBugError(e);
-            }
-        }
+    //--------------------------------------------------------------------------
 
-        getErrorCollector().failIfErrors();
+    @Deprecated
+    public void addPhaseOperation(final GroovyClassOperation op) {
+        addPhaseOperation((IGroovyClassOperation) op);
     }
 
-    private void changeBugText(final GroovyBugError e, final SourceUnit context) {
-        e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText());
+    @Deprecated
+    public void addPhaseOperation(final SourceUnitOperation op, final int phase) {
+        addPhaseOperation((ISourceUnitOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+        addPhaseOperation((IPrimaryClassNodeOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addFirstPhaseOperation(final PrimaryClassNodeOperation op, final int phase) {
+        addFirstPhaseOperation((IPrimaryClassNodeOperation) op, phase);
+    }
+
+    @Deprecated
+    public void addNewPhaseOperation(final SourceUnitOperation op, final int phase) {
+        addNewPhaseOperation((ISourceUnitOperation) op, phase);
+    }
+
+    @Deprecated
+    public abstract static class SourceUnitOperation implements ISourceUnitOperation {
+    }
+
+    @Deprecated
+    public abstract static class GroovyClassOperation implements IGroovyClassOperation {
+    }
+
+    @Deprecated
+    public abstract static class PrimaryClassNodeOperation implements IPrimaryClassNodeOperation {
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
index 25d8e9c..214bb98 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
@@ -1065,7 +1065,7 @@ public class CompilerConfiguration {
      * META-INF/services/org.codehaus.groovy.transform.ASTTransformation file.
      * If you explicitly add a global AST transformation in your compilation process,
      * for example using the {@link org.codehaus.groovy.control.customizers.ASTTransformationCustomizer} or
-     * using a {@link org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation},
+     * using a {@link org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation},
      * then nothing will prevent the transformation from being loaded.
      *
      * @param disabledGlobalASTTransformations a set of fully qualified class names of global AST transformations
diff --git a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
index 1dea516..e1dd084 100644
--- a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
@@ -68,7 +68,7 @@ import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
 public class StaticImportVisitor extends ClassCodeExpressionTransformer {
     private ClassNode currentClass;
     private MethodNode currentMethod;
-    private SourceUnit source;
+    private SourceUnit sourceUnit;
     private boolean inSpecialConstructorCall;
     private boolean inClosure;
     private boolean inPropertyExpression;
@@ -77,10 +77,19 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
     private boolean inAnnotation;
     private boolean inLeftExpression;
 
-    public void visitClass(ClassNode node, SourceUnit source) {
-        this.currentClass = node;
-        this.source = source;
-        super.visitClass(node);
+    public StaticImportVisitor(final ClassNode classNode, final SourceUnit sourceUnit) {
+        this.currentClass = classNode;
+        this.sourceUnit = sourceUnit;
+    }
+
+    /**
+     * Call {@link #StaticImportVisitor(ClassNode,SourceUnit)} then {@link #visitClass(ClassNode)}.
+     */
+    @Deprecated
+    public void visitClass(final ClassNode classNode, final SourceUnit sourceUnit) {
+        this.currentClass = classNode;
+        this.sourceUnit = sourceUnit;
+        visitClass(classNode);
     }
 
     @Override
@@ -565,7 +574,8 @@ public class StaticImportVisitor extends ClassCodeExpressionTransformer {
         return new StaticMethodCallExpression(type.getPlainNodeReference(), name, args);
     }
 
+    @Override
     protected SourceUnit getSourceUnit() {
-        return source;
+        return sourceUnit;
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
index 9d0b565..63583c5 100644
--- a/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
+++ b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java
@@ -29,10 +29,10 @@ import org.codehaus.groovy.control.CompilePhase;
  *
  * @since 1.8.0
  */
-public abstract class CompilationCustomizer extends CompilationUnit.PrimaryClassNodeOperation {
+public abstract class CompilationCustomizer implements CompilationUnit.IPrimaryClassNodeOperation {
     private final CompilePhase phase;
 
-    public CompilationCustomizer(CompilePhase phase) {
+    public CompilationCustomizer(final CompilePhase phase) {
         this.phase = phase;
     }
 
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
index de646aa..f43141b 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaAwareCompilationUnit.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.tools.javac;
 
 import groovy.lang.GroovyClassLoader;
 import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.ModuleNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
@@ -75,35 +76,24 @@ public class JavaAwareCompilationUnit extends CompilationUnit {
         this.stubGenerator = new JavaStubGenerator(generationGoal, false, useJava5, encoding);
         this.keepStubs = Boolean.TRUE.equals(options.get("keepStubs"));
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode node) throws CompilationFailedException {
-                if (!javaSources.isEmpty()) {
-                    VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                    scopeVisitor.visitClass(node);
-                    new JavaAwareResolveVisitor(JavaAwareCompilationUnit.this).startResolving(node, source);
-                    AnnotationConstantsVisitor acv = new AnnotationConstantsVisitor();
-                    acv.visitClass(node, source);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (!javaSources.isEmpty()) {
+                new VariableScopeVisitor(source).visitClass(classNode);
+                new JavaAwareResolveVisitor(this).startResolving(classNode, source);
+                new AnnotationConstantsVisitor().visitClass(classNode, source);
             }
         }, Phases.CONVERSION);
-        addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                ASTTransformationCollectorCodeVisitor collector =
-                        new ASTTransformationCollectorCodeVisitor(source, JavaAwareCompilationUnit.this.getTransformLoader());
-                collector.visitClass(classNode);
-            }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(source, getTransformLoader());
+            visitor.visitClass(classNode);
         }, Phases.CONVERSION);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                try {
-                    if (!javaSources.isEmpty()) stubGenerator.generateClass(classNode);
-                } catch (FileNotFoundException fnfe) {
-                    source.addException(fnfe);
-                }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            try {
+                if (!javaSources.isEmpty()) stubGenerator.generateClass(classNode);
+            } catch (FileNotFoundException fnfe) {
+                source.addException(fnfe);
             }
         }, Phases.CONVERSION);
     }
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
index e5aefae..46e1d42 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubCompilationUnit.java
@@ -53,23 +53,17 @@ public class JavaStubCompilationUnit extends CompilationUnit {
         String encoding = configuration.getSourceEncoding();
         stubGenerator = new JavaStubGenerator(destDir, false, useJava5, encoding);
 
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(SourceUnit source, GeneratorContext context, ClassNode node) throws CompilationFailedException {
-                VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
-                scopeVisitor.visitClass(node);
-                new JavaAwareResolveVisitor(JavaStubCompilationUnit.this).startResolving(node, source);
-            }
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            new VariableScopeVisitor(source).visitClass(classNode);
+            new JavaAwareResolveVisitor(this).startResolving(classNode, source);
         }, Phases.CONVERSION);
-        addPhaseOperation(new PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode node) throws CompilationFailedException {
-                try {
-                    stubGenerator.generateClass(node);
-                    stubCount++;
-                } catch (FileNotFoundException e) {
-                    source.addException(e);
-                }
+
+        addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            try {
+                stubGenerator.generateClass(classNode);
+                stubCount += 1;
+            } catch (FileNotFoundException e) {
+                source.addException(e);
             }
         }, Phases.CONVERSION);
     }
diff --git a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
index c2c508f..ea950a6 100644
--- a/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/ASTTransformationVisitor.java
@@ -27,10 +27,10 @@ import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
 import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.ASTTransformationsContext;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.Phases;
@@ -197,18 +197,14 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
 
 
     public static void addPhaseOperations(final CompilationUnit compilationUnit) {
-        final ASTTransformationsContext context = compilationUnit.getASTTransformationsContext();
+        ASTTransformationsContext context = compilationUnit.getASTTransformationsContext();
         addGlobalTransforms(context);
 
-        compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                ASTTransformationCollectorCodeVisitor collector =
-                    new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader());
-                collector.visitClass(classNode);
-            }
+        compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext ignore, final ClassNode classNode) -> {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(source, compilationUnit.getTransformLoader());
+            visitor.visitClass(classNode);
         }, Phases.SEMANTIC_ANALYSIS);
         for (CompilePhase phase : CompilePhase.values()) {
-            final ASTTransformationVisitor visitor = new ASTTransformationVisitor(phase, context);
             switch (phase) {
                 case INITIALIZATION:
                 case PARSING:
@@ -217,11 +213,10 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
                     break;
 
                 default:
-                    compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-                        public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
-                            visitor.source = source;
-                            visitor.visitClass(classNode);
-                        }
+                    compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext ignore, final ClassNode classNode) -> {
+                        ASTTransformationVisitor visitor = new ASTTransformationVisitor(phase, context);
+                        visitor.source = source;
+                        visitor.visitClass(classNode);
                     }, phase.getPhaseNumber());
                     break;
 
@@ -322,8 +317,8 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
         GroovyClassLoader transformLoader = compilationUnit.getTransformLoader();
         for (Map.Entry<String, URL> entry : transformNames.entrySet()) {
             try {
-                Class gTransClass = transformLoader.loadClass(entry.getKey(), false, true, false);
-                GroovyASTTransformation transformAnnotation = (GroovyASTTransformation) gTransClass.getAnnotation(GroovyASTTransformation.class);
+                Class<?> gTransClass = transformLoader.loadClass(entry.getKey(), false, true, false);
+                GroovyASTTransformation transformAnnotation = gTransClass.getAnnotation(GroovyASTTransformation.class);
                 if (transformAnnotation == null) {
                     compilationUnit.getErrorCollector().addWarning(new WarningMessage(
                         WarningMessage.POSSIBLE_ERRORS,
@@ -335,14 +330,12 @@ public final class ASTTransformationVisitor extends ClassCodeVisitorSupport {
                     continue;
                 }
                 if (ASTTransformation.class.isAssignableFrom(gTransClass)) {
-                    final ASTTransformation instance = (ASTTransformation)gTransClass.getDeclaredConstructor().newInstance();
+                    ASTTransformation instance = (ASTTransformation) gTransClass.getDeclaredConstructor().newInstance();
                     if (instance instanceof CompilationUnitAware) {
                         ((CompilationUnitAware)instance).setCompilationUnit(compilationUnit);
                     }
-                    CompilationUnit.SourceUnitOperation suOp = new CompilationUnit.SourceUnitOperation() {
-                        public void call(SourceUnit source) throws CompilationFailedException {
-                            instance.visit(new ASTNode[] {source.getAST()}, source);
-                        }
+                    CompilationUnit.ISourceUnitOperation suOp = source -> {
+                        instance.visit(new ASTNode[] {source.getAST()}, source);
                     };
                     if (isFirstScan) {
                         compilationUnit.addPhaseOperation(suOp, transformAnnotation.phase().getPhaseNumber());
diff --git a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
index 99b72c1..3dec6b8 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -49,14 +49,13 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.ast.tools.GeneralUtils;
-import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.asm.InvocationWriter;
 import org.codehaus.groovy.classgen.asm.MopWriter;
 import org.codehaus.groovy.classgen.asm.TypeChooser;
 import org.codehaus.groovy.classgen.asm.WriterControllerFactory;
 import org.codehaus.groovy.classgen.asm.sc.StaticCompilationMopWriter;
 import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser;
-import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilationUnit.IPrimaryClassNodeOperation;
 import org.codehaus.groovy.control.SourceUnit;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
@@ -155,13 +154,10 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
     private void addDynamicOuterClassAccessorsCallback(final ClassNode outer) {
         if (outer != null) {
             if (!isStaticallyCompiled(outer) && outer.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK) == null) {
-                outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, new CompilationUnit.PrimaryClassNodeOperation() {
-                    @Override
-                    public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) {
-                        if (classNode == outer) {
-                            addPrivateBridgeMethods(classNode);
-                            addPrivateFieldsAccessors(classNode);
-                        }
+                outer.putNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK, (IPrimaryClassNodeOperation) (source, context, classNode) -> {
+                    if (classNode == outer) {
+                        addPrivateBridgeMethods(classNode);
+                        addPrivateFieldsAccessors(classNode);
                     }
                 });
             }
diff --git a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
index a12584c..70952bf 100644
--- a/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.GroovyClassVisitor;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
@@ -47,7 +48,6 @@ import org.codehaus.groovy.ast.tools.GeneralUtils;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
 import org.codehaus.groovy.classgen.Verifier;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
@@ -340,19 +340,16 @@ public class TraitASTTransformation extends AbstractASTTransformation implements
     }
 
     private void registerASTTransformations(final ClassNode helper) {
-        ASTTransformationCollectorCodeVisitor collector = new ASTTransformationCollectorCodeVisitor(
-                unit, compilationUnit.getTransformLoader()
-        );
-        collector.visitClass(helper);
+        {
+            GroovyClassVisitor visitor = new ASTTransformationCollectorCodeVisitor(unit, compilationUnit.getTransformLoader());
+            visitor.visitClass(helper);
+        }
         // Perform an additional phase which has to be done *after* type checking
-        compilationUnit.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                if (classNode==helper) {
-                    PostTypeCheckingExpressionReplacer replacer = new PostTypeCheckingExpressionReplacer(source);
-                    replacer.visitClass(helper);
-                }
-            }
+        compilationUnit.addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            if (classNode != helper) return;
+
+            GroovyClassVisitor visitor = new PostTypeCheckingExpressionReplacer(source);
+            visitor.visitClass(helper);
         }, CompilePhase.INSTRUCTION_SELECTION.getPhaseNumber());
     }
 
diff --git a/src/test/groovy/lang/GroovyClassLoaderTest.groovy b/src/test/groovy/lang/GroovyClassLoaderTest.groovy
index 9466beb..41a41e3 100644
--- a/src/test/groovy/lang/GroovyClassLoaderTest.groovy
+++ b/src/test/groovy/lang/GroovyClassLoaderTest.groovy
@@ -270,13 +270,15 @@ class GroovyClassLoaderTestCustomGCL extends GroovyClassLoader {
     }
 }
 
-class GroovyClassLoaderTestPropertyAdder extends CompilationUnit.PrimaryClassNodeOperation {
+class GroovyClassLoaderTestPropertyAdder implements CompilationUnit.IPrimaryClassNodeOperation {
+    @Override
     void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
         classNode.addProperty("id", ClassNode.ACC_PUBLIC, ClassHelper.long_TYPE, null, null, null)
     }
 }
 
 class GroovyClassLoaderTestCustomPhaseOperation extends GroovyClassLoader {
+    @Override
     CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) {
         def cu = super.createCompilationUnit(config, source)
         cu.addPhaseOperation(new GroovyClassLoaderTestPropertyAdder(), Phases.CONVERSION)
diff --git a/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy b/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
index 9fc89b0..75d3196 100644
--- a/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
+++ b/src/test/org/codehaus/groovy/ClosureAndInnerClassNodeStructureTest.groovy
@@ -20,7 +20,6 @@ package org.codehaus.groovy
 
 import groovy.test.GroovyTestCase
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.control.SourceUnit
 import org.codehaus.groovy.control.Phases
@@ -53,7 +52,8 @@ class ClosureAndInnerClassNodeStructureTest extends GroovyTestCase {
 
         def classNodes = [:]
 
-        cu.addPhaseOperation(new PrimaryClassNodeOperation() {
+        cu.addPhaseOperation(new CompilationUnit.IPrimaryClassNodeOperation() {
+            @Override
             void call(SourceUnit source, GeneratorContext context, ClassNode cn) {
                 def recurse = { ClassNode node ->
                     classNodes[node.name] = node
diff --git a/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java b/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
index e903298..5788b39 100644
--- a/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
+++ b/src/test/org/codehaus/groovy/tools/gse/DependencyTest.java
@@ -18,39 +18,32 @@
  */
 package org.codehaus.groovy.tools.gse;
 
-import java.io.StringBufferInputStream;
-import java.util.Set;
-
+import groovy.test.GroovyTestCase;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
-import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.control.SourceUnit;
 
-import groovy.test.GroovyTestCase;
+import java.io.StringBufferInputStream;
+import java.util.Set;
 
 @SuppressWarnings("deprecation")
 public class DependencyTest extends GroovyTestCase {
     private CompilationUnit cu;
     StringSetMap cache;
-    
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        cu = new CompilationUnit();
         cache = new StringSetMap();
-        cu.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() {
-            @Override
-            public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) 
-                throws CompilationFailedException 
-            {   
-                DependencyTracker dt = new DependencyTracker(source,cache);
-                dt.visitClass(classNode);
-            }
+        cu = new CompilationUnit();
+        cu.addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
+            DependencyTracker dt = new DependencyTracker(source, cache);
+            dt.visitClass(classNode);
         }, Phases.CLASS_GENERATION);
     }
-    
+
     public void testDep(){
         cu.addSource("testDep.gtest", new StringBufferInputStream(
                 "class C1 {}\n" +
@@ -64,22 +57,22 @@ public class DependencyTest extends GroovyTestCase {
         assertEquals(cache.get("C1").size(),1);
         assertEquals(cache.get("C2").size(),1);
         assertEquals(cache.get("C3").size(),1);
-        
+
         Set<String> dep = cache.get("A1");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("C1"));
-        
+
         dep = cache.get("A2");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("C2"));
-        
+
         dep = cache.get("A3");
         assertEquals(dep.size(),4);
         assertTrue(dep.contains("C1"));
         assertTrue(dep.contains("C2"));
         assertTrue(dep.contains("C3"));
     }
-    
+
     public void testTransitiveDep(){
         cu.addSource("testTransitiveDep.gtest", new StringBufferInputStream(
                 "class A1 {}\n" +
@@ -88,19 +81,19 @@ public class DependencyTest extends GroovyTestCase {
         ));
         cu.compile(Phases.CLASS_GENERATION);
         cache.makeTransitiveHull();
-         
+
         Set<String> dep = cache.get("A1");
         assertEquals(dep.size(),1);
-        
+
         dep = cache.get("A2");
         assertEquals(dep.size(),2);
         assertTrue(dep.contains("A1"));
-        
+
         dep = cache.get("A3");
         assertEquals(dep.size(),3);
         assertTrue(dep.contains("A1"));
         assertTrue(dep.contains("A2"));
     }
-    
+
 
 }
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
index 292a8ae..1edda5f 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
@@ -95,7 +95,6 @@ import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.classgen.Verifier
 import org.codehaus.groovy.control.CompilationFailedException
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
@@ -189,7 +188,7 @@ and [compilephase] is a valid Integer based org.codehaus.groovy.control.CompileP
  * An adapter from ASTNode tree to source code.
  */
 @CompileStatic
-class AstNodeToScriptVisitor extends PrimaryClassNodeOperation implements GroovyCodeVisitor, GroovyClassVisitor {
+class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
     Stack<String> classNameStack = new Stack<String>()
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
index d42f3ba..c9e78f4 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ScriptToTreeNodeAdapter.groovy
@@ -98,7 +98,6 @@ import org.codehaus.groovy.classgen.GeneratorContext
 import org.codehaus.groovy.classgen.asm.BytecodeHelper
 import org.codehaus.groovy.control.CompilationFailedException
 import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.SourceUnit
 
@@ -275,7 +274,7 @@ class ScriptToTreeNodeAdapter {
 /**
  * This Node Operation builds up a root tree node for the viewer.
  */
-class TreeNodeBuildingNodeOperation extends PrimaryClassNodeOperation {
+class TreeNodeBuildingNodeOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
     final root
     final sourceCollected = new AtomicBoolean(false)
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
index 47e7990..281a31c 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/ScriptVariableAnalyzer.groovy
@@ -69,10 +69,10 @@ class ScriptVariableAnalyzer {
     }
 
     /**
-     * custom PrimaryClassNodeOperation
+     * custom IPrimaryClassNodeOperation
      * to be able to hook our code visitor
      */
-    static class VisitorSourceOperation extends CompilationUnit.PrimaryClassNodeOperation {
+    static class VisitorSourceOperation implements CompilationUnit.IPrimaryClassNodeOperation {
 
         final GroovyClassVisitor visitor
 
diff --git a/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java b/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
index 52b98a3..561beb2 100644
--- a/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
+++ b/subprojects/groovy-jsr223/src/test/java/org/codehaus/groovy/jsr223/JSR223SecurityTest.java
@@ -25,7 +25,6 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.classgen.GeneratorContext;
 import org.codehaus.groovy.control.CompilationUnit;
-import org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation;
 import org.codehaus.groovy.control.CompilerConfiguration;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.control.SourceUnit;
@@ -35,6 +34,7 @@ import org.junit.Test;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
+
 import java.lang.reflect.Field;
 import java.security.CodeSource;
 import java.util.HashSet;
@@ -161,11 +161,12 @@ class CustomGroovyClassLoader extends GroovyClassLoader {
     }
 }
 
-class CustomPrimaryClassNodeOperation extends PrimaryClassNodeOperation {
-
+class CustomPrimaryClassNodeOperation implements CompilationUnit.IPrimaryClassNodeOperation {
+    @Override
     public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
-        for (Object statement : source.getAST().getStatementBlock().getStatements())
+        for (Object statement : source.getAST().getStatementBlock().getStatements()) {
             ((ExpressionStatement) statement).visit(new CustomCodeVisitorSupport());
+        }
     }
 }
 
diff --git a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
index a4a5616..8d3b8d4 100644
--- a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
+++ b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
@@ -147,7 +147,7 @@ class AstDumper {
  * An adapter from ASTNode tree to source code.
  */
 @CompileStatic
-class AstNodeToScriptVisitor extends CompilationUnit.PrimaryClassNodeOperation implements GroovyCodeVisitor, GroovyClassVisitor {
+class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
     Stack<String> classNameStack = new Stack<String>()
@@ -164,6 +164,7 @@ class AstNodeToScriptVisitor extends CompilationUnit.PrimaryClassNodeOperation i
         this.scriptHasBeenVisited = false
     }
 
+    @Override
     void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
 
         visitPackage(source?.getAST()?.getPackage())