Skip to content

Commit cf5f727

Browse files
timo-aBerlizov
andauthored
Add support for Record-like getters (#269)
Co-authored-by: Eugeen Berlizov <[email protected]>
1 parent f5d3e30 commit cf5f727

File tree

11 files changed

+301
-24
lines changed

11 files changed

+301
-24
lines changed

src/main/java/org/assertj/assertions/generator/util/ClassUtil.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import static com.google.common.base.Preconditions.checkNotNull;
1616
import static com.google.common.collect.Lists.newArrayList;
17+
import static com.google.common.collect.Sets.newHashSet;
1718
import static com.google.common.collect.Sets.newLinkedHashSet;
1819
import static java.lang.reflect.Modifier.isPublic;
1920
import static java.lang.reflect.Modifier.isStatic;
@@ -76,6 +77,8 @@ public class ClassUtil {
7677
private static final Comparator<Method> GETTER_COMPARATOR = Comparator.comparing(Method::getName);
7778
public static final Package JAVA_LANG_PACKAGE = Object.class.getPackage();
7879
private static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
80+
private static final Set<String> FORBIDDEN_ENUM_GETTER_NAMES = newHashSet("name", "values", "ordinal");
81+
private static final Set<String> FORBIDDEN_GETTER_NAMES = newHashSet("hashCode", "toString");
7982

8083
/**
8184
* Call {@link #collectClasses(ClassLoader, String...)} with <code>Thread.currentThread().getContextClassLoader()
@@ -312,10 +315,48 @@ public static boolean inheritsCollectionOrIsIterable(Class<?> returnType) {
312315
return Collection.class.isAssignableFrom(returnType) || Iterable.class.equals(returnType);
313316
}
314317

315-
public static boolean isStandardGetter(Method method) {
316-
return isValidStandardGetterName(method.getName())
317-
&& !Void.TYPE.equals(method.getReturnType())
318-
&& method.getParameterTypes().length == 0;
318+
public static boolean isGetter(Method method) {
319+
return !Void.TYPE.equals(method.getReturnType())
320+
&& method.getParameterTypes().length == 0
321+
&& !isForbiddenGetter(method)
322+
&& !isReturnGeneric(method);
323+
}
324+
325+
private static boolean isForbiddenGetter(Method method) {
326+
return FORBIDDEN_GETTER_NAMES.contains(method.getName())
327+
|| isForbiddenEnumGetter(method);
328+
}
329+
330+
private static boolean isForbiddenEnumGetter(Method method) {
331+
Class<?> declaringClass = method.getDeclaringClass();
332+
boolean isEnum = declaringClass.isEnum() || declaringClass == java.lang.Enum.class;
333+
return isEnum && FORBIDDEN_ENUM_GETTER_NAMES.contains(method.getName());
334+
}
335+
336+
private static boolean isReturnGeneric(Method method) {
337+
Type returnType = method.getGenericReturnType();
338+
return containsGenericType(returnType);
339+
}
340+
341+
private static boolean containsGenericType(Type type) {
342+
return isGenericType(type)
343+
|| isParameterizedByGenericType(type);
344+
}
345+
346+
private static boolean isGenericType(Type type) {
347+
return type instanceof java.lang.reflect.TypeVariable;
348+
}
349+
350+
private static boolean isParameterizedByGenericType(Type type) {
351+
if (type instanceof ParameterizedType) {
352+
ParameterizedType parameterizedType = (ParameterizedType) type;
353+
for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
354+
if (containsGenericType(actualTypeArgument)) {
355+
return true;
356+
}
357+
}
358+
}
359+
return false;
319360
}
320361

321362
public static boolean isPredicate(Method method) {
@@ -389,11 +430,6 @@ public static boolean isValidGetterName(String methodName) {
389430
PREDICATE_PREFIXES = Collections.unmodifiableMap(map);
390431
}
391432

392-
private static boolean isValidStandardGetterName(String name) {
393-
Matcher m = PREFIX_PATTERN.matcher(name);
394-
return m.find() && m.group().equals(GET_PREFIX);
395-
}
396-
397433
public static String getPredicatePrefix(String name) {
398434
Matcher m = PREFIX_PATTERN.matcher(name);
399435
return m.find() ? m.group() : null;
@@ -438,7 +474,7 @@ && isGetter(method, includeAnnotations, isClassAnnotated)) {
438474
}
439475

440476
private static boolean isGetter(Method method, Set<Class<?>> includeAnnotations, boolean isClassAnnotated) {
441-
return isStandardGetter(method)
477+
return isGetter(method)
442478
|| isPredicate(method)
443479
|| isAnnotated(method, includeAnnotations, isClassAnnotated);
444480
}
@@ -772,10 +808,12 @@ private static String booleanPropertyOf(String memberName) {
772808
return memberName;
773809
}
774810

775-
private static String getterProperty(String memberName) {
811+
public static String getterProperty(String memberName) {
776812
if (memberName.startsWith(GET_PREFIX)) {
777813
String propertyWithCapitalLetter = removeStart(memberName, GET_PREFIX);
778-
return uncapitalize(propertyWithCapitalLetter);
814+
if (!propertyWithCapitalLetter.isEmpty()) {
815+
return uncapitalize(propertyWithCapitalLetter);
816+
}
779817
}
780818
return memberName;
781819
}

src/test/java/org/assertj/assertions/generator/data/nba/Player.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void setDisabled(boolean isDisabled) {
6060
this.isDisabled = isDisabled;
6161
}
6262

63-
public Name getName() {
63+
public Name name() {
6464
return name;
6565
}
6666

src/test/java/org/assertj/assertions/generator/description/GetterDescriptionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void should_create_valid_typename_from_class_for_user_defined_type_in_same_packa
5555

5656
@Test
5757
void should_create_valid_typename_from_class_for_user_defined_type_in_different_package() throws Exception {
58-
getterDescription = new GetterDescription("name", PLAYER_TYPE_DESCRIPTION, Player.class.getMethod("getName"));
58+
getterDescription = new GetterDescription("name", PLAYER_TYPE_DESCRIPTION, Player.class.getMethod("name"));
5959
assertThat(getterDescription.getName()).isEqualTo("name");
6060
assertThat(getterDescription.getTypeName()).isEqualTo("org.assertj.assertions.generator.data.Name");
6161
assertThat(getterDescription.getFullyQualifiedTypeName()).isEqualTo("org.assertj.assertions.generator.data.Name");

src/test/java/org/assertj/assertions/generator/description/converter/ClassToClassDescriptionConverterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void should_build_player_class_description() {
5555
assertThat(classDescription.getFullyQualifiedClassName()).isEqualTo("org.assertj.assertions.generator.data.nba.Player");
5656
assertThat(classDescription.getFullyQualifiedOuterClassName()).isEqualTo("org.assertj.assertions.generator.data.nba.Player");
5757
assertThat(classDescription.getFullyQualifiedClassNameWithoutGenerics()).isEqualTo(classDescription.getFullyQualifiedClassName());
58-
assertThat(classDescription.getGettersDescriptions()).hasSize(19);
58+
assertThat(classDescription.getGettersDescriptions()).hasSize(21);
5959
assertThat(classDescription.getAssertClassName()).isEqualTo("PlayerAssert");
6060
assertThat(classDescription.getAssertClassFilename()).isEqualTo("PlayerAssert.java");
6161
assertThat(classDescription.getFullyQualifiedAssertClassName()).isEqualTo("org.assertj.assertions.generator.data.nba.PlayerAssert");

src/test/java/org/assertj/assertions/generator/util/ClassUtilTest.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@
6363
import static org.assertj.assertions.generator.util.ClassUtil.getPredicatePrefix;
6464
import static org.assertj.assertions.generator.util.ClassUtil.getSimpleNameWithOuterClass;
6565
import static org.assertj.assertions.generator.util.ClassUtil.getterMethodsOf;
66+
import static org.assertj.assertions.generator.util.ClassUtil.getterProperty;
6667
import static org.assertj.assertions.generator.util.ClassUtil.inheritsCollectionOrIsIterable;
6768
import static org.assertj.assertions.generator.util.ClassUtil.isBoolean;
6869
import static org.assertj.assertions.generator.util.ClassUtil.isInnerPackageOf;
6970
import static org.assertj.assertions.generator.util.ClassUtil.isJavaLangType;
7071
import static org.assertj.assertions.generator.util.ClassUtil.isPredicate;
71-
import static org.assertj.assertions.generator.util.ClassUtil.isStandardGetter;
72+
import static org.assertj.assertions.generator.util.ClassUtil.isGetter;
7273
import static org.assertj.assertions.generator.util.ClassUtil.isValidGetterName;
7374
import static org.assertj.assertions.generator.util.ClassUtil.propertyNameOf;
7475
import static org.assertj.assertions.generator.util.ClassUtil.resolveTypeNameInPackage;
@@ -179,14 +180,15 @@ void should_return_true_if_class_implements_iterable_interface() {
179180

180181
@Test
181182
void should_return_true_if_method_is_a_standard_getter() throws Exception {
182-
assertThat(isStandardGetter(Player.class.getMethod("getTeam", NO_PARAMS))).isTrue();
183+
assertThat(isGetter(Player.class.getMethod("getTeam", NO_PARAMS))).isTrue();
184+
assertThat(isGetter(Player.class.getMethod("isRookie", NO_PARAMS))).isTrue();
185+
assertThat(isGetter(Player.class.getMethod("name", NO_PARAMS))).isTrue();
183186
}
184187

185188
@Test
186189
void should_return_false_if_method_is_not_a_standard_getter() throws Exception {
187-
assertThat(isStandardGetter(Player.class.getMethod("isRookie", NO_PARAMS))).isFalse();
188-
assertThat(isStandardGetter(Player.class.getMethod("getVoid", NO_PARAMS))).isFalse();
189-
assertThat(isStandardGetter(Player.class.getMethod("getWithParam", String.class))).isFalse();
190+
assertThat(isGetter(Player.class.getMethod("getVoid", NO_PARAMS))).isFalse();
191+
assertThat(isGetter(Player.class.getMethod("getWithParam", String.class))).isFalse();
190192
}
191193

192194
@Test
@@ -264,6 +266,13 @@ void should_not_return_inherited_getters_methods() throws Exception {
264266
.doesNotContain(ArtWork.class.getMethod("getTitle", NO_PARAMS));
265267
}
266268

269+
@Test
270+
void should_return_property_name_from_getter_method_name() throws Exception {
271+
assertThat(getterProperty("getName")).isEqualTo("name");
272+
assertThat(getterProperty("name")).isEqualTo("name");
273+
assertThat(getterProperty("get")).isEqualTo("get");
274+
}
275+
267276
@ParameterizedTest
268277
@FieldSource("NESTED_CLASSES")
269278
void should_return_inner_class_name_with_outer_class_name(NestedClass nestedClass) {

src/test/resources/AbstractAnnotatedClassAssert.expected.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,27 @@ public abstract class AbstractAnnotatedClassAssert<S extends AbstractAnnotatedCl
8686
return myself;
8787
}
8888

89+
/**
90+
* Verifies that the actual AnnotatedClass's thisIsNotAProperty is equal to the given one.
91+
* @param thisIsNotAProperty the given thisIsNotAProperty to compare the actual AnnotatedClass's thisIsNotAProperty to.
92+
* @return this assertion object.
93+
* @throws AssertionError - if the actual AnnotatedClass's thisIsNotAProperty is not equal to the given one.
94+
*/
95+
public S hasThisIsNotAProperty(boolean thisIsNotAProperty) {
96+
// check that actual AnnotatedClass we want to make assertions on is not null.
97+
isNotNull();
98+
99+
// overrides the default error message with a more explicit one
100+
String assertjErrorMessage = "\nExpecting thisIsNotAProperty of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
101+
102+
// check
103+
boolean actualThisIsNotAProperty = actual.thisIsNotAProperty();
104+
if (actualThisIsNotAProperty != thisIsNotAProperty) {
105+
failWithMessage(assertjErrorMessage, actual, thisIsNotAProperty, actualThisIsNotAProperty);
106+
}
107+
108+
// return the current assertion for method chaining
109+
return myself;
110+
}
111+
89112
}

src/test/resources/AbstractPlayerAssert.expected.txt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,52 @@ public abstract class AbstractPlayerAssert<S extends AbstractPlayerAssert<S, A>,
114114
return myself;
115115
}
116116

117+
/**
118+
* Verifies that the actual Player's get is equal to the given one.
119+
* @param get the given get to compare the actual Player's get to.
120+
* @return this assertion object.
121+
* @throws AssertionError - if the actual Player's get is not equal to the given one.
122+
*/
123+
public S hasGet(String get) {
124+
// check that actual Player we want to make assertions on is not null.
125+
isNotNull();
126+
127+
// overrides the default error message with a more explicit one
128+
String assertjErrorMessage = "\nExpecting get of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
129+
130+
// null safe check
131+
String actualGet = actual.get();
132+
if (!Objects.deepEquals(actualGet, get)) {
133+
failWithMessage(assertjErrorMessage, actual, get, actualGet);
134+
}
135+
136+
// return the current assertion for method chaining
137+
return myself;
138+
}
139+
140+
/**
141+
* Verifies that the actual Player's is is equal to the given one.
142+
* @param is the given is to compare the actual Player's is to.
143+
* @return this assertion object.
144+
* @throws AssertionError - if the actual Player's is is not equal to the given one.
145+
*/
146+
public S hasIs(String is) {
147+
// check that actual Player we want to make assertions on is not null.
148+
isNotNull();
149+
150+
// overrides the default error message with a more explicit one
151+
String assertjErrorMessage = "\nExpecting is of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
152+
153+
// null safe check
154+
String actualIs = actual.is();
155+
if (!Objects.deepEquals(actualIs, is)) {
156+
failWithMessage(assertjErrorMessage, actual, is, actualIs);
157+
}
158+
159+
// return the current assertion for method chaining
160+
return myself;
161+
}
162+
117163
/**
118164
* Verifies that the actual Player's name is equal to the given one.
119165
* @param name the given name to compare the actual Player's name to.
@@ -128,7 +174,7 @@ public abstract class AbstractPlayerAssert<S extends AbstractPlayerAssert<S, A>,
128174
String assertjErrorMessage = "\nExpecting name of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
129175

130176
// null safe check
131-
org.assertj.assertions.generator.data.Name actualName = actual.getName();
177+
org.assertj.assertions.generator.data.Name actualName = actual.name();
132178
if (!Objects.deepEquals(actualName, name)) {
133179
failWithMessage(assertjErrorMessage, actual, name, actualName);
134180
}

src/test/resources/AbstractPlayerAssert.generated.in.custom.package.expected.txt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,52 @@ public abstract class AbstractPlayerAssert<S extends AbstractPlayerAssert<S, A>,
115115
return myself;
116116
}
117117

118+
/**
119+
* Verifies that the actual Player's get is equal to the given one.
120+
* @param get the given get to compare the actual Player's get to.
121+
* @return this assertion object.
122+
* @throws AssertionError - if the actual Player's get is not equal to the given one.
123+
*/
124+
public S hasGet(String get) {
125+
// check that actual Player we want to make assertions on is not null.
126+
isNotNull();
127+
128+
// overrides the default error message with a more explicit one
129+
String assertjErrorMessage = "\nExpecting get of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
130+
131+
// null safe check
132+
String actualGet = actual.get();
133+
if (!Objects.deepEquals(actualGet, get)) {
134+
failWithMessage(assertjErrorMessage, actual, get, actualGet);
135+
}
136+
137+
// return the current assertion for method chaining
138+
return myself;
139+
}
140+
141+
/**
142+
* Verifies that the actual Player's is is equal to the given one.
143+
* @param is the given is to compare the actual Player's is to.
144+
* @return this assertion object.
145+
* @throws AssertionError - if the actual Player's is is not equal to the given one.
146+
*/
147+
public S hasIs(String is) {
148+
// check that actual Player we want to make assertions on is not null.
149+
isNotNull();
150+
151+
// overrides the default error message with a more explicit one
152+
String assertjErrorMessage = "\nExpecting is of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
153+
154+
// null safe check
155+
String actualIs = actual.is();
156+
if (!Objects.deepEquals(actualIs, is)) {
157+
failWithMessage(assertjErrorMessage, actual, is, actualIs);
158+
}
159+
160+
// return the current assertion for method chaining
161+
return myself;
162+
}
163+
118164
/**
119165
* Verifies that the actual Player's name is equal to the given one.
120166
* @param name the given name to compare the actual Player's name to.
@@ -129,7 +175,7 @@ public abstract class AbstractPlayerAssert<S extends AbstractPlayerAssert<S, A>,
129175
String assertjErrorMessage = "\nExpecting name of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
130176

131177
// null safe check
132-
org.assertj.assertions.generator.data.Name actualName = actual.getName();
178+
org.assertj.assertions.generator.data.Name actualName = actual.name();
133179
if (!Objects.deepEquals(actualName, name)) {
134180
failWithMessage(assertjErrorMessage, actual, name, actualName);
135181
}

src/test/resources/AnnotatedClassAssert.flat.expected.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,27 @@ public class AnnotatedClassAssert extends AbstractObjectAssert<AnnotatedClassAss
9797
return this;
9898
}
9999

100+
/**
101+
* Verifies that the actual AnnotatedClass's thisIsNotAProperty is equal to the given one.
102+
* @param thisIsNotAProperty the given thisIsNotAProperty to compare the actual AnnotatedClass's thisIsNotAProperty to.
103+
* @return this assertion object.
104+
* @throws AssertionError - if the actual AnnotatedClass's thisIsNotAProperty is not equal to the given one.
105+
*/
106+
public AnnotatedClassAssert hasThisIsNotAProperty(boolean thisIsNotAProperty) {
107+
// check that actual AnnotatedClass we want to make assertions on is not null.
108+
isNotNull();
109+
110+
// overrides the default error message with a more explicit one
111+
String assertjErrorMessage = "\nExpecting thisIsNotAProperty of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>";
112+
113+
// check
114+
boolean actualThisIsNotAProperty = actual.thisIsNotAProperty();
115+
if (actualThisIsNotAProperty != thisIsNotAProperty) {
116+
failWithMessage(assertjErrorMessage, actual, thisIsNotAProperty, actualThisIsNotAProperty);
117+
}
118+
119+
// return the current assertion for method chaining
120+
return this;
121+
}
122+
100123
}

0 commit comments

Comments
 (0)