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