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}