diff --git a/CHANGELOG.md b/CHANGELOG.md index afec200e..45982148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0-alpha.2 (yyyy-mm-dd) + + - Ignore underscores on Java fields and setters when resolving instantiators. (Fixes [#3](https://github.com/EvidentSolutions/dalesbred/issues/3)) + ## 1.0.0-alpha.1 (2015-05-02) First alpha for 1.0 -version of Dalesbred. This is not source compatible with previous versions, diff --git a/dalesbred/src/main/java/org/dalesbred/instantiation/PropertyAccessor.java b/dalesbred/src/main/java/org/dalesbred/instantiation/PropertyAccessor.java index c3ce1636..ea23ac34 100644 --- a/dalesbred/src/main/java/org/dalesbred/instantiation/PropertyAccessor.java +++ b/dalesbred/src/main/java/org/dalesbred/instantiation/PropertyAccessor.java @@ -33,6 +33,7 @@ import java.util.regex.Pattern; import static java.lang.reflect.Modifier.isPublic; +import static org.dalesbred.internal.utils.StringUtils.isEqualIgnoringCaseAndUnderscores; abstract class PropertyAccessor { @@ -45,13 +46,12 @@ abstract class PropertyAccessor { @NotNull static Optional findAccessor(@NotNull Class cl, @NotNull String name) { - String normalizedName = UNDERSCORE.matcher(name).replaceAll(""); - Optional setter = findSetter(cl, normalizedName).map(SetterPropertyAccessor::new); + Optional setter = findSetter(cl, name).map(SetterPropertyAccessor::new); if (setter.isPresent()) { return setter; } else { - return findField(cl, normalizedName).map(FieldPropertyAccessor::new); + return findField(cl, name).map(FieldPropertyAccessor::new); } } @@ -60,7 +60,7 @@ private static Optional findField(@NotNull Class cl, @NotNull String n Field result = null; for (Field field : cl.getFields()) - if (isPublic(field.getModifiers()) && field.getName().equalsIgnoreCase(name) && !field.isAnnotationPresent(DalesbredIgnore.class)) { + if (isPublic(field.getModifiers()) && isEqualIgnoringCaseAndUnderscores(name, field.getName()) && !field.isAnnotationPresent(DalesbredIgnore.class)) { if (result != null) throw new InstantiationException("Conflicting fields for property: " + result + " - " + name); result = field; @@ -75,7 +75,8 @@ private static Optional findSetter(@NotNull Class cl, @NotNull String String methodName = "set" + name; for (Method method : cl.getMethods()) { - if (isPublic(method.getModifiers()) && methodName.equalsIgnoreCase(method.getName()) && method.getParameterTypes().length == 1 && !method.isAnnotationPresent(DalesbredIgnore.class)) { + + if (isPublic(method.getModifiers()) && isEqualIgnoringCaseAndUnderscores(methodName, method.getName()) && method.getParameterTypes().length == 1 && !method.isAnnotationPresent(DalesbredIgnore.class)) { if (result != null) throw new InstantiationException("Conflicting setters for property: " + result + " - " + name); result = method; diff --git a/dalesbred/src/main/java/org/dalesbred/internal/utils/StringUtils.java b/dalesbred/src/main/java/org/dalesbred/internal/utils/StringUtils.java index f93d4323..f80f1853 100644 --- a/dalesbred/src/main/java/org/dalesbred/internal/utils/StringUtils.java +++ b/dalesbred/src/main/java/org/dalesbred/internal/utils/StringUtils.java @@ -65,4 +65,35 @@ public static String upperCamelToLowerUnderscore(@NotNull CharSequence cs) { public static String capitalize(@NotNull String s) { return s.isEmpty() ? s : (toUpperCase(s.charAt(0)) + s.substring(1)); } + + /** + * Returns true if two strings are equal, apart from case differences and underscores. + * Underscores in both sides are totally ignored. + */ + public static boolean isEqualIgnoringCaseAndUnderscores(@NotNull String s1, @NotNull String s2) { + int index1 = 0; + int index2 = 0; + int length1 = s1.length(); + int length2 = s2.length(); + + while (index1 < length1 && index2 < length2) { + char nameChar = s1.charAt(index1++); + if (nameChar == '_') continue; + + char memberNameChar = s2.charAt(index2++); + if (memberNameChar == '_') { + index1--; + continue; + } + + if (toLowerCase(nameChar) != toLowerCase(memberNameChar)) + return false; + } + + // Skip trailing underscores + while (index1 < length1 && s1.charAt(index1) == '_') index1++; + while (index2 < length2 && s2.charAt(index2) == '_') index2++; + + return index1 == length1 && index2 == length2; + } } diff --git a/dalesbred/src/test/java/org/dalesbred/DatabaseReflectiveInstantiationTest.java b/dalesbred/src/test/java/org/dalesbred/DatabaseReflectiveInstantiationTest.java index 114de9fc..b170fa88 100644 --- a/dalesbred/src/test/java/org/dalesbred/DatabaseReflectiveInstantiationTest.java +++ b/dalesbred/src/test/java/org/dalesbred/DatabaseReflectiveInstantiationTest.java @@ -74,6 +74,18 @@ public void setterBindingNullValuesAndConversions() { assertThat(result.getDateTime(), is(nullValue())); } + @Test + public void fieldsWithUnderscores() { + ClassWithUnderscoreFields result = db.findUnique(ClassWithUnderscoreFields.class, "select 'Fred' as first_name from (values (1))"); + assertThat(result.first_name, is("Fred")); + } + + @Test + public void settersWithUnderscores() { + ClassWithUnderscoreSetters result = db.findUnique(ClassWithUnderscoreSetters.class, "select 'Fred' as first_name from (values (1))"); + assertThat(result.first_name, is("Fred")); + } + public static class MyResult { public final int constructor; @@ -121,4 +133,21 @@ public void setDateTime(DateTime dateTime) { this.dateTime = dateTime; } } + + public static class ClassWithUnderscoreFields { + + @SuppressWarnings("InstanceVariableNamingConvention") + public String first_name; + } + + @SuppressWarnings("InstanceVariableNamingConvention") + public static class ClassWithUnderscoreSetters { + + private String first_name; + + @Reflective + public void setFirst_name(String firstName) { + this.first_name = firstName; + } + } } diff --git a/dalesbred/src/test/java/org/dalesbred/internal/utils/StringUtilsTest.java b/dalesbred/src/test/java/org/dalesbred/internal/utils/StringUtilsTest.java index 01932ff4..59a3c4a8 100644 --- a/dalesbred/src/test/java/org/dalesbred/internal/utils/StringUtilsTest.java +++ b/dalesbred/src/test/java/org/dalesbred/internal/utils/StringUtilsTest.java @@ -24,8 +24,7 @@ import org.junit.Test; -import static org.dalesbred.internal.utils.StringUtils.capitalize; -import static org.dalesbred.internal.utils.StringUtils.upperCamelToLowerUnderscore; +import static org.dalesbred.internal.utils.StringUtils.*; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -71,4 +70,20 @@ public void capitalization() { assertThat(capitalize("foo"), is("Foo")); assertThat(capitalize("fooBar"), is("FooBar")); } + + @Test + public void caseAndUnderscoreIgnoringEquality() { + assertThat(isEqualIgnoringCaseAndUnderscores("", ""), is(true)); + assertThat(isEqualIgnoringCaseAndUnderscores("foo", "foo"), is(true)); + assertThat(isEqualIgnoringCaseAndUnderscores("foo", "bar"), is(false)); + + assertThat(isEqualIgnoringCaseAndUnderscores("foo", "FOo"), is(true)); + assertThat(isEqualIgnoringCaseAndUnderscores("Foo", "FOo"), is(true)); + + assertThat(isEqualIgnoringCaseAndUnderscores("Foo_bar", "FOoBar"), is(true)); + assertThat(isEqualIgnoringCaseAndUnderscores("Foobar", "FOo_Bar"), is(true)); + + assertThat(isEqualIgnoringCaseAndUnderscores("_foo_", "foo"), is(true)); + assertThat(isEqualIgnoringCaseAndUnderscores("foo", "__foo__"), is(true)); + } }