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