blob: 0f5d9f27a93d559b96aa16ac732faed670e6ccda [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.apache.click.util;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import org.apache.click.Context;
import org.apache.click.service.ConfigService;
import org.apache.click.service.PropertyService;
import org.apache.commons.lang.Validate;
/**
* Provide property getter and setter utility methods. This class is provided
* for backward compatibility.
*/
public class PropertyUtils {
/**
* Provides a synchronized cache of get value reflection methods, with
* support for multiple class loaders.
*/
private static final ClassLoaderCache<Map<CacheKey, Method>> GET_METHOD_CLASSLOADER_CACHE
= new ClassLoaderCache<Map<CacheKey, Method>>();
// -------------------------------------------------------- Public Methods
/**
* Return the property value for the given object and property name. This
* method uses reflection internally to get the property value.
* <p/>
* This method is thread-safe, and caches reflected accessor methods in an
* internal synchronized cache.
* <p/>
* If the given source object is a <tt>Map</tt> this method will simply
* return the value for the given key name.
*
* @param source the source object
* @param name the name of the property
* @return the property value for the given source object and property name
*/
public static Object getValue(Object source, String name) {
String basePart = name;
String remainingPart = null;
if (source instanceof Map) {
return ((Map) source).get(name);
}
int baseIndex = name.indexOf(".");
if (baseIndex != -1) {
basePart = name.substring(0, baseIndex);
remainingPart = name.substring(baseIndex + 1);
}
Object value = getObjectPropertyValue(source, basePart, getGetMethodCache());
if (remainingPart == null || value == null) {
return value;
} else {
return getValue(value, remainingPart, getGetMethodCache());
}
}
/**
* Return the property value for the given object and property name. This
* method uses reflection internally to get the property value.
* <p/>
* This method caches the reflected property methods in the given Map cache.
* You must NOT modify the cache. Also note cache is ONLY valid for the
* current thread, as access to the cache is not synchronized. If you need
* multi-threaded access to shared cache use a thread-safe Map object, such
* as <tt>Collections.synchronizedMap(new HashMap())</tt>.
* <p/>
* If the given source object is a <tt>Map</tt> this method will simply
* return the value for the given key name.
*
* @param source the source object
* @param name the name of the property
* @param cache the cache of reflected property Method objects, do NOT modify
* this cache
* @return the property value for the given source object and property name
*/
public static Object getValue(Object source, String name, Map cache) {
Validate.notNull(cache, "Null cache paramenter");
String basePart = name;
String remainingPart = null;
if (source instanceof Map) {
return ((Map) source).get(name);
}
int baseIndex = name.indexOf(".");
if (baseIndex != -1) {
basePart = name.substring(0, baseIndex);
remainingPart = name.substring(baseIndex + 1);
}
Object value = getObjectPropertyValue(source, basePart, cache);
if (remainingPart == null || value == null) {
return value;
} else {
return getValue(value, remainingPart, cache);
}
}
/**
* Return the property value for the given object and property name using the MVEL library.
*
* @param target the target object to set the property of
* @param name the name of the property to set
* @param value the property value to set
*/
public static void setValue(Object target, String name, Object value) {
getPropertyService().setValue(target, name, value);
}
// Private Methods --------------------------------------------------------
/**
* Return the property value for the given object and property name. This
* method uses reflection internally to get the property value.
*
* @param source the source object
* @param name the name of the property
* @param cache the cache of reflected property Method objects
* @return the property value for the given source object and property name
*/
private static Object getObjectPropertyValue(Object source, String name, Map cache) {
CacheKey methodNameKey = new CacheKey(source, name);
Method method = null;
try {
method = (Method) cache.get(methodNameKey);
if (method == null) {
method = source.getClass().getMethod(ClickUtils.toGetterName(name));
cache.put(methodNameKey, method);
}
return method.invoke(source);
} catch (NoSuchMethodException nsme) {
try {
method = source.getClass().getMethod(ClickUtils.toIsGetterName(name));
cache.put(methodNameKey, method);
return method.invoke(source);
} catch (NoSuchMethodException nsme2) {
try {
method = source.getClass().getMethod(name);
cache.put(methodNameKey, method);
return method.invoke(source);
} catch (NoSuchMethodException nsme3) {
String msg = "No matching getter method found for property '"
+ name + "' on class " + source.getClass().getName();
throw new RuntimeException(msg);
} catch (Exception e) {
String msg = "Error getting property '" + name + "' from " + source.getClass();
throw new RuntimeException(msg, e);
}
} catch (Exception e) {
String msg = "Error getting property '" + name + "' from " + source.getClass();
throw new RuntimeException(msg, e);
}
} catch (Exception e) {
String msg = "Error getting property '" + name + "' from " + source.getClass();
throw new RuntimeException(msg, e);
}
}
private static PropertyService getPropertyService() {
ServletContext servletContext =
Context.getThreadLocalContext().getServletContext();
ConfigService configService = ClickUtils.getConfigService(servletContext);
return configService.getPropertyService();
}
private static Map<CacheKey, Method> getGetMethodCache() {
Map<CacheKey, Method> getMethodCache = GET_METHOD_CLASSLOADER_CACHE.get();
if (getMethodCache == null) {
getMethodCache = new ConcurrentHashMap<CacheKey, Method>();
GET_METHOD_CLASSLOADER_CACHE.put(getMethodCache);
}
return getMethodCache;
}
// Inner Classes ----------------------------------------------------------
/**
* See DRY Performance article by Kirk Pepperdine.
* <p/>
* http://www.javaspecialists.eu/archive/Issue134.html
*/
public static class CacheKey {
/** Class to encapsulate in cache key. */
private final Class sourceClass;
/** Property to encapsulate in cache key. */
private final String property;
/**
* Constructs a new CacheKey for the given object and property.
*
* @param source the object to build the cache key for
* @param property the property to build the cache key for
*/
public CacheKey(Object source, String property) {
if (source == null) {
throw new IllegalArgumentException("Null source parameter");
}
if (property == null) {
throw new IllegalArgumentException("Null property parameter");
}
this.sourceClass = source.getClass();
this.property = property;
}
/**
* @see Object#equals(Object)
*
* @param o the object with which to compare this instance with
* @return true if the specified object is the same as this object
*/
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CacheKey)) {
return false;
}
CacheKey that = (CacheKey) o;
if (!sourceClass.equals(that.sourceClass)) {
return false;
}
if (!property.equals(that.property)) {
return false;
}
return true;
}
/**
* @see Object#hashCode()
*
* @return a hash code value for this object.
*/
@Override
public final int hashCode() {
return sourceClass.hashCode() * 31 + property.hashCode();
}
}
}