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.json;
014
015import static org.apache.juneau.common.internal.IOUtils.*;
016
017import java.io.*;
018import java.lang.reflect.*;
019import java.nio.charset.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.httppart.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.serializer.*;
027import org.apache.juneau.svl.*;
028import org.apache.juneau.swap.*;
029
030/**
031 * Session object that lives for the duration of a single use of {@link JsonSerializer}.
032 *
033 * <h5 class='section'>Notes:</h5><ul>
034 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
035 * </ul>
036 *
037 * <h5 class='section'>See Also:</h5><ul>
038 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.JsonDetails">JSON Details</a>
039 * </ul>
040 */
041public class JsonSerializerSession extends WriterSerializerSession {
042
043   //-----------------------------------------------------------------------------------------------------------------
044   // Static
045   //-----------------------------------------------------------------------------------------------------------------
046
047   /**
048    * Creates a new builder for this object.
049    *
050    * @param ctx The context creating this session.
051    * @return A new builder.
052    */
053   public static Builder create(JsonSerializer ctx) {
054      return new Builder(ctx);
055   }
056
057   //-----------------------------------------------------------------------------------------------------------------
058   // Builder
059   //-----------------------------------------------------------------------------------------------------------------
060
061   /**
062    * Builder class.
063    */
064   @FluentSetters
065   public static class Builder extends WriterSerializerSession.Builder {
066
067      JsonSerializer ctx;
068
069      /**
070       * Constructor
071       *
072       * @param ctx The context creating this session.
073       */
074      protected Builder(JsonSerializer ctx) {
075         super(ctx);
076         this.ctx = ctx;
077      }
078
079      @Override
080      public JsonSerializerSession build() {
081         return new JsonSerializerSession(this);
082      }
083
084      // <FluentSetters>
085
086      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
087      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
088         super.apply(type, apply);
089         return this;
090      }
091
092      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
093      public Builder debug(Boolean value) {
094         super.debug(value);
095         return this;
096      }
097
098      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
099      public Builder properties(Map<String,Object> value) {
100         super.properties(value);
101         return this;
102      }
103
104      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
105      public Builder property(String key, Object value) {
106         super.property(key, value);
107         return this;
108      }
109
110      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
111      public Builder unmodifiable() {
112         super.unmodifiable();
113         return this;
114      }
115
116      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
117      public Builder locale(Locale value) {
118         super.locale(value);
119         return this;
120      }
121
122      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
123      public Builder localeDefault(Locale value) {
124         super.localeDefault(value);
125         return this;
126      }
127
128      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
129      public Builder mediaType(MediaType value) {
130         super.mediaType(value);
131         return this;
132      }
133
134      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
135      public Builder mediaTypeDefault(MediaType value) {
136         super.mediaTypeDefault(value);
137         return this;
138      }
139
140      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
141      public Builder timeZone(TimeZone value) {
142         super.timeZone(value);
143         return this;
144      }
145
146      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
147      public Builder timeZoneDefault(TimeZone value) {
148         super.timeZoneDefault(value);
149         return this;
150      }
151
152      @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
153      public Builder javaMethod(Method value) {
154         super.javaMethod(value);
155         return this;
156      }
157
158      @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
159      public Builder resolver(VarResolverSession value) {
160         super.resolver(value);
161         return this;
162      }
163
164      @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
165      public Builder schema(HttpPartSchema value) {
166         super.schema(value);
167         return this;
168      }
169
170      @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
171      public Builder schemaDefault(HttpPartSchema value) {
172         super.schemaDefault(value);
173         return this;
174      }
175
176      @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */
177      public Builder uriContext(UriContext value) {
178         super.uriContext(value);
179         return this;
180      }
181
182      @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
183      public Builder fileCharset(Charset value) {
184         super.fileCharset(value);
185         return this;
186      }
187
188      @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
189      public Builder streamCharset(Charset value) {
190         super.streamCharset(value);
191         return this;
192      }
193
194      @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */
195      public Builder useWhitespace(Boolean value) {
196         super.useWhitespace(value);
197         return this;
198      }
199
200      // </FluentSetters>
201   }
202
203   //-----------------------------------------------------------------------------------------------------------------
204   // Instance
205   //-----------------------------------------------------------------------------------------------------------------
206
207   private final JsonSerializer ctx;
208
209   /**
210    * Constructor.
211    *
212    * @param builder The builder for this object.
213    */
214   protected JsonSerializerSession(Builder builder) {
215      super(builder);
216      this.ctx = builder.ctx;
217   }
218
219   @Override /* SerializerSesssion */
220   protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
221      serializeAnything(getJsonWriter(out).i(getInitialDepth()), o, getExpectedRootType(o), "root", null);
222   }
223
224   /**
225    * Method that can be called from subclasses to serialize an object to JSON.
226    *
227    * <p>
228    * Used by {@link JsonSchemaSerializerSession} for serializing examples to JSON.
229    *
230    * @param o The object to serialize.
231    * @return The serialized object.
232    * @throws Exception Error occurred.
233    */
234   protected String serializeJson(Object o) throws Exception {
235      StringWriter sw = new StringWriter();
236      serializeAnything(getJsonWriter(createPipe(sw)).i(getInitialDepth()), o, getExpectedRootType(o), "root", null);
237      return sw.toString();
238   }
239
240   /**
241    * Workhorse method.
242    * Determines the type of object, and then calls the appropriate type-specific serialization method.
243    *
244    * @param out The output writer.
245    * @param o The object to serialize.
246    * @param eType The expected type.
247    * @param attrName The attribute name.
248    * @param pMeta The bean property currently being parsed.
249    * @return The same writer passed in.
250    * @throws SerializeException General serialization error occurred.
251    */
252   @SuppressWarnings({ "rawtypes" })
253   protected JsonWriter serializeAnything(JsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException {
254
255      if (o == null) {
256         out.append("null");
257         return out;
258      }
259
260      if (eType == null)
261         eType = object();
262
263      ClassMeta<?> aType;        // The actual type
264      ClassMeta<?> sType;        // The serialized type
265
266      aType = push2(attrName, o, eType);
267      boolean isRecursion = aType == null;
268
269      // Handle recursion
270      if (aType == null) {
271         o = null;
272         aType = object();
273      }
274
275      // Handle Optional<X>
276      if (isOptional(aType)) {
277         o = getOptionalValue(o);
278         eType = getOptionalType(eType);
279         aType = getClassMetaForObject(o, object());
280      }
281
282      sType = aType;
283      String typeName = getBeanTypeName(this, eType, aType, pMeta);
284
285      // Swap if necessary
286      ObjectSwap swap = aType.getSwap(this);
287      if (swap != null) {
288         o = swap(swap, o);
289         sType = swap.getSwapClassMeta(this);
290
291         // If the getSwapClass() method returns Object, we need to figure out
292         // the actual type now.
293         if (sType.isObject())
294            sType = getClassMetaForObject(o);
295      }
296
297      String wrapperAttr = getJsonClassMeta(sType).getWrapperAttr();
298      if (wrapperAttr != null) {
299         out.w('{').cr(indent).attr(wrapperAttr).w(':').s(indent);
300         indent++;
301      }
302
303      // '\0' characters are considered null.
304      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) {
305         out.append("null");
306      } else if (sType.isNumber() || sType.isBoolean()) {
307         out.append(o);
308      } else if (sType.isBean()) {
309         serializeBeanMap(out, toBeanMap(o), typeName);
310      } else if (sType.isUri() || (pMeta != null && pMeta.isUri())) {
311         out.uriValue(o);
312      } else if (sType.isMap()) {
313         if (o instanceof BeanMap)
314            serializeBeanMap(out, (BeanMap)o, typeName);
315         else
316            serializeMap(out, (Map)o, eType);
317      } else if (sType.isCollection()) {
318         serializeCollection(out, (Collection) o, eType);
319      } else if (sType.isArray()) {
320         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
321      } else if (sType.isReader()) {
322         pipe((Reader)o, out, SerializerSession::handleThrown);
323      } else if (sType.isInputStream()) {
324         pipe((InputStream)o, out, SerializerSession::handleThrown);
325      } else {
326         out.stringValue(toString(o));
327      }
328
329      if (wrapperAttr != null) {
330         indent--;
331         out.cre(indent-1).w('}');
332      }
333
334      if (! isRecursion)
335         pop();
336      return out;
337   }
338
339   @SuppressWarnings({ "rawtypes", "unchecked" })
340   private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws SerializeException {
341
342      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
343
344      int i = indent;
345      out.w('{');
346
347      Flag addComma = Flag.create();
348      forEachEntry(m, x -> {
349         addComma.ifSet(()->out.w(',').smi(i)).set();
350         Object value = x.getValue();
351         Object key = generalize(x.getKey(), keyType);
352         out.cr(i).attr(toString(key)).w(':').s(i);
353         serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null);
354      });
355
356      out.cre(i-1).w('}');
357
358      return out;
359   }
360
361   private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws SerializeException {
362      int i = indent;
363      out.w('{');
364
365      Flag addComma = Flag.create();
366      Predicate<Object> checkNull = x -> isKeepNullProperties() || x != null;
367
368      if (typeName != null) {
369         BeanPropertyMeta pm = m.getMeta().getTypeProperty();
370         out.cr(i).attr(pm.getName()).w(':').s(i).stringValue(typeName);
371         addComma.set();
372      }
373
374      m.forEachValue(checkNull, (pMeta,key,value,thrown) -> {
375         ClassMeta<?> cMeta = pMeta.getClassMeta();
376         if (thrown != null)
377            onBeanGetterException(pMeta, thrown);
378
379         if (canIgnoreValue(cMeta, key, value))
380            return;
381
382         addComma.ifSet(()->out.append(',').smi(i)).set();
383
384         out.cr(i).attr(key).w(':').s(i);
385
386         serializeAnything(out, value, cMeta, key, pMeta);
387      });
388
389      out.cre(i-1).w('}');
390      return out;
391   }
392
393   @SuppressWarnings({"rawtypes", "unchecked"})
394   private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws SerializeException {
395
396      ClassMeta<?> elementType = type.getElementType();
397
398      out.w('[');
399      Flag addComma = Flag.create();
400      forEachEntry(c, x -> {
401         addComma.ifSet(()->out.w(',').smi(indent)).set();
402         out.cr(indent);
403         serializeAnything(out, x, elementType, "<iterator>", null);
404      });
405
406      out.cre(indent-1).w(']');
407      return out;
408   }
409
410   /**
411    * Converts the specified output target object to an {@link JsonWriter}.
412    *
413    * @param out The output target object.
414    * @return The output target object wrapped in an {@link JsonWriter}.
415    * @throws IOException Thrown by underlying stream.
416    */
417   protected final JsonWriter getJsonWriter(SerializerPipe out) throws IOException {
418      Object output = out.getRawOutput();
419      if (output instanceof JsonWriter)
420         return (JsonWriter)output;
421      JsonWriter w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isEscapeSolidus(), getQuoteChar(),
422         isSimpleAttrs(), isTrimStrings(), getUriResolver());
423      out.setWriter(w);
424      return w;
425   }
426
427   //-----------------------------------------------------------------------------------------------------------------
428   // Properties
429   //-----------------------------------------------------------------------------------------------------------------
430
431   /**
432    * Add <js>"_type"</js> properties when needed.
433    *
434    * @see JsonSerializer.Builder#addBeanTypesJson()
435    * @return
436    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
437    *    through reflection.
438    */
439   @Override
440   protected final boolean isAddBeanTypes() {
441      return ctx.isAddBeanTypes();
442   }
443
444   /**
445    * Prefix solidus <js>'/'</js> characters with escapes.
446    *
447    * @see JsonSerializer.Builder#escapeSolidus()
448    * @return
449    *    <jk>true</jk> if solidus (e.g. slash) characters should be escaped.
450    */
451   protected final boolean isEscapeSolidus() {
452      return ctx.isEscapeSolidus();
453   }
454
455   /**
456    * Simple JSON attributes.
457    *
458    * @see JsonSerializer.Builder#simpleAttrs()
459    * @return
460    *    <jk>true</jk> if JSON attribute names will only be quoted when necessary.
461    *    <br>Otherwise, they are always quoted.
462    */
463   protected final boolean isSimpleAttrs() {
464      return ctx.isSimpleAttrs();
465   }
466
467   //-----------------------------------------------------------------------------------------------------------------
468   // Extended metadata
469   //-----------------------------------------------------------------------------------------------------------------
470
471   /**
472    * Returns the language-specific metadata on the specified class.
473    *
474    * @param cm The class to return the metadata on.
475    * @return The metadata.
476    */
477   protected JsonClassMeta getJsonClassMeta(ClassMeta<?> cm) {
478      return ctx.getJsonClassMeta(cm);
479   }
480}