View Javadoc

1   /*
2    * $Id: GroovyCategorySupport.java 4270 2006-11-27 21:43:01Z blackdrag $version Apr 26, 2004 4:22:50 PM $user Exp $
3    * 
4    * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
5    * 
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met: 1. Redistributions of source code must retain
9    * copyright statements and notices. Redistributions must also contain a copy
10   * of this document. 2. Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following disclaimer in
12   * the documentation and/or other materials provided with the distribution. 3.
13   * The name "groovy" must not be used to endorse or promote products derived
14   * from this Software without prior written permission of The Codehaus. For
15   * written permission, please contact info@codehaus.org. 4. Products derived
16   * from this Software may not be called "groovy" nor may "groovy" appear in
17   * their names without prior written permission of The Codehaus. "groovy" is a
18   * registered trademark of The Codehaus. 5. Due credit should be given to The
19   * Codehaus - http://groovy.codehaus.org/
20   * 
21   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
22   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31   * DAMAGE.
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 }