http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/Widgets.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/Widgets.groovy 
b/src/main/groovy/swing/Widgets.groovy
new file mode 100644
index 0000000..14266cf
--- /dev/null
+++ b/src/main/groovy/swing/Widgets.groovy
@@ -0,0 +1,220 @@
+/*
+ *  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 java.awt.Color
+import javax.swing.SwingConstants
+import javax.swing.WindowConstants
+import groovy.swing.SwingBuilder
+
+class Widgets {
+
+    def swing = new SwingBuilder()
+    def unownedDialog
+    def ownedDialog
+    
+
+    static void main(args) {
+        def demo = new Widgets()
+        demo.run()
+    }
+
+    def showUnownedDialog(event) {
+        unownedDialog.show();
+    }
+    
+    def showOwnedDialog(event) {
+        ownedDialog.show();
+    }
+
+    void run() {
+        unownedDialog = swing.dialog(
+            title:'unrooted dialog',
+            location: [200, 200],
+            pack:true,
+            defaultCloseOperation:WindowConstants.DISPOSE_ON_CLOSE
+            ) {
+                label("I am unowned, but not unwanted");
+            }
+
+        def frame = swing.frame(
+            title:'FrameTitle',
+            location:[100,100],
+            size:[800,400],
+            defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
+
+            menuBar {
+                menu(text:'File') {
+                    menuItem() {
+                        action(name:'New', closure:{ println("clicked on the 
new menu item!") })
+                    }
+                    menuItem() {
+                        action(name:'Open', closure:{ println("clicked on the 
open menu item!") })
+                    }
+                    separator()
+                    menuItem() {
+                        action(name:'Save', enabled:false, closure:{ 
println("clicked on the Save menu item!") })
+                    }
+                }
+                menu(text:'Dialogs') {
+                    menuItem() {
+                        action(name:'Owned Dialog', closure: 
this.&showOwnedDialog)
+                    }
+                    menuItem() {
+                        action(name:'Unowned Dialog', closure: 
this.&showUnownedDialog)
+                    }
+                    def deeplyOwnedDialog = swing.dialog(
+                        title:'rooted dialog #2',
+                        location: [200, 200],
+                        pack:true,
+                        defaultCloseOperation:WindowConstants.DISPOSE_ON_CLOSE
+                        ) {
+                        label("ownership is deep");
+                    }
+                    menuItem() {
+                        action(name:'Deeply Owned Dialog', closure: 
{deeplyOwnedDialog.show()} )
+                    }
+                }
+            }
+
+            tabbedPane() {
+
+                //colorChooser(
+                //    name:"Color Chooser",
+                //    color: 0xfeed42)
+
+                panel(name:"Formatted Text Fields") {
+                    gridLayout(columns: 2, rows: 0)
+                    label("Simple Constructor:")
+                    formattedTextField()
+                    label("Date Value")
+                    formattedTextField(value: new java.util.Date())
+                    label("Integer Value")
+                    formattedTextField(value: new java.lang.Integer(42))
+                    label("Date Format")
+                    formattedTextField(format: 
java.text.DateFormat.getDateInstance())
+                    label("Currency Format ")
+                    formattedTextField(format: new 
java.text.DecimalFormat("¤###.00;(¤###.00)"))
+                }
+
+                panel(name:"Sliders") {
+                    flowLayout()
+                    slider(minimum:-100, 
+                        maximum:100, 
+                        majorTickSpacing: 50,
+                        orientation: SwingConstants.VERTICAL, 
+                        paintLabels:true)
+                    slider(minimum:-100, 
+                        maximum:100, 
+                        orientation: SwingConstants.VERTICAL, 
+                        paintLabels:true,
+                        paintTicks:true,
+                        majorTickSpacing: 50,
+                        minorTickSpacing: 10,
+                        snapToTicks:true,
+                        paintTrack:true)
+                }
+
+                panel(name:"Spinners") {
+                    gridBagLayout()
+                    label(
+                        text:"Tempuature in London:",
+                        insets:[12, 12, 2, 2],
+                        anchor: EAST,
+                        gridx: 0)
+                    spinner(
+                        model:spinnerNumberModel(minimum:-10, 
+                            maximum: 40, 
+                            value:20,
+                            stepSize:5),
+                        insets:[12, 3, 2, 12],
+                        anchor: WEST,
+                        gridx: 1,
+                        fill: HORIZONTAL)
+                    label(
+                        text:"Baseball Leagues:",
+                        insets:[3, 12, 2, 2],
+                        anchor: EAST,
+                        gridx: 0)
+                    spinner(
+                        model:spinnerListModel(
+                            list: ["Major League", "AAA", "AA", "A", "Rookie", 
"Semi-Pro", "Rec A", "Rec B"],
+                            value: "AA"),
+                        insets:[3, 3, 2, 12],
+                        anchor: WEST,
+                        gridx: 1,
+                        fill: HORIZONTAL)
+                    label(
+                        text:"Today's Date:",
+                        insets:[3, 12, 2, 2],
+                        anchor: EAST,
+                        gridx: 0)
+                    spinner(
+                        model:spinnerDateModel(calendarField: 
Calendar.HOUR_OF_DAY),
+                        insets:[3, 3, 2, 12],
+                        anchor: WEST,
+                        gridx: 1,
+                        fill: HORIZONTAL)
+                }
+
+                panel(name:"Border Layout") {
+                    borderLayout()
+                    label(text:"Border Layout", 
+                          constraints:NORTH,
+                          horizontalAlignment:SwingConstants.CENTER)
+                    label(text:"South", 
+                          constraints:SOUTH,
+                          background:Color.YELLOW,
+                          opaque:true,
+                          horizontalAlignment:SwingConstants.CENTER,
+                          toolTipText:"Tooltip on south")
+                    label(text:"West", 
+                          constraints:WEST,
+                          background:Color.ORANGE,
+                          opaque:true,
+                          horizontalAlignment:SwingConstants.CENTER,
+                          toolTipText:"Tooltip on west")
+                    label(text:"East", 
+                          constraints:EAST,
+                          background:Color.GREEN,
+                          opaque:true,
+                          horizontalAlignment:SwingConstants.CENTER,
+                          toolTipText:"Tooltip on east")
+                    label(text:"Center", 
+                          constraints:CENTER,
+                          background:Color.WHITE,
+                          opaque:true,
+                          horizontalAlignment:SwingConstants.CENTER,
+                          toolTipText:"<html>This is not the tooltip you are 
looking for.<br><i>*waves hand*</i>")
+                }
+            }
+
+            ownedDialog = swing.dialog(
+                title:'rooted dialog',
+                location: [200, 200],
+                pack:true,
+                defaultCloseOperation:WindowConstants.DISPOSE_ON_CLOSE
+                ) {
+                label("j00 h4v3 b33n 0wn3xed");
+            }
+        }        
+        frame.show()
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/Caricature.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/Caricature.groovy 
b/src/main/groovy/swing/binding/caricature/Caricature.groovy
new file mode 100644
index 0000000..a37519c
--- /dev/null
+++ b/src/main/groovy/swing/binding/caricature/Caricature.groovy
@@ -0,0 +1,79 @@
+/*
+ *  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.binding.caricature
+
+import groovy.swing.SwingBuilder
+import java.awt.Color
+import javax.swing.border.TitledBorder
+
+SwingBuilder.build {
+    frame(pack:true, show:true,
+        defaultCloseOperation:javax.swing.JFrame.DISPOSE_ON_CLOSE)
+    {
+        borderLayout()
+        panel(constraints:CENTER,) {
+            // place in panel to insure square layout
+            caricature = widget(new JCaricature(
+                background:Color.WHITE, opaque:true))
+        }
+        hbox(constraints:SOUTH) {
+            panel(border:new TitledBorder("Style")) {
+                gridLayout(new java.awt.GridLayout(5, 2))
+                label("Eyes") // font stuff
+                eyeSlider = slider(minimum:0, maximum:4,
+                    value:bind(target:caricature,
+                        targetProperty:'eyeStyle',
+                        value:2))
+                label("Face") // font stuff
+                faceSlider = slider(minimum:0, maximum:4,
+                    value:bind(target:caricature,
+                        targetProperty:'faceStyle',
+                        value:2))
+                label("Mouth") // font stuff
+                mouthSlider = slider(minimum:0, maximum:4,
+                    value:bind(target:caricature,
+                        targetProperty:'mouthStyle',
+                        value:2))
+                label("Hair") // font stuff
+                hairSlider = slider(minimum:0, maximum:4,
+                    value:bind(target:caricature,
+                        targetProperty:'hairStyle',
+                        value:2))
+                label("Nose") // font stuff
+                noseSlider = slider(minimum:0, maximum:4,
+                    value:bind(target:caricature,
+                        targetProperty:'noseStyle',
+                        value:2))
+            }
+            panel(border:new TitledBorder("Effects")) {
+                gridLayout(new java.awt.GridLayout(2, 5))
+                label("Rotation") // font stuff
+                rotationSlider = slider(maximum:360,
+                    value:bind(target:caricature,
+                        targetProperty:'rotation',
+                        value:0))
+                label("Scale") // font stuff
+                scaleSlider = slider(maximum: 150, minimum:50,
+                    value:bind(target:caricature,
+                        targetProperty:'scale',
+                        converter: {it / 100f}, value:100))
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/JCaricature.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/JCaricature.java 
b/src/main/groovy/swing/binding/caricature/JCaricature.java
new file mode 100644
index 0000000..0041862
--- /dev/null
+++ b/src/main/groovy/swing/binding/caricature/JCaricature.java
@@ -0,0 +1,220 @@
+/*
+ *  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.
+ */
+/*
+ * Caricature.java
+ *
+ * Created on April 8, 2006, 4:09 PM
+ *
+ * To change this template, choose Tools | Template Manager
+ * and open the template in the editor.
+ */
+
+package swing.binding.caricature;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+
+/**
+ *
+ * @author sky
+ */
+public class JCaricature extends JPanel {
+    private Map/*<String,Image>*/ imageMap;
+
+    private boolean empty;
+    private int mouthStyle;
+    private int faceStyle;
+    private int hairStyle;
+    private int eyeStyle;
+    private int noseStyle;
+    private int rotation;
+    private float scale = 1.0f;
+
+    public JCaricature() {
+        if (imageMap == null) {
+            imageMap = new HashMap/*<String,Image>*/(1);
+            for (int i = 0; i < 5; i++) {
+                getImage("face", i);
+                getImage("hair", i);
+                getImage("eyes", i);
+                getImage("nose", i);
+                getImage("mouth", i);
+            }
+        }
+    }
+
+    public void setEmpty(boolean empty) {
+        if (this.empty != empty) {
+            this.empty = empty;
+            firePropertyChange("empty", !empty, empty);
+            repaint();
+        }
+    }
+
+    public boolean isEmpty() {
+        return empty;
+    }
+
+    public void setRotation(int rotation) {
+        int oldRotation = this.rotation;
+        this.rotation = rotation;
+        repaint();
+        firePropertyChange("rotation", oldRotation, rotation);
+    }
+
+    public int getRotation() {
+        return rotation;
+    }
+
+    public void setScale(float scale) {
+        float oldScale = this.scale;
+        this.scale = scale;
+        repaint();
+        firePropertyChange("scale", oldScale, scale);
+    }
+
+    public float getScale() {
+        return scale;
+    }
+
+    public void setMouthStyle(int style) {
+        int oldStyle = mouthStyle;
+        mouthStyle = style;
+        firePropertyChange("mouthStyle", oldStyle, style);
+        repaint();
+    }
+
+    public int getMouthStyle() {
+        return mouthStyle;
+    }
+
+    public void setFaceStyle(int style) {
+        int oldStyle = faceStyle;
+        faceStyle = style;
+        firePropertyChange("faceStyle", oldStyle, style);
+        repaint();
+    }
+
+    public int getFaceStyle() {
+        return faceStyle;
+    }
+
+    public void setHairStyle(int style) {
+        int oldStyle = hairStyle;
+        hairStyle = style;
+        firePropertyChange("hairStyle", oldStyle, style);
+        repaint();
+    }
+
+    public int getHairStyle() {
+        return hairStyle;
+    }
+
+    public void setEyeStyle(int style) {
+        int oldStyle = eyeStyle;
+        eyeStyle = style;
+        firePropertyChange("eyeStyle", oldStyle, style);
+        repaint();
+    }
+
+    public int getEyeStyle() {
+        return eyeStyle;
+    }
+
+    public void setNoseStyle(int style) {
+        int oldStyle = noseStyle;
+        noseStyle = style;
+        firePropertyChange("noseStyle", oldStyle, style);
+        repaint();
+    }
+
+    public int getNoseStyle() {
+        return noseStyle;
+    }
+
+    public Dimension getPreferredSize() {
+        if (!isPreferredSizeSet()) {
+            Image image = getImage("mouth", 0);
+            return new Dimension(image.getWidth(null), image.getHeight(null));
+        }
+        return super.getPreferredSize();
+    }
+
+    public Dimension getMaximumSize() {
+        if (!isMaximumSizeSet()) {
+            return getPreferredSize();
+        }
+        return super.getMaximumSize();
+    }
+
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+        if (empty) {
+            return;
+        }
+        Graphics2D g2 = (Graphics2D)g.create();
+        Image image = getImage("face", getFaceStyle());
+        int iw = image.getWidth(null);
+        int ih = image.getHeight(null);
+//        g2.translate(iw / 2, ih / 2);
+        g2.translate(getWidth() / 2, getHeight() / 2);
+        if (iw != getWidth()) {
+            float forcedScale = (float)getWidth() / (float)iw;
+            g2.scale(forcedScale, forcedScale);
+        }
+        float scale = getScale();
+        if (scale != 1) {
+            g2.scale((double)scale, (double)scale);
+        }
+        int rotation = getRotation();
+        if (rotation != 0) {
+            g2.rotate(Math.toRadians(rotation));
+        }
+        drawImage(g2, "face", getFaceStyle());
+        drawImage(g2, "hair", getHairStyle());
+        drawImage(g2, "eyes", getEyeStyle());
+        drawImage(g2, "nose", getNoseStyle());
+        drawImage(g2, "mouth", getMouthStyle());
+        g2.dispose();
+    }
+
+    private void drawImage(Graphics g, String string, int i) {
+        Image image = getImage(string, i);
+        g.drawImage(image, -image.getWidth(null) / 2, -image.getHeight(null) / 
2, null);
+    }
+
+    private Image getImage(String key, int style) {
+        String imageName = key + (style + 1) + ".gif";
+        Image image = (Image) imageMap.get(imageName);
+        if (image == null) {
+            System.err.println("name=" + imageName);
+            URL imageLoc = getClass().getResource("resources/" + imageName);
+            image = new ImageIcon(imageLoc).getImage();
+            imageMap.put(imageName, image);
+        }
+        return image;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/eyes1.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/eyes1.gif 
b/src/main/groovy/swing/binding/caricature/resources/eyes1.gif
new file mode 100644
index 0000000..54d0eb0
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/eyes1.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/eyes2.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/eyes2.gif 
b/src/main/groovy/swing/binding/caricature/resources/eyes2.gif
new file mode 100644
index 0000000..4aa3091
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/eyes2.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/eyes3.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/eyes3.gif 
b/src/main/groovy/swing/binding/caricature/resources/eyes3.gif
new file mode 100644
index 0000000..bfbbdea
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/eyes3.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/eyes4.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/eyes4.gif 
b/src/main/groovy/swing/binding/caricature/resources/eyes4.gif
new file mode 100644
index 0000000..e25f652
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/eyes4.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/eyes5.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/eyes5.gif 
b/src/main/groovy/swing/binding/caricature/resources/eyes5.gif
new file mode 100644
index 0000000..e5b6d6b
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/eyes5.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/face1.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/face1.gif 
b/src/main/groovy/swing/binding/caricature/resources/face1.gif
new file mode 100644
index 0000000..01a107f
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/face1.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/face2.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/face2.gif 
b/src/main/groovy/swing/binding/caricature/resources/face2.gif
new file mode 100644
index 0000000..0a8ea44
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/face2.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/face3.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/face3.gif 
b/src/main/groovy/swing/binding/caricature/resources/face3.gif
new file mode 100644
index 0000000..e8c467b
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/face3.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/face4.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/face4.gif 
b/src/main/groovy/swing/binding/caricature/resources/face4.gif
new file mode 100644
index 0000000..9364ab8
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/face4.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/face5.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/face5.gif 
b/src/main/groovy/swing/binding/caricature/resources/face5.gif
new file mode 100644
index 0000000..e5f5e7c
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/face5.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/glasses.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/glasses.gif 
b/src/main/groovy/swing/binding/caricature/resources/glasses.gif
new file mode 100644
index 0000000..ef46483
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/glasses.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/glassesWEyes.gif
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/swing/binding/caricature/resources/glassesWEyes.gif 
b/src/main/groovy/swing/binding/caricature/resources/glassesWEyes.gif
new file mode 100644
index 0000000..93585b0
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/glassesWEyes.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/hair1.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/hair1.gif 
b/src/main/groovy/swing/binding/caricature/resources/hair1.gif
new file mode 100644
index 0000000..0466d00
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/hair1.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/hair2.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/hair2.gif 
b/src/main/groovy/swing/binding/caricature/resources/hair2.gif
new file mode 100644
index 0000000..47adf81
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/hair2.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/hair3.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/hair3.gif 
b/src/main/groovy/swing/binding/caricature/resources/hair3.gif
new file mode 100644
index 0000000..d93bd45
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/hair3.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/hair4.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/hair4.gif 
b/src/main/groovy/swing/binding/caricature/resources/hair4.gif
new file mode 100644
index 0000000..eca9d4b
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/hair4.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/hair5.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/hair5.gif 
b/src/main/groovy/swing/binding/caricature/resources/hair5.gif
new file mode 100644
index 0000000..147057a
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/hair5.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/mouth1.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/mouth1.gif 
b/src/main/groovy/swing/binding/caricature/resources/mouth1.gif
new file mode 100644
index 0000000..a92e1d3
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/mouth1.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/mouth2.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/mouth2.gif 
b/src/main/groovy/swing/binding/caricature/resources/mouth2.gif
new file mode 100644
index 0000000..1486022
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/mouth2.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/mouth3.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/mouth3.gif 
b/src/main/groovy/swing/binding/caricature/resources/mouth3.gif
new file mode 100644
index 0000000..b6f5348
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/mouth3.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/mouth4.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/mouth4.gif 
b/src/main/groovy/swing/binding/caricature/resources/mouth4.gif
new file mode 100644
index 0000000..937b46d
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/mouth4.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/mouth5.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/mouth5.gif 
b/src/main/groovy/swing/binding/caricature/resources/mouth5.gif
new file mode 100644
index 0000000..f61a7a1
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/mouth5.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/nose1.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/nose1.gif 
b/src/main/groovy/swing/binding/caricature/resources/nose1.gif
new file mode 100644
index 0000000..336ea9f
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/nose1.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/nose2.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/nose2.gif 
b/src/main/groovy/swing/binding/caricature/resources/nose2.gif
new file mode 100644
index 0000000..5ab8609
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/nose2.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/nose3.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/nose3.gif 
b/src/main/groovy/swing/binding/caricature/resources/nose3.gif
new file mode 100644
index 0000000..ea3c643
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/nose3.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/nose4.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/nose4.gif 
b/src/main/groovy/swing/binding/caricature/resources/nose4.gif
new file mode 100644
index 0000000..832628b
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/nose4.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/binding/caricature/resources/nose5.gif
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/binding/caricature/resources/nose5.gif 
b/src/main/groovy/swing/binding/caricature/resources/nose5.gif
new file mode 100644
index 0000000..ec51067
Binary files /dev/null and 
b/src/main/groovy/swing/binding/caricature/resources/nose5.gif differ

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/greet/Greet.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/greet/Greet.groovy 
b/src/main/groovy/swing/greet/Greet.groovy
new file mode 100644
index 0000000..ca93900
--- /dev/null
+++ b/src/main/groovy/swing/greet/Greet.groovy
@@ -0,0 +1,153 @@
+/*
+ *  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.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: Danno.Ferrin
+ * Date: Apr 26, 2008
+ * Time: 8:32:03 AM
+ */
+package groovy.swing.greet
+
+import groovy.beans.Bindable
+import groovy.swing.SwingBuilder
+import javax.swing.JOptionPane
+
+class Greet {
+
+    TwitterAPI api
+    Binding view
+
+    @Bindable boolean allowLogin = true
+    @Bindable boolean allowSelection = true
+    @Bindable boolean allowTweet = true
+    @Bindable def focusedUser = ""
+    @Bindable def friends  = []
+    @Bindable def tweets   = []
+    @Bindable def timeline = []
+    @Bindable def statuses = []
+
+    void startUp() {
+        setAllowSelection(false)
+        setAllowTweet(false)
+        view.greetFrame.show()
+        view.loginDialog.show()
+    }
+
+    void login(evt) {
+        setAllowLogin(false)
+        view.doOutside {
+            try {
+                if (api.login(view.twitterNameField.text, 
view.twitterPasswordField.password)) {
+                    setFriends(api.getFriends(api.authenticatedUser))
+                    friends.each {it.status.user = 
[screen_name:it.screen_name, profile_image_url:it.profile_image_url] }
+                    setStatuses(friends.collect {it.status})
+                    selectUser(api.authenticatedUser)
+                    view.greetFrame.show()
+                    view.loginDialog.dispose()
+                } else {
+                    JOptionPane.showMessageDialog(view.loginDialog, "Login 
failed")
+                }
+            } catch (Exception e) {
+                e.printStackTrace()
+            } finally {
+                view.edt {
+                    setAllowLogin(true)
+                    setAllowSelection(true)
+                    setAllowTweet(true)
+                }
+            }
+        }
+    }
+
+    void filterTweets(evt = null) {
+        setAllowSelection(false)
+        setAllowTweet(false)
+        view.doOutside {
+            try {
+                setStatuses(
+                    friends.collect {it.status}.findAll {it.text =~ 
view.searchField.text}
+                )
+                setTimeline(
+                    api.getFriendsTimeline(focusedUser).findAll {it.text =~ 
view.searchField.text}
+                )
+                setTweets(
+                    api.getTweets(focusedUser).findAll {it.text =~ 
view.searchField.text}
+                )
+            } catch (Exception e) {
+                e.printStackTrace()
+            } finally {
+                view.edt {
+                    setAllowSelection(true)
+                    setAllowTweet(true)
+                }
+            }
+        }
+    }
+
+    def userSelected(evt) {
+        view.doOutside {
+            selectUser(view.users.selectedItem)
+        }
+    }
+
+    def selectUser(user) {
+        setAllowSelection(false)
+        setAllowTweet(false)
+        try {
+            setFocusedUser(api.getUser(user.screen_name as String))
+            setTweets(api.getTweets(focusedUser).findAll {it.text =~ 
view.searchField.text})
+            setTimeline(api.getFriendsTimeline(focusedUser).findAll {it.text 
=~ view.searchField.text})
+        } finally {
+            view.edt {
+                setAllowSelection(true)
+                setAllowTweet(true)
+            }
+        }
+    }
+
+    def tweet(evt = null) {
+        setAllowTweet(false)
+        view.doOutside {
+            try {
+                api.tweet(view.tweetBox.text)
+                // true story: it froze w/o the EDT call here
+                view.edt {tweetBox.text = ""}
+                filterTweets()
+            } finally {
+                setAllowTweet(true)
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        def model = new TwitterAPI()
+        def controller = new Greet()
+        def view = new SwingBuilder()
+
+        controller.api = model
+        controller.view = view
+
+        view.controller = controller
+
+        view.build(View)
+        view.view = view
+
+        controller.startUp()
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/greet/TwitterAPI.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/greet/TwitterAPI.groovy 
b/src/main/groovy/swing/greet/TwitterAPI.groovy
new file mode 100644
index 0000000..76d13f4
--- /dev/null
+++ b/src/main/groovy/swing/greet/TwitterAPI.groovy
@@ -0,0 +1,162 @@
+/*
+ *  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.
+ */
+/**
+ * Created by IntelliJ IDEA.
+ * User: Danno.Ferrin
+ * Date: Apr 25, 2008
+ * Time: 9:47:20 PM
+ */
+package groovy.swing.greet
+
+import groovy.beans.Bindable
+
+class TwitterAPI {
+
+    @Bindable String status = "\u00a0"
+    def authenticatedUser
+    XmlSlurper slurper = new XmlSlurper()
+    def imageMap = [:]
+
+    def withStatus(status, c) {
+        setStatus(status)
+        try {
+            def o = c()
+            setStatus("\u00a0")
+            return o
+        } catch (Throwable t) {
+            setStatus("Error $status : ${t.message =~ '400'?'Rate Limit 
Reached':t}")
+            throw t
+        }
+    }
+
+
+    boolean login(def name, def password) {
+        withStatus("Logging in") {
+            Authenticator.setDefault(
+                [getPasswordAuthentication : {
+                    return new PasswordAuthentication(name, password) }
+                ] as Authenticator)
+            authenticatedUser = getUser(name)
+            return true
+        }
+    }
+
+    def getFriends() {
+        getFriends(authenticatedUser)
+    }
+
+    def getFriends(String user) {
+        return getFriends(getUser(user))
+    }
+
+    def getFriends(user) {
+        def friends = [user]
+        withStatus("Loading Friends") {
+            def page = 1
+            def list = slurper.parse(new 
URL("http://twitter.com/statuses/friends/${user.screen_name}.xml";).openStream())
+            while (list.length) {
+                list.user.collect(friends) {it}
+                page++
+                try {
+                  list = 
slurper.parse("http://twitter.com/statuses/friends/${user.screen_name}.xml&page=$page";)
+                } catch (Exception e) { break }
+            }
+        }
+        withStatus("Loading Friends Images") {
+            return friends.each {
+                loadImage(it.profile_image_url as String)
+            }
+        }
+    }
+
+    def getFriendsTimeline() {
+        getFriendsTimeline(user)
+    }
+
+    def getFriendsTimeline(String friend) {
+        getFriendsTimeline(getUser(friend))
+    }
+
+    def getFriendsTimeline(user) {
+        def timeline = []
+        withStatus("Loading Timeline") {
+            timeline =  slurper.parse(
+                    new 
URL("http://twitter.com/statuses/friends_timeline/${user.screen_name}.xml";).openStream()
+                ).status.collect{it}
+        }
+        withStatus("Loading Timeline Images") {
+            return timeline.each {
+                loadImage(it.user.profile_image_url as String)
+            }
+        }
+    }
+
+    def getTweets() {
+        return getTweets(user)
+    }
+
+    def getTweets(String friend) {
+        return getTweets(getUser(frield))
+    }
+
+    def getTweets(friend) {
+        def tweets = []
+        withStatus("Loading Tweets") {
+            tweets = slurper.parse(
+                    new 
URL("http://twitter.com/statuses/user_timeline/${friend.screen_name}.xml";).openStream()
+                ).status.collect{it}
+        }
+        withStatus("Loading Tweet Images") {
+            return tweets.each {
+                loadImage(it.user.profile_image_url as String)
+            }
+        }
+    }
+
+    def getUser(String screen_name) {
+        withStatus("Loading User $screen_name") {
+            if (screen_name.contains('@')) {
+                return slurper.parse(
+                        new 
URL("http://twitter.com/users/show.xml?email=${screen_name}";).openStream()
+                    )
+            } else {
+                return slurper.parse(
+                        new 
URL("http://twitter.com/users/show/${screen_name}.xml";).openStream()
+                    )
+            }
+        }
+    }
+
+    def tweet(message) {
+        withStatus("Tweeting") {
+            def urlConnection = new 
URL("http://twitter.com/statuses/update.xml";).openConnection()
+            urlConnection.doOutput = true
+            urlConnection.outputStream << "status=${URLEncoder.encode(message, 
'UTF-8')}"
+            return slurper.parse(urlConnection.inputStream)
+        }
+    }
+
+    // no need to read these, swing seems to cache these so the EDT won't stall
+    def loadImage(image) {
+        if (!imageMap[image]) {
+            Thread.start {imageMap[image] = new javax.swing.ImageIcon(new 
URL(image))}
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/greet/View.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/greet/View.groovy 
b/src/main/groovy/swing/greet/View.groovy
new file mode 100644
index 0000000..3f8597c
--- /dev/null
+++ b/src/main/groovy/swing/greet/View.groovy
@@ -0,0 +1,175 @@
+/*
+ *  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.
+ */
+/**
+ * Created by IntelliJ IDEA.
+ * User: Danno.Ferrin
+ * Date: Apr 26, 2008
+ * Time: 8:31:21 AM
+ */
+package groovy.swing.greet
+
+import java.awt.Cursor
+import java.beans.PropertyChangeListener
+import javax.swing.*
+
+lookAndFeel('nimbus', 'mac', ['metal', [boldFonts: false]])
+
+actions() {
+    loginAction = action(
+        name: 'Login',
+        enabled: bind(source: controller, sourceProperty: 'allowLogin'),
+        closure: controller.&login
+    )
+
+    filterTweets = action(
+        name: 'Filter',
+        enabled: bind(source: controller, sourceProperty: 'allowSelection'),
+        closure: controller.&filterTweets
+    )
+
+    userSelected = action(
+        name: 'Select User',
+        enabled: bind(source: controller, sourceProperty: 'allowSelection'),
+        closure: controller.&userSelected
+    )
+
+    tweetAction = action(
+        name: 'Update',
+        enabled: bind(source: controller, sourceProperty: 'allowTweet'),
+        closure: controller.&tweet
+    )
+}
+
+tweetLineFont = new java.awt.Font("Ariel", 0, 12)
+tweetLine = panel(border: emptyBorder(3), preferredSize:[250,84]) {
+    gridBagLayout()
+    tweetIcon = label(verticalTextPosition:SwingConstants.BOTTOM,
+        horizontalTextPosition:SwingConstants.CENTER,
+        //anchor: BASELINE, insets: [3, 3, 3, 3])
+        anchor: CENTER, insets: [3, 3, 3, 3])
+    tweetText = textArea(rows: 4, lineWrap: true, wrapStyleWord: true,
+        opaque: false, editable: false, font: tweetLineFont,
+        gridwidth: REMAINDER, weightx: 1.0, fill: BOTH, insets: [3, 3, 3, 3])
+}
+tweetRenderer = {list, tweet, index, isSelected, isFocused ->
+    if (tweet?.user as String) {
+        tweetIcon.icon = controller.api.imageMap[tweet.user.profile_image_url 
as String]
+        tweetIcon.text = tweet.user.screen_name
+        tweetText.text = tweet.text
+    } else if (tweet?.text as String) {
+        tweetIcon.icon = 
controller.api.imageMap[tweet.parent().profile_image_url as String]
+        tweetIcon.text = tweet.parent().screen_name
+        tweetText.text = tweet.text
+    } else {
+        tweetIcon.icon = null
+        tweetIcon.text = null
+        tweetText.text = null
+    }
+    tweetLine
+} as ListCellRenderer
+
+
+userCell = label(border: emptyBorder(3))
+userCellRenderer = {list, user, index, isSelected, isFocused ->
+    if (user) {
+        userCell.icon = controller.api.imageMap[user.profile_image_url as 
String]
+        userCell.text = 
"<html>$user.screen_name<br>$user.name<br>$user.location<br>"
+    } else {
+        userCell.icon = null
+        userCell.text = null
+    }
+    userCell
+} as ListCellRenderer
+
+greetFrame = frame(title: "Greet - A Groovy Twitter Client",
+    defaultCloseOperation: javax.swing.JFrame.DISPOSE_ON_CLOSE, size: [320, 
480],
+    locationByPlatform:true)
+{
+    panel(cursor: bind(source: controller, sourceProperty: 'allowSelection',
+        converter: {it ? null : 
Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)})
+    ) {
+
+        gridBagLayout()
+        users = comboBox(renderer: userCellRenderer, action: userSelected,
+            gridwidth: REMAINDER, insets: [6, 6, 3, 6], fill: HORIZONTAL)
+        label('Search:', insets: [3, 6, 3, 3])
+        searchField = textField(columns: 20, action: filterTweets,
+            insets: [3, 3, 3, 3], weightx: 1.0, fill: BOTH)
+        button(action: filterTweets,
+            gridwidth: REMAINDER, insets: [3, 3, 3, 6], fill:HORIZONTAL)
+        tabbedPane(gridwidth: REMAINDER, weighty: 1.0, fill: BOTH) {
+            scrollPane(title: 'Timeline') {
+                timelineList = list(visibleRowCount: 20, cellRenderer: 
tweetRenderer)
+            }
+            scrollPane(title: 'Tweets') {
+                tweetList = list(visibleRowCount: 20, cellRenderer: 
tweetRenderer)
+            }
+            scrollPane(title: 'Statuses') {
+                statusList = list(visibleRowCount: 20, cellRenderer: 
tweetRenderer)
+            }
+            // add data change listeners
+            [timeline:timelineList, tweets:tweetList, 
statuses:statusList].each {p, w ->
+                controller.addPropertyChangeListener(p,
+                    {evt -> w.listData = evt.newValue as Object[]} as 
PropertyChangeListener
+                )
+            }
+        }
+        separator(fill: HORIZONTAL, gridwidth: REMAINDER)
+        tweetBox = textField(action:tweetAction,
+            fill:BOTH, weightx:1.0, insets:[3,3,3,3], gridwidth:2)
+        tweetButton = button(tweetAction,
+            enabled:bind(source:tweetBox, sourceProperty:'text', 
converter:{it.length() < 140}),
+            gridwidth:REMAINDER, insets:[3,3,3,3])
+        separator(fill: HORIZONTAL, gridwidth: REMAINDER)
+        statusLine = label(text: bind(source: controller.api, sourceProperty: 
'status'),
+            gridwidth: REMAINDER, insets: [3, 6, 3, 6], anchor: WEST
+        )
+    }
+
+
+    loginDialog = dialog(
+        title: "Login to Greet", pack: true, resizable: false,
+        defaultCloseOperation: WindowConstants.DISPOSE_ON_CLOSE,
+        locationByPlatform:true)
+    {
+        panel(border: emptyBorder(3),
+            cursor: bind(source: controller, sourceProperty: 'allowLogin',
+                converter: {it ? null : 
Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)})
+        ) {
+            gridBagLayout()
+            label("Username:",
+                anchor: EAST, insets: [3, 3, 3, 3])
+            twitterNameField = textField(action:loginAction, columns: 20,
+                gridwidth: REMAINDER, insets: [3, 3, 3, 3])
+            label("Password:",
+                anchor: EAST, insets: [3, 3, 3, 3])
+            twitterPasswordField = passwordField(action:loginAction, columns: 
20,
+                gridwidth: REMAINDER, insets: [3, 3, 3, 3])
+            panel()
+            button(loginAction, defaultButton: true,
+                anchor: EAST, insets: [3, 3, 3, 3])
+        }
+    }
+}
+
+controller.addPropertyChangeListener("friends", {evt ->
+    view.edt { users.model = new DefaultComboBoxModel(evt.newValue as 
Object[]) }
+} as PropertyChangeListener)
+
+new Timer(120000, filterTweets).start()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/timelog/TimeLogMain.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/timelog/TimeLogMain.groovy 
b/src/main/groovy/swing/timelog/TimeLogMain.groovy
new file mode 100644
index 0000000..31acefa
--- /dev/null
+++ b/src/main/groovy/swing/timelog/TimeLogMain.groovy
@@ -0,0 +1,46 @@
+/*
+ *  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.swing.timelog
+
+import groovy.swing.SwingBuilder
+
+SwingBuilder swing = new SwingBuilder();
+swing.lookAndFeel('system')
+
+swing.model = new TimeLogModel()
+
+swing.actions() {
+    action(name:'Start', id:'startAction') {
+        stopButton.requestFocusInWindow()
+        doOutside {
+            model.startRecording(tfClient.text)
+        }
+    }
+    action(name:'Stop', id:'stopAction') {
+        doOutside {
+            model.stopRecording();
+            clientsTable.revalidate()
+        }
+    }
+}
+
+frame = swing.build(TimeLogView)
+frame.pack()
+frame.show()
+

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/timelog/TimeLogModel.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/timelog/TimeLogModel.groovy 
b/src/main/groovy/swing/timelog/TimeLogModel.groovy
new file mode 100644
index 0000000..bb93772
--- /dev/null
+++ b/src/main/groovy/swing/timelog/TimeLogModel.groovy
@@ -0,0 +1,62 @@
+/*
+ *  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.swing.timelog
+
+import groovy.beans.Bindable
+
+class TimeLogRow {
+    String client
+    long start
+    long stop
+
+    long getDuration() {
+        return stop - start
+    }                                          
+}
+
+class TimeLogModel {
+
+    String currentClient
+    long currentStart
+    List<TimeLogRow> entries = []
+
+    @Bindable boolean running
+    @Bindable long elapsedTime
+
+    public synchronized startRecording(String client) {
+        if (running) throw new RuntimeException("Currently Running")
+        currentClient = client
+        currentStart = System.currentTimeMillis()
+        setRunning(true)
+
+        while (running) {
+            setElapsedTime(System.currentTimeMillis() - currentStart)
+            this.wait(1000)
+        }
+    }
+
+    public synchronized stopRecording() {
+        if (!running) throw new RuntimeException("Not Running")
+        setRunning(false)
+        this.notifyAll()
+        entries.add(new TimeLogRow(client:currentClient, start:currentStart, 
stop:System.currentTimeMillis()))
+    }
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/swing/timelog/TimeLogView.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/swing/timelog/TimeLogView.groovy 
b/src/main/groovy/swing/timelog/TimeLogView.groovy
new file mode 100644
index 0000000..91f711d
--- /dev/null
+++ b/src/main/groovy/swing/timelog/TimeLogView.groovy
@@ -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.swing.timelog
+
+import java.text.SimpleDateFormat
+import javax.swing.JFrame
+import java.awt.Font
+import java.awt.Color
+
+SimpleDateFormat timeFormat = new SimpleDateFormat('HH:mm:ss')
+timeFormat.setTimeZone(TimeZone.getTimeZone('GMT'))
+SimpleDateFormat dateFormat = new SimpleDateFormat('dd MMM yyyy HH:mm:ss')
+def convertTime = {it -> timeFormat.format new Date(it) }
+def convertDate = {it -> dateFormat.format new Date(it) }
+
+frame(title: 'Time Log Demo',
+    defaultCloseOperation : JFrame.EXIT_ON_CLOSE)
+{
+    gridBagLayout()
+
+    label('Client:', insets:[6,6,3,3])
+    textField('', id: 'tfClient',
+        enabled: bind( source: model, sourceProperty: 'running', converter: 
{!it} ),
+        gridwidth: REMAINDER, fill: HORIZONTAL, insets: [6,3,3,6])
+
+    label('00:00:00', font: new Font('Ariel', Font.BOLD, 42),
+        foreground: bind(source: model, sourceProperty: 'running', converter: 
{it ? Color.GREEN : Color.RED}),
+        text: bind(source: model, sourceProperty: 'elapsedTime', converter: 
convertTime),
+        gridwidth: 2, gridheight: 2, anchor: EAST, weightx: 1.0, insets: 
[3,6,3,3])
+    button(startAction, id: 'startButton',
+        enabled: bind(source: model, sourceProperty: 'running', converter: 
{!it}),
+        gridwidth: REMAINDER, insets: [3,3,3,6])
+    button(stopAction, id: 'stopButton',
+        enabled: bind(source: model, sourceProperty: 'running'),
+        gridwidth: REMAINDER, insets: [3,3,3,6])
+
+    separator(gridwidth: REMAINDER, fill: HORIZONTAL, insets: [9,6,9,6])
+
+    scrollPane(minimumSize: [100, 100],
+        gridwidth: REMAINDER, weighty: 1.0, fill: BOTH, insets: [3,6,6,6])
+    {
+        table(id: 'clientsTable') {
+            tableModel(list: model.entries) {
+                propertyColumn(header: 'Client',   propertyName: 'client')
+                closureColumn( header: 'Start',    read: {convertDate 
it.start})
+                closureColumn( header: 'Stop',     read: {convertDate it.stop})
+                closureColumn( header: 'Duration', read: {convertTime it.stop 
- it.start})
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/CompiledAtASTTransformation.groovy
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/transforms/global/CompiledAtASTTransformation.groovy 
b/src/main/groovy/transforms/global/CompiledAtASTTransformation.groovy
new file mode 100644
index 0000000..f94e444
--- /dev/null
+++ b/src/main/groovy/transforms/global/CompiledAtASTTransformation.groovy
@@ -0,0 +1,66 @@
+/*
+ *  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 transforms.global
+
+import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.transform.*
+import org.codehaus.groovy.control.*
+import org.codehaus.groovy.ast.expr.*
+import org.codehaus.groovy.ast.stmt.*
+import java.lang.annotation.*
+import org.codehaus.groovy.ast.builder.AstBuilder
+
+/**
+* This ASTTransformation adds a static getCompiledTime() : String method to 
every class.  
+*
+* @author Hamlet D'Arcy
+*/ 
+@GroovyASTTransformation(phase=CompilePhase.CONVERSION)
+public class CompiledAtASTTransformation implements ASTTransformation {
+
+    private static final compileTime = new Date().toString()
+
+    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
+
+        List classes = sourceUnit.ast?.classes
+        classes?.each { ClassNode clazz ->
+            clazz.addMethod(makeMethod())
+        }
+    }
+
+    /**
+    *  OpCodes should normally be referenced, but in a standalone example I 
don't want to have to include
+    * the jar at compile time. 
+    */ 
+    MethodNode makeMethod() {
+        def ast = new AstBuilder().buildFromSpec {
+            method('getCompiledTime', /*OpCodes.ACC_PUBLIC*/1 | 
/*OpCodes.ACC_STATIC*/8, String) {
+                parameters {}
+                exceptions {}
+                block { 
+                    returnStatement {
+                        constant(compileTime) 
+                    }
+                }
+                annotations {}
+            }
+        }
+        ast[0]
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/CompiledAtExample.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/CompiledAtExample.groovy 
b/src/main/groovy/transforms/global/CompiledAtExample.groovy
new file mode 100644
index 0000000..dffbf4f
--- /dev/null
+++ b/src/main/groovy/transforms/global/CompiledAtExample.groovy
@@ -0,0 +1,33 @@
+/*
+ *  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 transforms.global
+
+/**
+* Demonstrates how a global transformation works. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+
+println 'Script compiled at: ' + compiledTime
+
+class MyClass {
+    
+}
+
+println 'Class compiled at: ' + MyClass.compiledTime

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/CompiledAtIntegrationTest.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/CompiledAtIntegrationTest.groovy 
b/src/main/groovy/transforms/global/CompiledAtIntegrationTest.groovy
new file mode 100644
index 0000000..1f03f39
--- /dev/null
+++ b/src/main/groovy/transforms/global/CompiledAtIntegrationTest.groovy
@@ -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.
+ */
+import transforms.global.CompiledAtASTTransformation 
+import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.transform.*
+import org.codehaus.groovy.control.*
+import org.codehaus.groovy.tools.ast.*
+
+/**
+* This shows how to use the TransformTestHelper to test
+* a global transformation. It is a little hard to invoke 
+* because the CompiledAtASTTransformation must be on the 
+* classpath but the JAR containing the transform must not
+* or the transform gets applied twice. 
+*
+* @author Hamlet D'Arcy
+*/ 
+def transform = new CompiledAtASTTransformation()
+def phase = CompilePhase.CONVERSION
+def helper = new TransformTestHelper(transform, phase)
+def clazz = helper.parse(' class MyClass {} ' )
+assert clazz.getCompiledTime() != null
+println 'compiled at' + clazz.getCompiledTime()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/LoggingASTTransformation.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/LoggingASTTransformation.groovy 
b/src/main/groovy/transforms/global/LoggingASTTransformation.groovy
new file mode 100644
index 0000000..c6a2903
--- /dev/null
+++ b/src/main/groovy/transforms/global/LoggingASTTransformation.groovy
@@ -0,0 +1,73 @@
+/*
+ *  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 transforms.global
+
+import org.codehaus.groovy.transform.ASTTransformation
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.AnnotationNode
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import java.lang.annotation.Annotation
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.ast.stmt.Statement
+import org.codehaus.groovy.ast.stmt.ExpressionStatement
+
+/**
+* This ASTTransformation adds a start and stop message to every single method 
call. 
+*
+* @author Hamlet D'Arcy
+*/ 
+@GroovyASTTransformation(phase=CompilePhase.CONVERSION)
+public class LoggingASTTransformation implements ASTTransformation {
+
+
+    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
+        List methods = sourceUnit.getAST()?.getMethods()
+        methods?.each { MethodNode method ->
+            Statement startMessage = createPrintlnAst("Starting $method.name")
+            Statement endMessage = createPrintlnAst("Ending $method.name")
+
+            List existingStatements = method.getCode().getStatements()
+            existingStatements.add(0, startMessage)
+            existingStatements.add(endMessage)
+        }
+    }
+
+    /**
+    * Creates the AST for a println invocation. 
+    */ 
+    private Statement createPrintlnAst(String message) {
+        return new ExpressionStatement(
+            new MethodCallExpression(
+                new VariableExpression("this"),
+                new ConstantExpression("println"),
+                new ArgumentListExpression(
+                    new ConstantExpression(message)
+                )
+            )
+        )
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/LoggingExample.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/LoggingExample.groovy 
b/src/main/groovy/transforms/global/LoggingExample.groovy
new file mode 100644
index 0000000..2d9f38e
--- /dev/null
+++ b/src/main/groovy/transforms/global/LoggingExample.groovy
@@ -0,0 +1,48 @@
+/*
+ *  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 transforms.global
+
+/**
+* Demonstrates how a global transformation works. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+
+def greet() {
+    println "Hello World"
+}
+    
+// this prints out Hello World along with the extra compile time logging
+greet()
+
+
+//
+// The rest of this script is asserting that this all works correctly. 
+//
+
+// redirect standard out so we can make assertions on it
+def standardOut = new ByteArrayOutputStream();
+System.setOut(new PrintStream(standardOut)); 
+  
+greet()
+def result = standardOut.toString("ISO-8859-1").split('\n')
+assert "Starting greet"  == result[0].trim()
+assert "Hello World"     == result[1].trim()
+assert "Ending greet"    == result[2].trim()
+standardOut.close()

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
----------------------------------------------------------------------
diff --git 
a/src/main/groovy/transforms/global/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
 
b/src/main/groovy/transforms/global/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
new file mode 100644
index 0000000..2de1e18
--- /dev/null
+++ 
b/src/main/groovy/transforms/global/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
@@ -0,0 +1,17 @@
+# 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.
+
+transforms.global.LoggingASTTransformation
+transforms.global.CompiledAtASTTransformation

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/build.xml
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/build.xml 
b/src/main/groovy/transforms/global/build.xml
new file mode 100644
index 0000000..66d16c3
--- /dev/null
+++ b/src/main/groovy/transforms/global/build.xml
@@ -0,0 +1,52 @@
+<?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-global-ast-transformation-example" 
default="jar-transform">
+
+    <!-- necessary groovy jars are assumed to be on your classpath. -->
+    <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc" />
+    
+    <target name="init" description="cleanup old class files">
+        <delete dir="examples"/>
+        <delete>
+            <fileset dir="." includes="**/*.jar"/>
+        </delete>        
+    </target>
+
+    <target name="compile-transform" depends="init" description="Compiles the 
AST Transformation">
+    
+        <groovyc destdir="."
+                srcdir="."
+                includes="*Transformation.groovy" 
+                listfiles="true">
+        </groovyc>
+        
+    </target>
+
+    <target name="jar-transform" depends="compile-transform" 
description="Creates a .jar file for the global transform" >
+        <jar destfile="LoggingTransform.jar"
+            basedir="."
+            includes="**/*.class,META-INF/**" />
+
+        <echo>You can now run "groovy -cp LoggingTransform.jar 
LoggingExample.groovy" or "groovy -cp LoggingTransform.jar 
CompiledAtExample.groovy" to see that the transformation worked.</echo>
+    </target>
+</project>
+

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/global/readme.txt
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/global/readme.txt 
b/src/main/groovy/transforms/global/readme.txt
new file mode 100644
index 0000000..e774984
--- /dev/null
+++ b/src/main/groovy/transforms/global/readme.txt
@@ -0,0 +1,48 @@
+====
+     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.
+====
+
+Global AST Transformation Example
+
+This example shows how to wire together a global transformation. 
+
+The example requires ant in your path and the Groovy 1.6 (or greater) 
+Jar in your classpath. The current directory must *not* be on your
+classpath, otherwise ant will try to read the META-INF directory and
+apply the transformations prematurely. 
+
+To build the example run "ant" from the current directory. The default 
+target will compile the classes needed. The last step of the build 
+script prints out the command needed to run the example. 
+
+To run the first example perform the following from the command line: 
+  groovy -cp LoggingTransform.jar LoggingExample.groovy
+  
+The example should print: 
+  Starting greet
+  Hello World
+  Ending greet
+
+To run the second example perform the following from the command line: 
+  groovy -cp LoggingTransform.jar CompiledAtExample.groovy
+  
+The example should print: 
+  Scripted compiled at: [recently]
+  Class compiled at: [recently]
+
+No exceptions should occur. 

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/local/LoggingASTTransformation.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/local/LoggingASTTransformation.groovy 
b/src/main/groovy/transforms/local/LoggingASTTransformation.groovy
new file mode 100644
index 0000000..bd19b95
--- /dev/null
+++ b/src/main/groovy/transforms/local/LoggingASTTransformation.groovy
@@ -0,0 +1,74 @@
+/*
+ *  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 transforms.local
+
+import org.codehaus.groovy.transform.ASTTransformation
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.stmt.ExpressionStatement
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.stmt.Statement
+
+/**
+* This transformation finds all the methods defined in a script that have
+* the @WithLogging annotation on them, and then weaves in a start and stop 
+* message that is logged using println. 
+*
+* @author Hamlet D'Arcy
+*/ 
+@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
+public class LoggingASTTransformation implements ASTTransformation {
+
+    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
+        List methods = sourceUnit.getAST()?.getMethods()
+        // find all methods annotated with @WithLogging
+        methods.findAll { MethodNode method ->
+            method.getAnnotations(new ClassNode(WithLogging))
+        }.each { MethodNode method ->
+            Statement startMessage = createPrintlnAst("Starting $method.name")
+            Statement endMessage = createPrintlnAst("Ending $method.name")
+
+            List existingStatements = method.getCode().getStatements()
+            existingStatements.add(0, startMessage)
+            existingStatements.add(endMessage)
+        }
+    }
+
+    /**
+    * This creates the ASTNode for a println statement. 
+    */ 
+    private Statement createPrintlnAst(String message) {
+        return new ExpressionStatement(
+            new MethodCallExpression(
+                new VariableExpression("this"),
+                new ConstantExpression("println"),
+                new ArgumentListExpression(
+                    new ConstantExpression(message)
+                )
+            )
+        )
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/local/LoggingExample.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/local/LoggingExample.groovy 
b/src/main/groovy/transforms/local/LoggingExample.groovy
new file mode 100644
index 0000000..4d47a64
--- /dev/null
+++ b/src/main/groovy/transforms/local/LoggingExample.groovy
@@ -0,0 +1,64 @@
+/*
+ *  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 transforms.local
+
+/**
+* Demonstrates how a local transformation works. 
+* 
+* @author Hamlet D'Arcy
+*/ 
+
+def greet() {
+    println "Hello World"
+}
+    
+@WithLogging    //this should trigger extra logging
+def greetWithLogging() {
+    println "Hello World"
+}
+    
+// this prints out a simple Hello World
+greet()
+
+// this prints out Hello World along with the extra compile time logging
+greetWithLogging()
+
+
+//
+// The rest of this script is asserting that this all works correctly. 
+//
+
+// redirect standard out so we can make assertions on it
+def standardOut = new ByteArrayOutputStream();
+System.setOut(new PrintStream(standardOut)); 
+  
+greet()
+assert "Hello World" == standardOut.toString("ISO-8859-1").trim()
+
+// reset standard out and redirect it again
+standardOut.close()
+standardOut = new ByteArrayOutputStream();
+System.setOut(new PrintStream(standardOut)); 
+
+greetWithLogging()
+def result = standardOut.toString("ISO-8859-1").split('\n')
+assert "Starting greetWithLogging"  == result[0].trim()
+assert "Hello World"                == result[1].trim()
+assert "Ending greetWithLogging"    == result[2].trim()
+

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/local/WithLogging.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/local/WithLogging.groovy 
b/src/main/groovy/transforms/local/WithLogging.groovy
new file mode 100644
index 0000000..03611f2
--- /dev/null
+++ b/src/main/groovy/transforms/local/WithLogging.groovy
@@ -0,0 +1,37 @@
+/*
+ *  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 transforms.local
+import java.lang.annotation.Retention
+import java.lang.annotation.Target
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+import java.lang.annotation.ElementType
+import java.lang.annotation.RetentionPolicy
+
+/**
+* This is just a marker interface that will trigger a local transformation. 
+* The 3rd Annotation down is the important one: @GroovyASTTransformationClass
+* The parameter is the String form of a fully qualified class name. 
+*
+* @author Hamlet D'Arcy
+*/ 
+@Retention(RetentionPolicy.SOURCE)
+@Target([ElementType.METHOD])
+@GroovyASTTransformationClass(["transforms.local.LoggingASTTransformation"])
+public @interface WithLogging {
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/local/build.xml
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/local/build.xml 
b/src/main/groovy/transforms/local/build.xml
new file mode 100644
index 0000000..eef390a
--- /dev/null
+++ b/src/main/groovy/transforms/local/build.xml
@@ -0,0 +1,44 @@
+<?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-local-ast-transformation-example" 
default="compile-transform">
+
+    <!-- necessary groovy jars are assumed to be on your classpath. -->
+    <taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc" />
+    
+    
+    <target name="init" description="cleanup old class files">
+        <delete dir="examples"/>
+    </target>
+
+    <target name="compile-transform" depends="init" description="Compiles the 
AST Transformation">
+    
+        <groovyc destdir="."
+                srcdir="."
+                includes="LoggingASTTransformation.groovy,WithLogging.groovy" 
+                listfiles="true">
+        </groovyc>
+        
+        <echo>You can now run "groovy LoggingExample.groovy" to see that the 
transformation worked.</echo>
+    </target>
+
+</project>
+

http://git-wip-us.apache.org/repos/asf/groovy-examples/blob/3bd1e181/src/main/groovy/transforms/local/readme.txt
----------------------------------------------------------------------
diff --git a/src/main/groovy/transforms/local/readme.txt 
b/src/main/groovy/transforms/local/readme.txt
new file mode 100644
index 0000000..2daade2
--- /dev/null
+++ b/src/main/groovy/transforms/local/readme.txt
@@ -0,0 +1,40 @@
+====
+     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.
+====
+
+Local AST Transformation Example
+
+This example shows how to wire together a local transformation. 
+
+The example requires ant in your path and the Groovy 1.6 (or greater) 
+Jar in your classpath. 
+
+To build the example run "ant" from the current directory. The default 
+target will compile the classes needed. The last step of the build 
+script prints out the command needed to run the example. 
+
+To run the example perform the following from the command line: 
+  groovy LoggingExample.groovy
+  
+The example should print: 
+  Hello World
+  Starting greetWithLogging
+  Hello World
+  Ending greetWithLogging
+
+No exceptions should occur. 
\ No newline at end of file

Reply via email to