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 }