1 package com.fasterxml.jackson.databind.introspect; 2 3 import com.fasterxml.jackson.annotation.JsonCreator; 4 import com.fasterxml.jackson.databind.*; 5 import com.fasterxml.jackson.databind.cfg.MapperConfig; 6 7 /** 8 * Tests Scala-style JVM naming patterns for properties. 9 * 10 * Scala uses identifiers that are legal JVM names, but not legal Java names: 11 * 12 * <ul> 13 * <li><code>prop␣</code> (trailing space) for fields</li> 14 * <li><code>prop</code> for getters</li> 15 * <li><code>prop_=</code> for setters</li> 16 * </ul> 17 * 18 * Scala sources turn property accesses into method calls in most cases; the 19 * backing field and the particulars of the method names are implementation details. 20 * 21 * Since I can't reproduce them in Java, I've substituted legal but uncommonly 22 * used characters as placeholders. 23 */ 24 public class TestScalaLikeImplicitProperties extends BaseMapTest 25 { 26 static class NameMangler extends JacksonAnnotationIntrospector 27 { 28 private static final long serialVersionUID = 1L; 29 30 @Override findImplicitPropertyName(AnnotatedMember member)31 public String findImplicitPropertyName(AnnotatedMember member) { 32 String name = null; 33 if (member instanceof AnnotatedField) { 34 name = member.getName(); 35 if (name.endsWith("‿")) { 36 return name.substring(0, name.length()-1); 37 } 38 } else if (member instanceof AnnotatedMethod) { 39 name = member.getName(); 40 if (name.endsWith("_⁀")) { 41 return name.substring(0, name.length()-2); 42 } 43 if (!name.startsWith("get") && !name.startsWith("set")) { 44 return name; 45 } 46 } else if (member instanceof AnnotatedParameter) { 47 // A placeholder for legitimate property name detection 48 // such as what the JDK8 module provides 49 return "prop"; 50 } 51 return null; 52 } 53 54 /* Deprecated since 2.9 55 @Override 56 public boolean hasCreatorAnnotation(Annotated a) { 57 return (a instanceof AnnotatedConstructor); 58 } 59 */ 60 61 @Override findCreatorAnnotation(MapperConfig<?> config, Annotated a)62 public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) { 63 // A placeholder for legitimate creator detection. 64 // In Scala, all primary constructors should be creators, 65 // but I can't obtain a reference to the AnnotatedClass from the 66 // AnnotatedConstructor, so it's simulated here. 67 return (a instanceof AnnotatedConstructor) 68 ? JsonCreator.Mode.DEFAULT : null; 69 } 70 } 71 72 static class ValProperty 73 { 74 private final String prop‿; prop()75 public String prop() { return prop‿; } 76 ValProperty(String prop)77 public ValProperty(String prop) { 78 prop‿ = prop; 79 } 80 } 81 82 static class ValWithBeanProperty 83 { 84 private final String prop‿; prop()85 public String prop() { return prop‿; } getProp()86 public String getProp() { return prop‿; } 87 ValWithBeanProperty(String prop)88 public ValWithBeanProperty(String prop) { 89 prop‿ = prop; 90 } 91 } 92 93 static class VarProperty 94 { 95 private String prop‿; prop()96 public String prop() { return prop‿; } prop_�(String p)97 public void prop_⁀(String p) { prop‿ = p; } 98 VarProperty(String prop)99 public VarProperty(String prop) { 100 prop‿ = prop; 101 } 102 } 103 104 static class VarWithBeanProperty 105 { 106 private String prop‿; prop()107 public String prop() { return prop‿; } prop_�(String p)108 public void prop_⁀(String p) { prop‿ = p; } getProp()109 public String getProp() { return prop‿; } setProp(String p)110 public void setProp(String p) { prop‿ = p; } 111 VarWithBeanProperty(String prop)112 public VarWithBeanProperty(String prop) { 113 prop‿ = prop; 114 } 115 } 116 117 static class GetterSetterProperty 118 { 119 // Different name to represent an arbitrary implementation, not necessarily local to this class. 120 private String _prop_impl = "get/set"; prop()121 public String prop() { return _prop_impl; } prop_�(String p)122 public void prop_⁀(String p) { _prop_impl = p; } 123 124 // Getter/Setters are typically not in the constructor because they are implemented 125 // by the end user, not the compiler. They should be detected similar to 'bean-style' 126 // getProp/setProp pairs. 127 } 128 129 /* 130 /********************************************************** 131 /* Test methods 132 /********************************************************** 133 */ 134 testValProperty()135 public void testValProperty() throws Exception 136 { 137 ObjectMapper m = manglingMapper(); 138 139 assertEquals("{\"prop\":\"val\"}", m.writeValueAsString(new ValProperty("val"))); 140 } 141 testValWithBeanProperty()142 public void testValWithBeanProperty() throws Exception 143 { 144 ObjectMapper m = manglingMapper(); 145 146 assertEquals("{\"prop\":\"val\"}", m.writeValueAsString(new ValWithBeanProperty("val"))); 147 } 148 149 testVarProperty()150 public void testVarProperty() throws Exception 151 { 152 ObjectMapper m = manglingMapper(); 153 154 assertEquals("{\"prop\":\"var\"}", m.writeValueAsString(new VarProperty("var"))); 155 VarProperty result = m.readValue("{\"prop\":\"read\"}", VarProperty.class); 156 assertEquals("read", result.prop()); 157 } 158 159 testVarWithBeanProperty()160 public void testVarWithBeanProperty() throws Exception 161 { 162 ObjectMapper m = manglingMapper(); 163 164 assertEquals("{\"prop\":\"var\"}", m.writeValueAsString(new VarWithBeanProperty("var"))); 165 VarWithBeanProperty result = m.readValue("{\"prop\":\"read\"}", VarWithBeanProperty.class); 166 assertEquals("read", result.prop()); 167 } 168 169 testGetterSetterProperty()170 public void testGetterSetterProperty() throws Exception 171 { 172 ObjectMapper m = manglingMapper(); 173 174 assertEquals("{\"prop\":\"get/set\"}", m.writeValueAsString(new GetterSetterProperty())); 175 GetterSetterProperty result = m.readValue("{\"prop\":\"read\"}", GetterSetterProperty.class); 176 assertEquals("read", result.prop()); 177 } 178 179 /* 180 /********************************************************** 181 /* Helper methods 182 /********************************************************** 183 */ 184 manglingMapper()185 private ObjectMapper manglingMapper() 186 { 187 ObjectMapper m = new ObjectMapper(); 188 m.setAnnotationIntrospector(new NameMangler()); 189 return m; 190 } 191 } 192