001 /* 002 * $Id: GroovyCategorySupport.java,v 1.7 2005/06/19 19:01:25 spullara Exp $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.ArrayList; 042 import java.util.Collections; 043 import java.util.HashMap; 044 import java.util.Iterator; 045 import java.util.List; 046 import java.util.Map; 047 import java.util.WeakHashMap; 048 049 050 /** 051 * @author sam 052 */ 053 public class GroovyCategorySupport { 054 055 /** 056 * This method is used to pull all the new methods out of the local thread context with a particular name. 057 * 058 * @param categorizedClass 059 * @param name 060 * @return 061 */ 062 public static List getCategoryMethods(Class categorizedClass, String name) { 063 Map properties = getProperties(); 064 List methodList = new ArrayList(); 065 for (Iterator i = properties.keySet().iterator(); i.hasNext(); ) { 066 Class current = (Class) i.next(); 067 if (current.isAssignableFrom(categorizedClass)) { 068 Map metaMethodsMap = (Map) properties.get(current); 069 List newMethodList = (List) metaMethodsMap.get(name); 070 if (newMethodList != null) { 071 methodList.addAll(newMethodList); 072 } 073 } 074 } 075 if (methodList.size() == 0) return null; 076 return methodList; 077 } 078 079 private static class CategoryMethod extends NewInstanceMetaMethod implements Comparable { 080 private Class metaClass; 081 082 public CategoryMethod(MetaMethod metaMethod, Class metaClass) { 083 super(metaMethod); 084 this.metaClass = metaClass; 085 } 086 087 public boolean isCacheable() { return false; } 088 089 /** 090 * Sort by most specific to least specific. 091 * @param o 092 * @return 093 */ 094 public int compareTo(Object o) { 095 CategoryMethod thatMethod = (CategoryMethod) o; 096 Class thisClass = metaClass; 097 Class thatClass = thatMethod.metaClass; 098 if (thisClass == thatClass) return 0; 099 Class loop = thisClass; 100 while(loop != Object.class) { 101 loop = thisClass.getSuperclass(); 102 if (loop == thatClass) { 103 return -1; 104 } 105 } 106 loop = thatClass; 107 while (loop != Object.class) { 108 loop = thatClass.getSuperclass(); 109 if (loop == thisClass) { 110 return 1; 111 } 112 } 113 return 0; 114 } 115 } 116 117 /** 118 * This method is delegated to from the global use(CategoryClass) method. It scans the Category class for static methods 119 * that take 1 or more parameters. The first parameter is the class you are adding the category method to, additional parameters 120 * are those paramteres needed by that method. A use statement cannot be undone and is valid only for the current thread. 121 * 122 * @param categoryClass 123 */ 124 private static void use(Class categoryClass) { 125 Map properties = getProperties(); 126 Method[] methods = categoryClass.getMethods(); 127 for (int i = 0; i < methods.length; i++) { 128 Method method = methods[i]; 129 if (Modifier.isStatic(method.getModifiers())) { 130 Class[] paramTypes = method.getParameterTypes(); 131 if (paramTypes.length > 0) { 132 Class metaClass = paramTypes[0]; 133 Map metaMethodsMap = getMetaMethods(properties, metaClass); 134 List methodList = getMethodList(metaMethodsMap, method.getName()); 135 MetaMethod mmethod = new CategoryMethod(new MetaMethod(method), metaClass); 136 methodList.add(mmethod); 137 Collections.sort(methodList); 138 } 139 } 140 } 141 } 142 143 /** 144 * @param clazz 145 * @param closure 146 */ 147 public static void use(Class clazz, Closure closure) { 148 newScope(); 149 try { 150 use(clazz); 151 closure.call(); 152 } finally { 153 endScope(); 154 } 155 } 156 157 /** 158 * @param classes 159 * @param closure 160 */ 161 public static void use(List classes, Closure closure) { 162 newScope(); 163 try { 164 for (Iterator i = classes.iterator(); i.hasNext(); ) { 165 Class clazz = (Class) i.next(); 166 use(clazz); 167 } 168 closure.call(); 169 } finally { 170 endScope(); 171 } 172 } 173 174 private static ThreadLocal local = new ThreadLocal() { 175 protected Object initialValue() { 176 List stack = new ArrayList(); 177 stack.add(Collections.EMPTY_MAP); 178 return stack; 179 } 180 }; 181 182 private static void newScope() { 183 List stack = (List) local.get(); 184 Map properties = new WeakHashMap(getProperties()); 185 stack.add(properties); 186 } 187 188 private static void endScope() { 189 List stack = (List) local.get(); 190 stack.remove(stack.size() - 1); 191 } 192 193 private static Map getProperties() { 194 List stack = (List) local.get(); 195 Map properties = (Map) stack.get(stack.size() - 1); 196 return properties; 197 } 198 199 /** 200 * @param method 201 * @param metaMethodsMap 202 * @return 203 */ 204 private static List getMethodList(Map metaMethodsMap, String name) { 205 List methodList = (List) metaMethodsMap.get(name); 206 if (methodList == null) { 207 methodList = new ArrayList(1); 208 metaMethodsMap.put(name, methodList); 209 } 210 return methodList; 211 } 212 213 /** 214 * @param properties 215 * @param metaClass 216 * @return 217 */ 218 private static Map getMetaMethods(Map properties, Class metaClass) { 219 Map metaMethodsMap = (Map) properties.get(metaClass); 220 if (metaMethodsMap == null) { 221 metaMethodsMap = new HashMap(); 222 properties.put(metaClass, metaMethodsMap); 223 } 224 return metaMethodsMap; 225 } 226 227 }