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