diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java b/src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java index f68c2aa6..75ccf5e4 100644 --- a/src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonFormat.java @@ -484,18 +484,24 @@ public Features withOverrides(Features overrides) { public Features with(Feature...features) { int e = _enabled; + int d = _disabled; for (Feature f : features) { - e |= (1 << f.ordinal()); + int mask = (1 << f.ordinal()); + e |= mask; + d &= ~mask; } - return (e == _enabled) ? this : new Features(e, _disabled); + return (e == _enabled && d == _disabled) ? this : new Features(e, d); } public Features without(Feature...features) { + int e = _enabled; int d = _disabled; for (Feature f : features) { - d |= (1 << f.ordinal()); + int mask = (1 << f.ordinal()); + d |= mask; + e &= ~mask; } - return (d == _disabled) ? this : new Features(_enabled, d); + return (d == _disabled && e == _enabled) ? this : new Features(e, d); } public Boolean get(Feature f) { @@ -822,7 +828,7 @@ public Value withLocale(Locale l) { */ public Value withTimeZone(TimeZone tz) { return new Value(_pattern, _shape, _locale, null, tz, - _features, _lenient); + _features, _lenient, _radix); } /** @@ -1020,6 +1026,7 @@ public int hashCode() { hash += _locale.hashCode(); } hash ^= _features.hashCode(); + hash += _radix; return hash; } @@ -1037,9 +1044,8 @@ public boolean equals(Object o) { return Objects.equals(_lenient, other._lenient) && Objects.equals(_timezoneStr, other._timezoneStr) && Objects.equals(_pattern, other._pattern) - && Objects.equals(_timezone, other._timezone) && Objects.equals(_locale, other._locale) - && Objects.equals(_radix, other._radix); + && (_radix == other._radix); } } } diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java b/src/main/java/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java index ab219c39..23850c0f 100644 --- a/src/main/java/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java @@ -384,7 +384,7 @@ public String toString() { @Override public int hashCode() { - return (_ignored.size()) + return _ignored.hashCode() + (_ignoreUnknown ? 1 : -3) + (_allowGetters ? 3 : -7) + (_allowSetters ? 7 : -11) diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonInclude.java b/src/main/java/com/fasterxml/jackson/annotation/JsonInclude.java index 4310fa2f..5a5a60d6 100644 --- a/src/main/java/com/fasterxml/jackson/annotation/JsonInclude.java +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonInclude.java @@ -448,7 +448,7 @@ public Value withOverrides(Value overrides) { boolean viDiff = (vi != _valueInclusion) && (vi != Include.USE_DEFAULTS); boolean ciDiff = (ci != _contentInclusion) && (ci != Include.USE_DEFAULTS); - boolean filterDiff = (vf != _valueFilter) || (cf != _valueFilter); + boolean filterDiff = (vf != _valueFilter) || (cf != _contentFilter); if (viDiff) { if (ciDiff) { @@ -619,7 +619,9 @@ public String toString() { @Override public int hashCode() { return (_valueInclusion.hashCode() << 2) - + _contentInclusion.hashCode(); + + _contentInclusion.hashCode() + + ((_valueFilter == null) ? 0 : _valueFilter.hashCode()) + + ((_contentFilter == null) ? 0 : _contentFilter.hashCode()); } @Override diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonIncludeProperties.java b/src/main/java/com/fasterxml/jackson/annotation/JsonIncludeProperties.java index 72568ef5..c3512201 100644 --- a/src/main/java/com/fasterxml/jackson/annotation/JsonIncludeProperties.java +++ b/src/main/java/com/fasterxml/jackson/annotation/JsonIncludeProperties.java @@ -181,7 +181,7 @@ public String toString() { @Override public int hashCode() { - return ((_included == null) ? 0 : _included.size()) + return ((_included == null) ? 0 : _included.hashCode()) + (Boolean.TRUE.equals(_ordered) ? 1 : 0); } diff --git a/src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java b/src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java index ef5c4ce5..ba0766d7 100644 --- a/src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java +++ b/src/test/java/com/fasterxml/jackson/annotation/JsonFormatTest.java @@ -263,6 +263,54 @@ public void testFeatures() { assertEquals(Boolean.TRUE, f4.get(Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)); } + @Test + void testFeaturesWithClearsDisabled() { + // with() after without() on same feature should result in enabled + JsonFormat.Features f = JsonFormat.Features.empty() + .without(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .with(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + assertEquals(Boolean.TRUE, f.get(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)); + } + + @Test + void testFeaturesWithoutClearsEnabled() { + // without() after with() on same feature should result in disabled + JsonFormat.Features f = JsonFormat.Features.empty() + .with(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .without(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + assertEquals(Boolean.FALSE, f.get(Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)); + } + + @Test + void testEqualsIgnoresTransientTimezone() { + // Two Values created from same timezone string should be equal + // regardless of whether getTimeZone() has been called + JsonFormat.Value v1 = new JsonFormat.Value("", Shape.ANY, "", "UTC", + JsonFormat.Features.empty(), null, DEFAULT_RADIX); + JsonFormat.Value v2 = new JsonFormat.Value("", Shape.ANY, "", "UTC", + JsonFormat.Features.empty(), null, DEFAULT_RADIX); + // Force lazy _timezone population on v1 only + v1.getTimeZone(); + assertEquals(v1, v2); + } + + @Test + void testWithTimeZonePreservesRadix() { + int binaryRadix = 2; + JsonFormat.Value v = JsonFormat.Value.forRadix(binaryRadix); + JsonFormat.Value withTz = v.withTimeZone(java.util.TimeZone.getTimeZone("UTC")); + assertEquals(binaryRadix, withTz.getRadix()); + } + + @Test + void testRadixInHashCode() { + JsonFormat.Value v1 = JsonFormat.Value.forRadix(2); + JsonFormat.Value v2 = JsonFormat.Value.forRadix(16); + // Not equal, so hashCodes should (very likely) differ + assertNotEquals(v1, v2); + assertNotEquals(v1.hashCode(), v2.hashCode()); + } + @Test void testRadix() { //Non-Default radix overrides the default diff --git a/src/test/java/com/fasterxml/jackson/annotation/JsonIgnorePropertiesTest.java b/src/test/java/com/fasterxml/jackson/annotation/JsonIgnorePropertiesTest.java index 5bc26d86..9e7eb6c8 100644 --- a/src/test/java/com/fasterxml/jackson/annotation/JsonIgnorePropertiesTest.java +++ b/src/test/java/com/fasterxml/jackson/annotation/JsonIgnorePropertiesTest.java @@ -149,6 +149,14 @@ public void testMergeIgnoreProperties() assertTrue(all.contains("c")); } + @Test + public void testHashCodeIncludesIgnoredContents() { + JsonIgnoreProperties.Value v1 = EMPTY.withIgnored("a", "b"); + JsonIgnoreProperties.Value v2 = EMPTY.withIgnored("c", "d"); + assertNotEquals(v1, v2); + assertNotEquals(v1.hashCode(), v2.hashCode()); + } + @Test public void testToString() { assertEquals( diff --git a/src/test/java/com/fasterxml/jackson/annotation/JsonIncludePropertiesTest.java b/src/test/java/com/fasterxml/jackson/annotation/JsonIncludePropertiesTest.java index 8804f196..1a0221fd 100644 --- a/src/test/java/com/fasterxml/jackson/annotation/JsonIncludePropertiesTest.java +++ b/src/test/java/com/fasterxml/jackson/annotation/JsonIncludePropertiesTest.java @@ -94,6 +94,14 @@ public void testFromAnnotationOrdered() assertEquals(Boolean.TRUE, v.getOrdered()); } + @Test + public void testHashCodeIncludesContents() { + JsonIncludeProperties.Value v1 = new JsonIncludeProperties.Value(_set("a", "b"), null); + JsonIncludeProperties.Value v2 = new JsonIncludeProperties.Value(_set("c", "d"), null); + assertNotEquals(v1, v2); + assertNotEquals(v1.hashCode(), v2.hashCode()); + } + @Test public void testOrderedEquality() { diff --git a/src/test/java/com/fasterxml/jackson/annotation/JsonIncludeTest.java b/src/test/java/com/fasterxml/jackson/annotation/JsonIncludeTest.java index b48cf7fe..c09ddbca 100644 --- a/src/test/java/com/fasterxml/jackson/annotation/JsonIncludeTest.java +++ b/src/test/java/com/fasterxml/jackson/annotation/JsonIncludeTest.java @@ -136,6 +136,30 @@ public void testContentMerge76() assertEquals(JsonInclude.Include.NON_ABSENT, v21.getValueInclusion()); } + @Test + public void testHashCodeIncludesFilters() + { + JsonInclude.Value v1 = new JsonInclude.Value(Include.CUSTOM, Include.CUSTOM, + Integer.class, Long.class); + JsonInclude.Value v2 = new JsonInclude.Value(Include.CUSTOM, Include.CUSTOM, + String.class, Double.class); + assertNotEquals(v1, v2); + assertNotEquals(v1.hashCode(), v2.hashCode()); + } + + // Verify that withOverrides() properly detects content filter changes + @Test + public void testWithOverridesContentFilter() + { + JsonInclude.Value base = new JsonInclude.Value(Include.NON_EMPTY, Include.NON_EMPTY, + null, null); + JsonInclude.Value overrideContentFilter = new JsonInclude.Value( + Include.USE_DEFAULTS, Include.USE_DEFAULTS, null, Long.class); + + JsonInclude.Value merged = base.withOverrides(overrideContentFilter); + assertEquals(Long.class, merged.getContentFilter()); + } + @Test public void testFilters() {