001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.msgpack;
014
015import static org.apache.juneau.msgpack.MsgPackSerializer.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.internal.*;
021import org.apache.juneau.serializer.*;
022import org.apache.juneau.transform.*;
023
024/**
025 * Session object that lives for the duration of a single use of {@link MsgPackSerializer}.
026 * 
027 * <p>
028 * This class is NOT thread safe.
029 * It is typically discarded after one-time use although it can be reused within the same thread.
030 */
031public final class MsgPackSerializerSession extends OutputStreamSerializerSession {
032
033   private final boolean
034      addBeanTypeProperties;
035
036   /**
037    * Create a new session using properties specified in the context.
038    * 
039    * @param ctx
040    *    The context creating this session object.
041    *    The context contains all the configuration settings for this object.
042    * @param args
043    *    Runtime arguments.
044    *    These specify session-level information such as locale and URI context.
045    *    It also include session-level properties that override the properties defined on the bean and
046    *    serializer contexts.
047    */
048   protected MsgPackSerializerSession(MsgPackSerializer ctx, SerializerSessionArgs args) {
049      super(ctx, args);
050      addBeanTypeProperties = getProperty(MSGPACK_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties);
051   }
052
053   @Override /* Session */
054   public ObjectMap asMap() {
055      return super.asMap()
056         .append("MsgPackSerializerSession", new ObjectMap()
057            .append("addBeanTypeProperties", addBeanTypeProperties)
058         );
059   }
060
061   /**
062    * Returns the {@link MsgPackSerializer#MSGPACK_addBeanTypeProperties} setting value for this session.
063    * 
064    * @return The {@link MsgPackSerializer#MSGPACK_addBeanTypeProperties} setting value for this session.
065    */
066   @Override /* SerializerSession */
067   protected final boolean isAddBeanTypeProperties() {
068      return addBeanTypeProperties;
069   }
070
071   @Override /* SerializerSession */
072   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
073      serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null);
074   }
075
076   /*
077    * Converts the specified output target object to an {@link MsgPackOutputStream}.
078    */
079   private static final MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws Exception {
080      Object output = out.getRawOutput();
081      if (output instanceof MsgPackOutputStream)
082         return (MsgPackOutputStream)output;
083      MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream());
084      out.setOutputStream(os);
085      return os;
086   }
087
088   /*
089    * Workhorse method.
090    * Determines the type of object, and then calls the appropriate type-specific serialization method.
091    */
092   @SuppressWarnings({ "rawtypes", "unchecked" })
093   private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception {
094
095      if (o == null)
096         return out.appendNull();
097
098      if (eType == null)
099         eType = object();
100
101      ClassMeta<?> aType;        // The actual type
102      ClassMeta<?> sType;        // The serialized type
103
104      aType = push(attrName, o, eType);
105      boolean isRecursion = aType == null;
106
107      // Handle recursion
108      if (aType == null) {
109         o = null;
110         aType = object();
111      }
112
113      sType = aType;
114      String typeName = getBeanTypeName(eType, aType, pMeta);
115
116      // Swap if necessary
117      PojoSwap swap = aType.getPojoSwap(this);
118      if (swap != null) {
119         o = swap.swap(this, o);
120         sType = swap.getSwapClassMeta(this);
121
122         // If the getSwapClass() method returns Object, we need to figure out
123         // the actual type now.
124         if (sType.isObject())
125            sType = getClassMetaForObject(o);
126      }
127
128      // '\0' characters are considered null.
129      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
130         out.appendNull();
131      else if (sType.isBoolean())
132         out.appendBoolean((Boolean)o);
133      else if (sType.isNumber())
134         out.appendNumber((Number)o);
135      else if (sType.isBean())
136         serializeBeanMap(out, toBeanMap(o), typeName);
137      else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
138         out.appendString(resolveUri(o.toString()));
139      else if (sType.isMap()) {
140         if (o instanceof BeanMap)
141            serializeBeanMap(out, (BeanMap)o, typeName);
142         else
143            serializeMap(out, (Map)o, eType);
144      }
145      else if (sType.isCollection()) {
146         serializeCollection(out, (Collection) o, eType);
147      }
148      else if (sType.isArray()) {
149         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
150      }
151      else if (sType.isReader() || sType.isInputStream()) {
152         IOUtils.pipe(o, out);
153      }
154      else
155         out.appendString(toString(o));
156
157      if (! isRecursion)
158         pop();
159      return out;
160   }
161
162   @SuppressWarnings({ "rawtypes", "unchecked" })
163   private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws Exception {
164
165      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
166
167      m = sort(m);
168
169      // The map size may change as we're iterating over it, so
170      // grab a snapshot of the entries in a separate list.
171      List<SimpleMapEntry> entries = new ArrayList<>(m.size());
172      for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
173         entries.add(new SimpleMapEntry(e.getKey(), e.getValue()));
174
175      out.startMap(entries.size());
176
177      for (SimpleMapEntry e : entries) {
178         Object value = e.value;
179         Object key = generalize(e.key, keyType);
180
181         serializeAnything(out, key, keyType, null, null);
182         serializeAnything(out, value, valueType, null, null);
183      }
184   }
185
186   private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws Exception {
187
188      List<BeanPropertyValue> values = m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null);
189
190      int size = values.size();
191      for (BeanPropertyValue p : values)
192         if (p.getThrown() != null)
193            size--;
194      out.startMap(size);
195
196      for (BeanPropertyValue p : values) {
197         BeanPropertyMeta pMeta = p.getMeta();
198         if (pMeta.canRead()) {
199            ClassMeta<?> cMeta = p.getClassMeta();
200            String key = p.getName();
201            Object value = p.getValue();
202            Throwable t = p.getThrown();
203            if (t != null)
204               onBeanGetterException(pMeta, t);
205            else {
206               serializeAnything(out, key, null, null, null);
207               serializeAnything(out, value, cMeta, key, pMeta);
208            }
209         }
210      }
211   }
212
213   private static final class SimpleMapEntry {
214      final Object key;
215      final Object value;
216
217      SimpleMapEntry(Object key, Object value) {
218         this.key = key;
219         this.value = value;
220      }
221   }
222
223   @SuppressWarnings({"rawtypes", "unchecked"})
224   private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws Exception {
225
226      ClassMeta<?> elementType = type.getElementType();
227      List<Object> l = new ArrayList<>(c.size());
228
229      c = sort(c);
230      l.addAll(c);
231
232      out.startArray(l.size());
233
234      for (Object o : l)
235         serializeAnything(out, o, elementType, "<iterator>", null);
236   }
237}