Reflection Java

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