Skip to content

Commit c381ff7

Browse files
committed
Start work on #2800
1 parent 0a8157c commit c381ff7

File tree

9 files changed

+324
-158
lines changed

9 files changed

+324
-158
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.fasterxml.jackson.databind.introspect;
2+
3+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
4+
5+
/**
6+
* API for handlers used to "mangle" names of "getter" and "setter" methods to
7+
* find implicit property names.
8+
*
9+
* @since 2.12
10+
*/
11+
public abstract class AccessorNamingStrategy
12+
{
13+
/**
14+
* Method called to find whether given method would be considered an "is-getter"
15+
* method in context of type introspected.
16+
*<p>
17+
* Note that signature acceptability has already been checked (no arguments,
18+
* has return value) but NOT the specific limitation that return type should
19+
* be of boolean type -- implementation should apply latter check, if so desired
20+
* (some languages may use different criteria).
21+
*/
22+
public abstract String findNameForIsGetter(AnnotatedMethod am, String name);
23+
24+
/**
25+
* Method called to find whether given method would be considered a "regular"
26+
* getter method in context of type introspected.
27+
*<p>
28+
* Note that signature acceptability has already been checked (no arguments,
29+
* does have a return value) by caller.
30+
*<p>
31+
* Note that this method MAY be called for potential "is-getter" methods too
32+
* (before {@link #findNameForIsGetter})
33+
*/
34+
public abstract String findNameForRegularGetter(AnnotatedMethod am, String name);
35+
36+
/**
37+
* Method called to find whether given method would be considered a "mutator"
38+
* (usually setter, but for builders "with-method" or similar) in context of
39+
* type introspected.
40+
*<p>
41+
* Note that signature acceptability has already been checked (exactly one parameter)
42+
* by caller.
43+
*/
44+
public abstract String findNameForMutator(AnnotatedMethod am, String name);
45+
46+
// FunctionalInterface
47+
/**
48+
* Interface for provider (factory) for constructing {@link AccessorNamingStrategy} for given
49+
* type.
50+
*/
51+
public abstract static class Provider
52+
implements java.io.Serializable // since one configured with Mapper/MapperBuilder
53+
{
54+
private static final long serialVersionUID = 1L;
55+
56+
public abstract AccessorNamingStrategy forPOJO(MapperConfig<?> config, AnnotatedClass ac,
57+
String mutatorPrefix);
58+
59+
public abstract AccessorNamingStrategy forBuilder(MapperConfig<?> config,
60+
AnnotatedClass builderClass, AnnotatedClass targetClass, String mutatorPrefix);
61+
}
62+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package com.fasterxml.jackson.databind.introspect;
2+
3+
import com.fasterxml.jackson.databind.MapperFeature;
4+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
5+
import com.fasterxml.jackson.databind.util.ClassUtil;
6+
7+
/**
8+
* Default {@link AccessorNamingStrategy} used by Jackson: to be used either as-is,
9+
* or as base-class with overrides.
10+
*
11+
* @since 2.12
12+
*/
13+
public class DefaultAccessorNamingStrategy
14+
extends AccessorNamingStrategy
15+
{
16+
protected final MapperConfig<?> _config;
17+
protected final AnnotatedClass _forClass;
18+
19+
protected final boolean _stdBeanNaming;
20+
21+
/**
22+
* Prefix used by auto-detected mutators ("setters"): usually "set",
23+
* but differs for builder objects ("with" by default).
24+
*/
25+
protected final String _mutatorPrefix;
26+
27+
protected DefaultAccessorNamingStrategy(MapperConfig<?> config, AnnotatedClass forClass,
28+
String mutatorPrefix) {
29+
_config = config;
30+
_forClass = forClass;
31+
32+
_stdBeanNaming = config.isEnabled(MapperFeature.USE_STD_BEAN_NAMING);
33+
_mutatorPrefix = mutatorPrefix;
34+
}
35+
36+
@Override
37+
public String findNameForIsGetter(AnnotatedMethod am, String name)
38+
{
39+
final Class<?> rt = am.getRawType();
40+
if (rt == Boolean.class || rt == Boolean.TYPE) {
41+
if (name.startsWith("is")) { // plus, must return a boolean
42+
return _stdBeanNaming
43+
? stdManglePropertyName(name, 2)
44+
: legacyManglePropertyName(name, 2);
45+
}
46+
}
47+
return null;
48+
}
49+
50+
@Override
51+
public String findNameForRegularGetter(AnnotatedMethod am, String name)
52+
{
53+
if (name.startsWith("get")) {
54+
// 16-Feb-2009, tatu: To handle [JACKSON-53], need to block CGLib-provided
55+
// method "getCallbacks". Not sure of exact safe criteria to get decent
56+
// coverage without false matches; but for now let's assume there is
57+
// no reason to use any such getter from CGLib.
58+
if ("getCallbacks".equals(name)) {
59+
if (isCglibGetCallbacks(am)) {
60+
return null;
61+
}
62+
} else if ("getMetaClass".equals(name)) {
63+
// 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference
64+
if (isGroovyMetaClassGetter(am)) {
65+
return null;
66+
}
67+
}
68+
return _stdBeanNaming
69+
? stdManglePropertyName(name, 3)
70+
: legacyManglePropertyName(name, 3);
71+
}
72+
return null;
73+
}
74+
75+
@Override
76+
public String findNameForMutator(AnnotatedMethod am, String name)
77+
{
78+
if (name.startsWith(_mutatorPrefix)) {
79+
return _stdBeanNaming
80+
? stdManglePropertyName(name, _mutatorPrefix.length())
81+
: legacyManglePropertyName(name, _mutatorPrefix.length());
82+
}
83+
return null;
84+
}
85+
86+
/*
87+
/**********************************************************************
88+
/* Name-mangling methods copied in 2.12 from "BeanUtil"
89+
/**********************************************************************
90+
*/
91+
92+
/**
93+
* Method called to figure out name of the property, given
94+
* corresponding suggested name based on a method or field name.
95+
*
96+
* @param basename Name of accessor/mutator method, not including prefix
97+
* ("get"/"is"/"set")
98+
*/
99+
protected static String legacyManglePropertyName(final String basename, final int offset)
100+
{
101+
final int end = basename.length();
102+
if (end == offset) { // empty name, nope
103+
return null;
104+
}
105+
// next check: is the first character upper case? If not, return as is
106+
char c = basename.charAt(offset);
107+
char d = Character.toLowerCase(c);
108+
109+
if (c == d) {
110+
return basename.substring(offset);
111+
}
112+
// otherwise, lower case initial chars. Common case first, just one char
113+
StringBuilder sb = new StringBuilder(end - offset);
114+
sb.append(d);
115+
int i = offset+1;
116+
for (; i < end; ++i) {
117+
c = basename.charAt(i);
118+
d = Character.toLowerCase(c);
119+
if (c == d) {
120+
sb.append(basename, i, end);
121+
break;
122+
}
123+
sb.append(d);
124+
}
125+
return sb.toString();
126+
}
127+
128+
protected static String stdManglePropertyName(final String basename, final int offset)
129+
{
130+
final int end = basename.length();
131+
if (end == offset) { // empty name, nope
132+
return null;
133+
}
134+
// first: if it doesn't start with capital, return as-is
135+
char c0 = basename.charAt(offset);
136+
char c1 = Character.toLowerCase(c0);
137+
if (c0 == c1) {
138+
return basename.substring(offset);
139+
}
140+
// 17-Dec-2014, tatu: As per [databind#653], need to follow more
141+
// closely Java Beans spec; specifically, if two first are upper-case,
142+
// then no lower-casing should be done.
143+
if ((offset + 1) < end) {
144+
if (Character.isUpperCase(basename.charAt(offset+1))) {
145+
return basename.substring(offset);
146+
}
147+
}
148+
StringBuilder sb = new StringBuilder(end - offset);
149+
sb.append(c1);
150+
sb.append(basename, offset+1, end);
151+
return sb.toString();
152+
}
153+
154+
/*
155+
/**********************************************************************
156+
/* Legacy methods copied in 2.12 from "BeanUtil" -- are these still needed?
157+
/**********************************************************************
158+
*/
159+
160+
// This method was added to address the need to weed out CGLib-injected
161+
// "getCallbacks" method.
162+
// At this point caller has detected a potential getter method with
163+
// name "getCallbacks" and we need to determine if it is indeed injected
164+
// by Cglib. We do this by verifying that the result type is "net.sf.cglib.proxy.Callback[]"
165+
private static boolean isCglibGetCallbacks(AnnotatedMethod am)
166+
{
167+
Class<?> rt = am.getRawType();
168+
// Ok, first: must return an array type
169+
if (rt.isArray()) {
170+
// And that type needs to be "net.sf.cglib.proxy.Callback".
171+
// Theoretically could just be a type that implements it, but
172+
// for now let's keep things simple, fix if need be.
173+
174+
Class<?> compType = rt.getComponentType();
175+
// Actually, let's just verify it's a "net.sf.cglib.*" class/interface
176+
String pkgName = ClassUtil.getPackageName(compType);
177+
if (pkgName != null) {
178+
if (pkgName.contains(".cglib")) {
179+
return pkgName.startsWith("net.sf.cglib")
180+
// also, as per [JACKSON-177]
181+
|| pkgName.startsWith("org.hibernate.repackage.cglib")
182+
// and [core#674]
183+
|| pkgName.startsWith("org.springframework.cglib");
184+
}
185+
}
186+
}
187+
return false;
188+
}
189+
190+
// Another helper method to deal with Groovy's problematic metadata accessors
191+
private static boolean isGroovyMetaClassGetter(AnnotatedMethod am)
192+
{
193+
String pkgName = ClassUtil.getPackageName(am.getRawType());
194+
return (pkgName != null) && pkgName.startsWith("groovy.lang");
195+
}
196+
197+
/*
198+
/**********************************************************************
199+
/* Standard Provider implementation
200+
/**********************************************************************
201+
*/
202+
203+
/**
204+
* Provider for {@link DefaultAccessorNamingStrategy}.
205+
*/
206+
protected static class Provider
207+
extends AccessorNamingStrategy.Provider
208+
implements java.io.Serializable
209+
{
210+
private static final long serialVersionUID = 1L;
211+
212+
@Override
213+
public AccessorNamingStrategy forPOJO(MapperConfig<?> config, AnnotatedClass ac,
214+
String mutatorPrefix) {
215+
return new DefaultAccessorNamingStrategy(config, ac, mutatorPrefix);
216+
}
217+
218+
@Override
219+
public AccessorNamingStrategy forBuilder(MapperConfig<?> config, AnnotatedClass builderClass,
220+
AnnotatedClass targetClass, String mutatorPrefix)
221+
{
222+
return new DefaultAccessorNamingStrategy(config, builderClass, mutatorPrefix);
223+
}
224+
225+
}
226+
}

0 commit comments

Comments
 (0)