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      // Handle Optional<X>
094      if (isOptional(aType)) {
095         o = getOptionalValue(o);
096         eType = getOptionalType(eType);
097         aType = getClassMetaForObject(o, object());
098      }
099
100      sType = aType;
101      String typeName = getBeanTypeName(eType, aType, pMeta);
102
103      // Swap if necessary
104      PojoSwap swap = aType.getPojoSwap(this);
105      if (swap != null) {
106         o = swap(swap, o);
107         sType = swap.getSwapClassMeta(this);
108
109         // If the getSwapClass() method returns Object, we need to figure out
110         // the actual type now.
111         if (sType.isObject())
112            sType = getClassMetaForObject(o);
113      }
114
115      // '\0' characters are considered null.
116      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
117         out.appendNull();
118      else if (sType.isBoolean())
119         out.appendBoolean((Boolean)o);
120      else if (sType.isNumber())
121         out.appendNumber((Number)o);
122      else if (sType.isBean())
123         serializeBeanMap(out, toBeanMap(o), typeName);
124      else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
125         out.appendString(resolveUri(o.toString()));
126      else if (sType.isMap()) {
127         if (o instanceof BeanMap)
128            serializeBeanMap(out, (BeanMap)o, typeName);
129         else
130            serializeMap(out, (Map)o, eType);
131      }
132      else if (sType.isCollection()) {
133         serializeCollection(out, (Collection) o, eType);
134      }
135      else if (sType.isArray()) {
136         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
137      }
138      else if (sType.isReader() || sType.isInputStream()) {
139         IOUtils.pipe(o, out);
140      }
141      else
142         out.appendString(toString(o));
143
144      if (! isRecursion)
145         pop();
146      return out;
147   }
148
149   @SuppressWarnings({ "rawtypes", "unchecked" })
150   private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws IOException, SerializeException {
151
152      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
153
154      m = sort(m);
155
156      // The map size may change as we're iterating over it, so
157      // grab a snapshot of the entries in a separate list.
158      List<SimpleMapEntry> entries = new ArrayList<>(m.size());
159      for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
160         entries.add(new SimpleMapEntry(e.getKey(), e.getValue()));
161
162      out.startMap(entries.size());
163
164      for (SimpleMapEntry e : entries) {
165         Object value = e.value;
166         Object key = generalize(e.key, keyType);
167
168         serializeAnything(out, key, keyType, null, null);
169         serializeAnything(out, value, valueType, null, null);
170      }
171   }
172
173   private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws IOException, SerializeException {
174
175      List<BeanPropertyValue> values = m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null);
176
177      int size = values.size();
178      for (BeanPropertyValue p : values)
179         if (p.getThrown() != null)
180            size--;
181      out.startMap(size);
182
183      for (BeanPropertyValue p : values) {
184         BeanPropertyMeta pMeta = p.getMeta();
185         if (pMeta.canRead()) {
186            ClassMeta<?> cMeta = p.getClassMeta();
187            String key = p.getName();
188            Object value = p.getValue();
189            Throwable t = p.getThrown();
190            if (t != null)
191               onBeanGetterException(pMeta, t);
192            else {
193               serializeAnything(out, key, null, null, null);
194               serializeAnything(out, value, cMeta, key, pMeta);
195            }
196         }
197      }
198   }
199
200   private static final class SimpleMapEntry {
201      final Object key;
202      final Object value;
203
204      SimpleMapEntry(Object key, Object value) {
205         this.key = key;
206         this.value = value;
207      }
208   }
209
210   @SuppressWarnings({"rawtypes", "unchecked"})
211   private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws IOException, SerializeException {
212
213      ClassMeta<?> elementType = type.getElementType();
214      List<Object> l = new ArrayList<>(c.size());
215
216      c = sort(c);
217      l.addAll(c);
218
219      out.startArray(l.size());
220
221      for (Object o : l)
222         serializeAnything(out, o, elementType, "<iterator>", null);
223   }
224
225   //-----------------------------------------------------------------------------------------------------------------
226   // Properties
227   //-----------------------------------------------------------------------------------------------------------------
228
229   @Override
230   protected final boolean isAddBeanTypes() {
231      return ctx.isAddBeanTypes();
232   }
233
234   //-----------------------------------------------------------------------------------------------------------------
235   // Other methods
236   //-----------------------------------------------------------------------------------------------------------------
237
238   @Override /* Session */
239   public ObjectMap toMap() {
240      return super.toMap()
241         .append("MsgPackSerializerSession", new DefaultFilteringObjectMap()
242         );
243   }
244}