Skip to content

Commit ed351da

Browse files
committed
First half of actual #2800 (java.lang.Record) impl: find Record fields for serialization
1 parent 20182f9 commit ed351da

File tree

4 files changed

+149
-10
lines changed

4 files changed

+149
-10
lines changed

src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ protected POJOPropertiesCollector collectProperties(MapperConfig<?> config,
178178
JavaType type, MixInResolver r, boolean forSerialization)
179179
{
180180
final AnnotatedClass classDef = _resolveAnnotatedClass(config, type, r);
181-
final AccessorNamingStrategy accNaming = config.getAccessorNaming()
182-
.forPOJO(config, classDef);
181+
final AccessorNamingStrategy accNaming = type.isRecordType()
182+
? config.getAccessorNaming().forRecord(config, classDef)
183+
: config.getAccessorNaming().forPOJO(config, classDef);
183184
return constructPropertyCollector(config, classDef, type, forSerialization, accNaming);
184185
}
185186

src/main/java/com/fasterxml/jackson/databind/introspect/DefaultAccessorNamingStrategy.java

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.fasterxml.jackson.databind.introspect;
22

3+
import java.util.HashSet;
4+
import java.util.Set;
5+
36
import com.fasterxml.jackson.databind.AnnotationIntrospector;
47
import com.fasterxml.jackson.databind.BeanDescription;
58
import com.fasterxml.jackson.databind.MapperFeature;
69
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
710
import com.fasterxml.jackson.databind.cfg.MapperConfig;
11+
import com.fasterxml.jackson.databind.jdk14.JDK14Util;
812

913
/**
1014
* Default {@link AccessorNamingStrategy} used by Jackson: to be used either as-is,
@@ -20,19 +24,23 @@ public class DefaultAccessorNamingStrategy
2024

2125
protected final boolean _stdBeanNaming;
2226

27+
protected final String _getterPrefix;
28+
2329
/**
2430
* Prefix used by auto-detected mutators ("setters"): usually "set",
2531
* but differs for builder objects ("with" by default).
2632
*/
2733
protected final String _mutatorPrefix;
2834

2935
protected DefaultAccessorNamingStrategy(MapperConfig<?> config, AnnotatedClass forClass,
30-
String mutatorPrefix) {
36+
String mutatorPrefix, String getterPrefix)
37+
{
3138
_config = config;
3239
_forClass = forClass;
3340

3441
_stdBeanNaming = config.isEnabled(MapperFeature.USE_STD_BEAN_NAMING);
3542
_mutatorPrefix = mutatorPrefix;
43+
_getterPrefix = getterPrefix;
3644
}
3745

3846
@Override
@@ -52,7 +60,7 @@ public String findNameForIsGetter(AnnotatedMethod am, String name)
5260
@Override
5361
public String findNameForRegularGetter(AnnotatedMethod am, String name)
5462
{
55-
if (name.startsWith("get")) {
63+
if ((_getterPrefix != null) && name.startsWith(_getterPrefix)) {
5664
// 16-Feb-2009, tatu: To handle [JACKSON-53], need to block CGLib-provided
5765
// method "getCallbacks". Not sure of exact safe criteria to get decent
5866
// coverage without false matches; but for now let's assume there is
@@ -68,16 +76,16 @@ public String findNameForRegularGetter(AnnotatedMethod am, String name)
6876
}
6977
}
7078
return _stdBeanNaming
71-
? stdManglePropertyName(name, 3)
72-
: legacyManglePropertyName(name, 3);
79+
? stdManglePropertyName(name, _getterPrefix.length())
80+
: legacyManglePropertyName(name, _getterPrefix.length());
7381
}
7482
return null;
7583
}
7684

7785
@Override
7886
public String findNameForMutator(AnnotatedMethod am, String name)
7987
{
80-
if (name.startsWith(_mutatorPrefix)) {
88+
if ((_mutatorPrefix != null) && name.startsWith(_mutatorPrefix)) {
8189
return _stdBeanNaming
8290
? stdManglePropertyName(name, _mutatorPrefix.length())
8391
: legacyManglePropertyName(name, _mutatorPrefix.length());
@@ -210,7 +218,8 @@ public final static class Provider
210218
@Override
211219
public AccessorNamingStrategy forPOJO(MapperConfig<?> config, AnnotatedClass targetClass)
212220
{
213-
return new DefaultAccessorNamingStrategy(config, targetClass, "set");
221+
return new DefaultAccessorNamingStrategy(config, targetClass,
222+
"set", "get");
214223
}
215224

216225
@Override
@@ -220,13 +229,49 @@ public AccessorNamingStrategy forBuilder(MapperConfig<?> config,
220229
AnnotationIntrospector ai = config.isAnnotationProcessingEnabled() ? config.getAnnotationIntrospector() : null;
221230
JsonPOJOBuilder.Value builderConfig = (ai == null) ? null : ai.findPOJOBuilderConfig(builderClass);
222231
String mutatorPrefix = (builderConfig == null) ? JsonPOJOBuilder.DEFAULT_WITH_PREFIX : builderConfig.withPrefix;
223-
return new DefaultAccessorNamingStrategy(config, builderClass, mutatorPrefix);
232+
return new DefaultAccessorNamingStrategy(config, builderClass,
233+
mutatorPrefix, "get");
224234
}
225235

226236
@Override
227237
public AccessorNamingStrategy forRecord(MapperConfig<?> config, AnnotatedClass recordClass)
228238
{
229-
return new DefaultAccessorNamingStrategy(config, recordClass, "set");
239+
return new RecordNaming(config, recordClass);
240+
}
241+
}
242+
243+
public static class RecordNaming
244+
extends DefaultAccessorNamingStrategy
245+
{
246+
/**
247+
* Names of actual Record fields from definition; auto-detected.
248+
*/
249+
protected final Set<String> _fieldNames;
250+
251+
public RecordNaming(MapperConfig<?> config, AnnotatedClass forClass) {
252+
super(config, forClass,
253+
// no setters for (immutable) Records:
254+
null,
255+
// trickier: regular fields are ok (handled differently), but should
256+
// we also allow getter discovery? For now let's do so
257+
"get");
258+
_fieldNames = new HashSet<>();
259+
for (String name : JDK14Util.getRecordFieldNames(forClass.getRawType())) {
260+
_fieldNames.add(name);
261+
}
262+
}
263+
264+
@Override
265+
public String findNameForRegularGetter(AnnotatedMethod am, String name)
266+
{
267+
// By default, field names are un-prefixed, but verify so that we will not
268+
// include "toString()" or additional custom methods (unless latter are
269+
// annotated for inclusion)
270+
if (_fieldNames.contains(name)) {
271+
return name;
272+
}
273+
// but also allow auto-detecting additional getters, if any?
274+
return super.findNameForRegularGetter(am, name);
230275
}
231276
}
232277
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.fasterxml.jackson.databind.jdk14;
2+
3+
import java.lang.reflect.Method;
4+
5+
import com.fasterxml.jackson.databind.util.ClassUtil;
6+
7+
/**
8+
* Helper class to support some of JDK 14 (and later) features
9+
* without Jackson itself being run on (or even built with) Java 14.
10+
* In particular allows better support of {@code java.lang.Record}
11+
* types (see <a href="https://openjdk.java.net/jeps/359">JEP 359</a>).
12+
*
13+
* @since 2.12
14+
*/
15+
public class JDK14Util
16+
{
17+
public static String[] getRecordFieldNames(Class<?> recordType) {
18+
return RecordAccessor.instance().getRecordFieldNames(recordType);
19+
}
20+
21+
static class RecordAccessor {
22+
private final Method RECORD_GET_RECORD_COMPONENTS;
23+
private final Method RECORD_COMPONENT_GET_NAME;
24+
private final Method RECORD_COMPONENT_GET_TYPE;
25+
26+
private final static RecordAccessor INSTANCE;
27+
private final static RuntimeException PROBLEM;
28+
29+
static {
30+
RuntimeException prob = null;
31+
RecordAccessor inst = null;
32+
try {
33+
inst = new RecordAccessor();
34+
} catch (RuntimeException e) {
35+
prob = e;
36+
}
37+
INSTANCE = inst;
38+
PROBLEM = prob;
39+
}
40+
41+
private RecordAccessor() throws RuntimeException {
42+
try {
43+
RECORD_GET_RECORD_COMPONENTS = Class.class.getMethod("getRecordComponents");
44+
Class<?> c = Class.forName("java.lang.reflect.RecordComponent");
45+
RECORD_COMPONENT_GET_NAME = c.getMethod("getName");
46+
RECORD_COMPONENT_GET_TYPE = c.getMethod("getType");
47+
} catch (Exception e) {
48+
throw new RuntimeException(String.format(
49+
"Failed to access Methods needed to support `java.lang.Record`: (%s) %s",
50+
e.getClass().getName(), e.getMessage()), e);
51+
}
52+
}
53+
54+
public static RecordAccessor instance() {
55+
if (PROBLEM != null) {
56+
throw PROBLEM;
57+
}
58+
return INSTANCE;
59+
}
60+
61+
public String[] getRecordFieldNames(Class<?> recordType) throws IllegalArgumentException
62+
{
63+
final Object[] components = recordComponents(recordType);
64+
final String[] names = new String[components.length];
65+
for (int i = 0; i < components.length; i++) {
66+
try {
67+
names[i] = (String) RECORD_COMPONENT_GET_NAME.invoke(components[i]);
68+
} catch (Exception e) {
69+
throw new IllegalArgumentException(String.format(
70+
"Failed to access name of field #%d (of %d) of Record type %s",
71+
i, components.length, ClassUtil.nameOf(recordType)), e);
72+
}
73+
}
74+
return names;
75+
}
76+
77+
protected Object[] recordComponents(Class<?> recordType) throws IllegalArgumentException
78+
{
79+
try {
80+
return (Object[]) RECORD_GET_RECORD_COMPONENTS.invoke(recordType);
81+
} catch (Exception e) {
82+
throw new IllegalArgumentException("Failed to access RecordComponents of type "
83+
+ClassUtil.nameOf(recordType));
84+
}
85+
}
86+
}
87+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
Contains helper class(es) needed to support some of JDK14+
3+
features without requiring running or building using JDK 14.
4+
*/
5+
6+
package com.fasterxml.jackson.databind.jdk14;

0 commit comments

Comments
 (0)