/* * Copyright 2008 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 * * 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 grails.util; import java.util.*; /** * Utility methods for converting between different name types, * for example from class names -> property names and vice-versa. The * key aspect of this class is that it has no dependencies outside the JDK! */ public class GrailsNameUtils { private static final String PROPERTY_SET_PREFIX = "set"; private static final String PROPERTY_GET_PREFIX = "get"; private GrailsNameUtils() { } /** * Retrieves the name of a setter for the specified property name * @param propertyName The property name * @return The setter equivalent */ public static String getSetterName(String propertyName) { final String suffix = getSuffixForGetterOrSetter(propertyName); return PROPERTY_SET_PREFIX+suffix; } /** * Calculate the name for a getter method to retrieve the specified property * @param propertyName * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints */ public static String getGetterName(String propertyName) { final String suffix = getSuffixForGetterOrSetter(propertyName); return PROPERTY_GET_PREFIX + suffix; } private static String getSuffixForGetterOrSetter(String propertyName) { final String suffix; if (propertyName.length() > 1 && Character.isLowerCase(propertyName.charAt(0)) && Character.isUpperCase(propertyName.charAt(1))) { suffix = propertyName; } else { suffix = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); } return suffix; } /** * Returns the class name for the given logical name and trailing name. For example "person" and "Controller" would evaluate to "PersonController" * * @param logicalName The logical name * @param trailingName The trailing name * @return The class name */ public static String getClassName(String logicalName, String trailingName) { if (isBlank(logicalName)) { throw new IllegalArgumentException("Argument [logicalName] cannot be null or blank"); } String className = logicalName.substring(0,1).toUpperCase(Locale.ENGLISH) + logicalName.substring(1); if (trailingName != null) { className = className + trailingName; } return className; } /** * Returns the class name, including package, for the given class. This method will deals with proxies and closures. * * @param cls The class name */ public static String getFullClassName(Class cls) { String className = cls.getName(); return getFullClassName(className); } /** * Returns the class name, including package, for the given class. This method will deals with proxies and closures. * * @param className The class name */ public static String getFullClassName(String className) { final int i = className.indexOf('$'); if(i > -1) { className = className.substring(0, i); } return className; } /** * Return the class name for the given logical name. For example "person" would evaluate to "Person" * * @param logicalName The logical name * @return The class name */ public static String getClassName(String logicalName) { return getClassName(logicalName, ""); } /** * Returns the class name representation of the given name * * @param name The name to convert * @return The property name representation */ public static String getClassNameRepresentation(String name) { if (name == null || name.length() == 0) { return ""; } StringBuilder buf = new StringBuilder(); String[] tokens = name.split("[^\\w\\d]"); for (String token1 : tokens) { String token = token1.trim(); int length = token.length(); if (length > 0) { buf.append(token.substring(0, 1).toUpperCase(Locale.ENGLISH)); if (length > 1) { buf.append(token.substring(1)); } } } return buf.toString(); } /** * Converts foo-bar into FooBar. Empty and null strings are returned as-is. * * @param name The lower case hyphen separated name * @return The class name equivalent. */ private static String getClassNameForLowerCaseHyphenSeparatedName(String name) { // Handle null and empty strings. if (isBlank(name)) return name; if (name.indexOf('-') == -1) { return name.substring(0,1).toUpperCase() + name.substring(1); } StringBuilder buf = new StringBuilder(); String[] tokens = name.split("-"); for (String token : tokens) { if (token == null || token.length() == 0) continue; buf.append(token.substring(0, 1).toUpperCase()) .append(token.substring(1)); } return buf.toString(); } /** * Retrieves the logical class name of a Grails artifact given the Grails class * and a specified trailing name. * * @param clazz The class * @param trailingName The trailing name such as "Controller" or "TagLib" * @return The logical class name */ public static String getLogicalName(Class<?> clazz, String trailingName) { return getLogicalName(clazz.getName(), trailingName); } /** * Retrieves the logical name of the class without the trailing name * @param name The name of the class * @param trailingName The trailing name * @return The logical name */ public static String getLogicalName(String name, String trailingName) { if (isBlank(trailingName)) { return name; } String shortName = getShortName(name); if (shortName.indexOf(trailingName) == - 1) { return name; } return shortName.substring(0, shortName.length() - trailingName.length()); } public static String getLogicalPropertyName(String className, String trailingName) { if (!isBlank(className) && !isBlank(trailingName)) { if (className.length() == trailingName.length() + 1 && className.endsWith(trailingName)) { return className.substring(0, 1).toLowerCase(); } } return getLogicalName(getPropertyName(className), trailingName); } /** * Shorter version of getPropertyNameRepresentation. * @param name The name to convert * @return The property name version */ public static String getPropertyName(String name) { return getPropertyNameRepresentation(name); } /** * Shorter version of getPropertyNameRepresentation. * @param clazz The clazz to convert * @return The property name version */ public static String getPropertyName(Class<?> clazz) { return getPropertyNameRepresentation(clazz); } /** * Returns the property name equivalent for the specified class. * * @param targetClass The class to get the property name for * @return A property name reperesentation of the class name (eg. MyClass becomes myClass) */ public static String getPropertyNameRepresentation(Class<?> targetClass) { return getPropertyNameRepresentation(getShortName(targetClass)); } /** * Returns the property name representation of the given name. * * @param name The name to convert * @return The property name representation */ public static String getPropertyNameRepresentation(String name) { // Strip any package from the name. int pos = name.lastIndexOf('.'); if (pos != -1) { name = name.substring(pos + 1); } // Check whether the name begins with two upper case letters. if (name.length() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) { return name; } String propertyName = name.substring(0,1).toLowerCase(Locale.ENGLISH) + name.substring(1); if (propertyName.indexOf(' ') > -1) { propertyName = propertyName.replaceAll("\\s", ""); } return propertyName; } /** * Converts foo-bar into fooBar. * * @param name The lower case hyphen separated name * @return The property name equivalent */ public static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) { return getPropertyName(getClassNameForLowerCaseHyphenSeparatedName(name)); } /** * Returns the class name without the package prefix. * * @param targetClass The class to get a short name for * @return The short name of the class */ public static String getShortName(Class<?> targetClass) { return getShortName(targetClass.getName()); } /** * Returns the class name without the package prefix. * * @param className The class name to get a short name for * @return The short name of the class */ public static String getShortName(String className) { int i = className.lastIndexOf("."); if (i > -1) { className = className.substring(i + 1, className.length()); } return className; } /** * Returns the class name without the package prefix. * * @param className The class name to get a short name for * @return The short name of the class */ public static String getPackageName(String className) { int i = className.lastIndexOf("."); String packageName = ""; if (i > -1) { packageName = className.substring(0, i); } return packageName; } /** * Retrieves the script name representation of the supplied class. For example * MyFunkyGrailsScript would be my-funky-grails-script. * * @param clazz The class to convert * @return The script name representation */ public static String getScriptName(Class<?> clazz) { return clazz == null ? null : getScriptName(clazz.getName()); } /** * Retrieves the script name representation of the given class name. * For example MyFunkyGrailsScript would be my-funky-grails-script. * * @param name The class name to convert. * @return The script name representation. */ public static String getScriptName(String name) { if (name == null) { return null; } if (name.endsWith(".groovy")) { name = name.substring(0, name.length()-7); } return getNaturalName(name).replaceAll("\\s", "-").toLowerCase(); } /** * Calculates the class name from a script name in the form my-funk-grails-script. * * @param scriptName The script name * @return A class name */ public static String getNameFromScript(String scriptName) { return getClassNameForLowerCaseHyphenSeparatedName(scriptName); } /** * Returns the name of a plugin given the name of the *GrailsPlugin.groovy * descriptor file. For example, "DbUtilsGrailsPlugin.groovy" gives * "db-utils". * @param descriptorName The simple name of the plugin descriptor. * @return The plugin name for the descriptor, or <code>null</code> * if <i>descriptorName</i> is <code>null</code>, or an empty string * if <i>descriptorName</i> is an empty string. * @throws IllegalArgumentException if the given descriptor name is * not valid, i.e. if it doesn't end with "GrailsPlugin.groovy". */ public static String getPluginName(String descriptorName) { if (descriptorName == null || descriptorName.length() == 0) { return descriptorName; } if (!descriptorName.endsWith("GrailsPlugin.groovy")) { throw new IllegalArgumentException("Plugin descriptor name is not valid: " + descriptorName); } return getScriptName(descriptorName.substring(0, descriptorName.indexOf("GrailsPlugin.groovy"))); } /** * Converts a property name into its natural language equivalent eg ('firstName' becomes 'First Name') * @param name The property name to convert * @return The converted property name */ public static String getNaturalName(String name) { name = getShortName(name); List<String> words = new ArrayList<String>(); int i = 0; char[] chars = name.toCharArray(); for (int j = 0; j < chars.length; j++) { char c = chars[j]; String w; if (i >= words.size()) { w = ""; words.add(i, w); } else { w = words.get(i); } if (Character.isLowerCase(c) || Character.isDigit(c)) { if (Character.isLowerCase(c) && w.length() == 0) { c = Character.toUpperCase(c); } else if (w.length() > 1 && Character.isUpperCase(w.charAt(w.length() - 1))) { w = ""; words.add(++i,w); } words.set(i, w + c); } else if (Character.isUpperCase(c)) { if ((i == 0 && w.length() == 0) || (Character.isUpperCase(w.charAt(w.length() - 1)) && Character.isUpperCase(chars[j-1]))) { words.set(i, w + c); } else { words.add(++i, String.valueOf(c)); } } } StringBuilder buf = new StringBuilder(); for (Iterator<String> j = words.iterator(); j.hasNext();) { String word = j.next(); buf.append(word); if (j.hasNext()) { buf.append(' '); } } return buf.toString(); } /** * <p>Determines whether a given string is <code>null</code>, empty, * or only contains whitespace. If it contains anything other than * whitespace then the string is not considered to be blank and the * method returns <code>false</code>.</p> * <p>We could use Commons Lang for this, but we don't want GrailsNameUtils * to have a dependency on any external library to minimise the number of * dependencies required to bootstrap Grails.</p> * @param str The string to test. * @return <code>true</code> if the string is <code>null</code>, or * blank. */ public static boolean isBlank(String str) { return str == null || str.trim().length() == 0; } /** * Returns an appropriate property name for the given object. If the object is a collection will append List, Set, Collection or Map to the property name * @param object The object * @return The property name convention */ public static String getPropertyNameConvention(Object object) { String suffix = ""; return getPropertyNameConvention(object, suffix); } /** * Test whether the give package name is a valid Java package * * @param packageName The name of the package * @return True if it is valid */ public static boolean isValidJavaPackage(String packageName) { if(isBlank(packageName)) return false; final String[] parts = packageName.split("\\."); for (String part : parts) { if(!isValidJavaIdentifier(part)) { return false; } } return true; } /** * Test whether the given name is a valid Java identifier * * @param name The name * @return True if it is */ public static boolean isValidJavaIdentifier(String name) { if(isBlank(name)) return false; final char[] chars = name.toCharArray(); if(!Character.isJavaIdentifierStart(chars[0])) { return false; } for (char c : chars) { if(!Character.isJavaIdentifierPart(c)) { return false; } } return true; } /** * Returns an appropriate property name for the given object. If the object is a collection will append List, Set, Collection or Map to the property name * @param object The object * @param suffix The suffix to append to the name. * @return The property name convention */ public static String getPropertyNameConvention(Object object, String suffix) { if(object != null) { Class<?> type = object.getClass(); if (type.isArray()) { return getPropertyName(type.getComponentType()) + suffix + "Array"; } if (object instanceof Collection) { Collection coll = (Collection) object; if (coll.isEmpty()) { return "emptyCollection"; } Object first = coll.iterator().next(); if(coll instanceof List) { return getPropertyName(first.getClass()) + suffix + "List"; } if(coll instanceof Set) { return getPropertyName(first.getClass()) + suffix + "Set"; } return getPropertyName(first.getClass()) + suffix + "Collection"; } if (object instanceof Map) { Map map = (Map)object; if (map.isEmpty()) { return "emptyMap"; } Object entry = map.values().iterator().next(); if (entry != null) { return getPropertyName(entry.getClass()) + suffix + "Map"; } } else { return getPropertyName(object.getClass()) + suffix; } } return null; } /** * 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) { return getPropertyForGetter(getterName, boolean.class); } /** * Returns a property name equivalent for the given getter name and return type 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 * @param returnType The type the method returns * @return The property name equivalent */ public static String getPropertyForGetter(String getterName, Class returnType) { if (getterName == null || getterName.length() == 0) return null; if (getterName.startsWith("get")) { String prop = getterName.substring(3); return convertValidPropertyMethodSuffix(prop); } if (getterName.startsWith("is") && returnType == boolean.class) { String prop = getterName.substring(2); return convertValidPropertyMethodSuffix(prop); } return null; } /** * 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 */ 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(suffix.charAt(1))) { // "aProperty", "AProperty" return suffix; } if (Character.isUpperCase(suffix.charAt(0))) { return Character.toLowerCase(suffix.charAt(0)) + suffix.substring(1); } if('_' == suffix.charAt(0)) { return suffix; } return null; } /** * 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 The name of the method * @param args The arguments * @return true if it is a javabean property getter * @deprecated use {@link #isGetter(String, Class, Class[])} instead because this method has a defect for "is.." method with Boolean return types. */ public static boolean isGetter(String name, Class<?>[] args) { return isGetter(name, boolean.class, args); } /** * 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 The name of the method * @param returnType The return type of the method * @param args The arguments * @return true if it is a javabean property getter */ public static boolean isGetter(String name, Class returnType, Class<?>[] args) { if (name == null || name.length() == 0 || args == null)return false; if (args.length != 0)return false; if (name.startsWith("get")) { name = name.substring(3); if (isPropertyMethodSuffix(name)) return true; } else if (name.startsWith("is") && returnType == boolean.class) { name = name.substring(2); if (isPropertyMethodSuffix(name)) return true; } return false; } /** * This method is used when interrogating a method name to determine if the * method represents a property getter. For example, if a method is named * <code>getSomeProperty</code>, the value <code>"SomeProperty"</code> could * be passed to this method to determine that the method should be considered * a property getter. Examples of suffixes that would be considered property * getters: * <ul> * <li>SomeProperty</li> * <li>Word</li> * <li>aProperty</li> * <li>S</li> * <li>X567</li> * </ul> * * Examples of suffixes that would not be considered property getters: * <ul> * <li>someProperty</li> * <li>word</li> * <li>s</li> * <li>x567</li> * <li>2other</li> * <li>5</li> * </ul> * * A suffix like <code>prop</code> from a method <code>getprop()</code> is * not recognized as a valid suffix. However Groovy will recognize such a * method as a property getter but only if a method <code>getProp()</code> or * a property <code>prop</code> 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) 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 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, must be null or empty or a valid identifier name * @return The property name equivalent */ public static String getPropertyForSetter(String setterName) { if (setterName == null || setterName.length() == 0) return null; if (setterName.startsWith("set")) { String prop = setterName.substring(3); return convertValidPropertyMethodSuffix(prop); } return null; } }