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 }