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.common.internal.IOUtils.*; 016import static org.apache.juneau.common.internal.ThrowableUtils.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.nio.charset.*; 021import java.util.*; 022import java.util.function.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.httppart.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.reflect.*; 028import org.apache.juneau.serializer.*; 029import org.apache.juneau.svl.*; 030import org.apache.juneau.swap.*; 031 032/** 033 * Session object that lives for the duration of a single use of {@link UonSerializer}. 034 * 035 * <h5 class='section'>Notes:</h5><ul> 036 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 037 * </ul> 038 * 039 * <h5 class='section'>See Also:</h5><ul> 040 * <li class='link'><a class="doclink" href="../../../../index.html#jm.UonDetails">UON Details</a> 041 042 * </ul> 043 */ 044public class UonSerializerSession extends WriterSerializerSession implements HttpPartSerializerSession { 045 046 //----------------------------------------------------------------------------------------------------------------- 047 // Static 048 //----------------------------------------------------------------------------------------------------------------- 049 050 /** 051 * Creates a new builder for this object. 052 * 053 * @param ctx The context creating this session. 054 * @return A new builder. 055 */ 056 public static Builder create(UonSerializer ctx) { 057 return new Builder(ctx); 058 } 059 060 //----------------------------------------------------------------------------------------------------------------- 061 // Builder 062 //----------------------------------------------------------------------------------------------------------------- 063 064 /** 065 * Builder class. 066 */ 067 @FluentSetters 068 public static class Builder extends WriterSerializerSession.Builder { 069 070 UonSerializer ctx; 071 boolean encoding; 072 073 /** 074 * Constructor 075 * 076 * @param ctx The context creating this session. 077 */ 078 protected Builder(UonSerializer ctx) { 079 super(ctx); 080 this.ctx = ctx; 081 encoding = ctx.encoding; 082 } 083 084 @Override 085 public UonSerializerSession build() { 086 return new UonSerializerSession(this); 087 } 088 089 /** 090 * Overrides the encoding setting for this session. 091 * 092 * @param value The new value for this setting. 093 * @return This object. 094 */ 095 @FluentSetter 096 public Builder encoding(boolean value) { 097 encoding = value; 098 return this; 099 } 100 101 // <FluentSetters> 102 103 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 104 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 105 super.apply(type, apply); 106 return this; 107 } 108 109 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 110 public Builder debug(Boolean value) { 111 super.debug(value); 112 return this; 113 } 114 115 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 116 public Builder properties(Map<String,Object> value) { 117 super.properties(value); 118 return this; 119 } 120 121 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 122 public Builder property(String key, Object value) { 123 super.property(key, value); 124 return this; 125 } 126 127 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 128 public Builder unmodifiable() { 129 super.unmodifiable(); 130 return this; 131 } 132 133 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 134 public Builder locale(Locale value) { 135 super.locale(value); 136 return this; 137 } 138 139 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 140 public Builder localeDefault(Locale value) { 141 super.localeDefault(value); 142 return this; 143 } 144 145 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 146 public Builder mediaType(MediaType value) { 147 super.mediaType(value); 148 return this; 149 } 150 151 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 152 public Builder mediaTypeDefault(MediaType value) { 153 super.mediaTypeDefault(value); 154 return this; 155 } 156 157 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 158 public Builder timeZone(TimeZone value) { 159 super.timeZone(value); 160 return this; 161 } 162 163 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 164 public Builder timeZoneDefault(TimeZone value) { 165 super.timeZoneDefault(value); 166 return this; 167 } 168 169 @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */ 170 public Builder javaMethod(Method value) { 171 super.javaMethod(value); 172 return this; 173 } 174 175 @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */ 176 public Builder resolver(VarResolverSession value) { 177 super.resolver(value); 178 return this; 179 } 180 181 @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */ 182 public Builder schema(HttpPartSchema value) { 183 super.schema(value); 184 return this; 185 } 186 187 @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */ 188 public Builder schemaDefault(HttpPartSchema value) { 189 super.schemaDefault(value); 190 return this; 191 } 192 193 @Override /* GENERATED - org.apache.juneau.serializer.SerializerSession.Builder */ 194 public Builder uriContext(UriContext value) { 195 super.uriContext(value); 196 return this; 197 } 198 199 @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */ 200 public Builder fileCharset(Charset value) { 201 super.fileCharset(value); 202 return this; 203 } 204 205 @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */ 206 public Builder streamCharset(Charset value) { 207 super.streamCharset(value); 208 return this; 209 } 210 211 @Override /* GENERATED - org.apache.juneau.serializer.WriterSerializerSession.Builder */ 212 public Builder useWhitespace(Boolean value) { 213 super.useWhitespace(value); 214 return this; 215 } 216 217 // </FluentSetters> 218 } 219 220 //----------------------------------------------------------------------------------------------------------------- 221 // Instance 222 //----------------------------------------------------------------------------------------------------------------- 223 224 private final UonSerializer ctx; 225 private final boolean plainTextParams; 226 227 /** 228 * Constructor. 229 * 230 * @param builder The builder for this object. 231 */ 232 public UonSerializerSession(Builder builder) { 233 super(builder); 234 ctx = builder.ctx; 235 plainTextParams = ctx.getParamFormat() == ParamFormat.PLAINTEXT; 236 } 237 238 /** 239 * Converts the specified output target object to an {@link UonWriter}. 240 * 241 * @param out The output target object. 242 * @return The output target object wrapped in an {@link UonWriter}. 243 * @throws IOException Thrown by underlying stream. 244 */ 245 protected final UonWriter getUonWriter(SerializerPipe out) throws IOException { 246 Object output = out.getRawOutput(); 247 if (output instanceof UonWriter) 248 return (UonWriter)output; 249 UonWriter w = new UonWriter(this, out.getWriter(), isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getQuoteChar(), getUriResolver()); 250 out.setWriter(w); 251 return w; 252 } 253 254 private final UonWriter getUonWriter(Writer out) throws Exception { 255 return new UonWriter(this, out, isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getQuoteChar(), getUriResolver()); 256 } 257 258 @Override /* Serializer */ 259 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 260 serializeAnything(getUonWriter(out).i(getInitialDepth()), o, getExpectedRootType(o), "root", null); 261 } 262 263 /** 264 * Workhorse method. 265 * 266 * <p> 267 * Determines the type of object, and then calls the appropriate type-specific serialization method. 268 * 269 * @param out The writer to serialize to. 270 * @param o The object being serialized. 271 * @param eType The expected type of the object if this is a bean property. 272 * @param attrName 273 * The bean property name if this is a bean property. 274 * <jk>null</jk> if this isn't a bean property being serialized. 275 * @param pMeta The bean property metadata. 276 * @return The same writer passed in. 277 * @throws SerializeException Generic serialization error occurred. 278 */ 279 @SuppressWarnings({ "rawtypes" }) 280 protected SerializerWriter serializeAnything(UonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException { 281 282 if (o == null) { 283 out.appendObject(null, false); 284 return out; 285 } 286 287 if (eType == null) 288 eType = object(); 289 290 ClassMeta<?> aType; // The actual type 291 ClassMeta<?> sType; // The serialized type 292 293 aType = push2(attrName, o, eType); 294 boolean isRecursion = aType == null; 295 296 // Handle recursion 297 if (aType == null) { 298 o = null; 299 aType = object(); 300 } 301 302 // Handle Optional<X> 303 if (isOptional(aType)) { 304 o = getOptionalValue(o); 305 eType = getOptionalType(eType); 306 aType = getClassMetaForObject(o, object()); 307 } 308 309 sType = aType; 310 String typeName = getBeanTypeName(this, eType, aType, pMeta); 311 312 // Swap if necessary 313 ObjectSwap swap = aType.getSwap(this); 314 if (swap != null) { 315 o = swap(swap, o); 316 sType = swap.getSwapClassMeta(this); 317 318 // If the getSwapClass() method returns Object, we need to figure out 319 // the actual type now. 320 if (sType.isObject()) 321 sType = getClassMetaForObject(o); 322 } 323 324 // '\0' characters are considered null. 325 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) 326 out.appendObject(null, false); 327 else if (sType.isBoolean()) 328 out.appendBoolean(o); 329 else if (sType.isNumber()) 330 out.appendNumber(o); 331 else if (sType.isBean()) 332 serializeBeanMap(out, toBeanMap(o), typeName); 333 else if (sType.isUri() || (pMeta != null && pMeta.isUri())) 334 out.appendUri(o); 335 else if (sType.isMap()) { 336 if (o instanceof BeanMap) 337 serializeBeanMap(out, (BeanMap)o, typeName); 338 else 339 serializeMap(out, (Map)o, eType); 340 } 341 else if (sType.isCollection()) { 342 serializeCollection(out, (Collection) o, eType); 343 } 344 else if (sType.isArray()) { 345 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 346 } 347 else if (sType.isReader()) { 348 pipe((Reader)o, out, SerializerSession::handleThrown); 349 } 350 else if (sType.isInputStream()) { 351 pipe((InputStream)o, out, SerializerSession::handleThrown); 352 } 353 else { 354 out.appendObject(o, false); 355 } 356 357 if (! isRecursion) 358 pop(); 359 return out; 360 } 361 362 @SuppressWarnings({ "rawtypes", "unchecked" }) 363 private SerializerWriter serializeMap(UonWriter out, Map m, ClassMeta<?> type) throws SerializeException { 364 365 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 366 367 if (! plainTextParams) 368 out.append('('); 369 370 Flag addComma = Flag.create(); 371 forEachEntry(m, x -> { 372 addComma.ifSet(()->out.append(',')).set(); 373 Object value = x.getValue(); 374 Object key = generalize(x.getKey(), keyType); 375 out.cr(indent).appendObject(key, false).append('='); 376 serializeAnything(out, value, valueType, toString(key), null); 377 }); 378 379 addComma.ifSet(()->out.cre(indent-1)); 380 381 if (! plainTextParams) 382 out.append(')'); 383 384 return out; 385 } 386 387 private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, String typeName) throws SerializeException { 388 389 if (! plainTextParams) 390 out.append('('); 391 392 Flag addComma = Flag.create(); 393 394 if (typeName != null) { 395 BeanPropertyMeta pm = m.getMeta().getTypeProperty(); 396 out.cr(indent).appendObject(pm.getName(), false).append('=').appendObject(typeName, false); 397 addComma.set(); 398 } 399 400 Predicate<Object> checkNull = x -> isKeepNullProperties() || x != null; 401 m.forEachValue(checkNull, (pMeta,key,value,thrown) -> { 402 ClassMeta<?> cMeta = pMeta.getClassMeta(); 403 404 if (thrown != null) 405 onBeanGetterException(pMeta, thrown); 406 407 if (canIgnoreValue(cMeta, key, value)) 408 return; 409 410 addComma.ifSet(() -> out.append(',')).set(); 411 412 out.cr(indent).appendObject(key, false).append('='); 413 414 serializeAnything(out, value, cMeta, key, pMeta); 415 }); 416 417 if (m.size() > 0) 418 out.cre(indent-1); 419 if (! plainTextParams) 420 out.append(')'); 421 422 return out; 423 } 424 425 @SuppressWarnings({ "rawtypes", "unchecked" }) 426 private SerializerWriter serializeCollection(UonWriter out, Collection c, ClassMeta<?> type) throws SerializeException { 427 428 ClassMeta<?> elementType = type.getElementType(); 429 430 if (! plainTextParams) 431 out.append('@').append('('); 432 433 Flag addComma = Flag.create(); 434 forEachEntry(c, x -> { 435 addComma.ifSet(()->out.append(',')).set(); 436 out.cr(indent); 437 serializeAnything(out, x, elementType, "<iterator>", null); 438 }); 439 440 addComma.ifSet(()->out.cre(indent-1)); 441 if (! plainTextParams) 442 out.append(')'); 443 444 return out; 445 } 446 447 @Override /* HttpPartSerializer */ 448 public String serialize(HttpPartType type, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 449 try { 450 // Shortcut for simple types. 451 ClassMeta<?> cm = getClassMetaForObject(value); 452 if (cm != null && (schema == null || schema.getType() == HttpPartDataType.NO_TYPE)) { 453 if (cm.isNumber() || cm.isBoolean()) 454 return Mutaters.toString(value); 455 if (cm.isString()) { 456 String s = Mutaters.toString(value); 457 if (s.isEmpty() || ! UonUtils.needsQuotes(s)) 458 return s; 459 } 460 } 461 StringWriter w = new StringWriter(); 462 serializeAnything(getUonWriter(w).i(getInitialDepth()), value, getExpectedRootType(value), "root", null); 463 return w.toString(); 464 } catch (Exception e) { 465 throw asRuntimeException(e); 466 } 467 } 468 469 //----------------------------------------------------------------------------------------------------------------- 470 // Properties 471 //----------------------------------------------------------------------------------------------------------------- 472 473 /** 474 * Add <js>"_type"</js> properties when needed. 475 * 476 * @see UonSerializer.Builder#addBeanTypesUon() 477 * @return 478 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 479 * through reflection. 480 */ 481 @Override 482 protected final boolean isAddBeanTypes() { 483 return ctx.isAddBeanTypes(); 484 } 485 486 /** 487 * Encode non-valid URI characters. 488 * 489 * @see UonSerializer.Builder#encoding() 490 * @return 491 * <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs. 492 */ 493 protected final boolean isEncoding() { 494 return ctx.isEncoding(); 495 } 496 497 /** 498 * Format to use for query/form-data/header values. 499 * 500 * @see UonSerializer.Builder#paramFormat(ParamFormat) 501 * @return 502 * Specifies the format to use for URL GET parameter keys and values. 503 */ 504 protected final ParamFormat getParamFormat() { 505 return ctx.getParamFormat(); 506 } 507 508 /** 509 * Quote character. 510 * 511 * @see UonSerializer.Builder#quoteCharUon(char) 512 * @return 513 * The character used for quoting attributes and values. 514 */ 515 @Override 516 protected final char getQuoteChar() { 517 return ctx.getQuoteChar(); 518 } 519}