Package com.jaxws.json.codec.encode

Source Code of com.jaxws.json.codec.encode.WSJSONWriter

package com.jaxws.json.codec.encode;

import java.beans.BeanDescriptor;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.CharacterIterator;
import java.text.SimpleDateFormat;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.jaxws.json.codec.BeanAware;
import com.jaxws.json.codec.DateFormat;
import com.jaxws.json.codec.JSONCodec;
import com.jaxws.json.codec.JSONFault;
import com.jaxws.json.codec.PublicFieldPropertyDescriptor;
import com.jaxws.json.codec.decode.WSJSONPopulator;
import com.jaxws.json.feature.JSONWebService;
import com.jaxws.json.serializer.JSONObjectCustomizer;

/**
* @author Sundaramurthi
*
* JSON response writter.
*/
public class WSJSONWriter extends BeanAware {
    private static final Logger LOG = Logger.getLogger(WSJSONWriter.class.getName());

  private static final String XML_DEFAULT = "##default";
  private static final String NULL     = "\u0000";
 
  /**
   * HEX character list for unicode generation.
   */
  final static char[]   hex       = "0123456789ABCDEF".toCharArray();
   
    /**
     * JSON output stream. Initialized while object construction.
     *
     */
    private final   OutputStream    output;
   
    /**
     * rootObject
     */
    private final   Object       rootObject;
   
    /**
     * Cyclic finder Object stack
     */
    private final   Stack<Object>   stack;
   
    /**
     * List of user registered JSON customizer.
     */
    private final   Map<Class<? extends Object>,JSONObjectCustomizer> objectCustomizers;
   
    /**
   * Date format used to serialize JSON
   *
   * User may customize this property using JSON web service configuration property file.
   */
  private DateFormat dateFormat = DateFormat.RFC3339;
 
  /**
   * Either include or exclude property specified this expression flag turn true.
   * As a result JSON path expression constructed to handle include and/or exclude
   *
   * If flag goes true there is miner performance delay may happen
   */
  private     boolean     buildExpr         = false;
 
 
  /**
   * Currently processing JSON expression stack. Used to match include and/or exclude configuration.
   */
  private     String       exprStack         = "";

  /**
   * List of properties that excluded from serialization
   */
  private Collection<Pattern> excludeProperties;

  /**
   * List of properties that included from serialization
   */
  private Collection<Pattern> includeProperties;

  /**
   *
   */
  private Pattern listMapKey;

  /**
   *
   */
  private Pattern listMapValue;
 
  /**
   * Flag which enable write object as possible JSON document format or not.
   */
  private boolean metaDataMode  = false;
 
  private boolean schemaMode    = false;

  /**
   * List of response attachments.
   */
  private List<Map<String,Object>> attachments   = new ArrayList<Map<String,Object>>();
 
  private static final   Pattern pattern = Pattern.compile("[\"\b\t\f\n\r/\\\\\\x00\\x1F\\x7F\\x9F]");
  private         Matcher matcher = pattern.matcher("");
 
  private final   Stack<Class<?>>   stackNillableInstances;
    /**
     * Writer instance with parameter passed writer object.
     * @param writer
     */
    public WSJSONWriter(OutputStream output, Object rootObject, Map<Class<? extends Object>, JSONObjectCustomizer> objectCustomizers){
      if(output == null){
        throw new RuntimeException("Writer can't be null");
      }
      if(rootObject == null){
        throw new RuntimeException("rootObject can't be null");
      }
    this.output          = output;
    this.rootObject       = rootObject;
    this.stack          = new Stack<Object>();
    this.stackNillableInstances  = new Stack<Class<?>>();
    this.objectCustomizers = objectCustomizers != null ?
        objectCustomizers :
        new HashMap<Class<? extends Object>, JSONObjectCustomizer>();
  }
   
    /**
     * Serialize passed object to JSON string, writes into constructor passed writer object.
     * @param object Map object to serialize.
     */
    public void write(DateFormat dateFormat,
        Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties,
        Pattern listMapKey,Pattern listMapValue){
      this.initValues(dateFormat, excludeProperties, includeProperties, listMapKey, listMapValue);
      // Convert passed object to value.
      this.process(rootObject, null, null);
    }
    /**
     * Serialize passed object to JSON string, writes into constructor passed writer object.
     * For null properties new instance created and serialized as meta data.
     * @param object Map object to serialize.
     */
    public void writeMetadata(DateFormat dateFormat,
        Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties,
        Pattern listMapKey,Pattern listMapValue){
      this.writeMetadata(dateFormat, excludeProperties, includeProperties, listMapKey, listMapValue,false);
    }
   
    /**
     * Serialize passed object to JSON string, writes into constructor passed writer object.
     * For null properties new instance created and serialized as meta data.
     * @param object Map object to serialize.
     */
    public void writeMetadata(DateFormat dateFormat,
        Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties,
        Pattern listMapKey,Pattern listMapValue,boolean isSchema){
      this.initValues(dateFormat, excludeProperties, includeProperties, listMapKey, listMapValue);
      this.schemaMode  = isSchema;
      this.metaDataMode  = true;
      // Convert passed object to value.
      this.process(rootObject, null, null);
    }
   
    /**
     * Utility method to serialize object.
     * @param rootObject
     * @param objectCustomizers
     * @return
     */
    public static final String writeMetadata(Object rootObject,
        Map<Class<? extends Object>, JSONObjectCustomizer> objectCustomizers,boolean isSchema){
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      WSJSONWriter  writter = new WSJSONWriter(out, rootObject, objectCustomizers);
      writter.writeMetadata(JSONCodec.dateFormat, JSONCodec.excludeProperties,
          JSONCodec.includeProperties, JSONCodec.globalMapKeyPattern,
          JSONCodec.globalMapValuePattern, isSchema);
      return out.toString();
  }
   
    /**
     * Utility method to serialize object.
     * @param rootObject
     * @param objectCustomizers
     * @return
     */
    public static final String writeMetadata(Object rootObject,
        Map<Class<? extends Object>, JSONObjectCustomizer> objectCustomizers){
      return writeMetadata(rootObject, objectCustomizers,false);
    }

   
    /**
     * Pass write iniatated values.
     * @param dateFormat
     * @param excludeProperties
     * @param includeProperties
     * @param listMapKey
     * @param listMapValue
     */
    private void initValues(DateFormat dateFormat,
        Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties,
        Pattern listMapKey,Pattern listMapValue){
      // TODO if passed ROOT object is primitive ??
      if(dateFormat != null){
        this.dateFormat = dateFormat;
      }
      this.excludeProperties   = excludeProperties;
      this.includeProperties   = includeProperties;
      this.listMapKey      = listMapKey;
      this.listMapValue    = listMapValue;
     
      this.buildExpr = ((excludeProperties != null) &&
            !excludeProperties.isEmpty()) ||
            ((includeProperties != null) &&
                !includeProperties.isEmpty());
    }
   
    /**
     * Object processing entry.
     * Serialize object into json
     * @param object
     * @param method
     * @throws JSONFault
     */
    private void process(Object object, Method method, PropertyDescriptor descriptor) throws JSONFault {
      /*
       * Step 1. If object is null write null and return.
       * Detect cyclic references
       */
       if (object == null) {
             this.add("null");
             return;
         }
       Class<?>       clazz       = object.getClass();
       JSONWebService   customInfo     = method != null ? method.getAnnotation(JSONWebService.class) : null;
      /*
        * Step 2. Find is it Object and cyclic reference.
        * Detect cyclic references
        * Object is not in cyclic reference the add it to cyclic stack.
        */
         if (this.stack.contains(object) && !(clazz.isPrimitive() || clazz.equals(String.class))) {
             // Step 2.1 : object is cyclic reference and not primitive write as null
           LOG.log(Level.FINE, "Cyclic reference detected on " + object);
          //TODO folowing logic required globaly, normal json also haveing same problem. with empty list
            if(object instanceof ArrayList && this.schemaMode){
              // TODO find way to solve empty list object compare issue.
              this.stack.push(object);
            } else {
              this.add("null");
              return;
            }
         }else{
           this.stack.push(object);
         }
        /*
         * Step 3. Check is this class handled by Object Customizer. If yes. pass it to Object customizer.
         */
         if(this.objectCustomizers.containsKey(clazz)){
           this.objectCustomizers.get(clazz).encode(this.output, object);
           return;
         }
        
        /*
         * Step 4. Check is skipListWrapper enabled if yes validate and get wrapped object.
         */
        if(JSONCodec.listWrapperSkip && !(clazz.isPrimitive() || clazz.isEnum())){// definitely not a list
          Object   wrapperContent = getWrapperList(object,clazz);
          if(wrapperContent != null){
            object = wrapperContent;
            //this.stack.push(object); Since object pop after process not nice to push wrapper.
          }
        }
        if(this.schemaMode){
          this.add("{");
          // Genaral property
          if(descriptor != null){
            this.add("\"title\":\""+descriptor.getDisplayName()+"\"," +
                "\"description\":\""+descriptor.getShortDescription()+"\","+
                "\"readonly\":"+(descriptor.getWriteMethod() == null)+",");
          }else if(!isJSONPrimitive(clazz)){
        BeanDescriptor beanDescriptor = getBeanDescriptor(clazz);
        if(beanDescriptor != null)
              this.add("\"title\":\""+beanDescriptor.getDisplayName()+"\"," +
                  "\"description\":\""+beanDescriptor.getShortDescription()+"\",");
          }
         
          if(object instanceof String ||
                object instanceof Character ||
                object instanceof Locale ||
                object instanceof Class){
            this.add("\"type\":\"string\",\"nillable\":true,\"default\":");// TODO required for all
          } else if (object instanceof Number) {
            this.add("\"type\":\"number\",\"default\":");// TODO integer type
          } else if (object instanceof Boolean) {
            this.add("\"type\":\"boolean\",\"default\":");
          } else if (object instanceof Date) {
            this.add("\"type\":\"string\",\"nillable\":true,\"default\":");//\"format\":\"date\",
          } else if (object instanceof Calendar) {
            this.add("\"type\":\"string\",\"nillable\":true,\"default\":");
          } else if (object instanceof Map) {
            this.add("\"type\":\"object\",\"properties\":");
            // TODO
          } else if (object.getClass().isArray()) {
            this.add("\"type\":\"array\",\"items\":");
          } else if (object instanceof Iterable) {
            this.add("\"type\":\"array\",\"items\":");
          } else if (object instanceof Enum) {
            this.add("\"type\":\"string\",\"enum\":[");
            for(Object o : clazz.getEnumConstants()){
              this.add("\""+((Enum<?>)o).name()+"\",");
            }
            this.add("null],\"default\":");
          } else if (object instanceof JAXBElement<?>) {
            // called back with object
          } else if (object instanceof Node) {
            this.add("\"type\":\"any\",\"default\":");
          } else {
            this.add("\"type\":\"object\",\"properties\":");// TODO schema complex name
          }
        }
        /*
         * Step 5. convert given object to JSON.
         */
        if (object instanceof String ||
            object instanceof Character ||
            object instanceof Locale ||
            object instanceof Class) {
          // Step 5.1: If given object instance of String,Character,Locale or Class write class to string using convert to string
            this.string(object.toString());
        } else if (object instanceof Number) {// Big integer and Big double handled here
          // Step 5.2: If given object instance of number add to json.
            this.add(object.toString());
        } else if (object instanceof Boolean) {
          // Step 5.3: If given object instance of boolean add to json.
            this.bool(((Boolean) object).booleanValue());
        } else if (object instanceof Date) {
          // Step 5.4: If given object instance of date add to JSON with date customizer. util and sql date and timestamp handled here
            this.date((Date) object, method, customInfo);
        } else if (object instanceof Calendar) {
          // Step 5.5: If given object instance of Calendar convert to date and add to JSON with date customizer.
            this.date(((Calendar) object).getTime(), method, customInfo);
        } else if (object instanceof Map) {
          // Step 5.6: handle has Map
            this.map((Map<?,?>) object, method, customInfo);
        } else if (object.getClass().isArray()) {
          // Step 5.7: handle has array
            this.array(object, method, customInfo);
        } else if (object instanceof Iterable) {
          // Step 5.8: handle has List
            this.array(((Iterable<?>) object).iterator(), method, customInfo);
        else if (object instanceof Enum) {
          // Step 5.9: handle has Enumeration
            this.enumeration((Enum<?>) object, clazz);
        } else if (object instanceof JAXBElement<?>){
          // Step 5.10: handle has JAXB element
            this.process(((JAXBElement<?>)object).getValue(), null, descriptor);
        } else if (object instanceof Node){
          // Step 5.11: handle has JAXB element
            this.xmlNode((Node)object, null, false);
        } else {
          // Step 5.12: handle has Object
            this.bean(object, clazz);
        }
       
        if(this.schemaMode){
          this.add("}");
        }
        this.stack.pop();
    }

    /**
     * Step 5.1:  Convert to string with escape.
     * escape characters
     */
    private void string(String string) {
        this.add('"');
        if(!matcher.reset(string).find()){
          add(string);
        }else {
          CharacterIterator it = new StringCharacterIterator(string);
          for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
              if (c == '"') {
                  this.add("\\\"");
              } else if (c == '\\') {
                  this.add("\\\\");
              } else if (c == '/') {
                  this.add("\\/");
              } else if (c == '\b') {
                  this.add("\\b");
              } else if (c == '\f') {
                  this.add("\\f");
              } else if (c == '\n') {
                  this.add("\\n");
              } else if (c == '\r') {
                  this.add("\\r");
              } else if (c == '\t') {
                  this.add("\\t");
              } else if (Character.isISOControl(c) ) {
                  this.unicode(c);
              } else {
                  this.add(Character.toString(c));
              }
          }
        }
       
        this.add('"');
    }
   
   
    /**
     * Step 5.2:  Add directly to writer,
     * Add object to buffer
     */
    private void add(String obj) {
        try {
      this.output.write(obj.getBytes());
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage());
    }
    }
   
    /**
     * Step 5.3: add boolean
     * Add boolean to buffer
     */
    private void bool(boolean b) {
        this.add(b ? "true" : "false");
    }
   
    /**
     * Step 5.4, 5.5: add date
     * Add date to buffer
     */
    private void date(Date date, Method method, JSONWebService   customInfo) {
      assert date != null;
     
      /*
       *  Step 5.4.1: check is custom date format annotation added.
       */
      DateFormat     currentFormat   = (customInfo != null && customInfo.format().length() > 0) ?
          DateFormat.CUSTOM : this.dateFormat;
       
      /*
       *  Step 5.4.2: serialize date based on format.
       */
      switch(currentFormat){
      case PLAIN:
        this.add(String.valueOf(date.getTime()));
        break;
      case CUSTOM:
        this.string(this.date2String(date,customInfo.format()));
        return;
      default:
        this.string(this.date2String(date,currentFormat.getFormat()));
      }
    }
   
    /**
     * Step 5.6: add date
     * Add map to buffer
     */
    private void map(Map<?,?> map, Method method, JSONWebService customInfo) throws JSONFault {
        this.add("{");

        Iterator<?> it = map.entrySet().iterator();

        boolean hasData = false;
        while (it.hasNext()) {
            Map.Entry<?,?>   entry   = (Map.Entry<?,?>) it.next();
            Object       key   = entry.getKey();
            String expr = null;
            if (this.buildExpr) {
                if (key == null) {
                    LOG.log(Level.WARNING, "Cannot build expression for null key in " + this.exprStack);
                    continue;
                } else {
                    expr = this.expandExpr(key.toString());
                    if (this.shouldExcludeProperty(expr, customInfo)) {
                        continue;
                    }
                    expr = this.setExprStack(expr);
                }
            }
            if (hasData) {
                this.add(',');
            }
            hasData = true;
            // Process key
            // TODO if key is not primitive, it is not valid JSON output.
            this.string(String.valueOf(key));
            this.add(":");
            // Process value
            this.process(entry.getValue(), method, null);
            if (this.buildExpr) {
                this.setExprStack(expr);
            }
        }
       
        this.add("}");
    }
   
   
    /**
     * Step 5.7: add as array.
     * Add array to buffer
     */
    private void array(Object object, Method method, JSONWebService customInfo) throws JSONFault {
        this.add("[");

        int length = Array.getLength(object);

        boolean hasData = false;
        for (int i = 0; i < length; ++i) {
            String expr = null;
            if (this.buildExpr) {
                expr = this.expandExpr(i);
                if (this.shouldExcludeProperty(expr, customInfo)) {
                    continue;
                }
                expr = this.setExprStack(expr);
            }
            if (hasData) {
                this.add(',');
            }
            hasData = true;
            this.process(Array.get(object, i), method, null);
            if (this.buildExpr) {
                this.setExprStack(expr);
            }
        }

        this.add("]");
    }
   
    /**
     * Step 5.8: add as list array.
     * Add array to buffer
     */
    private void array(Iterator<?> iterator, Method method, JSONWebService customInfo) throws JSONFault {
      if(listMapKey != null && method != null && method.getGenericReturnType() != null
          && method.getGenericReturnType() instanceof ParameterizedType){
        Type[] types = ((ParameterizedType)method.getGenericReturnType()).getActualTypeArguments();
        if(types.length == 1){
          try{
            Class<?> clazz = (Class<?>) types[0];
            PropertyDescriptor[]   props       = getBeanProperties(clazz);
                  PropertyDescriptor    keyProperty    = null;
                  PropertyDescriptor    valueProperty  = null;
                  for(PropertyDescriptor prop : props){
                    if(listMapKey.matcher(clazz.getName() + "." + prop.getName()).find()){
                      keyProperty = prop;
                      break;
                    }
                  }
                  if(listMapValue != null){
                    for(PropertyDescriptor prop : props){
                      if(listMapValue.matcher(clazz.getName() + "." + prop.getName()).find()){
                        valueProperty = prop;
                        break;
                      }
                    }
                  }
                  if(keyProperty != null){
                    Method keyReadMethod   = keyProperty.getReadMethod();
                    Method valueReadMethod   = valueProperty != null ? valueProperty.getReadMethod() : null;
                    HashMap<String,Object> map = new LinkedHashMap<String,Object>();
                    while(iterator.hasNext()){
                      Object ob = iterator.next();
                      map.put(String.valueOf(keyReadMethod.invoke(ob)), valueReadMethod != null ? valueReadMethod.invoke(ob) : ob);
                    }
                    map(map, keyReadMethod, customInfo);
                    return;
                  }
          }catch(Throwable th){
            // Continue with out Map
          }
        }
         }
      if(!this.schemaMode)
        this.add("[");
        if(this.metaDataMode && method != null && method.getGenericReturnType() != null){
        Class<?> parameterType  = (Class<?>)((ParameterizedType)method.getGenericReturnType()).
          getActualTypeArguments()[0];
        if(parameterType.equals(Object.class) || WSJSONPopulator.isJSONPrimitive(parameterType)){
          if(method != null) {
            this.process(getMetaDataInstance(parameterType, customInfo,
                getDeclaredField(method.getDeclaringClass(),
                    Introspector.decapitalize(method.getName().substring(3)))), null, null);
          }
          // JAXB Choice
        } else {
          this.process(getNewInstance(parameterType), method, null);
        }
        }
        boolean hasData = false;
        for (int i = 0; iterator.hasNext(); i++) {
            String expr = null;
            if (this.buildExpr) {
                expr = this.expandExpr(i);
                if (this.shouldExcludeProperty(expr, customInfo)) {
                  iterator.next();
                    continue;
                }
                expr = this.setExprStack(expr);
            }
            if (hasData) {
                this.add(',');
            }
            hasData = true;
            this.process(iterator.next(), method, null);
            if (this.buildExpr) {
                this.setExprStack(expr);
            }
        }
        if(!this.schemaMode)
          this.add("]");
    }
   
    /**
     * Step 5.9: add as Enumeration.
     * Instrospect an Enum and serialize it as a name/value pair or as a bean including all its own properties
     */
    private void enumeration(Enum<?> enumeration, Class<?> clazz) throws JSONFault {
      try {
        /*
         *  Step 5.9.1: If enumeration contains more than name declaration serialize as bean.
         */
        PropertyDescriptor[] props = getBeanProperties(clazz);
      if(props.length > 0){
        this.bean(enumeration, clazz);
      } else {
        String value = enumeration.name();
        try{
          // If xml value annotation use it.
          value  = clazz.getDeclaredField(value).getAnnotation(XmlEnumValue.class).value();
        }catch(Throwable th){};
        this.string(value);
      }
    } catch (IntrospectionException e) {
      this.string(enumeration.name());
    }
    }
   
    /**
     * Step 5.10: serialize as Object
     * Instrospect bean and serialize its properties
     */
    private void bean(Object object, Class<?> clazz) throws JSONFault {
      assert object != null && clazz != null && !clazz.isPrimitive();

      this.add("{");
     
      boolean hasData = false;
     
      // handle any object type.
      if(clazz.equals(Object.class)){
        clazz  = object.getClass();
        hasData = hasData | this.add("class", clazz.getName(), null, hasData, null);
      }
     
        try {
          /*
           *  Step 5.10.1: If class level JSON annotation present and ask for ignore parent level, then ignore it.
           */
          PropertyDescriptor[] props = getBeanProperties(clazz);
         
            /*
           *  Step 5.10.2: Process all properties in Bean
           */
            nextProperty:
            for(PropertyDescriptor property : props){
               Class<?>   propertyType    = property.getPropertyType();
               String   name       = property.getName();
              /*
                 *  Step 5.10.2.1: If this property hard coded exclude list, exclude it.
                 */
                 if (this.shouldHardExcludeProperty(name)) {
                     continue;
                 }

                 Method   accessor   = property.getReadMethod();
              /*
                *  Step 5.10.2.2: Handle special case, When property is Boolean object (not boolean primitive) and getter method starts with "is"
                *  This is not standard boolean declaration. But logical to do in hand written bean. Then support it.
                */
                 if(accessor == null){
                   if(property instanceof IndexedPropertyDescriptor){
                     IndexedPropertyDescriptor idexedProp = (IndexedPropertyDescriptor)property;
                     accessor    = idexedProp.getReadMethod();
                     propertyType   = idexedProp.getIndexedPropertyType();
                   }else if(propertyType.isAssignableFrom(Boolean.class)){
                     // for Boolean Objet is method issue
                     try{
                       accessor = clazz.getMethod("is"+name.substring(0, 1).toUpperCase()+name.substring(1),((Class[])null));
                     }catch(Throwable th){/*Not an issue if not read method*/}
                   }
                 }
                
                 /*
                  *
                 *  Step 5.10.2.3:
                  * TODO is it required to support cglib?
                  * if (clazz.getName().indexOf("$$EnhancerByCGLIB$$") > -1) {
                    try {
                        baseAccessor = Class.forName(
                                clazz.getName().substring(0, clazz.getName().indexOf("$$")))
                                .getMethod(accessor.getName(), accessor.getParameterTypes());
                    } catch (Exception ex) {
                        log.debug(ex.getMessage(), ex);
                    }
                }
                 **/
                
                /*
                *  Step 5.10.2.4: If property accessible process it.
                * 
                */ 
                 if(accessor != null){
                  
                     Object value = null;
                     try{
                       value  = accessor.invoke(object, new Object[0]);
                     }catch(Throwable th){
                       if(property instanceof PublicFieldPropertyDescriptor){
                         value = ((PublicFieldPropertyDescriptor)property).getValue(object);
                       }
                       /*TODO trace*/
                     }
                    /*
                      *  Step 5.10.2.4.1: Read property value from object. If value null and exclude is true continue next property.
                      */
                     if(value == null){
                       /*
                       *  Step 5.10.2.4.2: Read property value from object. If value null attempt to get it from default value.
                       */
                       try{
                           Field declaredField = getDeclaredField(clazz,name);
                           XmlElement   xmlElm   =  declaredField.getAnnotation(XmlElement.class);
                             if(xmlElm != null){
                               if(!xmlElm.defaultValue().equals(NULL) && isJSONPrimitive(propertyType)){
                                 value = xmlElm.defaultValue();
                               } else if(!xmlElm.nillable() && createDefaultOnNonNullable){
                                 if(!isJSONPrimitive(propertyType)){
                                   if(!stackNillableInstances.contains(propertyType)){
                                     stackNillableInstances.push(propertyType);
                                     value = getNewInstance(propertyType);
                                     hasData = this.add(name, value, null, hasData, property) || hasData;
                                     stackNillableInstances.pop();
                                   }
                                   continue nextProperty;
                                 }else{
                                   // Primitive don't have any default WHAT TODO . E.g. Integer object can't be instantiated.
                                 }
                               }
                             }
                         }catch(Throwable th){}
                         // Value still null
                         if(value == null && JSONCodec.excludeNullProperties && !this.metaDataMode){
                           continue nextProperty;
                         } else if(this.metaDataMode) {
                       // In meta data mode always get data via meta provider.
                       value = getMetaDataInstance(propertyType, accessor.getAnnotation(JSONWebService.class),
                              getDeclaredField(clazz,name));
                      }
                    }else if(this.metaDataMode && propertyType.isPrimitive()){
                      // Primitive meta data. In case like int value become 0. But it may be from default
                     value = getMetaDataInstance(propertyType, accessor.getAnnotation(JSONWebService.class),
                          getDeclaredField(clazz,name));
                    }
                     
                  /*
                     *  Step 5.10.2.4.3: read property custom config. If it is serializable continue process with specified name if any
                     */
                  JSONWebService properyConfig = accessor.getAnnotation(JSONWebService.class);
                   if (properyConfig != null) {
                         if (!properyConfig.serialize())
                             continue;
                         else if (properyConfig.name().length() > 0)
                             name = properyConfig.name();
                     }else{
                    /*
                     *  Step 5.10.2.4.4: JSON config not present. Then read XML configuration.
                     */   
                       try{
                        // XML annotation present at field level.
                         Field     declaredField   = getDeclaredField(clazz,name);
                         XmlElement   xmlElm       = declaredField.getAnnotation(XmlElement.class);
                          /*
                           *  Step 5.10.2.4.5: If XML transient ignore property.
                           */
                         if (declaredField.isAnnotationPresent(XmlMimeType.class)){
                           if(this.metaDataMode){
                             value  = declaredField.getAnnotation(XmlMimeType.class).value();
                           }else{
                             Map<String,Object> attachment = new HashMap<String, Object>();;
                             attachment .put("name", name);
                             attachment.put("value", value);
                             attachment.put("mimeType",declaredField.getAnnotation(XmlMimeType.class).value());
                             attachments   .add(attachment);
                             continue nextProperty;
                           }
                         } else if(xmlElm != null) {// MAjor hits are here
                           /*
                                *  Step 5.10.2.4.4.2: Xml elements .
                                */
                           if(!xmlElm.name().equals(XML_DEFAULT)){
                             name = xmlElm.name();
                           }
                         }else if(declaredField.isAnnotationPresent(XmlElements.class) && Collection.class.isAssignableFrom(declaredField.getType())
                             && value instanceof Collection){
                           // XML choice list
                             /*
                                *  Step 5.10.2.4.4.1: Is it XML choice list?. If assign find right element name.
                                */
                           XmlElements xmlElms =  declaredField.getAnnotation(XmlElements.class);
                           Collection<?> valueList = (Collection<?>)value;
                           if(!valueList.isEmpty()){
                             // use first object to identify type
                             ArrayList<Map<String,Object>> group = new ArrayList<Map<String,Object>>();
                             for(Object ob : valueList){
                               for(XmlElement elm : xmlElms.value()){
                                 // TODO JAXBElement
                                 if(((Class<?>)elm.type()).isAssignableFrom(ob.getClass())){
                                   Map<String,Object> objectMap = new HashMap<String, Object>();
                                   objectMap.put(elm.name(), ob);
                                   group.add(objectMap);
                                   break;// XmlElement list break
                                 }
                               }
                             }
                             hasData = this.add(name, group, null, hasData, property) || hasData;
                             continue nextProperty;
                           } else {
                             // If choice element id empty, don't print it at all
                             if(!this.metaDataMode){
                               continue nextProperty;
                             } else {
                               name  = "CHOICE";
                               Map<String,Object> choices     = new HashMap<String, Object>();
                               for(XmlElement elm : xmlElms.value()){
                                 try{
                                   choices.put(elm.name(), getMetaDataInstance(elm.type(),null,null));
                                 }catch(Throwable th){
                                   //
                                   choices.put(elm.name(),"object");
                                 }
                               }
                               value = choices;
                             }
                           }
                         }else if(declaredField.isAnnotationPresent(XmlElementRefs.class)
                             && Collection.class.isAssignableFrom(declaredField.getType())
                             && value instanceof Collection){
                           // XML choice list
                             /*
                                *  Step 5.10.2.4.4.1: Is it XML choice list?. If assign find right element name.
                                */
                           XmlElementRefs xmlElms =  declaredField.getAnnotation(XmlElementRefs.class);
                           Collection<?> valueList = (Collection<?>)value;
                           if(!valueList.isEmpty()){
                             // use first object to identify type
                             Map<String,List<Object>> group = new HashMap<String,List<Object>>();
                             for(Object ob : valueList){
                               for(XmlElementRef elm : xmlElms.value()) {
                                 if(ob instanceof JAXBElement<?>) {
                                   JAXBElement<?> element = (JAXBElement<?>)ob;
                                   if(element.getName().getLocalPart().equals(elm.name())){// namespace too
                                     name = elm.name();
                                     if(!group.containsKey(name))
                                       group.put(name, new ArrayList<Object>());
                                     group.get(name).add(element.getValue());
                                     break;// Break element list
                                   }
                                 } else {
                                   if(((Class<?>)elm.type()).isAssignableFrom(ob.getClass())){
                                     name = elm.name();
                                     if(!group.containsKey(name))
                                       group.put(name, new ArrayList<Object>());
                                     group.get(name).add(ob);
                                     break;// Break element list
                                   }
                                 }
                               }
                             }
                             for(Map.Entry<String, List<Object>> entry : group.entrySet()){
                               hasData = this.add(entry.getKey(), entry.getValue(), null, hasData, property) || hasData;
                                       // TODO this.setExprStack(expr);
                             }
                             continue nextProperty;
                           } else {
                             // If choice element id empty, don't print it at all
                             if(!this.metaDataMode){
                               continue nextProperty;
                             } else {
                               name  = "CHOICE";
                               Map<String,Object> choices     = new HashMap<String, Object>();
                               for(XmlElementRef elm : xmlElms.value()){
                                 try{
                                   choices.put(elm.name(), getMetaDataInstance(elm.type(),null,null));
                                 }catch(Throwable th){
                                   //
                                   choices.put(elm.name(),"object");
                                 }
                               }
                               value = choices;
                             }
                           }
                         } else if(declaredField.isAnnotationPresent(XmlAttribute.class)){
                           /*
                                *  Step 5.10.2.4.4.3: Xml attribute.
                                */
                           if(!declaredField.getAnnotation(XmlAttribute.class).name().equals(XML_DEFAULT)){
                             name = declaredField.getAnnotation(XmlAttribute.class).name();
                           }
                            
                         }else if(accessor.isAnnotationPresent(XmlTransient.class) ||
                             (declaredField != null && declaredField.isAnnotationPresent(XmlTransient.class))){
                           /**
                            * XmlTransient contains lower periority than XMLElementX annotation.
                            * Transient fields should not have XMLElement,XMLElements or XmlAttribute
                            */
                           continue nextProperty;
                         }
                       }catch(Throwable th){
                         LOG.log(Level.FINER,"Processing xml annatation failed for field: "+name);
                       }
                     }
                  
                  /*
                      *  Step 5.10.2.4.5: if custom exclude and include present create JSON expression to handle it.
                      */
                   String expr = null;
                     if (this.buildExpr) {
                         expr = this.expandExpr(name);
                         if (this.shouldExcludeProperty(expr, properyConfig)) {
                             continue;
                         }
                         expr = this.setExprStack(expr);
                     }
                    
                     /*
                   *  Step 5.10.2.4.6: write value
                   */
                     boolean propertyPrinted = this.add(name, value, accessor, hasData, property);
                     hasData = hasData || propertyPrinted;
                     if (this.buildExpr) {
                         this.setExprStack(expr);
                     }
                 }
            }

            // special-case handling for an Enumeration - include the name() as a property */
            if (object instanceof Enum) {
                Object value = ((Enum<?>) object).name();
                this.add("_name", value, object.getClass().getMethod("name"), hasData, null);
            }
        } catch (Exception e) {
            throw new JSONFault("Server.json", "Failed to serialize object "+clazz.getName(), "JSONCodec", null, e);
        }

        this.add("}");
    }
   
   
    /**
     * Step 5.10.1: add object again
     * Add name/value pair to buffer
     */
    private boolean add(String name, Object value, Method method, boolean hasData, PropertyDescriptor descriptor) throws JSONFault {
        if (!JSONCodec.excludeNullProperties || value != null || this.metaDataMode) {
            if (hasData) {
                this.add(',');
            }
            this.add('"');
            this.add(name);
            this.add("\":");
            this.process(value, method, descriptor);
            return true;
        }

        return false;
    }
 
   
    /**
     * Step 5.10.11: process as xml elment
     * Add name/value pair to buffer
     */
    private boolean xmlNode(Node node, JSONWebService config,boolean hasData) {
    if(node instanceof Element){
      Element elem = (Element)node;
      if (hasData) {
        this.add(',');
          }
      this.add('{');
            this.add('"');
            this.add(elem.getTagName());
            this.add("\":[");
            NodeList childs = elem.getChildNodes();
            int childLength = childs.getLength();
            for(int c =0; c < childLength; c++){
              hasData = xmlNode(childs.item(c),null, hasData && c != 0);
            }
           
            this.add("]}");
            return true;
    }else if(node instanceof Attr){
      Attr atr = (Attr)node;
      return this.add(atr.getName(),atr.getValue(),null,false, null);
    }
    return false;
  }

    /**
     * Private method to chack exclude or include.
     * @param expr
     * @return
     */
    private boolean shouldExcludeProperty(String expr,JSONWebService config) {
      if(config != null && config.excludeProperties().length > 0){
        for(String match : config.excludeProperties()){
          if (Pattern.compile(match).matcher(expr).matches()) {
                    LOG.log(Level.FINEST, "Ignoring property because of exclude set to true in annotation: " + expr);
                    return true;
                }
        }
      }
     
        if (this.excludeProperties != null) {
            for (Pattern pattern : this.excludeProperties) {
                if (pattern.matcher(expr).matches()) {
                    LOG.log(Level.FINEST, "Ignoring property because of exclude rule: " + expr);
                    return true;
                }
            }
        }

        if(config != null && config.includeProperties().length > 0){
        for(String match : config.includeProperties()){
          if (Pattern.compile(match).matcher(expr).matches()) {
                    return false;
                }
        }
        LOG.log(Level.FINEST, "Ignoring property because of include rule set to true in annotation: " + expr);
      }
       
        if (this.includeProperties != null) {
            for (Pattern pattern : this.includeProperties) {
                if (pattern.matcher(expr).matches()) {
                    return false;
                }
            }
            LOG.log(Level.FINEST, "Ignoring property because of exclude rule: " + expr);
            return true;
        }
        return false;
    }

   
    ///// JSON Expression stack private method  Start
   
    private String expandExpr(int i) {
        return this.exprStack + "[" + i + "]";
    }

    private String expandExpr(String property) {
        if (this.exprStack.length() == 0)
            return property;
        return this.exprStack + "." + property;
    }

    private String setExprStack(String expr) {
        String s = this.exprStack;
        this.exprStack = expr;
        return s;
    }
   
    ///// JSON Expression stack private method end
   
    /**
     * Utility V  write as character
     * Add char to buffer
     */
    private void add(char c) {
        try {
      this.output.write(c);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    }

  

    /**
     * Utility IV  write as unicode
     * Represent as unicode
     *
     * @param c character to be encoded
     */
    private void unicode(char c) {
        this.add("\\u");

        int n = c;

        for (int i = 0; i < 4; ++i) {
            int digit = (n & 0xf000) >> 12;

            this.add(hex[digit]);
            n <<= 4;
        }
    }
   
    /**
     * Utility III  never serializable properties.
     * Ignore "class" field
     */
    private boolean shouldHardExcludeProperty(String name)
            throws SecurityException, NoSuchFieldException {
        if (name.equals("class") || name.equals("declaringClass") || name.equals("serialVersionUID")) {
            return true;
        }
        return false;
    }
   
   
    /**
     * Utility II to convert date to string.
     *
     * @param date
     * @return
     * @see
     */
     private String date2String(Date date,String timePattern) {
       if(timePattern == null || timePattern.trim().isEmpty() ){
         timePattern = "yyyy-MM-dd'T'HH:mm:ssZ";
       }
       SimpleDateFormat   formatter   = new SimpleDateFormat(timePattern);
       if(JSONCodec.useTimezoneSeparator){
         StringBuffer   dateStr   = new StringBuffer(formatter.format(date));
         return dateStr.length() > 22 ? dateStr.insert(22, ':').toString() : dateStr.toString();
       }else{
         return formatter.format(date);
       }
    }
    
     /**
    * Utility I method to read Object list from Wrapper
    * @param object
    * @param clazz
    * @return
    */
   private Object getWrapperList(Object object,Class<?> clazz){
       /* JSON webservice strip List wrapper  parameter
      * Read by XmlType and number of property.
      *
      * @XmlAccessorType(XmlAccessType.FIELD)
           @XmlType(name = "xxxxItems", propOrder = {
               "xxxxItem"
           })
            public class ReportItems implements Serializable {
          *
          */ 
         try {
           if(object == null || clazz == null || clazz.isPrimitive())
             return null;
           XmlType xmlType = clazz.getAnnotation(XmlType.class);
         if(xmlType != null && xmlType.propOrder().length == 1 &&
             xmlType.name().equals(xmlType.propOrder()[0]+"s")){
           PropertyDescriptor[] props = getBeanProperties(clazz);
          
           if(props.length == 1 &&
               Collection.class.isAssignableFrom(props[0].getReadMethod().getReturnType())){
             return props[0].getReadMethod().invoke(object, (Object[])null);
           }
         }
         } catch (Throwable e) {/*Dont mind*/}
         return null;
         // End
     }
  
  
   /**
    * Utility method return instance from class. For meta data document generation.
    * @param propertyType
    * @param field
    * @param webService
    * @return
    */
   private Object getMetaDataInstance(Class<?> propertyType, JSONWebService webService, Field field){
     String defaultVal = null;
     if(field != null && field.isAnnotationPresent(XmlElement.class)){
       XmlElement element = field.getAnnotation(XmlElement.class);
      if(!element.defaultValue().equals(NULL)){
        if(propertyType.isEnum()){
          defaultVal  = element.defaultValue();
          // In case of enum meta data is decided list
        } else if(Boolean.TYPE.equals(propertyType) || Boolean.class.equals(propertyType)){
          return Boolean.valueOf(element.defaultValue());
        } else {
          return element.defaultValue();
        }
      }
    }
    
     if(WSJSONPopulator.isJSONPrimitive(propertyType)){
        // Go with null
        if(Number.class.isAssignableFrom(propertyType)
            || Integer.TYPE.equals(propertyType)
            || Byte.TYPE.equals(propertyType)
            || Short.TYPE.equals(propertyType)
            || Long.TYPE.equals(propertyType)){
          return 0
        }else if(propertyType.isAssignableFrom(String.class)){
          return "";
        }else if(propertyType.isAssignableFrom(Boolean.class)
            || Boolean.TYPE.equals(propertyType)){
          return false;
        }else if(propertyType.isAssignableFrom(Date.class)){
          return new Date();
        }else if(propertyType.isAssignableFrom(Calendar.class)){
          return Calendar.getInstance();
        }else if(propertyType.isEnum()){
          if(this.schemaMode)
            return propertyType.getEnumConstants()[0];
          StringBuffer b = new StringBuffer();
          if(defaultVal != null){
            // Write default value as first constant in meta data.
            b.append(defaultVal);
          }
          for(Object cont: propertyType.getEnumConstants()){
            String value = ((Enum<?>)cont).name();
            try{
              // If xml value annotation use it.
              value  = propertyType.getDeclaredField(value).getAnnotation(XmlEnumValue.class).value();
            }catch(Throwable th){};
            if(value.equals(defaultVal))continue;
            b.append((b.length() != 0 ? "|" :"") + value);
          }
          return b.toString();
        }else if(Float.TYPE.equals(propertyType) || Double.TYPE.equals(propertyType)){
          return 0.0
        }else {
          return null;
        }
      }else{
        for(Object ob : stack){
          if(ob.getClass().equals(propertyType)){
            return ob;
          }
        }
        Object instance = getNewInstance(propertyType);
        return instance == null ? propertyType.getSimpleName() : instance;
      }
   }
  
  /**
   * Getter to return attachments
   * @return
   */
  public List<Map<String, Object>> getAttachments() {
    return attachments;
  }
}
TOP

Related Classes of com.jaxws.json.codec.encode.WSJSONWriter

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.