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