/**
*
*/
package com.jaxws.json.codec;
import java.beans.BeanDescriptor;
import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import com.jaxws.json.feature.JSONObject;
/**
* @author Sundaramurthi
* @since 0.5
* @version 1.0
*
* Encode and Decoder extends Bean aware. To use common bean properties.
*
*/
public abstract class BeanAware {
private static final Logger LOG = Logger.getLogger(BeanAware.class.getName());
private static DatatypeFactory datatypeFactory;
/**
* Private bean property cache.
* Standard bean inspector cache only for with out Hierarchy, When Hierarchy specify bean parser always parse bean. Its too slow.
*/
private static final Map<Class<?>,PropertyDescriptor[]> propertyDescriptorCache =
Collections.synchronizedMap(new WeakHashMap<Class<?>,PropertyDescriptor[]>());
/**
* Field name cache
*/
private static final Map<Class<?>,Map<String,java.lang.reflect.Field>> classFieldCache =
Collections.synchronizedMap(new WeakHashMap<Class<?>,Map<String,java.lang.reflect.Field>>());
/**
* Flag Property of populator which enable create default object on non nullable property.
*/
protected boolean createDefaultOnNonNullable = JSONCodec.createDefaultOnNonNullable;
/**
* Utility method
* @param clazz
* @return
*/
public static boolean isJSONPrimitive(Class<?> clazz) {
return clazz.isPrimitive() || Number.class.isAssignableFrom(clazz)
|| clazz.equals(String.class) || clazz.equals(Boolean.class)
|| clazz.isEnum() || isDateTime(clazz)
|| clazz.equals(Character.class) || clazz.equals(Locale.class);
}
/**
* Utility method
* @param clazz
* @return
*/
protected static boolean isDateTime(Class<?> clazz){
return Date.class.isAssignableFrom(clazz)//Timestamp.class, java.sql.Date.class
|| Calendar.class.isAssignableFrom(clazz); // GregorianCalendar, Calendar;
// XMLGregorianCallender should be responded as bean with year, day , month property.
}
/**
* Utility method to return bean property information.
* @param clazz
* @return
*/
protected static BeanDescriptor getBeanDescriptor(Class<?> clazz){
try{
BeanDescriptor beanDescriptor = ((clazz.isAnnotationPresent(JSONObject.class) &&
clazz.getAnnotation(JSONObject.class).ignoreHierarchy())
? Introspector.getBeanInfo(clazz)
: Introspector.getBeanInfo(clazz,clazz.isEnum() ? Enum.class : clazz.equals(Object.class)? null : Object.class)).getBeanDescriptor();
populateFeatureDescriptor(clazz, beanDescriptor);
return beanDescriptor;
}catch(IntrospectionException exp){
return null;
}
}
/**
* Utility method to return bean property information.
* @param clazz
* @return
* @throws IntrospectionException
*/
protected static PropertyDescriptor[] getBeanProperties(Class<?> clazz) throws IntrospectionException{
if(propertyDescriptorCache.containsKey(clazz))
return propertyDescriptorCache.get(clazz);
PropertyDescriptor[] props = ((clazz.isAnnotationPresent(JSONObject.class) &&
clazz.getAnnotation(JSONObject.class).ignoreHierarchy())
? Introspector.getBeanInfo(clazz, clazz.getSuperclass())
: Introspector.getBeanInfo(clazz,clazz.isEnum() ? Enum.class : clazz.equals(Object.class)? null : Object.class)).getPropertyDescriptors();
if(props.length == 0 && !clazz.isEnum()){
// There is no property descriptor, then use public fields, RPC document require this
props = PublicFieldPropertyDescriptor.getDiscriptors(clazz.getFields(), clazz);
}
populatePropertyDescriptors(clazz, props);
propertyDescriptorCache.put(clazz, props);
return props;
}
private static void populatePropertyDescriptors(Class<?> clazz, PropertyDescriptor[] props){
for(PropertyDescriptor prop : props){
populateFeatureDescriptor(clazz, prop);
}
}
private static void populateFeatureDescriptor(Class<?> clazz, FeatureDescriptor prop){
try{
ResourceBundle bundle = ResourceBundle.getBundle(clazz.getName());
prop.setDisplayName(
bundle.getString(prop.getName()+".displayName"));
}catch(MissingResourceException ex){
try{
ResourceBundle bundle = ResourceBundle.getBundle("beanLocales");
prop.setDisplayName(
bundle.getString(clazz.getName() + "." + prop.getName() + ".displayName"));
}catch(MissingResourceException exp){
try{
ResourceBundle bundle = ResourceBundle.getBundle("beanLocales");
prop.setDisplayName(
bundle.getString(prop.getName()+".displayName"));
}catch(MissingResourceException expe){}
}
}
try{
ResourceBundle bundle = ResourceBundle.getBundle(clazz.getName());
prop.setShortDescription(
bundle.getString(prop.getName()+".shortDescription"));
}catch(MissingResourceException ex){
try{
ResourceBundle bundle = ResourceBundle.getBundle("beanLocales");
prop.setShortDescription(
bundle.getString(clazz.getName() + "." + prop.getName()+".shortDescription"));
}catch(MissingResourceException exp) {
try{
ResourceBundle bundle = ResourceBundle.getBundle("beanLocales");
prop.setDisplayName(
bundle.getString(prop.getName()+".shortDescription"));
}catch(MissingResourceException expe){}
}
}
}
/**
* Utility method to read declaring field including private scope.
* @param clazz
* @param fieldName
* @return
*/
protected static java.lang.reflect.Field getDeclaredField(Class<?> clazz, String fieldName){
if(!classFieldCache.containsKey(clazz)){
classFieldCache.put(clazz, fillDeclaredFields(clazz,new HashMap<String, Field>()));
}
return classFieldCache.get(clazz).get(fieldName);
}
/**
* declared fields.
*/
private static Map<String, Field> fillDeclaredFields(Class<?> clazz,Map<String, Field> fieldMap){
try {
for(java.lang.reflect.Field field : clazz.getDeclaredFields()){
if(!fieldMap.containsKey(field.getName()))
fieldMap.put(field.getName(), field);
}
if(!Object.class.equals(clazz.getSuperclass())){
fillDeclaredFields(clazz.getSuperclass(),fieldMap);
}
} catch (Throwable e) {
//
}
return fieldMap;
}
/**
* @param clazz
* @return instanceof class or possible sub level object
*/
public Object getNewInstance(Class<?> clazz){
try {
return clazz.newInstance();
} catch (InstantiationException e) {
if (List.class.isAssignableFrom(clazz)){
return new ArrayList<Object>();
}else if(Map.class.isAssignableFrom(clazz)){
return new HashMap<String,Object>();
} else if (Set.class.isAssignableFrom(clazz)) {
return new HashSet<Object>();
} else if (Integer.class.isAssignableFrom(clazz)) {
return new Integer(0);
} else if (Long.class.isAssignableFrom(clazz)) {
return new Long(0);
} else if (Double.class.isAssignableFrom(clazz)) {
return new Double(0.0);
} else if (Float.class.isAssignableFrom(clazz)) {
return new Float(0.0);
} else if (Number.class.isAssignableFrom(clazz)) {
return 0;
} else if (Boolean.class.isAssignableFrom(clazz)) {
return new Boolean(false);
} else if(clazz.isEnum()){
return clazz.getEnumConstants()[0];
} else if(XMLGregorianCalendar.class.isAssignableFrom(clazz)){
if(datatypeFactory == null){
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e1) {
return null;
}
}
return datatypeFactory.newXMLGregorianCalendar();
}
} catch (IllegalAccessException e) {
LOG.log(Level.FINE, "Failed in iniate", e);
}
return null;
}
/**
* @return current populator instance value of createDefaultOnNonNullable
*
* Bean aware property.
* By setting value to true, either populator or writer compose default value for read and
* write for non nullable object with null value passed from client or server.
*
* Default value of createDefaultOnNonNullable assigned from JSONCodec.createDefaultOnNonNullable
* @see JSONCodec createDefaultOnNonNullable
*/
public boolean isCreateDefaultOnNonNullable() {
return createDefaultOnNonNullable;
}
/**
* @param createDefaultOnNonNullable
*/
public void setCreateDefaultOnNonNullable(boolean createDefaultOnNonNullable) {
this.createDefaultOnNonNullable = createDefaultOnNonNullable;
}
}