http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/BytecodeInterface8.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/BytecodeInterface8.java b/src/main/java/org/codehaus/groovy/runtime/BytecodeInterface8.java new file mode 100644 index 0000000..79fae05 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/BytecodeInterface8.java @@ -0,0 +1,377 @@ +/* + * 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.runtime; + +import org.codehaus.groovy.runtime.metaclass.DefaultMetaClassInfo; + +/** + * This class contains methods special to optimizations used directly from bytecode in Groovy 1.8 + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class BytecodeInterface8 { + + public static boolean disabledStandardMetaClass() { + return DefaultMetaClassInfo.disabledStandardMetaClass(); + } + + // ------------------ int ------------------ + + /** + * @return true if integer has its default MetaClass + */ + public static boolean isOrigInt(){ + return DefaultMetaClassInfo.isOrigInt(); + } + + // ------------------ int[] ------------------ + + /** + * @return true if integer array has its default MetaClass + */ + public static boolean isOrigIntArray(){ + return DefaultMetaClassInfo.isOrigIntArray(); + } + + + /** + * get value from int[] using normalized index + */ + public static int intArrayGet(int[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from int[] using normalized index + */ + public static void intArraySet(int[] a, int i, int v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ byte ------------------ + + /** + * @return true if byte has its default MetaClass + */ + public static boolean isOrigB(){ + return DefaultMetaClassInfo.isOrigByte(); + } + + // ------------------ byte[] ------------------ + + /** + * @return true if byte array has its default MetaClass + */ + public static boolean isOrigBArray(){ + return false; + } + + + /** + * get value from byte[] using normalized index + */ + public static byte bArrayGet(byte[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from byte[] using normalized index + */ + public static void bArraySet(byte[] a, int i, byte v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ short ------------------ + + /** + * @return true if short has its default MetaClass + */ + public static boolean isOrigS(){ + return DefaultMetaClassInfo.isOrigShort(); + } + + // ------------------ short[] ------------------ + + /** + * @return true if short array has its default MetaClass + */ + public static boolean isOrigSArray(){ + return false; + } + + + /** + * get value from short[] using normalized index + */ + public static short sArrayGet(short[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from short[] using normalized index + */ + public static void sArraySet(short[] a, int i, short v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ char ------------------ + + /** + * @return true if char has its default MetaClass + */ + public static boolean isOrigC(){ + return DefaultMetaClassInfo.isOrigChar(); + } + + // ------------------ char[] ------------------ + + /** + * @return true if char array has its default MetaClass + */ + public static boolean isOrigCArray(){ + return false; + } + + + /** + * get value from char[] using normalized index + */ + public static char cArrayGet(char[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from char[] using normalized index + */ + public static void cArraySet(char[] a, int i, char v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ long ------------------ + + /** + * @return true if long has its default MetaClass + */ + public static boolean isOrigL(){ + return DefaultMetaClassInfo.isOrigLong(); + } + + // ------------------ long[] ------------------ + + /** + * @return true if long array has its default MetaClass + */ + public static boolean isOrigLArray(){ + return false; + } + + + /** + * get value from long[] using normalized index + */ + public static long lArrayGet(long[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from long[] using normalized index + */ + public static void lArraySet(long[] a, int i, long v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ boolean ------------------ + + /** + * @return true if boolean has its default MetaClass + */ + public static boolean isOrigZ(){ + return DefaultMetaClassInfo.isOrigBool(); + } + + // ------------------ boolean[] ------------------ + + /** + * @return true if boolean array has its default MetaClass + */ + public static boolean isOrigZArray(){ + return false; + } + + /** + * get value from boolean[] using normalized index + */ + public static boolean zArrayGet(boolean[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from boolean[] using normalized index + */ + public static void zArraySet(boolean[] a, int i, boolean v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ float ------------------ + + /** + * @return true if float has its default MetaClass + */ + public static boolean isOrigF(){ + return DefaultMetaClassInfo.isOrigFloat(); + } + + // ------------------ float[] ------------------ + + /** + * @return true if float array has its default MetaClass + */ + public static boolean isOrigFArray(){ + return false; + } + + /** + * get value from float[] using normalized index + */ + public static float fArrayGet(float[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from float[] using normalized index + */ + public static void fArraySet(float[] a, int i, float v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ double ------------------ + + /** + * @return true if double has its default MetaClass + */ + public static boolean isOrigD(){ + return DefaultMetaClassInfo.isOrigDouble(); + } + + // ------------------ double[] ------------------ + + /** + * @return true if double array has its default MetaClass + */ + public static boolean isOrigDArray(){ + return false; + } + + /** + * get value from double[] using normalized index + */ + public static double dArrayGet(double[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from double[] using normalized index + */ + public static void dArraySet(double[] a, int i, double v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } + + // ------------------ Object[] ------------------ + public static Object objectArrayGet(Object[] a, int i) { + try { + return a[i]; + } catch (Throwable t) { + return a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]; + } + } + + /** + * set value from double[] using normalized index + */ + public static void objectArraySet(Object[] a, int i, Object v) { + try { + a[i]=v; + } catch (Throwable t) { + a[DefaultGroovyMethodsSupport.normaliseIndex(i,a.length)]=v; + } + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/ClassExtender.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ClassExtender.java b/src/main/java/org/codehaus/groovy/runtime/ClassExtender.java new file mode 100644 index 0000000..6e5d371 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/ClassExtender.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.runtime; + +import groovy.lang.Closure; + +import java.util.HashMap; +import java.util.Map; + + +/** + * A helper class used by the runtime to allow Groovy classes to be extended at runtime + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class ClassExtender { + private Map variables; + private Map methods; + + public synchronized Object get(String name) { + if (variables != null) { + return variables.get(name); + } + return null; + } + + public synchronized void set(String name, Object value) { + if (variables == null) { + variables = createMap(); + } + variables.put(name, value); + } + + public synchronized void remove(String name) { + if (variables != null) { + variables.remove(name); + } + } + + public void call(String name, Object params) { + Closure closure = null; + synchronized (this) { + if (methods != null) { + closure = (Closure) methods.get(name); + } + } + if (closure != null) { + closure.call(params); + } + /* + else { + throw DoesNotUnderstandException(); + } + */ + } + + public synchronized void addMethod(String name, Closure closure) { + if (methods == null) { + methods = createMap(); + } + methods.put(name, methods); + } + + public synchronized void removeMethod(String name) { + if (methods != null) { + methods.remove(name); + } + } + + protected Map createMap() { + return new HashMap(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/ComposedClosure.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ComposedClosure.java b/src/main/java/org/codehaus/groovy/runtime/ComposedClosure.java new file mode 100644 index 0000000..63e0406 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/ComposedClosure.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.runtime; + +import groovy.lang.Closure; + +import java.util.List; + +/** + * A wrapper for Closure to support composition. + * Normally used only internally through the <code>rightShift()</code> and + * <code>leftShift()</code> methods on <code>Closure</code>. + * <p> + * Typical usages: + * <pre class="groovyTestCase"> + * def twice = { a -> a * 2 } + * def inc = { b -> b + 1 } + * def f = { x -> twice(inc(x)) } // longhand + * def g = inc >> twice + * def h = twice << inc + * assert f(10) == 22 + * assert g(10) == 22 + * assert h(10) == 22 + * + * def s2c = { it.chars[0] } + * def p = Integer.&toHexString >> s2c >> Character.&toUpperCase + * assert p(15) == 'F' + * + * def multiply = { a, b -> a * b } + * def identity = { a -> [a, a] } + * def sq = identity >> multiply + * assert (1..5).collect{ sq(it) } == [1, 4, 9, 16, 25] + * + * def add3 = { a, b, c -> a + b + c } + * def add2plus10 = add3.curry(10) + * def multBoth = { a, b, c -> [a*c, b*c] } + * def twiceBoth = multBoth.rcurry(2) + * def twiceBothPlus10 = twiceBoth >> add2plus10 + * assert twiceBothPlus10(5, 10) == 40 + * </pre> + * + * @author Paul King + */ +public final class ComposedClosure<V> extends Closure<V> { + + private final Closure first; + private final Closure<V> second; + + public ComposedClosure(Closure first, Closure<V> second) { + super(first.clone()); + this.first = (Closure) getOwner(); + this.second = (Closure<V>) second.clone(); + maximumNumberOfParameters = first.getMaximumNumberOfParameters(); + } + + public void setDelegate(Object delegate) { + ((Closure) getOwner()).setDelegate(delegate); + second.setDelegate(delegate); + } + + public Object getDelegate() { + return ((Closure) getOwner()).getDelegate(); + } + + public void setResolveStrategy(int resolveStrategy) { + ((Closure) getOwner()).setResolveStrategy(resolveStrategy); + second.setResolveStrategy(resolveStrategy); + } + + public int getResolveStrategy() { + return ((Closure) getOwner()).getResolveStrategy(); + } + + public Object clone() { + return new ComposedClosure<V>(first, second); + } + + public Class[] getParameterTypes() { + return first.getParameterTypes(); + } + + public Object doCall(Object... args) { + return call(args); + } + + @Override + public V call(Object... args) { + Object temp = first.call(args); + if (temp instanceof List && second.getParameterTypes().length > 1) temp = ((List) temp).toArray(); + return temp instanceof Object[] ? second.call((Object[]) temp) : second.call(temp); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/ConversionHandler.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ConversionHandler.java b/src/main/java/org/codehaus/groovy/runtime/ConversionHandler.java new file mode 100644 index 0000000..8bf7c69 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/ConversionHandler.java @@ -0,0 +1,224 @@ +/* + * 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.runtime; + +import groovy.lang.GroovyObject; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovySystem; +import groovy.lang.MetaClass; +import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; +import org.codehaus.groovy.vmplugin.VMPlugin; +import org.codehaus.groovy.vmplugin.VMPluginFactory; + +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class is a general adapter to map a call to a Java interface + * to a given delegate. + * + * @author Ben Yu + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + */ +public abstract class ConversionHandler implements InvocationHandler, Serializable { + private final Object delegate; + private static final long serialVersionUID = 1162833717190835227L; + private final ConcurrentHashMap<Method, Object> handleCache; + { + if (VMPluginFactory.getPlugin().getVersion() >= 7) { + handleCache = new ConcurrentHashMap<Method, Object>(16, 0.9f, 2); + } else { + handleCache = null; + } + } + + private MetaClass metaClass; + + /** + * Creates a ConversionHandler with an delegate. + * + * @param delegate the delegate + * @throws IllegalArgumentException if the given delegate is null + */ + public ConversionHandler(Object delegate) { + if (delegate == null) { + throw new IllegalArgumentException("delegate must not be null"); + } + this.delegate = delegate; + } + + /** + * Returns the delegate. + * + * @return the delegate + */ + public Object getDelegate() { + return delegate; + } + + /** + * This method is a default implementation for the invoke method given in + * InvocationHandler. Any call to a method with a declaring class that is + * not Object, excluding toString() and default methods is redirected to invokeCustom. + * <p> + * Methods like equals and hashcode are called on the class itself instead + * of the delegate because they are considered fundamental methods that should + * not be overwritten. The toString() method gets special treatment as it is + * deemed to be a method that you might wish to override when called from Groovy. + * Interface default methods from Java 8 on the other hand are considered being + * default implementations you don't normally want to change. So they are called + * directly too + * </p><p> + * In many scenarios, it is better to overwrite the invokeCustom method where + * the core Object related methods are filtered out. + *</p> + * @param proxy the proxy + * @param method the method + * @param args the arguments + * @return the result of the invocation by method or delegate + * @throws Throwable if caused by the delegate or the method + * @see #invokeCustom(Object, Method, Object[]) + * @see InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (handleCache != null && isDefaultMethod(method)) { + VMPlugin plugin = VMPluginFactory.getPlugin(); + Object handle = handleCache.get(method); + if (handle == null) { + handle = plugin.getInvokeSpecialHandle(method, proxy); + handleCache.put(method, handle); + } + return plugin.invokeHandle(handle, args); + } + + if (!checkMethod(method)) { + try { + if (method.getDeclaringClass() == GroovyObject.class) { + if ("getMetaClass".equals(method.getName())) { + return getMetaClass(proxy); + } else if ("setMetaClass".equals(method.getName())) { + return setMetaClass((MetaClass) args[0]); + } + } + return invokeCustom(proxy, method, args); + } catch (GroovyRuntimeException gre) { + throw ScriptBytecodeAdapter.unwrap(gre); + } + } + + try { + return method.invoke(this, args); + } catch (InvocationTargetException ite) { + throw ite.getTargetException(); + } + } + + protected boolean isDefaultMethod(Method method) { + return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == + Modifier.PUBLIC) && method.getDeclaringClass().isInterface(); + } + + protected boolean checkMethod(Method method) { + return isCoreObjectMethod(method); + } + + /** + * This method is called for all Methods not defined on Object. + * The delegate should be called here. + * + * @param proxy the proxy + * @param method the method + * @param args the arguments + * @return the result of the invocation of the delegate + * @throws Throwable any exception causes by the delegate + * @see #invoke(Object, Method, Object[]) + * @see InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) + */ + public abstract Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable; + + /** + * Indicates whether some other object is "equal to" this one. + * The delegate is used if the class of the parameter and the + * current class are equal. In other cases the method will return + * false. The exact class is here used, if inheritance is needed, + * this method must be overwritten. + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj instanceof Proxy) { + obj = Proxy.getInvocationHandler(obj); + } + + if (obj instanceof ConversionHandler) { + return (((ConversionHandler) obj).getDelegate()).equals(delegate); + } else { + return false; + } + } + + /** + * Returns a hash code value for the delegate. + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return delegate.hashCode(); + } + + /** + * Returns a String version of the delegate. + * + * @see java.lang.Object#toString() + */ + public String toString() { + return delegate.toString(); + } + + /** + * Checks whether a method is a core method from java.lang.Object. + * Such methods often receive special treatment because they are + * deemed fundamental enough to not be tampered with. + * + * @param method the method to check + * @return true if the method is deemed to be a core method + */ + public static boolean isCoreObjectMethod(Method method) { + return Object.class.equals(method.getDeclaringClass()); + } + + private MetaClass setMetaClass(MetaClass mc) { + metaClass = mc; + return mc; + } + + private MetaClass getMetaClass(Object proxy) { + MetaClass mc = metaClass; + if (mc == null) { + mc = ((MetaClassRegistryImpl) GroovySystem.getMetaClassRegistry()).getMetaClass(proxy); + metaClass = mc; + } + return mc; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/ConvertedClosure.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ConvertedClosure.java b/src/main/java/org/codehaus/groovy/runtime/ConvertedClosure.java new file mode 100644 index 0000000..dfc7ea0 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/ConvertedClosure.java @@ -0,0 +1,58 @@ +/* + * 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.runtime; + +import groovy.lang.Closure; + +import java.io.Serializable; +import java.lang.reflect.Method; + +/** + * This class is a general adapter to adapt a closure to any Java interface. + * <p> + * @author Ben Yu + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + * Jul 27, 2006 3:50:51 PM + */ +public class ConvertedClosure extends ConversionHandler implements Serializable { + private final String methodName; + private static final long serialVersionUID = 1162833713450835227L; + + /** + * to create a ConvertedClosure object. + * @param closure the closure object. + */ + public ConvertedClosure(Closure closure, String method) { + super(closure); + this.methodName = method; + } + + public ConvertedClosure(Closure closure) { + this(closure,null); + } + + @Override + public Object invokeCustom(Object proxy, Method method, Object[] args) + throws Throwable { + if (methodName!=null && !methodName.equals(method.getName())) return null; + return ((Closure) getDelegate()).call(args); + } + +} + http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/ConvertedMap.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/ConvertedMap.java b/src/main/java/org/codehaus/groovy/runtime/ConvertedMap.java new file mode 100644 index 0000000..393d55e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/ConvertedMap.java @@ -0,0 +1,80 @@ +/* + * 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.runtime; + +import groovy.lang.Closure; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * This class is a general adapter to adapt a map of closures to + * any Java interface. + * + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + */ +public class ConvertedMap extends ConversionHandler { + + /** + * to create a ConvertedMap object. + * + * @param closures the map of closures + */ + protected ConvertedMap(Map closures) { + super(closures); + } + + @Override + public Object invokeCustom(Object proxy, Method method, Object[] args) + throws Throwable { + Map m = (Map) getDelegate(); + Closure cl = (Closure) m.get(method.getName()); + if(cl == null && "toString".equals(method.getName())) { + return m.toString(); + } + if (cl == null) { + throw new UnsupportedOperationException(); + } + return cl.call(args); + } + + @Override + public String toString() { + return DefaultGroovyMethods.toString(getDelegate()); + } + + @Override + protected boolean checkMethod(Method method) { + return isCoreObjectMethod(method); + } + + /** + * Checks whether a method is a core method from java.lang.Object. + * Such methods often receive special treatment because they are + * deemed fundamental enough to not be tampered with. + * call toString() is an exception to allow overriding toString() by a closure specified in the map + * + * @param method the method to check + * @return true if the method is deemed to be a core method + */ + public static boolean isCoreObjectMethod(Method method) { + return ConversionHandler.isCoreObjectMethod(method) && !"toString".equals(method.getName()); + } +} + http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java b/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java new file mode 100644 index 0000000..cabe230 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java @@ -0,0 +1,192 @@ +/* + * 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.runtime; + +import groovy.lang.Closure; + +/** + * A wrapper for Closure to support currying. + * Normally used only internally through the <code>curry()</code>, <code>rcurry()</code> or + * <code>ncurry()</code> methods on <code>Closure</code>. + * Typical usages: + * <pre class="groovyTestCase"> + * // normal usage + * def unitAdder = { first, second, unit -> "${first + second} $unit" } + * assert unitAdder(10, 15, "minutes") == "25 minutes" + * assert unitAdder.curry(60)(15, "minutes") == "75 minutes" + * def minuteAdder = unitAdder.rcurry("minutes") + * assert minuteAdder(15, 60) == "75 minutes" + * + * // explicit creation + * import org.codehaus.groovy.runtime.CurriedClosure + * assert new CurriedClosure(unitAdder, 45)(15, "minutes") == "60 minutes" + * assert new CurriedClosure(unitAdder, "six", "ty")("minutes") == "sixty minutes" + * </pre> + * + * Notes: + * <ul> + * <li>Caters for Groovy's lazy (rcurry) and eager (ncurry) calculation of argument position</li> + * </ul> + */ +public final class CurriedClosure<V> extends Closure<V> { + + private final Object[] curriedParams; + private final int minParamsExpected; + private int index; + private Class varargType = null; + + /** + * Creates the curried closure. + * + * @param index the position where the parameters should be injected (-ve for lazy) + * @param uncurriedClosure the closure to be called after the curried parameters are injected + * @param arguments the supplied parameters + */ + public CurriedClosure(int index, Closure<V> uncurriedClosure, Object... arguments) { + super(uncurriedClosure.clone()); + curriedParams = arguments; + this.index = index; + final int origMaxLen = uncurriedClosure.getMaximumNumberOfParameters(); + maximumNumberOfParameters = origMaxLen - arguments.length; + Class[] classes = uncurriedClosure.getParameterTypes(); + Class lastType = classes.length == 0 ? null : classes[classes.length-1]; + if (lastType != null && lastType.isArray()) { + varargType = lastType; + } + + if (!isVararg()) { + // perform some early param checking for non-vararg case + if (index < 0) { + // normalise + this.index += origMaxLen; + minParamsExpected = 0; + } else { + minParamsExpected = index + arguments.length; + } + if (maximumNumberOfParameters < 0) { + throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + origMaxLen + " parameters."); + } + if (index < 0) { + if (index < -origMaxLen || index > -arguments.length) + throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range " + + (-origMaxLen) + ".." + (-arguments.length) + " but found " + index); + } else if (index > maximumNumberOfParameters) { + throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." + + maximumNumberOfParameters + " but found " + index); + } + } else { + minParamsExpected = 0; + } + } + + public CurriedClosure(Closure<V> uncurriedClosure, Object... arguments) { + this(0, uncurriedClosure, arguments); + } + + public Object[] getUncurriedArguments(Object... arguments) { + if (isVararg()) { + int normalizedIndex = index < 0 ? index + arguments.length + curriedParams.length : index; + if (normalizedIndex < 0 || normalizedIndex > arguments.length) { + throw new IllegalArgumentException("When currying expected index range between " + + (-arguments.length - curriedParams.length) + ".." + (arguments.length + curriedParams.length) + " but found " + index); + } + return createNewCurriedParams(normalizedIndex, arguments); + } + if (curriedParams.length + arguments.length < minParamsExpected) { + throw new IllegalArgumentException("When currying expected at least " + index + " argument(s) to be supplied before known curried arguments but found " + arguments.length); + } + int newIndex = Math.min(index, curriedParams.length + arguments.length - 1); + // rcurried arguments are done lazily to allow normal method selection between overloaded alternatives + newIndex = Math.min(newIndex, arguments.length); + return createNewCurriedParams(newIndex, arguments); + } + + private Object[] createNewCurriedParams(int normalizedIndex, Object[] arguments) { + Object[] newCurriedParams = new Object[curriedParams.length + arguments.length]; + System.arraycopy(arguments, 0, newCurriedParams, 0, normalizedIndex); + System.arraycopy(curriedParams, 0, newCurriedParams, normalizedIndex, curriedParams.length); + if (arguments.length - normalizedIndex > 0) + System.arraycopy(arguments, normalizedIndex, newCurriedParams, curriedParams.length + normalizedIndex, arguments.length - normalizedIndex); + return newCurriedParams; + } + + public void setDelegate(Object delegate) { + ((Closure) getOwner()).setDelegate(delegate); + } + + public Object getDelegate() { + return ((Closure) getOwner()).getDelegate(); + } + + public void setResolveStrategy(int resolveStrategy) { + ((Closure) getOwner()).setResolveStrategy(resolveStrategy); + } + + public int getResolveStrategy() { + return ((Closure) getOwner()).getResolveStrategy(); + } + + @SuppressWarnings("unchecked") + public Object clone() { + Closure<V> uncurriedClosure = (Closure<V>) ((Closure) getOwner()).clone(); + return new CurriedClosure<V>(index, uncurriedClosure, curriedParams); + } + + public Class[] getParameterTypes() { + Class[] oldParams = ((Closure) getOwner()).getParameterTypes(); + int extraParams = 0; + int gobbledParams = curriedParams.length; + if (isVararg()) { + int numNonVarargs = oldParams.length - 1; + if (index < 0) { + int absIndex = -index; + // do -ve indexes based on actual args, so can't accurately calculate type here + // so work out minimal type params and vararg on end will allow for other possibilities + if (absIndex > numNonVarargs) gobbledParams = numNonVarargs; + int newNumNonVarargs = numNonVarargs - gobbledParams; + if (absIndex - curriedParams.length > newNumNonVarargs) extraParams = absIndex - curriedParams.length - newNumNonVarargs; + int keptParams = Math.max(numNonVarargs - absIndex, 0); + Class[] newParams = new Class[keptParams + newNumNonVarargs + extraParams + 1]; + System.arraycopy(oldParams, 0, newParams, 0, keptParams); + for (int i = 0; i < newNumNonVarargs; i++) newParams[keptParams + i] = Object.class; + for (int i = 0; i < extraParams; i++) newParams[keptParams + newNumNonVarargs + i] = varargType.getComponentType(); + newParams[newParams.length - 1] = varargType; + return newParams; + } + int leadingKept = Math.min(index, numNonVarargs); + int trailingKept = Math.max(numNonVarargs - leadingKept - curriedParams.length, 0); + if (index > leadingKept) extraParams = index - leadingKept; + Class[] newParams = new Class[leadingKept + trailingKept + extraParams + 1]; + System.arraycopy(oldParams, 0, newParams, 0, leadingKept); + if (trailingKept > 0) System.arraycopy(oldParams, leadingKept + curriedParams.length, newParams, leadingKept, trailingKept); + for (int i = 0; i < extraParams; i++) newParams[leadingKept + trailingKept + i] = varargType.getComponentType(); + newParams[newParams.length - 1] = varargType; + return newParams; + } + Class[] newParams = new Class[oldParams.length - gobbledParams + extraParams]; + System.arraycopy(oldParams, 0, newParams, 0, index); + if (newParams.length - index > 0) + System.arraycopy(oldParams, curriedParams.length + index, newParams, index, newParams.length - index); + return newParams; + } + + private boolean isVararg() { + return varargType != null; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java new file mode 100644 index 0000000..1c30802 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java @@ -0,0 +1,775 @@ +/* + * 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.runtime; + +import groovy.lang.Closure; +import groovy.lang.GroovyRuntimeException; + +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +/** + * This class defines new groovy methods which appear on normal JDK + * Date and Calendar classes inside the Groovy environment. + */ +public class DateGroovyMethods extends DefaultGroovyMethodsSupport { + + /** + * Support the subscript operator for a Date. + * + * @param self a Date + * @param field a Calendar field, e.g. MONTH + * @return the value for the given field, e.g. FEBRUARY + * @see java.util.Calendar + * @since 1.5.5 + */ + public static int getAt(Date self, int field) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + return cal.get(field); + } + + /** + * Convert a Date to a Calendar. + * + * @param self a Date + * @return a Calendar corresponding to the given Date + * @since 1.7.6 + */ + public static Calendar toCalendar(Date self) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + return cal; + } + + /** + * Support the subscript operator for a Calendar. + * + * @param self a Calendar + * @param field a Calendar field, e.g. MONTH + * @return the value for the given field, e.g. FEBRUARY + * @see java.util.Calendar + * @since 1.7.3 + */ + public static int getAt(Calendar self, int field) { + return self.get(field); + } + + /** + * Support the subscript operator for mutating a Calendar. + * Example usage: + * <pre> + * import static java.util.Calendar.* + * def cal = Calendar.instance + * cal[DAY_OF_WEEK] = MONDAY + * cal[MONTH] = MARCH + * println cal.time // A Monday in March + * </pre> + * + * @param self A Calendar + * @param field A Calendar field, e.g. MONTH + * @param value The value for the given field, e.g. FEBRUARY + * @see java.util.Calendar#set(int, int) + * @since 1.7.3 + */ + public static void putAt(Calendar self, int field, int value) { + self.set(field, value); + } + + /** + * Support the subscript operator for mutating a Date. + * + * @param self A Date + * @param field A Calendar field, e.g. MONTH + * @param value The value for the given field, e.g. FEBRUARY + * @see #putAt(java.util.Calendar, int, int) + * @see java.util.Calendar#set(int, int) + * @since 1.7.3 + */ + public static void putAt(Date self, int field, int value) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + putAt(cal, field, value); + self.setTime(cal.getTimeInMillis()); + } + + /** + * Support mutating a Calendar with a Map. + * <p> + * The map values are the normal values provided as the + * second parameter to <code>java.util.Calendar#set(int, int)</code>. + * The keys can either be the normal fields values provided as + * the first parameter to that method or one of the following Strings: + * <table border="1" cellpadding="4"> + * <caption>Calendar index values</caption> + * <tr><td>year</td><td>Calendar.YEAR</td></tr> + * <tr><td>month</td><td>Calendar.MONTH</td></tr> + * <tr><td>date</td><td>Calendar.DATE</td></tr> + * <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr> + * <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr> + * <tr><td>minute</td><td>Calendar.MINUTE</td></tr> + * <tr><td>second</td><td>Calendar.SECOND</td></tr> + * </table> + * Example usage: + * <pre> + * import static java.util.Calendar.* + * def cal = Calendar.instance + * def m = [:] + * m[YEAR] = 2010 + * m[MONTH] = DECEMBER + * m[DATE] = 25 + * cal.set(m) + * println cal.time // Christmas 2010 + * + * cal.set(year:2011, month:DECEMBER, date:25) + * println cal.time // Christmas 2010 + * </pre> + * + * @param self A Calendar + * @param updates A Map of Calendar keys and values + * @see java.util.Calendar#set(int, int) + * @see java.util.Calendar#set(int, int, int, int, int, int) + * @since 1.7.3 + */ + public static void set(Calendar self, Map<Object, Integer> updates) { + for (Map.Entry<Object, Integer> entry : updates.entrySet()) { + Object key = entry.getKey(); + if (key instanceof String) key = CAL_MAP.get(key); + if (key instanceof Integer) self.set((Integer) key, entry.getValue()); + } + } + + /** + * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy. + * + * @see #copyWith(java.util.Calendar, java.util.Map) + * @since 1.7.3 + */ + public static Calendar updated(Calendar self, Map<Object, Integer> updates) { + Calendar result = (Calendar) self.clone(); + set(result, updates); + return result; + } + + /** + * Support creating a new Date having similar properties to + * an existing Date (which remains unaltered) but with + * some fields updated according to a Map of changes. + * <p> + * Example usage: + * <pre> + * import static java.util.Calendar.YEAR + * def now = Calendar.instance + * def nextYear = now[YEAR] + 1 + * def oneYearFromNow = now.copyWith(year: nextYear) + * println now.time + * println oneYearFromNow.time + * </pre> + * + * @param self A Calendar + * @param updates A Map of Calendar keys and values + * @return The newly created Calendar + * @see java.util.Calendar#set(int, int) + * @see java.util.Calendar#set(int, int, int, int, int, int) + * @see #set(java.util.Calendar, java.util.Map) + * @since 2.2.0 + */ + public static Calendar copyWith(Calendar self, Map<Object, Integer> updates) { + Calendar result = (Calendar) self.clone(); + set(result, updates); + return result; + } + + /** + * Support mutating a Date with a Map. + * <p> + * The map values are the normal values provided as the + * second parameter to <code>java.util.Calendar#set(int, int)</code>. + * The keys can either be the normal fields values provided as + * the first parameter to that method or one of the following Strings: + * <table border="1" cellpadding="4"> + * <caption>Calendar index values</caption> + * <tr><td>year</td><td>Calendar.YEAR</td></tr> + * <tr><td>month</td><td>Calendar.MONTH</td></tr> + * <tr><td>date</td><td>Calendar.DATE</td></tr> + * <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr> + * <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr> + * <tr><td>minute</td><td>Calendar.MINUTE</td></tr> + * <tr><td>second</td><td>Calendar.SECOND</td></tr> + * </table> + * Example usage: + * <pre> + * import static java.util.Calendar.YEAR + * def date = new Date() + * def nextYear = date[YEAR] + 1 + * date.set(year: nextYear) + * println date + * </pre> + * + * @param self A Date + * @param updates A Map of Calendar keys and values + * @see java.util.Calendar#set(int, int) + * @see #set(java.util.Calendar, java.util.Map) + * @since 1.7.3 + */ + public static void set(Date self, Map<Object, Integer> updates) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + set(cal, updates); + self.setTime(cal.getTimeInMillis()); + } + + /** + * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy. + * + * @see #copyWith(java.util.Date, java.util.Map) + * @since 1.7.3 + */ + public static Date updated(Date self, Map<Object, Integer> updates) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + set(cal, updates); + return cal.getTime(); + } + + /** + * Support creating a new Date having similar properties to + * an existing Date (which remains unaltered) but with + * some fields updated according to a Map of changes. + * <p> + * Example usage: + * <pre> + * import static java.util.Calendar.YEAR + * def today = new Date() + * def nextYear = today[YEAR] + 1 + * def oneYearFromNow = today.copyWith(year: nextYear) + * println today + * println oneYearFromNow + * </pre> + * + * @param self A Date + * @param updates A Map of Calendar keys and values + * @return The newly created Date + * @see java.util.Calendar#set(int, int) + * @see #set(java.util.Date, java.util.Map) + * @see #copyWith(java.util.Calendar, java.util.Map) + * @since 2.2.0 + */ + public static Date copyWith(Date self, Map<Object, Integer> updates) { + Calendar cal = Calendar.getInstance(); + cal.setTime(self); + set(cal, updates); + return cal.getTime(); + } + + private static final Map<String, Integer> CAL_MAP = new HashMap<String, Integer>(); + + static { + CAL_MAP.put("year", Calendar.YEAR); + CAL_MAP.put("month", Calendar.MONTH); + CAL_MAP.put("date", Calendar.DATE); + CAL_MAP.put("dayOfMonth", Calendar.DATE); + CAL_MAP.put("hourOfDay", Calendar.HOUR_OF_DAY); + CAL_MAP.put("minute", Calendar.MINUTE); + CAL_MAP.put("second", Calendar.SECOND); + } + + /** + * Increment a Date by one day. + * + * @param self a Date + * @return the next days date + * @since 1.0 + */ + public static Date next(Date self) { + return plus(self, 1); + } + + /** + * Increment a Calendar by one day. + * + * @param self a Calendar + * @return a new Calendar set to the next day + * @since 1.8.7 + */ + public static Calendar next(Calendar self) { + Calendar result = (Calendar) self.clone(); + result.add(Calendar.DAY_OF_YEAR, 1); + return result; + } + + /** + * Decrement a Calendar by one day. + * + * @param self a Calendar + * @return a new Calendar set to the previous day + * @since 1.8.7 + */ + public static Calendar previous(Calendar self) { + Calendar result = (Calendar) self.clone(); + result.add(Calendar.DAY_OF_YEAR, -1); + return result; + } + + /** + * Increment a java.sql.Date by one day. + * + * @param self a java.sql.Date + * @return the next days date + * @since 1.0 + */ + public static java.sql.Date next(java.sql.Date self) { + return new java.sql.Date(next((Date) self).getTime()); + } + + /** + * Decrement a Date by one day. + * + * @param self a Date + * @return the previous days date + * @since 1.0 + */ + public static Date previous(Date self) { + return minus(self, 1); + } + + /** + * Decrement a java.sql.Date by one day. + * + * @param self a java.sql.Date + * @return the previous days date + * @since 1.0 + */ + public static java.sql.Date previous(java.sql.Date self) { + return new java.sql.Date(previous((Date) self).getTime()); + } + + /** + * Add a number of days to this date and returns the new date. + * + * @param self a Date + * @param days the number of days to increase + * @return the new date + * @since 1.0 + */ + public static Date plus(Date self, int days) { + Calendar calendar = (Calendar) Calendar.getInstance().clone(); + calendar.setTime(self); + calendar.add(Calendar.DAY_OF_YEAR, days); + return calendar.getTime(); + } + + /** + * Add a number of days to this date and returns the new date. + * + * @param self a java.sql.Date + * @param days the number of days to increase + * @return the new date + * @since 1.0 + */ + public static java.sql.Date plus(java.sql.Date self, int days) { + return new java.sql.Date(plus((Date) self, days).getTime()); + } + + /** + * Add number of days to this Timestamp and returns the new Timestamp object. + * + * @param self a Timestamp + * @param days the number of days to increase + * @return the new Timestamp + */ + public static Timestamp plus(Timestamp self, int days) { + Calendar calendar = (Calendar) Calendar.getInstance().clone(); + calendar.setTime(self); + calendar.add(Calendar.DAY_OF_YEAR, days); + Timestamp ts = new Timestamp(calendar.getTime().getTime()); + ts.setNanos(self.getNanos()); + return ts; + } + + /** + * Subtract a number of days from this date and returns the new date. + * + * @param self a Date + * @param days the number of days to subtract + * @return the new date + * @since 1.0 + */ + public static Date minus(Date self, int days) { + return plus(self, -days); + } + + /** + * Subtract a number of days from this date and returns the new date. + * + * @param self a java.sql.Date + * @param days the number of days to subtract + * @return the new date + * @since 1.0 + */ + public static java.sql.Date minus(java.sql.Date self, int days) { + return new java.sql.Date(minus((Date) self, days).getTime()); + } + + /** + * Subtract a number of days from this Timestamp and returns the new Timestamp object. + * + * @param self a Timestamp + * @param days the number of days to subtract + * @return the new Timestamp + */ + public static Timestamp minus(Timestamp self, int days) { + return plus(self, -days); + } + + /** + * Subtract another date from this one and return the number of days of the difference. + * <p> + * Date self = Date then + (Date self - Date then) + * <p> + * IOW, if self is before then the result is a negative value. + * + * @param self a Calendar + * @param then another Calendar + * @return number of days + * @since 1.6.0 + */ + public static int minus(Calendar self, Calendar then) { + Calendar a = self; + Calendar b = then; + + boolean swap = a.before(b); + + if (swap) { + Calendar t = a; + a = b; + b = t; + } + + int days = 0; + + b = (Calendar) b.clone(); + + while (a.get(Calendar.YEAR) > b.get(Calendar.YEAR)) { + days += 1 + (b.getActualMaximum(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR)); + b.set(Calendar.DAY_OF_YEAR, 1); + b.add(Calendar.YEAR, 1); + } + + days += a.get(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR); + + if (swap) days = -days; + + return days; + } + + /** + * Subtract another Date from this one and return the number of days of the difference. + * <p> + * Date self = Date then + (Date self - Date then) + * <p> + * IOW, if self is before then the result is a negative value. + * + * @param self a Date + * @param then another Date + * @return number of days + * @since 1.6.0 + */ + public static int minus(Date self, Date then) { + Calendar a = (Calendar) Calendar.getInstance().clone(); + a.setTime(self); + Calendar b = (Calendar) Calendar.getInstance().clone(); + b.setTime(then); + return minus(a, b); + } + + /** + * <p>Create a String representation of this date according to the given + * format pattern. + * <p> + * <p>For example, if the system timezone is GMT, + * <code>new Date(0).format('MM/dd/yy')</code> would return the string + * <code>"01/01/70"</code>. See documentation for {@link java.text.SimpleDateFormat} + * for format pattern use. + * <p> + * <p>Note that a new DateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self a Date + * @param format the format pattern to use according to {@link java.text.SimpleDateFormat} + * @return a string representation of this date. + * @see java.text.SimpleDateFormat + * @since 1.5.7 + */ + public static String format(Date self, String format) { + return new SimpleDateFormat(format).format(self); + } + + /** + * <p>Create a String representation of this date according to the given + * format pattern and timezone. + * <p> + * <p>For example: + * <code> + * def d = new Date(0) + * def tz = TimeZone.getTimeZone('GMT') + * println d.format('dd/MMM/yyyy', tz) + * </code> would return the string + * <code>"01/Jan/1970"</code>. See documentation for {@link java.text.SimpleDateFormat} + * for format pattern use. + * <p> + * <p>Note that a new DateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self a Date + * @param format the format pattern to use according to {@link java.text.SimpleDateFormat} + * @param tz the TimeZone to use + * @return a string representation of this date. + * @see java.text.SimpleDateFormat + * @since 1.8.3 + */ + public static String format(Date self, String format, TimeZone tz) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(tz); + return sdf.format(self); + } + + /** + * <p>Return a string representation of the 'day' portion of this date + * according to the locale-specific {@link java.text.DateFormat#SHORT} default format. + * For an "en_UK" system locale, this would be <code>dd/MM/yy</code>. + * <p> + * <p>Note that a new DateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self a Date + * @return a string representation of this date + * @see java.text.DateFormat#getDateInstance(int) + * @see java.text.DateFormat#SHORT + * @since 1.5.7 + */ + public static String getDateString(Date self) { + return DateFormat.getDateInstance(DateFormat.SHORT).format(self); + } + + /** + * <p>Return a string representation of the time portion of this date + * according to the locale-specific {@link java.text.DateFormat#MEDIUM} default format. + * For an "en_UK" system locale, this would be <code>HH:MM:ss</code>. + * <p> + * <p>Note that a new DateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self a Date + * @return a string representing the time portion of this date + * @see java.text.DateFormat#getTimeInstance(int) + * @see java.text.DateFormat#MEDIUM + * @since 1.5.7 + */ + public static String getTimeString(Date self) { + return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(self); + } + + /** + * <p>Return a string representation of the date and time time portion of + * this Date instance, according to the locale-specific format used by + * {@link java.text.DateFormat}. This method uses the {@link java.text.DateFormat#SHORT} + * preset for the day portion and {@link java.text.DateFormat#MEDIUM} for the time + * portion of the output string. + * <p> + * <p>Note that a new DateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self a Date + * @return a string representation of this date and time + * @see java.text.DateFormat#getDateTimeInstance(int, int) + * @since 1.5.7 + */ + public static String getDateTimeString(Date self) { + return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(self); + } + + /** + * Common code for {@link #clearTime(java.util.Calendar)} and {@link #clearTime(java.util.Date)} + * and {@link #clearTime(java.sql.Date)} + * + * @param self a Calendar to adjust + */ + private static void clearTimeCommon(final Calendar self) { + self.set(Calendar.HOUR_OF_DAY, 0); + self.clear(Calendar.MINUTE); + self.clear(Calendar.SECOND); + self.clear(Calendar.MILLISECOND); + } + + /** + * Clears the time portion of this Date instance; useful utility where + * it makes sense to compare month/day/year only portions of a Date. + * + * @param self a Date + * @return the Date but with the time portion cleared + * @since 1.6.7 + */ + public static Date clearTime(final Date self) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(self); + clearTimeCommon(calendar); + self.setTime(calendar.getTime().getTime()); + return self; + } + + /** + * Clears the time portion of this java.sql.Date instance; useful utility + * where it makes sense to compare month/day/year only portions of a Date. + * + * @param self a java.sql.Date + * @return the java.sql.Date but with the time portion cleared + * @since 1.6.7 + */ + public static java.sql.Date clearTime(final java.sql.Date self) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(self); + clearTimeCommon(calendar); + self.setTime(calendar.getTime().getTime()); + return self; + } + + /** + * Clears the time portion of this Calendar instance; useful utility + * where it makes sense to compare month/day/year only portions of a Calendar. + * + * @param self a Calendar + * @return the Calendar but with the time portion cleared + * @since 1.6.7 + */ + public static Calendar clearTime(final Calendar self) { + clearTimeCommon(self); + return self; + } + + /** + * <p>Shortcut for {@link java.text.SimpleDateFormat} to output a String representation + * of this calendar instance. This method respects the Calendar's assigned + * {@link java.util.TimeZone}, whereas calling <code>cal.time.format('HH:mm:ss')</code> + * would use the system timezone. + * <p>Note that Calendar equivalents of <code>date.getDateString()</code> + * and variants do not exist because those methods are Locale-dependent. + * Although a Calendar may be assigned a {@link java.util.Locale}, that information is + * lost and therefore cannot be used to control the default date/time formats + * provided by these methods. Instead, the system Locale would always be + * used. The alternative is to simply call + * {@link java.text.DateFormat#getDateInstance(int, java.util.Locale)} and pass the same Locale + * that was used for the Calendar. + * + * @param self this calendar + * @param pattern format pattern + * @return String representation of this calendar with the given format. + * @see java.text.DateFormat#setTimeZone(java.util.TimeZone) + * @see java.text.SimpleDateFormat#format(java.util.Date) + * @see #format(java.util.Date, String) + * @since 1.6.0 + */ + public static String format(Calendar self, String pattern) { + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + sdf.setTimeZone(self.getTimeZone()); + return sdf.format(self.getTime()); + } + + /** + * Iterates from this date up to the given date, inclusive, + * incrementing by one day each time. + * + * @param self a Date + * @param to another Date to go up to + * @param closure the closure to call + * @since 2.2 + */ + public static void upto(Date self, Date to, Closure closure) { + if (self.compareTo(to) <= 0) { + for (Date i = (Date) self.clone(); i.compareTo(to) <= 0; i = next(i)) { + closure.call(i); + } + } else + throw new GroovyRuntimeException("The argument (" + to + + ") to upto() cannot be earlier than the value (" + self + ") it's called on."); + } + + /** + * Iterates from the date represented by this calendar up to the date represented + * by the given calendar, inclusive, incrementing by one day each time. + * + * @param self a Calendar + * @param to another Calendar to go up to + * @param closure the closure to call + * @since 2.2 + */ + public static void upto(Calendar self, Calendar to, Closure closure) { + if (self.compareTo(to) <= 0) { + for (Calendar i = (Calendar) self.clone(); i.compareTo(to) <= 0; i = next(i)) { + closure.call(i); + } + } else + throw new GroovyRuntimeException("The argument (" + to + + ") to upto() cannot be earlier than the value (" + self + ") it's called on."); + } + + /** + * Iterates from this date down to the given date, inclusive, + * decrementing by one day each time. + * + * @param self a Date + * @param to another Date to go down to + * @param closure the closure to call + * @since 2.2 + */ + public static void downto(Date self, Date to, Closure closure) { + if (self.compareTo(to) >= 0) { + for (Date i = (Date) self.clone(); i.compareTo(to) >= 0; i = previous(i)) { + closure.call(i); + } + } else + throw new GroovyRuntimeException("The argument (" + to + + ") to downto() cannot be later than the value (" + self + ") it's called on."); + } + + /** + * Iterates from the date represented by this calendar up to the date represented + * by the given calendar, inclusive, incrementing by one day each time. + * + * @param self a Calendar + * @param to another Calendar to go down to + * @param closure the closure to call + * @since 2.2 + */ + public static void downto(Calendar self, Calendar to, Closure closure) { + if (self.compareTo(to) >= 0) { + for (Calendar i = (Calendar) self.clone(); i.compareTo(to) >= 0; i = previous(i)) { + closure.call(i); + } + } else + throw new GroovyRuntimeException("The argument (" + to + + ") to downto() cannot be later than the value (" + self + ") it's called on."); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/DefaultCachedMethodKey.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultCachedMethodKey.java b/src/main/java/org/codehaus/groovy/runtime/DefaultCachedMethodKey.java new file mode 100644 index 0000000..9dd8cd0 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultCachedMethodKey.java @@ -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. + */ +package org.codehaus.groovy.runtime; + +import org.codehaus.groovy.reflection.CachedClass; + + +/** + * A default implementation of MethodKey + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class DefaultCachedMethodKey extends MethodKey{ + + private final CachedClass[] parameterTypes; + + public DefaultCachedMethodKey(Class sender, String name, CachedClass[] parameterTypes, boolean isCallToSuper) { + super(sender, name,isCallToSuper); + this.parameterTypes = parameterTypes; + } + + public int getParameterCount() { + return parameterTypes.length; + } + + public Class getParameterType(int index) { + CachedClass c = parameterTypes[index]; + if (c==null) return Object.class; + return c.getTheClass(); + } +}
