diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java index 810987917d3..5cddaea8769 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/Property.java +++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ public final class Property { } - // package private + // Package private MethodParameter getMethodParameter() { return this.methodParameter; @@ -132,7 +132,7 @@ public final class Property { } - // internal helpers + // Internal helpers private String resolveName() { if (this.readMethod != null) { @@ -142,10 +142,13 @@ public final class Property { } else { index = this.readMethod.getName().indexOf("is"); - if (index == -1) { - throw new IllegalArgumentException("Not a getter method"); + if (index != -1) { + index += 2; + } + else { + // Record-style plain accessor method, e.g. name() + index = 0; } - index += 2; } return StringUtils.uncapitalize(this.readMethod.getName().substring(index)); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index e8cf3dad604..fb5c5f9e052 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -395,6 +395,11 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (method == null) { method = findMethodForProperty(getPropertyMethodSuffixes(propertyName), "is", clazz, mustBeStatic, 0, BOOLEAN_TYPES); + if (method == null) { + // Record-style plain accessor method, e.g. name() + method = findMethodForProperty(new String[] {propertyName}, + "", clazz, mustBeStatic, 0, ANY_TYPES); + } } return method; } @@ -683,12 +688,11 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { return true; } getterName = "is" + StringUtils.capitalize(name); - return getterName.equals(method.getName()); - } - else { - Field field = (Field) this.member; - return field.getName().equals(name); + if (getterName.equals(method.getName())) { + return true; + } } + return this.member.getName().equals(name); } @Override diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index b33027ea403..b5247527eb5 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.testresources.Inventor; import org.springframework.expression.spel.testresources.Person; +import org.springframework.expression.spel.testresources.RecordPerson; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -191,6 +192,20 @@ public class PropertyAccessTests extends AbstractExpressionTests { parser.parseExpression("name='p3'").getValue(context, target)); } + @Test + public void propertyReadOnlyWithRecordStyle() { + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + + Expression expr = parser.parseExpression("name"); + RecordPerson target1 = new RecordPerson("p1"); + assertThat(expr.getValue(context, target1)).isEqualTo("p1"); + RecordPerson target2 = new RecordPerson("p2"); + assertThat(expr.getValue(context, target2)).isEqualTo("p2"); + + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> + parser.parseExpression("name='p3'").getValue(context, target2)); + } + @Test public void propertyReadWrite() { EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index b53e8dbb0ba..993a6450f81 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -4180,6 +4180,13 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(expression.getValue(tc)).isEqualTo("value4"); assertCanCompile(expression); assertThat(expression.getValue(tc)).isEqualTo("value4"); + + // record-style accessor + expression = parser.parseExpression("strawberry"); + assertCantCompile(expression); + assertThat(expression.getValue(tc)).isEqualTo("value5"); + assertCanCompile(expression); + assertThat(expression.getValue(tc)).isEqualTo("value5"); } @Test @@ -4553,23 +4560,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { Object v = expression.getValue(ctx,holder); assertThat(v).isEqualTo("abc"); - // // time it interpreted - // long stime = System.currentTimeMillis(); - // for (int i = 0; i < 100000; i++) { - // v = expression.getValue(ctx,holder); - // } - // System.out.println((System.currentTimeMillis() - stime)); - assertCanCompile(expression); v = expression.getValue(ctx,holder); assertThat(v).isEqualTo("abc"); - - // // time it compiled - // stime = System.currentTimeMillis(); - // for (int i = 0; i < 100000; i++) { - // v = expression.getValue(ctx,holder); - // } - // System.out.println((System.currentTimeMillis() - stime)); } @Test @@ -4985,13 +4978,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(fast.compileExpression()).isTrue(); r.setValue2(null); // try the numbers 0,1,2,null - for (int i=0;i<4;i++) { - r.setValue(i<3?i:null); + for (int i = 0; i < 4; i++) { + r.setValue(i < 3 ? i : null); boolean slowResult = (Boolean)slow.getValue(ctx); boolean fastResult = (Boolean)fast.getValue(ctx); - // System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult); - assertThat(fastResult).as(" Differing results: expression="+expressionText+ - " value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult); + assertThat(fastResult).as("Differing results: expression=" + expressionText + + " value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult); } } @@ -5002,13 +4994,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(fast.compileExpression()).isTrue(); Reg r = (Reg)ctx.getRootObject().getValue(); // try the numbers 0,1,2,null - for (int i=0;i<4;i++) { - r.setValue(i<3?i:null); + for (int i = 0; i < 4; i++) { + r.setValue(i < 3 ? i : null); boolean slowResult = (Boolean)slow.getValue(ctx); boolean fastResult = (Boolean)fast.getValue(ctx); - // System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult); - assertThat(fastResult).as(" Differing results: expression="+expressionText+ - " value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult); + assertThat(fastResult).as("Differing results: expression=" + expressionText + + " value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult); } } @@ -5839,7 +5830,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { public String orange = "value1"; public static String apple = "value2"; - public long peach = 34L; public String getBanana() { @@ -5849,6 +5839,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { public static String getPlum() { return "value4"; } + + public String strawberry() { + return "value5"; + } } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/RecordPerson.java b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/RecordPerson.java new file mode 100644 index 00000000000..f4f35188082 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/RecordPerson.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel.testresources; + +public class RecordPerson { + + private String name; + + private Company company; + + public RecordPerson(String name) { + this.name = name; + } + + public RecordPerson(String name, Company company) { + this.name = name; + this.company = company; + } + + public String name() { + return name; + } + + public Company company() { + return company; + } + +}