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,
059: String name) {
060: Map properties = getProperties();
061: List methodList = new ArrayList();
062: for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
063: Class current = (Class) i.next();
064: if (current.isAssignableFrom(categorizedClass)) {
065: Map metaMethodsMap = (Map) properties.get(current);
066: List newMethodList = (List) metaMethodsMap.get(name);
067: if (newMethodList != null) {
068: methodList.addAll(newMethodList);
069: }
070: }
071: }
072: if (methodList.size() == 0)
073: return null;
074: return methodList;
075: }
076:
077: /**
078: * This method is used to pull all the new methods out of the local thread context.
079: *
080: * @param categorizedClass a class subject to the category methods in the thread context
081: * @return the list of methods
082: */
083: public static List getCategoryMethods(Class categorizedClass) {
084: Map properties = getProperties();
085: List methodList = new ArrayList();
086: for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
087: Class current = (Class) i.next();
088: if (current.isAssignableFrom(categorizedClass)) {
089: Map metaMethodsMap = (Map) properties.get(current);
090: Collection collection = metaMethodsMap.values();
091: for (Iterator iterator = collection.iterator(); iterator
092: .hasNext();) {
093: List newMethodList = (List) iterator.next();
094: if (newMethodList != null) {
095: methodList.addAll(newMethodList);
096: }
097: }
098: }
099: }
100: if (methodList.size() == 0)
101: return null;
102: return methodList;
103: }
104:
105: private static class CategoryMethod extends NewInstanceMetaMethod
106: implements Comparable {
107: private Class metaClass;
108:
109: public CategoryMethod(MetaMethod metaMethod, Class metaClass) {
110: super (metaMethod);
111: this .metaClass = metaClass;
112: }
113:
114: public boolean isCacheable() {
115: return false;
116: }
117:
118: /**
119: * Sort by most specific to least specific.
120: *
121: * @param o the object to compare against
122: */
123: public int compareTo(Object o) {
124: CategoryMethod thatMethod = (CategoryMethod) o;
125: Class this Class = metaClass;
126: Class thatClass = thatMethod.metaClass;
127: if (this Class == thatClass)
128: return 0;
129: Class loop = this Class;
130: while (loop != Object.class) {
131: loop = this Class.getSuperclass();
132: if (loop == thatClass) {
133: return -1;
134: }
135: }
136: loop = thatClass;
137: while (loop != Object.class) {
138: loop = thatClass.getSuperclass();
139: if (loop == this Class) {
140: return 1;
141: }
142: }
143: return 0;
144: }
145: }
146:
147: /**
148: * Create a scope based on given categoryClass and invoke closure within that scope.
149: *
150: * @param categoryClass the class containing category methods
151: * @param closure the closure during which to make the category class methods available
152: */
153: public static void use(Class categoryClass, Closure closure) {
154: newScope();
155: try {
156: use(categoryClass);
157: closure.call();
158: } finally {
159: endScope();
160: }
161: }
162:
163: /**
164: * Create a scope based on given categoryClasses and invoke closure within that scope.
165: *
166: * @param categoryClasses the list of classes containing category methods
167: * @param closure the closure during which to make the category class methods available
168: */
169: public static void use(List categoryClasses, Closure closure) {
170: newScope();
171: try {
172: for (Iterator i = categoryClasses.iterator(); i.hasNext();) {
173: Class clazz = (Class) i.next();
174: use(clazz);
175: }
176: closure.call();
177: } finally {
178: endScope();
179: }
180: }
181:
182: /**
183: * Delegated to from the global use(CategoryClass) method. It scans the Category class for static methods
184: * that take 1 or more parameters. The first parameter is the class you are adding the category method to,
185: * additional parameters are those paramteres needed by that method. A use statement cannot be undone and
186: * is valid only for the current thread.
187: *
188: * @param categoryClass the class containing category methods
189: */
190: private static void use(Class categoryClass) {
191: Map properties = getProperties();
192: Method[] methods = categoryClass.getMethods();
193: for (int i = 0; i < methods.length; i++) {
194: Method method = methods[i];
195: if (Modifier.isStatic(method.getModifiers())) {
196: Class[] paramTypes = method.getParameterTypes();
197: if (paramTypes.length > 0) {
198: Class metaClass = paramTypes[0];
199: Map metaMethodsMap = getMetaMethods(properties,
200: metaClass);
201: List methodList = getMethodList(metaMethodsMap,
202: method.getName());
203: MetaMethod mmethod = new CategoryMethod(
204: new MetaMethod(method), metaClass);
205: methodList.add(mmethod);
206: Collections.sort(methodList);
207: }
208: }
209: }
210: }
211:
212: private static ThreadLocal local = new ThreadLocal() {
213: protected Object initialValue() {
214: List stack = new ArrayList();
215: stack.add(Collections.EMPTY_MAP);
216: return stack;
217: }
218: };
219:
220: private static void newScope() {
221: categoriesInUse++;
222: List stack = (List) local.get();
223: Map properties = new WeakHashMap(getProperties());
224: stack.add(properties);
225: }
226:
227: private static void endScope() {
228: List stack = (List) local.get();
229: stack.remove(stack.size() - 1);
230: categoriesInUse--;
231: }
232:
233: private static Map getProperties() {
234: List stack = (List) local.get();
235: return (Map) stack.get(stack.size() - 1);
236: }
237:
238: public static boolean hasCategoryInAnyThread() {
239: return categoriesInUse != 0;
240: }
241:
242: private static List getMethodList(Map metaMethodsMap, String name) {
243: List methodList = (List) metaMethodsMap.get(name);
244: if (methodList == null) {
245: methodList = new ArrayList(1);
246: metaMethodsMap.put(name, methodList);
247: }
248: return methodList;
249: }
250:
251: private static Map getMetaMethods(Map properties, Class metaClass) {
252: Map metaMethodsMap = (Map) properties.get(metaClass);
253: if (metaMethodsMap == null) {
254: metaMethodsMap = new HashMap();
255: properties.put(metaClass, metaMethodsMap);
256: }
257: return metaMethodsMap;
258: }
259:
260: }
|