yakworks.commons.lang.NameUtils.groovy Maven / Gradle / Ivy
/*
* Copyright 2008 original authors
* SPDX-License-Identifier: Apache-2.0
*/
package yakworks.commons.lang
import java.util.regex.Matcher
import java.util.regex.Pattern
import groovy.transform.CompileStatic
import yakworks.commons.util.StringUtils
/**
* Copied in from grails.util.GrailsNameUtils and converted to groovy
* 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 stock groovy!
*/
@SuppressWarnings(['ClassSize', 'NestedBlockDepth', 'InvertedIfElse', 'UnnecessaryToString'])
@CompileStatic
class NameUtils {
private static final Pattern DOT_UPPER = Pattern.compile('\\.[A-Z\\$]');
private static final String PROPERTY_SET_PREFIX = "set"
private static final String PROPERTY_GET_PREFIX = "get"
public static final String DOLLAR_SEPARATOR = '$'
private static final Pattern SERVICE_ID_REGEX = Pattern.compile('[\\p{javaLowerCase}\\d-]+');
// private static final Pattern KEBAB_CASE_SEQUENCE = Pattern.compile('^(([a-z0-9])+(\\-|\\.|:)?)*([a-z0-9])+$');
private static final Pattern KEBAB_REPLACEMENTS = Pattern.compile('[_ ]');
/**
* The camel case version of the string with the first letter in lower case.
*
* @param str The string
* @return The new string in camel case
*/
public static String camelCase(String str) {
return camelCase(str, true);
}
static String toCamelCase( String text ) {
text = text.toLowerCase().replaceAll("(_)([A-Za-z0-9])") { List it -> it[2].toUpperCase() }
//println text
return text
}
/**
* The camel case version of the string with the first letter in lower case.
*
* @param str The string
* @param lowerCaseFirstLetter Whether the first letter is in upper case or lower case
* @return The new string in camel case
*/
public static String camelCase(String str, boolean lowerCaseFirstLetter) {
str = str.toLowerCase().replaceAll("(_)([A-Za-z0-9])") { List it -> it[2].toUpperCase() }
StringBuilder sb = new StringBuilder(str.length());
for (String s : str.split("[\\s_-]")) {
String capitalize = capitalize(s);
sb.append(capitalize);
}
String result = sb.toString();
if (lowerCaseFirstLetter) {
return decapitalize(result);
}
return result;
}
/**
* Retrieves the name of a setter for the specified property name
* @param propertyName The property name
* @return The setter equivalent
*/
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
* @return The name for the getter method for this property, if it were to exist, i.e. getConstraints
*/
static String getGetterName(String propertyName) {
final String suffix = getSuffixForGetterOrSetter(propertyName);
return PROPERTY_GET_PREFIX + suffix;
}
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
*/
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
*/
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
*/
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
*/
static String getClassName(String logicalName) {
return getClassName(logicalName, "");
}
/**
* Get the class name, taking into account proxies
*
* @param clazz The class
* @return The class name
*/
static String getClassName(Class clazz) {
final String sn = clazz.getSimpleName();
if(sn.contains(DOLLAR_SEPARATOR)) {
return clazz.getSuperclass().getName();
}
return clazz.getName();
}
/**
* Returns the class name representation of the given name
*
* @param name The name to convert
* @return The property name representation
*/
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 fooBar into FooBar. Empty and null strings are returned as-is.
*
* @param name The lower case hyphen separated name
* @return The class name equivalent.
*/
static String capitalize(String name) {
// Handle null and empty strings.
if (isBlank(name)) return name
return name.substring(0, 1).toUpperCase() + name.substring(1)
}
/**
* 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.
*/
static String getClassNameFromKebabCase(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();
}
/**
* Shorter version of getPropertyNameRepresentation.
* @param name The name to convert
* @return The property name version
*/
static String getPropertyName(String name) {
return getPropertyNameRepresentation(name);
}
/**
* Shorter version of getPropertyNameRepresentation.
* @param clazz The clazz to convert
* @return The property name version
*/
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)
*/
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
*/
static String getPropertyNameRepresentation(String name) {
// Strip any package from the name.
int pos = name.lastIndexOf('.');
if (pos != -1) {
name = name.substring(pos + 1);
}
if (name.isEmpty()) {
return name;
}
// 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
*/
static String getPropertyNameFromKebabCase(String name) {
return getPropertyName(getClassNameFromKebabCase(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
*/
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
*/
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
*/
static String getPackageName(String className) {
int i = className.lastIndexOf(".");
String packageName = "";
if (i > -1) {
packageName = className.substring(0, i);
}
return packageName;
}
/**
* Checks whether the given name is a valid service identifier.
*
* @param name The name
* @return True if it is
*/
public static boolean isHyphenatedLowerCase(String name) {
return StringUtils.isNotEmpty(name) && SERVICE_ID_REGEX.matcher(name).matches() && Character.isLetter(name.charAt(0));
}
/**
* Converts camel case to hyphenated, lowercase form.
*
* @param name The name
* @return The hyphenated string
*/
public static String hyphenate(String name) {
return hyphenate(name, true);
}
/**
* Converts camel case to hyphenated, lowercase form.
*
* @param name The name
* @param lowerCase Whether the result should be converted to lower case
* @return The hyphenated string
*/
public static String hyphenate(String name, boolean lowerCase) {
if (isHyphenatedLowerCase(name)) {
return KEBAB_REPLACEMENTS.matcher(name).replaceAll("-");
} else {
char separatorChar = '-';
return separateCamelCase(KEBAB_REPLACEMENTS.matcher(name).replaceAll("-"), lowerCase, separatorChar);
}
}
/**
* 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
*/
static String getNaturalName(String name) {
//exit fast with what was passed in if its falsy
if(!name) return name
name = getShortName(name);
List words = []
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 j = words.iterator(); j.hasNext();) {
String word = j.next();
buf.append(word);
if (j.hasNext()) {
buf.append(' ');
}
}
return buf.toString();
}
/**
* Determines whether a given string is null
, 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 false
.
* 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.
* @param str The string to test.
* @return true
if the string is null
, or
* blank.
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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.
*/
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
*/
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
* getSomeProperty
, the value "SomeProperty"
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:
*
* - SomeProperty
* - Word
* - aProperty
* - S
* - X567
*
*
* Examples of suffixes that would not be considered property getters:
*
* - someProperty
* - word
* - s
* - x567
* - 2other
* - 5
*
*
* 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) 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
*/
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;
}
/**
* Decapitalizes a given string according to the rule:
*
* - If the first or only character is Upper Case, it is made Lower Case
*
- UNLESS the second character is also Upper Case, when the String is
* returned unchanged
.
*
*
* @param name The String to decapitalize
* @return The decapitalized version of the String
*/
static String decapitalize(String name) {
if (name == null) {
return null;
}
int length = name.length();
if (length == 0) {
return name;
}
// Decapitalizes the first character if a lower case
// letter is found within 2 characters after the first
// Abc -> abc
// AB -> AB
// ABC -> ABC
// ABc -> aBc
boolean firstUpper = Character.isUpperCase(name.charAt(0));
if (firstUpper) {
if (length == 1) {
return Character.toString(Character.toLowerCase(name.charAt(0)));
}
for (int i = 1; i < Math.min(length, 3); i++) {
if (Character.isLowerCase(name.charAt(i))) {
char[] chars = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
}
}
return name;
}
/**
* Returns the simple name for a class represented as string.
*
* @param className The class name
* @return The simple name of the class
*/
public static String getSimpleName(String className) {
Matcher matcher = DOT_UPPER.matcher(className);
if (matcher.find()) {
int position = matcher.start();
return className.substring(position + 1);
}
return className;
}
private static String separateCamelCase(String name, boolean lowerCase, char separatorChar) {
if (!lowerCase) {
StringBuilder newName = new StringBuilder();
boolean first = true;
char last = '0';
for (char c : name.toCharArray()) {
if (first) {
newName.append(c);
first = false;
} else {
if (Character.isUpperCase(c) && !Character.isUpperCase(last)) {
if (c != separatorChar) {
newName.append(separatorChar);
}
newName.append(c);
} else {
if (c == '.') {
first = true;
}
if (c != separatorChar) {
if (last == separatorChar) {
newName.append(separatorChar);
}
newName.append(c);
}
}
}
last = c;
}
return newName.toString();
} else {
StringBuilder newName = new StringBuilder();
char[] chars = name.toCharArray();
boolean first = true;
char last = '0';
char secondLast = separatorChar;
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (Character.isLowerCase(c) || !Character.isLetter(c)) {
first = false;
if (c != separatorChar) {
if (last == separatorChar) {
newName.append(separatorChar);
}
newName.append(c);
}
} else {
char lowerCaseChar = Character.toLowerCase(c);
if (first) {
first = false;
newName.append(lowerCaseChar);
} else if (Character.isUpperCase(last) || last == '.') {
newName.append(lowerCaseChar);
} else if (Character.isDigit(last) && (Character.isUpperCase(secondLast) || secondLast == separatorChar)) {
newName.append(lowerCaseChar);
} else {
newName.append(separatorChar).append(lowerCaseChar);
}
}
if (i > 1) {
secondLast = last;
}
last = c;
}
return newName.toString();
}
}
}