001    /*
002     * $Id: GroovyCategorySupport.java,v 1.7 2005/06/19 19:01:25 spullara Exp $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.ArrayList;
042    import java.util.Collections;
043    import java.util.HashMap;
044    import java.util.Iterator;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.WeakHashMap;
048    
049    
050    /**
051     * @author sam
052     */
053    public class GroovyCategorySupport {
054    
055        /**
056         * This method is used to pull all the new methods out of the local thread context with a particular name.
057         * 
058         * @param categorizedClass
059         * @param name
060         * @return
061         */
062        public static List getCategoryMethods(Class categorizedClass, String name) {
063            Map properties = getProperties();
064            List methodList = new ArrayList();
065            for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
066                Class current = (Class) i.next();
067                if (current.isAssignableFrom(categorizedClass)) {
068                    Map metaMethodsMap = (Map) properties.get(current);
069                    List newMethodList = (List) metaMethodsMap.get(name);
070                    if (newMethodList != null) {
071                        methodList.addAll(newMethodList);
072                    }
073                }
074            }
075            if (methodList.size() == 0) return null;
076            return methodList;
077        }
078    
079        private static class CategoryMethod extends NewInstanceMetaMethod implements Comparable {
080            private Class metaClass;
081    
082            public CategoryMethod(MetaMethod metaMethod, Class metaClass) {
083                super(metaMethod);
084                this.metaClass = metaClass;
085            }
086    
087            public boolean isCacheable() { return false; }
088    
089            /**
090             * Sort by most specific to least specific.
091             * @param o
092             * @return
093             */
094            public int compareTo(Object o) {
095                CategoryMethod thatMethod = (CategoryMethod) o;
096                Class thisClass = metaClass;
097                Class thatClass = thatMethod.metaClass;
098                if (thisClass == thatClass) return 0;
099                Class loop = thisClass;
100                while(loop != Object.class) {
101                    loop = thisClass.getSuperclass();
102                    if (loop == thatClass) {
103                        return -1;
104                    }
105                }
106                loop = thatClass;
107                while (loop != Object.class) {
108                    loop = thatClass.getSuperclass();
109                    if (loop == thisClass) {
110                        return 1;
111                    }
112                }
113                return 0;
114            }
115        }
116    
117        /**
118         * This method is delegated to from the global use(CategoryClass) method.  It scans the Category class for static methods
119         * that take 1 or more parameters.  The first parameter is the class you are adding the category method to, additional parameters
120         * are those paramteres needed by that method.  A use statement cannot be undone and is valid only for the current thread.
121         * 
122         * @param categoryClass
123         */
124        private static void use(Class categoryClass) {
125            Map properties = getProperties();
126            Method[] methods = categoryClass.getMethods();
127            for (int i = 0; i < methods.length; i++) {
128                Method method = methods[i];
129                if (Modifier.isStatic(method.getModifiers())) {
130                    Class[] paramTypes = method.getParameterTypes();
131                    if (paramTypes.length > 0) {
132                        Class metaClass = paramTypes[0];
133                        Map metaMethodsMap = getMetaMethods(properties, metaClass);
134                        List methodList = getMethodList(metaMethodsMap, method.getName());
135                        MetaMethod mmethod = new CategoryMethod(new MetaMethod(method), metaClass);
136                        methodList.add(mmethod);
137                        Collections.sort(methodList);
138                    }
139                }
140            }
141        }
142        
143            /**
144             * @param clazz
145             * @param closure
146             */
147            public static void use(Class clazz, Closure closure) {
148                    newScope();
149                    try {
150                            use(clazz);
151                            closure.call();
152                    } finally {
153                            endScope();
154                    }
155            }
156    
157            /**
158             * @param classes
159             * @param closure
160             */
161            public static void use(List classes, Closure closure) {
162                    newScope();
163                    try {
164                            for (Iterator i = classes.iterator(); i.hasNext(); ) {
165                                    Class clazz = (Class) i.next();
166                                    use(clazz);
167                            }
168                            closure.call();
169                    } finally {
170                            endScope();
171                    }               
172            }
173    
174        private static ThreadLocal local = new ThreadLocal() {
175            protected Object initialValue() {
176                            List stack = new ArrayList();
177                            stack.add(Collections.EMPTY_MAP);
178                            return stack;
179                    }
180        };
181        
182        private static void newScope() {
183            List stack = (List) local.get();
184            Map properties = new WeakHashMap(getProperties());
185            stack.add(properties);
186        }
187        
188        private static void endScope() {
189            List stack = (List) local.get();
190            stack.remove(stack.size() - 1);
191        }
192        
193        private static Map getProperties() {
194            List stack = (List) local.get();
195            Map properties = (Map) stack.get(stack.size() - 1);
196            return properties;
197        }
198        
199        /**
200         * @param method
201         * @param metaMethodsMap
202         * @return
203         */
204        private static List getMethodList(Map metaMethodsMap, String name) {
205            List methodList = (List) metaMethodsMap.get(name);
206            if (methodList == null) {
207                methodList = new ArrayList(1);
208                metaMethodsMap.put(name, methodList);
209            }
210            return methodList;
211        }
212    
213        /**
214         * @param properties
215         * @param metaClass
216         * @return
217         */
218        private static Map getMetaMethods(Map properties, Class metaClass) {
219            Map metaMethodsMap = (Map) properties.get(metaClass);
220            if (metaMethodsMap == null) {
221                metaMethodsMap = new HashMap();
222                properties.put(metaClass, metaMethodsMap);
223            }
224            return metaMethodsMap;
225        }
226    
227    }