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 java.io.*;
016import java.util.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.httppart.*;
020import org.apache.juneau.internal.*;
021import org.apache.juneau.serializer.*;
022import org.apache.juneau.transform.*;
023
024/**
025 * Session object that lives for the duration of a single use of {@link UonSerializer}.
026 *
027 * <p>
028 * This class is NOT thread safe.
029 * It is typically discarded after one-time use although it can be reused within the same thread.
030 */
031public class UonSerializerSession extends WriterSerializerSession implements HttpPartSerializerSession {
032
033   private final UonSerializer ctx;
034   private final boolean plainTextParams;
035
036   /**
037    * @param ctx
038    *    The context creating this session object.
039    *    The context contains all the configuration settings for this object.
040    * @param encode Override the {@link UonSerializer#UON_encoding} setting.
041    * @param args
042    *    Runtime arguments.
043    *    These specify session-level information such as locale and URI context.
044    *    It also include session-level properties that override the properties defined on the bean and
045    *    serializer contexts.
046    */
047   public UonSerializerSession(UonSerializer ctx, Boolean encode, SerializerSessionArgs args) {
048      super(ctx, args);
049      this.ctx = ctx;
050      plainTextParams = ctx.getParamFormat() == ParamFormat.PLAINTEXT;
051   }
052
053   @Override /* Session */
054   public ObjectMap asMap() {
055      return super.asMap()
056         .append("UonSerializerSession", new ObjectMap()
057      );
058   }
059
060   /**
061    * Converts the specified output target object to an {@link UonWriter}.
062    *
063    * @param out The output target object.
064    * @return The output target object wrapped in an {@link UonWriter}.
065    * @throws Exception
066    */
067   protected final UonWriter getUonWriter(SerializerPipe out) throws Exception {
068      Object output = out.getRawOutput();
069      if (output instanceof UonWriter)
070         return (UonWriter)output;
071      UonWriter w = new UonWriter(this, out.getWriter(), isUseWhitespace(), getMaxIndent(), isEncodeChars(), isTrimStrings(), plainTextParams, getUriResolver());
072      out.setWriter(w);
073      return w;
074   }
075
076   private final UonWriter getUonWriter(Writer out) throws Exception {
077      return new UonWriter(this, out, isUseWhitespace(), getMaxIndent(), isEncodeChars(), isTrimStrings(), plainTextParams, getUriResolver());
078   }
079
080   @Override /* Serializer */
081   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
082      serializeAnything(getUonWriter(out), o, getExpectedRootType(o), "root", null);
083   }
084
085   /**
086    * Workhorse method.
087    *
088    * <p>
089    * Determines the type of object, and then calls the appropriate type-specific serialization method.
090    *
091    * @param out The writer to serialize to.
092    * @param o The object being serialized.
093    * @param eType The expected type of the object if this is a bean property.
094    * @param attrName
095    *    The bean property name if this is a bean property.
096    *    <jk>null</jk> if this isn't a bean property being serialized.
097    * @param pMeta The bean property metadata.
098    * @return The same writer passed in.
099    * @throws Exception
100    */
101   @SuppressWarnings({ "rawtypes", "unchecked" })
102   protected SerializerWriter serializeAnything(UonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception {
103
104      if (o == null) {
105         out.appendObject(null, false);
106         return out;
107      }
108
109      if (eType == null)
110         eType = object();
111
112      ClassMeta<?> aType;        // The actual type
113      ClassMeta<?> sType;        // The serialized type
114
115      aType = push(attrName, o, eType);
116      boolean isRecursion = aType == null;
117
118      // Handle recursion
119      if (aType == null) {
120         o = null;
121         aType = object();
122      }
123
124      sType = aType;
125      String typeName = getBeanTypeName(eType, aType, pMeta);
126
127      // Swap if necessary
128      PojoSwap swap = aType.getPojoSwap(this);
129      if (swap != null) {
130         o = swap.swap(this, o);
131         sType = swap.getSwapClassMeta(this);
132
133         // If the getSwapClass() method returns Object, we need to figure out
134         // the actual type now.
135         if (sType.isObject())
136            sType = getClassMetaForObject(o);
137      }
138
139      // '\0' characters are considered null.
140      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
141         out.appendObject(null, false);
142      else if (sType.isBoolean())
143         out.appendBoolean(o);
144      else if (sType.isNumber())
145         out.appendNumber(o);
146      else if (sType.isBean())
147         serializeBeanMap(out, toBeanMap(o), typeName);
148      else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
149         out.appendUri(o);
150      else if (sType.isMap()) {
151         if (o instanceof BeanMap)
152            serializeBeanMap(out, (BeanMap)o, typeName);
153         else
154            serializeMap(out, (Map)o, eType);
155      }
156      else if (sType.isCollection()) {
157         serializeCollection(out, (Collection) o, eType);
158      }
159      else if (sType.isArray()) {
160         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
161      }
162      else if (sType.isReader() || sType.isInputStream()) {
163         IOUtils.pipe(o, out);
164      }
165      else {
166         out.appendObject(o, false);
167      }
168
169      if (! isRecursion)
170         pop();
171      return out;
172   }
173
174   @SuppressWarnings({ "rawtypes", "unchecked" })
175   private SerializerWriter serializeMap(UonWriter out, Map m, ClassMeta<?> type) throws Exception {
176
177      m = sort(m);
178
179      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
180
181      if (! plainTextParams)
182         out.append('(');
183
184      Iterator mapEntries = m.entrySet().iterator();
185
186      while (mapEntries.hasNext()) {
187         Map.Entry e = (Map.Entry) mapEntries.next();
188         Object value = e.getValue();
189         Object key = generalize(e.getKey(), keyType);
190         out.cr(indent).appendObject(key, false).append('=');
191         serializeAnything(out, value, valueType, toString(key), null);
192         if (mapEntries.hasNext())
193            out.append(',');
194      }
195
196      if (m.size() > 0)
197         out.cre(indent-1);
198
199      if (! plainTextParams)
200         out.append(')');
201
202      return out;
203   }
204
205   private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, String typeName) throws Exception {
206
207      if (! plainTextParams)
208         out.append('(');
209
210      boolean addComma = false;
211
212      for (BeanPropertyValue p : m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
213         BeanPropertyMeta pMeta = p.getMeta();
214         if (pMeta.canRead()) {
215            ClassMeta<?> cMeta = p.getClassMeta();
216
217            String key = p.getName();
218            Object value = p.getValue();
219            Throwable t = p.getThrown();
220            if (t != null)
221               onBeanGetterException(pMeta, t);
222
223            if (canIgnoreValue(cMeta, key, value))
224               continue;
225
226            if (addComma)
227               out.append(',');
228
229            out.cr(indent).appendObject(key, false).append('=');
230
231            serializeAnything(out, value, cMeta, key, pMeta);
232
233            addComma = true;
234         }
235      }
236
237      if (m.size() > 0)
238         out.cre(indent-1);
239      if (! plainTextParams)
240         out.append(')');
241
242      return out;
243   }
244
245   @SuppressWarnings({ "rawtypes", "unchecked" })
246   private SerializerWriter serializeCollection(UonWriter out, Collection c, ClassMeta<?> type) throws Exception {
247
248      ClassMeta<?> elementType = type.getElementType();
249
250      c = sort(c);
251
252      if (! plainTextParams)
253         out.append('@').append('(');
254
255      for (Iterator i = c.iterator(); i.hasNext();) {
256         out.cr(indent);
257         serializeAnything(out, i.next(), elementType, "<iterator>", null);
258         if (i.hasNext())
259            out.append(',');
260      }
261
262      if (c.size() > 0)
263         out.cre(indent-1);
264      if (! plainTextParams)
265         out.append(')');
266
267      return out;
268   }
269
270   @Override /* HttpPartSerializer */
271   public String serialize(HttpPartType type, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
272      try {
273         // Shortcut for simple types.
274         ClassMeta<?> cm = getClassMetaForObject(value);
275         if (cm != null) {
276            if (cm.isNumber() || cm.isBoolean())
277               return ClassUtils.toString(value);
278            if (cm.isString()) {
279               String s = ClassUtils.toString(value);
280               if (s.isEmpty() || ! UonUtils.needsQuotes(s))
281                  return s;
282            }
283         }
284         StringWriter w = new StringWriter();
285         serializeAnything(getUonWriter(w), value, getExpectedRootType(value), "root", null);
286         return w.toString();
287      } catch (Exception e) {
288         throw new RuntimeException(e);
289      }
290   }
291
292   @Override /* HttpPartSerializer */
293   public String serialize(HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
294      return serialize(null, schema, value);
295   }
296
297   //-----------------------------------------------------------------------------------------------------------------
298   // Properties
299   //-----------------------------------------------------------------------------------------------------------------
300
301   /**
302    * Configuration property:  Encode non-valid URI characters.
303    *
304    * @see UonSerializer#UON_encoding
305    * @return
306    *    <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs.
307    */
308   protected final boolean isEncodeChars() {
309      return ctx.isEncodeChars();
310   }
311
312   /**
313    * Configuration property:  Add <js>"_type"</js> properties when needed.
314    *
315    * @see UonSerializer#UON_addBeanTypes
316    * @return
317    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
318    *    through reflection.
319    */
320   @Override
321   protected final boolean isAddBeanTypes() {
322      return ctx.isAddBeanTypes();
323   }
324
325   /**
326    * Configuration property:  Format to use for query/form-data/header values.
327    *
328    * @see UonSerializer#UON_paramFormat
329    * @return
330    *    Specifies the format to use for URL GET parameter keys and values.
331    */
332   protected final ParamFormat getParamFormat() {
333      return ctx.getParamFormat();
334   }
335
336   /**
337    * @deprecated Unused.
338    */
339   @SuppressWarnings("javadoc")
340   @Deprecated
341   public final boolean isAddBeanTypeProperties() {
342      return isAddBeanTypes();
343   }
344}