001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Proxy; 021import java.util.Collection; 022import java.util.Iterator; 023 024import org.springframework.lang.Nullable; 025import org.springframework.util.Assert; 026import org.springframework.util.ClassUtils; 027 028/** 029 * Provides methods to support various naming and other conventions used 030 * throughout the framework. Mainly for internal use within the framework. 031 * 032 * @author Rob Harrop 033 * @author Juergen Hoeller 034 * @author Rossen Stoyanchev 035 * @since 2.0 036 */ 037public final class Conventions { 038 039 /** 040 * Suffix added to names when using arrays. 041 */ 042 private static final String PLURAL_SUFFIX = "List"; 043 044 045 private Conventions() { 046 } 047 048 049 /** 050 * Determine the conventional variable name for the supplied {@code Object} 051 * based on its concrete type. The convention used is to return the 052 * un-capitalized short name of the {@code Class}, according to JavaBeans 053 * property naming rules. 054 * <p>For example:<br> 055 * {@code com.myapp.Product} becomes {@code "product"}<br> 056 * {@code com.myapp.MyProduct} becomes {@code "myProduct"}<br> 057 * {@code com.myapp.UKProduct} becomes {@code "UKProduct"}<br> 058 * <p>For arrays the pluralized version of the array component type is used. 059 * For {@code Collection}s an attempt is made to 'peek ahead' to determine 060 * the component type and return its pluralized version. 061 * @param value the value to generate a variable name for 062 * @return the generated variable name 063 */ 064 public static String getVariableName(Object value) { 065 Assert.notNull(value, "Value must not be null"); 066 Class<?> valueClass; 067 boolean pluralize = false; 068 069 if (value.getClass().isArray()) { 070 valueClass = value.getClass().getComponentType(); 071 pluralize = true; 072 } 073 else if (value instanceof Collection) { 074 Collection<?> collection = (Collection<?>) value; 075 if (collection.isEmpty()) { 076 throw new IllegalArgumentException( 077 "Cannot generate variable name for an empty Collection"); 078 } 079 Object valueToCheck = peekAhead(collection); 080 valueClass = getClassForValue(valueToCheck); 081 pluralize = true; 082 } 083 else { 084 valueClass = getClassForValue(value); 085 } 086 087 String name = ClassUtils.getShortNameAsProperty(valueClass); 088 return (pluralize ? pluralize(name) : name); 089 } 090 091 /** 092 * Determine the conventional variable name for the given parameter taking 093 * the generic collection type, if any, into account. 094 * <p>As of 5.0 this method supports reactive types:<br> 095 * {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br> 096 * {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br> 097 * {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br> 098 * @param parameter the method or constructor parameter 099 * @return the generated variable name 100 */ 101 public static String getVariableNameForParameter(MethodParameter parameter) { 102 Assert.notNull(parameter, "MethodParameter must not be null"); 103 Class<?> valueClass; 104 boolean pluralize = false; 105 String reactiveSuffix = ""; 106 107 if (parameter.getParameterType().isArray()) { 108 valueClass = parameter.getParameterType().getComponentType(); 109 pluralize = true; 110 } 111 else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { 112 valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); 113 if (valueClass == null) { 114 throw new IllegalArgumentException( 115 "Cannot generate variable name for non-typed Collection parameter type"); 116 } 117 pluralize = true; 118 } 119 else { 120 valueClass = parameter.getParameterType(); 121 ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(valueClass); 122 if (adapter != null && !adapter.getDescriptor().isNoValue()) { 123 reactiveSuffix = ClassUtils.getShortName(valueClass); 124 valueClass = parameter.nested().getNestedParameterType(); 125 } 126 } 127 128 String name = ClassUtils.getShortNameAsProperty(valueClass); 129 return (pluralize ? pluralize(name) : name + reactiveSuffix); 130 } 131 132 /** 133 * Determine the conventional variable name for the return type of the 134 * given method, taking the generic collection type, if any, into account. 135 * @param method the method to generate a variable name for 136 * @return the generated variable name 137 */ 138 public static String getVariableNameForReturnType(Method method) { 139 return getVariableNameForReturnType(method, method.getReturnType(), null); 140 } 141 142 /** 143 * Determine the conventional variable name for the return type of the given 144 * method, taking the generic collection type, if any, into account, falling 145 * back on the given actual return value if the method declaration is not 146 * specific enough, e.g. {@code Object} return type or untyped collection. 147 * @param method the method to generate a variable name for 148 * @param value the return value (may be {@code null} if not available) 149 * @return the generated variable name 150 */ 151 public static String getVariableNameForReturnType(Method method, @Nullable Object value) { 152 return getVariableNameForReturnType(method, method.getReturnType(), value); 153 } 154 155 /** 156 * Determine the conventional variable name for the return type of the given 157 * method, taking the generic collection type, if any, into account, falling 158 * back on the given return value if the method declaration is not specific 159 * enough, e.g. {@code Object} return type or untyped collection. 160 * <p>As of 5.0 this method supports reactive types:<br> 161 * {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br> 162 * {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br> 163 * {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br> 164 * @param method the method to generate a variable name for 165 * @param resolvedType the resolved return type of the method 166 * @param value the return value (may be {@code null} if not available) 167 * @return the generated variable name 168 */ 169 public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, @Nullable Object value) { 170 Assert.notNull(method, "Method must not be null"); 171 172 if (Object.class == resolvedType) { 173 if (value == null) { 174 throw new IllegalArgumentException( 175 "Cannot generate variable name for an Object return type with null value"); 176 } 177 return getVariableName(value); 178 } 179 180 Class<?> valueClass; 181 boolean pluralize = false; 182 String reactiveSuffix = ""; 183 184 if (resolvedType.isArray()) { 185 valueClass = resolvedType.getComponentType(); 186 pluralize = true; 187 } 188 else if (Collection.class.isAssignableFrom(resolvedType)) { 189 valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); 190 if (valueClass == null) { 191 if (!(value instanceof Collection)) { 192 throw new IllegalArgumentException("Cannot generate variable name " + 193 "for non-typed Collection return type and a non-Collection value"); 194 } 195 Collection<?> collection = (Collection<?>) value; 196 if (collection.isEmpty()) { 197 throw new IllegalArgumentException("Cannot generate variable name " + 198 "for non-typed Collection return type and an empty Collection value"); 199 } 200 Object valueToCheck = peekAhead(collection); 201 valueClass = getClassForValue(valueToCheck); 202 } 203 pluralize = true; 204 } 205 else { 206 valueClass = resolvedType; 207 ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(valueClass); 208 if (adapter != null && !adapter.getDescriptor().isNoValue()) { 209 reactiveSuffix = ClassUtils.getShortName(valueClass); 210 valueClass = ResolvableType.forMethodReturnType(method).getGeneric().toClass(); 211 } 212 } 213 214 String name = ClassUtils.getShortNameAsProperty(valueClass); 215 return (pluralize ? pluralize(name) : name + reactiveSuffix); 216 } 217 218 /** 219 * Convert {@code String}s in attribute name format (e.g. lowercase, hyphens 220 * separating words) into property name format (camel-case). For example 221 * {@code transaction-manager} becomes {@code "transactionManager"}. 222 */ 223 public static String attributeNameToPropertyName(String attributeName) { 224 Assert.notNull(attributeName, "'attributeName' must not be null"); 225 if (!attributeName.contains("-")) { 226 return attributeName; 227 } 228 char[] chars = attributeName.toCharArray(); 229 char[] result = new char[chars.length -1]; // not completely accurate but good guess 230 int currPos = 0; 231 boolean upperCaseNext = false; 232 for (char c : chars) { 233 if (c == '-') { 234 upperCaseNext = true; 235 } 236 else if (upperCaseNext) { 237 result[currPos++] = Character.toUpperCase(c); 238 upperCaseNext = false; 239 } 240 else { 241 result[currPos++] = c; 242 } 243 } 244 return new String(result, 0, currPos); 245 } 246 247 /** 248 * Return an attribute name qualified by the given enclosing {@link Class}. 249 * For example the attribute name '{@code foo}' qualified by {@link Class} 250 * '{@code com.myapp.SomeClass}' would be '{@code com.myapp.SomeClass.foo}' 251 */ 252 public static String getQualifiedAttributeName(Class<?> enclosingClass, String attributeName) { 253 Assert.notNull(enclosingClass, "'enclosingClass' must not be null"); 254 Assert.notNull(attributeName, "'attributeName' must not be null"); 255 return enclosingClass.getName() + '.' + attributeName; 256 } 257 258 259 /** 260 * Determine the class to use for naming a variable containing the given value. 261 * <p>Will return the class of the given value, except when encountering a 262 * JDK proxy, in which case it will determine the 'primary' interface 263 * implemented by that proxy. 264 * @param value the value to check 265 * @return the class to use for naming a variable 266 */ 267 private static Class<?> getClassForValue(Object value) { 268 Class<?> valueClass = value.getClass(); 269 if (Proxy.isProxyClass(valueClass)) { 270 Class<?>[] ifcs = valueClass.getInterfaces(); 271 for (Class<?> ifc : ifcs) { 272 if (!ClassUtils.isJavaLanguageInterface(ifc)) { 273 return ifc; 274 } 275 } 276 } 277 else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) { 278 // '$' in the class name but no inner class - 279 // assuming it's a special subclass (e.g. by OpenJPA) 280 valueClass = valueClass.getSuperclass(); 281 } 282 return valueClass; 283 } 284 285 /** 286 * Pluralize the given name. 287 */ 288 private static String pluralize(String name) { 289 return name + PLURAL_SUFFIX; 290 } 291 292 /** 293 * Retrieve the {@code Class} of an element in the {@code Collection}. 294 * The exact element for which the {@code Class} is retrieved will depend 295 * on the concrete {@code Collection} implementation. 296 */ 297 private static <E> E peekAhead(Collection<E> collection) { 298 Iterator<E> it = collection.iterator(); 299 if (!it.hasNext()) { 300 throw new IllegalStateException( 301 "Unable to peek ahead in non-empty collection - no element found"); 302 } 303 E value = it.next(); 304 if (value == null) { 305 throw new IllegalStateException( 306 "Unable to peek ahead in non-empty collection - only null element found"); 307 } 308 return value; 309 } 310 311}