/* * Copyright 2008-2017 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 griffon.util; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Locale; /** * Contains 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 GriffonNameUtils { private static final String PROPERTY_SET_PREFIX = "set"; private static final String PROPERTY_GET_PREFIX = "get"; private static final String[] KEYWORDS = new String[]{ "abstract", "assert", "as", "break", "case", "catch", "class", "const", "continue", "default", "do", "else", "enum", "extends", "final", "finally", "for", "goto", "if", "implements", "import", "in", "instanceof", "interface", "native", "new", "package", "private", "protected", "public", "return", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while" }; /** * Finds out if the given String is a Java/Groovy keyword. * * @param str The String to test * @return <tt>true</tt> if the given String is a keyword, false otherwise */ public static boolean isKeyword(String str) { return !isBlank(str) && Arrays.binarySearch(KEYWORDS, str.toLowerCase(Locale.ENGLISH)) > -1; } /** * Capitalizes a String (makes the first char uppercase) taking care * of blank strings and single character strings. * * @param str The String to be capitalized * @return Capitalized version of the target string if it is not blank */ public static String capitalize(String str) { if (isBlank(str)) return str; if (str.length() == 1) return str.toUpperCase(); return str.substring(0, 1).toUpperCase(Locale.ENGLISH) + str.substring(1); } /** * Uncapitalizes a String (makes the first char lowercase) taking care * of blank strings and single character strings. * * @param str The String to be uncapitalized * @return Uncapitalized version of the target string if it is not blank */ public static String uncapitalize(String str) { if (isBlank(str)) return str; if (str.length() == 1) return String.valueOf(Character.toLowerCase(str.charAt(0))); return Character.toLowerCase(str.charAt(0)) + str.substring(1); } /** * 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) { return PROPERTY_SET_PREFIX + capitalize(propertyName); } /** * Calculate the name for a getter method to retrieve the specified property * * @param propertyName The property name * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints */ public static String getGetterName(String propertyName) { return PROPERTY_GET_PREFIX + capitalize(propertyName); } /** * 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] must not be null or blank"); } String className = capitalize(logicalName); if (trailingName != null) { className = className + trailingName; } return className; } /** * 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) { StringBuilder buf = new StringBuilder(); if (name != null && name.length() > 0) { String[] tokens = name.split("[^\\w\\d]"); for (String token1 : tokens) { String token = token1.trim(); buf.append(capitalize(token)); } } 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. */ public static String getClassNameForLowerCaseHyphenSeparatedName(String name) { // Handle null and empty strings. if (isBlank(name)) return name; if (name.indexOf('-') > -1) { StringBuilder buf = new StringBuilder(); String[] tokens = name.split("-"); for (String token : tokens) { if (token == null || token.length() == 0) continue; buf.append(capitalize(token)); } return buf.toString(); } return capitalize(name); } /** * Retrieves the logical class name of a Griffon artifact given the Griffon 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)) { String shortName = getShortName(name); if (shortName.endsWith(trailingName)) { return shortName.substring(0, shortName.length() - trailingName.length()); } } return name; } 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 representation of the class name (eg. MyClass becomes myClass) */ public static String getPropertyNameRepresentation(Class<?> targetClass) { String shortName = getShortName(targetClass); return getPropertyNameRepresentation(shortName); } /** * 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) { if (isBlank(name)) return 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) { String className = targetClass.getName(); return getShortName(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 getShortName(String className) { if (isBlank(className)) return className; int i = className.lastIndexOf("."); if (i > -1) { className = className.substring(i + 1, className.length()); } return className; } /** * 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); if (isBlank(name)) return name; List<String> words = new ArrayList<>(); int i = 0; char[] chars = name.toCharArray(); for (char c : chars) { 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))) { 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 GriffonNameUtils * to have a dependency on any external library to minimise the number of * dependencies required to bootstrap Griffon.</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) { if (str == null || str.length() == 0) { return true; } for (char c : str.toCharArray()) { if (!Character.isWhitespace(c)) { return false; } } return true; } /** * Checks that the specified String is not {@code blank}. This * method is designed primarily for doing parameter validation in methods * and constructors, as demonstrated below: * <blockquote><pre> * public Foo(String str) { * this.str = GriffonNameUtils.requireNonBlank(str); * } * </pre></blockquote> * * @param str the String to check for blank * @return {@code str} if not {@code blank} * @throws IllegalArgumentException if {@code str} is {@code blank} */ public static String requireNonBlank(String str) { if (isBlank(str)) { throw new IllegalArgumentException(); } return str; } /** * Checks that the specified String is not {@code blank} and * throws a customized {@link IllegalArgumentException} if it is. This method * is designed primarily for doing parameter validation in methods and * constructors with multiple parameters, as demonstrated below: * <blockquote><pre> * public Foo(String str) { * this.str = GriffonNameUtils.requireNonBlank(str, "str must not be null"); * } * </pre></blockquote> * * @param str the String to check for blank * @param message detail message to be used in the event that a {@code * IllegalArgumentException} is thrown * @return {@code str} if not {@code blank} * @throws IllegalArgumentException if {@code str} is {@code blank} */ public static String requireNonBlank(String str, String message) { if (isBlank(str)) { throw new IllegalArgumentException(message); } return str; } /** * Retrieves the hyphenated name representation of the supplied class. For example * MyFunkyGriffonThingy would be my-funky-griffon-thingy. * * @param clazz The class to convert * @return The hyphenated name representation */ public static String getHyphenatedName(Class<?> clazz) { if (clazz == null) { return null; } return getHyphenatedName(clazz.getName()); } /** * Retrieves the hyphenated name representation of the given class name. * For example MyFunkyGriffonThingy would be my-funky-griffon-thingy. * * @param name The class name to convert. * @return The hyphenated name representation. */ public static String getHyphenatedName(String name) { if (isBlank(name)) return name; if (name.endsWith(".groovy")) { name = name.substring(0, name.length() - 7); } String naturalName = getNaturalName(getShortName(name)); return naturalName.replaceAll("\\s", "-").toLowerCase(); } /** * Concatenates the <code>toString()</code> representation of each * item in this Iterable, with the given String as a separator between each item. * * @param self an Iterable of objects * @param separator a String separator * @return the joined String */ @Nonnull public static String join(@Nonnull Iterable self, @Nullable String separator) { StringBuilder buffer = new StringBuilder(); boolean first = true; if (separator == null) separator = ""; for (Object value : self) { if (first) { first = false; } else { buffer.append(separator); } buffer.append(String.valueOf(value)); } return buffer.toString(); } /** * Applies single or double quotes to a string if it contains whitespace characters * * @param str the String to be surrounded by quotes * @return a copy of the original String, surrounded by quotes */ public static String quote(String str) { if (isBlank(str)) return str; for (int i = 0; i < str.length(); i++) { if (Character.isWhitespace(str.charAt(i))) { str = applyQuotes(str); break; } } return str; } /** * Removes single or double quotes from a String * * @param str the String from which quotes will be removed * @return the unquoted String */ public static String unquote(String str) { if (isBlank(str)) return str; if ((str.startsWith("'") && str.endsWith("'")) || (str.startsWith("\"") && str.endsWith("\""))) { return str.substring(1, str.length() - 1); } return str; } private static String applyQuotes(String string) { if (string == null || string.length() == 0) { return "\"\""; } char b; char c = 0; int i; int len = string.length(); StringBuilder sb = new StringBuilder(len * 2); String t; char[] chars = string.toCharArray(); char[] buffer = new char[1030]; int bufferIndex = 0; sb.append('"'); for (i = 0; i < len; i += 1) { if (bufferIndex > 1024) { sb.append(buffer, 0, bufferIndex); bufferIndex = 0; } b = c; c = chars[i]; switch (c) { case '\\': case '"': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = c; break; case '/': if (b == '<') { buffer[bufferIndex++] = '\\'; } buffer[bufferIndex++] = c; break; default: if (c < ' ') { switch (c) { case '\b': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 'b'; break; case '\t': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 't'; break; case '\n': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 'n'; break; case '\f': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 'f'; break; case '\r': buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 'r'; break; default: t = "000" + Integer.toHexString(c); int tLength = t.length(); buffer[bufferIndex++] = '\\'; buffer[bufferIndex++] = 'u'; buffer[bufferIndex++] = t.charAt(tLength - 4); buffer[bufferIndex++] = t.charAt(tLength - 3); buffer[bufferIndex++] = t.charAt(tLength - 2); buffer[bufferIndex++] = t.charAt(tLength - 1); } } else { buffer[bufferIndex++] = c; } } } sb.append(buffer, 0, bufferIndex); sb.append('"'); return sb.toString(); } }