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 }