View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.serializer;
18  
19  import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
20  import static org.apache.juneau.commons.utils.AssertionUtils.*;
21  import static org.apache.juneau.commons.utils.CollectionUtils.*;
22  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
23  import static org.apache.juneau.commons.utils.Utils.*;
24  
25  import java.io.*;
26  import java.lang.reflect.*;
27  import java.text.*;
28  import java.util.*;
29  import java.util.function.*;
30  import java.util.stream.*;
31  
32  import org.apache.juneau.*;
33  import org.apache.juneau.commons.collections.FluentMap;
34  import org.apache.juneau.commons.reflect.*;
35  import org.apache.juneau.cp.*;
36  import org.apache.juneau.httppart.*;
37  import org.apache.juneau.parser.*;
38  import org.apache.juneau.soap.*;
39  import org.apache.juneau.svl.*;
40  import org.apache.juneau.swap.*;
41  
42  /**
43   * Serializer session that lives for the duration of a single use of {@link Serializer}.
44   *
45   * <p>
46   * Used by serializers for the following purposes:
47   * <ul class='spaced-list'>
48   * 	<li>
49   * 		Keeping track of how deep it is in a model for indentation purposes.
50   * 	<li>
51   * 		Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
52   * 	<li>
53   * 		Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
54   * 	<li>
55   * 		Allowing serializer properties to be overridden on method calls.
56   * </ul>
57   *
58   * <h5 class='section'>Notes:</h5><ul>
59   * 	<li class='warn'>This class is not thread safe and is typically discarded after one use.
60   * </ul>
61   *
62   * <h5 class='section'>See Also:</h5><ul>
63   * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
64  
65   * </ul>
66   */
67  public class SerializerSession extends BeanTraverseSession {
68  	/**
69  	 * Builder class.
70  	 */
71  	public static class Builder extends BeanTraverseSession.Builder {
72  
73  		private HttpPartSchema schema;
74  		private Method javaMethod;
75  		private Serializer ctx;
76  		private UriContext uriContext;
77  		private VarResolverSession resolver;
78  
79  		/**
80  		 * Constructor
81  		 *
82  		 * @param ctx The context creating this session.
83  		 * 	<br>Cannot be <jk>null</jk>.
84  		 */
85  		protected Builder(Serializer ctx) {
86  			super(assertArgNotNull("ctx", ctx));
87  			this.ctx = ctx;
88  			mediaTypeDefault(ctx.getResponseContentType());
89  			uriContext = ctx.getUriContext();
90  		}
91  
92  		@Override /* Overridden from Builder */
93  		public <T> Builder apply(Class<T> type, Consumer<T> apply) {
94  			super.apply(type, apply);
95  			return this;
96  		}
97  
98  		@Override
99  		public SerializerSession build() {
100 			return new SerializerSession(this);
101 		}
102 
103 		@Override /* Overridden from Builder */
104 		public Builder debug(Boolean value) {
105 			super.debug(value);
106 			return this;
107 		}
108 
109 		/**
110 		 * The java method that called this serializer, usually the method in a REST servlet.
111 		 *
112 		 * @param value
113 		 * 	The new property value.
114 		 * 	<br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
115 		 * @return This object.
116 		 */
117 		public Builder javaMethod(Method value) {
118 			if (nn(value))
119 				javaMethod = value;
120 			return this;
121 		}
122 
123 		@Override /* Overridden from Builder */
124 		public Builder locale(Locale value) {
125 			super.locale(value);
126 			return this;
127 		}
128 
129 		@Override /* Overridden from Builder */
130 		public Builder mediaType(MediaType value) {
131 			super.mediaType(value);
132 			return this;
133 		}
134 
135 		@Override /* Overridden from Builder */
136 		public Builder mediaTypeDefault(MediaType value) {
137 			super.mediaTypeDefault(value);
138 			return this;
139 		}
140 
141 		@Override /* Overridden from Builder */
142 		public Builder properties(Map<String,Object> value) {
143 			super.properties(value);
144 			return this;
145 		}
146 
147 		@Override /* Overridden from Builder */
148 		public Builder property(String key, Object value) {
149 			super.property(key, value);
150 			return this;
151 		}
152 
153 		/**
154 		 * String variable resolver.
155 		 *
156 		 * <p>
157 		 * If not specified, defaults to session created by {@link VarResolver#DEFAULT}.
158 		 *
159 		 * @param value
160 		 * 	The new property value.
161 		 * 	<br>Can be <jk>null</jk> (value will not be set, defaults to session created by {@link VarResolver#DEFAULT} when accessed).
162 		 * @return This object.
163 		 */
164 		public Builder resolver(VarResolverSession value) {
165 			if (nn(value))
166 				resolver = value;
167 			return this;
168 		}
169 
170 		/**
171 		 * HTTP-part schema.
172 		 *
173 		 * <p>
174 		 * Used for schema-based serializers and parsers to define additional formatting.
175 		 *
176 		 * @param value
177 		 * 	The new value for this property.
178 		 * 	<br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
179 		 * @return This object.
180 		 */
181 		public Builder schema(HttpPartSchema value) {
182 			if (nn(value))
183 				schema = value;
184 			return this;
185 		}
186 
187 		/**
188 		 * Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set.
189 		 *
190 		 * @param value
191 		 * 	The new value for this property.
192 		 * 	<br>If <jk>null</jk>, then the locale defined on the context is used.
193 		 * @return This object.
194 		 */
195 		public Builder schemaDefault(HttpPartSchema value) {
196 			if (nn(value) && schema == null)
197 				schema = value;
198 			return this;
199 		}
200 
201 		@Override /* Overridden from Builder */
202 		public Builder timeZone(TimeZone value) {
203 			super.timeZone(value);
204 			return this;
205 		}
206 
207 		@Override /* Overridden from Builder */
208 		public Builder timeZoneDefault(TimeZone value) {
209 			super.timeZoneDefault(value);
210 			return this;
211 		}
212 
213 		@Override /* Overridden from Builder */
214 		public Builder unmodifiable() {
215 			super.unmodifiable();
216 			return this;
217 		}
218 
219 		/**
220 		 * URI context bean.
221 		 *
222 		 * <p>
223 		 * Bean used for resolution of URIs to absolute or root-relative form.
224 		 *
225 		 * <p>
226 		 * If not specified, defaults to {@link Serializer.Builder#uriContext(UriContext)}.
227 		 *
228 		 * @param value
229 		 * 	The new property value.
230 		 * 	<br>Can be <jk>null</jk> (value will not be set, defaults to {@link Serializer.Builder#uriContext(UriContext)} from context).
231 		 * @return This object.
232 		 */
233 		public Builder uriContext(UriContext value) {
234 			if (nn(value))
235 				uriContext = value;
236 			return this;
237 		}
238 	}
239 
240 	/**
241 	 * Creates a new builder for this object.
242 	 *
243 	 * @param ctx The context creating this session.
244 	 * 	<br>Cannot be <jk>null</jk>.
245 	 * @return A new builder.
246 	 */
247 	public static Builder create(Serializer ctx) {
248 		return new Builder(assertArgNotNull("ctx", ctx));
249 	}
250 
251 	/**
252 	 * Create a "_type" property that contains the dictionary name of the bean.
253 	 *
254 	 * @param m The bean map to create a class property on.
255 	 * @param typeName The type name of the bean.
256 	 * @return A new bean property value.
257 	 */
258 	protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) {
259 		BeanMeta<?> bm = m.getMeta();
260 		return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null);
261 	}
262 
263 	/**
264 	 * Converts the specified throwable to either a {@link RuntimeException} or {@link SerializeException}.
265 	 *
266 	 * @param <T> The throwable type.
267 	 * @param causedBy The exception to cast or wrap.
268 	 */
269 	protected static <T extends Throwable> void handleThrown(T causedBy) {
270 		if (causedBy instanceof Error)
271 			throw (Error)causedBy;
272 		if (causedBy instanceof RuntimeException)
273 			throw (RuntimeException)causedBy;
274 		if (causedBy instanceof StackOverflowError)
275 			throw new SerializeException(
276 				"Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
277 		if (causedBy instanceof SerializeException)
278 			throw (SerializeException)causedBy;
279 		throw new SerializeException(causedBy);
280 	}
281 
282 	/**
283 	 * Converts the contents of the specified object array to a list.
284 	 *
285 	 * <p>
286 	 * Works on both object and primitive arrays.
287 	 *
288 	 * <p>
289 	 * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension.
290 	 * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type
291 	 * <code><jk>int</jk>[]</code>.
292 	 *
293 	 * @param type The type of array.
294 	 * @param array The array being converted.
295 	 * @return The array as a list.
296 	 */
297 	protected static final List<Object> toList(Class<?> type, Object array) {
298 		var componentType = type.getComponentType();
299 		if (componentType.isPrimitive()) {
300 			var l = Array.getLength(array);
301 			var list = new ArrayList<>(l);
302 			for (var i = 0; i < l; i++)
303 				list.add(Array.get(array, i));
304 			return list;
305 		}
306 		return l((Object[])array);
307 	}
308 
309 	private final HttpPartSchema schema;
310 	private final Method javaMethod;                                                // Java method that invoked this serializer.
311 	private final Serializer ctx;
312 	private final SerializerListener listener;
313 	private final UriResolver uriResolver;
314 	private VarResolverSession vrs;
315 
316 	/**
317 	 * Constructor.
318 	 *
319 	 * @param builder The builder for this object.
320 	 */
321 	protected SerializerSession(Builder builder) {
322 		super(builder);
323 		ctx = builder.ctx;
324 		javaMethod = builder.javaMethod;
325 		schema = builder.schema;
326 		UriContext uriContext = builder.uriContext;
327 		uriResolver = UriResolver.of(ctx.getUriResolution(), ctx.getUriRelativity(), uriContext);
328 		vrs = builder.resolver;
329 		listener = BeanCreator.of(SerializerListener.class).type(ctx.getListener()).orElse(null);
330 	}
331 
332 	/**
333 	 * Adds a session object to the {@link VarResolverSession} in this session.
334 	 *
335 	 * @param <T> The bean type.
336 	 * @param c The bean type being added.
337 	 * @param value The bean being added.
338 	 * @return This object.
339 	 */
340 	public <T> SerializerSession addVarBean(Class<T> c, T value) {
341 		getVarResolver().bean(c, value);
342 		return this;
343 	}
344 
345 	/**
346 	 * Returns <jk>true</jk> if the specified value should not be serialized.
347 	 *
348 	 * @param cm The class type of the object being serialized.
349 	 * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute.
350 	 * @param value The object being serialized.
351 	 * @return <jk>true</jk> if the specified value should not be serialized.
352 	 * @throws SerializeException If recursion occurred.
353 	 */
354 	public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException {
355 
356 		if (value == null && ! isKeepNullProperties())
357 			return true;
358 
359 		if (value == null)
360 			return false;
361 
362 		if (cm == null)
363 			cm = object();
364 
365 		if (isTrimEmptyCollections()) {
366 			if (cm.isArray() || (cm.isObject() && isArray(value))) {
367 				if (((Object[])value).length == 0)
368 					return true;
369 			}
370 			if (cm.isCollection() || (cm.isObject() && info(value).isChildOf(Collection.class))) {
371 				if (((Collection<?>)value).isEmpty())
372 					return true;
373 			}
374 		}
375 
376 		if (isTrimEmptyMaps()) {
377 			if (cm.isMap() || (cm.isObject() && info(value).isChildOf(Map.class))) {
378 				if (((Map<?,?>)value).isEmpty())
379 					return true;
380 			}
381 		}
382 
383 		try {
384 			if ((! isKeepNullProperties()) && (willRecurse(attrName, value, cm) || willExceedDepth()))
385 				return true;
386 		} catch (BeanRecursionException e) {
387 			throw new SerializeException(e);
388 		}
389 
390 		return false;
391 	}
392 
393 	/**
394 	 * Consumes each entry in the list.
395 	 *
396 	 * @param <E> The element type.
397 	 * @param c The collection being sorted.
398 	 * @param consumer The entry consumer.
399 	 */
400 	public final <E> void forEachEntry(Collection<E> c, Consumer<E> consumer) {
401 		if (c == null || c.isEmpty())
402 			return;
403 		if (isSortCollections() && ! SortedSet.class.isInstance(c) && isSortable(c))
404 			c.stream().sorted().forEach(consumer);
405 		else
406 			c.forEach(consumer);
407 	}
408 
409 	/**
410 	 * Consumes each map entry in the map.
411 	 *
412 	 * @param <K> The key type.
413 	 * @param <V> The value type.
414 	 * @param m The map being consumed.
415 	 * @param consumer The map entry consumer.
416 	 */
417 	@SuppressWarnings({ "unchecked", "rawtypes", "cast" })
418 	public final <K,V> void forEachEntry(Map<K,V> m, Consumer<Map.Entry<K,V>> consumer) {
419 		if (m == null || m.isEmpty())
420 			return;
421 		if (isSortMaps() && ! SortedMap.class.isInstance(m) && isSortable(m.keySet()))
422 			((Map)m).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(x -> consumer.accept((Map.Entry<K,V>)x));
423 		else
424 			m.entrySet().forEach(consumer);
425 	}
426 
427 	/**
428 	 * Returns the listener associated with this session.
429 	 *
430 	 * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
431 	 */
432 	public SerializerListener getListener() { return listener; }
433 
434 	/**
435 	 * Returns the listener associated with this session.
436 	 *
437 	 * @param <T> The listener type.
438 	 * @param c The listener class to cast to.
439 	 * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
440 	 */
441 	@SuppressWarnings("unchecked")
442 	public <T extends SerializerListener> T getListener(Class<T> c) {
443 		return (T)listener;
444 	}
445 
446 	/**
447 	 * Optional method that specifies HTTP request headers for this serializer.
448 	 *
449 	 * <p>
450 	 * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header.
451 	 *
452 	 * <p>
453 	 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
454 	 * or client).
455 	 *
456 	 * <p>
457 	 * The default implementation of this method simply calls {@link Serializer#getResponseHeaders(SerializerSession)}.
458 	 *
459 	 * @return
460 	 * 	The HTTP headers to set on HTTP requests.
461 	 * 	Never <jk>null</jk>.
462 	 */
463 	public Map<String,String> getResponseHeaders() { return ctx.getResponseHeaders(this); }
464 
465 	/**
466 	 * HTTP part schema of object being serialized.
467 	 *
468 	 * @return HTTP part schema of object being serialized, or <jk>null</jk> if not specified.
469 	 */
470 	public final HttpPartSchema getSchema() { return schema; }
471 
472 	/**
473 	 * Returns the variable resolver session.
474 	 *
475 	 * @return The variable resolver session.
476 	 */
477 	public VarResolverSession getVarResolver() {
478 		if (vrs == null)
479 			vrs = createDefaultVarResolverSession();
480 		return vrs;
481 	}
482 
483 	/**
484 	 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
485 	 *
486 	 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
487 	 */
488 	public boolean isWriterSerializer() { return false; }
489 
490 	/**
491 	 * Resolves any variables in the specified string.
492 	 *
493 	 * @param string The string to resolve values in.
494 	 * @return The string with variables resolved.
495 	 */
496 	public String resolve(String string) {
497 		return getVarResolver().resolve(string);
498 	}
499 
500 	/**
501 	 * Converts a String to an absolute URI based on the {@link UriContext} on this session.
502 	 *
503 	 * @param uri
504 	 * 	The input URI.
505 	 * 	Can be any of the following:
506 	 * 	<ul>
507 	 * 		<li>{@link java.net.URI}
508 	 * 		<li>{@link java.net.URL}
509 	 * 		<li>{@link CharSequence}
510 	 * 	</ul>
511 	 * 	URI can be any of the following forms:
512 	 * 	<ul>
513 	 * 		<li><js>"foo://foo"</js> - Absolute URI.
514 	 * 		<li><js>"/foo"</js> - Root-relative URI.
515 	 * 		<li><js>"/"</js> - Root URI.
516 	 * 		<li><js>"context:/foo"</js> - Context-root-relative URI.
517 	 * 		<li><js>"context:/"</js> - Context-root URI.
518 	 * 		<li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
519 	 * 		<li><js>"servlet:/"</js> - Servlet-path URI.
520 	 * 		<li><js>"request:/foo"</js> - Request-path-relative URI.
521 	 * 		<li><js>"request:/"</js> - Request-path URI.
522 	 * 		<li><js>"foo"</js> - Path-info-relative URI.
523 	 * 		<li><js>""</js> - Path-info URI.
524 	 * 	</ul>
525 	 * @return The resolved URI.
526 	 */
527 	public final String resolveUri(Object uri) {
528 		return uriResolver.resolve(uri);
529 	}
530 
531 	/**
532 	 * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code>
533 	 * depending on the serializer type.
534 	 *
535 	 * @param o The object to serialize.
536 	 * @return
537 	 * 	The serialized object.
538 	 * 	<br>Character-based serializers will return a <c>String</c>.
539 	 * 	<br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>.
540 	 * @throws SerializeException If a problem occurred trying to convert the output.
541 	 */
542 	public Object serialize(Object o) throws SerializeException {
543 		throw unsupportedOp();
544 	}
545 
546 	/**
547 	 * Serialize the specified object using the specified session.
548 	 *
549 	 * @param out Where to send the output from the serializer.
550 	 * @param o The object to serialize.
551 	 * @throws SerializeException If a problem occurred trying to convert the output.
552 	 * @throws IOException Thrown by the underlying stream.
553 	 */
554 	public final void serialize(Object o, Object out) throws SerializeException, IOException {
555 		try (SerializerPipe pipe = createPipe(out)) {
556 			doSerialize(pipe, o);
557 		} catch (SerializeException | IOException e) {
558 			throw e;
559 		} catch (@SuppressWarnings("unused") StackOverflowError e) {
560 			throw new SerializeException(this,
561 				"Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
562 		} catch (Exception e) {
563 			throw new SerializeException(this, e);
564 		} finally {
565 			checkForWarnings();
566 		}
567 	}
568 
569 	/**
570 	 * Shortcut method for serializing an object to a String.
571 	 *
572 	 * @param o The object to serialize.
573 	 * @return
574 	 * 	The serialized object.
575 	 * 	<br>Character-based serializers will return a <c>String</c>
576 	 * 	<br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer.Builder#binaryFormat(BinaryFormat)} setting.
577 	 * @throws SerializeException If a problem occurred trying to convert the output.
578 	 */
579 	public String serializeToString(Object o) throws SerializeException {
580 		throw unsupportedOp();
581 	}
582 
583 	/**
584 	 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
585 	 *
586 	 * @param <E> The element type.
587 	 * @param c The collection being sorted.
588 	 * @return A new sorted {@link TreeSet}.
589 	 */
590 	public final <E> Collection<E> sort(Collection<E> c) {
591 		if (c == null || c.isEmpty() || SortedSet.class.isInstance(c))
592 			return c;
593 		if (isSortCollections() && isSortable(c))
594 			return c.stream().sorted().collect(Collectors.toList());
595 		return c;
596 	}
597 
598 	/**
599 	 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
600 	 *
601 	 * @param <E> The element type.
602 	 * @param c The collection being sorted.
603 	 * @return A new sorted {@link TreeSet}.
604 	 */
605 	public final <E> List<E> sort(List<E> c) {
606 		if (c == null || c.isEmpty())
607 			return c;
608 		if (isSortCollections() && isSortable(c))
609 			return c.stream().sorted().collect(Collectors.toList());
610 		return c;
611 	}
612 
613 	/**
614 	 * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>.
615 	 *
616 	 * @param <K> The key type.
617 	 * @param <V> The value type.
618 	 * @param m The map being sorted.
619 	 * @return A new sorted {@link TreeMap}.
620 	 */
621 	public final <K,V> Map<K,V> sort(Map<K,V> m) {
622 		if (m == null || m.isEmpty() || SortedMap.class.isInstance(m))
623 			return m;
624 		if (isSortMaps() && isSortable(m.keySet()))
625 			return new TreeMap<>(m);
626 		return m;
627 	}
628 
629 	/**
630 	 * Converts the specified object to a <c>String</c>.
631 	 *
632 	 * <p>
633 	 * Also has the following effects:
634 	 * <ul>
635 	 * 	<li><c>Class</c> object is converted to a readable name.  See {@link ClassInfo#getNameFull()}.
636 	 * 	<li>Whitespace is trimmed if the trim-strings setting is enabled.
637 	 * </ul>
638 	 *
639 	 * @param o The object to convert to a <c>String</c>.
640 	 * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>.
641 	 */
642 	public final String toString(Object o) {
643 		if (o == null)
644 			return null;
645 		if (o.getClass() == Class.class)
646 			return info((Class<?>)o).getNameFull();
647 		if (o.getClass() == ClassInfo.class)
648 			return ((ClassInfo)o).getNameFull();
649 		if (o.getClass().isEnum())
650 			return getClassMetaForObject(o).toString(o);
651 		var s = o.toString();
652 		if (isTrimStrings())
653 			s = s.trim();
654 		return s;
655 	}
656 
657 	/**
658 	 * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>.
659 	 *
660 	 * @param o The input string to trim.
661 	 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>.
662 	 */
663 	public final String trim(Object o) {
664 		if (o == null)
665 			return null;
666 		var s = o.toString();
667 		if (isTrimStrings())
668 			s = s.trim();
669 		return s;
670 	}
671 
672 	private static boolean isSortable(Collection<?> c) {
673 		if (c == null)
674 			return false;
675 		for (var o : c)
676 			if (! (o instanceof Comparable))
677 				return false;
678 		return true;
679 	}
680 
681 	/**
682 	 * Adds a session object to the {@link VarResolverSession} in this session.
683 	 *
684 	 * @return This object.
685 	 */
686 	protected VarResolverSession createDefaultVarResolverSession() {
687 		return VarResolver.DEFAULT.createSession();
688 	}
689 
690 	/**
691 	 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into
692 	 * a stream or reader.
693 	 *
694 	 * @param output
695 	 * 	The output location.
696 	 * 	<br>For character-based serializers, this can be any of the following types:
697 	 * 	<ul>
698 	 * 		<li>{@link Writer}
699 	 * 		<li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
700 	 * 		<li>{@link File} - Output will be written as system-default encoded stream.
701 	 * 		<li>{@link StringBuilder}
702 	 * 	</ul>
703 	 * 	<br>For byte-based serializers, this can be any of the following types:
704 	 * 	<ul>
705 	 * 		<li>{@link OutputStream}
706 	 * 		<li>{@link File}
707 	 * 	</ul>
708 	 * @return
709 	 * 	A new {@link ParserPipe} wrapper around the specified input object.
710 	 */
711 	protected SerializerPipe createPipe(Object output) {
712 		return new SerializerPipe(output);
713 	}
714 
715 	/**
716 	 * Serializes a POJO to the specified pipe.
717 	 *
718 	 * <p>
719 	 * This method should NOT close the context object.
720 	 *
721 	 * <p>
722 	 * The default implementation of this method simply calls {@link Serializer#doSerialize(SerializerSession,SerializerPipe,Object)}.
723 	 *
724 	 * @param pipe Where to send the output from the serializer.
725 	 * @param o The object to serialize.
726 	 * @throws IOException Thrown by underlying stream.
727 	 * @throws SerializeException Problem occurred trying to serialize object.
728 	 */
729 	protected void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException {
730 		ctx.doSerialize(this, pipe, o);
731 	}
732 
733 	/**
734 	 * Generalize the specified object if a POJO swap is associated with it.
735 	 *
736 	 * @param o The object to generalize.
737 	 * @param type The type of object.
738 	 * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>.
739 	 * @throws SerializeException If a problem occurred trying to convert the output.
740 	 */
741 	@SuppressWarnings({ "rawtypes", "unchecked" })
742 	protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException {
743 		try {
744 			if (o == null)
745 				return null;
746 			ObjectSwap f = (type == null || type.isObject() || type.isString() ? getClassMeta(o.getClass()).getSwap(this) : type.getSwap(this));
747 			if (f == null)
748 				return o;
749 			return f.swap(this, o);
750 		} catch (SerializeException e) {
751 			throw e;
752 		} catch (Exception e) {
753 			throw new SerializeException(e);
754 		}
755 	}
756 
757 	/**
758 	 * Resolves the dictionary name for the actual type.
759 	 *
760 	 * @param session The current serializer session.
761 	 * @param eType The expected type of the bean property.
762 	 * @param aType The actual type of the bean property.
763 	 * @param pMeta The current bean property being serialized.
764 	 * @return The bean dictionary name, or <jk>null</jk> if a name could not be found.
765 	 */
766 	@SuppressWarnings("null")
767 	protected final String getBeanTypeName(SerializerSession session, ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) {
768 		if (eType == aType || ! (isAddBeanTypes() || (session.isRoot() && isAddRootType())))
769 			return null;
770 
771 		String eTypeTn = eType.getBeanDictionaryName();
772 
773 		// First see if it's defined on the actual type.
774 		String tn = aType.getBeanDictionaryName();
775 		if (nn(tn) && ! tn.equals(eTypeTn)) {
776 			return tn;
777 		}
778 
779 		// Then see if it's defined on the expected type.
780 		// The expected type might be an interface with mappings for implementation classes.
781 		BeanRegistry br = eType.getBeanRegistry();
782 		if (nn(br)) {
783 			tn = br.getTypeName(aType);
784 			if (nn(tn) && ! tn.equals(eTypeTn))
785 				return tn;
786 		}
787 
788 		// Then look on the bean property.
789 		br = pMeta == null ? null : pMeta.getBeanRegistry();
790 		if (nn(br)) {
791 			tn = br.getTypeName(aType);
792 			if (nn(tn) && ! tn.equals(eTypeTn))
793 				return tn;
794 		}
795 
796 		// Finally look in the session.
797 		br = getBeanRegistry();
798 		if (nn(br)) {
799 			tn = br.getTypeName(aType);
800 			if (nn(tn) && ! tn.equals(eTypeTn))
801 				return tn;
802 		}
803 
804 		return null;
805 	}
806 
807 	/**
808 	 * Returns the parser-side expected type for the object.
809 	 *
810 	 * <p>
811 	 * The return value depends on the {@link Serializer.Builder#addRootType()} setting.
812 	 * When disabled, the parser already knows the Java POJO type being parsed, so there is
813 	 * no reason to add <js>"_type"</js> attributes to the root-level object.
814 	 *
815 	 * @param o The object to get the expected type on.
816 	 * @return The expected type.
817 	 */
818 	protected final ClassMeta<?> getExpectedRootType(Object o) {
819 		if (isAddRootType())
820 			return object();
821 		var cm = getClassMetaForObject(o);
822 		if (nn(cm) && cm.isOptional())
823 			return cm.getElementType();
824 		return cm;
825 	}
826 
827 	/**
828 	 * Returns the Java method that invoked this serializer.
829 	 *
830 	 * <p>
831 	 * When using the REST API, this is the Java method invoked by the REST call.
832 	 * Can be used to access annotations defined on the method or class.
833 	 *
834 	 * @return The Java method that invoked this serializer.
835 	*/
836 	protected final Method getJavaMethod() { return javaMethod; }
837 
838 	/**
839 	 * URI context bean.
840 	 *
841 	 * @see Serializer.Builder#uriContext(UriContext)
842 	 * @return
843 	 * 	Bean used for resolution of URIs to absolute or root-relative form.
844 	 */
845 	protected final UriContext getUriContext() { return ctx.getUriContext(); }
846 
847 	/**
848 	 * URI relativity.
849 	 *
850 	 * @see Serializer.Builder#uriRelativity(UriRelativity)
851 	 * @return
852 	 * 	Defines what relative URIs are relative to when serializing any of the following:
853 	 */
854 	protected final UriRelativity getUriRelativity() { return ctx.getUriRelativity(); }
855 
856 	/**
857 	 * URI resolution.
858 	 *
859 	 * @see Serializer.Builder#uriResolution(UriResolution)
860 	 * @return
861 	 * 	Defines the resolution level for URIs when serializing URIs.
862 	 */
863 	protected final UriResolution getUriResolution() { return ctx.getUriResolution(); }
864 
865 	/**
866 	 * Returns the URI resolver.
867 	 *
868 	 * @return The URI resolver.
869 	 */
870 	protected final UriResolver getUriResolver() { return uriResolver; }
871 
872 	/**
873 	 * Add <js>"_type"</js> properties when needed.
874 	 *
875 	 * @see Serializer.Builder#addBeanTypes()
876 	 * @return
877 	 * 	<jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
878 	 * 	through reflection.
879 	 */
880 	protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
881 
882 	/**
883 	 * Add type attribute to root nodes.
884 	 *
885 	 * @see Serializer.Builder#addRootType()
886 	 * @return
887 	 * 	<jk>true</jk> if type property should be added to root node.
888 	 */
889 	protected final boolean isAddRootType() { return ctx.isAddRootType(); }
890 
891 	/**
892 	 * Don't trim null bean property values.
893 	 *
894 	 * @see Serializer.Builder#keepNullProperties()
895 	 * @return
896 	 * 	<jk>true</jk> if null bean values are serialized to the output.
897 	 */
898 	protected final boolean isKeepNullProperties() { return ctx.isKeepNullProperties(); }
899 
900 	/**
901 	 * Sort arrays and collections alphabetically.
902 	 *
903 	 * @see Serializer.Builder#sortCollections()
904 	 * @return
905 	 * 	<jk>true</jk> if arrays and collections are copied and sorted before serialization.
906 	 */
907 	protected final boolean isSortCollections() { return ctx.isSortCollections(); }
908 
909 	/**
910 	 * Sort maps alphabetically.
911 	 *
912 	 * @see Serializer.Builder#sortMaps()
913 	 * @return
914 	 * 	<jk>true</jk> if maps are copied and sorted before serialization.
915 	 */
916 	protected final boolean isSortMaps() { return ctx.isSortMaps(); }
917 
918 	/**
919 	 * Trim empty lists and arrays.
920 	 *
921 	 * @see Serializer.Builder#trimEmptyCollections()
922 	 * @return
923 	 * 	<jk>true</jk> if empty lists and arrays are not serialized to the output.
924 	 */
925 	protected final boolean isTrimEmptyCollections() { return ctx.isTrimEmptyCollections(); }
926 
927 	/**
928 	 * Trim empty maps.
929 	 *
930 	 * @see Serializer.Builder#trimEmptyMaps()
931 	 * @return
932 	 * 	<jk>true</jk> if empty map values are not serialized to the output.
933 	 */
934 	protected final boolean isTrimEmptyMaps() { return ctx.isTrimEmptyMaps(); }
935 
936 	/**
937 	 * Trim strings.
938 	 *
939 	 * @see Serializer.Builder#trimStrings()
940 	 * @return
941 	 * 	<jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
942 	 */
943 	protected boolean isTrimStrings() { return ctx.isTrimStrings(); }
944 
945 	/**
946 	 * Specialized warning when an exception is thrown while executing a bean getter.
947 	 *
948 	 * @param p The bean map entry representing the bean property.
949 	 * @param t The throwable that the bean getter threw.
950 	 * @throws SerializeException Thrown if ignoreInvocationExceptionOnGetters is false.
951 	 */
952 	protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) throws SerializeException {
953 		if (nn(listener))
954 			listener.onBeanGetterException(this, t, p);
955 		String prefix = (isDebug() ? getStack(false) + ": " : "");
956 		addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t));
957 		if (! isIgnoreInvocationExceptionsOnGetters())
958 			throw new SerializeException(this, "{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t))
959 				.initCause(t);
960 	}
961 
962 	/**
963 	 * Logs a warning message.
964 	 *
965 	 * @param t The throwable that was thrown (if there was one).
966 	 * @param msg The warning message.
967 	 * @param args Optional {@link MessageFormat}-style arguments.
968 	 */
969 	@Override
970 	protected void onError(Throwable t, String msg, Object...args) {
971 		if (nn(listener))
972 			listener.onError(this, t, f(msg, args));
973 		super.onError(t, msg, args);
974 	}
975 
976 	@Override /* Overridden from BeanTraverseSession */
977 	protected FluentMap<String,Object> properties() {
978 		return super.properties()
979 			.a("uriResolver", uriResolver);
980 	}
981 
982 	/**
983 	 * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}.
984 	 *
985 	 * @param attrName The attribute name.
986 	 * @param o The current object being traversed.
987 	 * @param eType The expected class type.
988 	 * @return
989 	 * 	The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
990 	 * 	once (since they can be expensive).
991 	 * @throws SerializeException If recursion occurred.
992 	 */
993 	protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException {
994 		try {
995 			return super.push(attrName, o, eType);
996 		} catch (BeanRecursionException e) {
997 			throw new SerializeException(e);
998 		}
999 	}
1000 
1001 	/**
1002 	 * Opposite of {@link #resolveUri(Object)}.
1003 	 *
1004 	 * <p>
1005 	 * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter.
1006 	 *
1007 	 * <p>
1008 	 * Both parameters can be any of the following:
1009 	 * <ul>
1010 	 * 	<li>{@link java.net.URI}
1011 	 * 	<li>{@link java.net.URL}
1012 	 * 	<li>{@link CharSequence}
1013 	 * </ul>
1014 	 *
1015 	 * <p>
1016 	 * Both URIs can be any of the following forms:
1017 	 * <ul>
1018 	 * 	<li><js>"foo://foo"</js> - Absolute URI.
1019 	 * 	<li><js>"/foo"</js> - Root-relative URI.
1020 	 * 	<li><js>"/"</js> - Root URI.
1021 	 * 	<li><js>"context:/foo"</js> - Context-root-relative URI.
1022 	 * 	<li><js>"context:/"</js> - Context-root URI.
1023 	 * 	<li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
1024 	 * 	<li><js>"servlet:/"</js> - Servlet-path URI.
1025 	 * 	<li><js>"request:/foo"</js> - Request-path-relative URI.
1026 	 * 	<li><js>"request:/"</js> - Request-path URI.
1027 	 * 	<li><js>"foo"</js> - Path-info-relative URI.
1028 	 * 	<li><js>""</js> - Path-info URI.
1029 	 * </ul>
1030 	 *
1031 	 * @param relativeTo The URI to relativize against.
1032 	 * @param uri The URI to relativize.
1033 	 * @return The relativized URI.
1034 	 */
1035 	protected final String relativizeUri(Object relativeTo, Object uri) {
1036 		return uriResolver.relativize(relativeTo, uri);
1037 	}
1038 
1039 	/**
1040 	 * Invokes the specified swap on the specified object if the swap is not null.
1041 	 *
1042 	 * @param swap The swap to invoke.  Can be <jk>null</jk>.
1043 	 * @param o The input object.
1044 	 * @return The swapped object.
1045 	 * @throws SerializeException If swap method threw an exception.
1046 	 */
1047 	@SuppressWarnings({ "rawtypes", "unchecked" })
1048 	protected Object swap(ObjectSwap swap, Object o) throws SerializeException {
1049 		try {
1050 			if (swap == null)
1051 				return o;
1052 			return swap.swap(this, o);
1053 		} catch (Exception e) {
1054 			throw new SerializeException(e);
1055 		}
1056 	}
1057 }