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