You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/08/13 10:06:25 UTC

[3/4] groovy-examples git commit: initial version

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/groovyShell/ArithmeticShellTest.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovyShell/ArithmeticShellTest.groovy b/src/main/groovy/groovyShell/ArithmeticShellTest.groovy
new file mode 100644
index 0000000..2c9e982
--- /dev/null
+++ b/src/main/groovy/groovyShell/ArithmeticShellTest.groovy
@@ -0,0 +1,108 @@
+/*
+ *  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.
+ */
+/**
+* Unit test for ArithmeticShell.
+* Requires JUnit to be in path, just like any other GroovyTestCase. 
+*
+* @author Hamlet D'Arcy
+*/
+class ArithmeticShellTest extends GroovyTestCase {
+
+    private ArithmeticShell shell
+
+    void setUp() {
+        shell = new ArithmeticShell()
+    }
+
+    Number evaluate(String text) {
+        shell.evaluate(text)
+    }
+
+    void testEvaluate_SuccessfulPaths() {
+        assert 2.9073548971824276E135 == evaluate("((6L / 2f) - 1) ** 4.5e2")
+        assert -6.816387600233341 == evaluate("10 * Math.sin(15/-20)")
+        assert 1.0 == evaluate("Math.cos(2*Math.PI)")
+        assert 74.17310622494026 == shell.evaluate("80*Math.E**(-(+(11++/40)**2))")
+        assert 2147483646 == evaluate("Integer.MAX_VALUE - ++2%2")
+        assert 6 == evaluate("++(5)")
+        assert 0 == evaluate("5 < 4 ? 1 : 0")
+        assert 0 == evaluate("5 != 4 ? 0 : 1")
+        assert 0 == evaluate("5 < 4 ?: 0 ")
+    }
+
+    void testEvaluate_StaticImportOfMath() {
+        assert 6.283185307179586 == evaluate("2*PI")
+        assert 0.5403023058681398 == evaluate("cos(1)")
+        assert 1.0 == evaluate("cos(2*PI)")
+    }
+
+    void testEvaluate_Failures() {
+        shouldFail(SecurityException) {
+            evaluate("Double.valueOf(\"5\")")
+        }
+
+        shouldFail(SecurityException) {
+            evaluate("import java.text.DateFormat; 5")
+        }
+
+        shouldFail(SecurityException) {
+            evaluate("import static java.lang.System.*; 6 * out")
+        }
+
+        shouldFail(SecurityException) {
+            evaluate("def x = 5+3;x.toString()")
+        }
+
+        shouldFail(SecurityException) {
+            evaluate("new File();Double.valueOf('5')")
+        }
+
+        shouldFail(SecurityException) {
+             // without statement whitelist set, this causes the arithmetic shell to
+             // enter an infinite loop
+             evaluate("while (1);")    
+        }
+
+        shouldFail(SecurityException) {
+             // without statement whitelist set, security exception is still thrown as it should,
+             // but with the error message that closures are not allowed, which may be confusing
+             evaluate("for (;;);")
+        }
+
+        shouldFail(SecurityException) {
+             // without statement whitelist set, no exception is thrown
+             evaluate("if (1) 12 else 15")    
+        }
+
+        shouldFail(SecurityException) {
+             // without expression whitelist set, no exception is thrown
+             evaluate("[1,2]; 1")
+        }
+
+        shouldFail(SecurityException) {
+             // without expression whitelist set, no exception is thrown
+             evaluate("[1:2]; 1")
+        }
+
+        shouldFail(SecurityException) {
+             // without expression whitelist set, no exception is thrown
+             evaluate("new Object(); 1")
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/groovyShell/BlacklistingShell.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovyShell/BlacklistingShell.groovy b/src/main/groovy/groovyShell/BlacklistingShell.groovy
new file mode 100644
index 0000000..becff49
--- /dev/null
+++ b/src/main/groovy/groovyShell/BlacklistingShell.groovy
@@ -0,0 +1,96 @@
+/*
+ *  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.
+ */
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.MultipleCompilationErrorsException
+import org.codehaus.groovy.control.customizers.SecureASTCustomizer
+import org.codehaus.groovy.control.messages.ExceptionMessage
+import org.codehaus.groovy.ast.expr.MethodPointerExpression
+
+
+/**
+ * The blacklisting shell is similar to a GroovyShell in that it can evaluate text as
+ * code and return a result. It is intended as an example of using blacklisting to prevent
+ * running methods on a class - in this case, java.lang.System.  Please note that in creating
+ * any secure environment, there is no substitution for using a SecurityManager.
+ *
+ * Amoung the many different calls this class prevents are:
+ *   System.exit(0)
+ *   Eval.me("System.exit(0)")
+ *   evaluate("System.exit(0)")
+ *   (new GroovyShell()).evaluate("System.exit(0)")
+ *   Class.forName("java.lang.System").exit(0)
+ *   System.&exit.call(0)
+ *   System.getMetaClass().invokeMethod("exit",0)
+ *   def s = System; s.exit(0)
+ *   Script t = this; t.evaluate("System.exit(0)")
+ *
+ * The restrictions required, however, also prevent the following code from working:
+ *   println "test"
+ *   def s = "test" ; s.count("t") 
+ *
+ * @author Jim Driscoll (jamesgdriscoll@gmail.com)
+ */
+class BlacklistingShell {
+    
+    /**
+     * Compiles the text into a Groovy object and then executes it, returning the result.
+     * Prevents calling any method on java.lang.System within the VM
+     * @param text
+     *       the script to evaluate typed as a string
+     * @throws SecurityException
+     *       most likely the script is doing something other than arithmetic
+     * @throws IllegalStateException
+     *       if the script returns something other than a number
+     */
+    def evaluate(String text) {
+        try {
+            final SecureASTCustomizer secure = new SecureASTCustomizer()
+            secure.with {
+
+                receiversClassesBlackList = [
+                    Object,
+                    Script,
+                    GroovyShell,
+                    Eval,
+                    System,
+                ].asImmutable()
+                
+                expressionsBlacklist = [MethodPointerExpression].asImmutable()
+                
+            }
+            CompilerConfiguration config = new CompilerConfiguration()
+            config.addCompilationCustomizers(secure)
+            GroovyClassLoader loader = new GroovyClassLoader(this.class.classLoader, config)
+            Class clazz = loader.parseClass(text)
+            Script script = (Script) clazz.newInstance();
+            Object result = script.run()
+            return result
+        } catch (SecurityException ex) {
+            throw new SecurityException("Could not evaluate script: $text", ex)
+        } catch (MultipleCompilationErrorsException mce) {
+            //this allows compilation errors to be seen by the user       
+            mce.errorCollector.errors.each {
+                if (it instanceof ExceptionMessage && it.cause instanceof SecurityException) {
+                    throw it.cause
+                }
+            }
+            throw mce
+        }
+    }            
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/groovyShell/BlacklistingShellTest.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovyShell/BlacklistingShellTest.groovy b/src/main/groovy/groovyShell/BlacklistingShellTest.groovy
new file mode 100644
index 0000000..e24e149
--- /dev/null
+++ b/src/main/groovy/groovyShell/BlacklistingShellTest.groovy
@@ -0,0 +1,82 @@
+/*
+ *  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.
+ */
+/**
+* Unit test for BlacklistingShell.
+* Requires JUnit to be in path, just like any other GroovyTestCase. 
+*
+* @author Jim Driscoll jamesgdriscoll@gmail.com
+*/
+class BlacklistingShellTest extends GroovyTestCase {
+
+    private BlacklistingShell shell
+
+    void setUp() {
+        shell = new BlacklistingShell()
+    }
+
+    Object evaluate(String text) {
+        shell.evaluate(text)
+    }
+
+    void testEvaluate_SuccessfulPaths() {
+        assert 6 == evaluate("++(5)")
+        assert 0 == evaluate("5 < 4 ? 1 : 0")
+        assert 0 == evaluate("5 != 4 ? 0 : 1")
+        assert 0 == evaluate("5 < 4 ?: 0 ")
+    }
+
+    void testEvaluate_Failures() {
+        shouldFail(SecurityException) {
+            evaluate('def c = System.class; c.forName("java.lang.System").exit(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('Class.forName("java.lang.System").exit(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('System.exit(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('def e = System.&exit; e.call(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('System.&exit.call(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('System.getMetaClass().invokeMethod("exit",0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('evaluate("System.exit(0)")')
+        }
+        shouldFail(SecurityException) {
+            evaluate('(new GroovyShell()).evaluate("System.exit(0)")')
+        }
+        shouldFail(SecurityException) {
+            evaluate('def sh = new GroovyShell(); sh.evaluate("System.exit(0)")')
+        }
+        shouldFail(SecurityException) {
+            evaluate('Eval.me("System.exit(0)")')
+        }
+        shouldFail(SecurityException) {
+            evaluate('def s = System; s.exit(0)')
+        }
+        shouldFail(SecurityException) {
+            evaluate('Script t = this; t.evaluate("System.exit(0)")')
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java b/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java
new file mode 100644
index 0000000..848ebf3
--- /dev/null
+++ b/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java
@@ -0,0 +1,264 @@
+/*
+ *  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.grails.compiler.injection;
+
+//import org.apache.commons.logging.Log;
+//import org.apache.commons.logging.LogFactory;
+
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * This is substantially the same code from Grails, except some references de-referenced
+ * and the macro class added.
+ *
+ * Default implementation of domain class injector interface that adds the 'id'
+ * and 'version' properties and other previously boilerplate code
+ *
+ * @author Graeme Rocher
+ *
+ * @since 0.2
+ *
+ * Created: 20th June 2006
+ */
+@GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION)
+public class DefaultGrailsDomainClassInjector implements ASTTransformation {
+        //GrailsDomainClassInjector {
+
+    //private static final Log LOG  = LogFactory.getLog(DefaultGrailsDomainClassInjector.class);
+
+
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        performInjection((ClassNode) nodes[1]);
+    }
+
+    public void performInjection(ClassNode classNode) {
+        if(shouldInject(classNode)) {
+            injectIdProperty(classNode);
+
+            injectVersionProperty(classNode);
+
+            injectToStringMethod(classNode);
+
+            injectAssociations(classNode);
+
+        }
+    }
+
+    public boolean shouldInject(URL url) {
+        return true; //return GrailsResourceUtils.isDomainClass(url);
+    }
+
+    private boolean shouldInject(ClassNode classNode) {
+        //String fullName = GrailsASTUtils.getFullName(classNode);
+        //String mappingFile = GrailsDomainConfigurationUtil.getMappingFileName(fullName);
+
+        //if(getClass().getResource(mappingFile)!=null) {
+            //if(LOG.isDebugEnabled()) {
+                //LOG.debug("[GrailsDomainInjector] Mapping file ["+mappingFile+"] found. Skipping property injection.");
+            //}
+            //return false;
+        //}
+        return true;
+    }
+
+    private void injectAssociations(ClassNode classNode) {
+
+        List properties = classNode.getProperties();
+        List propertiesToAdd = new ArrayList();
+        for (Iterator p = properties.iterator(); p.hasNext();) {
+            PropertyNode pn = (PropertyNode) p.next();
+            final boolean isHasManyProperty = pn.getName().equals(/*GrailsDomainClassProperty.*/RELATES_TO_MANY) || pn.getName().equals(/*GrailsDomainClassProperty.*/HAS_MANY);
+            if(isHasManyProperty) {
+                Expression e = pn.getInitialExpression();
+                propertiesToAdd.addAll(createPropertiesForHasManyExpression(e,classNode));
+            }
+            final boolean isBelongsTo = pn.getName().equals(/*GrailsDomainClassProperty.*/BELONGS_TO);
+            if(isBelongsTo) {
+                Expression e = pn.getInitialExpression();
+                propertiesToAdd.addAll(createPropertiesForBelongsToExpression(e,classNode));
+            }
+        }
+        injectAssociationProperties(classNode,propertiesToAdd);
+    }
+
+    private Collection createPropertiesForBelongsToExpression(Expression e, ClassNode classNode)
+    {
+        List properties = new ArrayList();
+        if(e instanceof MapExpression) {
+            MapExpression me = (MapExpression)e;
+            List mapEntries = me.getMapEntryExpressions();
+            for (Iterator i = mapEntries.iterator(); i.hasNext();) {
+                MapEntryExpression mme = (MapEntryExpression) i.next();
+                String key = mme.getKeyExpression().getText();
+
+                String type = mme.getValueExpression().getText();
+
+                properties.add(new PropertyNode(key,Modifier.PUBLIC, ClassHelper.make(type) , classNode, null,null,null));
+            }
+        }
+
+        return properties;
+    }
+
+    private void injectAssociationProperties(ClassNode classNode, List propertiesToAdd) {
+        for (Iterator i = propertiesToAdd.iterator(); i.hasNext();) {
+            PropertyNode pn = (PropertyNode) i.next();
+            if(!/*GrailsASTUtils.*/hasProperty(classNode, pn.getName())) {
+                //if(LOG.isDebugEnabled()) {
+                //    LOG.debug("[GrailsDomainInjector] Adding property [" + pn.getName() + "] to class [" + classNode.getName() + "]");
+                //}
+                classNode.addProperty(pn);
+            }
+        }
+    }
+
+    private List createPropertiesForHasManyExpression(Expression e, ClassNode classNode) {
+        List properties = new ArrayList();
+        if(e instanceof MapExpression) {
+            MapExpression me = (MapExpression)e;
+            List mapEntries = me.getMapEntryExpressions();
+            for (Iterator j = mapEntries.iterator(); j.hasNext();) {
+                MapEntryExpression mee = (MapEntryExpression) j.next();
+                Expression keyExpression = mee.getKeyExpression();
+                String key = keyExpression.getText();
+                addAssociationForKey(key,properties,classNode);
+            }
+        }
+        return properties;
+    }
+
+    private void addAssociationForKey(String key, List properties, ClassNode classNode) {
+            properties.add(new PropertyNode(key, Modifier.PUBLIC, new ClassNode(Set.class), classNode,null, null, null));
+    }
+
+    private void injectToStringMethod(ClassNode classNode) {
+        final boolean hasToString = /*GrailsASTUtils.*/implementsZeroArgMethod(classNode, "toString");
+
+        if(!hasToString) {
+            GStringExpression ge = new GStringExpression(classNode.getName() + " : ${id}");
+            ge.addString(new ConstantExpression(classNode.getName()+" : "));
+            ge.addValue(new VariableExpression("id"));
+            Statement s = new ReturnStatement(ge);
+            MethodNode mn = new MethodNode("toString",Modifier.PUBLIC,new ClassNode(String.class), new Parameter[0],new ClassNode[0],s);
+            //if(LOG.isDebugEnabled()) {
+            //    LOG.debug("[GrailsDomainInjector] Adding method [toString()] to class [" + classNode.getName() + "]");
+            //}
+            classNode.addMethod(mn);
+        }
+    }
+
+    private void injectVersionProperty(ClassNode classNode) {
+        final boolean hasVersion = /*GrailsASTUtils.*/hasProperty(classNode, /*GrailsDomainClassProperty.*/VERSION);
+
+        if(!hasVersion) {
+            //if(LOG.isDebugEnabled()) {
+            //    LOG.debug("[GrailsDomainInjector] Adding property [" + GrailsDomainClassProperty.VERSION + "] to class [" + classNode.getName() + "]");
+            //}
+            classNode.addProperty( /*GrailsDomainClassProperty.*/VERSION, Modifier.PUBLIC, new ClassNode(Long.class), null, null, null);
+        }
+    }
+
+    private void injectIdProperty(ClassNode classNode) {
+        final boolean hasId = /*GrailsASTUtils.*/hasProperty(classNode,/*GrailsDomainClassProperty.*/IDENTITY);
+
+        if(!hasId) {
+            //if(LOG.isDebugEnabled()) {
+            //    LOG.debug("[GrailsDomainInjector] Adding property [" + GrailsDomainClassProperty.IDENTITY + "] to class [" + classNode.getName() + "]");
+            //}
+            classNode.addProperty( /*GrailsDomainClassProperty.*/IDENTITY, Modifier.PUBLIC, new ClassNode(Long.class), null, null, null);
+        }
+    }
+
+
+    //***************************************************************
+    // from GrailsASTUtils
+    //***************************************************************
+    /**
+     * Returns whether a classNode has the specified property or not
+     *
+     * @param classNode The ClassNode
+     * @param propertyName The name of the property
+     * @return True if the property exists in the ClassNode
+     */
+    public static boolean hasProperty(ClassNode classNode, String propertyName) {
+        if(classNode == null || propertyName == null || "".equals(propertyName.trim()))
+            return false;
+
+        List properties = classNode.getProperties();
+        for (Iterator i = properties.iterator(); i.hasNext();) {
+            PropertyNode pn = (PropertyNode) i.next();
+            if(pn.getName().equals(propertyName))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Tests whether the ClasNode implements the specified method name
+     *
+     * @param classNode The ClassNode
+     * @param methodName The method name
+     * @return True if it does implement the method
+     */
+    public static boolean implementsZeroArgMethod(ClassNode classNode, String methodName) {
+        return implementsMethod(classNode, methodName, new Class[0]);
+    }
+
+
+    /**
+     * Tests whether the ClassNode implements the specified method name
+     *
+     * @param classNode The ClassNode
+     * @param methodName The method name
+     * @param argTypes
+     * @return True if it implements the method
+     */
+    private static boolean implementsMethod(ClassNode classNode, String methodName, Class[] argTypes) {
+        List methods = classNode.getMethods();
+        if (argTypes == null || argTypes.length ==0) {
+            for (Iterator i = methods.iterator(); i.hasNext();) {
+                MethodNode mn = (MethodNode) i.next();
+                boolean methodMatch = mn.getName().equals(methodName);
+                if(methodMatch)return true;
+                // TODO Implement further parameter analysis
+            }
+        }
+        return false;
+    }
+
+    //***************************************************************
+    // from GrailsDomainClassProperty
+    //***************************************************************
+    private static final String RELATES_TO_MANY = "relatesToMany";
+    private static final String BELONGS_TO = "belongsTo";
+    private static final String HAS_MANY = "hasMany";
+    private static final String IDENTITY = "id";
+    private static final String VERSION = "version";
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DomainClass.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DomainClass.java b/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DomainClass.java
new file mode 100644
index 0000000..3494ede
--- /dev/null
+++ b/src/main/groovy/org/codehaus/groovy/grails/compiler/injection/DomainClass.java
@@ -0,0 +1,38 @@
+/*
+ *  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.grails.compiler.injection;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Danno
+ * Date: Jan 30, 2008
+ * Time: 8:11:08 PM
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+@GroovyASTTransformationClass("org.codehaus.groovy.grails.compiler.injection.DefaultGrailsDomainClassInjector")
+public @interface DomainClass {
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/build.properties
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/build.properties b/src/main/groovy/osgi/build.properties
new file mode 100644
index 0000000..bb104c4
--- /dev/null
+++ b/src/main/groovy/osgi/build.properties
@@ -0,0 +1,28 @@
+#
+#  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.
+#
+
+# groovy bin dir is required to locate the groovy jar 
+# in case it's not on the classpath
+groovy.bin.dir=../../../target/dist
+
+# version is required to locate jar file and build jar manifest
+groovy.version=1.7-beta-1-SNAPSHOT
+
+# osgi jar file location must be specified so code compiles
+osgi.jar=../../../../equinox-3.4/eclipse/plugins/org.eclipse.osgi_3.4.0.v20080605-1900.jar

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/build.xml
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/build.xml b/src/main/groovy/osgi/build.xml
new file mode 100644
index 0000000..9d31efb
--- /dev/null
+++ b/src/main/groovy/osgi/build.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0"?>
+<!--
+
+     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.
+
+-->
+<project name="groovy-osgi-sample" default="print-results">
+
+  <property name="build.dir" value="build"/>
+  <property file="build.properties"/>
+  <property name="groovy.jar" value="groovy-${groovy.version}.jar" />
+  <property name="hello.bundle" value="hello-bundle-imports-groovy.jar" />
+  <property name="hello.bundle.contains" value="hello-bundle-contains-groovy.jar" />
+  <property name="harness.bundle" value="hello-groovy-test-harness.jar" />
+    
+  <taskdef name="groovyc"
+	   classname="org.codehaus.groovy.ant.Groovyc"
+	   classpathref="project.classpath"/>
+  
+  <target name="init" description="cleanup and reinitialize">
+	  <path id="project.classpath">
+		  <pathelement location="${osgi.jar}"/>
+		  <pathelement location="${groovy.bin.dir}${file.separator}${groovy.jar}"/>
+		  <pathelement location="${build.dir}${file.separator}${hello.bundle}"/>
+	  </path>
+	  
+	  <delete dir="${build.dir}" />
+	  <mkdir dir="${build.dir}${file.separator}hello-groovy-bundle" />
+	  <mkdir dir="${build.dir}${file.separator}hello-groovy-test-harness" />
+  </target>
+  
+  <target name="make-hello-groovy-bundle" description="compile and build the Hello Groovy bundles">
+  
+    <groovyc destdir="${build.dir}${file.separator}hello-groovy-bundle"
+	     srcdir=".${file.separator}hello-groovy-bundle"
+              listfiles="true">
+              <classpath refid="project.classpath"/>
+    </groovyc>
+    
+    <!-- This jar file imports Groovy from the container -->
+    <jar destfile="${build.dir}${file.separator}${hello.bundle}">
+	    <fileset dir="${build.dir}${file.separator}hello-groovy-bundle"/>
+	    <manifest>
+		    <attribute name="Built-By" value="${user.name}"/>
+		    <attribute name="provider" value="org.codehaus.groovy.osgi"/>
+		    <attribute name="Bundle-ManifestVersion" value="2"/>
+		    <attribute name="Bundle-Name" value="Groovy OSGi Example Bundle"/>
+		    <attribute name="Bundle-SymbolicName" value="org.codehaus.groovy.osgi.hello-groovy-bundle"/>
+		    <attribute name="Bundle-Version" value="1.0.0"/>
+		    <attribute name="Bundle-Activator" value="org.codehaus.groovy.osgi.Activator"/>
+		    <attribute name="Bundle-Vendor" value="Groovy"/>
+		    <attribute name="Bundle-Localization" value="plugin"/>
+		    <attribute name="Import-Package" value="groovy.lang;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.reflection;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.runtime;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.runtime.callsite;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.w3c.dom,org.osgi.framework;version=&quot;1.3.0&quot;"/> 
+		    <attribute name="Export-Package" value="org.codehaus.groovy.osgi;version=&quot;1.0.0&quot;"/> 
+		    <attribute name="Bundle-ClassPath" value="."/>
+	    </manifest>
+    </jar>
+    
+    <!-- This jar includes the Groovy jar within itself-->
+    <jar destfile="${build.dir}${file.separator}${hello.bundle.contains}">
+	    <fileset dir="${build.dir}${file.separator}hello-groovy-bundle"/>
+	    <fileset file="${groovy.bin.dir}${file.separator}${groovy.jar}" casesensitive="no" />
+	    <manifest>
+		    <attribute name="Built-By" value="${user.name}"/>
+		    <attribute name="provider" value="org.codehaus.groovy.osgi"/>
+		    <attribute name="Bundle-ManifestVersion" value="2"/>
+		    <attribute name="Bundle-Name" value="Groovy OSGi Example Bundle"/>
+		    <attribute name="Bundle-SymbolicName" value="org.codehaus.groovy.osgi.hello-groovy-bundle"/>
+		    <attribute name="Bundle-Version" value="1.0.0"/>
+		    <attribute name="Bundle-Activator" value="org.codehaus.groovy.osgi.Activator"/>
+		    <attribute name="Bundle-Vendor" value="Groovy"/>
+		    <attribute name="Bundle-Localization" value="plugin"/>
+		    <attribute name="Import-Package" value="org.w3c.dom,org.osgi.framework;version=&quot;1.3.0&quot;"/> 
+		    <attribute name="Export-Package" value="org.codehaus.groovy.osgi;version=&quot;1.0.0&quot;"/> 
+		    <attribute name="Bundle-ClassPath" value=".,${groovy.jar}"/>
+	    </manifest>
+    </jar> 
+  </target>
+
+  <target name="make-harness-bundle" description="Makes the test harness bundle">
+	  
+	  <groovyc destdir="${build.dir}${file.separator}hello-groovy-test-harness"
+		   srcdir=".${file.separator}hello-groovy-test-harness"
+		   listfiles="true">
+		  <classpath refid="project.classpath"/>
+	  </groovyc>
+	  
+	  <jar destfile="${build.dir}${file.separator}${harness.bundle}">
+		  <fileset dir="${build.dir}${file.separator}hello-groovy-test-harness"/>
+		  <manifest>
+			  <attribute name="Built-By" value="${user.name}"/>
+			  <attribute name="provider" value="org.codehaus.groovy.osgi.harness"/>
+			  <attribute name="Bundle-ManifestVersion" value="2"/>
+			  <attribute name="Bundle-Name" value="Groovy OSGi Test Harness"/>
+			  <attribute name="Bundle-SymbolicName" value="org.codehaus.groovy.osgi.harness.hello-groovy-test-harness"/>
+			  <attribute name="Bundle-Version" value="1.0.0"/>
+			  <attribute name="Bundle-Activator" value="org.codehaus.groovy.osgi.harness.HarnessActivator"/>
+			  <attribute name="Bundle-Vendor" value="Groovy"/>
+			  <attribute name="Bundle-Localization" value="plugin"/>
+			  <attribute name="Import-Package" value="org.codehaus.groovy.runtime.typehandling;version=&quot;1.0.0&quot;,org.codehaus.groovy.osgi;version=&quot;1.0.0&quot;,groovy.lang;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.reflection;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.runtime;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.codehaus.groovy.runtime.callsite;version=&quot;1.7.0.beta-1-SNAPSHOT&quot;,org.w3c.dom,org.osgi.framework;version=&quot;1.3.0&quot;"/> 
+			  <attribute name="Bundle-ClassPath" value="."/>
+		  </manifest>
+	  </jar>
+	  
+  </target>
+
+  <target name="print-results" depends="init,make-hello-groovy-bundle,make-harness-bundle" description="compile and build everything">
+	<makeurl file="${basedir}/${groovy.bin.dir}/${groovy.jar}" property="groovy.jar.url"/>
+	<makeurl file="${basedir}/${build.dir}/${hello.bundle}" property="hello.jar1.url"/>
+	<makeurl file="${basedir}/${build.dir}/${hello.bundle.contains}" property="hello.jar2.url"/>
+	<makeurl file="${basedir}/${build.dir}/${harness.bundle}" property="harness.jar.url"/>
+	
+	<echo>To run the OSGi console, run the following command: 
+ java -jar ${osgi.jar} -console 
+To install these applications in the container, run the following commands in the OSGi container: 
+ install ${groovy.jar.url}
+ install ${hello.jar1.url}
+ install ${hello.jar2.url}
+ install ${harness.jar.url}
+To start the applications in the container, run the following commands in the OSGi container: 
+ start [bundle1] [bundle2]
+Where [bundle1] and [bundle] are the bundle IDs printed by the console in the previous step.</echo>  
+  </target>
+</project>
+

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/Activator.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/Activator.groovy b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/Activator.groovy
new file mode 100644
index 0000000..e250ec1
--- /dev/null
+++ b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/Activator.groovy
@@ -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.osgi
+
+import org.osgi.framework.BundleActivator
+import org.osgi.framework.BundleContext
+import org.osgi.framework.ServiceRegistration;
+
+/**
+* This is the OSGi Activator for the Groovy example bundles. 
+* Two things happen when the container starts this bundle: 
+*   1) a message is printed to standard out
+*   2) a service of type GroovyGreeter is added to the context 
+* The service is unregistered when the bundle is stopped. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+public class Activator implements BundleActivator {
+
+    ServiceRegistration registration
+  
+    public void start(BundleContext context) {
+        println "Groovy BundleActivator started"
+  
+        // Normally, the classloader code would not need to be run when 
+        // adding a service to the context. However, this is required when
+        // adding a Groovy service because of the way Groovy uses class 
+        // loaders and reflection. 
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader()
+        try {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader())
+            GroovyGreeter myService = new GroovyGreeterImpl()
+            registration = context.registerService(GroovyGreeter.class.getName(), myService, null)
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader)
+        }
+    }
+  
+    public void stop(BundleContext context) {
+        println "Groovy BundleActivator stopped"
+        registration.unregister();
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeter.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeter.groovy b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeter.groovy
new file mode 100644
index 0000000..4409cd4
--- /dev/null
+++ b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeter.groovy
@@ -0,0 +1,30 @@
+/*
+ *  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.osgi
+
+/**
+* Objects of this type know how to print simple messages to 
+* standard out. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+public interface GroovyGreeter {
+  
+    void sayHello(); 
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeterImpl.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeterImpl.groovy b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeterImpl.groovy
new file mode 100644
index 0000000..209871e
--- /dev/null
+++ b/src/main/groovy/osgi/hello-groovy-bundle/org/codehaus/groovy/osgi/GroovyGreeterImpl.groovy
@@ -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.osgi
+
+/**
+* A simple POGO that prints a greeting to standard out. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+public class GroovyGreeterImpl implements GroovyGreeter {
+  
+    public void sayHello() {
+        println "Hello from the Groovy Greeter!"
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/hello-groovy-test-harness/org/codehaus/groovy/osgi/harness/HarnessActivator.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/hello-groovy-test-harness/org/codehaus/groovy/osgi/harness/HarnessActivator.groovy b/src/main/groovy/osgi/hello-groovy-test-harness/org/codehaus/groovy/osgi/harness/HarnessActivator.groovy
new file mode 100644
index 0000000..adccf08
--- /dev/null
+++ b/src/main/groovy/osgi/hello-groovy-test-harness/org/codehaus/groovy/osgi/harness/HarnessActivator.groovy
@@ -0,0 +1,50 @@
+/*
+ *  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.osgi.harness
+
+import org.osgi.framework.BundleActivator
+import org.osgi.framework.BundleContext
+import org.osgi.framework.ServiceRegistration
+import org.osgi.framework.ServiceReference
+import org.codehaus.groovy.osgi.GroovyGreeter
+
+/**
+ * This OSGi Activator finds all registered services of type GroovyGreeter
+ * and then invokes the sayHello() method on any that it finds.
+ *
+ * @author Hamlet D'Arcy
+ */
+public class HarnessActivator implements BundleActivator {
+
+    public void start(BundleContext context) {
+        String serviceName = GroovyGreeter.class.getName()
+        ServiceReference[] references = context.getAllServiceReferences(serviceName, null)
+
+        println "${ references ? references.size() : 0 } GroovyGreeter services found."
+
+        references?.each { ServiceReference ref ->
+            Object serviceHandle = context.getService(ref)
+            GroovyGreeter service = (GroovyGreeter) serviceHandle
+            service.sayHello()
+        }
+    }
+
+    public void stop(BundleContext context) {
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/osgi/readme.txt
----------------------------------------------------------------------
diff --git a/src/main/groovy/osgi/readme.txt b/src/main/groovy/osgi/readme.txt
new file mode 100644
index 0000000..7ee164c
--- /dev/null
+++ b/src/main/groovy/osgi/readme.txt
@@ -0,0 +1,140 @@
+====
+     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.
+====
+
+About this Example
+------------------
+This example demonstrates two different usages of OSGi. The "hello-groovy-bundle" 
+service demonstrates how to write an OSGi bundle using Groovy. It is a simple 
+service that does two things when started within an OSGi container: 1) it prints 
+out a message to the console, and 2) it adds a Groovy service to the OSGi context 
+that can be consumed by the second example.  
+
+The "hello-groovy-test-harness" service, also written in Groovy, demonstrates how 
+to import and use the previous "hello-groovy-bundle" OSGi service. It locates and 
+invokes the service from the first example, which results in a message being 
+written to the console. 
+
+
+Building this Example
+---------------------
+IMPORTANT: You must edit build.properties before building the example. There are 
+three properties that must be set in build.properties: 
+  groovy.bin.dir - The example requires you specify the location of your 
+                   groovy jar. This is how the build finds groovyc.
+  groovy.version - The example requires you specify the version of your groovy
+                   jar. This is so that the jar files can be built correctly. 
+  osgi.jar - The example requires you specify the location of the OSGi jar. 
+                   This is required to compile the code. 
+
+This example was tested using the OSGi jar from Equinox 3.4, the OSGi container 
+that ships with Eclipse. You can download the Equinox jar from the Equinox website
+or search for it within your Eclipse directories. The jar will have a name similar 
+to : org.eclipse.osgi_3.4.0.v20080605-1900.jar
+  
+Once these properties are set, simply run ant to build: 
+  
+  ant
+
+The build creates three jar files: 
+  hello-bundle-imports-groovy.jar - OSGi bundle written in Groovy that resolves the 
+        groovy Jar file from the container.
+  hello-bundle-contains-groovy.jar - OSGi bundle written in Groovy that resolves the
+        groovy Jar file from within itself. The container never sees Groovy.
+  hello-groovy-test-harness.jar - OSGi bundle that loads and tests one of the previous
+        two services. 
+  
+The build also prints out the file URLs of the jar files. You need these URLs to 
+run the example. Also printed to the console is the command to run the Equinox 
+container. The final output of the Ant script may look like this: 
+
+     [echo] To run the OSGi console, run the following command:
+     [echo]  java -jar ../../../../equinox-3.4/eclipse/plugins/org.eclipse.osgi_3.4.0.v20080605-1900.jar -console
+     [echo] To install these applications in the container, run the following commands in the OSGi container:
+     [echo]  install file:/home/user/dev/groovy-core/target/dist/groovy-all-1.7.0.jar
+     [echo]  install file:/home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-imports-groovy.jar
+     [echo]  install file:/home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-contains-groovy.jar
+     [echo]  install file:/home/user/dev/groovy-core/src/examples/osgi/build/hello-groovy-test-harness.jar
+     [echo] To start the applications in the container, run the following commands in the OSGi container:
+     [echo]  start [bundle1] [bundle2]
+     [echo] Where [bundle1] and [bundle] are the bundle IDs printed by the console in the previous step.
+
+
+Running this Example
+--------------------
+To run the example you must start the OSGi container, install the services, and 
+start the services. 
+
+To start the Equinox container, invoke the OSGi jar using java: 
+  
+  java -jar ../../../../equinox-3.4/eclipse/plugins/org.eclipse.osgi_3.4.0.v20080605-1900.jar -console
+
+This opens an OSGi console. You should be presented with an OSGi prompt: 
+
+  osgi> 
+
+Type the command "ss" to get a system status: 
+
+  osgi> ss
+
+  Framework is launched.
+
+  id      State       Bundle
+  0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
+
+Install the three bundles using the "install" command and the file URLs of the 
+jars built by Ant. Remember, the Ant script printed the file URLs to the console 
+as part of the build (replace groovy-all with groovy for 2.5+).
+
+osgi> install file:/home/user/dev/groovy-core/target/dist/groovy-all-1.7.0.jar
+Bundle id is 1
+
+osgi> install file:/home/user/dev/groovy-core/src/examples/osgi/build/hello-bundle-imports-groovy.jar
+Bundle id is 2
+
+osgi> install file:/home/user/dev/groovy-core/src/examples/osgi/build/hello-groovy-test-harness.jar
+Bundle id is 3
+
+Run the ss command to verify the bundles loaded correctly: 
+
+
+osgi> ss
+
+Framework is launched.
+
+id      State       Bundle
+0       ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
+1       INSTALLED   groovy-all_1.7.0
+2       INSTALLED   org.codehaus.groovy.osgi.hello-groovy-bundle_1.0.0
+3       INSTALLED   org.codehaus.groovy.osgi.harness.hello-groovy-test-harness_1.0.0
+
+Start the bundles with the "start" command to see them working: 
+
+osgi> start 1 2 3
+Groovy BundleActivator started
+1 GroovyGreeter services found.
+Hello from the Groovy Greeter!
+
+As expected, bundle 2 printed out a message from an object implemented in Groovy, 
+and bundle 3 printed out a message from a service implemented in Groovy, which it 
+loaded as an OSGi service from the BundleContext. 
+
+You may wish to uninstall the services using the "uninstall" command: 
+
+  osgi> uninstall 3 2 1
+  Groovy BundleActivator stopped

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/searchEngine/Indexer.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/searchEngine/Indexer.groovy b/src/main/groovy/searchEngine/Indexer.groovy
new file mode 100644
index 0000000..2709e6a
--- /dev/null
+++ b/src/main/groovy/searchEngine/Indexer.groovy
@@ -0,0 +1,83 @@
+/*
+ *  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.
+ */
+import org.apache.lucene.analysis.standard.StandardAnalyzer
+import org.apache.lucene.document.Document
+import org.apache.lucene.document.Field
+import org.apache.lucene.index.IndexWriter
+import org.apache.lucene.util.Version
+import org.apache.lucene.index.IndexWriterConfig
+import org.apache.lucene.store.FSDirectory
+import org.apache.lucene.document.TextField
+import org.apache.lucene.document.StringField
+import static org.apache.lucene.document.Field.Store.*
+
+/**
+ * Indexer: traverses a file system and indexes .txt files
+ *
+ * @author Jeremy Rayner <gr...@ross-rayner.com>
+ * based on examples in the wonderful 'Lucene in Action' book
+ * by Erik Hatcher and Otis Gospodnetic (https://www.manning.com/books/lucene-in-action-second-edition)
+ *
+ * June 25th, 2013: Updated for Lucene 4.3.1
+ * requires a lucene-4.3.x.jar from http://lucene.apache.org
+ */
+
+if (args.size() != 2 ) {
+    throw new Exception("Usage: groovy -cp lucene-1.4.3.jar Indexer <index dir> <data dir>")
+}
+def indexDir = FSDirectory.open(new File(args[0])) // Create Lucene index in this directory
+def dataDir = new File(args[1]) // Index files in this directory
+
+def start = new Date().time
+def numIndexed = index(indexDir, dataDir)
+def end = new Date().time
+
+println "Indexing $numIndexed files took ${end - start} milliseconds"
+
+def index(indexDir, dataDir) {
+    if (!dataDir.exists() || !dataDir.directory) {
+        throw new IOException("$dataDir does not exist or is not a directory")
+    }
+    def config = new IndexWriterConfig(Version.LUCENE_43, new StandardAnalyzer(Version.LUCENE_43))
+    def writer = new IndexWriter(indexDir, config) // Create Lucene index
+
+    dataDir.eachFileRecurse {
+        if (it.name =~ /.txt$/) { // Index .txt files only
+            indexFile(writer,it)
+        }
+    }
+    def numIndexed = writer.numDocs()
+    writer.close() // Close index
+    return numIndexed
+}
+
+void indexFile(writer, f) {
+    if (f.hidden || !f.exists() || !f.canRead() || f.directory) { return }
+
+    println "Indexing $f.canonicalPath"
+    def doc = new Document()
+
+    // Construct a Field that is tokenized and indexed, but is not stored in the index verbatim.
+    doc.add(new TextField("contents", f.newReader()))
+
+    // Construct a Field that is not tokenized, but is indexed and stored.
+    doc.add(new StringField("filename",f.canonicalPath, YES))
+
+    writer.addDocument(doc) // Add document to Lucene index
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/searchEngine/Searcher.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/searchEngine/Searcher.groovy b/src/main/groovy/searchEngine/Searcher.groovy
new file mode 100644
index 0000000..4afd145
--- /dev/null
+++ b/src/main/groovy/searchEngine/Searcher.groovy
@@ -0,0 +1,61 @@
+/*
+ *  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.
+ */
+import org.apache.lucene.analysis.standard.StandardAnalyzer
+import org.apache.lucene.queryparser.classic.QueryParser
+import org.apache.lucene.search.IndexSearcher
+import org.apache.lucene.store.FSDirectory
+import org.apache.lucene.util.Version
+import org.apache.lucene.index.DirectoryReader
+
+/**
+ * Searcher: searches a Lucene index for a query passed as an argument
+ *
+ * @author Jeremy Rayner <gr...@ross-rayner.com>
+ * based on examples in the wonderful 'Lucene in Action' book
+ * by Erik Hatcher and Otis Gospodnetic (https://www.manning.com/books/lucene-in-action-second-edition)
+ *
+ * June 25th, 2013: Updated for Lucene 4.3.1
+ * requires a lucene-4.x.x.jar from http://lucene.apache.org
+ */
+
+if (args.size() != 2) {
+    throw new Exception("Usage: groovy -cp lucene-4.3.1.jar Searcher <index dir> <query>")
+}
+def indexDir = new File(args[0]) // Index directory create by Indexer
+def q = args[1] // Query string
+
+if (!indexDir.exists() || !indexDir.directory) {
+    throw new Exception("$indexDir does not exist or is not a directory")
+}
+
+def fsDir = DirectoryReader.open(FSDirectory.open(indexDir))
+def is = new IndexSearcher(fsDir) // Open index
+
+def parser = new QueryParser(Version.LUCENE_43, "contents", new StandardAnalyzer(Version.LUCENE_43))
+def query = parser.parse(q) // Parse query
+def start = new Date().time
+def hits = is.search(query, 10) // Search index
+def end = new Date().time
+
+println "Found ${hits.totalHits} document(s) (in ${end - start} milliseconds) that matched query '$q':"
+
+hits.scoreDocs.each { scoreDoc ->
+    println(is.doc(scoreDoc.doc)["filename"]) // Retrieve matching document and display filename
+}
+fsDir.close()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/BindingExample.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/BindingExample.groovy b/src/main/groovy/swing/BindingExample.groovy
new file mode 100644
index 0000000..3cdcb60
--- /dev/null
+++ b/src/main/groovy/swing/BindingExample.groovy
@@ -0,0 +1,63 @@
+/*
+ *  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.
+ */
+/**
+ * @author <a href="mailto:shemnon@yahoo.com">Danno Ferrin</a>
+ * @since Groovy 1.1
+ *
+ * The real interesting part of this example are in the three properties of button:
+ *
+ *           text: bind(source:textField, sourceProperty:'text'),
+ *           margin: bind(source:slider, sourceProperty:'value', converter:{[it, it, it, it] as Insets}),
+ *           enabled: bind(source:checkBox, sourceProperty:'selected')
+ *
+ * This is where the real magic goes on, causing the button to react to the changes
+ * in the source widgets values.
+ */
+ package swing
+
+import groovy.swing.SwingBuilder
+import java.awt.GridBagConstraints as gb
+import java.awt.Insets
+
+sb = SwingBuilder.build() {
+    frame = frame(defaultCloseOperation:javax.swing.JFrame.DISPOSE_ON_CLOSE) {
+        gridBagLayout()
+
+        label("Text:", anchor:gb.WEST, insets:[6,6,3,3] as Insets)
+        textField = textField("Change Me!", fill:gb.HORIZONTAL, gridwidth:gb.REMAINDER, insets:[6,3,3,6] as Insets)
+
+        label("Margin:", anchor:gb.WEST, insets:[3,6,3,3] as Insets)
+        slider = slider(value:5, fill:gb.HORIZONTAL, gridwidth:gb.REMAINDER, insets:[3,3,3,6] as Insets)
+
+        panel()
+        checkBox = checkBox("Enbled", anchor:gb.WEST, gridwidth:gb.REMAINDER, insets:[3,3,3,6] as Insets)
+
+        separator(fill:gb.HORIZONTAL, gridwidth:gb.REMAINDER)
+
+        button(anchor:gb.CENTER, gridwidth:gb.REMAINDER, gridheight:gb.REMAINDER, weightx:1.0, weighty:1.0, insets:[3,6,6,6] as Insets,
+            text: bind(source:textField, sourceProperty:'text'),
+            margin: bind(source:slider, sourceProperty:'value', converter:{[it, it, it, it] as Insets}),
+            enabled: bind(source:checkBox, sourceProperty:'selected')
+        )
+    }
+}
+
+frame.pack()
+frame.setSize(frame.width + 100, frame.height + 200)
+frame.show()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/BloglinesClient.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/BloglinesClient.groovy b/src/main/groovy/swing/BloglinesClient.groovy
new file mode 100644
index 0000000..017ef0a
--- /dev/null
+++ b/src/main/groovy/swing/BloglinesClient.groovy
@@ -0,0 +1,188 @@
+/*
+ *  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.
+ */
+/*
+ * BloglinesClient.groovy - an example of the Bloglines Web Services
+ *
+ * Written by Marc Hedlund <ma...@precipice.org>, September 2004.
+ * 
+ * Mangled by John Wilson September 2004
+ *
+ * Small adaptions to JSR Version by Dierk Koenig, June 2005
+ *
+ * Used in Marc's article at:
+ *    http://www.oreillynet.com/pub/a/network/2004/09/28/bloglines.html
+ *
+ * Requirements:
+ *   - install Groovy as detailed at <http://groovy.codehaus.org/>.
+ *   - put commons-httpclient-3.0-rc3.jar into GROOVY_HOME/lib
+ *       see <http://jakarta.apache.org/commons/httpclient/>.
+ *       note: this is currently designed for HttpClient2.x and not HttpClient3.x
+ *
+ * To Launch:
+ *   groovy BloglinesClient.groovy
+ *
+ * This work is licensed under the Creative Commons Attribution
+ * License. To view a copy of this license, visit
+ * <http://creativecommons.org/licenses/by/2.0/> or send a letter to
+ * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+ */
+package swing
+
+import groovy.swing.SwingBuilder
+import java.awt.BorderLayout
+import javax.swing.JOptionPane
+import javax.swing.JSplitPane
+import javax.swing.JTree
+import javax.swing.ListSelectionModel
+import javax.swing.WindowConstants
+import javax.swing.tree.DefaultMutableTreeNode
+import javax.swing.tree.TreeSelectionModel
+import org.apache.commons.httpclient.HttpClient
+import org.apache.commons.httpclient.UsernamePasswordCredentials
+import org.apache.commons.httpclient.methods.GetMethod
+
+//Set up global variables and data types
+server = 'rpc.bloglines.com'
+
+class Feed {
+    def name;
+    def id;
+    def unread;
+
+    String toString() { (unread == "0" ? name : "${name} (${unread})") }
+}
+
+class Item {
+    def title;
+    def description;
+
+    String toString() { title }
+}
+
+// Ask the user for account information (using simple dialogs)
+email = JOptionPane.showInputDialog(null, "Email address:", "Log in to Bloglines",
+        JOptionPane.QUESTION_MESSAGE)
+password = JOptionPane.showInputDialog(null, "Password:", "Log in to Bloglines",
+        JOptionPane.QUESTION_MESSAGE)
+
+//Use HTTPClient for web requests since the server requires authentication
+client = new HttpClient()
+credentials = new UsernamePasswordCredentials(email, password)
+client.state.setCredentials("Bloglines RPC", server, credentials)
+
+abstractCallBloglines = { method, parameters ->
+    url = "http://${server}/${method}${parameters}"
+    try {
+        get = new GetMethod(url)
+        get.doAuthentication = true
+        client.executeMethod(get)
+        return get.responseBodyAsStream
+    } catch (Exception e) {
+        println "Error retrieving <${url}>: ${e}"
+    }
+}
+
+callBloglinesListsub = abstractCallBloglines.curry('listsubs', '')
+callBloglinesGetItems = abstractCallBloglines.curry('getitems')
+
+//Get the list of subscriptions and parse it into a GPath structure
+opml = new XmlSlurper().parse(callBloglinesListsub())
+
+//Descend into the subscription outline, adding to the feed tree as we go
+treeTop = new DefaultMutableTreeNode("My Feeds")
+parseOutline(opml.body.outline.outline, treeTop)
+
+def parseOutline(parsedXml, treeLevel) {
+    parsedXml.each { outline ->
+        if (outline['@xmlUrl'] != null) {  // this is an individual feed
+            feed = new Feed(name: outline['@title'], id: outline['@BloglinesSubId'],
+                    unread: outline['@BloglinesUnread'])
+            treeLevel.add(new DefaultMutableTreeNode(feed))
+        } else {  // this is a folder of feeds
+            folder = new DefaultMutableTreeNode(outline['@title'])
+            parseOutline(outline.outline, folder)
+            treeLevel.add(folder)
+        }
+    }
+}
+
+//Build the base user interface objects and configure them
+swing = new SwingBuilder()
+feedTree = new JTree(treeTop)
+itemList = swing.list()
+itemText = swing.textPane(contentType: 'text/html', editable: false)
+model = feedTree.selectionModel
+model.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
+itemList.selectionMode = ListSelectionModel.SINGLE_SELECTION
+
+//Set up the action closures that will react to user selections
+listItems = { feed ->
+    rssStream = callBloglinesGetItems("?s=${feed.id}&n=0")
+    if (rssStream != null) {
+        try {
+            rss = new XmlSlurper().parse(rssStream)
+            itemList.listData = rss.channel.item.collect(new Vector()) {
+                new Item(title: it.title, description: it.description)
+            }
+            feed.unread = "0"  // update the unread item count in the feed list
+        } catch (Exception e) {
+            println "Error during <${feed.name}> RSS parse: ${e}"
+        }
+    }
+}
+
+feedTree.valueChanged = { event ->
+    itemText.text = ""  // clear any old item text
+    node = (DefaultMutableTreeNode) feedTree.getLastSelectedPathComponent()
+    if (node != null) {
+        feed = node.userObject
+        if (feed instanceof Feed && feed.unread != "0") {
+            listItems(feed)
+        }
+    }
+}
+
+itemList.valueChanged = { event ->
+    item = event.source.selectedValue
+    if (item instanceof Item && item?.description != null) {
+        itemText.text = "<html><body>${item.description}</body></html>"
+    }
+}
+
+//Put the user interface together and display it
+gui = swing.frame(title: 'Bloglines Client', location: [100, 100], size: [800, 600],
+        defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE) {
+    panel(layout: new BorderLayout()) {
+        splitPane(orientation: JSplitPane.HORIZONTAL_SPLIT, dividerLocation: 200) {
+            scrollPane {
+                widget(feedTree)
+            }
+            splitPane(orientation: JSplitPane.VERTICAL_SPLIT, dividerLocation: 150) {
+                scrollPane(constraints: BorderLayout.CENTER) {
+                    widget(itemList)
+                }
+                scrollPane(constraints: BorderLayout.CENTER) {
+                    widget(itemText)
+                }
+            }
+        }
+    }
+}
+
+gui.show()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/ModelNodeExample.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/ModelNodeExample.groovy b/src/main/groovy/swing/ModelNodeExample.groovy
new file mode 100644
index 0000000..da52139
--- /dev/null
+++ b/src/main/groovy/swing/ModelNodeExample.groovy
@@ -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 swing
+
+import groovy.swing.SwingBuilder
+import static javax.swing.WindowConstants.*
+import static java.awt.GridBagConstraints.*
+
+def bean = new ObservableMap([name:'Alice', phone:'719-555-1212', addr:'42 Other Way'])
+
+SwingBuilder.build {
+ frame = frame(
+       pack:true, 
+       show:true,
+       defaultCloseOperation:DISPOSE_ON_CLOSE)
+ {
+  beanModel = model(bean, bind:false)
+
+  gridBagLayout()
+
+  label('Name:', constraints:gbc(insets:[6,6,3,3]))
+  textField(text:beanModel.name,
+            columns:20,
+            gridwidth:REMAINDER,
+            fill:HORIZONTAL,
+            weightx:1,
+            insets:[6,3,3,6])
+
+  label('Phone:', constraints:gbc(insets:[3,6,3,3]))
+  textField(text:beanModel.phone,
+            columns:20,
+            gridwidth:REMAINDER,
+            fill:HORIZONTAL,
+            weightx:1,
+            insets:[3,3,3,6])
+
+  label('Address:', constraints:gbc(insets:[3,6,3,3]))
+  textField(text:beanModel.addr,
+            columns:20,
+            gridwidth:REMAINDER,
+            fill:HORIZONTAL,
+            weightx:1,
+            insets:[3,3,3,6])
+
+  button('Reset', actionPerformed:{beanModel.update()}, 
+                  constraints:gbc(gridwidth:2, 
+                                  anchor:EAST, 
+                                  weightx:1, 
+                                  insets:[9,0,0,6]))
+  button('Submit', 
+         insets:[9,0,0,0],
+         actionPerformed: { 
+             beanModel.reverseUpdate()
+             output.text = ("name = '$bean.name'\nphone = '$bean.phone'\naddr = '$bean.addr'\n\n")
+         })
+
+  separator(gridwidth:REMAINDER,
+            fill:HORIZONTAL,
+            insets:[3,6,3,6])
+  label('Output:',
+        gridwidth:REMAINDER,
+        anchor:WEST,
+        insets:[3,6,3,6])
+  scrollPane(preferredSize:[100, 100], 
+             gridwidth:REMAINDER,
+             fill:BOTH,
+             weighty:1,
+             insets:[3,6,6,6])
+  {
+   output = textArea()
+  }
+ }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/RegexCoach.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/RegexCoach.groovy b/src/main/groovy/swing/RegexCoach.groovy
new file mode 100644
index 0000000..e282116
--- /dev/null
+++ b/src/main/groovy/swing/RegexCoach.groovy
@@ -0,0 +1,126 @@
+/*
+ *  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.
+ */
+// inspired by http://weitz.de/regex-coach/
+package swing
+
+import java.awt.*
+import java.awt.event.*
+import java.util.regex.*
+import javax.swing.*
+import javax.swing.text.DefaultHighlighter
+import groovy.swing.SwingBuilder
+
+// define the view
+def swing = new SwingBuilder()
+def gui = swing.frame(title: 'The Groovy Regex Coach', location: [20, 40], size: [600, 500], defaultCloseOperation: WindowConstants.EXIT_ON_CLOSE) {
+    panel(layout: new BorderLayout()) {
+        splitPane(orientation: JSplitPane.VERTICAL_SPLIT, dividerLocation: 150) {
+            panel(layout: new BorderLayout()) {
+                label(constraints: BorderLayout.NORTH, text: 'Regular expression:')
+                scrollPane(constraints: BorderLayout.CENTER) {textPane(id: 'regexPane')}
+                label(constraints: BorderLayout.SOUTH, id: 'regexStatus', text: ' ')
+            }
+            panel(layout: new BorderLayout()) {
+                label(constraints: BorderLayout.NORTH, text: 'Target string:')
+                scrollPane(constraints: BorderLayout.CENTER) {textPane(id: 'targetPane')}
+                panel(constraints: BorderLayout.SOUTH, layout: new BorderLayout()) {
+                    label(constraints: BorderLayout.NORTH, id: 'targetStatus', text: ' ')
+                    panel(constraints: BorderLayout.SOUTH, layout: new FlowLayout()) {
+                        button('<<-', id: 'scanLeft')
+                        button('->>', id: 'scanRight')
+                    }
+                }
+            }
+        }
+    }
+}
+def highlighter = new RegexHighlighter(swing: swing)
+swing.regexPane.addKeyListener(highlighter)
+swing.targetPane.addKeyListener(highlighter)
+swing.scanLeft.addActionListener(highlighter)
+swing.scanRight.addActionListener(highlighter)
+gui.show()
+
+class RegexHighlighter extends KeyAdapter implements ActionListener {
+    def swing // reference to the view
+    int scanIndex // how many times to execute matcher.find()
+    def orange = new DefaultHighlighter.DefaultHighlightPainter(Color.ORANGE)
+    def yellow = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW)
+    def red = new DefaultHighlighter.DefaultHighlightPainter(Color.RED)
+
+    // react to user actions
+
+    public void actionPerformed(ActionEvent event) {
+        if (event.actionCommand == '<<-') {scanIndex = Math.max(scanIndex - 1, 0)}
+        if (event.actionCommand == '->>') {scanIndex++}
+        doHighlights()
+    }
+
+    public void keyReleased(KeyEvent event) {
+        scanIndex = 0
+        doHighlights()
+    }
+
+    private resetView() {
+        swing.regexPane.highlighter.removeAllHighlights()
+        swing.targetPane.highlighter.removeAllHighlights()
+        swing.regexStatus.text = ' '
+        swing.targetStatus.text = ' '
+    }
+
+    // the main regex logic
+
+    private doHighlights() {
+        try {
+            resetView()
+            // note: get the text from the underlying document,
+            // otherwise carriage return/line feeds different when using the JTextPane text
+            def regex = swing.regexPane.document.getText(0, swing.regexPane.document.length)
+            def target = swing.targetPane.document.getText(0, swing.targetPane.document.length)
+
+            def matcher = (target =~ regex)
+
+            // scan past the matches before the match we want
+            int scan = 0
+            while (scan < scanIndex) {
+                matcher.find()
+                scan++
+            }
+            if (matcher.find()) {
+                // highlight any captured groups
+                int i = 0
+                while (i++ < matcher.groupCount()) {
+                    swing.targetPane.highlighter.addHighlight(matcher.start(i), matcher.end(i), orange)
+                }
+                // highlight whole match
+                swing.targetPane.highlighter.addHighlight(matcher.start(), matcher.end(), yellow)
+                if (regex.length() != 0) {
+                    swing.targetStatus.text = "Match #${scanIndex + 1} from ${matcher.start()} to ${matcher.end()}."
+                }
+            } else { // not found
+                scanIndex = Math.max(scan - 1, 0)
+                if (scanIndex > 0) {doHighlights()}
+                swing.targetStatus.text = "No match."
+            }
+        } catch (PatternSyntaxException e) {
+            swing.regexPane.highlighter.addHighlight(e.index, e.index + 2, red)
+            swing.regexStatus.text = e.description
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/RegexCoachController.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/RegexCoachController.groovy b/src/main/groovy/swing/RegexCoachController.groovy
new file mode 100644
index 0000000..bf6cd9d
--- /dev/null
+++ b/src/main/groovy/swing/RegexCoachController.groovy
@@ -0,0 +1,107 @@
+/*
+ *  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 swing
+
+import groovy.swing.SwingBuilder
+import java.awt.Color
+import java.awt.event.ActionEvent
+import java.awt.event.ActionListener
+import java.awt.event.KeyAdapter
+import java.awt.event.KeyEvent
+import java.util.regex.PatternSyntaxException
+import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter
+
+// inspired by http://weitz.de/regex-coach/
+
+// define the view
+def swing = new SwingBuilder()
+
+def gui = swing.build(RegexCoachView)
+
+def highlighter = new RegexHighliter(swing: swing)
+swing.regexPane.addKeyListener(highlighter)
+swing.targetPane.addKeyListener(highlighter)
+swing.scanLeft.addActionListener(highlighter)
+swing.scanRight.addActionListener(highlighter)
+gui.show()
+
+class RegexHighliter extends KeyAdapter implements ActionListener {
+    def swing // reference to the view
+    int scanIndex // how many times to execute matcher.find()
+    def orange = new DefaultHighlightPainter(Color.ORANGE)
+    def yellow = new DefaultHighlightPainter(Color.YELLOW)
+    def red = new DefaultHighlightPainter(Color.RED)
+
+    // react to user actions
+    public void actionPerformed(ActionEvent event) {
+        if (event.actionCommand == '<<-') {scanIndex = Math.max(scanIndex - 1, 0)}
+        if (event.actionCommand == '->>') {scanIndex++}
+        doHighlights()
+    }
+    public void keyReleased(KeyEvent event) {
+        scanIndex = 0
+        doHighlights()
+    }
+
+    private resetView() {
+        swing.regexPane.highlighter.removeAllHighlights()
+        swing.targetPane.highlighter.removeAllHighlights()
+        swing.regexStatus.text = ' '
+        swing.targetStatus.text = ' '
+    }
+
+    // the main regex logic
+    private doHighlights() {
+        try {
+            resetView()
+            // note: get the text from the underlying document,
+            // otherwise carriage return/line feeds different when using the JTextPane text
+            def regex = swing.regexPane.document.getText(0, swing.regexPane.document.length)
+            def target = swing.targetPane.document.getText(0, swing.targetPane.document.length)
+
+            def matcher = (target =~ regex)
+
+            // scan past the matches before the match we want
+            int scan = 0
+            while (scan < scanIndex) {
+                matcher.find()
+                scan++
+            }
+            if (matcher.find()) {
+                // highlight any captured groups
+                int i = 0
+                while (i++ < matcher.groupCount()) {
+                    swing.targetPane.highlighter.addHighlight(matcher.start(i), matcher.end(i), orange)
+                }
+                // highlight whole match
+                swing.targetPane.highlighter.addHighlight(matcher.start(), matcher.end(), yellow)
+                if (regex.length() != 0) {
+                    swing.targetStatus.text = "Match #${scanIndex + 1} from ${matcher.start()} to ${matcher.end()}."
+                }
+            } else {// not found
+                scanIndex = Math.max(scan - 1, 0)
+                if (scanIndex > 0) {doHighlights()}
+                swing.targetStatus.text = "No match."
+            }
+        } catch (PatternSyntaxException e) {
+            swing.regexPane.highlighter.addHighlight(e.index, e.index + 2, red)
+            swing.regexStatus.text = e.description
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/RegexCoachView.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/RegexCoachView.groovy b/src/main/groovy/swing/RegexCoachView.groovy
new file mode 100644
index 0000000..37868ed
--- /dev/null
+++ b/src/main/groovy/swing/RegexCoachView.groovy
@@ -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 swing
+
+import static java.awt.BorderLayout.*
+import static javax.swing.JSplitPane.VERTICAL_SPLIT
+import static javax.swing.WindowConstants.EXIT_ON_CLOSE
+
+frame(title: 'The Groovy Regex Coach', location: [20, 40], size: [600, 500], defaultCloseOperation: EXIT_ON_CLOSE) {
+    panel {
+        borderLayout()
+        splitPane(orientation: VERTICAL_SPLIT, dividerLocation: 150) {
+            panel {
+                borderLayout()
+                label(constraints: NORTH, text: 'Regular expression:')
+                scrollPane(constraints: CENTER) {
+                    textPane(id: 'regexPane')
+                }
+                label(constraints: SOUTH, id: 'regexStatus', text: ' ')
+            }
+            panel {
+                borderLayout()
+                label(constraints: NORTH, text: 'Target string:')
+                scrollPane(constraints: CENTER) {
+                    textPane(id: 'targetPane')
+                }
+                panel(constraints: SOUTH) {
+                    borderLayout()
+                    label(constraints: NORTH, id: 'targetStatus', text: ' ')
+                    panel(constraints: SOUTH) {
+                        flowLayout()
+                        button('<<-', id: 'scanLeft')
+                        button('->>', id: 'scanRight')
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file