001    /*
002     * $Id: GroovyCategorySupport.java 4270 2006-11-27 21:43:01Z blackdrag $version Apr 26, 2004 4:22:50 PM $user Exp $
003     * 
004     * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
005     * 
006     * Redistribution and use of this software and associated documentation
007     * ("Software"), with or without modification, are permitted provided that the
008     * following conditions are met: 1. Redistributions of source code must retain
009     * copyright statements and notices. Redistributions must also contain a copy
010     * of this document. 2. Redistributions in binary form must reproduce the above
011     * copyright notice, this list of conditions and the following disclaimer in
012     * the documentation and/or other materials provided with the distribution. 3.
013     * The name "groovy" must not be used to endorse or promote products derived
014     * from this Software without prior written permission of The Codehaus. For
015     * written permission, please contact info@codehaus.org. 4. Products derived
016     * from this Software may not be called "groovy" nor may "groovy" appear in
017     * their names without prior written permission of The Codehaus. "groovy" is a
018     * registered trademark of The Codehaus. 5. Due credit should be given to The
019     * Codehaus - http://groovy.codehaus.org/
020     * 
021     * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024     * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028     * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031     * DAMAGE.
032     *  
033     */
034    package org.codehaus.groovy.runtime;
035    
036    import groovy.lang.Closure;
037    import groovy.lang.MetaMethod;
038    
039    import java.lang.reflect.Method;
040    import java.lang.reflect.Modifier;
041    import java.util.*;
042    
043    /**
044     * @author sam
045     * @author Paul King
046     */
047    public class GroovyCategorySupport {
048        
049        private static long categoriesInUse = 0; 
050    
051        /**
052         * This method is used to pull all the new methods out of the local thread context with a particular name.
053         * 
054         * @param categorizedClass a class subject to the category methods in the thread context
055         * @param name the method name of interest
056         * @return the list of methods
057         */
058        public static List getCategoryMethods(Class categorizedClass, String name) {
059            Map properties = getProperties();
060            List methodList = new ArrayList();
061            for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
062                Class current = (Class) i.next();
063                if (current.isAssignableFrom(categorizedClass)) {
064                    Map metaMethodsMap = (Map) properties.get(current);
065                    List newMethodList = (List) metaMethodsMap.get(name);
066                    if (newMethodList != null) {
067                        methodList.addAll(newMethodList);
068                    }
069                }
070            }
071            if (methodList.size() == 0) return null;
072            return methodList;
073        }
074    
075        /**
076         * This method is used to pull all the new methods out of the local thread context.
077         *
078         * @param categorizedClass a class subject to the category methods in the thread context
079         * @return the list of methods
080         */
081        public static List getCategoryMethods(Class categorizedClass) {
082            Map properties = getProperties();
083            List methodList = new ArrayList();
084            for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
085                Class current = (Class) i.next();
086                if (current.isAssignableFrom(categorizedClass)) {
087                    Map metaMethodsMap = (Map) properties.get(current);
088                    Collection collection = metaMethodsMap.values();
089                    for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
090                        List newMethodList = (List) iterator.next();
091                        if (newMethodList != null) {
092                            methodList.addAll(newMethodList);
093                        }                    
094                    }
095                }
096            }
097            if (methodList.size() == 0) return null;
098            return methodList;
099        }
100    
101        private static class CategoryMethod extends NewInstanceMetaMethod implements Comparable {
102            private Class metaClass;
103    
104            public CategoryMethod(MetaMethod metaMethod, Class metaClass) {
105                super(metaMethod);
106                this.metaClass = metaClass;
107            }
108    
109            public boolean isCacheable() { return false; }
110    
111            /**
112             * Sort by most specific to least specific.
113             *
114             * @param o the object to compare against
115             */
116            public int compareTo(Object o) {
117                CategoryMethod thatMethod = (CategoryMethod) o;
118                Class thisClass = metaClass;
119                Class thatClass = thatMethod.metaClass;
120                if (thisClass == thatClass) return 0;
121                Class loop = thisClass;
122                while(loop != Object.class) {
123                    loop = thisClass.getSuperclass();
124                    if (loop == thatClass) {
125                        return -1;
126                    }
127                }
128                loop = thatClass;
129                while (loop != Object.class) {
130                    loop = thatClass.getSuperclass();
131                    if (loop == thisClass) {
132                        return 1;
133                    }
134                }
135                return 0;
136            }
137        }
138    
139        /**
140         * Create a scope based on given categoryClass and invoke closure within that scope.
141         *
142         * @param categoryClass the class containing category methods
143             * @param closure the closure during which to make the category class methods available
144             */
145            public static void use(Class categoryClass, Closure closure) {
146                    newScope();
147                    try {
148                            use(categoryClass);
149                            closure.call();
150                    } finally {
151                            endScope();
152                    }
153            }
154    
155        /**
156         * Create a scope based on given categoryClasses and invoke closure within that scope.
157         *
158         * @param categoryClasses the list of classes containing category methods
159         * @param closure the closure during which to make the category class methods available
160         */
161        public static void use(List categoryClasses, Closure closure) {
162            newScope();
163            try {
164                for (Iterator i = categoryClasses.iterator(); i.hasNext(); ) {
165                    Class clazz = (Class) i.next();
166                    use(clazz);
167                }
168                closure.call();
169            } finally {
170                endScope();
171            }
172        }
173    
174        /**
175         * Delegated to from the global use(CategoryClass) method.  It scans the Category class for static methods
176         * that take 1 or more parameters.  The first parameter is the class you are adding the category method to,
177         * additional parameters are those paramteres needed by that method.  A use statement cannot be undone and
178         * is valid only for the current thread.
179         *
180         * @param categoryClass the class containing category methods
181         */
182        private static void use(Class categoryClass) {
183            Map properties = getProperties();
184            Method[] methods = categoryClass.getMethods();
185            for (int i = 0; i < methods.length; i++) {
186                Method method = methods[i];
187                if (Modifier.isStatic(method.getModifiers())) {
188                    Class[] paramTypes = method.getParameterTypes();
189                    if (paramTypes.length > 0) {
190                        Class metaClass = paramTypes[0];
191                        Map metaMethodsMap = getMetaMethods(properties, metaClass);
192                        List methodList = getMethodList(metaMethodsMap, method.getName());
193                        MetaMethod mmethod = new CategoryMethod(new MetaMethod(method), metaClass);
194                        methodList.add(mmethod);
195                        Collections.sort(methodList);
196                    }
197                }
198            }
199        }
200    
201        private static ThreadLocal local = new ThreadLocal() {
202            protected Object initialValue() {
203                            List stack = new ArrayList();
204                            stack.add(Collections.EMPTY_MAP);
205                            return stack;
206                    }
207        };
208        
209        private static void newScope() {
210            categoriesInUse++;
211            List stack = (List) local.get();
212            Map properties = new WeakHashMap(getProperties());
213            stack.add(properties);
214        }
215        
216        private static void endScope() {
217            List stack = (List) local.get();
218            stack.remove(stack.size() - 1);
219            categoriesInUse--;
220        }
221        
222        private static Map getProperties() {
223            List stack = (List) local.get();
224            return (Map) stack.get(stack.size() - 1);
225        }
226        
227        public static boolean hasCategoryInAnyThread() {
228            return categoriesInUse!=0;
229        }
230        
231        private static List getMethodList(Map metaMethodsMap, String name) {
232            List methodList = (List) metaMethodsMap.get(name);
233            if (methodList == null) {
234                methodList = new ArrayList(1);
235                metaMethodsMap.put(name, methodList);
236            }
237            return methodList;
238        }
239    
240        private static Map getMetaMethods(Map properties, Class metaClass) {
241            Map metaMethodsMap = (Map) properties.get(metaClass);
242            if (metaMethodsMap == null) {
243                metaMethodsMap = new HashMap();
244                properties.put(metaClass, metaMethodsMap);
245            }
246            return metaMethodsMap;
247        }
248    
249    }