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.lang.reflect.Field; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.Locale; 023import java.util.Map; 024import java.util.Set; 025 026import org.springframework.util.Assert; 027import org.springframework.util.ReflectionUtils; 028 029/** 030 * This class can be used to parse other classes containing constant definitions 031 * in public static final members. The {@code asXXXX} methods of this class 032 * allow these constant values to be accessed via their string names. 033 * 034 * <p>Consider class Foo containing {@code public final static int CONSTANT1 = 66;} 035 * An instance of this class wrapping {@code Foo.class} will return the constant value 036 * of 66 from its {@code asNumber} method given the argument {@code "CONSTANT1"}. 037 * 038 * <p>This class is ideal for use in PropertyEditors, enabling them to 039 * recognize the same names as the constants themselves, and freeing them 040 * from maintaining their own mapping. 041 * 042 * @author Rod Johnson 043 * @author Juergen Hoeller 044 * @since 16.03.2003 045 */ 046public class Constants { 047 048 /** The name of the introspected class */ 049 private final String className; 050 051 /** Map from String field name to object value */ 052 private final Map<String, Object> fieldCache = new HashMap<String, Object>(); 053 054 055 /** 056 * Create a new Constants converter class wrapping the given class. 057 * <p>All <b>public</b> static final variables will be exposed, whatever their type. 058 * @param clazz the class to analyze 059 * @throws IllegalArgumentException if the supplied {@code clazz} is {@code null} 060 */ 061 public Constants(Class<?> clazz) { 062 Assert.notNull(clazz, "Class must not be null"); 063 this.className = clazz.getName(); 064 Field[] fields = clazz.getFields(); 065 for (Field field : fields) { 066 if (ReflectionUtils.isPublicStaticFinal(field)) { 067 String name = field.getName(); 068 try { 069 Object value = field.get(null); 070 this.fieldCache.put(name, value); 071 } 072 catch (IllegalAccessException ex) { 073 // just leave this field and continue 074 } 075 } 076 } 077 } 078 079 080 /** 081 * Return the name of the analyzed class. 082 */ 083 public final String getClassName() { 084 return this.className; 085 } 086 087 /** 088 * Return the number of constants exposed. 089 */ 090 public final int getSize() { 091 return this.fieldCache.size(); 092 } 093 094 /** 095 * Exposes the field cache to subclasses: 096 * a Map from String field name to object value. 097 */ 098 protected final Map<String, Object> getFieldCache() { 099 return this.fieldCache; 100 } 101 102 103 /** 104 * Return a constant value cast to a Number. 105 * @param code the name of the field (never {@code null}) 106 * @return the Number value 107 * @see #asObject 108 * @throws ConstantException if the field name wasn't found 109 * or if the type wasn't compatible with Number 110 */ 111 public Number asNumber(String code) throws ConstantException { 112 Object obj = asObject(code); 113 if (!(obj instanceof Number)) { 114 throw new ConstantException(this.className, code, "not a Number"); 115 } 116 return (Number) obj; 117 } 118 119 /** 120 * Return a constant value as a String. 121 * @param code the name of the field (never {@code null}) 122 * @return the String value 123 * Works even if it's not a string (invokes {@code toString()}). 124 * @see #asObject 125 * @throws ConstantException if the field name wasn't found 126 */ 127 public String asString(String code) throws ConstantException { 128 return asObject(code).toString(); 129 } 130 131 /** 132 * Parse the given String (upper or lower case accepted) and return 133 * the appropriate value if it's the name of a constant field in the 134 * class that we're analysing. 135 * @param code the name of the field (never {@code null}) 136 * @return the Object value 137 * @throws ConstantException if there's no such field 138 */ 139 public Object asObject(String code) throws ConstantException { 140 Assert.notNull(code, "Code must not be null"); 141 String codeToUse = code.toUpperCase(Locale.ENGLISH); 142 Object val = this.fieldCache.get(codeToUse); 143 if (val == null) { 144 throw new ConstantException(this.className, codeToUse, "not found"); 145 } 146 return val; 147 } 148 149 150 /** 151 * Return all names of the given group of constants. 152 * <p>Note that this method assumes that constants are named 153 * in accordance with the standard Java convention for constant 154 * values (i.e. all uppercase). The supplied {@code namePrefix} 155 * will be uppercased (in a locale-insensitive fashion) prior to 156 * the main logic of this method kicking in. 157 * @param namePrefix prefix of the constant names to search (may be {@code null}) 158 * @return the set of constant names 159 */ 160 public Set<String> getNames(String namePrefix) { 161 String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : ""); 162 Set<String> names = new HashSet<String>(); 163 for (String code : this.fieldCache.keySet()) { 164 if (code.startsWith(prefixToUse)) { 165 names.add(code); 166 } 167 } 168 return names; 169 } 170 171 /** 172 * Return all names of the group of constants for the 173 * given bean property name. 174 * @param propertyName the name of the bean property 175 * @return the set of values 176 * @see #propertyToConstantNamePrefix 177 */ 178 public Set<String> getNamesForProperty(String propertyName) { 179 return getNames(propertyToConstantNamePrefix(propertyName)); 180 } 181 182 /** 183 * Return all names of the given group of constants. 184 * <p>Note that this method assumes that constants are named 185 * in accordance with the standard Java convention for constant 186 * values (i.e. all uppercase). The supplied {@code nameSuffix} 187 * will be uppercased (in a locale-insensitive fashion) prior to 188 * the main logic of this method kicking in. 189 * @param nameSuffix suffix of the constant names to search (may be {@code null}) 190 * @return the set of constant names 191 */ 192 public Set<String> getNamesForSuffix(String nameSuffix) { 193 String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : ""); 194 Set<String> names = new HashSet<String>(); 195 for (String code : this.fieldCache.keySet()) { 196 if (code.endsWith(suffixToUse)) { 197 names.add(code); 198 } 199 } 200 return names; 201 } 202 203 204 /** 205 * Return all values of the given group of constants. 206 * <p>Note that this method assumes that constants are named 207 * in accordance with the standard Java convention for constant 208 * values (i.e. all uppercase). The supplied {@code namePrefix} 209 * will be uppercased (in a locale-insensitive fashion) prior to 210 * the main logic of this method kicking in. 211 * @param namePrefix prefix of the constant names to search (may be {@code null}) 212 * @return the set of values 213 */ 214 public Set<Object> getValues(String namePrefix) { 215 String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : ""); 216 Set<Object> values = new HashSet<Object>(); 217 for (String code : this.fieldCache.keySet()) { 218 if (code.startsWith(prefixToUse)) { 219 values.add(this.fieldCache.get(code)); 220 } 221 } 222 return values; 223 } 224 225 /** 226 * Return all values of the group of constants for the 227 * given bean property name. 228 * @param propertyName the name of the bean property 229 * @return the set of values 230 * @see #propertyToConstantNamePrefix 231 */ 232 public Set<Object> getValuesForProperty(String propertyName) { 233 return getValues(propertyToConstantNamePrefix(propertyName)); 234 } 235 236 /** 237 * Return all values of the given group of constants. 238 * <p>Note that this method assumes that constants are named 239 * in accordance with the standard Java convention for constant 240 * values (i.e. all uppercase). The supplied {@code nameSuffix} 241 * will be uppercased (in a locale-insensitive fashion) prior to 242 * the main logic of this method kicking in. 243 * @param nameSuffix suffix of the constant names to search (may be {@code null}) 244 * @return the set of values 245 */ 246 public Set<Object> getValuesForSuffix(String nameSuffix) { 247 String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : ""); 248 Set<Object> values = new HashSet<Object>(); 249 for (String code : this.fieldCache.keySet()) { 250 if (code.endsWith(suffixToUse)) { 251 values.add(this.fieldCache.get(code)); 252 } 253 } 254 return values; 255 } 256 257 258 /** 259 * Look up the given value within the given group of constants. 260 * <p>Will return the first match. 261 * @param value constant value to look up 262 * @param namePrefix prefix of the constant names to search (may be {@code null}) 263 * @return the name of the constant field 264 * @throws ConstantException if the value wasn't found 265 */ 266 public String toCode(Object value, String namePrefix) throws ConstantException { 267 String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : ""); 268 for (Map.Entry<String, Object> entry : this.fieldCache.entrySet()) { 269 if (entry.getKey().startsWith(prefixToUse) && entry.getValue().equals(value)) { 270 return entry.getKey(); 271 } 272 } 273 throw new ConstantException(this.className, prefixToUse, value); 274 } 275 276 /** 277 * Look up the given value within the group of constants for 278 * the given bean property name. Will return the first match. 279 * @param value constant value to look up 280 * @param propertyName the name of the bean property 281 * @return the name of the constant field 282 * @throws ConstantException if the value wasn't found 283 * @see #propertyToConstantNamePrefix 284 */ 285 public String toCodeForProperty(Object value, String propertyName) throws ConstantException { 286 return toCode(value, propertyToConstantNamePrefix(propertyName)); 287 } 288 289 /** 290 * Look up the given value within the given group of constants. 291 * <p>Will return the first match. 292 * @param value constant value to look up 293 * @param nameSuffix suffix of the constant names to search (may be {@code null}) 294 * @return the name of the constant field 295 * @throws ConstantException if the value wasn't found 296 */ 297 public String toCodeForSuffix(Object value, String nameSuffix) throws ConstantException { 298 String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : ""); 299 for (Map.Entry<String, Object> entry : this.fieldCache.entrySet()) { 300 if (entry.getKey().endsWith(suffixToUse) && entry.getValue().equals(value)) { 301 return entry.getKey(); 302 } 303 } 304 throw new ConstantException(this.className, suffixToUse, value); 305 } 306 307 308 /** 309 * Convert the given bean property name to a constant name prefix. 310 * <p>Uses a common naming idiom: turning all lower case characters to 311 * upper case, and prepending upper case characters with an underscore. 312 * <p>Example: "imageSize" -> "IMAGE_SIZE"<br> 313 * Example: "imagesize" -> "IMAGESIZE".<br> 314 * Example: "ImageSize" -> "_IMAGE_SIZE".<br> 315 * Example: "IMAGESIZE" -> "_I_M_A_G_E_S_I_Z_E" 316 * @param propertyName the name of the bean property 317 * @return the corresponding constant name prefix 318 * @see #getValuesForProperty 319 * @see #toCodeForProperty 320 */ 321 public String propertyToConstantNamePrefix(String propertyName) { 322 StringBuilder parsedPrefix = new StringBuilder(); 323 for (int i = 0; i < propertyName.length(); i++) { 324 char c = propertyName.charAt(i); 325 if (Character.isUpperCase(c)) { 326 parsedPrefix.append("_"); 327 parsedPrefix.append(c); 328 } 329 else { 330 parsedPrefix.append(Character.toUpperCase(c)); 331 } 332 } 333 return parsedPrefix.toString(); 334 } 335 336}