/* * Copyright 2002-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 org.springframework.core; import java.io.Externalizable; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * Provides methods to support various naming and other conventions used * throughout the framework. Mainly for internal use within the framework. * * @author Rob Harrop * @author Juergen Hoeller * @author Rossen Stoyanchev * @since 2.0 */ public abstract class Conventions { /** * Suffix added to names when using arrays. */ private static final String PLURAL_SUFFIX = "List"; /** * Set of interfaces that are supposed to be ignored * when searching for the 'primary' interface of a proxy. */ private static final Set<Class<?>> IGNORED_INTERFACES; static { IGNORED_INTERFACES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( Serializable.class, Externalizable.class, Cloneable.class, Comparable.class))); } private static final ReactiveAdapterRegistry reactiveAdapterRegistry = new ReactiveAdapterRegistry(); /** * Determine the conventional variable name for the supplied {@code Object} * based on its concrete type. The convention used is to return the * un-capitalized short name of the {@code Class}, according to JavaBeans * property naming rules. * * <p>For example:<br> * {@code com.myapp.Product} becomes {@code "product"}<br> * {@code com.myapp.MyProduct} becomes {@code "myProduct"}<br> * {@code com.myapp.UKProduct} becomes {@code "UKProduct"}<br> * * <p>For arrays the pluralized version of the array component type is used. * For {@code Collection}s an attempt is made to 'peek ahead' to determine * the component type and return its pluralized version. * * @param value the value to generate a variable name for * @return the generated variable name */ public static String getVariableName(Object value) { Assert.notNull(value, "Value must not be null"); Class<?> valueClass; boolean pluralize = false; if (value.getClass().isArray()) { valueClass = value.getClass().getComponentType(); pluralize = true; } else if (value instanceof Collection) { Collection<?> collection = (Collection<?>) value; if (collection.isEmpty()) { throw new IllegalArgumentException( "Cannot generate variable name for an empty Collection"); } Object valueToCheck = peekAhead(collection); valueClass = getClassForValue(valueToCheck); pluralize = true; } else { valueClass = getClassForValue(value); } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name); } /** * Determine the conventional variable name for the given parameter taking * the generic collection type, if any, into account. * * <p>As of 5.0 this method supports reactive types:<br> * {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br> * {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br> * {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br> * * @param parameter the method or constructor parameter * @return the generated variable name */ public static String getVariableNameForParameter(MethodParameter parameter) { Assert.notNull(parameter, "MethodParameter must not be null"); Class<?> valueClass; boolean pluralize = false; String reactiveSuffix = ""; if (parameter.getParameterType().isArray()) { valueClass = parameter.getParameterType().getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); if (valueClass == null) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection parameter type"); } pluralize = true; } else { valueClass = parameter.getParameterType(); if (reactiveAdapterRegistry.hasAdapters()) { ReactiveAdapter adapter = reactiveAdapterRegistry.getAdapter(valueClass); if (adapter != null && !adapter.getDescriptor().isNoValue()) { reactiveSuffix = ClassUtils.getShortName(valueClass); valueClass = parameter.nested().getNestedParameterType(); } } } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name + reactiveSuffix); } /** * Determine the conventional variable name for the return type of the * given method, taking the generic collection type, if any, into account. * @param method the method to generate a variable name for * @return the generated variable name */ public static String getVariableNameForReturnType(Method method) { return getVariableNameForReturnType(method, method.getReturnType(), null); } /** * Determine the conventional variable name for the return type of the given * method, taking the generic collection type, if any, into account, falling * back on the given actual return value if the method declaration is not * specific enough, e.g. {@code Object} return type or untyped collection. * @param method the method to generate a variable name for * @param value the return value (may be {@code null} if not available) * @return the generated variable name */ public static String getVariableNameForReturnType(Method method, Object value) { return getVariableNameForReturnType(method, method.getReturnType(), value); } /** * Determine the conventional variable name for the return type of the given * method, taking the generic collection type, if any, into account, falling * back on the given return value if the method declaration is not specific * enough, e.g. {@code Object} return type or untyped collection. * * <p>As of 5.0 this method supports reactive types:<br> * {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br> * {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br> * {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br> * * @param method the method to generate a variable name for * @param resolvedType the resolved return type of the method * @param value the return value (may be {@code null} if not available) * @return the generated variable name */ public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) { Assert.notNull(method, "Method must not be null"); if (Object.class == resolvedType) { if (value == null) { throw new IllegalArgumentException( "Cannot generate variable name for an Object return type with null value"); } return getVariableName(value); } Class<?> valueClass; boolean pluralize = false; String reactiveSuffix = ""; if (resolvedType.isArray()) { valueClass = resolvedType.getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(resolvedType)) { valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); if (valueClass == null) { if (!(value instanceof Collection)) { throw new IllegalArgumentException("Cannot generate variable name " + "for non-typed Collection return type and a non-Collection value"); } Collection<?> collection = (Collection<?>) value; if (collection.isEmpty()) { throw new IllegalArgumentException("Cannot generate variable name " + "for non-typed Collection return type and an empty Collection value"); } Object valueToCheck = peekAhead(collection); valueClass = getClassForValue(valueToCheck); } pluralize = true; } else { valueClass = resolvedType; if (reactiveAdapterRegistry.hasAdapters()) { ReactiveAdapter adapter = reactiveAdapterRegistry.getAdapter(valueClass); if (adapter != null && !adapter.getDescriptor().isNoValue()) { reactiveSuffix = ClassUtils.getShortName(valueClass); valueClass = ResolvableType.forMethodReturnType(method).getGeneric(0).resolve(); } } } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name + reactiveSuffix); } /** * Convert {@code String}s in attribute name format (e.g. lowercase, hyphens * separating words) into property name format (camel-case). For example * {@code transaction-manager} becomes {@code "transactionManager"}. */ public static String attributeNameToPropertyName(String attributeName) { Assert.notNull(attributeName, "'attributeName' must not be null"); if (!attributeName.contains("-")) { return attributeName; } char[] chars = attributeName.toCharArray(); char[] result = new char[chars.length -1]; // not completely accurate but good guess int currPos = 0; boolean upperCaseNext = false; for (char c : chars) { if (c == '-') { upperCaseNext = true; } else if (upperCaseNext) { result[currPos++] = Character.toUpperCase(c); upperCaseNext = false; } else { result[currPos++] = c; } } return new String(result, 0, currPos); } /** * Return an attribute name qualified by the given enclosing {@link Class}. * For example the attribute name '{@code foo}' qualified by {@link Class} * '{@code com.myapp.SomeClass}' would be '{@code com.myapp.SomeClass.foo}' */ public static String getQualifiedAttributeName(Class<?> enclosingClass, String attributeName) { Assert.notNull(enclosingClass, "'enclosingClass' must not be null"); Assert.notNull(attributeName, "'attributeName' must not be null"); return enclosingClass.getName() + '.' + attributeName; } /** * Determine the class to use for naming a variable containing the given value. * <p>Will return the class of the given value, except when encountering a * JDK proxy, in which case it will determine the 'primary' interface * implemented by that proxy. * @param value the value to check * @return the class to use for naming a variable */ private static Class<?> getClassForValue(Object value) { Class<?> valueClass = value.getClass(); if (Proxy.isProxyClass(valueClass)) { Class<?>[] ifcs = valueClass.getInterfaces(); for (Class<?> ifc : ifcs) { if (!IGNORED_INTERFACES.contains(ifc)) { return ifc; } } } else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) { // '$' in the class name but no inner class - // assuming it's a special subclass (e.g. by OpenJPA) valueClass = valueClass.getSuperclass(); } return valueClass; } /** * Pluralize the given name. */ private static String pluralize(String name) { return name + PLURAL_SUFFIX; } /** * Retrieve the {@code Class} of an element in the {@code Collection}. * The exact element for which the {@code Class} is retrieved will depend * on the concrete {@code Collection} implementation. */ private static <E> E peekAhead(Collection<E> collection) { Iterator<E> it = collection.iterator(); if (!it.hasNext()) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - no element found"); } E value = it.next(); if (value == null) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - only null element found"); } return value; } }