Skip to content

Commit

Permalink
Fixed #1990
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Apr 20, 2018
1 parent 69f6f52 commit 11808cc
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 34 deletions.
4 changes: 4 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,10 @@ roeltje25@github
infinite loop
(2.9.5)
Freddy Boucher (freddyboucher@github)
* Reported #1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
(2.9.6)
Ondrej Zizka (OndraZizk@github)
* Reported #1999: "Duplicate property" issue should mention which class it complains about
(2.9.6)
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project: jackson-databind
#1964: Failed to specialize `Map` type during serialization where key type
incompatibility overidden via "raw" types
(reported by ptirador@github)
#1990: MixIn `@JsonProperty` for `Object.hashCode()` is ignored
(reported by Freddy B)
#1998: Removing "type" attribute with Mixin not taken in account if
using ObjectMapper.copy()
(reported by SBKila@github)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,40 @@ AnnotatedMethodMap collect(TypeFactory typeFactory, TypeResolutionContext tc,
type.getRawClass(), methods, mixin);
}
// Special case: mix-ins for Object.class? (to apply to ALL classes)
/*
boolean checkJavaLangObject = false;
if (_mixInResolver != null) {
Class<?> mixin = _mixInResolver.findMixInClassFor(Object.class);
if (mixin != null) {
_addMethodMixIns(tc, mainType.getRawClass(), memberMethods, mixin, mixins);
_addMethodMixIns(tc, mainType.getRawClass(), methods, mixin); //, mixins);
checkJavaLangObject = true;
}
}

// Any unmatched mix-ins? Most likely error cases (not matching any method);
// but there is one possible real use case: exposing Object#hashCode
// (alas, Object#getClass can NOT be exposed)
if (_intr != null) {
if (!mixins.isEmpty()) {
Iterator<AnnotatedMethod> it = mixins.iterator();
while (it.hasNext()) {
AnnotatedMethod mixIn = it.next();
try {
Method m = Object.class.getDeclaredMethod(mixIn.getName(), mixIn.getRawParameterTypes());
if (m != null) {
// Since it's from java.lang.Object, no generics, no need for real type context:
AnnotatedMethod am = _constructMethod(tc, m);
_addMixOvers(mixIn.getAnnotated(), am, false);
memberMethods.add(am);
}
} catch (Exception e) { }
// Since we only know of that ONE case, optimize for it
if (checkJavaLangObject && (_intr != null) && !methods.isEmpty()) {
// Could use lookup but probably as fast or faster to traverse
for (Map.Entry<MemberKey,MethodBuilder> entry : methods.entrySet()) {
MemberKey k = entry.getKey();
if (!"hashCode".equals(k.getName()) || (0 != k.argCount())) {
continue;
}
try {
// And with that, we can generate it appropriately
Method m = Object.class.getDeclaredMethod(k.getName());
if (m != null) {
MethodBuilder b = entry.getValue();
b.annotations = collectDefaultAnnotations(b.annotations,
m.getDeclaredAnnotations());
b.method = m;
}
} catch (Exception e) { }
}
}
*/

// And then let's
// And then let's create the lookup map
if (methods.isEmpty()) {
return new AnnotatedMethodMap();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ protected final AnnotationCollector collectFromBundle(AnnotationCollector c, Ann
// // // Defaulting ("mix under")

// Variant that only adds annotations that are missing
protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollector c, Annotation[] anns) {
protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollector c,
Annotation[] anns) {
for (int i = 0, end = anns.length; i < end; ++i) {
Annotation ann = anns[i];
if (!c.isPresent(ann)) {
Expand All @@ -81,7 +82,8 @@ protected final AnnotationCollector collectDefaultAnnotations(AnnotationCollecto
return c;
}

protected final AnnotationCollector collectDefaultFromBundle(AnnotationCollector c, Annotation bundle) {
protected final AnnotationCollector collectDefaultFromBundle(AnnotationCollector c,
Annotation bundle) {
Annotation[] anns = ClassUtil.findClassAnnotations(bundle.annotationType());
for (int i = 0, end = anns.length; i < end; ++i) {
Annotation ann = anns[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@ public MemberKey(String name, Class<?>[] argTypes)
_argTypes = (argTypes == null) ? NO_CLASSES : argTypes;
}

public String getName() {
return _name;
}

public int argCount() {
return _argTypes.length;
}

@Override
public String toString() {
return _name + "(" + _argTypes.length+"-args)";
}

@Override
public int hashCode()
{
public int hashCode() {
return _name.hashCode() + _argTypes.length;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@
public class TestMixinDeserForClass
extends BaseMapTest
{
/*
/**********************************************************
/* Helper bean classes
/**********************************************************
*/

static class BaseClass
{
/* property that is always found; but has lower priority than
Expand All @@ -35,6 +29,21 @@ static class LeafClass
@JsonAutoDetect(setterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE)
interface MixIn { }

// [databind#1990]
public interface HashCodeMixIn {
@Override
@JsonProperty
public int hashCode();
}

public class Bean1990WithoutHashCode {
}

public class Bean1990WithHashCode {
@Override
public int hashCode() { return 13; }
}

/*
/**********************************************************
/* Unit tests
Expand All @@ -48,18 +57,16 @@ public void testClassMixInsTopLevel() throws IOException
LeafClass result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
assertEquals("XXXvalue", result.a);

/* Then with leaf-level mix-in; without (method) auto-detect, should
* use field
*/
// Then with leaf-level mix-in; without (method) auto-detect,
// should use field
m = new ObjectMapper();
m.addMixIn(LeafClass.class, MixIn.class);
result = m.readValue("{\"a\":\"value\"}", LeafClass.class);
assertEquals("value", result.a);
}

/* and then a test for mid-level mixin; should have no effect
* when deserializing leaf (but will if deserializing base class)
*/
// and then a test for mid-level mixin; should have no effect
// when deserializing leaf (but will if deserializing base class)
public void testClassMixInsMidLevel() throws IOException
{
ObjectMapper m = new ObjectMapper();
Expand Down Expand Up @@ -95,4 +102,22 @@ public void testClassMixInsForObjectClass() throws IOException
assertEquals("XXX", result.a);
}
}

// [databind#1990]: can apply mix-in to `Object#hashCode()` to force serialization
public void testHashCodeViaObject() throws Exception
{
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, HashCodeMixIn.class);

// First, with something that overrides hashCode()
assertEquals( "{\"hashCode\":13}",
mapper.writeValueAsString(new Bean1990WithHashCode()));

// and then special case of accessing Object#hashCode()
String prefix = "{\"hashCode\":";
String json = mapper.writeValueAsString(new Bean1990WithoutHashCode());
if (!json.startsWith(prefix)) {
fail("Should start with ["+prefix+"], does not: ["+json+"]");
}
}
}

0 comments on commit 11808cc

Please sign in to comment.