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.collections.*;
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 MsgPackSerializer ctx;
034
035   /**
036    * Create a new session using properties specified in the context.
037    *
038    * @param ctx
039    *    The context creating this session object.
040    *    The context contains all the configuration settings for this object.
041    * @param args
042    *    Runtime arguments.
043    *    These specify session-level information such as locale and URI context.
044    *    It also include session-level properties that override the properties defined on the bean and
045    *    serializer contexts.
046    */
047   protected MsgPackSerializerSession(MsgPackSerializer ctx, SerializerSessionArgs args) {
048      super(ctx, args);
049      this.ctx = ctx;
050   }
051
052   @Override /* SerializerSession */
053   protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
054      serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null);
055   }
056
057   /*
058    * Converts the specified output target object to an {@link MsgPackOutputStream}.
059    */
060   private static final MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws IOException {
061      Object output = out.getRawOutput();
062      if (output instanceof MsgPackOutputStream)
063         return (MsgPackOutputStream)output;
064      MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream());
065      out.setOutputStream(os);
066      return os;
067   }
068
069   /*
070    * Workhorse method.
071    * Determines the type of object, and then calls the appropriate type-specific serialization method.
072    */
073   @SuppressWarnings({ "rawtypes" })
074   private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws IOException, SerializeException {
075
076      if (o == null)
077         return out.appendNull();
078
079      if (eType == null)
080         eType = object();
081
082      ClassMeta<?> aType;        // The actual type
083      ClassMeta<?> sType;        // The serialized type
084
085      aType = push2(attrName, o, eType);
086      boolean isRecursion = aType == null;
087
088      // Handle recursion
089      if (aType == null)
090         return out.appendNull();
091
092      // Handle Optional<X>
093      if (isOptional(aType)) {
094         o = getOptionalValue(o);
095         eType = getOptionalType(eType);
096         aType = getClassMetaForObject(o, object());
097      }
098
099      sType = aType;
100      String typeName = getBeanTypeName(this, eType, aType, pMeta);
101
102      // Swap if necessary
103      PojoSwap swap = aType.getSwap(this);
104      if (swap != null) {
105         o = swap(swap, 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 IOException, SerializeException {
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 IOException, SerializeException {
173
174      List<BeanPropertyValue> values = m.getValues(isKeepNullProperties(), 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         // Must handle the case where recursion occurs and property is not serialized.
181         if ((! isKeepNullProperties()) && willRecurse(p))
182            size--;
183      }
184
185      out.startMap(size);
186
187      for (BeanPropertyValue p : values) {
188         BeanPropertyMeta pMeta = p.getMeta();
189         if (pMeta.canRead()) {
190            ClassMeta<?> cMeta = p.getClassMeta();
191            String key = p.getName();
192            Object value = p.getValue();
193            Throwable t = p.getThrown();
194            if (t != null) {
195               onBeanGetterException(pMeta, t);
196            } else if ((! isKeepNullProperties()) && willRecurse(p)) {
197               /* Ignored */
198            } else {
199               serializeAnything(out, key, null, null, null);
200               serializeAnything(out, value, cMeta, key, pMeta);
201            }
202         }
203      }
204   }
205
206   private boolean willRecurse(BeanPropertyValue v) throws SerializeException {
207      ClassMeta<?> aType = push2(v.getName(), v.getValue(), v.getClassMeta());
208       if (aType != null)
209          pop();
210       return aType == null;
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 IOException, SerializeException {
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
238   //-----------------------------------------------------------------------------------------------------------------
239   // Properties
240   //-----------------------------------------------------------------------------------------------------------------
241
242   @Override
243   protected final boolean isAddBeanTypes() {
244      return ctx.isAddBeanTypes();
245   }
246
247   //-----------------------------------------------------------------------------------------------------------------
248   // Other methods
249   //-----------------------------------------------------------------------------------------------------------------
250
251   @Override /* Session */
252   public OMap toMap() {
253      return super.toMap()
254         .a("MsgPackSerializerSession", new DefaultFilteringOMap()
255         );
256   }
257}