1 package com.fasterxml.jackson.annotation;
2
3 import java.lang.annotation.*;
4 import java.util.Locale;
5 import java.util.TimeZone;
6
7 /**
8 * General-purpose annotation used for configuring details of how
9 * values of properties are to be serialized.
10 * Unlike most other Jackson annotations, annotation does not
11 * have specific universal interpretation: instead, effect depends on datatype
12 * of property being annotated (or more specifically, deserializer
13 * and serializer being used).
14 *<p>
15 * Common uses include choosing between alternate representations -- for example,
16 * whether {@link java.util.Date} is to be serialized as number (Java timestamp)
17 * or String (such as ISO-8601 compatible time value) -- as well as configuring
18 * exact details with {@link #pattern} property.
19 *<p>
20 * As of Jackson 2.6, known special handling includes:
21 *<ul>
22 * <li>{@link java.util.Date}: Shape can be {@link Shape#STRING} or {@link Shape#NUMBER};
23 * pattern may contain {@link java.text.SimpleDateFormat}-compatible pattern definition.
24 * </li>
25 * <li>Can be used on Classes (types) as well, for modified default behavior, possibly
26 * overridden by per-property annotation
27 * </li>
28 * <li>{@link java.lang.Enum}s: Shapes {@link Shape#STRING} and {@link Shape#NUMBER} can be
29 * used to change between numeric (index) and textual (name or <code>toString()</code>);
30 * but it is also possible to use {@link Shape#OBJECT} to serialize (but not deserialize)
31 * {@link java.lang.Enum}s as JSON Objects (as if they were POJOs). NOTE: serialization
32 * as JSON Object only works with class annotation;
33 * will not work as per-property annotation.
34 * </li>
35 * <li>{@link java.util.Collection}s can be serialized as (and deserialized from) JSON Objects,
36 * if {@link Shape#OBJECT} is used. NOTE: can ONLY be used as class annotation;
37 * will not work as per-property annotation.
38 * </li>
39 * <li>{@link java.lang.Number} subclasses can be serialized as full objects if
40 * {@link Shape#OBJECT} is used. Otherwise the default behavior of serializing to a
41 * scalar number value will be preferred. NOTE: can ONLY be used as class annotation;
42 * will not work as per-property annotation.
43 * </li>
44 *</ul>
45 *
46 * @since 2.0
47 */
48 @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
49 ElementType.TYPE})
50 @Retention(RetentionPolicy.RUNTIME)
51 @JacksonAnnotation
52 public @interface JsonFormat
53 {
54 /**
55 * Value that indicates that default {@link java.util.Locale}
56 * (from deserialization or serialization context) should be used:
57 * annotation does not define value to use.
58 */
59 public final static String DEFAULT_LOCALE = "##default";
60
61 /**
62 * Value that indicates that default {@link java.util.TimeZone}
63 * (from deserialization or serialization context) should be used:
64 * annotation does not define value to use.
65 *<p>
66 * NOTE: default here does NOT mean JVM defaults but Jackson databindings
67 * default, usually UTC, but may be changed on <code>ObjectMapper</code>.
68 */
69 public final static String DEFAULT_TIMEZONE = "##default";
70
71 /**
72 * Datatype-specific additional piece of configuration that may be used
73 * to further refine formatting aspects. This may, for example, determine
74 * low-level format String used for {@link java.util.Date} serialization;
75 * however, exact use is determined by specific <code>JsonSerializer</code>
76 */
77 public String pattern() default "";
78
79 /**
80 * Structure to use for serialization: definition of mapping depends on datatype,
81 * but usually has straight-forward counterpart in data format (JSON).
82 * Note that commonly only a subset of shapes is available; and if 'invalid' value
83 * is chosen, defaults are usually used.
84 */
85 public Shape shape() default Shape.ANY;
86
87 /**
88 * {@link java.util.Locale} to use for serialization (if needed).
89 * Special value of {@link #DEFAULT_LOCALE}
90 * can be used to mean "just use the default", where default is specified
91 * by the serialization context, which in turn defaults to system
92 * defaults ({@link java.util.Locale#getDefault()}) unless explicitly
93 * set to another locale.
94 */
95 public String locale() default DEFAULT_LOCALE;
96
97 /**
98 * {@link java.util.TimeZone} to use for serialization (if needed).
99 * Special value of {@link #DEFAULT_TIMEZONE}
100 * can be used to mean "just use the default", where default is specified
101 * by the serialization context, which in turn defaults to system
102 * default (UTC) unless explicitly set to another timezone.
103 */
104 public String timezone() default DEFAULT_TIMEZONE;
105
106 /**
107 * Property that indicates whether "lenient" handling should be enabled or
108 * disabled. This is relevant mostly for deserialization of some textual
109 * datatypes, especially date/time types.
110 *<p>
111 * Note that underlying default setting depends on datatype (or more precisely
112 * deserializer for it): for most date/time types, default is for leniency
113 * to be enabled.
114 *
115 * @since 2.9
116 */
117 public OptBoolean lenient() default OptBoolean.DEFAULT;
118
119 /**
120 * Set of {@link JsonFormat.Feature}s to explicitly enable with respect
121 * to handling of annotated property. This will have precedence over possible
122 * global configuration.
123 *
124 * @since 2.6
125 */
126 public JsonFormat.Feature[] with() default { };
127
128 /**
129 * Set of {@link JsonFormat.Feature}s to explicitly disable with respect
130 * to handling of annotated property. This will have precedence over possible
131 * global configuration.
132 *
133 * @since 2.6
134 */
135 public JsonFormat.Feature[] without() default { };
136
137 /*
138 /**********************************************************
139 /* Value enumeration(s), value class(es)
140 /**********************************************************
141 */
142
143 /**
144 * Value enumeration used for indicating preferred Shape; translates
145 * loosely to JSON types, with some extra values to indicate less precise
146 * choices (i.e. allowing one of multiple actual shapes)
147 */
148 public enum Shape
149 {
150 /**
151 * Marker enum value that indicates "whatever" choice, meaning that annotation
152 * does NOT specify shape to use.
153 * Note that this is different from {@link Shape#NATURAL}, which
154 * specifically instructs use of the "natural" shape for datatype.
155 */
156 ANY,
157
158 /**
159 * Marker enum value that indicates the "default" choice for given datatype;
160 * for example, JSON String for {@link java.lang.String}, or JSON Number
161 * for Java numbers.
162 * Note that this is different from {@link Shape#ANY} in that this is actual
163 * explicit choice that overrides possible default settings.
164 *
165 * @since 2.8
166 */
167 NATURAL,
168
169 /**
170 * Value that indicates shape should not be structural (that is, not
171 * {@link #ARRAY} or {@link #OBJECT}, but can be any other shape.
172 */
173 SCALAR,
174
175 /**
176 * Value that indicates that (JSON) Array type should be used.
177 */
178 ARRAY,
179
180 /**
181 * Value that indicates that (JSON) Object type should be used.
182 */
183 OBJECT,
184
185 /**
186 * Value that indicates that a numeric (JSON) type should be used
187 * (but does not specify whether integer or floating-point representation
188 * should be used)
189 */
190 NUMBER,
191
192 /**
193 * Value that indicates that floating-point numeric type should be used
194 */
195 NUMBER_FLOAT,
196
197 /**
198 * Value that indicates that integer number type should be used
199 * (and not {@link #NUMBER_FLOAT}).
200 */
201 NUMBER_INT,
202
203 /**
204 * Value that indicates that (JSON) String type should be used.
205 */
206 STRING,
207
208 /**
209 * Value that indicates that (JSON) boolean type
210 * (true, false) should be used.
211 */
212 BOOLEAN,
213
214 /**
215 * Value that indicates that Binary type (native, if format supports it;
216 * encoding using Base64 if only textual types supported) should be used.
217 *
218 * @since 2.10
219 */
220 BINARY
221 ;
222
223 public boolean isNumeric() {
224 return (this == NUMBER) || (this == NUMBER_INT) || (this == NUMBER_FLOAT);
225 }
226
227 public boolean isStructured() {
228 return (this == OBJECT) || (this == ARRAY);
229 }
230 }
231
232 /**
233 * Set of features that can be enabled/disabled for property annotated.
234 * These often relate to specific <code>SerializationFeature</code>
235 * or <code>DeserializationFeature</code>, as noted by entries.
236 *<p>
237 * Note that whether specific setting has an effect depends on whether
238 * <code>JsonSerializer</code> / <code>JsonDeserializer</code> being used
239 * takes the format setting into account. If not, please file an issue
240 * for adding support via issue tracker for package that has handlers
241 * (if you know which one; if not, just use `jackson-databind`).
242 *
243 * @since 2.6
244 */
245 public enum Feature {
246 /**
247 * Override for <code>DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY</code>
248 * which will allow deserialization of JSON non-array values into single-element
249 * Java arrays and {@link java.util.Collection}s.
250 */
251 ACCEPT_SINGLE_VALUE_AS_ARRAY,
252
253 /**
254 * Override for <code>MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES</code>,
255 * which allows case-insensitive matching of property names (but NOT values,
256 * see {@link #ACCEPT_CASE_INSENSITIVE_VALUES} for that).
257 *<p>
258 * Only affects deserialization, has no effect on serialization.
259 *
260 * @since 2.8
261 */
262 ACCEPT_CASE_INSENSITIVE_PROPERTIES,
263
264 /**
265 * Override for <code>MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES</code>,
266 * which allows case-sensitive matching of (some) property values, such
267 * as {@code Enum}s.
268 * Only affects deserialization, has no effect on serialization.
269 *
270 * @since 2.10
271 */
272 ACCEPT_CASE_INSENSITIVE_VALUES,
273
274 /**
275 * Override for <code>SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS</code>,
276 * similar constraints apply.
277 */
278 WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS,
279
280 /**
281 * Override for <code>SerializationFeature.WRITE_DATES_WITH_ZONE_ID</code>,
282 * similar constraints apply.
283 */
284 WRITE_DATES_WITH_ZONE_ID,
285
286 /**
287 * Override for <code>SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED</code>
288 * which will force serialization of single-element arrays and {@link java.util.Collection}s
289 * as that single element and excluding array wrapper.
290 */
291 WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,
292
293 /**
294 * Override for <code>SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS</code>,
295 * enabling of which will force sorting of {@link java.util.Map} keys before
296 * serialization.
297 */
298 WRITE_SORTED_MAP_ENTRIES,
299
300 /**
301 * Override for <code>DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIMEZONE</code>
302 * that specifies whether context provided timezone
303 * <code>DeserializationContext.getTimeZone()</code> should be used to adjust Date/Time
304 * values on deserialization, even if value itself contains timezone informatio
305 *<p>
306 * NOTE: due to limitations of "old" JDK date/time types (that is,
307 * {@link java.util.Date} and {@link java.util.Calendar}), this setting is only
308 * applicable to <code>Joda</code> and <code>Java 8 date/time</code> values,
309 * but not to <code>java.util.Date</code> or <code>java.util.Calendar</code>.
310 *
311 * @since 2.8
312 */
313 ADJUST_DATES_TO_CONTEXT_TIME_ZONE
314 }
315
316 /**
317 * Helper class that encapsulates information equivalent to {@link java.lang.Boolean}
318 * valued {@link java.util.EnumMap}.
319 *
320 * @since 2.6
321 */
322 public static class Features
323 {
324 private final int _enabled, _disabled;
325
326 private final static Features EMPTY = new Features(0, 0);
327
328 private Features(int e, int d) {
329 _enabled = e;
330 _disabled = d;
331 }
332
333 public static Features empty() {
334 return EMPTY;
335 }
336
337 public static Features construct(JsonFormat f) {
338 return construct(f.with(), f.without());
339 }
340
341 public static Features construct(Feature[] enabled, Feature[] disabled)
342 {
343 int e = 0;
344 for (Feature f : enabled) {
345 e |= (1 << f.ordinal());
346 }
347 int d = 0;
348 for (Feature f : disabled) {
349 d |= (1 << f.ordinal());
350 }
351 return new Features(e, d);
352 }
353
354 public Features withOverrides(Features overrides) {
355 // Cheap checks first: maybe one is empty?
356 if (overrides == null) {
357 return this;
358 }
359 int overrideD = overrides._disabled;
360 int overrideE = overrides._enabled;
361 if ((overrideD == 0) && (overrideE == 0)) {
362 return this;
363 }
364 if ((_enabled == 0) && (_disabled == 0)) {
365 return overrides;
366 }
367 // If not, calculate combination with overrides
368 int newE = (_enabled & ~overrideD) | overrideE;
369 int newD = (_disabled & ~overrideE) | overrideD;
370
371 // one more thing; no point in creating new instance if there's no change
372 if ((newE == _enabled) && (newD == _disabled)) {
373 return this;
374 }
375
376 return new Features(newE, newD);
377 }
378
379 public Features with(Feature...features) {
380 int e = _enabled;
381 for (Feature f : features) {
382 e |= (1 << f.ordinal());
383 }
384 return (e == _enabled) ? this : new Features(e, _disabled);
385 }
386
387 public Features without(Feature...features) {
388 int d = _disabled;
389 for (Feature f : features) {
390 d |= (1 << f.ordinal());
391 }
392 return (d == _disabled) ? this : new Features(_enabled, d);
393 }
394
395 public Boolean get(Feature f) {
396 int mask = (1 << f.ordinal());
397 if ((_disabled & mask) != 0) {
398 return Boolean.FALSE;
399 }
400 if ((_enabled & mask) != 0) {
401 return Boolean.TRUE;
402 }
403 return null;
404 }
405
406 @Override
407 public String toString() {
408 if (this == EMPTY) {
409 return "EMPTY";
410 }
411 return String.format("(enabled=0x%x,disabled=0x%x)", _enabled, _disabled);
412 }
413
414 @Override
415 public int hashCode() {
416 return _disabled + _enabled;
417 }
418
419 @Override
420 public boolean equals(Object o) {
421 if (o == this) return true;
422 if (o == null) return false;
423 if (o.getClass() != getClass()) return false;
424 Features other = (Features) o;
425 return (other._enabled == _enabled) && (other._disabled == _disabled);
426 }
427 }
428
429 /**
430 * Helper class used to contain information from a single {@link JsonFormat}
431 * annotation.
432 */
433 public static class Value
434 implements JacksonAnnotationValue<JsonFormat>, // since 2.6
435 java.io.Serializable
436 {
437 private static final long serialVersionUID = 1L;
438
439 private final static Value EMPTY = new Value();
440
441 private final String _pattern;
442 private final Shape _shape;
443 private final Locale _locale;
444
445 private final String _timezoneStr;
446
447 /**
448 * @since 2.9
449 */
450 private final Boolean _lenient;
451
452 /**
453 * @since 2.6
454 */
455 private final Features _features;
456
457 // lazily constructed when created from annotations
458 private transient TimeZone _timezone;
459
460 public Value() {
461 this("", Shape.ANY, "", "", Features.empty(), null);
462 }
463
464 public Value(JsonFormat ann) {
465 this(ann.pattern(), ann.shape(), ann.locale(), ann.timezone(),
466 Features.construct(ann), ann.lenient().asBoolean());
467 }
468
469 /**
470 * @since 2.9
471 */
472 public Value(String p, Shape sh, String localeStr, String tzStr, Features f,
473 Boolean lenient)
474 {
475 this(p, sh,
476 (localeStr == null || localeStr.length() == 0 || DEFAULT_LOCALE.equals(localeStr)) ?
477 null : new Locale(localeStr),
478 (tzStr == null || tzStr.length() == 0 || DEFAULT_TIMEZONE.equals(tzStr)) ?
479 null : tzStr,
480 null, f, lenient);
481 }
482
483 /**
484 * @since 2.9
485 */
486 public Value(String p, Shape sh, Locale l, TimeZone tz, Features f,
487 Boolean lenient)
488 {
489 _pattern = (p == null) ? "" : p;
490 _shape = (sh == null) ? Shape.ANY : sh;
491 _locale = l;
492 _timezone = tz;
493 _timezoneStr = null;
494 _features = (f == null) ? Features.empty() : f;
495 _lenient = lenient;
496 }
497
498 /**
499 * @since 2.9
500 */
501 public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f,
502 Boolean lenient)
503 {
504 _pattern = (p == null) ? "" : p;
505 _shape = (sh == null) ? Shape.ANY : sh;
506 _locale = l;
507 _timezone = tz;
508 _timezoneStr = tzStr;
509 _features = (f == null) ? Features.empty() : f;
510 _lenient = lenient;
511 }
512
513 @Deprecated // since 2.9
514 public Value(String p, Shape sh, Locale l, String tzStr, TimeZone tz, Features f) {
515 this(p, sh, l, tzStr, tz, f, null);
516 }
517
518 @Deprecated // since 2.9
519 public Value(String p, Shape sh, String localeStr, String tzStr, Features f) {
520 this(p, sh, localeStr, tzStr, f, null);
521 }
522 @Deprecated // since 2.9
523 public Value(String p, Shape sh, Locale l, TimeZone tz, Features f) {
524 this(p, sh, l, tz, f, null);
525 }
526
527 /**
528 * @since 2.7
529 */
530 public final static Value empty() {
531 return EMPTY;
532 }
533
534 /**
535 * Helper method that will try to combine values from two {@link Value}
536 * instances, using one as base settings, and the other as overrides
537 * to use instead of base values when defined; base values are only
538 * use if override does not specify a value (matching value is null
539 * or logically missing).
540 * Note that one or both of value instances may be `null`, directly;
541 * if both are `null`, result will also be `null`; otherwise never null.
542 *
543 * @since 2.8
544 */
545 public static Value merge(Value base, Value overrides)
546 {
547 return (base == null) ? overrides
548 : base.withOverrides(overrides);
549 }
550
551 /**
552 * @since 2.8
553 */
554 public static Value mergeAll(Value... values)
555 {
556 Value result = null;
557 for (Value curr : values) {
558 if (curr != null) {
559 result = (result == null) ? curr : result.withOverrides(curr);
560 }
561 }
562 return result;
563 }
564
565 /**
566 * @since 2.7
567 */
568 public final static Value from(JsonFormat ann) {
569 return (ann == null) ? EMPTY : new Value(ann);
570 }
571
572 /**
573 * @since 2.7
574 */
575 public final Value withOverrides(Value overrides) {
576 if ((overrides == null) || (overrides == EMPTY) || (overrides == this)) {
577 return this;
578 }
579 if (this == EMPTY) { // cheesy, but probably common enough
580 return overrides;
581 }
582 String p = overrides._pattern;
583 if ((p == null) || p.isEmpty()) {
584 p = _pattern;
585 }
586 Shape sh = overrides._shape;
587 if (sh == Shape.ANY) {
588 sh = _shape;
589 }
590 Locale l = overrides._locale;
591 if (l == null) {
592 l = _locale;
593 }
594 Features f = _features;
595 if (f == null) {
596 f = overrides._features;
597 } else {
598 f = f.withOverrides(overrides._features);
599 }
600 Boolean lenient = overrides._lenient;
601 if (lenient == null) {
602 lenient = _lenient;
603 }
604
605 // timezone not merged, just choose one
606 String tzStr = overrides._timezoneStr;
607 TimeZone tz;
608
609 if ((tzStr == null) || tzStr.isEmpty()) { // no overrides, use space
610 tzStr = _timezoneStr;
611 tz = _timezone;
612 } else {
613 tz = overrides._timezone;
614 }
615 return new Value(p, sh, l, tzStr, tz, f, lenient);
616 }
617
618 /**
619 * @since 2.6
620 */
621 public static Value forPattern(String p) {
622 return new Value(p, null, null, null, null, Features.empty(), null);
623 }
624
625 /**
626 * @since 2.7
627 */
628 public static Value forShape(Shape sh) {
629 return new Value("", sh, null, null, null, Features.empty(), null);
630 }
631
632 /**
633 * @since 2.9
634 */
635 public static Value forLeniency(boolean lenient) {
636 return new Value("", null, null, null, null, Features.empty(),
637 Boolean.valueOf(lenient));
638 }
639
640 /**
641 * @since 2.1
642 */
643 public Value withPattern(String p) {
644 return new Value(p, _shape, _locale, _timezoneStr, _timezone,
645 _features, _lenient);
646 }
647
648 /**
649 * @since 2.1
650 */
651 public Value withShape(Shape s) {
652 if (s == _shape) {
653 return this;
654 }
655 return new Value(_pattern, s, _locale, _timezoneStr, _timezone,
656 _features, _lenient);
657 }
658
659 /**
660 * @since 2.1
661 */
662 public Value withLocale(Locale l) {
663 return new Value(_pattern, _shape, l, _timezoneStr, _timezone,
664 _features, _lenient);
665 }
666
667 /**
668 * @since 2.1
669 */
670 public Value withTimeZone(TimeZone tz) {
671 return new Value(_pattern, _shape, _locale, null, tz,
672 _features, _lenient);
673 }
674
675 /**
676 * @since 2.9
677 */
678 public Value withLenient(Boolean lenient) {
679 if (lenient == _lenient) {
680 return this;
681 }
682 return new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
683 _features, lenient);
684 }
685
686 /**
687 * @since 2.6
688 */
689 public Value withFeature(JsonFormat.Feature f) {
690 Features newFeats = _features.with(f);
691 return (newFeats == _features) ? this :
692 new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
693 newFeats, _lenient);
694 }
695
696 /**
697 * @since 2.6
698 */
699 public Value withoutFeature(JsonFormat.Feature f) {
700 Features newFeats = _features.without(f);
701 return (newFeats == _features) ? this :
702 new Value(_pattern, _shape, _locale, _timezoneStr, _timezone,
703 newFeats, _lenient);
704 }
705
706 @Override
707 public Class<JsonFormat> valueFor() {
708 return JsonFormat.class;
709 }
710
711 public String getPattern() { return _pattern; }
712 public Shape getShape() { return _shape; }
713 public Locale getLocale() { return _locale; }
714
715 /**
716 * @return {@code Boolean.TRUE} if explicitly set to true; {@code Boolean.FALSE}
717 * if explicit set to false; or {@code null} if not set either way (assuming
718 * "default leniency" for the context)
719 *
720 * @since 2.9
721 */
722 public Boolean getLenient() {
723 return _lenient;
724 }
725
726 /**
727 * Convenience method equivalent to
728 *<pre>
729 * Boolean.TRUE.equals(getLenient())
730 *</pre>
731 * that is, returns {@code true} if (and only if) leniency has been explicitly
732 * set to {code true}; but not if it is undefined.
733 *
734 * @since 2.9
735 */
736 public boolean isLenient() {
737 return Boolean.TRUE.equals(_lenient);
738 }
739
740 /**
741 * Alternate access (compared to {@link #getTimeZone()}) which is useful
742 * when caller just wants time zone id to convert, but not as JDK
743 * provided {@link TimeZone}
744 *
745 * @since 2.4
746 */
747 public String timeZoneAsString() {
748 if (_timezone != null) {
749 return _timezone.getID();
750 }
751 return _timezoneStr;
752 }
753
754 public TimeZone getTimeZone() {
755 TimeZone tz = _timezone;
756 if (tz == null) {
757 if (_timezoneStr == null) {
758 return null;
759 }
760 tz = TimeZone.getTimeZone(_timezoneStr);
761 _timezone = tz;
762 }
763 return tz;
764 }
765
766 /**
767 * @since 2.4
768 */
769 public boolean hasShape() { return _shape != Shape.ANY; }
770
771 /**
772 * @since 2.4
773 */
774 public boolean hasPattern() {
775 return (_pattern != null) && (_pattern.length() > 0);
776 }
777
778 /**
779 * @since 2.4
780 */
781 public boolean hasLocale() { return _locale != null; }
782
783 /**
784 * @since 2.4
785 */
786 public boolean hasTimeZone() {
787 return (_timezone != null) || (_timezoneStr != null && !_timezoneStr.isEmpty());
788 }
789
790 /**
791 * Accessor for checking whether there is a setting for leniency.
792 * NOTE: does NOT mean that `lenient` is `true` necessarily; just that
793 * it has been set.
794 *
795 * @since 2.9
796 */
797 public boolean hasLenient() {
798 return _lenient != null;
799 }
800
801 /**
802 * Accessor for checking whether this format value has specific setting for
803 * given feature. Result is 3-valued with either `null`, {@link Boolean#TRUE} or
804 * {@link Boolean#FALSE}, indicating 'yes/no/dunno' choices, where `null` ("dunno")
805 * indicates that the default handling should be used based on global defaults,
806 * and there is no format override.
807 *
808 * @since 2.6
809 */
810 public Boolean getFeature(JsonFormat.Feature f) {
811 return _features.get(f);
812 }
813
814 /**
815 * Accessor for getting full set of features enabled/disabled.
816 *
817 * @since 2.8
818 */
819 public Features getFeatures() {
820 return _features;
821 }
822
823 @Override
824 public String toString() {
825 return String.format("JsonFormat.Value(pattern=%s,shape=%s,lenient=%s,locale=%s,timezone=%s,features=%s)",
826 _pattern, _shape, _lenient, _locale, _timezoneStr, _features);
827 }
828
829 @Override
830 public int hashCode() {
831 int hash = (_timezoneStr == null) ? 1 : _timezoneStr.hashCode();
832 if (_pattern != null) {
833 hash ^= _pattern.hashCode();
834 }
835 hash += _shape.hashCode();
836 if (_lenient != null) {
837 hash ^= _lenient.hashCode();
838 }
839 if (_locale != null) {
840 hash += _locale.hashCode();
841 }
842 hash ^= _features.hashCode();
843 return hash;
844 }
845
846 @Override
847 public boolean equals(Object o) {
848 if (o == this) return true;
849 if (o == null) return false;
850 if (o.getClass() != getClass()) return false;
851 Value other = (Value) o;
852
853 if ((_shape != other._shape)
854 || !_features.equals(other._features)) {
855 return false;
856 }
857 return _equal(_lenient, other._lenient)
858 && _equal(_timezoneStr, other._timezoneStr)
859 && _equal(_pattern, other._pattern)
860 && _equal(_timezone, other._timezone)
861 && _equal(_locale, other._locale);
862 }
863
864 private static <T> boolean _equal(T value1, T value2)
865 {
866 if (value1 == null) {
867 return (value2 == null);
868 }
869 if (value2 == null) {
870 return false;
871 }
872 return value1.equals(value2);
873 }
874 }
875 }
876