Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ protected List<String> getPropertyAccessNames(ClassMetaData meta) {
* member cannot be managed. Default behavior: For fields, returns the
* field name. For getter methods, returns the minus "get" or "is" with
* the next letter lower-cased. For other methods, returns null.
* <p>
* Note that this deliberately diverges from
* <code>java.beans.Introspector.decapitalize()</code>, which leaves a name
* whose first two characters are both upper case untouched. Here the first
* character is lower-cased unconditionally, so <code>getAWord()</code> and
* <code>getaWord()</code> both yield <code>aWord</code>, so that both
* accessor spellings map to a single property. Changing it would rename
* existing properties and their default column names. See OPENJPA-2993.
*/
public static String getFieldName(Member member) {
if (member instanceof Field) {
Expand Down Expand Up @@ -379,11 +387,21 @@ public static boolean isNormalGetter(Method method) {
* Affirms if the given method matches the following signature
* <code> public boolean isXXX() </code>
* <code> public Boolean isXXX() </code>
* <p>
* The case of the character following the <code>is</code> prefix is
* deliberately not constrained. OpenJPA derives the property name by
* lower-casing the first character after the prefix (see
* {@link #getFieldName}), so <code>isaBoolean()</code> and
* <code>isABoolean()</code> both resolve to the property
* <code>aBoolean</code>. This mirrors {@link #isNormalGetter} and
* <code>PersistenceMetaDataDefaults.isSetter</code>, neither of which
* carries such a requirement; requiring an upper-case character here
* silently dropped JavaBeans-style <code>is&lt;lowercase&gt;()</code>
* properties. See OPENJPA-2993.
*/
public static boolean isBooleanGetter(Method method) {
String methodName = method.getName();
return startsWith(methodName, "is")
&& Character.isUpperCase(methodName.charAt(2))
&& method.getParameterTypes().length == 0
&& isBoolean(method.getReturnType());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.apache.openjpa.meta;

import java.lang.reflect.Method;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
* Pins the accessor recognition and property-name derivation rules of
* {@link AbstractMetaDataDefaults}.
* <p>
* OpenJPA lower-cases the first character after the <code>get</code> /
* <code>is</code> prefix unconditionally, so the JavaBeans-Introspector
* spelling (<code>getaWord()</code>) and the capitalized spelling
* (<code>getAWord()</code>) resolve to the same property. Neither getter form
* may require a particular case for that character. See OPENJPA-2467 and
* OPENJPA-2993.
*/
public class TestAccessorNameDerivation {

private static class Fixture {
public int getaWord() {
return 0;
}

public int getAWord() {
return 0;
}

public int getaCAPITAL() {
return 0;
}

public int getACAPITAL() {
return 0;
}

public boolean isaBoolean() {
return false;
}

public boolean isABoolean() {
return false;
}

public int getA() {
return 0;
}

public int getA1() {
return 0;
}

public String getURL() {
return null;
}

public String isValue() {
return null;
}

public void getaway() {
}
}

private static Method method(String name) {
try {
return Fixture.class.getDeclaredMethod(name);
}
catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException(name, nsme);
}
}

private static String fieldName(String methodName) {
return AbstractMetaDataDefaults.getFieldName(method(methodName));
}

@Test
public void testBothGetterSpellingsYieldTheSameName() {
assertTrue(AbstractMetaDataDefaults.isNormalGetter(method("getaWord")));
assertTrue(AbstractMetaDataDefaults.isNormalGetter(method("getAWord")));
assertEquals("aWord", fieldName("getaWord"));
assertEquals("aWord", fieldName("getAWord"));
assertEquals("aCAPITAL", fieldName("getaCAPITAL"));
assertEquals("aCAPITAL", fieldName("getACAPITAL"));
}

@Test
public void testBothBooleanGetterSpellingsYieldTheSameName() {
// OPENJPA-2993: is<lowercase>() must stay a recognized boolean getter,
// otherwise the property is silently dropped from the metadata.
assertTrue(AbstractMetaDataDefaults.isBooleanGetter(method("isaBoolean")));
assertTrue(AbstractMetaDataDefaults.isBooleanGetter(method("isABoolean")));
assertTrue(AbstractMetaDataDefaults.isGetter(method("isaBoolean"), false));
assertEquals("aBoolean", fieldName("isaBoolean"));
assertEquals("aBoolean", fieldName("isABoolean"));
}

@Test
public void testNonGetters() {
// non-boolean return type
assertFalse(AbstractMetaDataDefaults.isBooleanGetter(method("isValue")));
assertNull(fieldName("isValue"));
// void return type
assertFalse(AbstractMetaDataDefaults.isNormalGetter(method("getaway")));
assertNull(fieldName("getaway"));
}

@Test
public void testShortNames() {
assertEquals("a", fieldName("getA"));
assertEquals("a1", fieldName("getA1"));
}

@Test
public void testDivergenceFromIntrospectorDecapitalize() {
// OPENJPA-2993: OpenJPA deliberately does not implement the JavaBeans
// rule that leaves a name starting with two upper-case characters
// alone. Aligning with it would rename existing properties and their
// default column names, so the divergence is documented, not fixed.
assertEquals("uRL", fieldName("getURL"));
assertEquals("URL", java.beans.Introspector.decapitalize("URL"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;

/**
* Uses the JavaBeans-Introspector / IDE-generated accessor spelling
* (<code>getaWord()</code>, <code>getaCAPITAL()</code>,
* <code>isaBoolean()</code>) for fields whose first character is lower case and
* whose second character is upper case.
* {@link PropertyAccessCapitalizationOldBehavior} is the
* <code>getAWord()</code> / <code>getACAPITAL()</code> /
* <code>isABoolean()</code> twin. Both spellings must resolve to the same
* properties <code>aWord</code>, <code>aCAPITAL</code> and
* <code>aBoolean</code>, because
* <code>AbstractMetaDataDefaults.getFieldName()</code> lower-cases the first
* character after the prefix unconditionally. See OPENJPA-2467 and
* OPENJPA-2993; do not rename these accessors to make a test pass.
*/
@Entity
@Table(name = "CAPITALIZATION_TABLE")
public class PropertyAccessCapitalization {
Expand All @@ -42,11 +56,11 @@ public class PropertyAccessCapitalization {
private boolean BOOLEAN;
private boolean Bool;

public int getACAPITAL() {
public int getaCAPITAL() {
return aCAPITAL;
}

public void setACAPITAL(int aCAPITAL) {
public void setaCAPITAL(int aCAPITAL) {
this.aCAPITAL = aCAPITAL;
}

Expand Down Expand Up @@ -83,11 +97,11 @@ public void setWord(int word) {
this.word = word;
}

public int getAWord() {
public int getaWord() {
return aWord;
}

public void setAWord(int aWord) {
public void setaWord(int aWord) {
this.aWord = aWord;
}

Expand Down Expand Up @@ -139,11 +153,11 @@ public void setAnother(int another) {
Another = another;
}

public boolean isABoolean() {
public boolean isaBoolean() {
return aBoolean;
}

public void setABoolean(boolean aBoolean) {
public void setaBoolean(boolean aBoolean) {
this.aBoolean = aBoolean;
}

Expand Down Expand Up @@ -198,11 +212,11 @@ public boolean equals(Object obj) {
return false;
if (getA1() != other.getA1())
return false;
if (isABoolean() != other.isABoolean())
if (isaBoolean() != other.isaBoolean())
return false;
if (getACAPITAL() != other.getACAPITAL())
if (getaCAPITAL() != other.getaCAPITAL())
return false;
if (getAWord() != other.getAWord())
if (getaWord() != other.getaWord())
return false;
if (getAaWord() != other.getAaWord())
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

import jakarta.persistence.EntityManager;

import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.persistence.JPAFacadeHelper;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

public class TestPropertyAccessCapitalization extends SingleEMFTestCase {
Expand All @@ -37,17 +40,17 @@ public void testCorrectCapitalization() {
Random r = new Random();
entity.setId(r.nextInt());
entity.setWord(r.nextInt());
entity.setAWord(r.nextInt());
entity.setaWord(r.nextInt());
entity.setAaWord(r.nextInt());
entity.setAaaWord(r.nextInt());
entity.setCAPITAL(r.nextInt());
entity.setACAPITAL(r.nextInt());
entity.setaCAPITAL(r.nextInt());
entity.setAnother(r.nextInt());
entity.setA1(r.nextInt());
entity.setB1(r.nextInt());
entity.setA(r.nextInt());
entity.setB(r.nextInt());
entity.setABoolean(true);
entity.setaBoolean(true);
entity.setBBoolean(true);
entity.setBOOLEAN(true);
entity.setBool(true);
Expand Down Expand Up @@ -92,4 +95,15 @@ public void testOldCapitalization() {
em.find(PropertyAccessCapitalizationOldBehavior.class, entity.getId());
assertEquals(entity, persistentEntity);
}

public void testBothAccessorSpellingsYieldTheSamePropertyNames() {
ClassMetaData beanStyle = JPAFacadeHelper.getMetaData(emf, PropertyAccessCapitalization.class);
ClassMetaData oldStyle = JPAFacadeHelper.getMetaData(emf, PropertyAccessCapitalizationOldBehavior.class);
for (ClassMetaData meta : new ClassMetaData[] { beanStyle, oldStyle }) {
assertTrue("Access type should be PROPERTY", AccessCode.isProperty(meta.getAccessType()));
assertNotNull(meta + ": 'aWord' should be persistent", meta.getField("aWord"));
assertNotNull(meta + ": 'aCAPITAL' should be persistent", meta.getField("aCAPITAL"));
assertNotNull(meta + ": 'aBoolean' should be persistent", meta.getField("aBoolean"));
}
}
}
Loading