You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2017/12/17 14:01:22 UTC
[11/62] [abbrv] [partial] groovy git commit: Move Java source set
into `src/main/java`
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/SecureASTCustomizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/SecureASTCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/SecureASTCustomizer.java
new file mode 100644
index 0000000..1bb302b
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/SecureASTCustomizer.java
@@ -0,0 +1,1189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.ImportNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.AttributeExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BitwiseNegationExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.CastExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ClosureListExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.GStringExpression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.MethodPointerExpression;
+import org.codehaus.groovy.ast.expr.NotExpression;
+import org.codehaus.groovy.ast.expr.PostfixExpression;
+import org.codehaus.groovy.ast.expr.PrefixExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.RangeExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.expr.SpreadMapExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.expr.TernaryExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.UnaryMinusExpression;
+import org.codehaus.groovy.ast.expr.UnaryPlusExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.AssertStatement;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.BreakStatement;
+import org.codehaus.groovy.ast.stmt.CaseStatement;
+import org.codehaus.groovy.ast.stmt.CatchStatement;
+import org.codehaus.groovy.ast.stmt.ContinueStatement;
+import org.codehaus.groovy.ast.stmt.DoWhileStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ForStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.stmt.SwitchStatement;
+import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
+import org.codehaus.groovy.ast.stmt.ThrowStatement;
+import org.codehaus.groovy.ast.stmt.TryCatchStatement;
+import org.codehaus.groovy.ast.stmt.WhileStatement;
+import org.codehaus.groovy.classgen.BytecodeExpression;
+import org.codehaus.groovy.classgen.GeneratorContext;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.syntax.Token;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This customizer allows securing source code by controlling what code constructs are allowed. For example, if you only
+ * want to allow arithmetic operations in a groovy shell, you can configure this customizer to restrict package imports,
+ * method calls and so on.
+ * <p>
+ * Most of the security customization options found in this class work with either blacklist or whitelist. This means that, for a
+ * single option, you can set a whitelist OR a blacklist, but not both. You can mix whitelist/blacklist strategies for
+ * different options. For example, you can have import whitelist and tokens blacklist.
+ * <p>
+ * The recommended way of securing shells is to use whitelists because it is guaranteed that future features of the
+ * Groovy language won't be allowed by defaut. Using blacklists, you can limit the features of the languages by opting
+ * out, but new language features would require you to update your configuration.
+ * <p>
+ * If you set neither a whitelist nor a blacklist, then everything is authorized.
+ * <p>
+ * Combinations of import and star imports constraints are authorized as long as you use the same type of list for both.
+ * For example, you may use an import whitelist and a star import whitelist together, but you cannot use an import white
+ * list with a star import blacklist. static imports are handled separately, meaning that blacklisting an import <b>
+ * does not</b> prevent from using a static import.
+ * <p>
+ * Eventually, if the features provided here are not sufficient, you may implement custom AST filtering handlers, either
+ * implementing the {@link StatementChecker} interface or {@link ExpressionChecker} interface then register your
+ * handlers thanks to the {@link #addExpressionCheckers(org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker...)}
+ * and {@link #addStatementCheckers(org.codehaus.groovy.control.customizers.SecureASTCustomizer.StatementChecker...)}
+ * methods.
+ * <p>
+ * Here is an example of usage. We will create a groovy classloader which only supports arithmetic operations and imports
+ * the java.lang.Math classes by default.
+ *
+ * <pre>
+ * final ImportCustomizer imports = new ImportCustomizer().addStaticStars('java.lang.Math') // add static import of java.lang.Math
+ * final SecureASTCustomizer secure = new SecureASTCustomizer()
+ * secure.with {
+ * closuresAllowed = false
+ * methodDefinitionAllowed = false
+ *
+ * importsWhitelist = []
+ * staticImportsWhitelist = []
+ * staticStarImportsWhitelist = ['java.lang.Math'] // only java.lang.Math is allowed
+ *
+ * tokensWhitelist = [
+ * PLUS,
+ * MINUS,
+ * MULTIPLY,
+ * DIVIDE,
+ * MOD,
+ * POWER,
+ * PLUS_PLUS,
+ * MINUS_MINUS,
+ * COMPARE_EQUAL,
+ * COMPARE_NOT_EQUAL,
+ * COMPARE_LESS_THAN,
+ * COMPARE_LESS_THAN_EQUAL,
+ * COMPARE_GREATER_THAN,
+ * COMPARE_GREATER_THAN_EQUAL,
+ * ].asImmutable()
+ *
+ * constantTypesClassesWhiteList = [
+ * Integer,
+ * Float,
+ * Long,
+ * Double,
+ * BigDecimal,
+ * Integer.TYPE,
+ * Long.TYPE,
+ * Float.TYPE,
+ * Double.TYPE
+ * ].asImmutable()
+ *
+ * receiversClassesWhiteList = [
+ * Math,
+ * Integer,
+ * Float,
+ * Double,
+ * Long,
+ * BigDecimal
+ * ].asImmutable()
+ * }
+ * CompilerConfiguration config = new CompilerConfiguration()
+ * config.addCompilationCustomizers(imports, secure)
+ * GroovyClassLoader loader = new GroovyClassLoader(this.class.classLoader, config)
+ * </pre>
+ *
+ * @author Cedric Champeau
+ * @author Guillaume Laforge
+ * @author Hamlet D'Arcy
+ * @since 1.8.0
+ */
+public class SecureASTCustomizer extends CompilationCustomizer {
+
+ private boolean isPackageAllowed = true;
+ private boolean isMethodDefinitionAllowed = true;
+ private boolean isClosuresAllowed = true;
+
+ // imports
+ private List<String> importsWhitelist;
+ private List<String> importsBlacklist;
+
+ // static imports
+ private List<String> staticImportsWhitelist;
+ private List<String> staticImportsBlacklist;
+
+ // star imports
+ private List<String> starImportsWhitelist;
+ private List<String> starImportsBlacklist;
+
+ // static star imports
+ private List<String> staticStarImportsWhitelist;
+ private List<String> staticStarImportsBlacklist;
+
+
+ // indirect import checks
+ // if set to true, then security rules on imports will also be applied on classnodes.
+ // Direct instantiation of classes without imports will therefore also fail if this option is enabled
+ private boolean isIndirectImportCheckEnabled;
+
+ // statements
+ private List<Class<? extends Statement>> statementsWhitelist;
+ private List<Class<? extends Statement>> statementsBlacklist;
+ private final List<StatementChecker> statementCheckers = new LinkedList<StatementChecker>();
+
+ // expressions
+ private List<Class<? extends Expression>> expressionsWhitelist;
+ private List<Class<? extends Expression>> expressionsBlacklist;
+ private final List<ExpressionChecker> expressionCheckers = new LinkedList<ExpressionChecker>();
+
+ // tokens from Types
+ private List<Integer> tokensWhitelist;
+ private List<Integer> tokensBlacklist;
+
+ // constant types
+ private List<String> constantTypesWhiteList;
+ private List<String> constantTypesBlackList;
+
+ // receivers
+ private List<String> receiversWhiteList;
+ private List<String> receiversBlackList;
+
+ public SecureASTCustomizer() {
+ super(CompilePhase.CANONICALIZATION);
+ }
+
+ public boolean isMethodDefinitionAllowed() {
+ return isMethodDefinitionAllowed;
+ }
+
+ public void setMethodDefinitionAllowed(final boolean methodDefinitionAllowed) {
+ isMethodDefinitionAllowed = methodDefinitionAllowed;
+ }
+
+ public boolean isPackageAllowed() {
+ return isPackageAllowed;
+ }
+
+ public boolean isClosuresAllowed() {
+ return isClosuresAllowed;
+ }
+
+ public void setClosuresAllowed(final boolean closuresAllowed) {
+ isClosuresAllowed = closuresAllowed;
+ }
+
+ public void setPackageAllowed(final boolean packageAllowed) {
+ isPackageAllowed = packageAllowed;
+ }
+
+ public List<String> getImportsBlacklist() {
+ return importsBlacklist;
+ }
+
+ public void setImportsBlacklist(final List<String> importsBlacklist) {
+ if (importsWhitelist != null || starImportsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.importsBlacklist = importsBlacklist;
+ }
+
+ public List<String> getImportsWhitelist() {
+ return importsWhitelist;
+ }
+
+ public void setImportsWhitelist(final List<String> importsWhitelist) {
+ if (importsBlacklist != null || starImportsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.importsWhitelist = importsWhitelist;
+ }
+
+ public List<String> getStarImportsBlacklist() {
+ return starImportsBlacklist;
+ }
+
+ public void setStarImportsBlacklist(final List<String> starImportsBlacklist) {
+ if (importsWhitelist != null || starImportsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.starImportsBlacklist = normalizeStarImports(starImportsBlacklist);
+ if (this.importsBlacklist == null) importsBlacklist = Collections.emptyList();
+ }
+
+ public List<String> getStarImportsWhitelist() {
+ return starImportsWhitelist;
+ }
+
+ public void setStarImportsWhitelist(final List<String> starImportsWhitelist) {
+ if (importsBlacklist != null || starImportsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.starImportsWhitelist = normalizeStarImports(starImportsWhitelist);
+ if (this.importsWhitelist == null) importsWhitelist = Collections.emptyList();
+ }
+
+ /**
+ * Ensures that every star import ends with .* as this is the expected syntax in import checks.
+ */
+ private static List<String> normalizeStarImports(List<String> starImports) {
+ List<String> result = new ArrayList<String>(starImports.size());
+ for (String starImport : starImports) {
+ if (starImport.endsWith(".*")) {
+ result.add(starImport);
+ } else if (starImport.endsWith(".")) {
+ result.add(starImport + "*");
+ } else {
+ result.add(starImport + ".*");
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ public List<String> getStaticImportsBlacklist() {
+ return staticImportsBlacklist;
+ }
+
+ public void setStaticImportsBlacklist(final List<String> staticImportsBlacklist) {
+ if (staticImportsWhitelist != null || staticStarImportsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.staticImportsBlacklist = staticImportsBlacklist;
+ }
+
+ public List<String> getStaticImportsWhitelist() {
+ return staticImportsWhitelist;
+ }
+
+ public void setStaticImportsWhitelist(final List<String> staticImportsWhitelist) {
+ if (staticImportsBlacklist != null || staticStarImportsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.staticImportsWhitelist = staticImportsWhitelist;
+ }
+
+ public List<String> getStaticStarImportsBlacklist() {
+ return staticStarImportsBlacklist;
+ }
+
+ public void setStaticStarImportsBlacklist(final List<String> staticStarImportsBlacklist) {
+ if (staticImportsWhitelist != null || staticStarImportsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.staticStarImportsBlacklist = normalizeStarImports(staticStarImportsBlacklist);
+ if (this.staticImportsBlacklist == null) this.staticImportsBlacklist = Collections.emptyList();
+ }
+
+ public List<String> getStaticStarImportsWhitelist() {
+ return staticStarImportsWhitelist;
+ }
+
+ public void setStaticStarImportsWhitelist(final List<String> staticStarImportsWhitelist) {
+ if (staticImportsBlacklist != null || staticStarImportsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.staticStarImportsWhitelist = normalizeStarImports(staticStarImportsWhitelist);
+ if (this.staticImportsWhitelist == null) this.staticImportsWhitelist = Collections.emptyList();
+ }
+
+ public List<Class<? extends Expression>> getExpressionsBlacklist() {
+ return expressionsBlacklist;
+ }
+
+ public void setExpressionsBlacklist(final List<Class<? extends Expression>> expressionsBlacklist) {
+ if (expressionsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.expressionsBlacklist = expressionsBlacklist;
+ }
+
+ public List<Class<? extends Expression>> getExpressionsWhitelist() {
+ return expressionsWhitelist;
+ }
+
+ public void setExpressionsWhitelist(final List<Class<? extends Expression>> expressionsWhitelist) {
+ if (expressionsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.expressionsWhitelist = expressionsWhitelist;
+ }
+
+ public List<Class<? extends Statement>> getStatementsBlacklist() {
+ return statementsBlacklist;
+ }
+
+ public void setStatementsBlacklist(final List<Class<? extends Statement>> statementsBlacklist) {
+ if (statementsWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.statementsBlacklist = statementsBlacklist;
+ }
+
+ public List<Class<? extends Statement>> getStatementsWhitelist() {
+ return statementsWhitelist;
+ }
+
+ public void setStatementsWhitelist(final List<Class<? extends Statement>> statementsWhitelist) {
+ if (statementsBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.statementsWhitelist = statementsWhitelist;
+ }
+
+ public List<Integer> getTokensBlacklist() {
+ return tokensBlacklist;
+ }
+
+ public boolean isIndirectImportCheckEnabled() {
+ return isIndirectImportCheckEnabled;
+ }
+
+ /**
+ * Set this option to true if you want your import rules to be checked against every class node. This means that if
+ * someone uses a fully qualified class name, then it will also be checked against the import rules, preventing, for
+ * example, instantiation of classes without imports thanks to FQCN.
+ *
+ * @param indirectImportCheckEnabled set to true to enable indirect checks
+ */
+ public void setIndirectImportCheckEnabled(final boolean indirectImportCheckEnabled) {
+ isIndirectImportCheckEnabled = indirectImportCheckEnabled;
+ }
+
+ /**
+ * Sets the list of tokens which are blacklisted.
+ *
+ * @param tokensBlacklist the tokens. The values of the tokens must be those of {@link org.codehaus.groovy.syntax.Types}
+ */
+ public void setTokensBlacklist(final List<Integer> tokensBlacklist) {
+ if (tokensWhitelist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.tokensBlacklist = tokensBlacklist;
+ }
+
+ public List<Integer> getTokensWhitelist() {
+ return tokensWhitelist;
+ }
+
+ /**
+ * Sets the list of tokens which are whitelisted.
+ *
+ * @param tokensWhitelist the tokens. The values of the tokens must be those of {@link org.codehaus.groovy.syntax.Types}
+ */
+ public void setTokensWhitelist(final List<Integer> tokensWhitelist) {
+ if (tokensBlacklist != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.tokensWhitelist = tokensWhitelist;
+ }
+
+ public void addStatementCheckers(StatementChecker... checkers) {
+ statementCheckers.addAll(Arrays.asList(checkers));
+ }
+
+ public void addExpressionCheckers(ExpressionChecker... checkers) {
+ expressionCheckers.addAll(Arrays.asList(checkers));
+ }
+
+ public List<String> getConstantTypesBlackList() {
+ return constantTypesBlackList;
+ }
+
+ public void setConstantTypesBlackList(final List<String> constantTypesBlackList) {
+ if (constantTypesWhiteList != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.constantTypesBlackList = constantTypesBlackList;
+ }
+
+ public List<String> getConstantTypesWhiteList() {
+ return constantTypesWhiteList;
+ }
+
+ public void setConstantTypesWhiteList(final List<String> constantTypesWhiteList) {
+ if (constantTypesBlackList != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.constantTypesWhiteList = constantTypesWhiteList;
+ }
+
+ /**
+ * An alternative way of setting constant types.
+ *
+ * @param constantTypesWhiteList a list of classes.
+ */
+ public void setConstantTypesClassesWhiteList(final List<Class> constantTypesWhiteList) {
+ List<String> values = new LinkedList<String>();
+ for (Class aClass : constantTypesWhiteList) {
+ values.add(aClass.getName());
+ }
+ setConstantTypesWhiteList(values);
+ }
+
+ /**
+ * An alternative way of setting constant types.
+ *
+ * @param constantTypesBlackList a list of classes.
+ */
+ public void setConstantTypesClassesBlackList(final List<Class> constantTypesBlackList) {
+ List<String> values = new LinkedList<String>();
+ for (Class aClass : constantTypesBlackList) {
+ values.add(aClass.getName());
+ }
+ setConstantTypesBlackList(values);
+ }
+
+ public List<String> getReceiversBlackList() {
+ return receiversBlackList;
+ }
+
+ /**
+ * Sets the list of classes which deny method calls.
+ *
+ * Please note that since Groovy is a dynamic language, and
+ * this class performs a static type check, it will be reletively
+ * simple to bypass any blacklist unless the receivers blacklist contains, at
+ * a minimum, Object, Script, GroovyShell, and Eval. Additionally,
+ * it is necessary to also blacklist MethodPointerExpression in the
+ * expressions blacklist for the receivers blacklist to function
+ * as a security check.
+ *
+ * @param receiversBlackList the list of refused classes, as fully qualified names
+ */
+ public void setReceiversBlackList(final List<String> receiversBlackList) {
+ if (receiversWhiteList != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.receiversBlackList = receiversBlackList;
+ }
+
+ /**
+ * An alternative way of setting {@link #setReceiversBlackList(java.util.List) receiver classes}.
+ *
+ * @param receiversBlacklist a list of classes.
+ */
+ public void setReceiversClassesBlackList(final List<Class> receiversBlacklist) {
+ List<String> values = new LinkedList<String>();
+ for (Class aClass : receiversBlacklist) {
+ values.add(aClass.getName());
+ }
+ setReceiversBlackList(values);
+ }
+
+ public List<String> getReceiversWhiteList() {
+ return receiversWhiteList;
+ }
+
+ /**
+ * Sets the list of classes which may accept method calls.
+ *
+ * @param receiversWhiteList the list of accepted classes, as fully qualified names
+ */
+ public void setReceiversWhiteList(final List<String> receiversWhiteList) {
+ if (receiversBlackList != null) {
+ throw new IllegalArgumentException("You are not allowed to set both whitelist and blacklist");
+ }
+ this.receiversWhiteList = receiversWhiteList;
+ }
+
+ /**
+ * An alternative way of setting {@link #setReceiversWhiteList(java.util.List) receiver classes}.
+ *
+ * @param receiversWhitelist a list of classes.
+ */
+ public void setReceiversClassesWhiteList(final List<Class> receiversWhitelist) {
+ List<String> values = new LinkedList<String>();
+ for (Class aClass : receiversWhitelist) {
+ values.add(aClass.getName());
+ }
+ setReceiversWhiteList(values);
+ }
+
+ @Override
+ public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
+ final ModuleNode ast = source.getAST();
+ if (!isPackageAllowed && ast.getPackage() != null) {
+ throw new SecurityException("Package definitions are not allowed");
+ }
+ checkMethodDefinitionAllowed(classNode);
+
+ // verify imports
+ if (importsBlacklist != null || importsWhitelist != null || starImportsBlacklist != null || starImportsWhitelist != null) {
+ for (ImportNode importNode : ast.getImports()) {
+ final String className = importNode.getClassName();
+ assertImportIsAllowed(className);
+ }
+ for (ImportNode importNode : ast.getStarImports()) {
+ final String className = importNode.getPackageName();
+ assertStarImportIsAllowed(className + "*");
+ }
+ }
+
+ // verify static imports
+ if (staticImportsBlacklist != null || staticImportsWhitelist != null || staticStarImportsBlacklist != null || staticStarImportsWhitelist != null) {
+ for (Map.Entry<String, ImportNode> entry : ast.getStaticImports().entrySet()) {
+ final String className = entry.getValue().getClassName();
+ assertStaticImportIsAllowed(entry.getKey(), className);
+ }
+ for (Map.Entry<String, ImportNode> entry : ast.getStaticStarImports().entrySet()) {
+ final String className = entry.getValue().getClassName();
+ assertStaticImportIsAllowed(entry.getKey(), className);
+ }
+ }
+
+ final SecuringCodeVisitor visitor = new SecuringCodeVisitor();
+ ast.getStatementBlock().visit(visitor);
+ for (ClassNode clNode : ast.getClasses()) {
+ if (clNode!=classNode) {
+ checkMethodDefinitionAllowed(clNode);
+ for (MethodNode methodNode : clNode.getMethods()) {
+ if (!methodNode.isSynthetic() && methodNode.getCode() != null) {
+ methodNode.getCode().visit(visitor);
+ }
+ }
+ }
+ }
+
+ List<MethodNode> methods = filterMethods(classNode);
+ if (isMethodDefinitionAllowed) {
+ for (MethodNode method : methods) {
+ if (method.getDeclaringClass()==classNode && method.getCode() != null) method.getCode().visit(visitor);
+ }
+ }
+ }
+
+ private void checkMethodDefinitionAllowed(ClassNode owner) {
+ if (isMethodDefinitionAllowed) return;
+ List<MethodNode> methods = filterMethods(owner);
+ if (!methods.isEmpty()) throw new SecurityException("Method definitions are not allowed");
+ }
+
+ private static List<MethodNode> filterMethods(ClassNode owner) {
+ List<MethodNode> result = new LinkedList<MethodNode>();
+ List<MethodNode> methods = owner.getMethods();
+ for (MethodNode method : methods) {
+ if (method.getDeclaringClass() == owner && !method.isSynthetic()) {
+ if ("main".equals(method.getName()) || "run".equals(method.getName()) && owner.isScriptBody()) continue;
+ result.add(method);
+ }
+ }
+ return result;
+ }
+
+ private void assertStarImportIsAllowed(final String packageName) {
+ if (starImportsWhitelist != null && !starImportsWhitelist.contains(packageName)) {
+ throw new SecurityException("Importing [" + packageName + "] is not allowed");
+ }
+ if (starImportsBlacklist != null && starImportsBlacklist.contains(packageName)) {
+ throw new SecurityException("Importing [" + packageName + "] is not allowed");
+ }
+ }
+
+ private void assertImportIsAllowed(final String className) {
+ if (importsWhitelist != null && !importsWhitelist.contains(className)) {
+ if (starImportsWhitelist != null) {
+ // we should now check if the import is in the star imports
+ ClassNode node = ClassHelper.make(className);
+ final String packageName = node.getPackageName();
+ if (!starImportsWhitelist.contains(packageName + ".*")) {
+ throw new SecurityException("Importing [" + className + "] is not allowed");
+ }
+ } else {
+ throw new SecurityException("Importing [" + className + "] is not allowed");
+ }
+ }
+ if (importsBlacklist != null && importsBlacklist.contains(className)) {
+ throw new SecurityException("Importing [" + className + "] is not allowed");
+ }
+ // check that there's no star import blacklist
+ if (starImportsBlacklist != null) {
+ ClassNode node = ClassHelper.make(className);
+ final String packageName = node.getPackageName();
+ if (starImportsBlacklist.contains(packageName + ".*")) {
+ throw new SecurityException("Importing [" + className + "] is not allowed");
+ }
+ }
+ }
+
+ private void assertStaticImportIsAllowed(final String member, final String className) {
+ final String fqn = member.equals(className) ? member : className + "." + member;
+ if (staticImportsWhitelist != null && !staticImportsWhitelist.contains(fqn)) {
+ if (staticStarImportsWhitelist != null) {
+ // we should now check if the import is in the star imports
+ if (!staticStarImportsWhitelist.contains(className + ".*")) {
+ throw new SecurityException("Importing [" + fqn + "] is not allowed");
+ }
+ } else {
+ throw new SecurityException("Importing [" + fqn + "] is not allowed");
+ }
+ }
+ if (staticImportsBlacklist != null && staticImportsBlacklist.contains(fqn)) {
+ throw new SecurityException("Importing [" + fqn + "] is not allowed");
+ }
+ // check that there's no star import blacklist
+ if (staticStarImportsBlacklist != null) {
+ if (staticStarImportsBlacklist.contains(className + ".*")) {
+ throw new SecurityException("Importing [" + fqn + "] is not allowed");
+ }
+ }
+ }
+
+ /**
+ * This visitor directly implements the {@link GroovyCodeVisitor} interface instead of using the {@link
+ * CodeVisitorSupport} class to make sure that future features of the language gets managed by this visitor. Thus,
+ * adding a new feature would result in a compilation error if this visitor is not updated.
+ */
+ private class SecuringCodeVisitor implements GroovyCodeVisitor {
+
+ /**
+ * Checks that a given statement is either in the whitelist or not in the blacklist.
+ *
+ * @param statement the statement to be checked
+ * @throws SecurityException if usage of this statement class is forbidden
+ */
+ private void assertStatementAuthorized(final Statement statement) throws SecurityException {
+ final Class<? extends Statement> clazz = statement.getClass();
+ if (statementsBlacklist != null && statementsBlacklist.contains(clazz)) {
+ throw new SecurityException(clazz.getSimpleName() + "s are not allowed");
+ } else if (statementsWhitelist != null && !statementsWhitelist.contains(clazz)) {
+ throw new SecurityException(clazz.getSimpleName() + "s are not allowed");
+ }
+ for (StatementChecker statementChecker : statementCheckers) {
+ if (!statementChecker.isAuthorized(statement)) {
+ throw new SecurityException("Statement [" + clazz.getSimpleName() + "] is not allowed");
+ }
+ }
+ }
+
+ /**
+ * Checks that a given expression is either in the whitelist or not in the blacklist.
+ *
+ * @param expression the expression to be checked
+ * @throws SecurityException if usage of this expression class is forbidden
+ */
+ private void assertExpressionAuthorized(final Expression expression) throws SecurityException {
+ final Class<? extends Expression> clazz = expression.getClass();
+ if (expressionsBlacklist != null && expressionsBlacklist.contains(clazz)) {
+ throw new SecurityException(clazz.getSimpleName() + "s are not allowed: " + expression.getText());
+ } else if (expressionsWhitelist != null && !expressionsWhitelist.contains(clazz)) {
+ throw new SecurityException(clazz.getSimpleName() + "s are not allowed: " + expression.getText());
+ }
+ for (ExpressionChecker expressionChecker : expressionCheckers) {
+ if (!expressionChecker.isAuthorized(expression)) {
+ throw new SecurityException("Expression [" + clazz.getSimpleName() + "] is not allowed: " + expression.getText());
+ }
+ }
+ if (isIndirectImportCheckEnabled) {
+ try {
+ if (expression instanceof ConstructorCallExpression) {
+ assertImportIsAllowed(expression.getType().getName());
+ } else if (expression instanceof MethodCallExpression) {
+ MethodCallExpression expr = (MethodCallExpression) expression;
+ ClassNode objectExpressionType = expr.getObjectExpression().getType();
+ final String typename = getExpressionType(objectExpressionType).getName();
+ assertImportIsAllowed(typename);
+ assertStaticImportIsAllowed(expr.getMethodAsString(), typename);
+ } else if (expression instanceof StaticMethodCallExpression) {
+ StaticMethodCallExpression expr = (StaticMethodCallExpression) expression;
+ final String typename = expr.getOwnerType().getName();
+ assertImportIsAllowed(typename);
+ assertStaticImportIsAllowed(expr.getMethod(), typename);
+ } else if (expression instanceof MethodPointerExpression) {
+ MethodPointerExpression expr = (MethodPointerExpression) expression;
+ final String typename = expr.getType().getName();
+ assertImportIsAllowed(typename);
+ assertStaticImportIsAllowed(expr.getText(), typename);
+ }
+ } catch (SecurityException e) {
+ throw new SecurityException("Indirect import checks prevents usage of expression", e);
+ }
+ }
+ }
+
+ private ClassNode getExpressionType(ClassNode objectExpressionType) {
+ return objectExpressionType.isArray() ? getExpressionType(objectExpressionType.getComponentType()) : objectExpressionType;
+ }
+
+ /**
+ * Checks that a given token is either in the whitelist or not in the blacklist.
+ *
+ * @param token the token to be checked
+ * @throws SecurityException if usage of this token is forbidden
+ */
+ private void assertTokenAuthorized(final Token token) throws SecurityException {
+ final int value = token.getType();
+ if (tokensBlacklist != null && tokensBlacklist.contains(value)) {
+ throw new SecurityException("Token " + token + " is not allowed");
+ } else if (tokensWhitelist != null && !tokensWhitelist.contains(value)) {
+ throw new SecurityException("Token " + token + " is not allowed");
+ }
+ }
+
+ public void visitBlockStatement(final BlockStatement block) {
+ assertStatementAuthorized(block);
+ for (Statement statement : block.getStatements()) {
+ statement.visit(this);
+ }
+ }
+
+
+ public void visitForLoop(final ForStatement forLoop) {
+ assertStatementAuthorized(forLoop);
+ forLoop.getCollectionExpression().visit(this);
+ forLoop.getLoopBlock().visit(this);
+ }
+
+ public void visitWhileLoop(final WhileStatement loop) {
+ assertStatementAuthorized(loop);
+ loop.getBooleanExpression().visit(this);
+ loop.getLoopBlock().visit(this);
+ }
+
+ public void visitDoWhileLoop(final DoWhileStatement loop) {
+ assertStatementAuthorized(loop);
+ loop.getBooleanExpression().visit(this);
+ loop.getLoopBlock().visit(this);
+ }
+
+ public void visitIfElse(final IfStatement ifElse) {
+ assertStatementAuthorized(ifElse);
+ ifElse.getBooleanExpression().visit(this);
+ ifElse.getIfBlock().visit(this);
+
+ Statement elseBlock = ifElse.getElseBlock();
+ if (elseBlock instanceof EmptyStatement) {
+ // dispatching to EmptyStatement will not call back visitor,
+ // must call our visitEmptyStatement explicitly
+ visitEmptyStatement((EmptyStatement) elseBlock);
+ } else {
+ elseBlock.visit(this);
+ }
+ }
+
+ public void visitExpressionStatement(final ExpressionStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ }
+
+ public void visitReturnStatement(final ReturnStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ }
+
+ public void visitAssertStatement(final AssertStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getBooleanExpression().visit(this);
+ statement.getMessageExpression().visit(this);
+ }
+
+ public void visitTryCatchFinally(final TryCatchStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getTryStatement().visit(this);
+ for (CatchStatement catchStatement : statement.getCatchStatements()) {
+ catchStatement.visit(this);
+ }
+ Statement finallyStatement = statement.getFinallyStatement();
+ if (finallyStatement instanceof EmptyStatement) {
+ // dispatching to EmptyStatement will not call back visitor,
+ // must call our visitEmptyStatement explicitly
+ visitEmptyStatement((EmptyStatement) finallyStatement);
+ } else {
+ finallyStatement.visit(this);
+ }
+ }
+
+ protected void visitEmptyStatement(EmptyStatement statement) {
+ // noop
+ }
+
+ public void visitSwitch(final SwitchStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ for (CaseStatement caseStatement : statement.getCaseStatements()) {
+ caseStatement.visit(this);
+ }
+ statement.getDefaultStatement().visit(this);
+ }
+
+ public void visitCaseStatement(final CaseStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ statement.getCode().visit(this);
+ }
+
+ public void visitBreakStatement(final BreakStatement statement) {
+ assertStatementAuthorized(statement);
+ }
+
+ public void visitContinueStatement(final ContinueStatement statement) {
+ assertStatementAuthorized(statement);
+ }
+
+ public void visitThrowStatement(final ThrowStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ }
+
+ public void visitSynchronizedStatement(final SynchronizedStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getExpression().visit(this);
+ statement.getCode().visit(this);
+ }
+
+ public void visitCatchStatement(final CatchStatement statement) {
+ assertStatementAuthorized(statement);
+ statement.getCode().visit(this);
+ }
+
+ public void visitMethodCallExpression(final MethodCallExpression call) {
+ assertExpressionAuthorized(call);
+ Expression receiver = call.getObjectExpression();
+ final String typeName = receiver.getType().getName();
+ if (receiversWhiteList != null && !receiversWhiteList.contains(typeName)) {
+ throw new SecurityException("Method calls not allowed on [" + typeName + "]");
+ } else if (receiversBlackList != null && receiversBlackList.contains(typeName)) {
+ throw new SecurityException("Method calls not allowed on [" + typeName + "]");
+ }
+ receiver.visit(this);
+ final Expression method = call.getMethod();
+ checkConstantTypeIfNotMethodNameOrProperty(method);
+ call.getArguments().visit(this);
+ }
+
+ public void visitStaticMethodCallExpression(final StaticMethodCallExpression call) {
+ assertExpressionAuthorized(call);
+ final String typeName = call.getOwnerType().getName();
+ if (receiversWhiteList != null && !receiversWhiteList.contains(typeName)) {
+ throw new SecurityException("Method calls not allowed on [" + typeName + "]");
+ } else if (receiversBlackList != null && receiversBlackList.contains(typeName)) {
+ throw new SecurityException("Method calls not allowed on [" + typeName + "]");
+ }
+ call.getArguments().visit(this);
+ }
+
+ public void visitConstructorCallExpression(final ConstructorCallExpression call) {
+ assertExpressionAuthorized(call);
+ call.getArguments().visit(this);
+ }
+
+ public void visitTernaryExpression(final TernaryExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getBooleanExpression().visit(this);
+ expression.getTrueExpression().visit(this);
+ expression.getFalseExpression().visit(this);
+ }
+
+ public void visitShortTernaryExpression(final ElvisOperatorExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitTernaryExpression(expression);
+ }
+
+ public void visitBinaryExpression(final BinaryExpression expression) {
+ assertExpressionAuthorized(expression);
+ assertTokenAuthorized(expression.getOperation());
+ expression.getLeftExpression().visit(this);
+ expression.getRightExpression().visit(this);
+ }
+
+ public void visitPrefixExpression(final PrefixExpression expression) {
+ assertExpressionAuthorized(expression);
+ assertTokenAuthorized(expression.getOperation());
+ expression.getExpression().visit(this);
+ }
+
+ public void visitPostfixExpression(final PostfixExpression expression) {
+ assertExpressionAuthorized(expression);
+ assertTokenAuthorized(expression.getOperation());
+ expression.getExpression().visit(this);
+ }
+
+ public void visitBooleanExpression(final BooleanExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitClosureExpression(final ClosureExpression expression) {
+ assertExpressionAuthorized(expression);
+ if (!isClosuresAllowed) throw new SecurityException("Closures are not allowed");
+ expression.getCode().visit(this);
+ }
+
+ public void visitTupleExpression(final TupleExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitListOfExpressions(expression.getExpressions());
+ }
+
+ public void visitMapExpression(final MapExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitListOfExpressions(expression.getMapEntryExpressions());
+ }
+
+ public void visitMapEntryExpression(final MapEntryExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getKeyExpression().visit(this);
+ expression.getValueExpression().visit(this);
+ }
+
+ public void visitListExpression(final ListExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitListOfExpressions(expression.getExpressions());
+ }
+
+ public void visitRangeExpression(final RangeExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getFrom().visit(this);
+ expression.getTo().visit(this);
+ }
+
+ public void visitPropertyExpression(final PropertyExpression expression) {
+ assertExpressionAuthorized(expression);
+ Expression receiver = expression.getObjectExpression();
+ final String typeName = receiver.getType().getName();
+ if (receiversWhiteList != null && !receiversWhiteList.contains(typeName)) {
+ throw new SecurityException("Property access not allowed on [" + typeName + "]");
+ } else if (receiversBlackList != null && receiversBlackList.contains(typeName)) {
+ throw new SecurityException("Property access not allowed on [" + typeName + "]");
+ }
+ receiver.visit(this);
+ final Expression property = expression.getProperty();
+ checkConstantTypeIfNotMethodNameOrProperty(property);
+ }
+
+ private void checkConstantTypeIfNotMethodNameOrProperty(final Expression expr) {
+ if (expr instanceof ConstantExpression) {
+ if (!"java.lang.String".equals(expr.getType().getName())) {
+ expr.visit(this);
+ }
+ } else {
+ expr.visit(this);
+ }
+ }
+
+ public void visitAttributeExpression(final AttributeExpression expression) {
+ assertExpressionAuthorized(expression);
+ Expression receiver = expression.getObjectExpression();
+ final String typeName = receiver.getType().getName();
+ if (receiversWhiteList != null && !receiversWhiteList.contains(typeName)) {
+ throw new SecurityException("Attribute access not allowed on [" + typeName + "]");
+ } else if (receiversBlackList != null && receiversBlackList.contains(typeName)) {
+ throw new SecurityException("Attribute access not allowed on [" + typeName + "]");
+ }
+ receiver.visit(this);
+ final Expression property = expression.getProperty();
+ checkConstantTypeIfNotMethodNameOrProperty(property);
+ }
+
+ public void visitFieldExpression(final FieldExpression expression) {
+ assertExpressionAuthorized(expression);
+ }
+
+ public void visitMethodPointerExpression(final MethodPointerExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ expression.getMethodName().visit(this);
+ }
+
+ public void visitConstantExpression(final ConstantExpression expression) {
+ assertExpressionAuthorized(expression);
+ final String type = expression.getType().getName();
+ if (constantTypesWhiteList != null && !constantTypesWhiteList.contains(type)) {
+ throw new SecurityException("Constant expression type [" + type + "] is not allowed");
+ }
+ if (constantTypesBlackList != null && constantTypesBlackList.contains(type)) {
+ throw new SecurityException("Constant expression type [" + type + "] is not allowed");
+ }
+ }
+
+ public void visitClassExpression(final ClassExpression expression) {
+ assertExpressionAuthorized(expression);
+ }
+
+ public void visitVariableExpression(final VariableExpression expression) {
+ assertExpressionAuthorized(expression);
+ final String type = expression.getType().getName();
+ if (constantTypesWhiteList != null && !constantTypesWhiteList.contains(type)) {
+ throw new SecurityException("Usage of variables of type [" + type + "] is not allowed");
+ }
+ if (constantTypesBlackList != null && constantTypesBlackList.contains(type)) {
+ throw new SecurityException("Usage of variables of type [" + type + "] is not allowed");
+ }
+ }
+
+ public void visitDeclarationExpression(final DeclarationExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitBinaryExpression(expression);
+ }
+
+ protected void visitListOfExpressions(List<? extends Expression> list) {
+ if (list == null) return;
+ for (Expression expression : list) {
+ if (expression instanceof SpreadExpression) {
+ Expression spread = ((SpreadExpression) expression).getExpression();
+ spread.visit(this);
+ } else {
+ expression.visit(this);
+ }
+ }
+ }
+
+ public void visitGStringExpression(final GStringExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitListOfExpressions(expression.getStrings());
+ visitListOfExpressions(expression.getValues());
+ }
+
+ public void visitArrayExpression(final ArrayExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitListOfExpressions(expression.getExpressions());
+ visitListOfExpressions(expression.getSizeExpression());
+ }
+
+ public void visitSpreadExpression(final SpreadExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitSpreadMapExpression(final SpreadMapExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitNotExpression(final NotExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitUnaryMinusExpression(final UnaryMinusExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitUnaryPlusExpression(final UnaryPlusExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitBitwiseNegationExpression(final BitwiseNegationExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitCastExpression(final CastExpression expression) {
+ assertExpressionAuthorized(expression);
+ expression.getExpression().visit(this);
+ }
+
+ public void visitArgumentlistExpression(final ArgumentListExpression expression) {
+ assertExpressionAuthorized(expression);
+ visitTupleExpression(expression);
+ }
+
+ public void visitClosureListExpression(final ClosureListExpression closureListExpression) {
+ assertExpressionAuthorized(closureListExpression);
+ if (!isClosuresAllowed) throw new SecurityException("Closures are not allowed");
+ visitListOfExpressions(closureListExpression.getExpressions());
+ }
+
+ public void visitBytecodeExpression(final BytecodeExpression expression) {
+ assertExpressionAuthorized(expression);
+ }
+ }
+
+ /**
+ * This interface allows the user to plugin custom expression checkers if expression blacklist or whitelist are not
+ * sufficient
+ */
+ public interface ExpressionChecker {
+ boolean isAuthorized(Expression expression);
+ }
+
+ /**
+ * This interface allows the user to plugin custom statement checkers if statement blacklist or whitelist are not
+ * sufficient
+ */
+ public interface StatementChecker {
+ boolean isAuthorized(Statement expression);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/SourceAwareCustomizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/SourceAwareCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/SourceAwareCustomizer.java
new file mode 100644
index 0000000..3bb5183
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/SourceAwareCustomizer.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers;
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.classgen.GeneratorContext;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.FileReaderSource;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+/**
+ * A base class for customizers which only have to be applied on specific source units.
+ * This is for example useful if you want a customizer to be applied only for files
+ * matching some extensions.
+ * <p>
+ * For convenience, this class implements several methods that you may extend to customize
+ * the behaviour of this utility. For example, if you want to apply a customizer only
+ * for classes matching the '.foo' file extension, then you only have to override the
+ * {@link #acceptExtension(String)} method:
+ * <pre><code>return "foo".equals(extension)</code></pre>
+ *
+ * @since 2.1.0
+ * @author Cedric Champeau
+ */
+public class SourceAwareCustomizer extends DelegatingCustomizer {
+
+ private Closure<Boolean> extensionValidator;
+ private Closure<Boolean> baseNameValidator;
+ private Closure<Boolean> sourceUnitValidator;
+ private Closure<Boolean> classValidator;
+
+ public SourceAwareCustomizer(CompilationCustomizer delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
+ String fileName = source.getName();
+ ReaderSource reader = source.getSource();
+ if (reader instanceof FileReaderSource) {
+ FileReaderSource file = (FileReaderSource) reader;
+ fileName = file.getFile().getName();
+ }
+ if (acceptSource(source) && acceptClass(classNode) && accept(fileName)) {
+ delegate.call(source, context, classNode);
+ }
+ }
+
+ public void setBaseNameValidator(final Closure<Boolean> baseNameValidator) {
+ this.baseNameValidator = baseNameValidator;
+ }
+
+ public void setExtensionValidator(final Closure<Boolean> extensionValidator) {
+ this.extensionValidator = extensionValidator;
+ }
+
+ public void setSourceUnitValidator(final Closure<Boolean> sourceUnitValidator) {
+ this.sourceUnitValidator = sourceUnitValidator;
+ }
+
+ public void setClassValidator(final Closure<Boolean> classValidator) {
+ this.classValidator = classValidator;
+ }
+
+ public boolean accept(String fileName) {
+ int ext = fileName.lastIndexOf(".");
+ String baseName = ext<0?fileName:fileName.substring(0, ext);
+ String extension = ext<0?"":fileName.substring(ext+1);
+ return acceptExtension(extension) && acceptBaseName(baseName);
+ }
+
+ public boolean acceptClass(ClassNode cnode) {
+ return classValidator == null || classValidator.call(cnode);
+ }
+
+ public boolean acceptSource(SourceUnit unit) {
+ return sourceUnitValidator==null || sourceUnitValidator.call(unit);
+ }
+
+ public boolean acceptExtension(String extension) {
+ return extensionValidator==null || extensionValidator.call(extension);
+ }
+
+ public boolean acceptBaseName(String baseName) {
+ return baseNameValidator==null || baseNameValidator.call(baseName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/CustomizersFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/CustomizersFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/CustomizersFactory.java
new file mode 100644
index 0000000..40ce7e7
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/CustomizersFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.util.AbstractFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.codehaus.groovy.control.customizers.CompilationCustomizer;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This factory generates an array of compilation customizers.
+ *
+ * @author Cedric Champeau
+ * @since 2.1.0
+ */
+public class CustomizersFactory extends AbstractFactory implements PostCompletionFactory {
+
+ public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
+ return new LinkedList();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void setChild(final FactoryBuilderSupport builder, final Object parent, final Object child) {
+ if (parent instanceof Collection && child instanceof CompilationCustomizer) {
+ ((Collection) parent).add(child);
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public Object postCompleteNode(final FactoryBuilderSupport factory, final Object parent, final Object node) {
+ if (node instanceof List) {
+ List col = (List) node;
+ return col.toArray(new CompilationCustomizer[col.size()]);
+ }
+ return node;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/ImportCustomizerFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/ImportCustomizerFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/ImportCustomizerFactory.java
new file mode 100644
index 0000000..e9e8b7e
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/ImportCustomizerFactory.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.lang.Closure;
+import groovy.lang.GString;
+import groovy.util.AbstractFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.codehaus.groovy.control.customizers.ImportCustomizer;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This factory allows the generation of an {@link ImportCustomizer import customizer}. You may embed several
+ * elements:
+ * <ul>
+ * <li><i>normal</i> for "regular" imports</li>
+ * <li><i>star</i> for "star" imports</li>
+ * <li><i>staticStar</i> for "static star" imports</li>
+ * <li><i>alias</i> for imports with alias</li>
+ * <li><i>staticMember</i> for static imports of individual members</li>
+ * </ul>
+ *
+ * For example:
+ * <pre><code>builder.imports {
+ * alias 'AI', 'java.util.concurrent.atomic.AtomicInteger'
+ * alias 'AL', 'java.util.concurrent.atomic.AtomicLong'
+ *}</code></pre>
+ *
+ * @author Cedric Champeau
+ * @since 2.1.0
+ */
+public class ImportCustomizerFactory extends AbstractFactory {
+ @Override
+ public boolean isHandlesNodeChildren() {
+ return true;
+ }
+
+ public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
+ ImportCustomizer customizer = new ImportCustomizer();
+ addImport(customizer, value);
+ return customizer;
+ }
+
+ private void addImport(final ImportCustomizer customizer, final Object value) {
+ if (value==null) return;
+ if (value instanceof Collection) {
+ for (Object e : (Collection)value) {
+ addImport(customizer, e);
+ }
+ } else if (value instanceof String) {
+ customizer.addImports((String)value);
+ } else if (value instanceof Class) {
+ customizer.addImports(((Class) value).getName());
+ } else if (value instanceof GString) {
+ customizer.addImports(value.toString());
+ } else {
+ throw new RuntimeException("Unsupported import value type ["+value+"]");
+ }
+ }
+
+ @Override
+ public boolean onNodeChildren(final FactoryBuilderSupport builder, final Object node, final Closure childContent) {
+ if (node instanceof ImportCustomizer) {
+ Closure clone = (Closure) childContent.clone();
+ clone.setDelegate(new ImportHelper((ImportCustomizer) node));
+ clone.call();
+ }
+ return false;
+ }
+
+ private static final class ImportHelper {
+ private final ImportCustomizer customizer;
+
+ private ImportHelper(final ImportCustomizer customizer) {
+ this.customizer = customizer;
+ }
+
+ protected void normal(String... names) {
+ customizer.addImports(names);
+ }
+ protected void normal(Class... classes) {
+ for (Class aClass : classes) {
+ customizer.addImports(aClass.getName());
+ }
+ }
+
+ protected void alias(String alias, String name) {
+ customizer.addImport(alias, name);
+ }
+ protected void alias(String alias, Class clazz) {
+ customizer.addImport(alias, clazz.getName());
+ }
+
+ protected void star(String... packages) {
+ customizer.addStarImports(packages);
+ }
+
+ protected void staticStar(String... classNames) {
+ customizer.addStaticStars(classNames);
+ }
+ protected void staticStar(Class... classes) {
+ for (Class aClass : classes) {
+ customizer.addStaticStars(aClass.getName());
+ }
+ }
+
+ protected void staticMember(String name, String field) {
+ customizer.addStaticImport(name, field);
+ }
+ protected void staticMember(String alias, String name, String field) {
+ customizer.addStaticImport(alias, name, field);
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/InlinedASTCustomizerFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/InlinedASTCustomizerFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/InlinedASTCustomizerFactory.java
new file mode 100644
index 0000000..c7b599c
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/InlinedASTCustomizerFactory.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.lang.Closure;
+import groovy.util.AbstractFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.customizers.CompilationCustomizer;
+import org.codehaus.groovy.runtime.ProxyGeneratorAdapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This factory lets a user define a compilation customizer without having to define
+ * an anonymous inner class.
+ * <p>
+ * Here is an example, which only logs the class name during compilation:
+ * <pre>
+ * inline(phase:'CONVERSION') { source, context, classNode ->
+ * println "visiting $classNode"
+ * }
+ * </pre>
+ *
+ * @author Cedric Champeau
+ * @since 2.1.0
+ */
+@SuppressWarnings("unchecked")
+public class InlinedASTCustomizerFactory extends AbstractFactory implements PostCompletionFactory {
+
+ @Override
+ public boolean isHandlesNodeChildren() {
+ return true;
+ }
+
+ public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
+ if (attributes.isEmpty() || !attributes.containsKey("phase")) {
+ throw new RuntimeException("You must specify a CompilePhase to run at, using the [phase] attribute");
+ }
+ Map result = new HashMap(1+attributes.size());
+ result.putAll(attributes);
+ return result;
+ }
+
+ @Override
+ public boolean onNodeChildren(final FactoryBuilderSupport builder, final Object node, final Closure childContent) {
+ if (node instanceof Map) {
+ ((Map)node).put("call", childContent.clone());
+ }
+ return false;
+ }
+
+ public Object postCompleteNode(final FactoryBuilderSupport factory, final Object parent, final Object node) {
+ if (node instanceof Map) {
+ Map map = (Map) node;
+ ProxyGeneratorAdapter adapter = new ProxyGeneratorAdapter(
+ map,
+ map.containsKey("superClass")?(Class)map.get("superClass"):CompilationCustomizer.class,
+ map.containsKey("interfaces")?(Class[])map.get("interfaces"):null,
+ this.getClass().getClassLoader(),
+ false,
+ null
+ );
+ Object phase = map.get("phase");
+ if (!(phase instanceof CompilePhase)) {
+ phase = CompilePhase.valueOf(phase.toString());
+ }
+ return adapter.proxy(map, phase);
+ }
+ return node;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/PostCompletionFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/PostCompletionFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/PostCompletionFactory.java
new file mode 100644
index 0000000..8855dbf
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/PostCompletionFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.util.FactoryBuilderSupport;
+
+/**
+ * A helper interface for factories which require post processing of generated nodes.
+ *
+ * @since 2.1.0
+ * @author Cedric Champeau
+ */
+public interface PostCompletionFactory {
+ Object postCompleteNode(FactoryBuilderSupport factory, Object parent, Object node);
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/SecureASTCustomizerFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/SecureASTCustomizerFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/SecureASTCustomizerFactory.java
new file mode 100644
index 0000000..3c81dee
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/SecureASTCustomizerFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.lang.Closure;
+import groovy.util.AbstractFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
+
+import java.util.Map;
+
+/**
+ * This factory allows the generation of a {@link SecureASTCustomizer}. Embedded elements are delegated
+ * to a {@link SecureASTCustomizer} instance.
+ *
+ * @since 2.1.0
+ * @author Cedric Champeau
+ */
+public class SecureASTCustomizerFactory extends AbstractFactory {
+ @Override
+ public boolean isHandlesNodeChildren() {
+ return true;
+ }
+
+ public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
+ return new SecureASTCustomizer();
+ }
+
+ @Override
+ public boolean onNodeChildren(final FactoryBuilderSupport builder, final Object node, final Closure childContent) {
+ if (node instanceof SecureASTCustomizer) {
+ Closure clone = (Closure) childContent.clone();
+ clone.setDelegate(node);
+ clone.setResolveStrategy(Closure.DELEGATE_FIRST);
+ clone.call();
+ }
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/customizers/builder/SourceAwareCustomizerFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/customizers/builder/SourceAwareCustomizerFactory.java b/src/main/java/org/codehaus/groovy/control/customizers/builder/SourceAwareCustomizerFactory.java
new file mode 100644
index 0000000..d0fc541
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/customizers/builder/SourceAwareCustomizerFactory.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.customizers.builder;
+
+import groovy.lang.Closure;
+import groovy.util.AbstractFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.codehaus.groovy.control.customizers.CompilationCustomizer;
+import org.codehaus.groovy.control.customizers.SourceAwareCustomizer;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Factory for use with {@link CompilerCustomizationBuilder}. Allows the construction of {@link SourceAwareCustomizer
+ * source aware customizers}. Syntax:
+ * <pre><code>
+ * // apply CompileStatic AST annotation on .sgroovy files
+ * builder.source(extension: 'sgroovy') {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on .sgroovy or .sg files
+ * builder.source(extensions: ['sgroovy','sg']) {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on .sgroovy or .sg files
+ * builder.source(extensionValidator: { it.name in ['sgroovy','sg']}) {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on files whose name is 'foo'
+ * builder.source(basename: 'foo') {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on files whose name is 'foo' or 'bar'
+ * builder.source(basenames: ['foo', 'bar']) {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on files whose name is 'foo' or 'bar'
+ * builder.source(basenameValidator: { it in ['foo', 'bar'] }) {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on files that do not contain a class named 'Baz'
+ * builder.source(unitValidator: { unit -> !unit.AST.classes.any { it.name == 'Baz' } }) {
+ * ast(CompileStatic)
+ * }
+ *
+ * // apply CompileStatic AST annotation on class nodes that end with 'CS'
+ * builder.source(classValidator: { cn -> cn.name.endsWith('CS') }) {
+ * ast(CompileStatic)
+ * }
+ * </code></pre>
+ *
+ * @author Cedric Champeau
+ */
+public class SourceAwareCustomizerFactory extends AbstractFactory implements PostCompletionFactory {
+
+ public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException {
+ SourceOptions data = new SourceOptions();
+ if (value instanceof CompilationCustomizer) {
+ data.delegate = (CompilationCustomizer) value;
+ }
+ return data;
+ }
+
+ @Override
+ public void setChild(final FactoryBuilderSupport builder, final Object parent, final Object child) {
+ if (child instanceof CompilationCustomizer && parent instanceof SourceOptions) {
+ ((SourceOptions) parent).delegate = (CompilationCustomizer) child;
+ }
+ }
+
+ public Object postCompleteNode(final FactoryBuilderSupport factory, final Object parent, final Object node) {
+ SourceOptions data = (SourceOptions) node;
+ SourceAwareCustomizer sourceAwareCustomizer = new SourceAwareCustomizer(data.delegate);
+ if (data.extensionValidator !=null && (data.extension!=null || data.extensions!=null)) {
+ throw new RuntimeException("You must choose between an extension name validator or an explicit extension name");
+ }
+ if (data.basenameValidator!=null && (data.basename!=null || data.basenames!=null)) {
+ throw new RuntimeException("You must choose between an base name validator or an explicit base name");
+ }
+
+ addExtensionValidator(sourceAwareCustomizer, data);
+ addBasenameValidator(sourceAwareCustomizer, data);
+ if (data.unitValidator!=null) sourceAwareCustomizer.setSourceUnitValidator(data.unitValidator);
+ if (data.classValidator!=null) sourceAwareCustomizer.setClassValidator(data.classValidator);
+ return sourceAwareCustomizer;
+ }
+
+ private static void addExtensionValidator(final SourceAwareCustomizer sourceAwareCustomizer, final SourceOptions data) {
+ final List<String> extensions = data.extensions!=null?data.extensions : new LinkedList<String>();
+ if (data.extension!=null) extensions.add(data.extension);
+ Closure<Boolean> extensionValidator = data.extensionValidator;
+ if (extensionValidator==null && !extensions.isEmpty()) {
+ extensionValidator = new Closure<Boolean>(sourceAwareCustomizer) {
+ @Override
+ @SuppressWarnings("unchecked")
+ public Boolean call(final Object arguments) {
+ return extensions.contains(arguments);
+ }
+ };
+ }
+ sourceAwareCustomizer.setExtensionValidator(extensionValidator);
+ }
+
+ private static void addBasenameValidator(final SourceAwareCustomizer sourceAwareCustomizer, final SourceOptions data) {
+ final List<String> basenames = data.basenames!=null?data.basenames : new LinkedList<String>();
+ if (data.basename!=null) basenames.add(data.basename);
+ Closure<Boolean> basenameValidator = data.basenameValidator;
+ if (basenameValidator==null && !basenames.isEmpty()) {
+ basenameValidator = new Closure<Boolean>(sourceAwareCustomizer) {
+ @Override
+ @SuppressWarnings("unchecked")
+ public Boolean call(final Object arguments) {
+ return basenames.contains(arguments);
+ }
+ };
+ }
+ sourceAwareCustomizer.setBaseNameValidator(basenameValidator);
+ }
+
+ public static class SourceOptions {
+ public CompilationCustomizer delegate;
+ // validate with closures
+ public Closure<Boolean> extensionValidator;
+ public Closure<Boolean> unitValidator;
+ public Closure<Boolean> basenameValidator;
+ public Closure<Boolean> classValidator;
+
+ // validate with one string
+ public String extension;
+ public String basename;
+
+ // validate with list of strings
+ public List<String> extensions;
+ public List<String> basenames;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/io/AbstractReaderSource.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/io/AbstractReaderSource.java b/src/main/java/org/codehaus/groovy/control/io/AbstractReaderSource.java
new file mode 100644
index 0000000..1c1084c
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/control/io/AbstractReaderSource.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.control.io;
+
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.Janitor;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/**
+ * For ReaderSources that can choose a parent class, a base that
+ * provides common functionality.
+ *
+ * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
+ */
+
+public abstract class AbstractReaderSource implements ReaderSource {
+ protected CompilerConfiguration configuration; // Configuration data
+
+ public AbstractReaderSource(CompilerConfiguration configuration) {
+ if (configuration == null) {
+ throw new IllegalArgumentException("Compiler configuration must not be null!");
+ // ... or more relaxed?
+ // configuration = CompilerConfiguration.DEFAULT;
+ }
+ this.configuration = configuration;
+ }
+
+ /**
+ * Returns true if the source can be restarted (ie. if getReader()
+ * will return non-null on subsequent calls.
+ */
+ public boolean canReopenSource() {
+ return true;
+ }
+
+ private BufferedReader lineSource = null; // If set, a reader on the current source file
+ private String line = null; // The last line read from the current source file
+ private int number = 0; // The last line number read
+
+ /**
+ * Returns a line from the source, or null, if unavailable. If
+ * you supply a Janitor, resources will be cached.
+ */
+ public String getLine(int lineNumber, Janitor janitor) {
+ // If the source is already open and is passed the line we
+ // want, close it.
+ if (lineSource != null && number > lineNumber) {
+ cleanup();
+ }
+
+ // If the line source is closed, try to open it.
+ if (lineSource == null) {
+ try {
+ lineSource = new BufferedReader(getReader());
+ } catch (Exception e) {
+ // Ignore
+ }
+ number = 0;
+ }
+
+ // Read until the appropriate line number.
+ if (lineSource != null) {
+ while (number < lineNumber) {
+ try {
+ line = lineSource.readLine();
+ number++;
+ }
+ catch (IOException e) {
+ cleanup();
+ }
+ }
+
+ if (janitor == null) {
+ final String result = line; // otherwise cleanup() will wipe out value
+ cleanup();
+ return result;
+ } else {
+ janitor.register(this);
+ }
+ }
+
+ return line;
+ }
+
+ /**
+ * Cleans up any cached resources used by getLine().
+ */
+ public void cleanup() {
+ if (lineSource != null) {
+ try {
+ lineSource.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ lineSource = null;
+ line = null;
+ number = 0;
+ }
+
+}