001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.awt.Color;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.InvocationTargetException;
023: import java.math.BigDecimal;
024: import java.math.BigInteger;
025: import java.net.MalformedURLException;
026: import java.net.URL;
027: import java.text.ParseException;
028: import java.text.SimpleDateFormat;
029: import java.util.ArrayList;
030: import java.util.Calendar;
031: import java.util.Collection;
032: import java.util.Date;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Locale;
036:
037: import org.apache.commons.collections.IteratorUtils;
038: import org.apache.commons.collections.iterators.IteratorChain;
039: import org.apache.commons.collections.iterators.SingletonIterator;
040: import org.apache.commons.lang.BooleanUtils;
041: import org.apache.commons.lang.StringUtils;
042:
043: /**
044: * A utility class to convert the configuration properties into any type.
045: *
046: * @author Emmanuel Bourg
047: * @version $Revision: 490321 $, $Date: 2006-12-26 17:19:07 +0100 (Di, 26 Dez 2006) $
048: * @since 1.1
049: */
050: public final class PropertyConverter {
051: /** Constant for the list delimiter escaping character.*/
052: static final String LIST_ESCAPE = "\\";
053:
054: /** Constant for the prefix of hex numbers.*/
055: private static final String HEX_PREFIX = "0x";
056:
057: /** Constant for the radix of hex numbers.*/
058: private static final int HEX_RADIX = 16;
059:
060: /** Constant for the argument classes of the Number constructor that takes
061: * a String.
062: */
063: private static final Class[] CONSTR_ARGS = { String.class };
064:
065: /**
066: * Private constructor prevents instances from being created.
067: */
068: private PropertyConverter() {
069: // to prevent instanciation...
070: }
071:
072: /**
073: * Convert the specified object into a Boolean. Internally the
074: * <code>org.apache.commons.lang.BooleanUtils</code> class from the
075: * <a href="http://jakarta.apache.org/commons/lang/">Commons Lang</a>
076: * project is used to perform this conversion. This class accepts some more
077: * tokens for the boolean value of <b>true</b>, e.g. <code>yes</code> and
078: * <code>on</code>. Please refer to the documentation of this class for more
079: * details.
080: *
081: * @param value the value to convert
082: * @return the converted value
083: * @throws ConversionException thrown if the value cannot be converted to a boolean
084: */
085: public static Boolean toBoolean(Object value)
086: throws ConversionException {
087: if (value instanceof Boolean) {
088: return (Boolean) value;
089: } else if (value instanceof String) {
090: Boolean b = BooleanUtils.toBooleanObject((String) value);
091: if (b == null) {
092: throw new ConversionException("The value " + value
093: + " can't be converted to a Boolean object");
094: }
095: return b;
096: } else {
097: throw new ConversionException("The value " + value
098: + " can't be converted to a Boolean object");
099: }
100: }
101:
102: /**
103: * Convert the specified object into a Byte.
104: *
105: * @param value the value to convert
106: * @return the converted value
107: * @throws ConversionException thrown if the value cannot be converted to a byte
108: */
109: public static Byte toByte(Object value) throws ConversionException {
110: Number n = toNumber(value, Byte.class);
111: if (n instanceof Byte) {
112: return (Byte) n;
113: } else {
114: return new Byte(n.byteValue());
115: }
116: }
117:
118: /**
119: * Convert the specified object into a Short.
120: *
121: * @param value the value to convert
122: * @return the converted value
123: * @throws ConversionException thrown if the value cannot be converted to a short
124: */
125: public static Short toShort(Object value)
126: throws ConversionException {
127: Number n = toNumber(value, Short.class);
128: if (n instanceof Short) {
129: return (Short) n;
130: } else {
131: return new Short(n.shortValue());
132: }
133: }
134:
135: /**
136: * Convert the specified object into an Integer.
137: *
138: * @param value the value to convert
139: * @return the converted value
140: * @throws ConversionException thrown if the value cannot be converted to an integer
141: */
142: public static Integer toInteger(Object value)
143: throws ConversionException {
144: Number n = toNumber(value, Integer.class);
145: if (n instanceof Integer) {
146: return (Integer) n;
147: } else {
148: return new Integer(n.intValue());
149: }
150: }
151:
152: /**
153: * Convert the specified object into a Long.
154: *
155: * @param value the value to convert
156: * @return the converted value
157: * @throws ConversionException thrown if the value cannot be converted to a Long
158: */
159: public static Long toLong(Object value) throws ConversionException {
160: Number n = toNumber(value, Long.class);
161: if (n instanceof Long) {
162: return (Long) n;
163: } else {
164: return new Long(n.longValue());
165: }
166: }
167:
168: /**
169: * Convert the specified object into a Float.
170: *
171: * @param value the value to convert
172: * @return the converted value
173: * @throws ConversionException thrown if the value cannot be converted to a Float
174: */
175: public static Float toFloat(Object value)
176: throws ConversionException {
177: Number n = toNumber(value, Float.class);
178: if (n instanceof Float) {
179: return (Float) n;
180: } else {
181: return new Float(n.floatValue());
182: }
183: }
184:
185: /**
186: * Convert the specified object into a Double.
187: *
188: * @param value the value to convert
189: * @return the converted value
190: * @throws ConversionException thrown if the value cannot be converted to a Double
191: */
192: public static Double toDouble(Object value)
193: throws ConversionException {
194: Number n = toNumber(value, Double.class);
195: if (n instanceof Double) {
196: return (Double) n;
197: } else {
198: return new Double(n.doubleValue());
199: }
200: }
201:
202: /**
203: * Convert the specified object into a BigInteger.
204: *
205: * @param value the value to convert
206: * @return the converted value
207: * @throws ConversionException thrown if the value cannot be converted to a BigInteger
208: */
209: public static BigInteger toBigInteger(Object value)
210: throws ConversionException {
211: Number n = toNumber(value, BigInteger.class);
212: if (n instanceof BigInteger) {
213: return (BigInteger) n;
214: } else {
215: return BigInteger.valueOf(n.longValue());
216: }
217: }
218:
219: /**
220: * Convert the specified object into a BigDecimal.
221: *
222: * @param value the value to convert
223: * @return the converted value
224: * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
225: */
226: public static BigDecimal toBigDecimal(Object value)
227: throws ConversionException {
228: Number n = toNumber(value, BigDecimal.class);
229: if (n instanceof BigDecimal) {
230: return (BigDecimal) n;
231: } else {
232: return new BigDecimal(n.doubleValue());
233: }
234: }
235:
236: /**
237: * Tries to convert the specified object into a number object. This method
238: * is used by the conversion methods for number types. Note that the return
239: * value is not in always of the specified target class, but only if a new
240: * object has to be created.
241: *
242: * @param value the value to be converted (must not be <b>null</b>)
243: * @param targetClass the target class of the conversion (must be derived
244: * from <code>java.lang.Number</code>)
245: * @return the converted number
246: * @throws ConversionException if the object cannot be converted
247: */
248: static Number toNumber(Object value, Class targetClass)
249: throws ConversionException {
250: if (value instanceof Number) {
251: return (Number) value;
252: } else {
253: String str = value.toString();
254: if (str.startsWith(HEX_PREFIX)) {
255: try {
256: return new BigInteger(str.substring(HEX_PREFIX
257: .length()), HEX_RADIX);
258: } catch (NumberFormatException nex) {
259: throw new ConversionException("Could not convert "
260: + str + " to " + targetClass.getName()
261: + "! Invalid hex number.", nex);
262: }
263: }
264:
265: try {
266: Constructor constr = targetClass
267: .getConstructor(CONSTR_ARGS);
268: return (Number) constr
269: .newInstance(new Object[] { str });
270: } catch (InvocationTargetException itex) {
271: throw new ConversionException("Could not convert "
272: + str + " to " + targetClass.getName(), itex
273: .getTargetException());
274: } catch (Exception ex) {
275: // Treat all possible exceptions the same way
276: throw new ConversionException(
277: "Conversion error when trying to convert "
278: + str + " to " + targetClass.getName(),
279: ex);
280: }
281: }
282: }
283:
284: /**
285: * Convert the specified object into an URL.
286: *
287: * @param value the value to convert
288: * @return the converted value
289: * @throws ConversionException thrown if the value cannot be converted to an URL
290: */
291: public static URL toURL(Object value) throws ConversionException {
292: if (value instanceof URL) {
293: return (URL) value;
294: } else if (value instanceof String) {
295: try {
296: return new URL((String) value);
297: } catch (MalformedURLException e) {
298: throw new ConversionException("The value " + value
299: + " can't be converted to an URL", e);
300: }
301: } else {
302: throw new ConversionException("The value " + value
303: + " can't be converted to an URL");
304: }
305: }
306:
307: /**
308: * Convert the specified object into a Locale.
309: *
310: * @param value the value to convert
311: * @return the converted value
312: * @throws ConversionException thrown if the value cannot be converted to a Locale
313: */
314: public static Locale toLocale(Object value)
315: throws ConversionException {
316: if (value instanceof Locale) {
317: return (Locale) value;
318: } else if (value instanceof String) {
319: List elements = split((String) value, '_');
320: int size = elements.size();
321:
322: if (size >= 1
323: && (((String) elements.get(0)).length() == 2 || ((String) elements
324: .get(0)).length() == 0)) {
325: String language = (String) elements.get(0);
326: String country = (String) ((size >= 2) ? elements
327: .get(1) : "");
328: String variant = (String) ((size >= 3) ? elements
329: .get(2) : "");
330:
331: return new Locale(language, country, variant);
332: } else {
333: throw new ConversionException("The value " + value
334: + " can't be converted to a Locale");
335: }
336: } else {
337: throw new ConversionException("The value " + value
338: + " can't be converted to a Locale");
339: }
340: }
341:
342: /**
343: * Split a string on the specified delimiter. To be removed when
344: * commons-lang has a better replacement available (Tokenizer?).
345: *
346: * todo: replace with a commons-lang equivalent
347: *
348: * @param s the string to split
349: * @param delimiter the delimiter
350: * @return a list with the single tokens
351: */
352: public static List split(String s, char delimiter) {
353: if (s == null) {
354: return new ArrayList();
355: }
356:
357: List list = new ArrayList();
358:
359: StringBuffer token = new StringBuffer();
360: int begin = 0;
361: int end = 0;
362: while (begin <= s.length()) {
363: // find the next delimiter
364: int index = s.indexOf(delimiter, end);
365:
366: // move the end index at the end of the string if the delimiter is not found
367: end = (index != -1) ? index : s.length();
368:
369: // extract the chunk
370: String chunk = s.substring(begin, end);
371:
372: if (chunk.endsWith(LIST_ESCAPE) && end != s.length()) {
373: token.append(chunk.substring(0, chunk.length() - 1));
374: token.append(delimiter);
375: } else {
376: // append the chunk to the token
377: token.append(chunk);
378:
379: // add the token to the list
380: list.add(token.toString().trim());
381:
382: // reset the token
383: token = new StringBuffer();
384: }
385:
386: // move to the next chunk
387: end = end + 1;
388: begin = end;
389: }
390:
391: return list;
392: }
393:
394: /**
395: * Escapes the delimiters that might be contained in the given string. This
396: * method ensures that list delimiter characters that are part of a
397: * property's value are correctly escaped when a configuration is saved to a
398: * file. Otherwise when loaded again the property will be treated as a list
399: * property.
400: *
401: * @param s the string with the value
402: * @param delimiter the list delimiter to use
403: * @return the correctly esaped string
404: */
405: public static String escapeDelimiters(String s, char delimiter) {
406: return StringUtils.replace(s, String.valueOf(delimiter),
407: LIST_ESCAPE + delimiter);
408: }
409:
410: /**
411: * Convert the specified object into a Color. If the value is a String,
412: * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
413: * <ul>
414: * <li>FF0000 (red)</li>
415: * <li>0000FFA0 (semi transparent blue)</li>
416: * <li>#CCCCCC (gray)</li>
417: * <li>#00FF00A0 (semi transparent green)</li>
418: * </ul>
419: *
420: * @param value the value to convert
421: * @return the converted value
422: * @throws ConversionException thrown if the value cannot be converted to a Color
423: */
424: public static Color toColor(Object value)
425: throws ConversionException {
426: if (value instanceof Color) {
427: return (Color) value;
428: } else if (value instanceof String
429: && !StringUtils.isBlank((String) value)) {
430: String color = ((String) value).trim();
431:
432: int[] components = new int[3];
433:
434: // check the size of the string
435: int minlength = components.length * 2;
436: if (color.length() < minlength) {
437: throw new ConversionException("The value " + value
438: + " can't be converted to a Color");
439: }
440:
441: // remove the leading #
442: if (color.startsWith("#")) {
443: color = color.substring(1);
444: }
445:
446: try {
447: // parse the components
448: for (int i = 0; i < components.length; i++) {
449: components[i] = Integer.parseInt(color.substring(
450: 2 * i, 2 * i + 2), HEX_RADIX);
451: }
452:
453: // parse the transparency
454: int alpha;
455: if (color.length() >= minlength + 2) {
456: alpha = Integer.parseInt(color.substring(minlength,
457: minlength + 2), HEX_RADIX);
458: } else {
459: alpha = Color.black.getAlpha();
460: }
461:
462: return new Color(components[0], components[1],
463: components[2], alpha);
464: } catch (Exception e) {
465: throw new ConversionException("The value " + value
466: + " can't be converted to a Color", e);
467: }
468: } else {
469: throw new ConversionException("The value " + value
470: + " can't be converted to a Color");
471: }
472: }
473:
474: /**
475: * Convert the specified object into a Date.
476: *
477: * @param value the value to convert
478: * @param format the DateFormat pattern to parse String values
479: * @return the converted value
480: * @throws ConversionException thrown if the value cannot be converted to a Calendar
481: */
482: public static Date toDate(Object value, String format)
483: throws ConversionException {
484: if (value instanceof Date) {
485: return (Date) value;
486: } else if (value instanceof Calendar) {
487: return ((Calendar) value).getTime();
488: } else if (value instanceof String) {
489: try {
490: return new SimpleDateFormat(format)
491: .parse((String) value);
492: } catch (ParseException e) {
493: throw new ConversionException("The value " + value
494: + " can't be converted to a Date", e);
495: }
496: } else {
497: throw new ConversionException("The value " + value
498: + " can't be converted to a Date");
499: }
500: }
501:
502: /**
503: * Convert the specified object into a Calendar.
504: *
505: * @param value the value to convert
506: * @param format the DateFormat pattern to parse String values
507: * @return the converted value
508: * @throws ConversionException thrown if the value cannot be converted to a Calendar
509: */
510: public static Calendar toCalendar(Object value, String format)
511: throws ConversionException {
512: if (value instanceof Calendar) {
513: return (Calendar) value;
514: } else if (value instanceof Date) {
515: Calendar calendar = Calendar.getInstance();
516: calendar.setTime((Date) value);
517: return calendar;
518: } else if (value instanceof String) {
519: try {
520: Calendar calendar = Calendar.getInstance();
521: calendar.setTime(new SimpleDateFormat(format)
522: .parse((String) value));
523: return calendar;
524: } catch (ParseException e) {
525: throw new ConversionException("The value " + value
526: + " can't be converted to a Calendar", e);
527: }
528: } else {
529: throw new ConversionException("The value " + value
530: + " can't be converted to a Calendar");
531: }
532: }
533:
534: /**
535: * Return an iterator over the simple values of a composite value. The value
536: * specified is handled depending on its type:
537: * <ul>
538: * <li>Strings are checked for delimiter characters and splitted if necessary.</li>
539: * <li>For collections the single elements are checked.</li>
540: * <li>Arrays are treated like collections.</li>
541: * <li>All other types are directly inserted.</li>
542: * <li>Recursive combinations are supported, e.g. a collection containing array that contain strings.</li>
543: * </ul>
544: *
545: * @param value the value to "split"
546: * @param delimiter the delimiter for String values
547: * @return an iterator for accessing the single values
548: */
549: public static Iterator toIterator(Object value, char delimiter) {
550: if (value == null) {
551: return IteratorUtils.emptyIterator();
552: }
553: if (value instanceof String) {
554: String s = (String) value;
555: if (s.indexOf(delimiter) > 0) {
556: return split((String) value, delimiter).iterator();
557: } else {
558: return new SingletonIterator(value);
559: }
560: } else if (value instanceof Collection) {
561: return toIterator(((Collection) value).iterator(),
562: delimiter);
563: } else if (value.getClass().isArray()) {
564: return toIterator(IteratorUtils.arrayIterator(value),
565: delimiter);
566: } else if (value instanceof Iterator) {
567: Iterator iterator = (Iterator) value;
568: IteratorChain chain = new IteratorChain();
569: while (iterator.hasNext()) {
570: chain
571: .addIterator(toIterator(iterator.next(),
572: delimiter));
573: }
574: return chain;
575: } else {
576: return new SingletonIterator(value);
577: }
578: }
579:
580: /**
581: * Performs interpolation of the specified value. This method checks if the
582: * given value contains variables of the form <code>${...}</code>. If
583: * this is the case, all occurrances will be substituted by their current
584: * values.
585: *
586: * @param value the value to be interpolated
587: * @param config the current configuration object
588: * @return the interpolated value
589: */
590: public static Object interpolate(Object value,
591: AbstractConfiguration config) {
592: if (value instanceof String) {
593: return config.getSubstitutor().replace((String) value);
594: } else {
595: return value;
596: }
597: }
598: }
|