001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.msgpack;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.IoUtils.*;
022import static org.apache.juneau.commons.utils.Utils.*;
023
024import java.io.*;
025import java.lang.reflect.*;
026import java.util.*;
027import java.util.function.*;
028
029import org.apache.juneau.*;
030import org.apache.juneau.httppart.*;
031import org.apache.juneau.serializer.*;
032import org.apache.juneau.svl.*;
033
034/**
035 * Session object that lives for the duration of a single use of {@link MsgPackSerializer}.
036 *
037 * <h5 class='section'>Notes:</h5><ul>
038 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
039 * </ul>
040 *
041 * <h5 class='section'>See Also:</h5><ul>
042 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MessagePackBasics">MessagePack Basics</a>
043
044 * </ul>
045 */
046@SuppressWarnings("resource")
047public class MsgPackSerializerSession extends OutputStreamSerializerSession {
048   /**
049    * Builder class.
050    */
051   public static class Builder extends OutputStreamSerializerSession.Builder {
052
053      private MsgPackSerializer ctx;
054
055      /**
056       * Constructor
057       *
058       * @param ctx The context creating this session.
059       *    <br>Cannot be <jk>null</jk>.
060       */
061      protected Builder(MsgPackSerializer ctx) {
062         super(assertArgNotNull("ctx", ctx));
063         this.ctx = ctx;
064      }
065
066      @Override /* Overridden from Builder */
067      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
068         super.apply(type, apply);
069         return this;
070      }
071
072      @Override
073      public MsgPackSerializerSession build() {
074         return new MsgPackSerializerSession(this);
075      }
076
077      @Override /* Overridden from Builder */
078      public Builder debug(Boolean value) {
079         super.debug(value);
080         return this;
081      }
082
083      @Override /* Overridden from Builder */
084      public Builder javaMethod(Method value) {
085         super.javaMethod(value);
086         return this;
087      }
088
089      @Override /* Overridden from Builder */
090      public Builder locale(Locale value) {
091         super.locale(value);
092         return this;
093      }
094
095      @Override /* Overridden from Builder */
096      public Builder mediaType(MediaType value) {
097         super.mediaType(value);
098         return this;
099      }
100
101      @Override /* Overridden from Builder */
102      public Builder mediaTypeDefault(MediaType value) {
103         super.mediaTypeDefault(value);
104         return this;
105      }
106
107      @Override /* Overridden from Builder */
108      public Builder properties(Map<String,Object> value) {
109         super.properties(value);
110         return this;
111      }
112
113      @Override /* Overridden from Builder */
114      public Builder property(String key, Object value) {
115         super.property(key, value);
116         return this;
117      }
118
119      @Override /* Overridden from Builder */
120      public Builder resolver(VarResolverSession value) {
121         super.resolver(value);
122         return this;
123      }
124
125      @Override /* Overridden from Builder */
126      public Builder schema(HttpPartSchema value) {
127         super.schema(value);
128         return this;
129      }
130
131      @Override /* Overridden from Builder */
132      public Builder schemaDefault(HttpPartSchema value) {
133         super.schemaDefault(value);
134         return this;
135      }
136
137      @Override /* Overridden from Builder */
138      public Builder timeZone(TimeZone value) {
139         super.timeZone(value);
140         return this;
141      }
142
143      @Override /* Overridden from Builder */
144      public Builder timeZoneDefault(TimeZone value) {
145         super.timeZoneDefault(value);
146         return this;
147      }
148
149      @Override /* Overridden from Builder */
150      public Builder unmodifiable() {
151         super.unmodifiable();
152         return this;
153      }
154
155      @Override /* Overridden from Builder */
156      public Builder uriContext(UriContext value) {
157         super.uriContext(value);
158         return this;
159      }
160   }
161
162   private static class SimpleMapEntry {
163      final Object key;
164      final Object value;
165
166      SimpleMapEntry(Object key, Object value) {
167         this.key = key;
168         this.value = value;
169      }
170   }
171
172   /**
173    * Creates a new builder for this object.
174    *
175    * @param ctx The context creating this session.
176    *    <br>Cannot be <jk>null</jk>.
177    * @return A new builder.
178    */
179   public static Builder create(MsgPackSerializer ctx) {
180      return new Builder(assertArgNotNull("ctx", ctx));
181   }
182
183   /*
184    * Converts the specified output target object to an {@link MsgPackOutputStream}.
185    */
186   private static MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws IOException {
187      Object output = out.getRawOutput();
188      if (output instanceof MsgPackOutputStream output2)
189         return output2;
190      var os = new MsgPackOutputStream(out.getOutputStream());
191      out.setOutputStream(os);
192      return os;
193   }
194
195   private final MsgPackSerializer ctx;
196
197   /**
198    * Constructor.
199    *
200    * @param builder The builder for this object.
201    */
202   protected MsgPackSerializerSession(Builder builder) {
203      super(builder);
204      ctx = builder.ctx;
205   }
206
207   /*
208    * Workhorse method.
209    * Determines the type of object, and then calls the appropriate type-specific serialization method.
210    */
211   @SuppressWarnings({ "rawtypes" })
212   private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException {
213
214      if (o == null)
215         return out.appendNull();
216
217      if (eType == null)
218         eType = object();
219
220      var aType = (ClassMeta<?>)null;        // The actual type
221      var sType = (ClassMeta<?>)null;        // The serialized type
222
223      aType = push2(attrName, o, eType);
224      boolean isRecursion = aType == null;
225
226      // Handle recursion
227      if (aType == null)
228         return out.appendNull();
229
230      // Handle Optional<X>
231      if (isOptional(aType)) {
232         o = getOptionalValue(o);
233         eType = getOptionalType(eType);
234         aType = getClassMetaForObject(o, object());
235      }
236
237      sType = aType;
238      String typeName = getBeanTypeName(this, eType, aType, pMeta);
239
240      // Swap if necessary
241      var swap = aType.getSwap(this);
242      if (nn(swap)) {
243         o = swap(swap, o);
244         sType = swap.getSwapClassMeta(this);
245
246         // If the getSwapClass() method returns Object, we need to figure out
247         // the actual type now.
248         if (sType.isObject())
249            sType = getClassMetaForObject(o);
250      }
251
252      // '\0' characters are considered null.
253      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
254         out.appendNull();
255      else if (sType.isBoolean())
256         out.appendBoolean((Boolean)o);
257      else if (sType.isNumber())
258         out.appendNumber((Number)o);
259      else if (sType.isBean())
260         serializeBeanMap(out, toBeanMap(o), typeName);
261      else if (sType.isUri() || (nn(pMeta) && pMeta.isUri()))
262         out.appendString(resolveUri(o.toString()));
263      else if (sType.isMap()) {
264         if (o instanceof BeanMap)
265            serializeBeanMap(out, (BeanMap)o, typeName);
266         else
267            serializeMap(out, (Map)o, eType);
268      } else if (sType.isCollection()) {
269         serializeCollection(out, (Collection)o, eType);
270      } else if (sType.isByteArray()) {
271         out.appendBinary((byte[])o);
272      } else if (sType.isArray()) {
273         serializeCollection(out, toList(sType.inner(), o), eType);
274      } else if (sType.isReader()) {
275         pipe((Reader)o, out, SerializerSession::handleThrown);
276      } else if (sType.isInputStream()) {
277         pipe((InputStream)o, out, SerializerSession::handleThrown);
278      } else
279         out.appendString(toString(o));
280
281      if (! isRecursion)
282         pop();
283      return out;
284   }
285
286   private void serializeBeanMap(MsgPackOutputStream out, BeanMap<?> m, String typeName) throws SerializeException {
287
288      Predicate<Object> checkNull = x -> isKeepNullProperties() || nn(x);
289
290      var values = new ArrayList<BeanPropertyValue>();
291
292      if (nn(typeName)) {
293         BeanPropertyMeta pm = m.getMeta().getTypeProperty();
294         values.add(new BeanPropertyValue(pm, pm.getName(), typeName, null));
295      }
296
297      m.forEachValue(checkNull, (pMeta, key, value, thrown) -> {
298         if (nn(thrown)) {
299            onBeanGetterException(pMeta, thrown);
300            return;
301         }
302         var p = new BeanPropertyValue(pMeta, key, value, null);
303
304         if ((! isKeepNullProperties()) && willRecurse(p)) {
305            return; // Must handle the case where recursion occurs and property is not serialized.
306         }
307
308         values.add(p);
309      });
310
311      out.startMap(values.size());
312
313      values.forEach(x -> {
314         BeanPropertyMeta pMeta = x.getMeta();
315         if (pMeta.canRead()) {
316            var cMeta = x.getClassMeta();
317            String key = x.getName();
318            Object value = x.getValue();
319            serializeAnything(out, key, null, null, null);
320            serializeAnything(out, value, cMeta, key, pMeta);
321         }
322      });
323   }
324
325   @SuppressWarnings({ "rawtypes", "unchecked" })
326   private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws SerializeException {
327      var elementType = type.getElementType();
328      List<Object> l = listOfSize(c.size());
329      c = sort(c);
330      l.addAll(c);
331      out.startArray(l.size());
332      l.forEach(x -> serializeAnything(out, x, elementType, "<iterator>", null));
333   }
334
335   @SuppressWarnings({ "rawtypes", "unchecked" })
336   private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws SerializeException {
337
338      var keyType = type.getKeyType();
339      var valueType = type.getValueType();
340
341      m = sort(m);
342
343      // The map size may change as we're iterating over it, so
344      // grab a snapshot of the entries in a separate list.
345      List<SimpleMapEntry> entries = listOfSize(m.size());
346      m.forEach((k, v) -> entries.add(new SimpleMapEntry(k, v)));
347
348      out.startMap(entries.size());
349
350      entries.forEach(x -> {
351         Object value = x.value;
352         Object key = generalize(x.key, keyType);
353         serializeAnything(out, key, keyType, null, null);
354         serializeAnything(out, value, valueType, null, null);
355      });
356   }
357
358   private boolean willRecurse(BeanPropertyValue v) throws SerializeException {
359      var aType = push2(v.getName(), v.getValue(), v.getClassMeta());
360      if (nn(aType))
361         pop();
362      return aType == null;
363   }
364
365   @Override /* Overridden from SerializerSession */
366   protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
367      serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null);
368   }
369
370   @Override
371   protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
372}