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}