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