blob: b43de25eeac0f8f7610c5c73032dc6fa256a95f7 [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed 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.metaclass;
import groovy.lang.Closure;
import groovy.lang.MetaBeanProperty;
import groovy.lang.MetaMethod;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.WeakHashMap;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.ReflectionCache;
/**
* This MetaBeanProperty will create a psuedo property whoes value is bound to the current
* Thread using soft references. The values will go out of scope and be garabage collected when
* the Thread dies or when memory is required by the JVM
*
* The property uses a InheritableThreadLocal instance internally so child threads will still be able
* to see the property
*
* @author Graeme Rocher
* @since 1.1
*
*/
public class ThreadManagedMetaBeanProperty extends MetaBeanProperty {
private static final CachedClass[] ZERO_ARGUMENT_LIST = new CachedClass[0];
private static final ThreadLocal PROPERTY_INSTANCE_HOLDER = new InheritableThreadLocal();
private Class declaringClass;
private ThreadBoundGetter getter;
private ThreadBoundSetter setter;
private Object initialValue;
private static final String PROPERTY_SET_PREFIX = "set";
private Closure initialValueCreator;
/**
* Retrieves the initial value of the ThreadBound property
*
* @return The initial value
*/
public synchronized Object getInitialValue() {
return getInitialValue(null);
}
public synchronized Object getInitialValue(Object object) {
if(initialValueCreator != null) {
return initialValueCreator.call(object);
}
return initialValue;
}
/**
* Closure responsible for creating the initial value of thread managed bean properties
*
* @param callable The closure responsible for creating the initial value
*/
public void setInitialValueCreator(Closure callable) {
this.initialValueCreator = callable;
}
/**
* Constructs a new ThreadManagedBeanProperty for the given arguments
*
* @param declaringClass The class that declares the property
* @param name The name of the property
* @param type The type of the property
* @param iv The properties initial value
*/
public ThreadManagedMetaBeanProperty(Class declaringClass, String name, Class type, Object iv) {
super(name, type, null,null);
this.type = type;
this.declaringClass = declaringClass;
this.getter = new ThreadBoundGetter(name);
this.setter = new ThreadBoundSetter(name);
initialValue = iv;
}
/**
* Constructs a new ThreadManagedBeanProperty for the given arguments
*
* @param declaringClass The class that declares the property
* @param name The name of the property
* @param type The type of the property
* @param initialValueCreator The closure responsible for creating the initial value
*/
public ThreadManagedMetaBeanProperty(Class declaringClass, String name, Class type, Closure initialValueCreator) {
super(name, type, null,null);
this.type = type;
this.declaringClass = declaringClass;
this.getter = new ThreadBoundGetter(name);
this.setter = new ThreadBoundSetter(name);
this.initialValueCreator = initialValueCreator;
}
private static Object getThreadBoundPropertyValue(Object obj, String name, Object initialValue) {
Map propertyMap = getThreadBoundPropertMap();
String key = System.identityHashCode(obj) + name;
if(propertyMap.containsKey(key)) {
return propertyMap.get(key);
}
else {
propertyMap.put(key, initialValue);
return initialValue;
}
}
private static Map getThreadBoundPropertMap() {
Map propertyMap = (Map) PROPERTY_INSTANCE_HOLDER.get();
if(propertyMap == null) {
propertyMap = new WeakHashMap();
PROPERTY_INSTANCE_HOLDER.set(propertyMap);
}
return propertyMap;
}
private static Object setThreadBoundPropertyValue(Object obj, String name, Object value) {
Map propertyMap = getThreadBoundPropertMap();
String key = System.identityHashCode(obj) + name;
return propertyMap.put(key,value);
}
/* (non-Javadoc)
* @see groovy.lang.MetaBeanProperty#getGetter()
*/
public MetaMethod getGetter() {
return this.getter;
}
/* (non-Javadoc)
* @see groovy.lang.MetaBeanProperty#getSetter()
*/
public MetaMethod getSetter() {
return this.setter;
}
/**
* Accesses the ThreadBound state of the property as a getter
*
* @author Graeme Rocher
*
*/
class ThreadBoundGetter extends MetaMethod {
private String getterName;
public ThreadBoundGetter(String name) {
super(name, declaringClass, ZERO_ARGUMENT_LIST, type, Modifier.PUBLIC);
getterName = getGetterName(name, type);
}
/* (non-Javadoc)
* @see groovy.lang.MetaMethod#getName()
*/
public String getName() {
return getterName;
}
/* (non-Javadoc)
* @see groovy.lang.MetaMethod#invoke(java.lang.Object, java.lang.Object[])
*/
public Object invoke(Object object, Object[] arguments) {
return getThreadBoundPropertyValue(object, name, getInitialValue());
}
}
/**
* Sets the ThreadBound state of the property like a setter
*
* @author Graeme Rocher
*
*/
private class ThreadBoundSetter extends MetaMethod {
private String setterName;
public ThreadBoundSetter(String name) {
super(name, declaringClass, new CachedClass[]{ReflectionCache.getCachedClass(type)}, type, Modifier.PUBLIC);
setterName = getSetterName(name);
}
/* (non-Javadoc)
* @see groovy.lang.MetaMethod#getName()
*/
public String getName() {
return setterName;
}
/* (non-Javadoc)
* @see groovy.lang.MetaMethod#invoke(java.lang.Object, java.lang.Object[])
*/
public Object invoke(Object object, Object[] arguments) {
return setThreadBoundPropertyValue(object, name, arguments[0]);
}
}
private String getGetterName(String propertyName, Class type)
{
String prefix = type == boolean.class || type == Boolean.class ? "is" : "get";
return prefix + Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1);
}
private String getSetterName(String propertyName) {
return PROPERTY_SET_PREFIX+propertyName.substring(0,1).toUpperCase()+ propertyName.substring(1);
}
}