diff --git a/grails-core/src/main/groovy/grails/util/GrailsClassUtils.java b/grails-core/src/main/groovy/grails/util/GrailsClassUtils.java index 1457bd27c0b..51e49bcc6ae 100644 --- a/grails-core/src/main/groovy/grails/util/GrailsClassUtils.java +++ b/grails-core/src/main/groovy/grails/util/GrailsClassUtils.java @@ -722,11 +722,12 @@ public static String getSetterName(String propertyName) { } /** - * Returns true if the name of the method specified and the number of arguments make it a javabean property + * Returns true if the name of the method specified and the number of arguments make it a javabean property getter. + * The name is assumed to be a valid Java method name, that is not verified. * - * @param name True if its a Javabean property + * @param name The name of the method * @param args The arguments - * @return true if it is a javabean property method + * @return true if it is a javabean property getter */ public static boolean isGetter(String name, Class[] args) { if (!StringUtils.hasText(name) || args == null)return false; @@ -755,90 +756,121 @@ else if (name.startsWith("is")) { *
  • Word
  • *
  • aProperty
  • *
  • S
  • + *
  • X567
  • * * - * Example sof suffixes that would not be considered property getters: + * Examples of suffixes that would not be considered property getters: * + * + * A suffix like prop from a method getprop() is + * not recognized as a valid suffix. However Groovy will recognize such a + * method as a property getter but only if a method getProp() or + * a property prop does not also exist. The Java Beans + * specification is unclear on how to treat such method names, it only says + * that "by default" the suffix will start with a capital letter because of + * the camel case style usually used. (See the JavaBeans API specification + * sections 8.3 and 8.8.) + * + * This method assumes that all characters in the name are valid Java identifier + * letters. + * * @param suffix The suffix to inspect * @return true if suffix indicates a property name */ protected static boolean isPropertyMethodSuffix(String suffix) { - if (suffix.length() > 0) { - if(suffix.length() == 1) { - if(Character.isUpperCase(suffix.charAt(0))) { - return true; - } - } else { - if(Character.isUpperCase(suffix.charAt(0)) || - (Character.isUpperCase(suffix.charAt(1)) && Character.isLowerCase(suffix.charAt(0)))) { - return true; - } - } - } - return false; + if(suffix.length() == 0) return false; + if(!Character.isJavaIdentifierStart(suffix.charAt(0))) return false; + if(suffix.length() == 1) return Character.isUpperCase(suffix.charAt(0)); + return Character.isUpperCase(suffix.charAt(0)) || Character.isUpperCase(suffix.charAt(1)); } /** - * Returns a property name equivalent for the given getter name or null if it is not a getter + * Returns a property name equivalent for the given getter name or null if it is not a valid getter. If not null + * or empty the getter name is assumed to be a valid identifier. * * @param getterName The getter name * @return The property name equivalent */ public static String getPropertyForGetter(String getterName) { - if (!StringUtils.hasText(getterName))return null; + if (getterName == null || getterName.length() == 0) return null; if (getterName.startsWith("get")) { String prop = getterName.substring(3); - return convertPropertyName(prop); + return convertValidPropertyMethodSuffix(prop); } if (getterName.startsWith("is")) { String prop = getterName.substring(2); - return convertPropertyName(prop); + return convertValidPropertyMethodSuffix(prop); } return null; } - private static String convertPropertyName(String prop) { - if (prop.length() == 1) { - return prop.toLowerCase(); + /** + * This method functions the same as {@link #isPropertyMethodSuffix(String)}, + * but in addition returns the property name, or null if not a valid property. + * + * @param suffix The suffix to inspect + * @return The property name or null + */ + private static String convertValidPropertyMethodSuffix(String suffix) { + if (suffix.length() == 0) return null; + + // We assume all characters are Character.isJavaIdentifierPart, but the first one may not be a valid + // starting character. + if (!Character.isJavaIdentifierStart(suffix.charAt(0))) return null; + + if (suffix.length() == 1) { + return Character.isUpperCase(suffix.charAt(0)) ? suffix.toLowerCase() : null; } - if (Character.isUpperCase(prop.charAt(0)) && Character.isUpperCase(prop.charAt(1))) { - return prop; + if (Character.isUpperCase(suffix.charAt(1))) { + // "aProperty", "AProperty" + return suffix; } - if (Character.isDigit(prop.charAt(0))) { - return prop; + if (Character.isUpperCase(suffix.charAt(0))) { + return Character.toLowerCase(suffix.charAt(0)) + suffix.substring(1); } - return Character.toLowerCase(prop.charAt(0)) + prop.substring(1); + return null; } /** - * Returns a property name equivalent for the given setter name or null if it is not a getter + * Returns a property name equivalent for the given setter name or null if it is not a valid setter. If not null + * or empty the setter name is assumed to be a valid identifier. * - * @param setterName The setter name + * @param setterName The setter name, must be null or empty or a valid identifier name * @return The property name equivalent */ public static String getPropertyForSetter(String setterName) { - if (!StringUtils.hasText(setterName))return null; + if (setterName == null || setterName.length() == 0) return null; if (setterName.startsWith("set")) { String prop = setterName.substring(3); - return convertPropertyName(prop); + return convertValidPropertyMethodSuffix(prop); } return null; } + /** + * Returns true if the name of the method specified and the number of arguments make it a javabean property setter. + * The name is assumed to be a valid Java method name, that is not verified. + * + * @param name The name of the method + * @param args The arguments + * @return true if it is a javabean property setter + */ @SuppressWarnings("rawtypes") public static boolean isSetter(String name, Class[] args) { if (!StringUtils.hasText(name) || args == null)return false; if (name.startsWith("set")) { if (args.length != 1) return false; - name = name.substring(3); - if (name.length() > 0 && Character.isUpperCase(name.charAt(0))) return true; + return isPropertyMethodSuffix(name.substring(3)); } return false; diff --git a/grails-core/src/main/groovy/org/grails/core/util/ClassPropertyFetcher.java b/grails-core/src/main/groovy/org/grails/core/util/ClassPropertyFetcher.java index 43e9d072eff..c73c8eb768a 100644 --- a/grails-core/src/main/groovy/org/grails/core/util/ClassPropertyFetcher.java +++ b/grails-core/src/main/groovy/org/grails/core/util/ClassPropertyFetcher.java @@ -126,31 +126,24 @@ public void doWith(CachedMethod method) throws IllegalArgumentException, if (!method.isPublic()) { return; } - if (returnType != Void.class && returnType != void.class) { - if (method.getParameterTypes().length == 0) { - String name = method.getName(); - if (name.indexOf('$') == -1) { - if (name.length() > 3 && name.startsWith("get") - && Character.isUpperCase(name.charAt(3))) { - name = name.substring(3); - } else if (name.length() > 2 - && name.startsWith("is") - && Character.isUpperCase(name.charAt(2)) - && (returnType == Boolean.class || - returnType == boolean.class)) { - name = name.substring(2); - } - if (method.isStatic()) { - GetterPropertyFetcher fetcher = new GetterPropertyFetcher(method, true); - staticFetchers.put(name, fetcher); - staticFetchers.put(StringUtils.uncapitalize(name), - fetcher); - } else { - instanceFetchers.put(StringUtils.uncapitalize(name), - new GetterPropertyFetcher(method, false)); - } - } - } + if (returnType == Void.class || returnType == void.class || method.getParameterTypes().length != 0) { + return; + } + + String propertyName = GrailsClassUtils.getPropertyForGetter(method.getName()); + if(propertyName == null || propertyName.indexOf('$') != -1) { + return; + } + + if (method.getName().startsWith("is") && + !(returnType == Boolean.class || returnType == boolean.class)) { + return; + } + + if (method.isStatic()) { + staticFetchers.put(propertyName, new GetterPropertyFetcher(method, true)); + } else { + instanceFetchers.put(propertyName, new GetterPropertyFetcher(method, false)); } } }; diff --git a/grails-core/src/test/groovy/org/grails/core/util/ClassPropertyFetcherSpec.groovy b/grails-core/src/test/groovy/org/grails/core/util/ClassPropertyFetcherSpec.groovy index c76678b6f13..a82dab0d959 100644 --- a/grails-core/src/test/groovy/org/grails/core/util/ClassPropertyFetcherSpec.groovy +++ b/grails-core/src/test/groovy/org/grails/core/util/ClassPropertyFetcherSpec.groovy @@ -15,9 +15,20 @@ class ClassPropertyFetcherSpec extends Specification { cpf.getPropertyValue(new Author(name: "Fred"),"name") == "Fred" cpf.getPropertyValue(new Author(name: "Fred", books: ["test"]),"books").contains "test" } + + void "test properties that have the fifth letter of their getter capitalized instead of the fourth"() { + when:"A class property fetcher is created" + def cpf = ClassPropertyFetcher.forClass(Person) + + then:"all properties are correct" + def person = new Person(name: "Fred", xAge: 30) + person.getxAge() == 30 + cpf.getPropertyValue(person, "xAge") == 30 + } } class Person { String name + Integer xAge } class Author extends Person { Set books diff --git a/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsClassUtilsTests.java b/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsClassUtilsTests.java index 7bb92d5b23f..944c6545270 100644 --- a/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsClassUtilsTests.java +++ b/grails-test-suite-uber/src/test/groovy/org/grails/commons/GrailsClassUtilsTests.java @@ -113,8 +113,19 @@ public void testGetterNames() { public void testIsGetterOrSetter() { assertTrue(GrailsClassUtils.isSetter("setSomething", new Class[] { String.class })); assertTrue(GrailsClassUtils.isGetter("getSomething", new Class[0])); + assertTrue(GrailsClassUtils.isGetter("isSomething", new Class[0])); assertTrue(GrailsClassUtils.isSetter("setURL", new Class[] { String.class })); assertTrue(GrailsClassUtils.isGetter("getURL", new Class[0])); + assertTrue(GrailsClassUtils.isGetter("isURL", new Class[0])); + assertTrue(GrailsClassUtils.isSetter("setaProp", new Class[] { String.class })); + assertTrue(GrailsClassUtils.isGetter("getaProp", new Class[0])); + assertTrue(GrailsClassUtils.isGetter("isaProp", new Class[0])); + assertTrue(GrailsClassUtils.isSetter("setX", new Class[] { String.class })); + assertTrue(GrailsClassUtils.isGetter("getX", new Class[0])); + assertTrue(GrailsClassUtils.isGetter("isX", new Class[0])); + assertTrue(GrailsClassUtils.isSetter("setX2", new Class[] { String.class })); + assertTrue(GrailsClassUtils.isGetter("getX2", new Class[0])); + assertTrue(GrailsClassUtils.isGetter("isX2", new Class[0])); assertFalse(GrailsClassUtils.isGetter("something", new Class[] { String.class })); assertFalse(GrailsClassUtils.isGetter("get", new Class[0])); @@ -123,6 +134,20 @@ public void testIsGetterOrSetter() { assertFalse(GrailsClassUtils.isSetter("setSomething", new Class[] { String.class, Object.class })); assertFalse(GrailsClassUtils.isGetter("getSomething", new Class[] { Object.class })); + assertFalse(GrailsClassUtils.isGetter("getsomething", new Class[0])); + assertFalse(GrailsClassUtils.isGetter("issomething", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("setsomething", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter("get0", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("set0", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter("get2other", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("set2other", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter("getq3", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("setq3", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter("get5A", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("set5A", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter("", new Class[0])); + assertFalse(GrailsClassUtils.isSetter("", new Class[] { String.class })); + assertFalse(GrailsClassUtils.isGetter(null, new Class[] { Object.class })); assertFalse(GrailsClassUtils.isGetter("getSomething", null)); assertFalse(GrailsClassUtils.isGetter(null, null)); @@ -132,6 +157,21 @@ public void testGetPropertyForGetter() { assertEquals("something", GrailsClassUtils.getPropertyForGetter("getSomething")); assertEquals("URL", GrailsClassUtils.getPropertyForGetter("getURL")); assertEquals("p", GrailsClassUtils.getPropertyForGetter("getP")); + assertEquals("URL", GrailsClassUtils.getPropertyForGetter("isURL")); + assertEquals("aProp", GrailsClassUtils.getPropertyForGetter("getaProp")); + assertEquals("x2", GrailsClassUtils.getPropertyForGetter("getX2")); + assertEquals("x2", GrailsClassUtils.getPropertyForGetter("isX2")); + + assertNull(GrailsClassUtils.getPropertyForGetter(null)); + assertNull(GrailsClassUtils.getPropertyForGetter("")); + assertNull(GrailsClassUtils.getPropertyForGetter("get0")); + assertNull(GrailsClassUtils.getPropertyForGetter("get2other")); + assertNull(GrailsClassUtils.getPropertyForGetter("getq3")); + assertNull(GrailsClassUtils.getPropertyForGetter("get5A")); + assertNull(GrailsClassUtils.getPropertyForGetter("setSomething")); + assertNull(GrailsClassUtils.getPropertyForGetter("getit")); + assertNull(GrailsClassUtils.getPropertyForGetter("geta")); + assertNull(GrailsClassUtils.getPropertyForGetter("get0")); } public void testGetStaticField() {