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.uon.UonSerializer.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
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 {
032
033   private final boolean
034      encodeChars,
035      addBeanTypeProperties,
036      plainTextParams;
037
038   /**
039    * @param ctx
040    *    The context creating this session object.
041    *    The context contains all the configuration settings for this object.
042    * @param encode Override the {@link UonSerializer#UON_encoding} setting.
043    * @param args
044    *    Runtime arguments.
045    *    These specify session-level information such as locale and URI context.
046    *    It also include session-level properties that override the properties defined on the bean and
047    *    serializer contexts.
048    */
049   public UonSerializerSession(UonSerializer ctx, Boolean encode, SerializerSessionArgs args) {
050      super(ctx, args);
051      encodeChars = encode == null ? getProperty(UON_encoding, boolean.class, ctx.encodeChars) : encode;
052      addBeanTypeProperties = getProperty(UON_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties);
053      plainTextParams = getProperty(UON_paramFormat, ParamFormat.class, ctx.paramFormat) == ParamFormat.PLAINTEXT;
054   }
055
056   @Override /* Session */
057   public ObjectMap asMap() {
058      return super.asMap()
059         .append("UonSerializerSession", new ObjectMap()
060            .append("addBeanTypeProperties", addBeanTypeProperties)
061            .append("encodeChars", encodeChars)
062            .append("plainTextParams", plainTextParams)
063         );
064   }
065
066   /**
067    * Returns the {@link UonSerializer#UON_addBeanTypeProperties} setting value for this session.
068    * 
069    * @return The {@link UonSerializer#UON_addBeanTypeProperties} setting value for this session.
070    */
071   @Override /* SerializerSession */
072   public final boolean isAddBeanTypeProperties() {
073      return addBeanTypeProperties;
074   }
075
076   /**
077    * Converts the specified output target object to an {@link UonWriter}.
078    * 
079    * @param out The output target object.
080    * @return The output target object wrapped in an {@link UonWriter}.
081    * @throws Exception
082    */
083   protected final UonWriter getUonWriter(SerializerPipe out) throws Exception {
084      Object output = out.getRawOutput();
085      if (output instanceof UonWriter)
086         return (UonWriter)output;
087      UonWriter w = new UonWriter(this, out.getWriter(), isUseWhitespace(), getMaxIndent(), encodeChars, isTrimStrings(), plainTextParams, getUriResolver());
088      out.setWriter(w);
089      return w;
090   }
091
092   @Override /* Serializer */
093   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
094      serializeAnything(getUonWriter(out), o, getExpectedRootType(o), "root", null);
095   }
096
097   /**
098    * Workhorse method. Determines the type of object, and then calls the appropriate type-specific serialization
099    * method.
100    * 
101    * @param out The writer to serialize to.
102    * @param o The object being serialized.
103    * @param eType The expected type of the object if this is a bean property.
104    * @param attrName
105    *    The bean property name if this is a bean property.
106    *    <jk>null</jk> if this isn't a bean property being serialized.
107    * @param pMeta The bean property metadata.
108    * @return The same writer passed in.
109    * @throws Exception
110    */
111   @SuppressWarnings({ "rawtypes", "unchecked" })
112   protected SerializerWriter serializeAnything(UonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception {
113
114      if (o == null) {
115         out.appendObject(null, false);
116         return out;
117      }
118
119      if (eType == null)
120         eType = object();
121
122      ClassMeta<?> aType;        // The actual type
123      ClassMeta<?> sType;        // The serialized type
124
125      aType = push(attrName, o, eType);
126      boolean isRecursion = aType == null;
127
128      // Handle recursion
129      if (aType == null) {
130         o = null;
131         aType = object();
132      }
133
134      sType = aType;
135      String typeName = getBeanTypeName(eType, aType, pMeta);
136
137      // Swap if necessary
138      PojoSwap swap = aType.getPojoSwap(this);
139      if (swap != null) {
140         o = swap.swap(this, o);
141         sType = swap.getSwapClassMeta(this);
142
143         // If the getSwapClass() method returns Object, we need to figure out
144         // the actual type now.
145         if (sType.isObject())
146            sType = getClassMetaForObject(o);
147      }
148
149      // '\0' characters are considered null.
150      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
151         out.appendObject(null, false);
152      else if (sType.isBoolean())
153         out.appendBoolean(o);
154      else if (sType.isNumber())
155         out.appendNumber(o);
156      else if (sType.isBean())
157         serializeBeanMap(out, toBeanMap(o), typeName);
158      else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
159         out.appendUri(o);
160      else if (sType.isMap()) {
161         if (o instanceof BeanMap)
162            serializeBeanMap(out, (BeanMap)o, typeName);
163         else
164            serializeMap(out, (Map)o, eType);
165      }
166      else if (sType.isCollection()) {
167         serializeCollection(out, (Collection) o, eType);
168      }
169      else if (sType.isArray()) {
170         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
171      }
172      else if (sType.isReader() || sType.isInputStream()) {
173         IOUtils.pipe(o, out);
174      }
175      else {
176         out.appendObject(o, false);
177      }
178
179      if (! isRecursion)
180         pop();
181      return out;
182   }
183
184   @SuppressWarnings({ "rawtypes", "unchecked" })
185   private SerializerWriter serializeMap(UonWriter out, Map m, ClassMeta<?> type) throws Exception {
186
187      m = sort(m);
188
189      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
190
191      if (! plainTextParams)
192         out.append('(');
193
194      Iterator mapEntries = m.entrySet().iterator();
195
196      while (mapEntries.hasNext()) {
197         Map.Entry e = (Map.Entry) mapEntries.next();
198         Object value = e.getValue();
199         Object key = generalize(e.getKey(), keyType);
200         out.cr(indent).appendObject(key, false).append('=');
201         serializeAnything(out, value, valueType, toString(key), null);
202         if (mapEntries.hasNext())
203            out.append(',');
204      }
205
206      if (m.size() > 0)
207         out.cre(indent-1);
208
209      if (! plainTextParams)
210         out.append(')');
211
212      return out;
213   }
214
215   private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, String typeName) throws Exception {
216
217      if (! plainTextParams)
218         out.append('(');
219
220      boolean addComma = false;
221
222      for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
223         BeanPropertyMeta pMeta = p.getMeta();
224         if (pMeta.canRead()) {
225            ClassMeta<?> cMeta = p.getClassMeta();
226
227            String key = p.getName();
228            Object value = p.getValue();
229            Throwable t = p.getThrown();
230            if (t != null)
231               onBeanGetterException(pMeta, t);
232
233            if (canIgnoreValue(cMeta, key, value))
234               continue;
235
236            if (addComma)
237               out.append(',');
238
239            out.cr(indent).appendObject(key, false).append('=');
240
241            serializeAnything(out, value, cMeta, key, pMeta);
242
243            addComma = true;
244         }
245      }
246
247      if (m.size() > 0)
248         out.cre(indent-1);
249      if (! plainTextParams)
250         out.append(')');
251
252      return out;
253   }
254
255   @SuppressWarnings({ "rawtypes", "unchecked" })
256   private SerializerWriter serializeCollection(UonWriter out, Collection c, ClassMeta<?> type) throws Exception {
257
258      ClassMeta<?> elementType = type.getElementType();
259
260      c = sort(c);
261
262      if (! plainTextParams)
263         out.append('@').append('(');
264
265      for (Iterator i = c.iterator(); i.hasNext();) {
266         out.cr(indent);
267         serializeAnything(out, i.next(), elementType, "<iterator>", null);
268         if (i.hasNext())
269            out.append(',');
270      }
271
272      if (c.size() > 0)
273         out.cre(indent-1);
274      if (! plainTextParams)
275         out.append(')');
276
277      return out;
278   }
279}