blob: 4c36935df7b3a977ca03b2bee5d9da4ce4dfc16a [file] [log] [blame]
/*
* 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;
}
}
}