/**
* Copyright (C) 2010 altuure http://www.altuure.com/projects/yagdao
*
* 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 com.altuure.yagdao.common;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Fast Property accessor for a single class.
*
* @author altuure
*/
@SuppressWarnings("rawtypes")
public class FastPropertyUtil {
public static final Log log = LogFactory.getLog(FastPropertyUtil.class);
/**
* init setter and getter lazy.
*/
public static final boolean LAZY = true;
/**
* Get method prefix.
*/
private static final String GET = "get";
/**
* is method prefix.
*/
private static final String IS = "is";
/**
* set method prefix.
*/
private static final String SET = "set";
/**
* set method cache.
*/
private Map setters = new HashMap();
/**
* getter method cache.
*/
private Map getters = new HashMap();
/**
* wrapped class .
*/
private Class adaptedClass;
/**
* create new instance for given class.
*
* @see #LAZY
* @param adaptedClass
* wrapped class
*/
public FastPropertyUtil(Class adaptedClass) {
this(adaptedClass, LAZY);
}
/**
* create new instance for given class .
*
* @param adaptedClass
* wrapped class
* @param initLazy
* true if init lazy or false init now
*/
public FastPropertyUtil(Class adaptedClass, boolean initLazy) {
super();
this.adaptedClass = adaptedClass;
init(initLazy);
}
/**
* set object property value.
*
* @param obj
* object
* @param property
* property
* @param value
* value
* @throws IllegalArgumentException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws IllegalAccessException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws InvocationTargetException
* underlying exception
* @throws InstantiationException
* new instance error
* @see {@link Method#invoke(Object, Object...)}
*/
public void set(Object obj, String property, Object value)
throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException, InstantiationException {
if (setters.containsKey(property)) {
NestedHandler member = setters.get(property);
member.set(obj, value);
} else {
NestedHandler nestedHandler = new NestedHandler(property, true);
setters.put(property, nestedHandler);
nestedHandler.set(obj, value);
}
}
/**
* get object property value.
*
* @param obj
* object
* @param property
* property
* @return value
* @throws IllegalArgumentException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws IllegalAccessException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws InvocationTargetException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
*/
public Object get(Object obj, String property)
throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
if (getters.containsKey(property)) {
NestedHandler member = getters.get(property);
return member.get(obj);
} else {
NestedHandler nestedHandler = new NestedHandler(property, false);
getters.put(property, nestedHandler);
return nestedHandler.get(obj);
}
}
/**
* goes through fields and initialize caches setters and getters.
*
* @param initLazy
* is lazy
*/
private void init(boolean initLazy) {
if (initLazy)
return;
Field[] fields = adaptedClass.getFields();
for (Field field : fields) {
field.setAccessible(true);
setters.put(field.getName(), new NestedHandler(field));
getters.put(field.getName(), new NestedHandler(field));
}
Method[] methods = adaptedClass.getMethods();
for (Method method : methods) {
if (isPropertyAccessor(method))
continue;// not a property accessor;
String propertyName = getPropertyAccessor(method);
if (propertyName != null)
getters.put(propertyName, new NestedHandler(method));
propertyName = setPropertyAccessor(method);
if (propertyName != null)
setters.put(propertyName, new NestedHandler(method));
}
}
/**
* return a property name for setter methods .
*
* @param method
* method
* @return null if not starts with set
*/
private String setPropertyAccessor(Method method) {
return propertyAccessor(method, SET);
}
/**
* return a property name for getter methods .
*
* @param method
* method
* @return null if not starts with is or get
*/
private String getPropertyAccessor(Method method) {
return propertyAccessor(method, IS, GET);
}
/**
* check if the method starts with given prefix and if yes retriev the
* property name.
*
* @param method
* method
* @param prefixs
* potential prefix s
* @return null is not available
*/
private String propertyAccessor(Method method, String... prefixs) {
String name = method.getName();
String property;
String prefix = null;
for (String prefixCandidate : prefixs) {
if (name.startsWith(prefixCandidate)) {
prefix = prefixCandidate;
break;
}
}
// if not qualified accessor return null
if (prefix == null)
return null;
if (name.length() < prefix.length() + 1)
return null;
property = name.substring(prefix.length(), prefix.length() + 1)
.toLowerCase(Locale.ENGLISH);
if (name.length() > prefix.length() + 2)
property += name.substring(prefix.length() + 1);
return property;
}
/**
* checks if there is any valid prefix: is,get,get.
*
* @param method
* method to be check
* @return true if there is any parameter
*/
private boolean isPropertyAccessor(Method method) {
String name = method.getName();
return name.startsWith(IS) || name.startsWith(GET)
|| name.startsWith(SET);
}
/**
* property handle property.
*
* @author altuure
*/
public class NestedHandler {
/**
* list of access members
*/
private List members;
private List setMembers;
/**
* single method handler.
*
* @param member
* delegated member
*/
public NestedHandler(Member member) {
members = new ArrayList(1);
members.add(member);
}
/**
* create instance for given class for given property.
*
* @param property
* property eg: name or product.name
* @param writeOperation
* true if set else false
*/
public NestedHandler(String property, boolean writeOperation) {
initThis(writeOperation, property);
}
/**
* parse property and find access method.
*
* @param writeOperation
* true if set else false
* @param property
* property
*/
private void initThis(boolean writeOperation, String property) {
String[] split = splitDot(property);
members = new ArrayList(split.length - 1);
setMembers = new ArrayList(split.length - 1);
Class currentClass = adaptedClass;
for (int i = 0; i < split.length - 1; i++) {
String string = split[i];
Member member;
member = findAccessor(currentClass, i, string, GET, IS);
Member setMember = findAccessor(currentClass, i, string, SET);
if (member == null)
throw new IllegalArgumentException(
"no such property found " + currentClass.getName()
+ "." + string);
members.add(member);
setMembers.add(setMember);
if (member instanceof Method) {
Method m = (Method) member;
currentClass = m.getReturnType();
} else if (member instanceof Field) {
Field m = (Field) member;
currentClass = m.getType();
}
}
Member lastMember;
String lastProperty = split[split.length - 1];
if (writeOperation) {
lastMember = findAccessor(currentClass, split.length - 1,
lastProperty, SET);
} else {
lastMember = findAccessor(currentClass, split.length - 1,
lastProperty, GET, IS);
}
if (lastMember == null)
throw new IllegalArgumentException("no such property found "
+ currentClass.getName() + "." + lastProperty);
members.add(lastMember);
}
/**
* find access method or field for given class.
*
* @param currentClass
* given class
* @param i
* depth
* @param property
* property with no .
* @param prefixs
* possible method prefixs
* @return null if not found
*/
@SuppressWarnings("unchecked")
private Member findAccessor(Class currentClass, int i, String property,
String... prefixs) {
Member member = null;
if (i == 0 && getters.containsKey(property)) {
member = getters.get(property).getLastMember();
} else {
for (String prefix : prefixs) {
try {
member = findMethod(currentClass, property, prefix);
break;
} catch (NoSuchMethodException e) {
if (log.isDebugEnabled())
log.debug("method not found: "
+ currentClass.getName() + "#" + prefix
+ capitilize(property));
} catch (SecurityException e) {
if (log.isDebugEnabled())
log.debug("method is not accessible: "
+ currentClass.getName() + "#" + prefix
+ capitilize(property));
}
}
try {
if (member == null) {
final Field field = currentClass.getField(property);
field.setAccessible(true);
member = field;
}
} catch (NoSuchFieldException e) {
if (log.isDebugEnabled())
log.debug("field is not found: "
+ currentClass.getName() + "#" + property);
}
}
return member;
}
private Method findMethod(Class currentClass, String property,
String prefix) throws NoSuchMethodException {
String name = prefix + capitilize(property);
try {
return currentClass.getMethod(name);
} catch (NoSuchMethodException e) {
log.debug("method not found", e);
}
Method[] methods = currentClass.getMethods();
for (Method method : methods) {
if (method.getName().equals(name))
return method;
}
throw new NoSuchMethodException("no such method found in "
+ currentClass.getName() + ":" + name);
}
/**
* get object property value.
*
* @param obj
* object
* @return value
* @throws IllegalArgumentException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws IllegalAccessException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws InvocationTargetException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
*/
public Object get(Object obj) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
Object currentObject = obj;
for (Member member : members) {
currentObject = executeGetterMember(currentObject, member);
}
return currentObject;
}
/**
* set object property value.
*
* @param obj
* object
* @param value
* property value
* @throws IllegalArgumentException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws IllegalAccessException
* underlying exception
* @see {@link Method#invoke(Object, Object...)}
* @throws InvocationTargetException
* underlying exception
* @throws InstantiationException
* new instance error
* @see {@link Method#invoke(Object, Object...)}
*/
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException, InstantiationException {
Object currentObject = obj;
for (int i = 0; i < members.size(); i++) {
Member member = members.get(i);
if (i < members.size() - 1) {
Object tempObject = executeGetterMember(currentObject,
member);
if (tempObject == null) {
Member setMember = setMembers.get(i);
tempObject = initSetterMember(currentObject, setMember);
}
currentObject = tempObject;
} else {
currentObject = executeSetterMember(currentObject, member,
value);
}
}
}
/**
* execute setter method or field.
*
* @param currentObject
* obj
* @param member
* member
* @param value
* set value
* @return get value
* @throws IllegalAccessException
* delegated exception
* @throws java.lang.reflect.InvocationTargetException
* delegated exception
*/
private Object executeSetterMember(Object currentObject, Member member,
Object value) throws IllegalAccessException,
InvocationTargetException {
if (member instanceof Method) {
Method m = (Method) member;
currentObject = m.invoke(currentObject, value);
} else if (member instanceof Field) {
Field m = (Field) member;
m.set(currentObject, value);
currentObject = null;
}
return currentObject;
}
private Object initSetterMember(Object currentObject, Member member)
throws IllegalAccessException, InvocationTargetException,
InstantiationException {
Object value = null;
if (member instanceof Method) {
Method m = (Method) member;
value = m.getParameterTypes()[0].newInstance();
m.invoke(currentObject, value);
} else if (member instanceof Field) {
Field m = (Field) member;
value = m.getType().newInstance();
m.set(currentObject, value);
}
return value;
}
/**
* Execute getter method or field.
*
* @param currentObject
* obj
* @param member
* executed value
* @return return value
* @throws IllegalAccessException
* delegated exception
* @throws java.lang.reflect.InvocationTargetException
* delegated exception
*/
private Object executeGetterMember(Object currentObject, Member member)
throws IllegalAccessException, InvocationTargetException {
if (member instanceof Method) {
Method m = (Method) member;
currentObject = m.invoke(currentObject);
} else if (member instanceof Field) {
Field m = (Field) member;
currentObject = m.get(currentObject);
}
return currentObject;
}
/**
* last property field or method.
*
* @return member
*/
public Member getLastMember() {
return members.get(members.size() - 1);
}
}
/**
* upperCase only first string.
*
* @param text
* whole input text
* @return capitilized text
*/
public static String capitilize(String text) {
if (text == null)
return null;
if (text.length() == 0)
return text;
String result = text.substring(0, 1).toUpperCase(Locale.ENGLISH);
if (text.length() > 1)
result += text.substring(1);
return result;
}
/**
* split text with dot seperator.
*
* @param text
* text to split
* @return splitted text
*/
public static String[] splitDot(String text) {
String DOT_SEPERATOR_REGEX = "\\.";
return text.split(DOT_SEPERATOR_REGEX);
}
}