Repository: groovy
Updated Branches:
  refs/heads/master 049f7d7af -> 05abc726f


CliBuilder enhancements


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/73e75fa6
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/73e75fa6
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/73e75fa6

Branch: refs/heads/master
Commit: 73e75fa6d9f2236caf1a9e5284e8d50a88d4974f
Parents: 049f7d7
Author: Paul King <pa...@asert.com.au>
Authored: Fri Jul 10 07:45:26 2015 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Wed Apr 13 20:38:44 2016 +1000

----------------------------------------------------------------------
 gradle/assemble.gradle                          |   1 +
 src/main/groovy/cli/EnhancedCommandLine.groovy  |  52 ++++++
 src/main/groovy/cli/Option.java                 |  67 ++++++++
 src/main/groovy/cli/OptionField.groovy          |  27 +++
 src/main/groovy/cli/TypedOption.java            |  27 +++
 src/main/groovy/cli/Unparsed.java               |  39 +++++
 src/main/groovy/cli/UnparsedField.groovy        |  27 +++
 src/main/groovy/util/CliBuilder.groovy          | 170 +++++++++++++++++--
 .../transform/FieldASTTransformation.java       |  13 ++
 9 files changed, 413 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/gradle/assemble.gradle
----------------------------------------------------------------------
diff --git a/gradle/assemble.gradle b/gradle/assemble.gradle
index 84af440..f1cc68e 100644
--- a/gradle/assemble.gradle
+++ b/gradle/assemble.gradle
@@ -168,6 +168,7 @@ allprojects {
                     'org/codehaus/groovy/cli/GroovyPosixParser*.class',
                     'groovy/util/CliBuilder*.class',
                     'groovy/util/OptionAccessor*.class',
+                    'groovy/cli/EnhancedCommandLine*.class',
                     'org/codehaus/groovy/tools/shell/util/HelpFormatter*.class'
             ].join(',')
             boolean isRoot = project == rootProject

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/EnhancedCommandLine.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/EnhancedCommandLine.groovy 
b/src/main/groovy/cli/EnhancedCommandLine.groovy
new file mode 100644
index 0000000..2f46bcb
--- /dev/null
+++ b/src/main/groovy/cli/EnhancedCommandLine.groovy
@@ -0,0 +1,52 @@
+package groovy.cli
+
+import org.apache.commons.cli.CommandLine
+import org.codehaus.groovy.runtime.StringGroovyMethods
+import org.apache.commons.cli.Option as CliOption
+import java.lang.reflect.Array
+
+class EnhancedCommandLine {
+    @Delegate CommandLine delegate
+
+    public <T> T getOptionValue(TypedOption<T> typedOption) {
+        getOptionValue(typedOption, null)
+    }
+
+    public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.get("longOpt")
+        if (delegate.hasOption(optionName)) {
+            return getTypedValueFromName(optionName)
+        }
+        return defaultValue
+    }
+
+    public <T> T[] getOptionValues(TypedOption<T> typedOption) {
+        String optionName = (String) typedOption.get("longOpt")
+        CliOption option = delegate.options.find{ it.longOpt == optionName }
+        T[] result = null
+        if (option) {
+            Object type = option.getType()
+            int count = 0
+            def optionValues = delegate.getOptionValues(optionName)
+            for (String optionValue : optionValues) {
+                if (result == null) {
+                    result = (T[]) Array.newInstance((Class<?>) type, 
optionValues.length)
+                }
+                result[count++] = (T) getTypedValue(type, optionValue)
+            }
+        }
+        return result
+    }
+
+    private <T> T getTypedValueFromName(String optionName) {
+        CliOption option = delegate.options.find{ it.longOpt == optionName }
+        if (!option) return null
+        Object type = option.getType()
+        String optionValue = delegate.getOptionValue(optionName)
+        return (T) getTypedValue(type, optionValue)
+    }
+
+    private static <T> T getTypedValue(Object type, String optionValue) {
+        return StringGroovyMethods.asType(optionValue, (Class<T>) type)
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/Option.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/Option.java b/src/main/groovy/cli/Option.java
new file mode 100644
index 0000000..83c50fc
--- /dev/null
+++ b/src/main/groovy/cli/Option.java
@@ -0,0 +1,67 @@
+/*
+ *  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 groovy.cli;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method is a CLI option.
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Option {
+    /**
+     * The description of this option
+     *
+     * @return the description of this option
+     */
+    String description() default "";
+
+    /**
+     * The short name of this option
+     *
+     * @return the short name of this option
+     */
+    String shortName() default "";
+
+    /**
+     * The long name of this option
+     *
+     * @return the long name of this option
+     */
+    String longName() default "";
+
+    /**
+     * The value separator for this multi-valued option
+     *
+     * @return the value separator for this multi-valued option
+     */
+    char valueSeparator() default 0;
+
+    /**
+     * The default value for this option
+     *
+     * @return the default value for this option
+     */
+    String defaultValue() default "";
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/OptionField.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/OptionField.groovy 
b/src/main/groovy/cli/OptionField.groovy
new file mode 100644
index 0000000..69cc1f5
--- /dev/null
+++ b/src/main/groovy/cli/OptionField.groovy
@@ -0,0 +1,27 @@
+/*
+ *  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 groovy.cli
+
+import groovy.transform.AnnotationCollector
+import groovy.transform.Field
+
+@Option
+@Field
+@AnnotationCollector
+@interface OptionField { }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/TypedOption.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/TypedOption.java 
b/src/main/groovy/cli/TypedOption.java
new file mode 100644
index 0000000..e669324
--- /dev/null
+++ b/src/main/groovy/cli/TypedOption.java
@@ -0,0 +1,27 @@
+/*
+ *  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 groovy.cli;
+
+import java.util.HashMap;
+
+public class TypedOption<T> extends HashMap<String, T> {
+    public T defaultValue() {
+        return (T) super.get("defaultValue");
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/Unparsed.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/Unparsed.java 
b/src/main/groovy/cli/Unparsed.java
new file mode 100644
index 0000000..d615cf1
--- /dev/null
+++ b/src/main/groovy/cli/Unparsed.java
@@ -0,0 +1,39 @@
+/*
+ *  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 groovy.cli;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method will contain the remaining arguments.
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Unparsed {
+    /**
+     * The description for the remaining non-option arguments
+     *
+     * @return the description for the remaining non-option arguments
+     */
+    String description() default "ARGUMENTS";
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/cli/UnparsedField.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/cli/UnparsedField.groovy 
b/src/main/groovy/cli/UnparsedField.groovy
new file mode 100644
index 0000000..b185431
--- /dev/null
+++ b/src/main/groovy/cli/UnparsedField.groovy
@@ -0,0 +1,27 @@
+/*
+ *  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 groovy.cli
+
+import groovy.transform.AnnotationCollector
+import groovy.transform.Field
+
+@Unparsed
+@Field
+@AnnotationCollector
+@interface UnparsedField { }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/groovy/util/CliBuilder.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/util/CliBuilder.groovy 
b/src/main/groovy/util/CliBuilder.groovy
index 4ecbe82..6e39eb9 100644
--- a/src/main/groovy/util/CliBuilder.groovy
+++ b/src/main/groovy/util/CliBuilder.groovy
@@ -18,15 +18,25 @@
  */
 package groovy.util
 
+import groovy.cli.EnhancedCommandLine
+import groovy.cli.Option
+import groovy.cli.TypedOption
+import groovy.cli.Unparsed
 import org.apache.commons.cli.CommandLine
 import org.apache.commons.cli.CommandLineParser
 import org.apache.commons.cli.DefaultParser
 import org.apache.commons.cli.GnuParser
 import org.apache.commons.cli.HelpFormatter
-import org.apache.commons.cli.Option
+import org.apache.commons.cli.Option as CliOption
 import org.apache.commons.cli.Options
 import org.apache.commons.cli.ParseException
 import org.codehaus.groovy.runtime.InvokerHelper
+import org.codehaus.groovy.runtime.MetaClassHelper
+import org.codehaus.groovy.runtime.StringGroovyMethods
+
+import java.lang.annotation.Annotation
+import java.lang.reflect.Field
+import java.lang.reflect.Method
 
 /**
  * Provides a builder to assist the processing of command line arguments.
@@ -237,6 +247,14 @@ class CliBuilder {
      */
     Options options = new Options()
 
+    Map<String, TypedOption> savedTypeOptions = new HashMap<String, 
TypedOption>()
+    /*
+    Object defaultValue = map.get("defaultValue");
+    defaultValues.put(makeDefaultValueKey(option), defaultValue);
+    Class type = (Class) map.get("type");
+    if (type != null) option.setType(type);
+     */
+
     /**
      * Internal method: Detect option specification method calls.
      */
@@ -246,7 +264,7 @@ class CliBuilder {
                 options.addOption(option(name, [:], args[0]))
                 return null
             }
-            if (args.size() == 1 && args[0] instanceof Option && name == 
'leftShift') {
+            if (args.size() == 1 && args[0] instanceof CliOption && name == 
'leftShift') {
                 options.addOption(args[0])
                 return null
             }
@@ -268,7 +286,7 @@ class CliBuilder {
             parser = posix != null && posix == false ? new GnuParser() : new 
DefaultParser()
         }
         try {
-            return new OptionAccessor(parser.parse(options, args as String[], 
stopAtNonOption))
+            return new OptionAccessor(new EnhancedCommandLine(delegate: 
parser.parse(options, args as String[], stopAtNonOption)))
         } catch (ParseException pe) {
             writer.println("error: " + pe.message)
             usage()
@@ -284,18 +302,125 @@ class CliBuilder {
         writer.flush()
     }
 
+    public <T> T parseFromSpec(Class<T> optionsClass, String[] args) {
+        addOptionsFromAnnotations(optionsClass, false)
+        def cli = parse(args)
+        def cliOptions = [:]
+        setOptionsFromAnnotations(cli, optionsClass, cliOptions, false)
+        cliOptions as T
+    }
+
+    public <T> T parseFromInstance(T optionInstance, args) {
+        addOptionsFromAnnotations(options.getClass(), true)
+        def cli = parse(args)
+        setOptionsFromAnnotations(cli, optionInstance.getClass(), 
optionInstance, true)
+        optionInstance
+    }
+
+    void addOptionsFromAnnotations(Class optionClass, boolean namesAreSetters) 
{
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method 
m ->
+            Annotation annotation = m.getAnnotation(Option)
+            options.addOption(processAddAnnotation(annotation, m, 
namesAreSetters))
+        }
+        optionClass.declaredFields.findAll{ it.getAnnotation(Option) }.each { 
Field f ->
+            Annotation annotation = f.getAnnotation(Option)
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            options.addOption(processAddAnnotation(annotation, m, true))
+        }
+    }
+
+    private CliOption processAddAnnotation(Option annotation, Method m, 
boolean namesAreSetters) {
+        String shortName = annotation.shortName()
+        String description = annotation.description()
+        String defaultValue = annotation.defaultValue()
+        char valueSeparator = annotation.valueSeparator()
+        String longName = adjustLongName(annotation.longName(), m, 
namesAreSetters)
+        def builder = shortName && !shortName.isEmpty() ? 
CliOption.builder(shortName) : CliOption.builder()
+        builder.longOpt(longName)
+        if (description && !description.isEmpty()) builder.desc(description)
+        if (defaultValue && !defaultValue.isEmpty()) 
builder.withDefaultValue(defaultValue)
+        if (valueSeparator) builder.valueSeparator(valueSeparator)
+        Class type = namesAreSetters ? (m.parameterTypes.size() > 0 ? 
m.parameterTypes[0] : null) : m.returnType
+        if (type) {
+            println "$longName $shortName $type"
+            builder.hasArg(type.simpleName.toLowerCase() != 'boolean')
+            builder.type(type)
+        }
+        def typedOption = builder.build()
+        savedTypeOptions[longName] = typedOption
+        typedOption
+    }
+
+    def setOptionsFromAnnotations(def cli, Class optionClass, Object t, 
boolean namesAreSetters) {
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method 
m ->
+            Annotation annotation = m.getAnnotation(Option)
+            String longName = adjustLongName(annotation.longName(), m, 
namesAreSetters)
+            processSetAnnotation(m, t, longName, cli, namesAreSetters)
+        }
+        optionClass.declaredFields.findAll{ it.getAnnotation(Option) }.each { 
Field f ->
+            Annotation annotation = f.getAnnotation(Option)
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            String longName = adjustLongName(annotation.longName(), m, true)
+            processSetAnnotation(m, t, longName, cli, true)
+        }
+        def remaining = cli.arguments()
+        optionClass.methods.findAll{ it.getAnnotation(Unparsed) }.each { 
Method m ->
+            processSetRemaining(m, remaining, t, namesAreSetters)
+        }
+        optionClass.declaredFields.findAll{ it.getAnnotation(Unparsed) }.each 
{ Field f ->
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            processSetRemaining(m, remaining, t, namesAreSetters)
+        }
+    }
+
+    private void processSetRemaining(Method m, remaining, Object t, boolean 
namesAreSetters) {
+        if (namesAreSetters) {
+            m.invoke(t, remaining.toList())
+        } else {
+            String longName = adjustLongName("", m, namesAreSetters)
+            t.put(longName, { -> remaining.toList() })
+        }
+    }
+
+    private void processSetAnnotation(Method m, Object t, String longName, 
cli, boolean namesAreSetters) {
+        if (namesAreSetters) {
+            boolean isFlag = m.parameterTypes.size() > 0 && 
m.parameterTypes[0].simpleName.toLowerCase() == 'boolean'
+            if (cli.hasOption(longName) || isFlag) {
+                m.invoke(t, [isFlag ? cli.hasOption(longName) : cli[longName] 
/* cliOptions.getOptionValue(savedTypeOptions[longName]) */] as Object[])
+            }
+        } else {
+            boolean isFlag = m.returnType.simpleName.toLowerCase() == 'boolean'
+            t.put(m.getName(), cli.hasOption(longName) ?
+                    { -> isFlag ? true : cli[longName] /* 
cliOptions.getOptionValue(savedTypeOptions[longName]) */} :
+                    { -> isFlag ? false : null })
+        }
+    }
+
+    private String adjustLongName(String longName, Method m, boolean 
namesAreSetters) {
+        if (!longName || longName.isEmpty()) {
+            longName = m.getName()
+            if (namesAreSetters && longName.startsWith("set")) {
+                longName = 
MetaClassHelper.convertPropertyName(longName.substring(3))
+            }
+        }
+        longName
+    }
+
     // implementation details -------------------------------------
 
     /**
      * Internal method: How to create an option from the specification.
      */
-    Option option(shortname, Map details, info) {
-        Option option
+    CliOption option(shortname, Map details, info) {
+        CliOption option
         if (shortname == '_') {
-            option = 
Option.builder().desc(info).longOpt(details.longOpt).build()
+            option = 
CliOption.builder().desc(info).longOpt(details.longOpt).build()
             details.remove('longOpt')
         } else {
-            option = new Option(shortname, info)
+            option = new CliOption(shortname, info)
         }
         details.each { key, value -> option[key] = value }
         return option
@@ -336,18 +461,42 @@ class CliBuilder {
 }
 
 class OptionAccessor {
-    CommandLine inner
+    /* EnhancedCommandLine */ def inner
+    Map<String, TypedOption> savedTypeOptions
 
-    OptionAccessor(CommandLine inner) {
+    OptionAccessor(inner) {
         this.inner = inner
     }
 
+    boolean hasOption(TypedOption typedOption) {
+        return inner.hasOption((String) typedOption.get("longOpt"))
+    }
+
+    public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.get("longOpt");
+        if (hasOption(optionName)) {
+            return getTypedValueFromName(optionName);
+        }
+        return defaultValue;
+    }
+
+    private <T> T getTypedValueFromName(String optionName) {
+        CliOption option = savedTypeOptions.get(optionName);
+        Object type = option.getType();
+        String optionValue = inner.getOptionValue(optionName);
+        return (T) getTypedValue(type, optionValue);
+    }
+
+    private <T> T getTypedValue(Object type, String optionValue) {
+        return StringGroovyMethods.asType(optionValue, (Class<T>) type);
+    }
+
     def invokeMethod(String name, Object args) {
         return InvokerHelper.getMetaClass(inner).invokeMethod(inner, name, 
args)
     }
 
     def getProperty(String name) {
-        def methodname = 'getOptionValue'
+        def methodname = 'getParsedOptionValue'
         if (name.size() > 1 && name.endsWith('s')) {
             def singularName = name[0..-2]
             if (hasOption(singularName)) {
@@ -357,6 +506,7 @@ class OptionAccessor {
         }
         if (name.size() == 1) name = name as char
         def result = InvokerHelper.getMetaClass(inner).invokeMethod(inner, 
methodname, name)
+        println "result = $result for name $name, methodName $methodname"
         if (null == result) result = inner.hasOption(name)
         if (result instanceof String[]) result = result.toList()
         return result

http://git-wip-us.apache.org/repos/asf/groovy/blob/73e75fa6/src/main/org/codehaus/groovy/transform/FieldASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/FieldASTTransformation.java 
b/src/main/org/codehaus/groovy/transform/FieldASTTransformation.java
index 2191710..d0fee21 100644
--- a/src/main/org/codehaus/groovy/transform/FieldASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/FieldASTTransformation.java
@@ -25,6 +25,7 @@ import org.codehaus.groovy.ast.ASTNode;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.MethodNode;
@@ -40,6 +41,7 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.classgen.VariableScopeVisitor;
 import org.codehaus.groovy.control.CompilePhase;
 import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.runtime.MetaClassHelper;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Arrays;
@@ -47,6 +49,13 @@ import java.util.Iterator;
 import java.util.List;
 
 import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 
 /**
  * Handles transformation for the @Field annotation.
@@ -98,6 +107,10 @@ public class FieldASTTransformation extends 
ClassCodeExpressionTransformer imple
             fieldNode = new FieldNode(variableName, ve.getModifiers(), 
ve.getType(), null, de.getRightExpression());
             fieldNode.setSourcePosition(de);
             cNode.addField(fieldNode);
+            String setterName = "set" + 
MetaClassHelper.capitalize(variableName);
+            cNode.addMethod(setterName, ACC_PUBLIC | ACC_SYNTHETIC, 
ClassHelper.VOID_TYPE, params(param(ve.getType(), variableName)), 
ClassNode.EMPTY_ARRAY, block(
+                    stmt(assignX(propX(varX("this"), variableName), 
varX(variableName)))
+            ));
 
             // GROOVY-4833 : annotations that are not Groovy transforms should 
be transferred to the generated field
             // GROOVY-6112 : also copy acceptable Groovy transforms

Reply via email to