Fast Property accessor for a single class.
/** * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> 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<String, NestedHandler> setters = new HashMap<String, NestedHandler>(); /** * getter method cache. */ private Map<String, NestedHandler> getters = new HashMap<String, NestedHandler>(); /** * 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<Member> members; private List<Member> setMembers; /** * single method handler. * * @param member * delegated member */ public NestedHandler(Member member) { members = new ArrayList<Member>(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<Member>(split.length - 1); setMembers = new ArrayList<Member>(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); } }