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