Coverage Report - org.apache.commons.configuration.PropertyConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyConverter
98%
180/184
100%
59/59
6,048
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *     http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 package org.apache.commons.configuration;
 19  
 
 20  
 import java.awt.Color;
 21  
 import java.lang.reflect.Constructor;
 22  
 import java.lang.reflect.InvocationTargetException;
 23  
 import java.math.BigDecimal;
 24  
 import java.math.BigInteger;
 25  
 import java.net.MalformedURLException;
 26  
 import java.net.URL;
 27  
 import java.text.ParseException;
 28  
 import java.text.SimpleDateFormat;
 29  
 import java.util.ArrayList;
 30  
 import java.util.Calendar;
 31  
 import java.util.Collection;
 32  
 import java.util.Date;
 33  
 import java.util.Iterator;
 34  
 import java.util.List;
 35  
 import java.util.Locale;
 36  
 
 37  
 import org.apache.commons.collections.IteratorUtils;
 38  
 import org.apache.commons.collections.iterators.IteratorChain;
 39  
 import org.apache.commons.collections.iterators.SingletonIterator;
 40  
 import org.apache.commons.lang.BooleanUtils;
 41  
 import org.apache.commons.lang.StringUtils;
 42  
 
 43  
 /**
 44  
  * A utility class to convert the configuration properties into any type.
 45  
  *
 46  
  * @author Emmanuel Bourg
 47  
  * @version $Revision: 442690 $, $Date: 2006-09-12 22:03:18 +0200 (Di, 12 Sep 2006) $
 48  
  * @since 1.1
 49  
  */
 50  108
 public final class PropertyConverter
 51  
 {
 52  
     /** Constant for the list delimiter escaping character.*/
 53  
     static final String LIST_ESCAPE = "\\";
 54  
 
 55  
     /** Constant for the prefix of hex numbers.*/
 56  
     private static final String HEX_PREFIX = "0x";
 57  
 
 58  
     /** Constant for the radix of hex numbers.*/
 59  
     private static final int HEX_RADIX = 16;
 60  
 
 61  
     /** Constant for the argument classes of the Number constructor that takes
 62  
      * a String.
 63  
      */
 64  53
     private static final Class[] CONSTR_ARGS = {String.class};
 65  
 
 66  
     /**
 67  
      * Private constructor prevents instances from being created.
 68  
      */
 69  
     private PropertyConverter()
 70  0
     {
 71  
         // to prevent instanciation...
 72  0
     }
 73  
 
 74  
     /**
 75  
      * Convert the specified object into a Boolean. Internally the
 76  
      * <code>org.apache.commons.lang.BooleanUtils</code> class from the
 77  
      * <a href="http://jakarta.apache.org/commons/lang/">Commons Lang</a>
 78  
      * project is used to perform this conversion. This class accepts some more
 79  
      * tokens for the boolean value of <b>true</b>, e.g. <code>yes</code> and
 80  
      * <code>on</code>. Please refer to the documentation of this class for more
 81  
      * details.
 82  
      *
 83  
      * @param value the value to convert
 84  
      * @return the converted value
 85  
      * @throws ConversionException thrown if the value cannot be converted to a boolean
 86  
      */
 87  
     public static Boolean toBoolean(Object value) throws ConversionException
 88  
     {
 89  117
         if (value instanceof Boolean)
 90  
         {
 91  42
             return (Boolean) value;
 92  
         }
 93  75
         else if (value instanceof String)
 94  
         {
 95  73
             Boolean b = BooleanUtils.toBooleanObject((String) value);
 96  73
             if (b == null)
 97  
             {
 98  5
                 throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
 99  
             }
 100  68
             return b;
 101  
         }
 102  
         else
 103  
         {
 104  2
             throw new ConversionException("The value " + value + " can't be converted to a Boolean object");
 105  
         }
 106  
     }
 107  
 
 108  
     /**
 109  
      * Convert the specified object into a Byte.
 110  
      *
 111  
      * @param value the value to convert
 112  
      * @return the converted value
 113  
      * @throws ConversionException thrown if the value cannot be converted to a byte
 114  
      */
 115  
     public static Byte toByte(Object value) throws ConversionException
 116  
     {
 117  54
         Number n = toNumber(value, Byte.class);
 118  48
         if (n instanceof Byte)
 119  
         {
 120  46
             return (Byte) n;
 121  
         }
 122  
         else
 123  
         {
 124  2
             return new Byte(n.byteValue());
 125  
         }
 126  
     }
 127  
 
 128  
     /**
 129  
      * Convert the specified object into a Short.
 130  
      *
 131  
      * @param value the value to convert
 132  
      * @return the converted value
 133  
      * @throws ConversionException thrown if the value cannot be converted to a short
 134  
      */
 135  
     public static Short toShort(Object value) throws ConversionException
 136  
     {
 137  58
         Number n = toNumber(value, Short.class);
 138  52
         if (n instanceof Short)
 139  
         {
 140  51
             return (Short) n;
 141  
         }
 142  
         else
 143  
         {
 144  1
             return new Short(n.shortValue());
 145  
         }
 146  
     }
 147  
 
 148  
     /**
 149  
      * Convert the specified object into an Integer.
 150  
      *
 151  
      * @param value the value to convert
 152  
      * @return the converted value
 153  
      * @throws ConversionException thrown if the value cannot be converted to an integer
 154  
      */
 155  
     public static Integer toInteger(Object value) throws ConversionException
 156  
     {
 157  76
         Number n = toNumber(value, Integer.class);
 158  72
         if (n instanceof Integer)
 159  
         {
 160  71
             return (Integer) n;
 161  
         }
 162  
         else
 163  
         {
 164  1
             return new Integer(n.intValue());
 165  
         }
 166  
     }
 167  
 
 168  
     /**
 169  
      * Convert the specified object into a Long.
 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 Long
 174  
      */
 175  
     public static Long toLong(Object value) throws ConversionException
 176  
     {
 177  55
         Number n = toNumber(value, Long.class);
 178  49
         if (n instanceof Long)
 179  
         {
 180  46
             return (Long) n;
 181  
         }
 182  
         else
 183  
         {
 184  3
             return new Long(n.longValue());
 185  
         }
 186  
     }
 187  
 
 188  
     /**
 189  
      * Convert the specified object into a Float.
 190  
      *
 191  
      * @param value the value to convert
 192  
      * @return the converted value
 193  
      * @throws ConversionException thrown if the value cannot be converted to a Float
 194  
      */
 195  
     public static Float toFloat(Object value) throws ConversionException
 196  
     {
 197  53
         Number n = toNumber(value, Float.class);
 198  47
         if (n instanceof Float)
 199  
         {
 200  46
             return (Float) n;
 201  
         }
 202  
         else
 203  
         {
 204  1
             return new Float(n.floatValue());
 205  
         }
 206  
     }
 207  
 
 208  
     /**
 209  
      * Convert the specified object into a Double.
 210  
      *
 211  
      * @param value the value to convert
 212  
      * @return the converted value
 213  
      * @throws ConversionException thrown if the value cannot be converted to a Double
 214  
      */
 215  
     public static Double toDouble(Object value) throws ConversionException
 216  
     {
 217  54
         Number n = toNumber(value, Double.class);
 218  48
         if (n instanceof Double)
 219  
         {
 220  47
             return (Double) n;
 221  
         }
 222  
         else
 223  
         {
 224  1
             return new Double(n.doubleValue());
 225  
         }
 226  
     }
 227  
 
 228  
     /**
 229  
      * Convert the specified object into a BigInteger.
 230  
      *
 231  
      * @param value the value to convert
 232  
      * @return the converted value
 233  
      * @throws ConversionException thrown if the value cannot be converted to a BigInteger
 234  
      */
 235  
     public static BigInteger toBigInteger(Object value) throws ConversionException
 236  
     {
 237  41
         Number n = toNumber(value, BigInteger.class);
 238  35
         if (n instanceof BigInteger)
 239  
         {
 240  34
             return (BigInteger) n;
 241  
         }
 242  
         else
 243  
         {
 244  1
             return BigInteger.valueOf(n.longValue());
 245  
         }
 246  
     }
 247  
 
 248  
     /**
 249  
      * Convert the specified object into a BigDecimal.
 250  
      *
 251  
      * @param value the value to convert
 252  
      * @return the converted value
 253  
      * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
 254  
      */
 255  
     public static BigDecimal toBigDecimal(Object value) throws ConversionException
 256  
     {
 257  40
         Number n = toNumber(value, BigDecimal.class);
 258  34
         if (n instanceof BigDecimal)
 259  
         {
 260  33
             return (BigDecimal) n;
 261  
         }
 262  
         else
 263  
         {
 264  1
             return new BigDecimal(n.doubleValue());
 265  
         }
 266  
     }
 267  
 
 268  
     /**
 269  
      * Tries to convert the specified object into a number object. This method
 270  
      * is used by the conversion methods for number types. Note that the return
 271  
      * value is not in always of the specified target class, but only if a new
 272  
      * object has to be created.
 273  
      *
 274  
      * @param value the value to be converted (must not be <b>null</b>)
 275  
      * @param targetClass the target class of the conversion (must be derived
 276  
      * from <code>java.lang.Number</code>)
 277  
      * @return the converted number
 278  
      * @throws ConversionException if the object cannot be converted
 279  
      */
 280  
     static Number toNumber(Object value, Class targetClass)
 281  
             throws ConversionException
 282  
     {
 283  439
         if (value instanceof Number)
 284  
         {
 285  157
             return (Number) value;
 286  
         }
 287  
         else
 288  
         {
 289  282
             String str = value.toString();
 290  282
             if (str.startsWith(HEX_PREFIX))
 291  
             {
 292  
                 try
 293  
                 {
 294  7
                     return new BigInteger(str.substring(HEX_PREFIX.length()),
 295  
                             HEX_RADIX);
 296  
                 }
 297  
                 catch (NumberFormatException nex)
 298  
                 {
 299  1
                     throw new ConversionException("Could not convert " + str
 300  
                             + " to " + targetClass.getName()
 301  
                             + "! Invalid hex number.", nex);
 302  
                 }
 303  
             }
 304  
 
 305  
             try
 306  
             {
 307  275
                 Constructor constr = targetClass.getConstructor(CONSTR_ARGS);
 308  274
                 return (Number) constr.newInstance(new Object[]{str});
 309  
             }
 310  
             catch (InvocationTargetException itex)
 311  
             {
 312  47
                 throw new ConversionException("Could not convert " + str
 313  
                         + " to " + targetClass.getName(), itex
 314  
                         .getTargetException());
 315  
             }
 316  
             catch (Exception ex)
 317  
             {
 318  
                 // Treat all possible exceptions the same way
 319  1
                 throw new ConversionException(
 320  
                         "Conversion error when trying to convert " + str
 321  
                                 + " to " + targetClass.getName(), ex);
 322  
             }
 323  
         }
 324  
     }
 325  
 
 326  
     /**
 327  
      * Convert the specified object into an URL.
 328  
      *
 329  
      * @param value the value to convert
 330  
      * @return the converted value
 331  
      * @throws ConversionException thrown if the value cannot be converted to an URL
 332  
      */
 333  
     public static URL toURL(Object value) throws ConversionException
 334  
     {
 335  35
         if (value instanceof URL)
 336  
         {
 337  15
             return (URL) value;
 338  
         }
 339  20
         else if (value instanceof String)
 340  
         {
 341  
             try
 342  
             {
 343  18
                 return new URL((String) value);
 344  
             }
 345  
             catch (MalformedURLException e)
 346  
             {
 347  2
                 throw new ConversionException("The value " + value + " can't be converted to an URL", e);
 348  
             }
 349  
         }
 350  
         else
 351  
         {
 352  2
             throw new ConversionException("The value " + value + " can't be converted to an URL");
 353  
         }
 354  
     }
 355  
 
 356  
     /**
 357  
      * Convert the specified object into a Locale.
 358  
      *
 359  
      * @param value the value to convert
 360  
      * @return the converted value
 361  
      * @throws ConversionException thrown if the value cannot be converted to a Locale
 362  
      */
 363  
     public static Locale toLocale(Object value) throws ConversionException
 364  
     {
 365  40
         if (value instanceof Locale)
 366  
         {
 367  14
             return (Locale) value;
 368  
         }
 369  26
         else if (value instanceof String)
 370  
         {
 371  24
             List elements = split((String) value, '_');
 372  24
             int size = elements.size();
 373  
 
 374  24
             if (size >= 1 && (((String) elements.get(0)).length() == 2 || ((String) elements.get(0)).length() == 0))
 375  
             {
 376  22
                 String language = (String) elements.get(0);
 377  22
                 String country = (String) ((size >= 2) ? elements.get(1) : "");
 378  22
                 String variant = (String) ((size >= 3) ? elements.get(2) : "");
 379  
 
 380  22
                 return new Locale(language, country, variant);
 381  
             }
 382  
             else
 383  
             {
 384  2
                 throw new ConversionException("The value " + value + " can't be converted to a Locale");
 385  
             }
 386  
         }
 387  
         else
 388  
         {
 389  2
             throw new ConversionException("The value " + value + " can't be converted to a Locale");
 390  
         }
 391  
     }
 392  
 
 393  
     /**
 394  
      * Split a string on the specified delimiter. To be removed when
 395  
      * commons-lang has a better replacement available (Tokenizer?).
 396  
      *
 397  
      * todo: replace with a commons-lang equivalent
 398  
      *
 399  
      * @param s          the string to split
 400  
      * @param delimiter  the delimiter
 401  
      * @return a list with the single tokens
 402  
      */
 403  
     public static List split(String s, char delimiter)
 404  
     {
 405  10079
         if (s == null)
 406  
         {
 407  9
             return new ArrayList();
 408  
         }
 409  
 
 410  10070
         List list = new ArrayList();
 411  
 
 412  10070
         StringBuffer token = new StringBuffer();
 413  10070
         int begin = 0;
 414  10070
         int end = 0;
 415  33813
         while (begin <= s.length())
 416  
         {
 417  
             // find the next delimiter
 418  13673
             int index = s.indexOf(delimiter, end);
 419  
 
 420  
             // move the end index at the end of the string if the delimiter is not found
 421  13673
             end = (index != -1) ? index : s.length();
 422  
 
 423  
             // extract the chunk
 424  13673
             String chunk = s.substring(begin , end);
 425  
 
 426  13673
             if (chunk.endsWith(LIST_ESCAPE) && end != s.length())
 427  
             {
 428  936
                 token.append(chunk.substring(0, chunk.length() - 1));
 429  936
                 token.append(delimiter);
 430  
             }
 431  
             else
 432  
             {
 433  
                 // append the chunk to the token
 434  12737
                 token.append(chunk);
 435  
 
 436  
                 // add the token to the list
 437  12737
                 list.add(token.toString().trim());
 438  
 
 439  
                 // reset the token
 440  12737
                 token = new StringBuffer();
 441  
             }
 442  
 
 443  
             // move to the next chunk
 444  13673
             end = end + 1;
 445  13673
             begin = end;
 446  
         }
 447  
 
 448  10070
         return list;
 449  
     }
 450  
 
 451  
     /**
 452  
      * Escapes the delimiters that might be contained in the given string. This
 453  
      * method ensures that list delimiter characters that are part of a
 454  
      * property's value are correctly escaped when a configuration is saved to a
 455  
      * file. Otherwise when loaded again the property will be treated as a list
 456  
      * property.
 457  
      *
 458  
      * @param s the string with the value
 459  
      * @param delimiter the list delimiter to use
 460  
      * @return the correctly esaped string
 461  
      */
 462  
     public static String escapeDelimiters(String s, char delimiter)
 463  
     {
 464  105
         return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE
 465  
                 + delimiter);
 466  
     }
 467  
 
 468  
     /**
 469  
      * Convert the specified object into a Color. If the value is a String,
 470  
      * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples:
 471  
      * <ul>
 472  
      *   <li>FF0000 (red)</li>
 473  
      *   <li>0000FFA0 (semi transparent blue)</li>
 474  
      *   <li>#CCCCCC (gray)</li>
 475  
      *   <li>#00FF00A0 (semi transparent green)</li>
 476  
      * </ul>
 477  
      *
 478  
      * @param value the value to convert
 479  
      * @return the converted value
 480  
      * @throws ConversionException thrown if the value cannot be converted to a Color
 481  
      */
 482  
     public static Color toColor(Object value) throws ConversionException
 483  
     {
 484  36
         if (value instanceof Color)
 485  
         {
 486  14
             return (Color) value;
 487  
         }
 488  22
         else if (value instanceof String && !StringUtils.isBlank((String) value))
 489  
         {
 490  20
             String color = ((String) value).trim();
 491  
 
 492  20
             int[] components = new int[3];
 493  
 
 494  
             // check the size of the string
 495  20
             int minlength = components.length * 2;
 496  20
             if (color.length() < minlength)
 497  
             {
 498  0
                 throw new ConversionException("The value " + value + " can't be converted to a Color");
 499  
             }
 500  
 
 501  
             // remove the leading #
 502  20
             if (color.startsWith("#"))
 503  
             {
 504  2
                 color = color.substring(1);
 505  
             }
 506  
 
 507  
             try
 508  
             {
 509  
                 // parse the components
 510  74
                 for (int i = 0; i < components.length; i++)
 511  
                 {
 512  56
                     components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX);
 513  
                 }
 514  
 
 515  
                 // parse the transparency
 516  
                 int alpha;
 517  18
                 if (color.length() >= minlength + 2)
 518  
                 {
 519  1
                     alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX);
 520  
                 }
 521  
                 else
 522  
                 {
 523  17
                     alpha = Color.black.getAlpha();
 524  
                 }
 525  
 
 526  18
                 return new Color(components[0], components[1], components[2], alpha);
 527  
             }
 528  
             catch (Exception e)
 529  
             {
 530  2
                 throw new ConversionException("The value " + value + " can't be converted to a Color", e);
 531  
             }
 532  
         }
 533  
         else
 534  
         {
 535  2
             throw new ConversionException("The value " + value + " can't be converted to a Color");
 536  
         }
 537  
     }
 538  
 
 539  
     /**
 540  
      * Convert the specified object into a Date.
 541  
      *
 542  
      * @param value  the value to convert
 543  
      * @param format the DateFormat pattern to parse String values
 544  
      * @return the converted value
 545  
      * @throws ConversionException thrown if the value cannot be converted to a Calendar
 546  
      */
 547  
     public static Date toDate(Object value, String format) throws ConversionException
 548  
     {
 549  42
         if (value instanceof Date)
 550  
         {
 551  15
             return (Date) value;
 552  
         }
 553  27
         else if (value instanceof Calendar)
 554  
         {
 555  5
             return ((Calendar) value).getTime();
 556  
         }
 557  22
         else if (value instanceof String)
 558  
         {
 559  
             try
 560  
             {
 561  20
                 return new SimpleDateFormat(format).parse((String) value);
 562  
             }
 563  
             catch (ParseException e)
 564  
             {
 565  2
                 throw new ConversionException("The value " + value + " can't be converted to a Date", e);
 566  
             }
 567  
         }
 568  
         else
 569  
         {
 570  2
             throw new ConversionException("The value " + value + " can't be converted to a Date");
 571  
         }
 572  
     }
 573  
 
 574  
     /**
 575  
      * Convert the specified object into a Calendar.
 576  
      *
 577  
      * @param value  the value to convert
 578  
      * @param format the DateFormat pattern to parse String values
 579  
      * @return the converted value
 580  
      * @throws ConversionException thrown if the value cannot be converted to a Calendar
 581  
      */
 582  
     public static Calendar toCalendar(Object value, String format) throws ConversionException
 583  
     {
 584  40
         if (value instanceof Calendar)
 585  
         {
 586  10
             return (Calendar) value;
 587  
         }
 588  30
         else if (value instanceof Date)
 589  
         {
 590  10
             Calendar calendar = Calendar.getInstance();
 591  10
             calendar.setTime((Date) value);
 592  10
             return calendar;
 593  
         }
 594  20
         else if (value instanceof String)
 595  
         {
 596  
             try
 597  
             {
 598  18
                 Calendar calendar = Calendar.getInstance();
 599  18
                 calendar.setTime(new SimpleDateFormat(format).parse((String) value));
 600  16
                 return calendar;
 601  
             }
 602  
             catch (ParseException e)
 603  
             {
 604  2
                 throw new ConversionException("The value " + value + " can't be converted to a Calendar", e);
 605  
             }
 606  
         }
 607  
         else
 608  
         {
 609  2
             throw new ConversionException("The value " + value + " can't be converted to a Calendar");
 610  
         }
 611  
     }
 612  
 
 613  
     /**
 614  
      * Return an iterator over the simple values of a composite value. The value
 615  
      * specified is handled depending on its type:
 616  
      * <ul>
 617  
      *   <li>Strings are checked for delimiter characters and splitted if necessary.</li>
 618  
      *   <li>For collections the single elements are checked.</li>
 619  
      *   <li>Arrays are treated like collections.</li>
 620  
      *   <li>All other types are directly inserted.</li>
 621  
      *   <li>Recursive combinations are supported, e.g. a collection containing array that contain strings.</li>
 622  
      * </ul>
 623  
      *
 624  
      * @param value     the value to "split"
 625  
      * @param delimiter the delimiter for String values
 626  
      * @return an iterator for accessing the single values
 627  
      */
 628  
     public static Iterator toIterator(Object value, char delimiter)
 629  
     {
 630  42069
         if (value == null)
 631  
         {
 632  3
             return IteratorUtils.emptyIterator();
 633  
         }
 634  42066
         if (value instanceof String)
 635  
         {
 636  28736
             String s = (String) value;
 637  28736
             if (s.indexOf(delimiter) > 0)
 638  
             {
 639  2057
                 return split((String) value, delimiter).iterator();
 640  
             }
 641  
             else
 642  
             {
 643  26679
                 return new SingletonIterator(value);
 644  
             }
 645  
         }
 646  13330
         else if (value instanceof Collection)
 647  
         {
 648  698
             return toIterator(((Collection) value).iterator(), delimiter);
 649  
         }
 650  12632
         else if (value.getClass().isArray())
 651  
         {
 652  810
             return toIterator(IteratorUtils.arrayIterator(value), delimiter);
 653  
         }
 654  11822
         else if (value instanceof Iterator)
 655  
         {
 656  1508
             Iterator iterator = (Iterator) value;
 657  1508
             IteratorChain chain = new IteratorChain();
 658  6234
             while (iterator.hasNext())
 659  
             {
 660  3218
                 chain.addIterator(toIterator(iterator.next(), delimiter));
 661  
             }
 662  1508
             return chain;
 663  
         }
 664  
         else
 665  
         {
 666  10314
             return new SingletonIterator(value);
 667  
         }
 668  
     }
 669  
 
 670  
     /**
 671  
      * Performs interpolation of the specified value. This method checks if the
 672  
      * given value contains variables of the form <code>${...}</code>. If
 673  
      * this is the case, all occurrances will be substituted by their current
 674  
      * values.
 675  
      *
 676  
      * @param value the value to be interpolated
 677  
      * @param config the current configuration object
 678  
      * @return the interpolated value
 679  
      */
 680  
     public static Object interpolate(Object value, AbstractConfiguration config)
 681  
     {
 682  2139
         if (value instanceof String)
 683  
         {
 684  1424
             return interpolateHelper((String) value, null, config);
 685  
         }
 686  
         else
 687  
         {
 688  715
             return value;
 689  
         }
 690  
     }
 691  
 
 692  
     /**
 693  
      * Recursive handler for multple levels of interpolation. This will be
 694  
      * replaced when Commons Lang provides an interpolation feature. When called
 695  
      * the first time, priorVariables should be null.
 696  
      *
 697  
      * @param base string with the ${key} variables
 698  
      * @param priorVariables serves two purposes: to allow checking for loops,
 699  
      * and creating a meaningful exception message should a loop occur. It's
 700  
      * 0'th element will be set to the value of base from the first call. All
 701  
      * subsequent interpolated variables are added afterward.
 702  
      * @param config the current configuration
 703  
      * @return the string with the interpolation taken care of
 704  
      */
 705  
     private static String interpolateHelper(String base, List priorVariables,
 706  
             AbstractConfiguration config)
 707  
     {
 708  1529
         if (base == null)
 709  
         {
 710  0
             return null;
 711  
         }
 712  
 
 713  
         // on the first call initialize priorVariables
 714  
         // and add base as the first element
 715  1529
         if (priorVariables == null)
 716  
         {
 717  1424
             priorVariables = new ArrayList();
 718  1424
             priorVariables.add(base);
 719  
         }
 720  
 
 721  1529
         int begin = -1;
 722  1529
         int end = -1;
 723  1529
         int prec = 0 - AbstractConfiguration.END_TOKEN.length();
 724  1529
         StringBuffer result = new StringBuffer();
 725  
 
 726  
         // FIXME: we should probably allow the escaping of the start token
 727  1529
         while (((begin = base.indexOf(AbstractConfiguration.START_TOKEN, prec
 728  
                 + AbstractConfiguration.END_TOKEN.length())) > -1)
 729  1637
                 && ((end = base.indexOf(AbstractConfiguration.END_TOKEN, begin)) > -1))
 730  
         {
 731  117
             result.append(base.substring(prec
 732  
                     + AbstractConfiguration.END_TOKEN.length(), begin));
 733  117
             String variable = base.substring(begin
 734  
                     + AbstractConfiguration.START_TOKEN.length(), end);
 735  
 
 736  
             // if we've got a loop, create a useful exception message and throw
 737  117
             if (priorVariables.contains(variable))
 738  
             {
 739  3
                 String initialBase = priorVariables.remove(0).toString();
 740  3
                 priorVariables.add(variable);
 741  3
                 StringBuffer priorVariableSb = new StringBuffer();
 742  
 
 743  
                 // create a nice trace of interpolated variables like so:
 744  
                 // var1->var2->var3
 745  15
                 for (Iterator it = priorVariables.iterator(); it.hasNext();)
 746  
                 {
 747  9
                     priorVariableSb.append(it.next());
 748  9
                     if (it.hasNext())
 749  
                     {
 750  6
                         priorVariableSb.append("->");
 751  
                     }
 752  
                 }
 753  
 
 754  3
                 throw new IllegalStateException(
 755  
                         "infinite loop in property interpolation of "
 756  
                                 + initialBase + ": "
 757  
                                 + priorVariableSb.toString());
 758  
             }
 759  
             // otherwise, add this variable to the interpolation list.
 760  
             else
 761  
             {
 762  114
                 priorVariables.add(variable);
 763  
             }
 764  
 
 765  114
             Object value = config.resolveContainerStore(variable);
 766  114
             if (value != null)
 767  
             {
 768  105
                 result.append(interpolateHelper(value.toString(),
 769  
                         priorVariables, config));
 770  
 
 771  
                 // pop the interpolated variable off the stack
 772  
                 // this maintains priorVariables correctness for
 773  
                 // properties with multiple interpolations, e.g.
 774  
                 // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
 775  99
                 priorVariables.remove(priorVariables.size() - 1);
 776  
             }
 777  
             else
 778  
             {
 779  
                 // variable not defined - so put it back in the value
 780  9
                 result.append(AbstractConfiguration.START_TOKEN);
 781  9
                 result.append(variable);
 782  9
                 result.append(AbstractConfiguration.END_TOKEN);
 783  
             }
 784  
 
 785  108
             prec = end;
 786  
         }
 787  1520
         result.append(base.substring(prec
 788  
                 + AbstractConfiguration.END_TOKEN.length(), base.length()));
 789  1520
         return result.toString();
 790  
     }
 791  
 }