All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.starter.util.NameUtils Maven / Gradle / Ivy

There is a newer version: 4.7.0
Show newest version
/*
 * Copyright 2017-2022 original 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
 *
 * https://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 io.micronaut.starter.util;

import io.micronaut.core.util.StringUtils;
import io.micronaut.starter.application.Project;
import io.micronaut.starter.application.ProjectIdentifier;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public final class NameUtils {
    public static final String MICRONAUT = "micronaut";
    private static final String PROPERTY_SET_PREFIX = "set";
    private static final String PROPERTY_GET_PREFIX = "get";

    private static final Pattern SERVICE_ID_REGEX = Pattern.compile("[\\p{javaLowerCase}\\d-]+");

    private NameUtils() {
    }

    public static ProjectIdentifier parseProjectIdentifier(String name) throws IllegalArgumentException {
        if (!isValidAppName(name)) {
            throw new IllegalArgumentException("\"" + name + "\" is not a valid app name");
        }
        String packageName;
        String appName;
        String[] parts = name.split("\\.");
        if (parts.length == 1) {
            appName = parts[0];
            packageName = createPackageName(appName);
        } else {
            if (parts.length == 0) {
                throw new IllegalArgumentException("Cannot create a valid package name for [" + name + "]. Please specify a name that is also a valid Java package.");
            }
            int lastIdx = parts.length - 1;
            appName = parts[lastIdx];
            packageName = Arrays.stream(parts).limit(lastIdx).collect(Collectors.joining("."));
        }
        if (!isValidJavaPackage(packageName)) {
            throw new IllegalArgumentException("Cannot create a valid package name for [" + packageName + "]. Please specify a name that is also a valid Java package.");
        }
        if (!isValidAppName(appName)) {
            throw new IllegalArgumentException("\"" + appName + "\" is not a valid app name");
        }
        return new ProjectIdentifier(packageName, appName);
    }

    /**
     * verifies the app is not blank, null or matches {@literal micronaut}
     * @param appName Application's name
     * @return True if it is valid
     */
    public static boolean isValidAppName(String appName) {
        return !StringUtils.isEmpty(appName) && !appName.equalsIgnoreCase(MICRONAUT);
    }

    public static Project parse(String name) throws IllegalArgumentException {
        ProjectIdentifier projectIdentifier = parseProjectIdentifier(name);
        String packageName = projectIdentifier.getPackageName();
        String appName = projectIdentifier.getName();
        String packagePath = packageName.replace('.', '/');
        String className = getNameFromScript(appName);
        String naturalName = getNaturalName(appName);
        String propertyName = getPropertyName(appName);

        return new Project(packageName, packagePath, className, naturalName, propertyName, appName);
    }

    private static String createPackageName(String appName) {
        StringBuilder packageNameBuilder = new StringBuilder();
        for (char c: appName.toCharArray()) {
            if (c == '-') {
                packageNameBuilder.append('.');
            }
            if (Character.isJavaIdentifierPart(c)) {
                packageNameBuilder.append(c);
            }
        }
        return packageNameBuilder.toString();
    }

    /**
     * Checks whether the given name is a valid service identifier.
     *
     * @param name The name
     * @return True if it is
     */
    public static boolean isValidServiceId(String name) {
        return name != null && name.length() > 0 && SERVICE_ID_REGEX.matcher(name).matches() && Character.isLetter(name.charAt(0));
    }

    /**
     * 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 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) {
        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
     * @return The full 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
     * @return the full 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 Micronaut artifact given the Micronaut class
     * and a specified trailing name.
     *
     * @param clazz        The class
     * @param trailingName The trailing name such as "Controller" or "Service"
     * @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.contains(trailingName)) {
            return name;
        }

        return shortName.substring(0, shortName.length() - trailingName.length());
    }

    /**
     * @param className    The class name
     * @param trailingName The trailing name
     * @return The logical property 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 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);
        }

        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
     */
    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 from which to get a short name
     * @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);
        }
        return className;
    }

    /**
     * Returns the package name without the class.
     *
     * @param className The class name from which to get a package name
     * @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.toLowerCase();
    }

    /**
     * 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 null
     * if descriptorName is null, or an empty string
     * if descriptorName 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);

        if (isBlank(name)) {
            return name;
        }

        if (name.length() == 1) {
            return name.toUpperCase();
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append(name.charAt(name.length() - 1));
            //Traversing the string in reverse order
            for (int i = name.length() - 2; i > 0; i--) {
                char currChar = name.charAt(i);
                char prevChar = name.charAt(i - 1);
                char nextChar = name.charAt(i + 1);

                boolean isCurrentCharLowerCase = Character.isLowerCase(currChar);
                boolean isPrevCharLowerCase = Character.isLowerCase(prevChar);
                boolean isNextCharLowerCase = Character.isLowerCase(nextChar);
                boolean isPrevCharHyphen = prevChar == '-';
                boolean isCurCharHyphen = currChar == '-';

                if (isCurCharHyphen) {
                    // hyphen is replaced by space
                    sb.append(" ");
                } else if (isPrevCharHyphen) {
                    // and next letter after hyphen becomes upper case
                    sb.append(Character.toUpperCase(currChar));
                } else if (isCurrentCharLowerCase != isPrevCharLowerCase && !isCurrentCharLowerCase) {
                    sb.append(currChar).append(" ");
                } else if (isCurrentCharLowerCase == isPrevCharLowerCase && !isCurrentCharLowerCase && isNextCharLowerCase) {
                    sb.append(currChar).append(" ");
                } else {
                    sb.append(currChar);
                }
            }
            //The first character of the string is always in Upper case
            sb.append(Character.toUpperCase(name.charAt(0)));
            return sb.reverse().toString();
        }
    }

    /**
     * Converts an enum style name into its natural language equivalent eg ('FIRST_NAME' becomes 'First Name').
     *
     * @param name The string to convert
     * @return The converted property name
     */
    public static String getNaturalNameOfEnum(String name) {
        return getNaturalName(io.micronaut.core.naming.NameUtils.camelCase(name.toLowerCase()));
    }

    /**
     * 

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 NameUtils * to have a dependency on any external library to minimise the number of * dependencies required to bootstrap Micronaut.

* * @param str The string to test. * @return true if the string is null, or * blank. */ public static boolean isBlank(String str) { return str == null || str.trim().length() == 0; } /** * 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 coll) { 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) { 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); return isPropertyMethodSuffix(name); } else if (name.startsWith("is") && returnType == boolean.class) { name = name.substring(2); return isPropertyMethodSuffix(name); } 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 */ 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy