Package er.ajax.json.serializer

Source Code of er.ajax.json.serializer.ERXBeanSerializer$BeanData

package er.ajax.json.serializer;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jabsorb.serializer.AbstractSerializer;
import org.jabsorb.serializer.MarshallException;
import org.jabsorb.serializer.ObjectMatch;
import org.jabsorb.serializer.SerializerState;
import org.jabsorb.serializer.UnmarshallException;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import er.extensions.foundation.ERXStringUtilities;

/**
* ERXBeanSerializer is a rip-off of BeanSerializer except that it supports
* WO-style naming (i.e. missing "get").
*/
public class ERXBeanSerializer extends AbstractSerializer {
  /**
   * Stores the readable and writable properties for the Bean.
   */
  protected static class BeanData {
    // TODO: Legacy comment. WTF?
    // in absence of getters and setters, these fields are
    // public to allow subclasses to access.
    /**
     * The bean info for a certain bean
     */
    public BeanInfo beanInfo;

    /**
     * The readable properties of the bean.
     */
    public Map<String, Method> readableProps;

    /**
     * The writable properties of the bean.
     */
    public Map<String, Method> writableProps;
  }

  /**
   * Unique serialisation id.
   */
  private final static long serialVersionUID = 2;

  /**
   * The logger for this class
   */
  private final static Logger log = LoggerFactory.getLogger(ERXBeanSerializer.class);

  /**
   * Caches analysed beans
   */
  private static Map<Class<?>, BeanData> beanCache = new HashMap<Class<?>, BeanData>();

  /**
   * Classes that this can serialise to.
   *
   * TODO: Yay for bloat!
   */
  private static Class<?>[] _JSONClasses = new Class[] {};

  private static Set<String> _ignoreMethodNames = new HashSet<String>();
 
  static {
    _ignoreMethodNames = new HashSet<String>();
    _ignoreMethodNames.add("equals");
    _ignoreMethodNames.add("hashCode");
    _ignoreMethodNames.add("main");
    _ignoreMethodNames.add("declaringClass");
  }
 
  private Class _clazz;
 
  /**
   * Analyses a bean, returning a BeanData with the data extracted from it.
   *
   * @param clazz The class of the bean to analyse
   * @return A populated BeanData
   * @throws IntrospectionException If a problem occurs during getting the bean
   *           info.
   */
  public static BeanData analyzeBean(Class<?> clazz) throws IntrospectionException {
    log.info("analyzing " + clazz.getName());
    BeanData bd = new BeanData();
    bd.beanInfo = Introspector.getBeanInfo(clazz, Object.class);
    bd.readableProps = new HashMap<String, Method>();
    bd.writableProps = new HashMap<String, Method>();
    for (MethodDescriptor md : bd.beanInfo.getMethodDescriptors()) {
      Method method = md.getMethod();
      if (!Modifier.isStatic(method.getModifiers())) {
        String name = method.getName();
        if (!_ignoreMethodNames.contains(name)) {
          Class<?> returnType = method.getReturnType();
          String propertyName = name;
          if (propertyName.startsWith("get") || propertyName.startsWith("set")) {
            propertyName = ERXStringUtilities.uncapitalize(propertyName.substring("set".length()));
          }
          if (returnType == void.class && name.startsWith("set") && method.getParameterTypes().length == 1) {
            bd.writableProps.put(propertyName, method);
          }
          else if (returnType != null && method.getParameterTypes().length == 0 && !name.startsWith("_")) {
            bd.readableProps.put(propertyName, method);
          }
        }
      }
    }
    return bd;
  }

  /**
   * Gets the bean data from cache if possible, otherwise analyses the bean.
   *
   * @param clazz The class of the bean to analyse
   * @return A populated BeanData
   * @throws IntrospectionException If a problem occurs during getting the bean
   *           info.
   */
  public static BeanData getBeanData(Class<?> clazz) throws IntrospectionException {
    BeanData bd;
    synchronized (beanCache) {
      bd = beanCache.get(clazz);
      if (bd == null) {
        bd = analyzeBean(clazz);
        beanCache.put(clazz, bd);
      }
    }
    return bd;
  }
 
  public ERXBeanSerializer(Class clazz) {
    _clazz = clazz;
  }

  @Override
  public boolean canSerialize(Class clazz, Class jsonClazz) {
    return (_clazz.isAssignableFrom(clazz) && (jsonClazz == null || jsonClazz == JSONObject.class));
  }

  public Class<?>[] getJSONClasses() {
    return _JSONClasses;
  }

  public Class<?>[] getSerializableClasses() {
    return new Class[] { _clazz };
  }

  public Object marshall(SerializerState state, Object p, Object o) throws MarshallException {
    BeanData bd;
    try {
      bd = getBeanData(o.getClass());
    }
    catch (IntrospectionException e) {
      throw new MarshallException(o.getClass().getName() + " is not a bean", e);
    }

    JSONObject val = new JSONObject();
    if (ser.getMarshallClassHints()) {
      try {
        val.put("javaClass", o.getClass().getName());
      }
      catch (JSONException e) {
        throw new MarshallException("JSONException: " + e.getMessage(), e);
      }
    }
    Object args[] = new Object[0];
    Object result;
    for (Map.Entry<String, Method> ent : bd.readableProps.entrySet()) {
      String prop = ent.getKey();
      Method getMethod = ent.getValue();
      if (log.isDebugEnabled()) {
        log.debug("invoking " + getMethod.getName() + "()");
      }
      try {
        result = getMethod.invoke(o, args);
      }
      catch (Throwable e) {
        if (e instanceof InvocationTargetException) {
          e = ((InvocationTargetException) e).getTargetException();
        }
        throw new MarshallException("bean " + o.getClass().getName() + " can't invoke " + getMethod.getName() + ": " + e.getMessage(), e);
      }
      try {
        if (result != null || ser.getMarshallNullAttributes()) {
          try {
            Object json = ser.marshall(state, o, result, prop);
            val.put(prop, json);
          }
          catch (JSONException e) {
            throw new MarshallException("JSONException: " + e.getMessage(), e);
          }
        }
      }
      catch (MarshallException e) {
        throw new MarshallException("bean " + o.getClass().getName() + " " + e.getMessage(), e);
      }
    }

    return val;
  }

  public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {
    JSONObject jso = (JSONObject) o;
    BeanData bd;
    try {
      bd = getBeanData(clazz);
    }
    catch (IntrospectionException e) {
      throw new UnmarshallException(clazz.getName() + " is not a bean", e);
    }

    int match = 0;
    int mismatch = 0;
    for (Map.Entry<String, Method> ent : bd.writableProps.entrySet()) {
      String prop = ent.getKey();
      if (jso.has(prop)) {
        match++;
      }
      else {
        mismatch++;
      }
    }
    if (match == 0) {
      throw new UnmarshallException("bean has no matches");
    }

    // create a concrete ObjectMatch that is always returned in order to satisfy circular reference requirements
    ObjectMatch returnValue = new ObjectMatch(-1);
    state.setSerialized(o, returnValue);

    ObjectMatch m = null;
    ObjectMatch tmp;
    Iterator<String> i = jso.keys();
    while (i.hasNext()) {
      String field = i.next();
      Method setMethod = bd.writableProps.get(field);
      if (setMethod != null) {
        try {
          Class<?> param[] = setMethod.getParameterTypes();
          if (param.length != 1) {
            throw new UnmarshallException("bean " + clazz.getName() + " method " + setMethod.getName() + " does not have one arg");
          }
          tmp = ser.tryUnmarshall(state, param[0], jso.get(field));
          if (tmp != null) {
            if (m == null) {
              m = tmp;
            }
            else {
              m = m.max(tmp);
            }
          }
        }
        catch (UnmarshallException e) {
          throw new UnmarshallException("bean " + clazz.getName() + " " + e.getMessage(), e);
        }
        catch (JSONException e) {
          throw new UnmarshallException("bean " + clazz.getName() + " " + e.getMessage(), e);
        }
      }
      else {
        mismatch++;
      }
    }
    if (m != null) {
      returnValue.setMismatch(m.max(new ObjectMatch(mismatch)).getMismatch());
    }
    else {
      returnValue.setMismatch(mismatch);
    }
    return returnValue;
  }

  public Object unmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {
    JSONObject jso = (JSONObject) o;
    BeanData bd;
    try {
      bd = getBeanData(clazz);
    }
    catch (IntrospectionException e) {
      throw new UnmarshallException(clazz.getName() + " is not a bean", e);
    }
    if (log.isDebugEnabled()) {
      log.debug("instantiating " + clazz.getName());
    }
    Object instance;
    try {
      instance = clazz.newInstance();
    }
    catch (InstantiationException e) {
      throw new UnmarshallException("could not instantiate bean of type " + clazz.getName() + ", make sure it has a no argument " + "constructor and that it is not an interface or " + "abstract class", e);
    }
    catch (IllegalAccessException e) {
      throw new UnmarshallException("could not instantiate bean of type " + clazz.getName(), e);
    }
    catch (RuntimeException e) {
      throw new UnmarshallException("could not instantiate bean of type " + clazz.getName(), e);
    }
    state.setSerialized(o, instance);
    Object invokeArgs[] = new Object[1];
    Object fieldVal;
    Iterator<String> i = jso.keys();
    while (i.hasNext()) {
      String field = i.next();
      Method setMethod = bd.writableProps.get(field);
      if (setMethod != null) {
        try {
          Class<?> param[] = setMethod.getParameterTypes();
          fieldVal = ser.unmarshall(state, param[0], jso.get(field));
        }
        catch (UnmarshallException e) {
          throw new UnmarshallException("could not unmarshall field \"" + field + "\" of bean " + clazz.getName(), e);
        }
        catch (JSONException e) {
          throw new UnmarshallException("could not unmarshall field \"" + field + "\" of bean " + clazz.getName(), e);
        }
        if (log.isDebugEnabled()) {
          log.debug("invoking " + setMethod.getName() + "(" + fieldVal + ")");
        }
        invokeArgs[0] = fieldVal;
        try {
          setMethod.invoke(instance, invokeArgs);
        }
        catch (Throwable e) {
          if (e instanceof InvocationTargetException) {
            e = ((InvocationTargetException) e).getTargetException();
          }
          throw new UnmarshallException("bean " + clazz.getName() + "can't invoke " + setMethod.getName() + ": " + e.getMessage(), e);
        }
      }
    }
    return instance;
  }
}
TOP

Related Classes of er.ajax.json.serializer.ERXBeanSerializer$BeanData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.