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.serializer; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017import static org.apache.juneau.serializer.Serializer.*; 018 019import java.io.*; 020import java.lang.reflect.*; 021import java.text.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.internal.*; 026import org.apache.juneau.parser.*; 027import org.apache.juneau.soap.*; 028import org.apache.juneau.transform.*; 029 030/** 031 * Serializer session that lives for the duration of a single use of {@link Serializer}. 032 * 033 * <p> 034 * Used by serializers for the following purposes: 035 * <ul class='spaced-list'> 036 * <li> 037 * Keeping track of how deep it is in a model for indentation purposes. 038 * <li> 039 * Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model. 040 * <li> 041 * Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled. 042 * <li> 043 * Allowing serializer properties to be overridden on method calls. 044 * </ul> 045 * 046 * <p> 047 * This class is NOT thread safe. 048 * It is typically discarded after one-time use although it can be reused within the same thread. 049 */ 050public abstract class SerializerSession extends BeanSession { 051 052 private final int maxDepth, initialDepth, maxIndent; 053 private final boolean 054 detectRecursions, 055 ignoreRecursions, 056 useWhitespace, 057 addBeanTypeProperties, 058 trimNulls, 059 trimEmptyCollections, 060 trimEmptyMaps, 061 trimStrings, 062 sortCollections, 063 sortMaps, 064 abridged; 065 private final char quoteChar; 066 private final UriResolver uriResolver; 067 068 private final Map<Object,Object> set; // Contains the current objects in the current branch of the model. 069 private final LinkedList<StackElement> stack = new LinkedList<>(); // Contains the current objects in the current branch of the model. 070 private final Method javaMethod; // Java method that invoked this serializer. 071 072 // Writable properties 073 private boolean isBottom; // If 'true', then we're at a leaf in the model (i.e. a String, Number, Boolean, or null). 074 private BeanPropertyMeta currentProperty; 075 private ClassMeta<?> currentClass; 076 private final SerializerListener listener; 077 078 /** The current indentation depth into the model. */ 079 public int indent; 080 081 082 /** 083 * Create a new session using properties specified in the context. 084 * 085 * @param ctx 086 * The context creating this session object. 087 * The context contains all the configuration settings for this object. 088 * Can be <jk>null</jk>. 089 * @param args 090 * Runtime arguments. 091 * These specify session-level information such as locale and URI context. 092 * It also include session-level properties that override the properties defined on the bean and 093 * serializer contexts. 094 */ 095 protected SerializerSession(Serializer ctx, SerializerSessionArgs args) { 096 super(ctx, args); 097 this.javaMethod = args.javaMethod; 098 UriResolution uriResolution; 099 UriRelativity uriRelativity; 100 Class<?> listenerClass; 101 102 maxDepth = getProperty(SERIALIZER_maxDepth, int.class, ctx.maxDepth); 103 initialDepth = getProperty(SERIALIZER_initialDepth, int.class, ctx.initialDepth); 104 detectRecursions = getProperty(SERIALIZER_detectRecursions, boolean.class, ctx.detectRecursions); 105 ignoreRecursions = getProperty(SERIALIZER_ignoreRecursions, boolean.class, ctx.ignoreRecursions); 106 useWhitespace = getProperty(SERIALIZER_useWhitespace, boolean.class, ctx.useWhitespace); 107 maxIndent = getProperty(SERIALIZER_maxIndent, int.class, ctx.maxIndent); 108 addBeanTypeProperties = getProperty(SERIALIZER_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties); 109 trimNulls = getProperty(SERIALIZER_trimNullProperties, boolean.class, ctx.trimNulls); 110 trimEmptyCollections = getProperty(SERIALIZER_trimEmptyCollections, boolean.class, ctx.trimEmptyCollections); 111 trimEmptyMaps = getProperty(SERIALIZER_trimEmptyMaps, boolean.class, ctx.trimEmptyMaps); 112 trimStrings = getProperty(SERIALIZER_trimStrings, boolean.class, ctx.trimStrings); 113 quoteChar = getProperty(SERIALIZER_quoteChar, String.class, ""+ctx.quoteChar).charAt(0); 114 sortCollections = getProperty(SERIALIZER_sortCollections, boolean.class, ctx.sortMaps); 115 sortMaps = getProperty(SERIALIZER_sortMaps, boolean.class, ctx.sortMaps); 116 abridged = getProperty(SERIALIZER_abridged, boolean.class, ctx.abridged); 117 uriResolution = getInstanceProperty(SERIALIZER_uriResolution, UriResolution.class, ctx.uriResolution); 118 uriRelativity = getInstanceProperty(SERIALIZER_uriRelativity, UriRelativity.class, ctx.uriRelativity); 119 listenerClass = getProperty(SERIALIZER_listener, Class.class, ctx.listener); 120 121 uriResolver = new UriResolver(uriResolution, uriRelativity, args.uriContext == null ? ctx.uriContext : args.uriContext); 122 123 listener = newInstance(SerializerListener.class, listenerClass); 124 125 this.indent = initialDepth; 126 if (detectRecursions || isDebug()) { 127 set = new IdentityHashMap<>(); 128 } else { 129 set = Collections.emptyMap(); 130 } 131 } 132 133 /** 134 * Default constructor. 135 * 136 * @param args 137 * Runtime arguments. 138 * These specify session-level information such as locale and URI context. 139 * It also include session-level properties that override the properties defined on the bean and 140 * serializer contexts. 141 */ 142 protected SerializerSession(SerializerSessionArgs args) { 143 this(Serializer.DEFAULT, args); 144 } 145 146 @Override /* Session */ 147 public ObjectMap asMap() { 148 return super.asMap() 149 .append("SerializerSession", new ObjectMap() 150 .append("maxDepth", maxDepth) 151 .append("initialDepth", initialDepth) 152 .append("maxIndent", maxIndent) 153 .append("detectRecursions", detectRecursions) 154 .append("ignoreRecursions", ignoreRecursions) 155 .append("useWhitespace", useWhitespace) 156 .append("addBeanTypeProperties", addBeanTypeProperties) 157 .append("trimNulls", trimNulls) 158 .append("trimEmptyCollections", trimEmptyCollections) 159 .append("trimEmptyMaps", trimEmptyMaps) 160 .append("trimStrings", trimStrings) 161 .append("sortCollections", sortCollections) 162 .append("sortMaps", sortMaps) 163 .append("abridged", abridged) 164 .append("quoteChar", quoteChar) 165 .append("uriResolver", uriResolver) 166 ); 167 } 168 169 /** 170 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into 171 * a stream or reader. 172 * 173 * @param output 174 * The output location. 175 * <br>For character-based serializers, this can be any of the following types: 176 * <ul> 177 * <li>{@link Writer} 178 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 179 * <li>{@link File} - Output will be written as system-default encoded stream. 180 * <li>{@link StringBuilder} 181 * </ul> 182 * <br>For byte-based serializers, this can be any of the following types: 183 * <ul> 184 * <li>{@link OutputStream} 185 * <li>{@link File} 186 * </ul> 187 * @return 188 * A new {@link ParserPipe} wrapper around the specified input object. 189 */ 190 protected SerializerPipe createPipe(Object output) { 191 return new SerializerPipe(output); 192 } 193 194 195 //-------------------------------------------------------------------------------- 196 // Abstract methods 197 //-------------------------------------------------------------------------------- 198 199 /** 200 * Serializes a POJO to the specified output stream or writer. 201 * 202 * <p> 203 * This method should NOT close the context object. 204 * 205 * @param pipe Where to send the output from the serializer. 206 * @param o The object to serialize. 207 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 208 */ 209 protected abstract void doSerialize(SerializerPipe pipe, Object o) throws Exception; 210 211 /** 212 * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> 213 * depending on the serializer type. 214 * 215 * @param o The object to serialize. 216 * @return 217 * The serialized object. 218 * <br>Character-based serializers will return a <code>String</code> 219 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> 220 * @throws SerializeException If a problem occurred trying to convert the output. 221 */ 222 public abstract Object serialize(Object o) throws SerializeException; 223 224 /** 225 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 226 * 227 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 228 */ 229 public abstract boolean isWriterSerializer(); 230 231 232 //-------------------------------------------------------------------------------- 233 // Other methods 234 //-------------------------------------------------------------------------------- 235 236 /** 237 * Serialize the specified object using the specified session. 238 * 239 * @param out Where to send the output from the serializer. 240 * @param o The object to serialize. 241 * @throws SerializeException If a problem occurred trying to convert the output. 242 */ 243 public final void serialize(Object o, Object out) throws SerializeException { 244 try (SerializerPipe pipe = createPipe(out)) { 245 doSerialize(pipe, o); 246 } catch (SerializeException e) { 247 throw e; 248 } catch (StackOverflowError e) { 249 throw new SerializeException(this, 250 "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the Serializer.SERIALIZER_detectRecursions setting to help locate the loop.").initCause(e); 251 } catch (Exception e) { 252 throw new SerializeException(this, e); 253 } finally { 254 checkForWarnings(); 255 } 256 } 257 258 /** 259 * Sets the current bean property being serialized for proper error messages. 260 * 261 * @param currentProperty The current property being serialized. 262 */ 263 protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { 264 this.currentProperty = currentProperty; 265 } 266 267 /** 268 * Sets the current class being serialized for proper error messages. 269 * 270 * @param currentClass The current class being serialized. 271 */ 272 protected final void setCurrentClass(ClassMeta<?> currentClass) { 273 this.currentClass = currentClass; 274 } 275 276 /** 277 * Returns the Java method that invoked this serializer. 278 * 279 * <p> 280 * When using the REST API, this is the Java method invoked by the REST call. 281 * Can be used to access annotations defined on the method or class. 282 * 283 * @return The Java method that invoked this serializer. 284 */ 285 protected final Method getJavaMethod() { 286 return javaMethod; 287 } 288 289 /** 290 * Returns the URI resolver. 291 * 292 * @return The URI resolver. 293 */ 294 protected final UriResolver getUriResolver() { 295 return uriResolver; 296 } 297 298 /** 299 * Returns the {@link Serializer#SERIALIZER_maxDepth} setting value for this session. 300 * 301 * @return The {@link Serializer#SERIALIZER_maxDepth} setting value for this session. 302 */ 303 protected final int getMaxDepth() { 304 return maxDepth; 305 } 306 307 /** 308 * Returns the {@link Serializer#SERIALIZER_initialDepth} setting value for this session. 309 * 310 * @return The {@link Serializer#SERIALIZER_initialDepth} setting value for this session. 311 */ 312 protected final int getInitialDepth() { 313 return initialDepth; 314 } 315 316 /** 317 * Returns the {@link Serializer#SERIALIZER_detectRecursions} setting value for this session. 318 * 319 * @return The {@link Serializer#SERIALIZER_detectRecursions} setting value for this session. 320 */ 321 protected final boolean isDetectRecursions() { 322 return detectRecursions; 323 } 324 325 /** 326 * Returns the {@link Serializer#SERIALIZER_ignoreRecursions} setting value for this session. 327 * 328 * @return The {@link Serializer#SERIALIZER_ignoreRecursions} setting value for this session. 329 */ 330 protected final boolean isIgnoreRecursions() { 331 return ignoreRecursions; 332 } 333 334 /** 335 * Returns the {@link Serializer#SERIALIZER_useWhitespace} setting value for this session. 336 * 337 * @return The {@link Serializer#SERIALIZER_useWhitespace} setting value for this session. 338 */ 339 protected boolean isUseWhitespace() { 340 return useWhitespace; 341 } 342 343 /** 344 * Returns the {@link Serializer#SERIALIZER_maxIndent} setting value for this session. 345 * 346 * @return The {@link Serializer#SERIALIZER_maxIndent} setting value for this session. 347 */ 348 protected int getMaxIndent() { 349 return maxIndent; 350 } 351 352 /** 353 * Returns the {@link Serializer#SERIALIZER_addBeanTypeProperties} setting value for this session. 354 * 355 * @return The {@link Serializer#SERIALIZER_addBeanTypeProperties} setting value for this session. 356 */ 357 protected boolean isAddBeanTypeProperties() { 358 return addBeanTypeProperties; 359 } 360 361 /** 362 * Returns the {@link Serializer#SERIALIZER_quoteChar} setting value for this session. 363 * 364 * @return The {@link Serializer#SERIALIZER_quoteChar} setting value for this session. 365 */ 366 protected char getQuoteChar() { 367 return quoteChar; 368 } 369 370 /** 371 * Returns the {@link Serializer#SERIALIZER_trimNullProperties} setting value for this session. 372 * 373 * @return The {@link Serializer#SERIALIZER_trimNullProperties} setting value for this session. 374 */ 375 protected final boolean isTrimNulls() { 376 return trimNulls; 377 } 378 379 /** 380 * Returns the {@link Serializer#SERIALIZER_trimEmptyCollections} setting value for this session. 381 * 382 * @return The {@link Serializer#SERIALIZER_trimEmptyCollections} setting value for this session. 383 */ 384 protected final boolean isTrimEmptyCollections() { 385 return trimEmptyCollections; 386 } 387 388 /** 389 * Returns the {@link Serializer#SERIALIZER_trimEmptyMaps} setting value for this session. 390 * 391 * @return The {@link Serializer#SERIALIZER_trimEmptyMaps} setting value for this session. 392 */ 393 protected final boolean isTrimEmptyMaps() { 394 return trimEmptyMaps; 395 } 396 397 /** 398 * Returns the {@link Serializer#SERIALIZER_trimStrings} setting value for this session. 399 * 400 * @return The {@link Serializer#SERIALIZER_trimStrings} setting value for this session. 401 */ 402 protected boolean isTrimStrings() { 403 return trimStrings; 404 } 405 406 /** 407 * Returns the {@link Serializer#SERIALIZER_sortCollections} setting value for this session. 408 * 409 * @return The {@link Serializer#SERIALIZER_sortCollections} setting value for this session. 410 */ 411 protected final boolean isSortCollections() { 412 return sortCollections; 413 } 414 415 /** 416 * Returns the {@link Serializer#SERIALIZER_sortMaps} setting value for this session. 417 * 418 * @return The {@link Serializer#SERIALIZER_sortMaps} setting value for this session. 419 */ 420 protected final boolean isSortMaps() { 421 return sortMaps; 422 } 423 424 /** 425 * Push the specified object onto the stack. 426 * 427 * @param attrName The attribute name. 428 * @param o The current object being serialized. 429 * @param eType The expected class type. 430 * @return 431 * The {@link ClassMeta} of the object so that <code>instanceof</code> operations only need to be performed 432 * once (since they can be expensive). 433 * @throws SerializeException If recursion occurred. 434 */ 435 protected final ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { 436 indent++; 437 isBottom = true; 438 if (o == null) 439 return null; 440 Class<?> c = o.getClass(); 441 ClassMeta<?> cm = (eType != null && c == eType.getInnerClass()) ? eType : getClassMeta(c); 442 if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean()) 443 return cm; 444 if (detectRecursions || isDebug()) { 445 if (stack.size() > maxDepth) 446 return null; 447 if (willRecurse(attrName, o, cm)) 448 return null; 449 isBottom = false; 450 stack.add(new StackElement(stack.size(), attrName, o, cm)); 451 if (isDebug()) 452 getLogger().info(getStack(false)); 453 set.put(o, o); 454 } 455 return cm; 456 } 457 458 /** 459 * Returns <jk>true</jk> if {@link Serializer#SERIALIZER_detectRecursions} is enabled, and the specified 460 * object is already higher up in the serialization chain. 461 * 462 * @param attrName The bean property attribute name, or some other identifier. 463 * @param o The object to check for recursion. 464 * @param cm The metadata on the object class. 465 * @return <jk>true</jk> if recursion detected. 466 * @throws SerializeException If recursion occurred. 467 */ 468 protected final boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws SerializeException { 469 if (! (detectRecursions || isDebug())) 470 return false; 471 if (! set.containsKey(o)) 472 return false; 473 if (ignoreRecursions && ! isDebug()) 474 return true; 475 476 stack.add(new StackElement(stack.size(), attrName, o, cm)); 477 throw new SerializeException("Recursion occurred, stack={0}", getStack(true)); 478 } 479 480 /** 481 * Pop an object off the stack. 482 */ 483 protected final void pop() { 484 indent--; 485 if ((detectRecursions || isDebug()) && ! isBottom) { 486 Object o = stack.removeLast().o; 487 Object o2 = set.remove(o); 488 if (o2 == null) 489 onError(null, "Couldn't remove object of type ''{0}'' on attribute ''{1}'' from object stack.", 490 o.getClass().getName(), stack); 491 } 492 isBottom = false; 493 } 494 495 /** 496 * Specialized warning when an exception is thrown while executing a bean getter. 497 * 498 * @param p The bean map entry representing the bean property. 499 * @param t The throwable that the bean getter threw. 500 */ 501 protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) { 502 if (listener != null) 503 listener.onBeanGetterException(this, t, p); 504 String prefix = (isDebug() ? getStack(false) + ": " : ""); 505 addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, 506 p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage()); 507 } 508 509 /** 510 * Logs a warning message. 511 * 512 * @param t The throwable that was thrown (if there was one). 513 * @param msg The warning message. 514 * @param args Optional {@link MessageFormat}-style arguments. 515 */ 516 protected final void onError(Throwable t, String msg, Object... args) { 517 if (listener != null) 518 listener.onError(this, t, format(msg, args)); 519 super.addWarning(msg, args); 520 } 521 522 /** 523 * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>. 524 * 525 * @param o The input string to trim. 526 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 527 */ 528 protected final String trim(Object o) { 529 if (o == null) 530 return null; 531 String s = o.toString(); 532 if (trimStrings) 533 s = s.trim(); 534 return s; 535 } 536 537 /** 538 * Generalize the specified object if a POJO swap is associated with it. 539 * 540 * @param o The object to generalize. 541 * @param type The type of object. 542 * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>. 543 * @throws SerializeException If a problem occurred trying to convert the output. 544 */ 545 @SuppressWarnings({ "rawtypes", "unchecked" }) 546 protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { 547 try { 548 if (o == null) 549 return null; 550 PojoSwap f = (type == null || type.isObject() ? getClassMeta(o.getClass()).getPojoSwap(this) : type.getPojoSwap(this)); 551 if (f == null) 552 return o; 553 return f.swap(this, o); 554 } catch (SerializeException e) { 555 throw e; 556 } catch (Exception e) { 557 throw new SerializeException(e); 558 } 559 } 560 561 /** 562 * Returns <jk>true</jk> if the specified value should not be serialized. 563 * 564 * @param cm The class type of the object being serialized. 565 * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute. 566 * @param value The object being serialized. 567 * @return <jk>true</jk> if the specified value should not be serialized. 568 * @throws SerializeException If recursion occurred. 569 */ 570 protected final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { 571 572 if (trimNulls && value == null) 573 return true; 574 575 if (value == null) 576 return false; 577 578 if (cm == null) 579 cm = object(); 580 581 if (trimEmptyCollections) { 582 if (cm.isArray() || (cm.isObject() && value.getClass().isArray())) { 583 if (((Object[])value).length == 0) 584 return true; 585 } 586 if (cm.isCollection() || (cm.isObject() && isParentClass(Collection.class, value.getClass()))) { 587 if (((Collection<?>)value).isEmpty()) 588 return true; 589 } 590 } 591 592 if (trimEmptyMaps) { 593 if (cm.isMap() || (cm.isObject() && isParentClass(Map.class, value.getClass()))) { 594 if (((Map<?,?>)value).isEmpty()) 595 return true; 596 } 597 } 598 599 if (trimNulls && willRecurse(attrName, value, cm)) 600 return true; 601 602 return false; 603 } 604 605 /** 606 * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>. 607 * 608 * @param m The map being sorted. 609 * @return A new sorted {@link TreeMap}. 610 */ 611 protected final <K,V> Map<K,V> sort(Map<K,V> m) { 612 if (sortMaps && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable<?>) 613 return new TreeMap<>(m); 614 return m; 615 } 616 617 /** 618 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 619 * 620 * @param c The collection being sorted. 621 * @return A new sorted {@link TreeSet}. 622 */ 623 protected final <E> Collection<E> sort(Collection<E> c) { 624 if (sortCollections && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable<?>) 625 return new TreeSet<>(c); 626 return c; 627 } 628 629 /** 630 * Converts the contents of the specified object array to a list. 631 * 632 * <p> 633 * Works on both object and primitive arrays. 634 * 635 * <p> 636 * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. 637 * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type 638 * <code><jk>int</jk>[]</code>. 639 * 640 * @param type The type of array. 641 * @param array The array being converted. 642 * @return The array as a list. 643 */ 644 protected static final List<Object> toList(Class<?> type, Object array) { 645 Class<?> componentType = type.getComponentType(); 646 if (componentType.isPrimitive()) { 647 int l = Array.getLength(array); 648 List<Object> list = new ArrayList<>(l); 649 for (int i = 0; i < l; i++) 650 list.add(Array.get(array, i)); 651 return list; 652 } 653 return Arrays.asList((Object[])array); 654 } 655 656 /** 657 * Converts a String to an absolute URI based on the {@link UriContext} on this session. 658 * 659 * @param uri 660 * The input URI. 661 * Can be any of the following: 662 * <ul> 663 * <li>{@link java.net.URI} 664 * <li>{@link java.net.URL} 665 * <li>{@link CharSequence} 666 * </ul> 667 * URI can be any of the following forms: 668 * <ul> 669 * <li><js>"foo://foo"</js> - Absolute URI. 670 * <li><js>"/foo"</js> - Root-relative URI. 671 * <li><js>"/"</js> - Root URI. 672 * <li><js>"context:/foo"</js> - Context-root-relative URI. 673 * <li><js>"context:/"</js> - Context-root URI. 674 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 675 * <li><js>"servlet:/"</js> - Servlet-path URI. 676 * <li><js>"request:/foo"</js> - Request-path-relative URI. 677 * <li><js>"request:/"</js> - Request-path URI. 678 * <li><js>"foo"</js> - Path-info-relative URI. 679 * <li><js>""</js> - Path-info URI. 680 * </ul> 681 * @return The resolved URI. 682 */ 683 public final String resolveUri(Object uri) { 684 return uriResolver.resolve(uri); 685 } 686 687 /** 688 * Opposite of {@link #resolveUri(Object)}. 689 * 690 * <p> 691 * Converts the URI to a value relative to the specified <code>relativeTo</code> parameter. 692 * 693 * <p> 694 * Both parameters can be any of the following: 695 * <ul> 696 * <li>{@link java.net.URI} 697 * <li>{@link java.net.URL} 698 * <li>{@link CharSequence} 699 * </ul> 700 * 701 * <p> 702 * Both URIs can be any of the following forms: 703 * <ul> 704 * <li><js>"foo://foo"</js> - Absolute URI. 705 * <li><js>"/foo"</js> - Root-relative URI. 706 * <li><js>"/"</js> - Root URI. 707 * <li><js>"context:/foo"</js> - Context-root-relative URI. 708 * <li><js>"context:/"</js> - Context-root URI. 709 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 710 * <li><js>"servlet:/"</js> - Servlet-path URI. 711 * <li><js>"request:/foo"</js> - Request-path-relative URI. 712 * <li><js>"request:/"</js> - Request-path URI. 713 * <li><js>"foo"</js> - Path-info-relative URI. 714 * <li><js>""</js> - Path-info URI. 715 * </ul> 716 * 717 * @param relativeTo The URI to relativize against. 718 * @param uri The URI to relativize. 719 * @return The relativized URI. 720 */ 721 protected final String relativizeUri(Object relativeTo, Object uri) { 722 return uriResolver.relativize(relativeTo, uri); 723 } 724 725 /** 726 * Converts the specified object to a <code>String</code>. 727 * 728 * <p> 729 * Also has the following effects: 730 * <ul> 731 * <li><code>Class</code> object is converted to a readable name. See {@link ClassUtils#getReadableClassName(Class)}. 732 * <li>Whitespace is trimmed if the trim-strings setting is enabled. 733 * </ul> 734 * 735 * @param o The object to convert to a <code>String</code>. 736 * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>. 737 */ 738 public final String toString(Object o) { 739 if (o == null) 740 return null; 741 if (o.getClass() == Class.class) 742 return getReadableClassName((Class<?>)o); 743 String s = o.toString(); 744 if (trimStrings) 745 s = s.trim(); 746 return s; 747 } 748 749 private static final class StackElement { 750 final int depth; 751 final String name; 752 final Object o; 753 final ClassMeta<?> aType; 754 755 StackElement(int depth, String name, Object o, ClassMeta<?> aType) { 756 this.depth = depth; 757 this.name = name; 758 this.o = o; 759 this.aType = aType; 760 } 761 762 String toString(boolean simple) { 763 StringBuilder sb = new StringBuilder().append('[').append(depth).append(']'); 764 sb.append(isEmpty(name) ? "<noname>" : name).append(':'); 765 sb.append(aType.toString(simple)); 766 if (aType != aType.getSerializedClassMeta(null)) 767 sb.append('/').append(aType.getSerializedClassMeta(null).toString(simple)); 768 return sb.toString(); 769 } 770 } 771 772 private String getStack(boolean full) { 773 StringBuilder sb = new StringBuilder(); 774 for (StackElement e : stack) { 775 if (full) { 776 sb.append("\n\t"); 777 for (int i = 1; i < e.depth; i++) 778 sb.append(" "); 779 if (e.depth > 0) 780 sb.append("->"); 781 sb.append(e.toString(false)); 782 } else { 783 sb.append(" > ").append(e.toString(true)); 784 } 785 } 786 return sb.toString(); 787 } 788 789 /** 790 * Returns information used to determine at what location in the parse a failure occurred. 791 * 792 * @return A map, typically containing something like <code>{line:123,column:456,currentProperty:"foobar"}</code> 793 */ 794 protected final ObjectMap getLastLocation() { 795 ObjectMap m = new ObjectMap(); 796 if (currentClass != null) 797 m.put("currentClass", currentClass); 798 if (currentProperty != null) 799 m.put("currentProperty", currentProperty); 800 if (stack != null && ! stack.isEmpty()) 801 m.put("stack", stack); 802 return m; 803 } 804 805 /** 806 * Create a "_type" property that contains the dictionary name of the bean. 807 * 808 * @param m The bean map to create a class property on. 809 * @param typeName The type name of the bean. 810 * @return A new bean property value. 811 */ 812 protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { 813 BeanMeta<?> bm = m.getMeta(); 814 return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null); 815 } 816 817 /** 818 * Resolves the dictionary name for the actual type. 819 * 820 * @param eType The expected type of the bean property. 821 * @param aType The actual type of the bean property. 822 * @param pMeta The current bean property being serialized. 823 * @return The bean dictionary name, or <jk>null</jk> if a name could not be found. 824 */ 825 protected final String getBeanTypeName(ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { 826 if (eType == aType) 827 return null; 828 829 if (! isAddBeanTypeProperties()) 830 return null; 831 832 String eTypeTn = eType.getDictionaryName(); 833 834 // First see if it's defined on the actual type. 835 String tn = aType.getDictionaryName(); 836 if (tn != null && ! tn.equals(eTypeTn)) { 837 return tn; 838 } 839 840 // Then see if it's defined on the expected type. 841 // The expected type might be an interface with mappings for implementation classes. 842 BeanRegistry br = eType.getBeanRegistry(); 843 if (br != null) { 844 tn = br.getTypeName(aType); 845 if (tn != null && ! tn.equals(eTypeTn)) 846 return tn; 847 } 848 849 // Then look on the bean property. 850 br = pMeta == null ? null : pMeta.getBeanRegistry(); 851 if (br != null) { 852 tn = br.getTypeName(aType); 853 if (tn != null && ! tn.equals(eTypeTn)) 854 return tn; 855 } 856 857 // Finally look in the session. 858 br = getBeanRegistry(); 859 if (br != null) { 860 tn = br.getTypeName(aType); 861 if (tn != null && ! tn.equals(eTypeTn)) 862 return tn; 863 } 864 865 return null; 866 } 867 868 /** 869 * Returns the parser-side expected type for the object. 870 * 871 * <p> 872 * The return value depends on the {@link Serializer#SERIALIZER_abridged} setting. 873 * When enabled, the parser already knows the Java POJO type being parsed, so there is 874 * no reason to add <js>"_type"</js> attributes to the root-level object. 875 * 876 * @param o The object to get the expected type on. 877 * @return The expected type. 878 */ 879 protected final ClassMeta<?> getExpectedRootType(Object o) { 880 return abridged ? getClassMetaForObject(o) : object(); 881 } 882 883 /** 884 * Optional method that specifies HTTP request headers for this serializer. 885 * 886 * <p> 887 * For example, {@link SoapXmlSerializer} needs to set a <code>SOAPAction</code> header. 888 * 889 * <p> 890 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 891 * or client). 892 * 893 * @return 894 * The HTTP headers to set on HTTP requests. 895 * Never <jk>null</jk>. 896 */ 897 public Map<String,String> getResponseHeaders() { 898 return Collections.emptyMap(); 899 } 900 901 902 /** 903 * Returns the listener associated with this session. 904 * 905 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 906 */ 907 public SerializerListener getListener() { 908 return listener; 909 } 910 911 /** 912 * Returns the listener associated with this session. 913 * 914 * @param c The listener class to cast to. 915 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 916 */ 917 @SuppressWarnings("unchecked") 918 public <T extends SerializerListener> T getListener(Class<T> c) { 919 return (T)listener; 920 } 921}