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 }