http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java new file mode 100644 index 0000000..3838fdc --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java @@ -0,0 +1,383 @@ +/* + * 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.EmptyRange; +import groovy.lang.IntRange; +import groovy.lang.Range; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Hashtable; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Properties; +import java.util.Queue; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Stack; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.Vector; +import java.util.WeakHashMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.logging.Logger; + +/** + * Support methods for DefaultGroovyMethods and PluginDefaultMethods. + */ +public class DefaultGroovyMethodsSupport { + + private static final Logger LOG = Logger.getLogger(DefaultGroovyMethodsSupport.class.getName()); + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + // helper method for getAt and putAt + protected static RangeInfo subListBorders(int size, Range range) { + if (range instanceof IntRange) { + return ((IntRange)range).subListBorders(size); + } + int from = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getFrom()), size); + int to = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getTo()), size); + boolean reverse = range.isReverse(); + if (from > to) { + // support list[1..-1] + int tmp = to; + to = from; + from = tmp; + reverse = !reverse; + } + return new RangeInfo(from, to + 1, reverse); + } + + // helper method for getAt and putAt + protected static RangeInfo subListBorders(int size, EmptyRange range) { + int from = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getFrom()), size); + return new RangeInfo(from, from, false); + } + + /** + * This converts a possibly negative index to a real index into the array. + * + * @param i the unnormalized index + * @param size the array size + * @return the normalised index + */ + protected static int normaliseIndex(int i, int size) { + int temp = i; + if (i < 0) { + i += size; + } + if (i < 0) { + throw new ArrayIndexOutOfBoundsException("Negative array index [" + temp + "] too large for array size " + size); + } + return i; + } + + /** + * Close the Closeable. Logging a warning if any problems occur. + * + * @param closeable the thing to close + */ + public static void closeWithWarning(Closeable closeable) { + tryClose(closeable, true); // ignore result + } + + /** + * Attempts to close the closeable returning rather than throwing + * any Exception that may occur. + * + * @param closeable the thing to close + * @param logWarning if true will log a warning if an exception occurs + * @return throwable Exception from the close method, else null + */ + static Throwable tryClose(AutoCloseable closeable, boolean logWarning) { + Throwable thrown = null; + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + thrown = e; + if (logWarning) { + LOG.warning("Caught exception during close(): " + e); + } + } + } + return thrown; + } + + /** + * Close the Closeable. Ignore any problems that might occur. + * + * @param c the thing to close + */ + public static void closeQuietly(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + /* ignore */ + } + } + } + + @SuppressWarnings("unchecked") + protected static <T> Collection<T> cloneSimilarCollection(Collection<T> orig, int newCapacity) { + Collection<T> answer = (Collection<T>) cloneObject(orig); + if (answer != null) return answer; + + // fall back to creation + answer = createSimilarCollection(orig, newCapacity); + answer.addAll(orig); + return answer; + } + + private static Object cloneObject(Object orig) { + if (orig instanceof Cloneable) { + try { + return InvokerHelper.invokeMethod(orig, "clone", EMPTY_OBJECT_ARRAY); + } catch (Exception ex) { + // ignore + } + } + return null; + } + + protected static Collection createSimilarOrDefaultCollection(Object object) { + if (object instanceof Collection) { + return createSimilarCollection((Collection<?>) object); + } + return new ArrayList(); + } + + protected static <T> Collection<T> createSimilarCollection(Iterable<T> iterable) { + if (iterable instanceof Collection) { + return createSimilarCollection((Collection<T>) iterable); + } else { + return new ArrayList<T>(); + } + } + + protected static <T> Collection<T> createSimilarCollection(Collection<T> collection) { + return createSimilarCollection(collection, collection.size()); + } + + protected static <T> Collection<T> createSimilarCollection(Collection<T> orig, int newCapacity) { + if (orig instanceof Set) { + return createSimilarSet((Set<T>) orig); + } + if (orig instanceof List) { + return createSimilarList((List<T>) orig, newCapacity); + } + if (orig instanceof Queue) { + return createSimilarQueue((Queue<T>) orig); + } + return new ArrayList<T>(newCapacity); + } + + protected static <T> List<T> createSimilarList(List<T> orig, int newCapacity) { + if (orig instanceof LinkedList) + return new LinkedList<T>(); + + if (orig instanceof Stack) + return new Stack<T>(); + + if (orig instanceof Vector) + return new Vector<T>(); + + if (orig instanceof CopyOnWriteArrayList) + return new CopyOnWriteArrayList<T>(); + + return new ArrayList<T>(newCapacity); + } + + @SuppressWarnings("unchecked") + protected static <T> T[] createSimilarArray(T[] orig, int newCapacity) { + Class<T> componentType = (Class<T>) orig.getClass().getComponentType(); + return (T[]) Array.newInstance(componentType, newCapacity); + } + + @SuppressWarnings("unchecked") + protected static <T> Set<T> createSimilarSet(Set<T> orig) { + if (orig instanceof SortedSet) { + Comparator comparator = ((SortedSet) orig).comparator(); + if (orig instanceof ConcurrentSkipListSet) { + return new ConcurrentSkipListSet<T>(comparator); + } else { + return new TreeSet<T>(comparator); + } + } else { + if (orig instanceof CopyOnWriteArraySet) { + return new CopyOnWriteArraySet<T>(); + } else { + // Do not use HashSet + return new LinkedHashSet<T>(); + } + } + } + + @SuppressWarnings("unchecked") + protected static <T> Queue<T> createSimilarQueue(Queue<T> orig) { + if (orig instanceof ArrayBlockingQueue) { + ArrayBlockingQueue queue = (ArrayBlockingQueue) orig; + return new ArrayBlockingQueue<T>(queue.size() + queue.remainingCapacity()); + } else if (orig instanceof ArrayDeque) { + return new ArrayDeque<T>(); + } else if (orig instanceof ConcurrentLinkedQueue) { + return new ConcurrentLinkedQueue<T>(); + } else if (orig instanceof DelayQueue) { + return new DelayQueue(); + } else if (orig instanceof LinkedBlockingDeque) { + return new LinkedBlockingDeque<T>(); + } else if (orig instanceof LinkedBlockingQueue) { + return new LinkedBlockingQueue<T>(); + } else if (orig instanceof PriorityBlockingQueue) { + return new PriorityBlockingQueue<T>(); + } else if (orig instanceof PriorityQueue) { + return new PriorityQueue<T>(11, ((PriorityQueue) orig).comparator()); + } else if (orig instanceof SynchronousQueue) { + return new SynchronousQueue<T>(); + } else { + return new LinkedList<T>(); + } + } + + @SuppressWarnings("unchecked") + protected static <K, V> Map<K, V> createSimilarMap(Map<K, V> orig) { + if (orig instanceof SortedMap) { + Comparator comparator = ((SortedMap) orig).comparator(); + if (orig instanceof ConcurrentSkipListMap) { + return new ConcurrentSkipListMap<K, V>(comparator); + } else { + return new TreeMap<K, V>(comparator); + } + } else { + if (orig instanceof ConcurrentHashMap) { + return new ConcurrentHashMap<K, V>(); + } else if (orig instanceof Hashtable) { + if (orig instanceof Properties) { + return (Map<K, V>) new Properties(); + } else { + return new Hashtable<K, V>(); + } + } else if (orig instanceof IdentityHashMap) { + return new IdentityHashMap<K, V>(); + } else if (orig instanceof WeakHashMap) { + return new WeakHashMap<K, V>(); + } else { + // Do not use HashMap + return new LinkedHashMap<K, V>(); + } + } + } + + @SuppressWarnings("unchecked") + protected static <K, V> Map<K ,V> cloneSimilarMap(Map<K, V> orig) { + Map<K, V> answer = (Map<K, V>) cloneObject(orig); + if (answer != null) return answer; + + // fall back to some defaults + if (orig instanceof SortedMap) { + if (orig instanceof ConcurrentSkipListMap) { + return new ConcurrentSkipListMap<K, V>(orig); + } else { + return new TreeMap<K, V>(orig); + } + } else { + if (orig instanceof ConcurrentHashMap) { + return new ConcurrentHashMap<K, V>(orig); + } else if (orig instanceof Hashtable) { + if (orig instanceof Properties) { + Map<K, V> map = (Map<K, V>) new Properties(); + map.putAll(orig); + return map; + } else { + return new Hashtable<K, V>(orig); + } + } else if (orig instanceof IdentityHashMap) { + return new IdentityHashMap<K, V>(orig); + } else if (orig instanceof WeakHashMap) { + return new WeakHashMap<K, V>(orig); + } else { + // Do not use HashMap + return new LinkedHashMap<K, V>(orig); + } + } + } + + /** + * Determines if all items of this array are of the same type. + * + * @param cols an array of collections + * @return true if the collections are all of the same type + */ + @SuppressWarnings("unchecked") + protected static boolean sameType(Collection[] cols) { + List all = new LinkedList(); + for (Collection col : cols) { + all.addAll(col); + } + if (all.isEmpty()) + return true; + + Object first = all.get(0); + + //trying to determine the base class of the collections + //special case for Numbers + Class baseClass; + if (first instanceof Number) { + baseClass = Number.class; + } else if (first == null) { + baseClass = NullObject.class; + } else { + baseClass = first.getClass(); + } + + for (Collection col : cols) { + for (Object o : col) { + if (!baseClass.isInstance(o)) { + return false; + } + } + } + return true; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java new file mode 100644 index 0000000..1c85e91 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java @@ -0,0 +1,314 @@ +/* + * 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 org.codehaus.groovy.reflection.ReflectionUtils; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.TimeZone; +import java.util.regex.Matcher; + +/** + * This class defines all the new static groovy methods which appear on normal + * JDK classes inside the Groovy environment. Static methods are used with the + * first parameter as the destination class. + * + * @author Guillaume Laforge + * @author Dierk Koenig + * @author Joachim Baumann + * @author Paul King + * @author Kent Inge Fagerland Simonsen + */ +public class DefaultGroovyStaticMethods { + + /** + * Start a Thread with the given closure as a Runnable instance. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param closure the Runnable closure + * @return the started thread + * @since 1.0 + */ + public static Thread start(Thread self, Closure closure) { + return createThread(null, false, closure); + } + + /** + * Start a Thread with a given name and the given closure + * as a Runnable instance. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param name the name to give the thread + * @param closure the Runnable closure + * @return the started thread + * @since 1.6 + */ + public static Thread start(Thread self, String name, Closure closure) { + return createThread(name, false, closure); + } + + /** + * Start a daemon Thread with the given closure as a Runnable instance. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param closure the Runnable closure + * @return the started thread + * @since 1.0 + */ + public static Thread startDaemon(Thread self, Closure closure) { + return createThread(null, true, closure); + } + + /** + * Start a daemon Thread with a given name and the given closure as + * a Runnable instance. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param name the name to give the thread + * @param closure the Runnable closure + * @return the started thread + * @since 1.6 + */ + public static Thread startDaemon(Thread self, String name, Closure closure) { + return createThread(name, true, closure); + } + + private static Thread createThread(String name, boolean daemon, Closure closure) { + Thread thread = name != null ? new Thread(closure, name) : new Thread(closure); + if (daemon) thread.setDaemon(true); + thread.start(); + return thread; + } + + /** + * Get the last hidden matcher that the system used to do a match. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @return the last regex matcher + * @since 1.0 + */ + public static Matcher getLastMatcher(Matcher self) { + return RegexSupport.getLastMatcher(); + } + + /** + * This method is used by both sleep() methods to implement sleeping + * for the given time even if interrupted + * + * @param millis the number of milliseconds to sleep + * @param closure optional closure called when interrupted + * as long as the closure returns false the sleep continues + */ + private static void sleepImpl(long millis, Closure closure) { + long start = System.currentTimeMillis(); + long rest = millis; + long current; + while (rest > 0) { + try { + Thread.sleep(rest); + rest = 0; + } catch (InterruptedException e) { + if (closure != null) { + if (DefaultTypeTransformation.castToBoolean(closure.call(e))) { + return; + } + } + current = System.currentTimeMillis(); // compensate for closure's time + rest = millis + start - current; + } + } + } + + /** + * Sleep for so many milliseconds, even if interrupted. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param milliseconds the number of milliseconds to sleep + * @since 1.0 + */ + public static void sleep(Object self, long milliseconds) { + sleepImpl(milliseconds, null); + } + + /** + * Sleep for so many milliseconds, using a given closure for interrupt processing. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param milliseconds the number of milliseconds to sleep + * @param onInterrupt interrupt handler, InterruptedException is passed to the Closure + * as long as it returns false, the sleep continues + * @since 1.0 + */ + public static void sleep(Object self, long milliseconds, Closure onInterrupt) { + sleepImpl(milliseconds, onInterrupt); + } + + /** + * Parse a String into a Date instance using the given pattern. + * This convenience method acts as a wrapper for {@link java.text.SimpleDateFormat}. + * <p> + * Note that a new SimpleDateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param format pattern used to parse the input string. + * @param input String to be parsed to create the date instance + * @return a new Date instance representing the parsed input string + * @throws ParseException if there is a parse error + * @see java.text.SimpleDateFormat#parse(java.lang.String) + * @since 1.5.7 + */ + public static Date parse(Date self, String format, String input) throws ParseException { + return new SimpleDateFormat(format).parse(input); + } + + /** + * Parse a String into a Date instance using the given pattern and TimeZone. + * This convenience method acts as a wrapper for {@link java.text.SimpleDateFormat}. + * <p> + * Note that a new SimpleDateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param format pattern used to parse the input string. + * @param input String to be parsed to create the date instance + * @param zone TimeZone to use when parsing + * @return a new Date instance representing the parsed input string + * @throws ParseException if there is a parse error + * @see java.text.SimpleDateFormat#parse(java.lang.String) + * @since 2.4.1 + */ + public static Date parse(Date self, String format, String input, TimeZone zone) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(zone); + return sdf.parse(input); + } + + /** + * Parse a String matching the pattern EEE MMM dd HH:mm:ss zzz yyyy + * containing US-locale-constants only (e.g. Sat for Saturdays). + * Such a string is generated by the toString method of {@link java.util.Date} + * <p> + * Note that a new SimpleDateFormat instance is created for every + * invocation of this method (for thread safety). + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param dateToString String to be parsed to create the date instance. Must match the pattern EEE MMM dd HH:mm:ss zzz yyyy with US-locale symbols + * @return a new Date instance representing the parsed input string + * @throws ParseException if there is a parse error + */ + public static Date parseToStringDate(Date self, String dateToString) throws ParseException { + return new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US).parse(dateToString); + } + + /** + * Works exactly like ResourceBundle.getBundle(String). This is needed + * because the java method depends on a particular stack configuration that + * is not guaranteed in Groovy when calling the Java method. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param bundleName the name of the bundle. + * @return the resource bundle + * @see java.util.ResourceBundle#getBundle(java.lang.String) + * @since 1.6.0 + */ + public static ResourceBundle getBundle(ResourceBundle self, String bundleName) { + return getBundle(self, bundleName, Locale.getDefault()); + } + + /** + * Works exactly like ResourceBundle.getBundle(String, Locale). This is needed + * because the java method depends on a particular stack configuration that + * is not guaranteed in Groovy when calling the Java method. + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @param bundleName the name of the bundle. + * @param locale the specific locale + * @return the resource bundle + * @see java.util.ResourceBundle#getBundle(java.lang.String, java.util.Locale) + * @since 1.6.0 + */ + public static ResourceBundle getBundle(ResourceBundle self, String bundleName, Locale locale) { + Class c = ReflectionUtils.getCallingClass(); + ClassLoader targetCL = c != null ? c.getClassLoader() : null; + if (targetCL == null) targetCL = ClassLoader.getSystemClassLoader(); + return ResourceBundle.getBundle(bundleName, locale, targetCL); + } + + public static File createTempDir(File self) throws IOException { + return createTempDir(self, "groovy-generated-", "-tmpdir"); + } + + public static File createTempDir(File self, final String prefix, final String suffix) throws IOException { + final int MAXTRIES = 3; + int accessDeniedCounter = 0; + File tempFile=null; + for (int i=0; i<MAXTRIES; i++) { + try { + tempFile = File.createTempFile(prefix, suffix); + tempFile.delete(); + tempFile.mkdirs(); + break; + } catch (IOException ioe) { + if (ioe.getMessage().startsWith("Access is denied")) { + accessDeniedCounter++; + try { Thread.sleep(100); } catch (InterruptedException e) {} + } + if (i==MAXTRIES-1) { + if (accessDeniedCounter==MAXTRIES) { + String msg = + "Access is denied.\nWe tried " + + + accessDeniedCounter+ + " times to create a temporary directory"+ + " and failed each time. If you are on Windows"+ + " you are possibly victim to"+ + " http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6325169. "+ + " this is no bug in Groovy."; + throw new IOException(msg); + } else { + throw ioe; + } + } + continue; + } + } + return tempFile; + } + + /** + * Get the current time in seconds + * + * @param self placeholder variable used by Groovy categories; ignored for default static methods + * @return the difference, measured in seconds, between + * the current time and midnight, January 1, 1970 UTC. + * @see System#currentTimeMillis() + */ + public static long currentTimeSeconds(System self){ + return System.currentTimeMillis() / 1000; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/DefaultMethodKey.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultMethodKey.java b/src/main/java/org/codehaus/groovy/runtime/DefaultMethodKey.java new file mode 100644 index 0000000..96612ff --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultMethodKey.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * A default implementation of MethodKey + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class DefaultMethodKey extends MethodKey{ + + private final Class[] parameterTypes; + + public DefaultMethodKey(Class sender, String name, Class[] parameterTypes, boolean isCallToSuper) { + super(sender, name,isCallToSuper); + this.parameterTypes = parameterTypes; + } + + public int getParameterCount() { + return parameterTypes.length; + } + + public Class getParameterType(int index) { + Class c = parameterTypes[index]; + if (c==null) return Object.class; + return c; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethods.java new file mode 100644 index 0000000..62b6eeb --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethods.java @@ -0,0 +1,367 @@ +/* + * 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.StringWriterIOException; +import groovy.lang.Writable; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +import static org.codehaus.groovy.runtime.EncodingGroovyMethodsSupport.TRANSLATE_TABLE; +import static org.codehaus.groovy.runtime.EncodingGroovyMethodsSupport.TRANSLATE_TABLE_URLSAFE; + +/** + * This class defines all the encoding/decoding groovy methods which enhance + * the normal JDK classes when inside the Groovy environment. + * Static methods are used with the first parameter the destination class. + */ +public class EncodingGroovyMethods { + + private static final char[] T_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); + + private static final char[] T_TABLE_URLSAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=".toCharArray(); + + private static final String CHUNK_SEPARATOR = "\r\n"; + + /** + * Produce a Writable object which writes the Base64 encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 encoding and chunking see <code>RFC 4648</code>. + * + * @param data Byte array to be encoded + * @param chunked whether or not the Base64 encoded data should be MIME chunked + * @return object which will write the Base64 encoding of the byte array + * @since 1.5.1 + */ + public static Writable encodeBase64(Byte[] data, final boolean chunked) { + return encodeBase64(DefaultTypeTransformation.convertToByteArray(data), chunked); + } + + /** + * Produce a Writable object which writes the Base64 encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 encoding and chunking see <code>RFC 4648</code>. + * + * @param data Byte array to be encoded + * @return object which will write the Base64 encoding of the byte array + * @since 1.0 + */ + public static Writable encodeBase64(Byte[] data) { + return encodeBase64(DefaultTypeTransformation.convertToByteArray(data), false); + } + + /** + * Produce a Writable object which writes the Base64 encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 encoding and chunking see <code>RFC 4648</code>. + * + * @param data byte array to be encoded + * @param chunked whether or not the Base64 encoded data should be MIME chunked + * @return object which will write the Base64 encoding of the byte array + * @since 1.5.7 + */ + public static Writable encodeBase64(final byte[] data, final boolean chunked) { + return encodeBase64(data, chunked, false, true); + } + + private static Writable encodeBase64(final byte[] data, final boolean chunked, final boolean urlSafe, final boolean pad) { + return new Writable() { + public Writer writeTo(final Writer writer) throws IOException { + int charCount = 0; + final int dLimit = (data.length / 3) * 3; + final char[] table = urlSafe ? T_TABLE_URLSAFE : T_TABLE; + for (int dIndex = 0; dIndex != dLimit; dIndex += 3) { + int d = ((data[dIndex] & 0XFF) << 16) | ((data[dIndex + 1] & 0XFF) << 8) | (data[dIndex + 2] & 0XFF); + + writer.write(table[d >> 18]); + writer.write(table[(d >> 12) & 0X3F]); + writer.write(table[(d >> 6) & 0X3F]); + writer.write(table[d & 0X3F]); + + if (chunked && ++charCount == 19) { + writer.write(CHUNK_SEPARATOR); + charCount = 0; + } + } + + if (dLimit != data.length) { + int d = (data[dLimit] & 0XFF) << 16; + + if (dLimit + 1 != data.length) { + d |= (data[dLimit + 1] & 0XFF) << 8; + } + + writer.write(table[d >> 18]); + writer.write(table[(d >> 12) & 0X3F]); + if (pad) { + writer.write((dLimit + 1 < data.length) ? table[(d >> 6) & 0X3F] : '='); + writer.write('='); + } else { + if (dLimit + 1 < data.length) { + writer.write(table[(d >> 6) & 0X3F]); + } + } + if (chunked && charCount != 0) { + writer.write(CHUNK_SEPARATOR); + } + } + + return writer; + } + + public String toString() { + StringWriter buffer = new StringWriter(); + + try { + writeTo(buffer); + } catch (IOException e) { + throw new StringWriterIOException(e); + } + + return buffer.toString(); + } + }; + } + + /** + * Produce a Writable object which writes the Base64 encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 encoding and chunking see <code>RFC 4648</code>. + * + * @param data byte array to be encoded + * @return object which will write the Base64 encoding of the byte array + * @since 1.0 + */ + public static Writable encodeBase64(final byte[] data) { + return encodeBase64(data, false); + } + + /** + * Produce a Writable object which writes the Base64 URL and Filename Safe encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 URL and Filename Safe encoding see <code>RFC 4648 - Section 5 + * Base 64 Encoding with URL and Filename Safe Alphabet</code>. + * <p> + * The method omits padding and is equivalent to calling + * {@link org.codehaus.groovy.runtime.EncodingGroovyMethods#encodeBase64Url(Byte[], boolean)} with a + * value of {@code false}. + * + * @param data Byte array to be encoded + * @return object which will write the Base64 URL and Filename Safe encoding of the byte array + * @see org.codehaus.groovy.runtime.EncodingGroovyMethods#encodeBase64Url(Byte[], boolean) + * @since 2.5.0 + */ + public static Writable encodeBase64Url(Byte[] data) { + return encodeBase64Url(data, false); + } + + /** + * Produce a Writable object which writes the Base64 URL and Filename Safe encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 URL and Filename Safe encoding see <code>RFC 4648 - Section 5 + * Base 64 Encoding with URL and Filename Safe Alphabet</code>. + * + * @param data Byte array to be encoded + * @param pad whether or not the encoded data should be padded + * @return object which will write the Base64 URL and Filename Safe encoding of the byte array + * @since 2.5.0 + */ + public static Writable encodeBase64Url(Byte[] data, boolean pad) { + return encodeBase64Url(DefaultTypeTransformation.convertToByteArray(data), pad); + } + + /** + * Produce a Writable object which writes the Base64 URL and Filename Safe encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 URL and Filename Safe encoding see <code>RFC 4648 - Section 5 + * Base 64 Encoding with URL and Filename Safe Alphabet</code>. + * <p> + * The method omits padding and is equivalent to calling + * {@link org.codehaus.groovy.runtime.EncodingGroovyMethods#encodeBase64Url(byte[], boolean)} with a + * value of {@code false}. + * + * @param data Byte array to be encoded + * @return object which will write the Base64 URL and Filename Safe encoding of the byte array + * @see org.codehaus.groovy.runtime.EncodingGroovyMethods#encodeBase64Url(byte[], boolean) + * @since 2.5.0 + */ + public static Writable encodeBase64Url(final byte[] data) { + return encodeBase64Url(data, false); + } + + /** + * Produce a Writable object which writes the Base64 URL and Filename Safe encoding of the byte array. + * Calling toString() on the result returns the encoding as a String. For more + * information on Base64 URL and Filename Safe encoding see <code>RFC 4648 - Section 5 + * Base 64 Encoding with URL and Filename Safe Alphabet</code>. + * + * @param data Byte array to be encoded + * @param pad whether or not the encoded data should be padded + * @return object which will write the Base64 URL and Filename Safe encoding of the byte array + * @since 2.5.0 + */ + public static Writable encodeBase64Url(final byte[] data, final boolean pad) { + return encodeBase64(data, false, true, pad); + } + + /** + * Decode the String from Base64 into a byte array. + * + * @param value the string to be decoded + * @return the decoded bytes as an array + * @since 1.0 + */ + public static byte[] decodeBase64(String value) { + return decodeBase64(value, false); + } + + /** + * Decodes a Base64 URL and Filename Safe encoded String into a byte array. + * + * @param value the string to be decoded + * @return the decoded bytes as an array + * @since 2.5.0 + */ + public static byte[] decodeBase64Url(String value) { + return decodeBase64(value, true); + } + + private static byte[] decodeBase64(String value, boolean urlSafe) { + int byteShift = 4; + int tmp = 0; + boolean done = false; + final StringBuilder buffer = new StringBuilder(); + final byte[] table = urlSafe ? TRANSLATE_TABLE_URLSAFE : TRANSLATE_TABLE; + for (int i = 0; i != value.length(); i++) { + final char c = value.charAt(i); + final int sixBit = (c < 123) ? table[c] : 66; + + if (sixBit < 64) { + if (done) + throw new RuntimeException("= character not at end of base64 value"); // TODO: change this exception type + + tmp = (tmp << 6) | sixBit; + + if (byteShift-- != 4) { + buffer.append((char) ((tmp >> (byteShift * 2)) & 0XFF)); + } + + } else if (sixBit == 64) { + + byteShift--; + done = true; + + } else if (sixBit == 66) { + // RFC 2045 says that I'm allowed to take the presence of + // these characters as evidence of data corruption + // So I will + throw new RuntimeException("bad character in base64 value"); // TODO: change this exception type + } + + if (byteShift == 0) byteShift = 4; + } + + try { + return buffer.toString().getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Base 64 decode produced byte values > 255"); // TODO: change this exception type + } + } + + /** + * Produces a Writable that writes the hex encoding of the Byte[]. Calling + * toString() on this Writable returns the hex encoding as a String. The hex + * encoding includes two characters for each byte and all letters are lower case. + * + * @param data byte array to be encoded + * @return object which will write the hex encoding of the byte array + * @see Integer#toHexString(int) + */ + public static Writable encodeHex(final Byte[] data) { + return encodeHex(DefaultTypeTransformation.convertToByteArray(data)); + } + + /** + * Produces a Writable that writes the hex encoding of the byte[]. Calling + * toString() on this Writable returns the hex encoding as a String. The hex + * encoding includes two characters for each byte and all letters are lower case. + * + * @param data byte array to be encoded + * @return object which will write the hex encoding of the byte array + * @see Integer#toHexString(int) + */ + public static Writable encodeHex(final byte[] data) { + return new Writable() { + public Writer writeTo(Writer out) throws IOException { + for (int i = 0; i < data.length; i++) { + // convert byte into unsigned hex string + String hexString = Integer.toHexString(data[i] & 0xFF); + + // add leading zero if the length of the string is one + if (hexString.length() < 2) { + out.write("0"); + } + + // write hex string to writer + out.write(hexString); + } + return out; + } + + public String toString() { + StringWriter buffer = new StringWriter(); + + try { + writeTo(buffer); + } catch (IOException e) { + throw new StringWriterIOException(e); + } + + return buffer.toString(); + } + }; + } + + /** + * Decodes a hex string to a byte array. The hex string can contain either upper + * case or lower case letters. + * + * @param value string to be decoded + * @return decoded byte array + * @throws NumberFormatException If the string contains an odd number of characters + * or if the characters are not valid hexadecimal values. + */ + public static byte[] decodeHex(final String value) { + // if string length is odd then throw exception + if (value.length() % 2 != 0) { + throw new NumberFormatException("odd number of characters in hex string"); + } + + byte[] bytes = new byte[value.length() / 2]; + for (int i = 0; i < value.length(); i += 2) { + bytes[i / 2] = (byte) Integer.parseInt(value.substring(i, i + 2), 16); + } + + return bytes; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethodsSupport.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethodsSupport.java b/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethodsSupport.java new file mode 100644 index 0000000..24929a8 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/EncodingGroovyMethodsSupport.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * Keep this constant in a separate file as it is troublesome for Antlr to parse for doc purposes. + */ +public class EncodingGroovyMethodsSupport { + static final byte[] TRANSLATE_TABLE = ( + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // \t \n \r + + "\u0042\u0041\u0041\u0042\u0042\u0041\u0042\u0042" + // + + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // + + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // sp ! " # $ % & ' + + "\u0041\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // ( ) * + , - . / + + "\u0042\u0042\u0042\u003E\u0042\u0042\u0042\u003F" + // 0 1 2 3 4 5 6 7 + + "\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B" + // 8 9 : ; < = > ? + + "\u003C\u003D\u0042\u0042\u0042\u0040\u0042\u0042" + // @ A B C D E F G + + "\u0042\u0000\u0001\u0002\u0003\u0004\u0005\u0006" + // H I J K L M N O + + "\u0007\u0008\t\n\u000B\u000C\r\u000E" + // P Q R S T U V W + + "\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016" + // X Y Z [ \ ] ^ _ + + "\u0017\u0018\u0019\u0042\u0042\u0042\u0042\u0042" + // ' a b c d e f g + + "\u0042\u001A\u001B\u001C\u001D\u001E\u001F\u0020" + // h i j k l m n o + + "\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028" + // p q r s t u v w + + "\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030" + // x y z + + "\u0031\u0032\u0033").getBytes(); + + static final byte[] TRANSLATE_TABLE_URLSAFE = ( + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // \t \n \r + + "\u0042\u0041\u0041\u0042\u0042\u0041\u0042\u0042" + // + + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // + + "\u0042\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // sp ! " # $ % & ' + + "\u0041\u0042\u0042\u0042\u0042\u0042\u0042\u0042" + // ( ) * + , - . / + + "\u0042\u0042\u0042\u0042\u0042\u003E\u0042\u0042" + // 0 1 2 3 4 5 6 7 + + "\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B" + // 8 9 : ; < = > ? + + "\u003C\u003D\u0042\u0042\u0042\u0040\u0042\u0042" + // @ A B C D E F G + + "\u0042\u0000\u0001\u0002\u0003\u0004\u0005\u0006" + // H I J K L M N O + + "\u0007\u0008\t\n\u000B\u000C\r\u000E" + // P Q R S T U V W + + "\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016" + // X Y Z [ \ ] ^ _ + + "\u0017\u0018\u0019\u0042\u0042\u0042\u0042\u003F" + // ' a b c d e f g + + "\u0042\u001A\u001B\u001C\u001D\u001E\u001F\u0020" + // h i j k l m n o + + "\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028" + // p q r s t u v w + + "\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030" + // x y z + + "\u0031\u0032\u0033").getBytes(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/FlushingStreamWriter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/FlushingStreamWriter.java b/src/main/java/org/codehaus/groovy/runtime/FlushingStreamWriter.java new file mode 100644 index 0000000..6fb77b7 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/FlushingStreamWriter.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 org.codehaus.groovy.runtime; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +/** + * Stream writer which flushes after each write operation. + * + * @author Guillaume Laforge + */ +public class FlushingStreamWriter extends OutputStreamWriter { + + public FlushingStreamWriter(OutputStream out) { + super(out); + } + + public void write(char[] cbuf, int off, int len) throws IOException { + super.write(cbuf, off, len); + flush(); + } + + public void write(int c) throws IOException { + super.write(c); + flush(); + } + + public void write(String str, int off, int len) throws IOException { + super.write(str, off, len); + flush(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java new file mode 100644 index 0000000..d4cd857 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/GStringImpl.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.codehaus.groovy.runtime; + +import groovy.lang.GString; + +/** + * Default implementation of a GString used by the compiler. A GString + * consist of a list of values and strings which can be combined to + * create a new String. + * + * @author Jochen Theodorou + * @see groovy.lang.GString + */ +public class GStringImpl extends GString { + private final String[] strings; + + /** + * Create a new GString with values and strings. + * <p> + * Each value is prefixed by a string, after the last value + * an additional String might be used. This means + * <code>strings.length == values.length || strings.length == values.length + 1</code>. + * <p> + * <b>NOTE:</b> The lengths are <b>not</b> checked. Using different lengths might result + * in unpredictable behaviour. + * + * @param values the value parts + * @param strings the string parts + */ + public GStringImpl(Object[] values, String[] strings) { + super(values); + this.strings = strings; + } + + /** + * Get the strings of this GString. + * <p> + * This methods returns the same array as used in the constructor. Changing + * the values will result in changes of the GString. It is not recommended + * to do so. + */ + public String[] getStrings() { + return strings; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/GeneratedClosure.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/GeneratedClosure.java b/src/main/java/org/codehaus/groovy/runtime/GeneratedClosure.java new file mode 100644 index 0000000..50da843 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/GeneratedClosure.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Marker interface to identify closures generated by the groovy compiler. + * For internal use only! + * + * @author Jochen Theodorou + * @since 1.5 + * @see org.codehaus.groovy.runtime.metaclass.ClosureMetaClass + */ +public interface GeneratedClosure {} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/GroovyCategorySupport.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/GroovyCategorySupport.java b/src/main/java/org/codehaus/groovy/runtime/GroovyCategorySupport.java new file mode 100644 index 0000000..19f53d2 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/GroovyCategorySupport.java @@ -0,0 +1,365 @@ +/* + * 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 org.codehaus.groovy.reflection.CachedClass; +import org.codehaus.groovy.reflection.CachedMethod; +import org.codehaus.groovy.reflection.ReflectionCache; +import org.codehaus.groovy.runtime.metaclass.DefaultMetaClassInfo; +import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; +import org.codehaus.groovy.vmplugin.VMPluginFactory; + +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author sam + * @author Paul King + * @author Alex Tkachman + */ +public class GroovyCategorySupport { + + private static int categoriesInUse = 0; + + public static class CategoryMethodList extends ArrayList<CategoryMethod> { + public final int level; + final CategoryMethodList previous; + final AtomicInteger usage; + + public CategoryMethodList(String name, int level, CategoryMethodList previous) { + this.level = level; + this.previous = previous; + if (previous != null) { + addAll(previous); + usage = previous.usage; + } + else { + usage = getCategoryNameUsage (name); + } + } + + public boolean add(CategoryMethod o) { + usage.incrementAndGet(); + return super.add(o); + } + } + + public static class ThreadCategoryInfo extends HashMap<String, CategoryMethodList>{ + + private static final Object LOCK = new Object(); + + int level; + + private Map<String, String> propertyGetterMap; + private Map<String, String> propertySetterMap; + + private void newScope () { + synchronized (LOCK) { + categoriesInUse++; + DefaultMetaClassInfo.setCategoryUsed(true); + } + VMPluginFactory.getPlugin().invalidateCallSites(); + level++; + } + + private void endScope () { + for (Iterator<Map.Entry<String, CategoryMethodList>> it = entrySet().iterator(); it.hasNext(); ) { + final Map.Entry<String, CategoryMethodList> e = it.next(); + final CategoryMethodList list = e.getValue(); + if (list.level == level) { + final CategoryMethodList prev = list.previous; + if (prev == null) { + it.remove(); + list.usage.addAndGet(-list.size()); + } + else { + e.setValue(prev); + list.usage.addAndGet(prev.size()-list.size()); + } + } + } + level--; + VMPluginFactory.getPlugin().invalidateCallSites(); + synchronized (LOCK) { + if (--categoriesInUse == 0) { + DefaultMetaClassInfo.setCategoryUsed(false); + } + } + if (level == 0) { + THREAD_INFO.remove(); + } + } + + private <T> T use(Class categoryClass, Closure<T> closure) { + newScope(); + try { + use(categoryClass); + return closure.call(); + } finally { + endScope(); + } + } + + public <T> T use(List<Class> categoryClasses, Closure<T> closure) { + newScope(); + try { + for (Class categoryClass : categoryClasses) { + use(categoryClass); + } + return closure.call(); + } finally { + endScope(); + } + } + + private void applyUse(CachedClass cachedClass) { + CachedMethod[] methods = cachedClass.getMethods(); + for (CachedMethod cachedMethod : methods) { + if (cachedMethod.isStatic() && cachedMethod.isPublic()) { + CachedClass[] paramTypes = cachedMethod.getParameterTypes(); + if (paramTypes.length > 0) { + CachedClass metaClass = paramTypes[0]; + CategoryMethod mmethod = new CategoryMethod(cachedMethod, metaClass.getTheClass()); + final String name = cachedMethod.getName(); + CategoryMethodList list = get(name); + if (list == null || list.level != level) { + list = new CategoryMethodList(name, level, list); + put(name, list); + } + list.add(mmethod); + Collections.sort(list); + cachePropertyAccessor(mmethod); + } + } + } + } + + private void cachePropertyAccessor(CategoryMethod method) { + String name = method.getName(); + int parameterLength = method.getParameterTypes().length; + + if (name.startsWith("get") && name.length() > 3 && parameterLength == 0) { + propertyGetterMap = putPropertyAccessor(3, name, propertyGetterMap); + } + else if (name.startsWith("set") && name.length() > 3 && parameterLength == 1) { + propertySetterMap = putPropertyAccessor(3, name, propertySetterMap); + } + } + + // Precondition: accessorName.length() > prefixLength + private Map<String, String> putPropertyAccessor(int prefixLength, String accessorName, Map<String, String> map) { + if (map == null) { + map = new HashMap<String, String>(); + } + String property = accessorName.substring(prefixLength, prefixLength+1).toLowerCase() + accessorName.substring(prefixLength+1); + map.put(property, accessorName); + return map; + } + + private void use(Class categoryClass) { + CachedClass cachedClass = ReflectionCache.getCachedClass(categoryClass); + LinkedList<CachedClass> classStack = new LinkedList<CachedClass>(); + for (CachedClass superClass = cachedClass; superClass.getTheClass()!=Object.class; superClass = superClass.getCachedSuperClass()) { + classStack.add(superClass); + } + + while (!classStack.isEmpty()) { + CachedClass klazz = classStack.removeLast(); + applyUse(klazz); + } + } + + public CategoryMethodList getCategoryMethods(String name) { + return level == 0 ? null : get(name); + } + + + String getPropertyCategoryGetterName(String propertyName){ + return propertyGetterMap != null ? propertyGetterMap.get(propertyName) : null; + } + + String getPropertyCategorySetterName(String propertyName){ + return propertySetterMap != null ? propertySetterMap.get(propertyName) : null; + } + } + + private static final MyThreadLocal THREAD_INFO = new MyThreadLocal(); + + public static class CategoryMethod extends NewInstanceMetaMethod implements Comparable { + private final Class metaClass; + + public CategoryMethod(CachedMethod metaMethod, Class metaClass) { + super(metaMethod); + this.metaClass = metaClass; + } + + public boolean isCacheable() { return false; } + + /** + * Sort by most specific to least specific. + * + * @param o the object to compare against + */ + public int compareTo(Object o) { + CategoryMethod thatMethod = (CategoryMethod) o; + Class thisClass = metaClass; + Class thatClass = thatMethod.metaClass; + if (thisClass == thatClass) return 0; + if (isChildOfParent(thisClass, thatClass)) return -1; + if (isChildOfParent(thatClass, thisClass)) return 1; + return 0; + } + + private boolean isChildOfParent(Class candidateChild, Class candidateParent) { + Class loop = candidateChild; + while(loop != null && loop != Object.class) { + loop = loop.getSuperclass(); + if (loop == candidateParent) { + return true; + } + } + return false; + } + } + + public static AtomicInteger getCategoryNameUsage (String name) { + return THREAD_INFO.getUsage (name); + } + + /** + * Create a scope based on given categoryClass and invoke closure within that scope. + * + * @param categoryClass the class containing category methods + * @param closure the closure during which to make the category class methods available + * @return the value returned from the closure + */ + public static <T> T use(Class categoryClass, Closure<T> closure) { + return THREAD_INFO.getInfo().use(categoryClass, closure); + } + + /** + * Create a scope based on given categoryClasses and invoke closure within that scope. + * + * @param categoryClasses the list of classes containing category methods + * @param closure the closure during which to make the category class methods available + * @return the value returned from the closure + */ + public static <T> T use(List<Class> categoryClasses, Closure<T> closure) { + return THREAD_INFO.getInfo().use(categoryClasses, closure); + } + + public static boolean hasCategoryInCurrentThread() { + /* + * Synchronization is avoided here for performance reasons since + * this method is called frequently from callsite locations. For + * a typical case when no Categories are in use the initialized + * value of 0 will be correctly read. For cases where multiple + * Threads are using Categories it is possible that a stale + * non-zero value may be read but in that case the ThreadLocal + * check will produce the correct result. When the current Thread + * is using Categories, it would have incremented the counter + * so whatever version of the value it observes here should be + * non-zero and good enough for the purposes of this quick exit + * check. + */ + if (categoriesInUse == 0) { + return false; + } + ThreadCategoryInfo infoNullable = THREAD_INFO.getInfoNullable(); + return infoNullable != null && infoNullable.level != 0; + } + + /** + * @deprecated use {@link #hasCategoryInCurrentThread()} + */ + @Deprecated + public static boolean hasCategoryInAnyThread() { + synchronized (ThreadCategoryInfo.LOCK) { + return categoriesInUse != 0; + } + } + + /** + * This method is used to pull all the new methods out of the local thread context with a particular name. + * + * @param name the method name of interest + * @return the list of methods + */ + public static CategoryMethodList getCategoryMethods(String name) { + final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); + return categoryInfo == null ? null : categoryInfo.getCategoryMethods(name); + } + + public static String getPropertyCategoryGetterName(String propertyName) { + final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); + return categoryInfo == null ? null : categoryInfo.getPropertyCategoryGetterName(propertyName); + } + + public static String getPropertyCategorySetterName(String propertyName) { + final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); + return categoryInfo == null ? null : categoryInfo.getPropertyCategorySetterName(propertyName); + } + + private static class MyThreadLocal extends ThreadLocal<SoftReference> { + + final ConcurrentHashMap<String,AtomicInteger> usage = new ConcurrentHashMap<String,AtomicInteger> (); + + public ThreadCategoryInfo getInfo() { + final SoftReference reference = get(); + ThreadCategoryInfo tcinfo; + if (reference != null) { + tcinfo = (ThreadCategoryInfo) reference.get(); + if( tcinfo == null ) { + tcinfo = new ThreadCategoryInfo(); + set(new SoftReference(tcinfo)); + } + } + else { + tcinfo = new ThreadCategoryInfo(); + set(new SoftReference(tcinfo)); + } + return tcinfo; + } + + public ThreadCategoryInfo getInfoNullable() { + final SoftReference reference = get(); + return reference == null ? null : (ThreadCategoryInfo) reference.get(); + } + + public AtomicInteger getUsage (String name) { + AtomicInteger u = usage.get(name); + if (u != null) { + return u; + } + + final AtomicInteger ai = new AtomicInteger(); + final AtomicInteger prev = usage.putIfAbsent(name, ai); + return prev == null ? ai : prev; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/HandleMetaClass.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/HandleMetaClass.java b/src/main/java/org/codehaus/groovy/runtime/HandleMetaClass.java new file mode 100644 index 0000000..a838c28 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/HandleMetaClass.java @@ -0,0 +1,122 @@ +/* + * 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.DelegatingMetaClass; +import groovy.lang.ExpandoMetaClass; +import groovy.lang.GroovyObject; +import groovy.lang.MetaBeanProperty; +import groovy.lang.MetaClass; +import groovy.lang.MetaMethod; + +import java.lang.reflect.Method; + +public class HandleMetaClass extends DelegatingMetaClass { + private Object object; + private static final Object NONE = new Object(); + + public HandleMetaClass(MetaClass mc) { + this(mc, null); + } + + public HandleMetaClass(MetaClass mc, Object obj) { + super(mc); + if (obj != null) { + if (InvokerHelper.getMetaClass(obj.getClass()) == mc || !(mc instanceof ExpandoMetaClass)) + object = obj; // object has default meta class, so we need to replace it on demand + else + object = NONE; // object already has per instance meta class + } + } + + public void initialize() { + replaceDelegate(); + delegate.initialize(); + } + + public GroovyObject replaceDelegate() { + if (object == null) { + if (!(delegate instanceof ExpandoMetaClass)) { + delegate = new ExpandoMetaClass(delegate.getTheClass(), true, true); + delegate.initialize(); + } + DefaultGroovyMethods.setMetaClass(delegate.getTheClass(), delegate); + } + else { + if (object != NONE) { + final MetaClass metaClass = delegate; + delegate = new ExpandoMetaClass(delegate.getTheClass(), false, true); + if (metaClass instanceof ExpandoMetaClass) { + ExpandoMetaClass emc = (ExpandoMetaClass) metaClass; + for (MetaMethod method : emc.getExpandoMethods()) + ((ExpandoMetaClass)delegate).registerInstanceMethod(method); + } + delegate.initialize(); + MetaClassHelper.doSetMetaClass(object, delegate); + object = NONE; + } + } + return (GroovyObject)delegate; + } + + public Object invokeMethod(String name, Object args) { + return replaceDelegate().invokeMethod(name, args); + } + + // this method mimics EMC behavior + public Object getProperty(String property) { + if(ExpandoMetaClass.isValidExpandoProperty(property)) { + if(property.equals(ExpandoMetaClass.STATIC_QUALIFIER) || + property.equals(ExpandoMetaClass.CONSTRUCTOR) || + Holder.META_CLASS.hasProperty(this, property) == null) { + return replaceDelegate().getProperty(property); + } + } + return Holder.META_CLASS.getProperty(this, property); + } + + public void setProperty(String property, Object newValue) { + replaceDelegate().setProperty(property, newValue); + } + + public void addNewInstanceMethod(Method method) { + throw new UnsupportedOperationException(); + } + + public void addNewStaticMethod(Method method) { + throw new UnsupportedOperationException(); + } + + public void addMetaMethod(MetaMethod metaMethod) { + throw new UnsupportedOperationException(); + } + + public void addMetaBeanProperty(MetaBeanProperty metaBeanProperty) { + throw new UnsupportedOperationException(); + } + + public boolean equals(Object obj) { + return super.equals(obj) || getAdaptee().equals(obj) || (obj instanceof HandleMetaClass && equals(((HandleMetaClass)obj).getAdaptee())); + } + + // Lazily initialize the single instance of the HandleMetaClass metaClass + private static class Holder { + static final MetaClass META_CLASS = InvokerHelper.getMetaClass(HandleMetaClass.class); + } +}
