1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package org.codehaus.groovy.runtime;
35
36 import groovy.lang.Closure;
37 import groovy.lang.MetaMethod;
38
39 import java.lang.reflect.Method;
40 import java.lang.reflect.Modifier;
41 import java.util.*;
42
43 /***
44 * @author sam
45 * @author Paul King
46 */
47 public class GroovyCategorySupport {
48
49 private static long categoriesInUse = 0;
50
51 /***
52 * This method is used to pull all the new methods out of the local thread context with a particular name.
53 *
54 * @param categorizedClass a class subject to the category methods in the thread context
55 * @param name the method name of interest
56 * @return the list of methods
57 */
58 public static List getCategoryMethods(Class categorizedClass, String name) {
59 Map properties = getProperties();
60 List methodList = new ArrayList();
61 for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
62 Class current = (Class) i.next();
63 if (current.isAssignableFrom(categorizedClass)) {
64 Map metaMethodsMap = (Map) properties.get(current);
65 List newMethodList = (List) metaMethodsMap.get(name);
66 if (newMethodList != null) {
67 methodList.addAll(newMethodList);
68 }
69 }
70 }
71 if (methodList.size() == 0) return null;
72 return methodList;
73 }
74
75 /***
76 * This method is used to pull all the new methods out of the local thread context.
77 *
78 * @param categorizedClass a class subject to the category methods in the thread context
79 * @return the list of methods
80 */
81 public static List getCategoryMethods(Class categorizedClass) {
82 Map properties = getProperties();
83 List methodList = new ArrayList();
84 for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) {
85 Class current = (Class) i.next();
86 if (current.isAssignableFrom(categorizedClass)) {
87 Map metaMethodsMap = (Map) properties.get(current);
88 Collection collection = metaMethodsMap.values();
89 for (Iterator iterator = collection.iterator(); iterator.hasNext();) {
90 List newMethodList = (List) iterator.next();
91 if (newMethodList != null) {
92 methodList.addAll(newMethodList);
93 }
94 }
95 }
96 }
97 if (methodList.size() == 0) return null;
98 return methodList;
99 }
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 }