groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sun...@apache.org
Subject [32/49] groovy git commit: Move source files to proper packages
Date Wed, 20 Dec 2017 00:56:29 GMT
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/org/codehaus/groovy/transform/TimedInterruptibleASTTransformation.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/org/codehaus/groovy/transform/TimedInterruptibleASTTransformation.groovy b/src/main/groovy/groovy/org/codehaus/groovy/transform/TimedInterruptibleASTTransformation.groovy
new file mode 100644
index 0000000..fbc923b
--- /dev/null
+++ b/src/main/groovy/groovy/org/codehaus/groovy/transform/TimedInterruptibleASTTransformation.groovy
@@ -0,0 +1,321 @@
+/*
+ *  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.transform
+
+import groovy.transform.TimedInterrupt
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.AnnotatedNode
+import org.codehaus.groovy.ast.AnnotationNode
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.FieldNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.PropertyNode
+import org.codehaus.groovy.ast.expr.ClosureExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.DeclarationExpression
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.ast.stmt.DoWhileStatement
+import org.codehaus.groovy.ast.stmt.ForStatement
+import org.codehaus.groovy.ast.stmt.WhileStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+import static org.codehaus.groovy.ast.ClassHelper.make
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ltX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.plusX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
+
+/**
+ * Allows "interrupt-safe" executions of scripts by adding timer expiration
+ * checks on loops (for, while, do) and first statement of closures. By default,
+ * also adds an interrupt check statement on the beginning of method calls.
+ *
+ * @author Cedric Champeau
+ * @author Hamlet D'Arcy
+ * @author Paul King
+ * @see groovy.transform.ThreadInterrupt
+ * @since 1.8.0
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class TimedInterruptibleASTTransformation extends AbstractASTTransformation {
+
+    private static final ClassNode MY_TYPE = make(TimedInterrupt)
+    private static final String CHECK_METHOD_START_MEMBER = 'checkOnMethodStart'
+    private static final String APPLY_TO_ALL_CLASSES = 'applyToAllClasses'
+    private static final String APPLY_TO_ALL_MEMBERS = 'applyToAllMembers'
+    private static final String THROWN_EXCEPTION_TYPE = "thrown"
+
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        init(nodes, source);
+        AnnotationNode node = nodes[0]
+        AnnotatedNode annotatedNode = nodes[1]
+        if (!MY_TYPE.equals(node.getClassNode())) {
+            internalError("Transformation called from wrong annotation: $node.classNode.name")
+        }
+
+        def checkOnMethodStart = getConstantAnnotationParameter(node, CHECK_METHOD_START_MEMBER, Boolean.TYPE, true)
+        def applyToAllMembers = getConstantAnnotationParameter(node, APPLY_TO_ALL_MEMBERS, Boolean.TYPE, true)
+        def applyToAllClasses = applyToAllMembers ? getConstantAnnotationParameter(node, APPLY_TO_ALL_CLASSES, Boolean.TYPE, true) : false
+        def maximum = getConstantAnnotationParameter(node, 'value', Long.TYPE, Long.MAX_VALUE)
+        def thrown = AbstractInterruptibleASTTransformation.getClassAnnotationParameter(node, THROWN_EXCEPTION_TYPE, make(TimeoutException))
+
+        Expression unit = node.getMember('unit') ?: propX(classX(TimeUnit), "SECONDS")
+
+        // should be limited to the current SourceUnit or propagated to the whole CompilationUnit
+        // DO NOT inline visitor creation in code below. It has state that must not persist between calls
+        if (applyToAllClasses) {
+            // guard every class and method defined in this script
+            source.getAST()?.classes?.each { ClassNode it ->
+                def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+                visitor.visitClass(it)
+            }
+        } else if (annotatedNode instanceof ClassNode) {
+            // only guard this particular class
+            def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+            visitor.visitClass annotatedNode
+        } else if (!applyToAllMembers && annotatedNode instanceof MethodNode) {
+            // only guard this particular method (plus initCode for class)
+            def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+            visitor.visitMethod annotatedNode
+            visitor.visitClass annotatedNode.declaringClass
+        } else if (!applyToAllMembers && annotatedNode instanceof FieldNode) {
+            // only guard this particular field (plus initCode for class)
+            def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+            visitor.visitField annotatedNode
+            visitor.visitClass annotatedNode.declaringClass
+        } else if (!applyToAllMembers && annotatedNode instanceof DeclarationExpression) {
+            // only guard this particular declaration (plus initCode for class)
+            def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+            visitor.visitDeclarationExpression annotatedNode
+            visitor.visitClass annotatedNode.declaringClass
+        } else {
+            // only guard the script class
+            source.getAST()?.classes?.each { ClassNode it ->
+                if (it.isScript()) {
+                    def visitor = new TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, node.hashCode())
+                    visitor.visitClass(it)
+                }
+            }
+        }
+    }
+
+    static def getConstantAnnotationParameter(AnnotationNode node, String parameterName, Class type, defaultValue) {
+        def member = node.getMember(parameterName)
+        if (member) {
+            if (member instanceof ConstantExpression) {
+                // TODO not sure this try offers value - testing Groovy annotation type handing - throw GroovyBugError or remove?
+                try {
+                    return member.value.asType(type)
+                } catch (ignore) {
+                    internalError("Expecting boolean value for ${parameterName} annotation parameter. Found $member")
+                }
+            } else {
+                internalError("Expecting boolean value for ${parameterName} annotation parameter. Found $member")
+            }
+        }
+        return defaultValue
+    }
+
+    private static void internalError(String message) {
+        throw new RuntimeException("Internal error: $message")
+    }
+
+    private static class TimedInterruptionVisitor extends ClassCodeVisitorSupport {
+        final private SourceUnit source
+        final private boolean checkOnMethodStart
+        final private boolean applyToAllClasses
+        final private boolean applyToAllMembers
+        private FieldNode expireTimeField = null
+        private FieldNode startTimeField = null
+        private final Expression unit
+        private final maximum
+        private final ClassNode thrown
+        private final String basename
+
+        TimedInterruptionVisitor(source, checkOnMethodStart, applyToAllClasses, applyToAllMembers, maximum, unit, thrown, hash) {
+            this.source = source
+            this.checkOnMethodStart = checkOnMethodStart
+            this.applyToAllClasses = applyToAllClasses
+            this.applyToAllMembers = applyToAllMembers
+            this.unit = unit
+            this.maximum = maximum
+            this.thrown = thrown
+            this.basename = 'timedInterrupt' + hash
+        }
+
+        /**
+         * @return Returns the interruption check statement.
+         */
+        final createInterruptStatement() {
+            ifS(
+
+                    ltX(
+                            propX(varX("this"), basename + '$expireTime'),
+                            callX(make(System), 'nanoTime')
+                    ),
+                    throwS(
+                            ctorX(thrown,
+                                    args(
+                                            plusX(
+                                                    plusX(
+                                                            constX('Execution timed out after ' + maximum + ' '),
+                                                            callX(callX(unit, 'name'), 'toLowerCase', propX(classX(Locale), 'US'))
+                                                    ),
+                                                    plusX(
+                                                            constX('. Start time: '),
+                                                            propX(varX("this"), basename + '$startTime')
+                                                    )
+                                            )
+
+                                    )
+                            )
+                    )
+            )
+        }
+
+        /**
+         * Takes a statement and wraps it into a block statement which first element is the interruption check statement.
+         * @param statement the statement to be wrapped
+         * @return a {@link BlockStatement block statement}    which first element is for checking interruption, and the
+         * second one the statement to be wrapped.
+         */
+        private wrapBlock(statement) {
+            def stmt = new BlockStatement();
+            stmt.addStatement(createInterruptStatement());
+            stmt.addStatement(statement);
+            stmt
+        }
+
+        @Override
+        void visitClass(ClassNode node) {
+            if (node.getDeclaredField(basename + '$expireTime')) {
+                return
+            }
+            expireTimeField = node.addField(basename + '$expireTime',
+                    ACC_FINAL | ACC_PRIVATE,
+                    ClassHelper.long_TYPE,
+                    plusX(
+                            callX(make(System), 'nanoTime'),
+                            callX(
+                                    propX(classX(TimeUnit), 'NANOSECONDS'),
+                                    'convert',
+                                    args(constX(maximum, true), unit)
+                            )
+                    )
+            );
+            expireTimeField.synthetic = true
+            startTimeField = node.addField(basename + '$startTime',
+                    ACC_FINAL | ACC_PRIVATE,
+                    make(Date),
+                    ctorX(make(Date))
+            )
+            startTimeField.synthetic = true
+
+            // force these fields to be initialized first
+            node.fields.remove(expireTimeField)
+            node.fields.remove(startTimeField)
+            node.fields.add(0, startTimeField)
+            node.fields.add(0, expireTimeField)
+            if (applyToAllMembers) {
+                super.visitClass node
+            }
+        }
+
+        @Override
+        void visitClosureExpression(ClosureExpression closureExpr) {
+            def code = closureExpr.code
+            if (code instanceof BlockStatement) {
+                code.statements.add(0, createInterruptStatement())
+            } else {
+                closureExpr.code = wrapBlock(code)
+            }
+            super.visitClosureExpression closureExpr
+        }
+
+        @Override
+        void visitField(FieldNode node) {
+            if (!node.isStatic() && !node.isSynthetic()) {
+                super.visitField node
+            }
+        }
+
+        @Override
+        void visitProperty(PropertyNode node) {
+            if (!node.isStatic() && !node.isSynthetic()) {
+                super.visitProperty node
+            }
+        }
+
+        /**
+         * Shortcut method which avoids duplicating code for every type of loop.
+         * Actually wraps the loopBlock of different types of loop statements.
+         */
+        private visitLoop(loopStatement) {
+            def statement = loopStatement.loopBlock
+            loopStatement.loopBlock = wrapBlock(statement)
+        }
+
+        @Override
+        void visitForLoop(ForStatement forStatement) {
+            visitLoop(forStatement)
+            super.visitForLoop(forStatement)
+        }
+
+        @Override
+        void visitDoWhileLoop(final DoWhileStatement doWhileStatement) {
+            visitLoop(doWhileStatement)
+            super.visitDoWhileLoop(doWhileStatement)
+        }
+
+        @Override
+        void visitWhileLoop(final WhileStatement whileStatement) {
+            visitLoop(whileStatement)
+            super.visitWhileLoop(whileStatement)
+        }
+
+        @Override
+        void visitMethod(MethodNode node) {
+            if (checkOnMethodStart && !node.isSynthetic() && !node.isStatic() && !node.isAbstract()) {
+                def code = node.code
+                node.code = wrapBlock(code);
+            }
+            if (!node.isSynthetic() && !node.isStatic()) {
+                super.visitMethod(node)
+            }
+        }
+
+        protected SourceUnit getSourceUnit() {
+            return source;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/security/GroovyCodeSourcePermission.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/security/GroovyCodeSourcePermission.java b/src/main/groovy/groovy/security/GroovyCodeSourcePermission.java
new file mode 100644
index 0000000..e723e0b
--- /dev/null
+++ b/src/main/groovy/groovy/security/GroovyCodeSourcePermission.java
@@ -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.
+ */
+package groovy.security;
+
+import java.security.BasicPermission;
+
+/**
+ * Permission required to explicitly specify a codebase for a groovy script whose
+ * codebase cannot be determined.  Typically this permission is only
+ * required by clients that want to associate a code source with a script which
+ * is a String or an InputStream.
+ *
+ * @author Steve Goetze
+ */
+public final class GroovyCodeSourcePermission extends BasicPermission {
+
+    public GroovyCodeSourcePermission(String name) {
+        super(name);
+    }
+
+    public GroovyCodeSourcePermission(String name, String actions) {
+        super(name, actions);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/security/package.html
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/security/package.html b/src/main/groovy/groovy/security/package.html
new file mode 100644
index 0000000..cb094ff
--- /dev/null
+++ b/src/main/groovy/groovy/security/package.html
@@ -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.
+
+-->
+<html>
+  <head>
+    <title>package groovy.security.*</title>
+  </head>
+  <body>
+    <p>
+      Security-related classes
+    </p>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/BaseDuration.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/BaseDuration.java b/src/main/groovy/groovy/time/BaseDuration.java
new file mode 100644
index 0000000..e2cc023
--- /dev/null
+++ b/src/main/groovy/groovy/time/BaseDuration.java
@@ -0,0 +1,141 @@
+/*
+ *  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.time;
+
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.runtime.StringGroovyMethods;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Base class for date and time durations.
+ *
+ * @author John Wilson tug@wilson.co.uk
+ * @see Duration
+ */
+public abstract class BaseDuration implements Comparable<BaseDuration> {
+    protected final int years;
+    protected final int months;
+    protected final int days;
+    protected final int hours;
+    protected final int minutes;
+    protected final int seconds;
+    protected final int millis;
+
+    protected BaseDuration(final int years, final int months, final int days, final int hours, final int minutes, final int seconds, final int millis) {
+        this.years = years;
+        this.months = months;
+        this.days = days;
+        this.hours = hours;
+        this.minutes = minutes;
+        this.seconds = seconds;
+        this.millis = millis;
+    }
+
+    protected BaseDuration(final int days, final int hours, final int minutes, final int seconds, final int millis) {
+        this(0, 0, days, hours, minutes, seconds, millis);
+    }
+
+    public int getYears() {
+        return this.years;
+    }
+
+    public int getMonths() {
+        return this.months;
+    }
+
+    public int getDays() {
+        return this.days;
+    }
+
+    public int getHours() {
+        return this.hours;
+    }
+
+    public int getMinutes() {
+        return this.minutes;
+    }
+
+    public int getSeconds() {
+        return this.seconds;
+    }
+
+    public int getMillis() {
+        return this.millis;
+    }
+
+    public Date plus(final Date date) {
+        final Calendar cal = Calendar.getInstance();
+
+        cal.setTime(date);
+        cal.add(Calendar.YEAR, this.years);
+        cal.add(Calendar.MONTH, this.months);
+        cal.add(Calendar.DAY_OF_YEAR, this.days);
+        cal.add(Calendar.HOUR_OF_DAY, this.hours);
+        cal.add(Calendar.MINUTE, this.minutes);
+        cal.add(Calendar.SECOND, this.seconds);
+        cal.add(Calendar.MILLISECOND, this.millis);
+
+        return cal.getTime();
+    }
+
+    public String toString() {
+        List buffer = new ArrayList();
+
+        if (this.years != 0) buffer.add(this.years + " years");
+        if (this.months != 0) buffer.add(this.months + " months");
+        if (this.days != 0) buffer.add(this.days + " days");
+        if (this.hours != 0) buffer.add(this.hours + " hours");
+        if (this.minutes != 0) buffer.add(this.minutes + " minutes");
+
+        if (this.seconds != 0 || this.millis != 0) {
+            int norm_millis = this.millis % 1000;
+            int norm_seconds = this.seconds + DefaultGroovyMethods.intdiv(this.millis - norm_millis, 1000).intValue();
+            CharSequence millisToPad = "" + Math.abs(norm_millis);
+            buffer.add((norm_seconds == 0 ? (norm_millis < 0 ? "-0" : "0") : norm_seconds) + "." + StringGroovyMethods.padLeft(millisToPad, 3, "0") + " seconds");
+        }
+
+        if (!buffer.isEmpty()) {
+            return DefaultGroovyMethods.join(buffer.iterator(), ", ");
+        } else {
+            return "0";
+        }
+    }
+
+    public abstract long toMilliseconds();
+
+    public abstract Date getAgo();
+
+    public abstract From getFrom();
+
+    public int compareTo(BaseDuration otherDuration) {
+        return Long.signum(toMilliseconds() - otherDuration.toMilliseconds());
+    }
+
+    public abstract static class From {
+        public abstract Date getNow();
+
+        public Date getToday() {
+            return getNow();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/DatumDependentDuration.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/DatumDependentDuration.java b/src/main/groovy/groovy/time/DatumDependentDuration.java
new file mode 100644
index 0000000..c73d9aa
--- /dev/null
+++ b/src/main/groovy/groovy/time/DatumDependentDuration.java
@@ -0,0 +1,141 @@
+/*
+ *  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.time;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * DatumDependentDuration represents durations whose length in milliseconds 
+ * cannot be determined without knowing the datum point.
+ * <p>
+ * I don't know how many days in a year unless I know if it's a leap year or not.
+ * <p>
+ * I don't know how many days in a month unless I know the name of the month 
+ * (and if it's a leap year if the month is February)
+ *
+ * @author John Wilson tug@wilson.co.uk
+ */
+public class DatumDependentDuration extends BaseDuration {
+    public DatumDependentDuration(final int years, final int months, final int days, final int hours, final int minutes, final int seconds, final int millis) {
+        super(years, months, days, hours, minutes, seconds, millis);
+    }
+
+    public int getMonths() {
+        return this.months;
+    }
+
+    public int getYears() {
+        return this.years;
+    }
+
+    public DatumDependentDuration plus(final DatumDependentDuration rhs) {
+        return new DatumDependentDuration(this.getYears() + rhs.getYears(), this.getMonths() + rhs.getMonths(),
+                this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                this.getMillis() + rhs.getMillis());
+    }
+
+    public DatumDependentDuration plus(final TimeDatumDependentDuration rhs) {
+        return rhs.plus(this);
+    }
+
+    public DatumDependentDuration plus(final Duration rhs) {
+        return new DatumDependentDuration(this.getYears(), this.getMonths(),
+                this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                this.getMillis() + rhs.getMillis());
+
+    }
+
+    public DatumDependentDuration plus(final TimeDuration rhs) {
+        return rhs.plus(this);
+
+    }
+
+    public DatumDependentDuration minus(final DatumDependentDuration rhs) {
+        return new DatumDependentDuration(this.getYears() - rhs.getYears(), this.getMonths() - rhs.getMonths(),
+                this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                this.getMillis() - rhs.getMillis());
+
+    }
+
+    public DatumDependentDuration minus(final Duration rhs) {
+        return new DatumDependentDuration(this.getYears(), this.getMonths(),
+                this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                this.getMillis() - rhs.getMillis());
+
+    }
+
+    /**
+     * @see groovy.time.BaseDuration#toMilliseconds()
+     *
+     * Change the duration into milliseconds, relative to 'now.'  Therefore
+     * things like timezone and time of year will affect how this conversion 
+     * occurs.
+     */
+    public long toMilliseconds() {
+        final Date now = new Date();
+        return TimeCategory.minus(plus(now), now).toMilliseconds();
+    }
+
+    public Date getAgo() {
+        final Calendar cal = Calendar.getInstance();
+
+        cal.add(Calendar.YEAR, -this.getYears());
+        cal.add(Calendar.MONTH, -this.getMonths());
+        cal.add(Calendar.DAY_OF_YEAR, -this.getDays());
+        cal.add(Calendar.HOUR_OF_DAY, -this.getHours());
+        cal.add(Calendar.MINUTE, -this.getMinutes());
+        cal.add(Calendar.SECOND, -this.getSeconds());
+        cal.add(Calendar.MILLISECOND, -this.getMillis());
+
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+
+        return new Date(cal.getTimeInMillis());
+    }
+
+    public From getFrom() {
+        return new From() {
+            public Date getNow() {
+                final Calendar cal = Calendar.getInstance();
+
+                cal.add(Calendar.YEAR, DatumDependentDuration.this.getYears());
+                cal.add(Calendar.MONTH, DatumDependentDuration.this.getMonths());
+                cal.add(Calendar.DAY_OF_YEAR, DatumDependentDuration.this.getDays());
+                cal.add(Calendar.HOUR_OF_DAY, DatumDependentDuration.this.getHours());
+                cal.add(Calendar.MINUTE, DatumDependentDuration.this.getMinutes());
+                cal.add(Calendar.SECOND, DatumDependentDuration.this.getSeconds());
+                cal.add(Calendar.MILLISECOND, DatumDependentDuration.this.getMillis());
+
+                cal.set(Calendar.HOUR_OF_DAY, 0);
+                cal.set(Calendar.MINUTE, 0);
+                cal.set(Calendar.SECOND, 0);
+                cal.set(Calendar.MILLISECOND, 0);
+
+                return new Date(cal.getTimeInMillis());
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/Duration.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/Duration.java b/src/main/groovy/groovy/time/Duration.java
new file mode 100644
index 0000000..e187957
--- /dev/null
+++ b/src/main/groovy/groovy/time/Duration.java
@@ -0,0 +1,119 @@
+/*
+ *  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.time;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Duration represents time periods which have values independent of the context.
+ * So, whilst we can't say how long a month is without knowing the year and the name of the month,
+ * we know how long a day is independent of the date.
+ * <p>
+ * This is not 100% true for days.
+ * Days can actually be 23, 24 or 25 hours long (due to daylight saving adjustments.)
+ * <p>
+ * If you ask Duration to convert itself to milliseconds then it will work on the basis of 24 hours
+ * in a day. If you add or subtract it from a date it will take daylight saving into account.
+ *
+ * @author John Wilson tug@wilson.co.uk
+ */
+public class Duration extends BaseDuration {
+    public Duration(final int days, final int hours, final int minutes, final int seconds, final int millis) {
+        super(days, hours, minutes, seconds, millis);
+    }
+    
+    public Duration plus(final Duration rhs) {
+        return new Duration(this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                            this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                            this.getMillis() + rhs.getMillis());
+    }
+
+    public Duration plus(final TimeDuration rhs) {
+        return rhs.plus(this);
+    }
+    
+    public DatumDependentDuration plus(final DatumDependentDuration rhs) {
+        return rhs.plus(this);
+    }
+    
+    public Duration minus(final Duration rhs) {
+        return new Duration(this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                            this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                            this.getMillis() - rhs.getMillis());
+    }
+    
+    public TimeDuration minus(final TimeDuration rhs) {
+        return new TimeDuration(this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                this.getMillis() - rhs.getMillis());
+    }
+    
+    public DatumDependentDuration minus(final DatumDependentDuration rhs) {
+        return new DatumDependentDuration(-rhs.getYears(), -rhs.getMonths(),
+                                          this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                          this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                          this.getMillis() - rhs.getMillis());
+    }
+    
+    public TimeDatumDependentDuration minus(final TimeDatumDependentDuration rhs) {
+        return new TimeDatumDependentDuration(-rhs.getYears(), -rhs.getMonths(),
+                                              this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                              this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                              this.getMillis() - rhs.getMillis());
+    }
+    
+    public long toMilliseconds() {
+        return ((((((long)(this.getDays() * 24 ) + this.getHours()) * 60 + this.getMinutes()) * 60) + this.getSeconds()) * 1000) + this.getMillis();
+    }
+    
+    public Date getAgo() {
+    final Calendar cal = Calendar.getInstance();
+
+        cal.add(Calendar.DAY_OF_YEAR, -this.getDays());
+        cal.add(Calendar.HOUR_OF_DAY, -this.getHours());
+        cal.add(Calendar.MINUTE, -this.getMinutes());
+        cal.add(Calendar.SECOND, -this.getSeconds());
+        cal.add(Calendar.MILLISECOND, -this.getMillis());
+
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        
+        return new Date(cal.getTimeInMillis());
+    }
+     
+    public From getFrom() {
+        return new From() {
+            public Date getNow() {
+            final Calendar cal = Calendar.getInstance();
+
+                cal.add(Calendar.DAY_OF_YEAR, Duration.this.getDays());
+
+                cal.set(Calendar.HOUR_OF_DAY, 0);
+                cal.set(Calendar.MINUTE, 0);
+                cal.set(Calendar.SECOND, 0);
+                cal.set(Calendar.MILLISECOND, 0);
+
+                return new Date(cal.getTimeInMillis());
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/TimeCategory.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/TimeCategory.java b/src/main/groovy/groovy/time/TimeCategory.java
new file mode 100644
index 0000000..dbc08cd
--- /dev/null
+++ b/src/main/groovy/groovy/time/TimeCategory.java
@@ -0,0 +1,203 @@
+/*
+ *  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.time;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Apply a number of methods to allow convenient Date/Time manipulation,such as:
+ * <pre class="groovyTestCase">
+ * use (groovy.time.TimeCategory) {
+ *     // application on numbers:
+ *     println 1.minute.from.now
+ *     println 10.hours.ago
+ *
+ *     // application on dates
+ *     def someDate = new Date()
+ *     println someDate - 3.months
+ * }
+ * </pre>
+ *
+ * @see BaseDuration
+ */
+public class TimeCategory {
+    /*
+     * Methods to allow Date Duration arithmetic
+     */
+
+    public static Date plus(final Date date, final BaseDuration duration) {
+        return duration.plus(date);
+    }
+
+    public static Date minus(final Date date, final BaseDuration duration) {
+        final Calendar cal = Calendar.getInstance();
+
+        cal.setTime(date);
+        cal.add(Calendar.YEAR, -duration.getYears());
+        cal.add(Calendar.MONTH, -duration.getMonths());
+        cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
+        cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
+        cal.add(Calendar.MINUTE, -duration.getMinutes());
+        cal.add(Calendar.SECOND, -duration.getSeconds());
+        cal.add(Calendar.MILLISECOND, -duration.getMillis());
+
+        return cal.getTime();
+    }
+
+    /**
+     * Retrieves the default TimeZone for a date by using the default Locale
+     * settings. Recommended that you use {@code TimeZone.getDefault()} instead.
+     *
+     * @param self a Date
+     * @return the TimeZone
+     */
+    @Deprecated
+    public static TimeZone getTimeZone(Date self) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(self);
+        return calendar.getTimeZone();
+    }
+
+    /**
+     * Get the DST offset (if any) for the default locale and the given date.
+     *
+     * @param self a Date
+     * @return the DST offset as a Duration.
+     */
+    public static Duration getDaylightSavingsOffset(Date self) {
+        TimeZone timeZone = getTimeZone(self);
+        int millis = (timeZone.useDaylightTime() && timeZone.inDaylightTime(self))
+                ? timeZone.getDSTSavings() : 0;
+        return new TimeDuration(0, 0, 0, millis);
+    }
+
+    public static Duration getDaylightSavingsOffset(BaseDuration self) {
+        return getDaylightSavingsOffset(new Date(self.toMilliseconds() + 1));
+    }
+
+    /**
+     * Return a Duration representing the DST difference (if any) between two
+     * dates.  i.e. if one date is before the DST changeover, and the other
+     * date is after, the resulting duration will represent the DST offset.
+     *
+     * @param self  a Date
+     * @param other another Date
+     * @return a Duration
+     */
+    public static Duration getRelativeDaylightSavingsOffset(Date self, Date other) {
+        Duration d1 = getDaylightSavingsOffset(self);
+        Duration d2 = getDaylightSavingsOffset(other);
+        return new TimeDuration(0, 0, 0, (int) (d2.toMilliseconds() - d1.toMilliseconds()));
+    }
+
+    /**
+     * Subtract one date from the other.
+     *
+     * @param lhs a Date
+     * @param rhs another Date
+     * @return a Duration
+     */
+    public static TimeDuration minus(final Date lhs, final Date rhs) {
+        long milliseconds = lhs.getTime() - rhs.getTime();
+        long days = milliseconds / (24 * 60 * 60 * 1000);
+        milliseconds -= days * 24 * 60 * 60 * 1000;
+        int hours = (int) (milliseconds / (60 * 60 * 1000));
+        milliseconds -= hours * 60 * 60 * 1000;
+        int minutes = (int) (milliseconds / (60 * 1000));
+        milliseconds -= minutes * 60 * 1000;
+        int seconds = (int) (milliseconds / 1000);
+        milliseconds -= seconds * 1000;
+
+        return new TimeDuration((int) days, hours, minutes, seconds, (int) milliseconds);
+    }
+
+    /*
+    * Methods on Integer to implement 1.month, 4.years etc.
+    */
+
+    public static DatumDependentDuration getMonths(final Integer self) {
+        return new DatumDependentDuration(0, self, 0, 0, 0, 0, 0);
+    }
+
+    public static DatumDependentDuration getMonth(final Integer self) {
+        return getMonths(self);
+    }
+
+    public static DatumDependentDuration getYears(final Integer self) {
+        return new DatumDependentDuration(self, 0, 0, 0, 0, 0, 0);
+    }
+
+    public static DatumDependentDuration getYear(final Integer self) {
+        return getYears(self);
+    }
+
+    /*
+    * Methods on Integer to implement 1.week, 4.days etc.
+    */
+
+    public static Duration getWeeks(final Integer self) {
+        return new Duration(self * 7, 0, 0, 0, 0);
+    }
+
+    public static Duration getWeek(final Integer self) {
+        return getWeeks(self);
+    }
+
+    public static Duration getDays(final Integer self) {
+        return new Duration(self, 0, 0, 0, 0);
+    }
+
+    public static Duration getDay(final Integer self) {
+        return getDays(self);
+    }
+
+    public static TimeDuration getHours(final Integer self) {
+        return new TimeDuration(0, self, 0, 0, 0);
+    }
+
+    public static TimeDuration getHour(final Integer self) {
+        return getHours(self);
+    }
+
+    public static TimeDuration getMinutes(final Integer self) {
+        return new TimeDuration(0, 0, self, 0, 0);
+    }
+
+    public static TimeDuration getMinute(final Integer self) {
+        return getMinutes(self);
+    }
+
+    public static TimeDuration getSeconds(final Integer self) {
+        return new TimeDuration(0, 0, 0, self, 0);
+    }
+
+    public static TimeDuration getSecond(final Integer self) {
+        return getSeconds(self);
+    }
+
+    public static TimeDuration getMilliseconds(final Integer self) {
+        return new TimeDuration(0, 0, 0, 0, self);
+    }
+
+    public static TimeDuration getMillisecond(final Integer self) {
+        return getMilliseconds(self);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/TimeDatumDependentDuration.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/TimeDatumDependentDuration.java b/src/main/groovy/groovy/time/TimeDatumDependentDuration.java
new file mode 100644
index 0000000..eead45a
--- /dev/null
+++ b/src/main/groovy/groovy/time/TimeDatumDependentDuration.java
@@ -0,0 +1,81 @@
+/*
+ *  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.time;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * TimeDatumDuration represents a time period which results from an
+ * arithmetic operation between a TimeDuration object and a DatumDuration object
+ * 
+ * @author John Wilson tug@wilson.co.uk
+ */
+public class TimeDatumDependentDuration extends DatumDependentDuration {
+    public TimeDatumDependentDuration(int years, int months, int days, int hours, int minutes, int seconds, int millis) {
+        super(years, months, days, hours, minutes, seconds, millis);
+    }
+    
+    public DatumDependentDuration plus(final Duration rhs) {
+        return new TimeDatumDependentDuration(this.getYears(), this.getMonths(),
+                                              this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                                              this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                                              this.getMillis() + rhs.getMillis());
+    }
+    
+    public DatumDependentDuration plus(final DatumDependentDuration rhs) {
+        return new TimeDatumDependentDuration(this.getYears() + rhs.getYears(), this.getMonths() + rhs.getMonths(),
+                                              this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                                              this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                                              this.getMillis() + rhs.getMillis());
+    }
+    
+    public DatumDependentDuration minus(final Duration rhs) {
+        return new TimeDatumDependentDuration(this.getYears(), this.getMonths(),
+                                              this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                              this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                              this.getMillis() - rhs.getMillis());
+    }
+    
+    public DatumDependentDuration minus(final DatumDependentDuration rhs) {
+        return new TimeDatumDependentDuration(this.getYears() - rhs.getYears(), this.getMonths() - rhs.getMonths(),
+                                              this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                              this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                              this.getMillis() - rhs.getMillis());
+    }
+    
+    public From getFrom() {
+        return new From() {
+            public Date getNow() {
+            final Calendar cal = Calendar.getInstance();
+
+                cal.add(Calendar.YEAR, TimeDatumDependentDuration.this.getYears());
+                cal.add(Calendar.MONTH, TimeDatumDependentDuration.this.getMonths());
+                cal.add(Calendar.DAY_OF_YEAR, TimeDatumDependentDuration.this.getDays());
+                cal.add(Calendar.HOUR_OF_DAY, TimeDatumDependentDuration.this.getHours());
+                cal.add(Calendar.MINUTE, TimeDatumDependentDuration.this.getMinutes());
+                cal.add(Calendar.SECOND, TimeDatumDependentDuration.this.getSeconds());
+                cal.add(Calendar.MILLISECOND, TimeDatumDependentDuration.this.getMillis());
+                
+                return cal.getTime();
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/TimeDuration.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/TimeDuration.java b/src/main/groovy/groovy/time/TimeDuration.java
new file mode 100644
index 0000000..767b8e6
--- /dev/null
+++ b/src/main/groovy/groovy/time/TimeDuration.java
@@ -0,0 +1,102 @@
+/*
+ *  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.time;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * TimeDuration represents time periods expressed in units of hours, minutes,
+ * seconds and milliseconds.
+ * <p>
+ * Whilst we can't say how long a month is without knowing the year and the name of the month,
+ * we know how long a second is independent of the date.
+ * <p>
+ * This is not 100% true for minutes.
+ * Minutes can be 59, 60 or 61 seconds long (due to leap seconds.)
+ * <p>
+ * If you ask Duration to convert itself to milliseconds then it will work on the basis of 60 seconds in a minute.
+ * If you add or subtract it from a date it will take leap seconds into account.
+ *
+ * @author John Wilson tug@wilson.co.uk
+ */
+
+public class TimeDuration extends Duration {
+    public TimeDuration(final int hours, final int minutes, final int seconds, final int millis) {
+        super(0, hours, minutes, seconds, millis);
+     }
+    
+    public TimeDuration(final int days, final int hours, final int minutes, final int seconds, final int millis) {
+        super(days, hours, minutes, seconds, millis);
+     }
+    
+    public Duration plus(final Duration rhs) {
+        return new TimeDuration(this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                                this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                                this.getMillis() + rhs.getMillis());
+    }
+    
+    public DatumDependentDuration plus(final DatumDependentDuration rhs) {
+        return new TimeDatumDependentDuration(rhs.getYears(), rhs.getMonths(),
+                                              this.getDays() + rhs.getDays(), this.getHours() + rhs.getHours(),
+                                              this.getMinutes() + rhs.getMinutes(), this.getSeconds() + rhs.getSeconds(),
+                                              this.getMillis() + rhs.getMillis());
+    }
+    
+    public Duration minus(final Duration rhs) {
+        return new TimeDuration(this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                this.getMillis() - rhs.getMillis());
+    }
+    
+    public DatumDependentDuration minus(final DatumDependentDuration rhs) {
+        return new TimeDatumDependentDuration(-rhs.getYears(), -rhs.getMonths(),
+                                              this.getDays() - rhs.getDays(), this.getHours() - rhs.getHours(),
+                                              this.getMinutes() - rhs.getMinutes(), this.getSeconds() - rhs.getSeconds(),
+                                              this.getMillis() - rhs.getMillis());
+    }
+    
+    public Date getAgo() {
+        final Calendar cal = Calendar.getInstance();
+
+        cal.add(Calendar.DAY_OF_YEAR, -this.getDays());
+        cal.add(Calendar.HOUR_OF_DAY, -this.getHours());
+        cal.add(Calendar.MINUTE, -this.getMinutes());
+        cal.add(Calendar.SECOND, -this.getSeconds());
+        cal.add(Calendar.MILLISECOND, -this.getMillis());
+        
+        return cal.getTime();
+    }        
+
+    public From getFrom() {
+        return new From() {
+            public Date getNow() {
+                final Calendar cal = Calendar.getInstance();
+
+                cal.add(Calendar.DAY_OF_YEAR, TimeDuration.this.getDays());
+                cal.add(Calendar.HOUR_OF_DAY, TimeDuration.this.getHours());
+                cal.add(Calendar.MINUTE, TimeDuration.this.getMinutes());
+                cal.add(Calendar.SECOND, TimeDuration.this.getSeconds());
+                cal.add(Calendar.MILLISECOND, TimeDuration.this.getMillis());
+                
+                return cal.getTime();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/time/package.html
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/time/package.html b/src/main/groovy/groovy/time/package.html
new file mode 100644
index 0000000..94fb458
--- /dev/null
+++ b/src/main/groovy/groovy/time/package.html
@@ -0,0 +1,47 @@
+<!--
+
+     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.
+
+-->
+<html>
+  <head>
+    <title>package groovy.time.*</title>
+  </head>
+  <body>
+    <p>
+      Classes for easily manipulating Dates and times.  While 
+      <code>java.util.Date</code> has GDK methods for adding or subtracting days,
+      this is not so useful for different durations of time.  
+      {@link groovy.time.TimeCategory TimeCategory} creates a simple internal DSL
+      for manipulating dates and times in a clean and precise fashion.  
+    </p>
+    <h3>Examples</h3>
+    <pre>
+  use ( TimeCategory ) {
+  	// application on numbers:
+  	println 1.minute.from.now
+  	println 10.days.ago
+  
+  	// application on dates
+  	def someDate = new Date()
+  	println someDate - 3.months 
+  }</pre>
+     
+    @see groovy.time.TimeCategory
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/ASTTest.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/ASTTest.java b/src/main/groovy/groovy/transform/ASTTest.java
new file mode 100644
index 0000000..1cdb480
--- /dev/null
+++ b/src/main/groovy/groovy/transform/ASTTest.java
@@ -0,0 +1,71 @@
+/*
+ *  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.transform;
+
+import org.codehaus.groovy.control.CompilePhase;
+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;
+
+/**
+ * This AST transformation aims at helping in debugging other AST transformations. It provides a basic
+ * infrastructure for performing tests on AST nodes. You can place this annotation on any node which
+ * accepts an annotation (types, methods, annotations, constructors, fields, local variables, packages
+ * or parameters), then use a script which is run against this AST node at a specific phase. For example,
+ * you could test the {@link Field} AST transformation this way:
+ *
+ * <pre class="groovyTestCase">
+ * import groovy.transform.*
+ *
+ * {@code @ASTTest}(value = {
+ *    def owner = node.declaringClass
+ *    assert owner.fields.any { it.name == 'x' }
+ *  })
+ * {@code @Field int x}
+ *
+ * </pre>
+ *
+ * The closure code is executed after the specified phase has completed. If no phase is selected, then the
+ * code is executed after the {@link org.codehaus.groovy.control.CompilePhase#SEMANTIC_ANALYSIS semantic analysis} phase.
+ * The <code>node</code> variable refers to the AST node where the AST test annotation is put. In the previous example,
+ * it means that <i>node</i> refers to the declaration node (int x).
+ *
+ * @author Cedric Champeau
+ * @since 2.0.0
+ *
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD,
+ElementType.LOCAL_VARIABLE, ElementType.PACKAGE, ElementType.PARAMETER})
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.ASTTestTransformation")
+public @interface ASTTest {
+    /**
+     * The compile phase after which the test code should run.
+     */
+    CompilePhase phase() default CompilePhase.SEMANTIC_ANALYSIS;
+
+    /**
+     * A closure which is executed against the annotated node after the specified phase has completed.
+     */
+    Class value();
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/AnnotationCollector.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/AnnotationCollector.java b/src/main/groovy/groovy/transform/AnnotationCollector.java
new file mode 100644
index 0000000..b9f2161
--- /dev/null
+++ b/src/main/groovy/groovy/transform/AnnotationCollector.java
@@ -0,0 +1,199 @@
+/*
+ *  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.transform;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The AnnotationCollector can be used to define aliases for groups of 
+ * annotations. The Alias needs to be a class or annotation annotated with 
+ * AnnotationCollector, otherwise nothing is required. The alias will be 
+ * replaced on the AST level and will never appear in later. Any members of the 
+ * class or annotation will be ignored, but could be used by a custom processor.
+ * Annotation arguments are mapped to the aliased annotations
+ * if existing. Should the default processor not be able to map one of the
+ * arguments and error will be given. Is this not wished or if you want a 
+ * different mapping a custom processor has to be used. There are two ways of 
+ * using the alias. The first way is by providing the annotations as list/array:
+ * <pre class="groovyTestCase">
+ *          import groovy.transform.*
+ *          &#64;AnnotationCollector([ToString, EqualsAndHashCode, Immutable])
+ *          &#64;interface Alias {}
+
+ *          &#64;Alias(excludes=["a"])
+ *          class Foo {
+ *              Integer a, b
+ *          }
+ *          assert Foo.class.annotations.size()==3 
+ *          assert new Foo(1,2).toString() == "Foo(2)"
+ * </pre>
+ * In the example above we have Alias as the alias annotation and an argument
+ * excludes which will be mapped to ToString and EqualsAndHashCode. Immutable 
+ * doesn't have excludes, thus nothing will be done there.<br>
+ * The other way is to add annotations to the alias:
+ * <pre class="groovyTestCase">
+ * import groovy.transform.*
+ * &#64;ToString(excludes=["a"])
+ * &#64;EqualsAndHashCode
+ * &#64;Immutable
+ * &#64;AnnotationCollector
+ * &#64;interface Alias {}
+ *
+ * &#64;Alias
+ * class Foo {
+ *     Integer a, b
+ * }
+ * assert Foo.class.annotations.size()==3
+ * assert new Foo(1,2).toString() == "Foo(2)"
+ * </pre>
+ * In the example above we have again Alias as the alias annotation, but
+ * this time the argument is part of the alias. Instead of mapping excludes to
+ * ToString as well as EqualsAndHashCode, only ToString will have the excludes.
+ * Again the alias can have an argument excludes, which would overwrite the 
+ * excludes given in from the definition and be mapped to ToString as well as
+ * EqualsAndHashCode.
+ * If both ways are combined, then the list overwrites annotation usage.
+ * NOTE: The aliasing does not support aliasing of aliased annotations. 
+ * <p>More examples:</p>
+ * <pre class="groovyTestCase">
+ * //--------------------------------------------------------------------------
+ * import groovy.transform.*
+ * &#64;AnnotationCollector([EqualsAndHashCode, ToString])
+ * &#64;interface Simple {}
+ *
+ *
+ * &#64;Simple
+ * class User {
+ *     String username
+ *     int age
+ * }
+ *
+ * def user = new User(username: 'mrhaki', age: 39)
+ * assert user.toString() == 'User(mrhaki, 39)'
+ *
+ * // We still have 2 annotations:
+ * assert User.class.annotations.size() == 2
+ *
+ *
+ * // We can use the attributes from the 
+ * // grouped annotations.
+ * &#64;Simple(excludes = 'street')
+ * class Address {
+ *     String street, town
+ * }
+ *
+ * def address = new Address(street: 'Evergreen Terrace', town: 'Springfield') 
+ * assert address.toString() == 'Address(Springfield)'
+ * </pre>
+ * <pre class="groovyTestCase">
+ * //--------------------------------------------------------------------------
+ * // Use a custom processor to handle attributes.
+ * import org.codehaus.groovy.transform.*
+ * import org.codehaus.groovy.ast.*
+ * import org.codehaus.groovy.control.*
+ *
+ * class SimpleProcessor extends AnnotationCollectorTransform {
+ *
+ *     public List&lt;AnnotationNode&gt; visit(AnnotationNode collector, 
+ *                                       AnnotationNode aliasAnnotationUsage, 
+ *                                       AnnotatedNode aliasAnnotated, 
+ *                                       SourceUnit source) {
+ *
+ *         // Get attributes and attribute value for dontUse.
+ *         def attributes = aliasAnnotationUsage.getMembers()
+ *         def dontUse = attributes.get('dontUse')
+ *         attributes.remove('dontUse')
+ *
+ *         if (dontUse) {
+ *             // Assign value of dontUse to excludes attributes.
+ *             aliasAnnotationUsage.addMember("excludes", dontUse)
+ *         }
+ *
+ *         super.visit(collector, aliasAnnotationUsage, aliasAnnotated, source)
+ *     }
+ *
+ * }
+ *
+ * new GroovyShell(this.class.classLoader).evaluate '''
+ * import groovy.transform.*
+ *
+ * &#64;AnnotationCollector(value = [EqualsAndHashCode, ToString], processor = 'SimpleProcessor')
+ * &#64;interface Simple {}
+ *
+ *
+ * &#64;Simple(dontUse = 'age')
+ * class User {
+ *     String username
+ *     int age
+ * }
+ *
+ * def user = new User(username: 'mrhaki', age: 39)
+ * assert user.toString() == 'User(mrhaki)'
+ * '''
+ * </pre>
+ * <pre class="groovyTestCase">
+ * //--------------------------------------------------------------------------
+ * // Use AnnotationCollector as last annotation to group the
+ * // previous annotations.
+ * import groovy.transform.*
+ * &#64;EqualsAndHashCode
+ * &#64;ToString
+ * &#64;AnnotationCollector
+ * &#64;interface Simple {}
+ *
+ *
+ * &#64;Simple
+ * class User {
+ *     String username
+ * }
+ *
+ * def user = new User(username: 'mrhaki')
+ * assert user.toString() == 'User(mrhaki)'
+ * </pre>
+ * 
+ * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
+ * @see org.codehaus.groovy.transform.AnnotationCollectorTransform
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+public @interface AnnotationCollector {
+    /**
+     * Processor used for computing custom logic or the list of annotations, or 
+     * both. The default is org.codehaus.groovy.transform.AnnotationCollectorTransform.
+     * Custom processors need to extend that class. 
+     */
+    String processor() default "org.codehaus.groovy.transform.AnnotationCollectorTransform";
+
+    /**
+     * When the collector annotation is replaced, whether to check for duplicates between
+     * the replacement annotations and existing explicit annotations.
+     * If you use a custom processor, it is up to that processor whether it honors or ignores
+     * this parameter. The default processor honors the parameter.
+     */
+    AnnotationCollectorMode mode() default AnnotationCollectorMode.DUPLICATE;
+
+    /**
+     * List of aliased annotations.
+     */
+    Class[] value() default {};
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/AnnotationCollectorMode.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/AnnotationCollectorMode.java b/src/main/groovy/groovy/transform/AnnotationCollectorMode.java
new file mode 100644
index 0000000..308d0c0
--- /dev/null
+++ b/src/main/groovy/groovy/transform/AnnotationCollectorMode.java
@@ -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 groovy.transform;
+
+public enum AnnotationCollectorMode {
+    // TODO should we support @Repeatable from Java 8?
+    /**
+     * Annotations from the annotation collection will always be inserted. After all transforms have been run, it will
+     * be an error if multiple annotations (excluding those with SOURCE retention) exist.
+     */
+    DUPLICATE,
+
+    /**
+     * Annotations from the collector will be added and any existing annotations with the same name will be removed.
+     */
+    PREFER_COLLECTOR,
+
+    /**
+     * Annotations from the collector will be ignored if any existing annotations with the same name are found.
+     */
+    PREFER_EXPLICIT,
+
+    /**
+     * Annotations from the collector will be added and any existing annotations with the same name will be removed but any new parameters found within existing annotations will be merged into the added annotation.
+     */
+    PREFER_COLLECTOR_MERGED,
+
+    /**
+     * Annotations from the collector will be ignored if any existing annotations with the same name are found but any new parameters on the collector annotation will be added to existing annotations.
+     */
+    PREFER_EXPLICIT_MERGED
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/AutoClone.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/AutoClone.java b/src/main/groovy/groovy/transform/AutoClone.java
new file mode 100644
index 0000000..995a187
--- /dev/null
+++ b/src/main/groovy/groovy/transform/AutoClone.java
@@ -0,0 +1,281 @@
+/*
+ *  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.transform;
+
+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;
+
+/**
+ * Class annotation used to assist in the creation of {@code Cloneable} classes.
+ * The {@code @AutoClone} annotation instructs the compiler to execute an
+ * AST transformation which adds a public {@code clone()} method and adds
+ * {@code Cloneable} to the list of interfaces which the class implements.
+ * <p>
+ * Because the JVM doesn't have a one-size fits all cloning strategy, several
+ * customizations exist for the cloning implementation. By default, the {@code clone()}
+ * method will call {@code super.clone()} before calling {@code clone()} on each
+ * {@code Cloneable} property of the class.
+ * <p>
+ * Example usage:
+ * <pre>
+ * import groovy.transform.AutoClone
+ * {@code @AutoClone}
+ * class Person {
+ *   String first, last
+ *   List favItems
+ *   Date since
+ * }
+ * </pre>
+ * Which will create a class equivalent to the following:
+ * <pre>
+ * class Person implements Cloneable {
+ *   ...
+ *   public Person clone() throws CloneNotSupportedException {
+ *     Person result = (Person) super.clone()
+ *     result.favItems = favItems instanceof Cloneable ? (List) favItems.clone() : favItems
+ *     result.since = (Date) since.clone()
+ *     return result
+ *   }
+ *   ...
+ * }
+ * </pre>
+ * Which can be used as follows:
+ * <pre>
+ * def p = new Person(first:'John', last:'Smith', favItems:['ipod', 'shiraz'], since:new Date())
+ * def p2 = p.clone()
+ *
+ * assert p instanceof Cloneable
+ * assert p.favItems instanceof Cloneable
+ * assert p.since instanceof Cloneable
+ * assert !(p.first instanceof Cloneable)
+ *
+ * assert !p.is(p2)
+ * assert !p.favItems.is(p2.favItems)
+ * assert !p.since.is(p2.since)
+ * assert p.first.is(p2.first)
+ * </pre>
+ * In the above example, {@code super.clone()} is called which in this case
+ * calls {@code clone()} from {@code java.lang.Object}. This does a bit-wise
+ * copy of all the properties (references and primitive values). Properties
+ * like {@code first} has type {@code String} which is not {@code Cloneable}
+ * so it is left as the bit-wise copy. Both {@code Date} and {@code ArrayList}
+ * are {@code Cloneable} so the {@code clone()} method on each of those properties
+ * will be called. For the list, a shallow copy is made during its {@code clone()} method.
+ * <p>
+ * If your classes require deep cloning, it is up to you to provide the appropriate
+ * deep cloning logic in the respective {@code clone()} method for your class.
+ * <p>
+ * If one of your properties contains an object that doesn't support cloning
+ * or attempts deep copying of a data structure containing an object that
+ * doesn't support cloning, then a {@code CloneNotSupportedException} may occur
+ * at runtime.
+ * <p>
+ * Another popular cloning strategy is known as the copy constructor pattern.
+ * If any of your fields are {@code final} and {@code Cloneable} you should set
+ * {@code style=COPY_CONSTRUCTOR} which will then use the copy constructor pattern.
+ * Here is an example making use of the copy constructor pattern:
+ * <pre>
+ * import groovy.transform.AutoClone
+ * import static groovy.transform.AutoCloneStyle.*
+ * {@code @AutoClone(style=COPY_CONSTRUCTOR)}
+ * class Person {
+ *   final String first, last
+ *   final Date birthday
+ * }
+ * {@code @AutoClone(style=COPY_CONSTRUCTOR)}
+ * class Customer extends Person {
+ *   final int numPurchases
+ *   final List favItems
+ * }
+ * </pre>
+ * Which will create classes equivalent to the following:
+ * <pre>
+ * class Person implements Cloneable {
+ *   ...
+ *   protected Person(Person other) throws CloneNotSupportedException {
+ *     first = other.first
+ *     last = other.last
+ *     birthday = (Date) other.birthday.clone()
+ *   }
+ *   public Person clone() throws CloneNotSupportedException {
+ *     return new Person(this)
+ *   }
+ *   ...
+ * }
+ * class Customer extends Person {
+ *   ...
+ *   protected Customer(Customer other) throws CloneNotSupportedException {
+ *     super(other)
+ *     numPurchases = other.numPurchases
+ *     favItems = other.favItems instanceof Cloneable ? (List) other.favItems.clone() : other.favItems
+ *   }
+ *   public Customer clone() throws CloneNotSupportedException {
+ *     return new Customer(this)
+ *   }
+ *   ...
+ * }
+ * </pre>
+ * If you use this style on a child class, the parent class must
+ * also have a copy constructor (created using this annotation or by hand).
+ * This approach can be slightly slower than the traditional cloning approach
+ * but the {@code Cloneable} fields of your class can be final. When using the copy constructor style,
+ * you can provide your own custom constructor by hand if you wish. If you do so, it is up to you to
+ * correctly copy, clone or deep clone the properties of your class.
+ * <p>
+ * As a variation of the last two styles, if you set {@code style=SIMPLE}
+ * then the no-arg constructor will be called followed by setting the
+ * individual properties (and/or fields) calling {@code clone()} if the
+ * property/field implements {@code Cloneable}. Here is an example:
+ * <pre>
+ * import groovy.transform.AutoClone
+ * import static groovy.transform.AutoCloneStyle.*
+ * {@code @AutoClone(style=SIMPLE)}
+ * class Person {
+ *   final String first, last
+ *   final Date birthday
+ * }
+ * {@code @AutoClone(style=SIMPLE)}
+ * class Customer {
+ *   final List favItems
+ * }
+ * </pre>
+ * Which will create classes equivalent to the following:
+ * <pre>
+ * class Person implements Cloneable {
+ *   ...
+ *   public Person clone() throws CloneNotSupportedException {
+ *     def result = new Person()
+ *     copyOrCloneMembers(result)
+ *     return result
+ *   }
+ *   protected void copyOrCloneMembers(Person other) {
+ *     other.first = first
+ *     other.last = last
+ *     other.birthday = (Date) birthday.clone()
+ *   }
+ *   ...
+ * }
+ * class Customer extends Person {
+ *   ...
+ *   public Customer clone() throws CloneNotSupportedException {
+ *     def result = new Customer()
+ *     copyOrCloneMembers(result)
+ *     return result
+ *   }
+ *   protected void copyOrCloneMembers(Customer other) {
+ *     super.copyOrCloneMembers(other)
+ *     other.favItems = favItems instanceof Cloneable ? (List) favItems.clone() : favItems
+ *   }
+ *   ...
+ * }
+ * </pre>
+ * You would typically use this style only for base classes where you didn't
+ * want the normal {@code Object} {@code clone()} method to be called and
+ * you would typically need to use the {@code SIMPLE} style for any child classes.
+ * <p>
+ * As a final example, if your class already implements the {@code Serializable}
+ * or {@code Externalizable} interface, you can choose the following cloning style:
+ * <pre>
+ * {@code @AutoClone(style=SERIALIZATION)}
+ * class Person implements Serializable {
+ *   String first, last
+ *   Date birthday
+ * }
+ * </pre>
+ * which outputs a class with the following form:
+ * <pre>
+ * class Person implements Cloneable, Serializable {
+ *   ...
+ *   Person clone() throws CloneNotSupportedException {
+ *     def baos = new ByteArrayOutputStream()
+ *     baos.withObjectOutputStream{ it.writeObject(this) }
+ *     def bais = new ByteArrayInputStream(baos.toByteArray())
+ *     bais.withObjectInputStream(getClass().classLoader){ (Person) it.readObject() }
+ *   }
+ *   ...
+ * }
+ * </pre>
+ * This will output an error if your class doesn't implement one of
+ * {@code Serializable} or {@code Externalizable}, will typically be
+ * significantly slower than the other approaches, also doesn't
+ * allow fields to be final, will take up more memory as even immutable classes
+ * like String will be cloned but does have the advantage that it performs
+ * deep cloning automatically.
+ * <p>
+ * Further references on cloning:
+ * <ul>
+ * <li><a href="http://www.codeguru.com/java/tij/tij0128.shtml">http://www.codeguru.com/java/tij/tij0128.shtml</a>
+ * <li><a href="http://www.artima.com/objectsandjava/webuscript/ClonCollInner1.html">http://www.artima.com/objectsandjava/webuscript/ClonCollInner1.html</a>
+ * <li><a href="http://courses.dce.harvard.edu/~cscie160/JDCTipsCloning">http://courses.dce.harvard.edu/~cscie160/JDCTipsCloning</a>
+ * <li><a href="http://www.agiledeveloper.com/articles/cloning072002.htm">http://www.agiledeveloper.com/articles/cloning072002.htm</a>
+ * </ul>
+ *
+ * @author Paul King
+ * @see AutoCloneStyle
+ * @see ExternalizeMethods
+ * @since 1.8.0
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.AutoCloneASTTransformation")
+public @interface AutoClone {
+    /**
+     * Comma separated list of property (and/or field) names to exclude from cloning.
+     * For convenience, a String with comma separated names can be used in addition
+     * to an array (using Groovy's literal list notation) of String values.
+     * <p>
+     * NOTE: When using the {@code CLONE} style, property (and/or field) copying might occur as part of
+     * calling {@code super.clone()} which will ignore this list. You can then use this list to
+     * streamline the provided {@code clone()} implementation by selecting which Cloneable properties
+     * (and/or fields) will have a subsequent call to their {@code clone()} method. If you have
+     * immutable properties (and/or fields) this can be useful as the extra {@code clone()} will
+     * not be necessary and cloning will be more efficient.
+     * <p>
+     * NOTE: This doesn't affect property (and/or field) copying that might occur as part
+     * of serialization when using the {@code SERIALIZATION} style, i.e. this flag is ignored;
+     * instead adjust your serialization code to include or exclude the desired
+     * properties (and/or fields) which should carry over during cloning.
+     */
+    String[] excludes() default {};
+
+    /**
+     * Include fields as well as properties when cloning.
+     * <p>
+     * NOTE: When using the {@code CLONE} style, field copying might occur as part of
+     * calling {@code super.clone()} and might be all you require; if you turn on
+     * this flag, the provided {@code clone()} implementation will also
+     * subsequently call {@code clone()} for each {@code Cloneable} field which can be
+     * useful if you have mutable fields.
+     * <p>
+     * NOTE: This doesn't affect field copying that might occur as part of
+     * serialization when using the {@code SERIALIZATION} style, i.e. this flag is ignored;
+     * instead adjust your serialization code to include or exclude your fields.
+     */
+    boolean includeFields() default false;
+
+    /**
+     * Style to use when cloning.
+     */
+    AutoCloneStyle style() default AutoCloneStyle.CLONE;
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/AutoCloneStyle.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/AutoCloneStyle.java b/src/main/groovy/groovy/transform/AutoCloneStyle.java
new file mode 100644
index 0000000..81b0ebd
--- /dev/null
+++ b/src/main/groovy/groovy/transform/AutoCloneStyle.java
@@ -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 groovy.transform;
+
+/**
+ * Intended style to use for cloning when using the {@code @}AutoClone annotation.
+ *
+ * @author Paul King
+ * @since 1.8.0
+ * @see groovy.transform.AutoClone
+ */
+public enum AutoCloneStyle {
+    /**
+     * Uses only cloning.
+     */
+    CLONE,
+
+    /**
+     * Uses the no-arg constructor followed by property/field copying/cloning.
+     */
+    SIMPLE,
+
+    /**
+     * Uses the copy constructor pattern.
+     */
+    COPY_CONSTRUCTOR,
+
+    /**
+     * Uses serialization to clone.
+     */
+    SERIALIZATION
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/AutoExternalize.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/transform/AutoExternalize.groovy b/src/main/groovy/groovy/transform/AutoExternalize.groovy
new file mode 100644
index 0000000..d1356b9
--- /dev/null
+++ b/src/main/groovy/groovy/transform/AutoExternalize.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 groovy.transform
+
+/**
+ * Class annotation used to assist in the creation of {@code Externalizable} classes.
+ * The {@code @AutoExternalize} annotation instructs the compiler to execute an
+ * AST transformation which adds {@code writeExternal()} and {@code readExternal()} methods
+ * to a class and adds {@code Externalizable} to the interfaces which the class implements.
+ * The {@code writeExternal()} method writes each property (or field) for the class while the
+ * {@code readExternal()} method will read each one back in the same order. Properties or fields
+ * marked as {@code transient} are ignored.
+ * <p>
+ * Example usage:
+ * <pre>
+ * import groovy.transform.*
+ * {@code @AutoExternalize}
+ * class Person {
+ *   String first, last
+ *   List favItems
+ *   Date since
+ * }
+ * </pre>
+ * Which will create a class of the following form:
+ * <pre>
+ * class Person implements Externalizable {
+ *   ...
+ *   public void writeExternal(ObjectOutput out) throws IOException {
+ *     out.writeObject(first)
+ *     out.writeObject(last)
+ *     out.writeObject(favItems)
+ *     out.writeObject(since)
+ *   }
+ *
+ *   public void readExternal(ObjectInput oin) {
+ *     first = oin.readObject()
+ *     last = oin.readObject()
+ *     favItems = oin.readObject()
+ *     since = oin.readObject()
+ *   }
+ *   ...
+ * }
+ * </pre>
+ * <p>
+ * The {@code @AutoExternalize} transform is implemented as a combination of the {@code @ExternalizeMethods} and {@code @ExternalizeVerifier} transforms.
+ *
+ * @since 1.8.0
+ */
+@AnnotationCollector([ExternalizeMethods, ExternalizeVerifier])
+@interface AutoExternalize { }


Mime
View raw message