001/* 002 * Copyright 2002-2017 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.io.Externalizable; 020import java.io.Serializable; 021import java.lang.reflect.Method; 022import java.lang.reflect.Proxy; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.Set; 029 030import org.springframework.util.Assert; 031import org.springframework.util.ClassUtils; 032 033/** 034 * Provides methods to support various naming and other conventions used 035 * throughout the framework. Mainly for internal use within the framework. 036 * 037 * @author Rob Harrop 038 * @author Juergen Hoeller 039 * @since 2.0 040 */ 041public abstract class Conventions { 042 043 /** 044 * Suffix added to names when using arrays. 045 */ 046 private static final String PLURAL_SUFFIX = "List"; 047 048 /** 049 * Set of interfaces that are supposed to be ignored 050 * when searching for the 'primary' interface of a proxy. 051 */ 052 private static final Set<Class<?>> IGNORED_INTERFACES; 053 054 static { 055 IGNORED_INTERFACES = Collections.unmodifiableSet(new HashSet<Class<?>>( 056 Arrays.<Class<?>>asList(Serializable.class, Externalizable.class, Cloneable.class, Comparable.class))); 057 } 058 059 060 /** 061 * Determine the conventional variable name for the supplied 062 * {@code Object} based on its concrete type. The convention 063 * used is to return the uncapitalized short name of the {@code Class}, 064 * according to JavaBeans property naming rules: So, 065 * {@code com.myapp.Product} becomes {@code product}; 066 * {@code com.myapp.MyProduct} becomes {@code myProduct}; 067 * {@code com.myapp.UKProduct} becomes {@code UKProduct}. 068 * <p>For arrays, we use the pluralized version of the array component type. 069 * For {@code Collection}s we attempt to 'peek ahead' in the 070 * {@code Collection} to determine the component type and 071 * return the pluralized version of that component type. 072 * @param value the value to generate a variable name for 073 * @return the generated variable name 074 */ 075 public static String getVariableName(Object value) { 076 Assert.notNull(value, "Value must not be null"); 077 Class<?> valueClass; 078 boolean pluralize = false; 079 080 if (value.getClass().isArray()) { 081 valueClass = value.getClass().getComponentType(); 082 pluralize = true; 083 } 084 else if (value instanceof Collection) { 085 Collection<?> collection = (Collection<?>) value; 086 if (collection.isEmpty()) { 087 throw new IllegalArgumentException("Cannot generate variable name for an empty Collection"); 088 } 089 Object valueToCheck = peekAhead(collection); 090 valueClass = getClassForValue(valueToCheck); 091 pluralize = true; 092 } 093 else { 094 valueClass = getClassForValue(value); 095 } 096 097 String name = ClassUtils.getShortNameAsProperty(valueClass); 098 return (pluralize ? pluralize(name) : name); 099 } 100 101 /** 102 * Determine the conventional variable name for the supplied parameter, 103 * taking the generic collection type (if any) into account. 104 * @param parameter the method or constructor parameter to generate a variable name for 105 * @return the generated variable name 106 */ 107 public static String getVariableNameForParameter(MethodParameter parameter) { 108 Assert.notNull(parameter, "MethodParameter must not be null"); 109 Class<?> valueClass; 110 boolean pluralize = false; 111 112 if (parameter.getParameterType().isArray()) { 113 valueClass = parameter.getParameterType().getComponentType(); 114 pluralize = true; 115 } 116 else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { 117 valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); 118 if (valueClass == null) { 119 throw new IllegalArgumentException( 120 "Cannot generate variable name for non-typed Collection parameter type"); 121 } 122 pluralize = true; 123 } 124 else { 125 valueClass = parameter.getParameterType(); 126 } 127 128 String name = ClassUtils.getShortNameAsProperty(valueClass); 129 return (pluralize ? pluralize(name) : name); 130 } 131 132 /** 133 * Determine the conventional variable name for the return type of the supplied method, 134 * 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 supplied method, 144 * taking the generic collection type (if any) into account, falling back to the 145 * given return value if the method declaration is not specific enough (i.e. in case of 146 * the return type being declared as {@code Object} or as 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, Object value) { 152 return getVariableNameForReturnType(method, method.getReturnType(), value); 153 } 154 155 /** 156 * Determine the conventional variable name for the return type of the supplied method, 157 * taking the generic collection type (if any) into account, falling back to the 158 * given return value if the method declaration is not specific enough (i.e. in case of 159 * the return type being declared as {@code Object} or as untyped collection). 160 * @param method the method to generate a variable name for 161 * @param resolvedType the resolved return type of the method 162 * @param value the return value (may be {@code null} if not available) 163 * @return the generated variable name 164 */ 165 public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) { 166 Assert.notNull(method, "Method must not be null"); 167 168 if (Object.class == resolvedType) { 169 if (value == null) { 170 throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value"); 171 } 172 return getVariableName(value); 173 } 174 175 Class<?> valueClass; 176 boolean pluralize = false; 177 178 if (resolvedType.isArray()) { 179 valueClass = resolvedType.getComponentType(); 180 pluralize = true; 181 } 182 else if (Collection.class.isAssignableFrom(resolvedType)) { 183 valueClass = ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); 184 if (valueClass == null) { 185 if (!(value instanceof Collection)) { 186 throw new IllegalArgumentException( 187 "Cannot generate variable name for non-typed Collection return type and a non-Collection value"); 188 } 189 Collection<?> collection = (Collection<?>) value; 190 if (collection.isEmpty()) { 191 throw new IllegalArgumentException( 192 "Cannot generate variable name for non-typed Collection return type and an empty Collection value"); 193 } 194 Object valueToCheck = peekAhead(collection); 195 valueClass = getClassForValue(valueToCheck); 196 } 197 pluralize = true; 198 } 199 else { 200 valueClass = resolvedType; 201 } 202 203 String name = ClassUtils.getShortNameAsProperty(valueClass); 204 return (pluralize ? pluralize(name) : name); 205 } 206 207 /** 208 * Convert {@code String}s in attribute name format (lowercase, hyphens separating words) 209 * into property name format (camel-cased). For example, {@code transaction-manager} is 210 * converted into {@code transactionManager}. 211 */ 212 public static String attributeNameToPropertyName(String attributeName) { 213 Assert.notNull(attributeName, "'attributeName' must not be null"); 214 if (!attributeName.contains("-")) { 215 return attributeName; 216 } 217 char[] chars = attributeName.toCharArray(); 218 char[] result = new char[chars.length -1]; // not completely accurate but good guess 219 int currPos = 0; 220 boolean upperCaseNext = false; 221 for (char c : chars) { 222 if (c == '-') { 223 upperCaseNext = true; 224 } 225 else if (upperCaseNext) { 226 result[currPos++] = Character.toUpperCase(c); 227 upperCaseNext = false; 228 } 229 else { 230 result[currPos++] = c; 231 } 232 } 233 return new String(result, 0, currPos); 234 } 235 236 /** 237 * Return an attribute name qualified by the supplied enclosing {@link Class}. For example, 238 * the attribute name '{@code foo}' qualified by {@link Class} '{@code com.myapp.SomeClass}' 239 * would be '{@code com.myapp.SomeClass.foo}' 240 */ 241 public static String getQualifiedAttributeName(Class<?> enclosingClass, String attributeName) { 242 Assert.notNull(enclosingClass, "'enclosingClass' must not be null"); 243 Assert.notNull(attributeName, "'attributeName' must not be null"); 244 return enclosingClass.getName() + '.' + attributeName; 245 } 246 247 248 /** 249 * Determines the class to use for naming a variable that contains 250 * the given value. 251 * <p>Will return the class of the given value, except when 252 * encountering a JDK proxy, in which case it will determine 253 * the 'primary' interface implemented by that proxy. 254 * @param value the value to check 255 * @return the class to use for naming a variable 256 */ 257 private static Class<?> getClassForValue(Object value) { 258 Class<?> valueClass = value.getClass(); 259 if (Proxy.isProxyClass(valueClass)) { 260 Class<?>[] ifcs = valueClass.getInterfaces(); 261 for (Class<?> ifc : ifcs) { 262 if (!IGNORED_INTERFACES.contains(ifc)) { 263 return ifc; 264 } 265 } 266 } 267 else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) { 268 // '$' in the class name but no inner class - 269 // assuming it's a special subclass (e.g. by OpenJPA) 270 valueClass = valueClass.getSuperclass(); 271 } 272 return valueClass; 273 } 274 275 /** 276 * Pluralize the given name. 277 */ 278 private static String pluralize(String name) { 279 return name + PLURAL_SUFFIX; 280 } 281 282 /** 283 * Retrieves the {@code Class} of an element in the {@code Collection}. 284 * The exact element for which the {@code Class} is retrieved will depend 285 * on the concrete {@code Collection} implementation. 286 */ 287 private static <E> E peekAhead(Collection<E> collection) { 288 Iterator<E> it = collection.iterator(); 289 if (!it.hasNext()) { 290 throw new IllegalStateException( 291 "Unable to peek ahead in non-empty collection - no element found"); 292 } 293 E value = it.next(); 294 if (value == null) { 295 throw new IllegalStateException( 296 "Unable to peek ahead in non-empty collection - only null element found"); 297 } 298 return value; 299 } 300 301}