/* | |
* 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(); | |
} | |
} | |
} |