| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.codehaus.groovy.runtime; |
| |
| import groovy.lang.Closure; |
| import java.lang.ref.SoftReference; |
| |
| import org.codehaus.groovy.reflection.CachedClass; |
| import org.codehaus.groovy.reflection.CachedMethod; |
| import org.codehaus.groovy.reflection.ReflectionCache; |
| import org.codehaus.groovy.runtime.metaclass.DefaultMetaClassInfo; |
| import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod; |
| import org.codehaus.groovy.vmplugin.VMPluginFactory; |
| |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * @author sam |
| * @author Paul King |
| * @author Alex Tkachman |
| */ |
| public class GroovyCategorySupport { |
| |
| private static int categoriesInUse = 0; |
| private static final AtomicInteger atomicCategoryUsageCounter = new AtomicInteger(); |
| |
| public static class CategoryMethodList extends ArrayList<CategoryMethod> { |
| public final int level; |
| final CategoryMethodList previous; |
| final AtomicInteger usage; |
| |
| public CategoryMethodList(String name, int level, CategoryMethodList previous) { |
| this.level = level; |
| this.previous = previous; |
| if (previous != null) { |
| addAll(previous); |
| usage = previous.usage; |
| } |
| else { |
| usage = getCategoryNameUsage (name); |
| } |
| } |
| |
| public boolean add(CategoryMethod o) { |
| usage.incrementAndGet(); |
| return super.add(o); |
| } |
| } |
| |
| public static class ThreadCategoryInfo extends HashMap<String, CategoryMethodList>{ |
| int level; |
| |
| private Map<String, String> propertyGetterMap; |
| private Map<String, String> propertySetterMap; |
| |
| private void newScope () { |
| categoriesInUse = atomicCategoryUsageCounter.incrementAndGet(); |
| DefaultMetaClassInfo.setCategoryUsed(true); |
| VMPluginFactory.getPlugin().invalidateCallSites(); |
| level++; |
| } |
| |
| private void endScope () { |
| for (Iterator<Map.Entry<String, CategoryMethodList>> it = entrySet().iterator(); it.hasNext(); ) { |
| final Map.Entry<String, CategoryMethodList> e = it.next(); |
| final CategoryMethodList list = e.getValue(); |
| if (list.level == level) { |
| final CategoryMethodList prev = list.previous; |
| if (prev == null) { |
| it.remove(); |
| list.usage.addAndGet(-list.size()); |
| } |
| else { |
| e.setValue(prev); |
| list.usage.addAndGet(prev.size()-list.size()); |
| } |
| } |
| } |
| level--; |
| categoriesInUse = atomicCategoryUsageCounter.decrementAndGet(); |
| VMPluginFactory.getPlugin().invalidateCallSites(); |
| if (categoriesInUse==0) DefaultMetaClassInfo.setCategoryUsed(false); |
| if (level == 0) { |
| THREAD_INFO.remove(); |
| } |
| } |
| |
| private <T> T use(Class categoryClass, Closure<T> closure) { |
| newScope(); |
| try { |
| use(categoryClass); |
| return closure.call(); |
| } finally { |
| endScope(); |
| } |
| } |
| |
| public <T> T use(List<Class> categoryClasses, Closure<T> closure) { |
| newScope(); |
| try { |
| for (Class categoryClass : categoryClasses) { |
| use(categoryClass); |
| } |
| return closure.call(); |
| } finally { |
| endScope(); |
| } |
| } |
| |
| private void applyUse(CachedClass cachedClass) { |
| CachedMethod[] methods = cachedClass.getMethods(); |
| for (CachedMethod cachedMethod : methods) { |
| if (cachedMethod.isStatic() && cachedMethod.isPublic()) { |
| CachedClass[] paramTypes = cachedMethod.getParameterTypes(); |
| if (paramTypes.length > 0) { |
| CachedClass metaClass = paramTypes[0]; |
| CategoryMethod mmethod = new CategoryMethod(cachedMethod, metaClass.getTheClass()); |
| final String name = cachedMethod.getName(); |
| CategoryMethodList list = get(name); |
| if (list == null || list.level != level) { |
| list = new CategoryMethodList(name, level, list); |
| put(name, list); |
| } |
| list.add(mmethod); |
| Collections.sort(list); |
| cachePropertyAccessor(mmethod); |
| } |
| } |
| } |
| } |
| |
| private void cachePropertyAccessor(CategoryMethod method) { |
| String name = method.getName(); |
| int parameterLength = method.getParameterTypes().length; |
| |
| if (name.startsWith("get") && name.length() > 3 && parameterLength == 0) { |
| propertyGetterMap = putPropertyAccessor(3, name, propertyGetterMap); |
| } |
| else if (name.startsWith("set") && name.length() > 3 && parameterLength == 1) { |
| propertySetterMap = putPropertyAccessor(3, name, propertySetterMap); |
| } |
| } |
| |
| // Precondition: accessorName.length() > prefixLength |
| private Map<String, String> putPropertyAccessor(int prefixLength, String accessorName, Map<String, String> map) { |
| if (map == null) { |
| map = new HashMap<String, String>(); |
| } |
| String property = accessorName.substring(prefixLength, prefixLength+1).toLowerCase() + accessorName.substring(prefixLength+1); |
| map.put(property, accessorName); |
| return map; |
| } |
| |
| private void use(Class categoryClass) { |
| CachedClass cachedClass = ReflectionCache.getCachedClass(categoryClass); |
| LinkedList<CachedClass> classStack = new LinkedList<CachedClass>(); |
| for (CachedClass superClass = cachedClass; superClass.getTheClass()!=Object.class; superClass = superClass.getCachedSuperClass()) { |
| classStack.add(superClass); |
| } |
| |
| while (!classStack.isEmpty()) { |
| CachedClass klazz = classStack.removeLast(); |
| applyUse(klazz); |
| } |
| } |
| |
| public CategoryMethodList getCategoryMethods(String name) { |
| return level == 0 ? null : get(name); |
| } |
| |
| |
| String getPropertyCategoryGetterName(String propertyName){ |
| return propertyGetterMap != null ? propertyGetterMap.get(propertyName) : null; |
| } |
| |
| String getPropertyCategorySetterName(String propertyName){ |
| return propertySetterMap != null ? propertySetterMap.get(propertyName) : null; |
| } |
| } |
| |
| private static final MyThreadLocal THREAD_INFO = new MyThreadLocal(); |
| |
| public static class CategoryMethod extends NewInstanceMetaMethod implements Comparable { |
| private final Class metaClass; |
| |
| public CategoryMethod(CachedMethod metaMethod, Class metaClass) { |
| super(metaMethod); |
| this.metaClass = metaClass; |
| } |
| |
| public boolean isCacheable() { return false; } |
| |
| /** |
| * Sort by most specific to least specific. |
| * |
| * @param o the object to compare against |
| */ |
| public int compareTo(Object o) { |
| CategoryMethod thatMethod = (CategoryMethod) o; |
| Class thisClass = metaClass; |
| Class thatClass = thatMethod.metaClass; |
| if (thisClass == thatClass) return 0; |
| if (isChildOfParent(thisClass, thatClass)) return -1; |
| if (isChildOfParent(thatClass, thisClass)) return 1; |
| return 0; |
| } |
| |
| private boolean isChildOfParent(Class candidateChild, Class candidateParent) { |
| Class loop = candidateChild; |
| while(loop != null && loop != Object.class) { |
| loop = loop.getSuperclass(); |
| if (loop == candidateParent) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| public static AtomicInteger getCategoryNameUsage (String name) { |
| return THREAD_INFO.getUsage (name); |
| } |
| |
| /** |
| * Create a scope based on given categoryClass and invoke closure within that scope. |
| * |
| * @param categoryClass the class containing category methods |
| * @param closure the closure during which to make the category class methods available |
| * @return the value returned from the closure |
| */ |
| public static <T> T use(Class categoryClass, Closure<T> closure) { |
| return THREAD_INFO.getInfo().use(categoryClass, closure); |
| } |
| |
| /** |
| * Create a scope based on given categoryClasses and invoke closure within that scope. |
| * |
| * @param categoryClasses the list of classes containing category methods |
| * @param closure the closure during which to make the category class methods available |
| * @return the value returned from the closure |
| */ |
| public static <T> T use(List<Class> categoryClasses, Closure<T> closure) { |
| return THREAD_INFO.getInfo().use(categoryClasses, closure); |
| } |
| |
| public static boolean hasCategoryInCurrentThread() { |
| if (categoriesInUse == 0) return false; |
| ThreadCategoryInfo infoNullable = THREAD_INFO.getInfoNullable(); |
| return infoNullable != null && infoNullable.level != 0; |
| } |
| |
| public static boolean hasCategoryInAnyThread() { |
| return atomicCategoryUsageCounter.get() != 0; |
| } |
| |
| /** |
| * This method is used to pull all the new methods out of the local thread context with a particular name. |
| * |
| * @param name the method name of interest |
| * @return the list of methods |
| */ |
| public static CategoryMethodList getCategoryMethods(String name) { |
| final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); |
| return categoryInfo == null ? null : categoryInfo.getCategoryMethods(name); |
| } |
| |
| public static String getPropertyCategoryGetterName(String propertyName) { |
| final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); |
| return categoryInfo == null ? null : categoryInfo.getPropertyCategoryGetterName(propertyName); |
| } |
| |
| public static String getPropertyCategorySetterName(String propertyName) { |
| final ThreadCategoryInfo categoryInfo = THREAD_INFO.getInfoNullable(); |
| return categoryInfo == null ? null : categoryInfo.getPropertyCategorySetterName(propertyName); |
| } |
| |
| private static class MyThreadLocal extends ThreadLocal<SoftReference> { |
| |
| final ConcurrentHashMap<String,AtomicInteger> usage = new ConcurrentHashMap<String,AtomicInteger> (); |
| |
| public ThreadCategoryInfo getInfo() { |
| final SoftReference reference = get(); |
| ThreadCategoryInfo tcinfo; |
| if (reference != null) { |
| tcinfo = (ThreadCategoryInfo) reference.get(); |
| if( tcinfo == null ) { |
| tcinfo = new ThreadCategoryInfo(); |
| set(new SoftReference(tcinfo)); |
| } |
| } |
| else { |
| tcinfo = new ThreadCategoryInfo(); |
| set(new SoftReference(tcinfo)); |
| } |
| return tcinfo; |
| } |
| |
| public ThreadCategoryInfo getInfoNullable() { |
| final SoftReference reference = get(); |
| return reference == null ? null : (ThreadCategoryInfo) reference.get(); |
| } |
| |
| public AtomicInteger getUsage (String name) { |
| AtomicInteger u = usage.get(name); |
| if (u != null) { |
| return u; |
| } |
| |
| final AtomicInteger ai = new AtomicInteger(); |
| final AtomicInteger prev = usage.putIfAbsent(name, ai); |
| return prev == null ? ai : prev; |
| } |
| } |
| } |