Coverage Report - com.puppycrawl.tools.checkstyle.ConfigurationLoader
 
Classes in this File Line Coverage Branch Coverage Complexity
ConfigurationLoader
84%
82/97
93%
28/30
4.846
ConfigurationLoader$1
N/A
N/A
4.846
ConfigurationLoader$InternalLoader
85%
35/41
65%
13/20
4.846
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 package com.puppycrawl.tools.checkstyle;
 20  
 
 21  
 import com.google.common.collect.Lists;
 22  
 import com.google.common.collect.Maps;
 23  
 import com.puppycrawl.tools.checkstyle.api.AbstractLoader;
 24  
 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 25  
 import com.puppycrawl.tools.checkstyle.api.Configuration;
 26  
 import com.puppycrawl.tools.checkstyle.api.FastStack;
 27  
 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
 28  
 import org.xml.sax.Attributes;
 29  
 import org.xml.sax.InputSource;
 30  
 import org.xml.sax.SAXException;
 31  
 import org.xml.sax.SAXParseException;
 32  
 
 33  
 import javax.xml.parsers.ParserConfigurationException;
 34  
 import java.io.File;
 35  
 import java.io.FileNotFoundException;
 36  
 import java.io.IOException;
 37  
 import java.io.InputStream;
 38  
 import java.net.MalformedURLException;
 39  
 import java.net.URI;
 40  
 import java.net.URISyntaxException;
 41  
 import java.net.URL;
 42  
 import java.util.Iterator;
 43  
 import java.util.List;
 44  
 import java.util.Map;
 45  
 
 46  
 /**
 47  
  * Loads a configuration from a standard configuration XML file.
 48  
  *
 49  
  * @author Oliver Burn
 50  
  * @version 1.0
 51  
  */
 52  284
 public final class ConfigurationLoader
 53  
 {
 54  
     /** the public ID for version 1_0 of the configuration dtd */
 55  
     private static final String DTD_PUBLIC_ID_1_0 =
 56  
         "-//Puppy Crawl//DTD Check Configuration 1.0//EN";
 57  
 
 58  
     /** the resource for version 1_0 of the configuration dtd */
 59  
     private static final String DTD_RESOURCE_NAME_1_0 =
 60  
         "com/puppycrawl/tools/checkstyle/configuration_1_0.dtd";
 61  
 
 62  
     /** the public ID for version 1_1 of the configuration dtd */
 63  
     private static final String DTD_PUBLIC_ID_1_1 =
 64  
         "-//Puppy Crawl//DTD Check Configuration 1.1//EN";
 65  
 
 66  
     /** the resource for version 1_1 of the configuration dtd */
 67  
     private static final String DTD_RESOURCE_NAME_1_1 =
 68  
         "com/puppycrawl/tools/checkstyle/configuration_1_1.dtd";
 69  
 
 70  
     /** the public ID for version 1_2 of the configuration dtd */
 71  
     private static final String DTD_PUBLIC_ID_1_2 =
 72  
         "-//Puppy Crawl//DTD Check Configuration 1.2//EN";
 73  
 
 74  
     /** the resource for version 1_2 of the configuration dtd */
 75  
     private static final String DTD_RESOURCE_NAME_1_2 =
 76  
         "com/puppycrawl/tools/checkstyle/configuration_1_2.dtd";
 77  
 
 78  
     /** the public ID for version 1_3 of the configuration dtd */
 79  
     private static final String DTD_PUBLIC_ID_1_3 =
 80  
         "-//Puppy Crawl//DTD Check Configuration 1.3//EN";
 81  
 
 82  
     /** the resource for version 1_3 of the configuration dtd */
 83  
     private static final String DTD_RESOURCE_NAME_1_3 =
 84  
         "com/puppycrawl/tools/checkstyle/configuration_1_3.dtd";
 85  
 
 86  
     /**
 87  
      * Implements the SAX document handler interfaces, so they do not
 88  
      * appear in the public API of the ConfigurationLoader.
 89  
      */
 90  11
     private final class InternalLoader
 91  
         extends AbstractLoader
 92  
     {
 93  
         /** module elements */
 94  
         private static final String MODULE = "module";
 95  
         /** name attribute */
 96  
         private static final String NAME = "name";
 97  
         /** property element */
 98  
         private static final String PROPERTY = "property";
 99  
         /** value attribute */
 100  
         private static final String VALUE = "value";
 101  
         /** default attribute */
 102  
         private static final String DEFAULT = "default";
 103  
         /** name of the severity property */
 104  
         private static final String SEVERITY = "severity";
 105  
         /** name of the message element */
 106  
         private static final String MESSAGE = "message";
 107  
         /** name of the key attribute */
 108  
         private static final String KEY = "key";
 109  
 
 110  
         /**
 111  
          * Creates a new InternalLoader.
 112  
          * @throws SAXException if an error occurs
 113  
          * @throws ParserConfigurationException if an error occurs
 114  
          */
 115  
         private InternalLoader()
 116  
             throws SAXException, ParserConfigurationException
 117  11
         {
 118  
             // super(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
 119  11
             super(createIdToResourceNameMap());
 120  11
         }
 121  
 
 122  
         @Override
 123  
         public void startElement(String aNamespaceURI,
 124  
                                  String aLocalName,
 125  
                                  String aQName,
 126  
                                  Attributes aAtts)
 127  
             throws SAXException
 128  
         {
 129  
             // TODO: debug logging for support purposes
 130  61
             if (aQName.equals(MODULE)) {
 131  
                 //create configuration
 132  39
                 final String name = aAtts.getValue(NAME);
 133  39
                 final DefaultConfiguration conf =
 134  
                     new DefaultConfiguration(name);
 135  
 
 136  39
                 if (mConfiguration == null) {
 137  9
                     mConfiguration = conf;
 138  
                 }
 139  
 
 140  
                 //add configuration to it's parent
 141  39
                 if (!mConfigStack.isEmpty()) {
 142  30
                     final DefaultConfiguration top =
 143  
                         mConfigStack.peek();
 144  30
                     top.addChild(conf);
 145  
                 }
 146  
 
 147  39
                 mConfigStack.push(conf);
 148  39
             }
 149  22
             else if (aQName.equals(PROPERTY)) {
 150  
                 //extract name and value
 151  21
                 final String name = aAtts.getValue(NAME);
 152  
                 final String value;
 153  
                 try {
 154  21
                     value = replaceProperties(aAtts.getValue(VALUE),
 155  
                         mOverridePropsResolver, aAtts.getValue(DEFAULT));
 156  
                 }
 157  0
                 catch (final CheckstyleException ex) {
 158  0
                     throw new SAXException(ex.getMessage());
 159  21
                 }
 160  
 
 161  
                 //add to attributes of configuration
 162  21
                 final DefaultConfiguration top =
 163  
                     mConfigStack.peek();
 164  21
                 top.addAttribute(name, value);
 165  21
             }
 166  1
             else if (aQName.equals(MESSAGE)) {
 167  
                 //extract key and value
 168  1
                 final String key = aAtts.getValue(KEY);
 169  1
                 final String value = aAtts.getValue(VALUE);
 170  
 
 171  
                 //add to messages of configuration
 172  1
                 final DefaultConfiguration top = mConfigStack.peek();
 173  1
                 top.addMessage(key, value);
 174  
             }
 175  61
         }
 176  
 
 177  
         @Override
 178  
         public void endElement(String aNamespaceURI,
 179  
                                String aLocalName,
 180  
                                String aQName)
 181  
             throws SAXException
 182  
         {
 183  59
             if (aQName.equals(MODULE)) {
 184  
 
 185  37
                 final Configuration recentModule =
 186  
                     mConfigStack.pop();
 187  
 
 188  
                 // remove modules with severity ignore if these modules should
 189  
                 // be omitted
 190  37
                 SeverityLevel level = null;
 191  
                 try {
 192  37
                     final String severity = recentModule.getAttribute(SEVERITY);
 193  0
                     level = SeverityLevel.getInstance(severity);
 194  
                 }
 195  37
                 catch (final CheckstyleException e) {
 196  
                     //severity not set -> ignore
 197  
                     ;
 198  0
                 }
 199  
 
 200  
                 // omit this module if these should be omitted and the module
 201  
                 // has the severity 'ignore'
 202  37
                 final boolean omitModule = mOmitIgnoredModules
 203  
                     && SeverityLevel.IGNORE.equals(level);
 204  
 
 205  37
                 if (omitModule && !mConfigStack.isEmpty()) {
 206  0
                     final DefaultConfiguration parentModule =
 207  
                         mConfigStack.peek();
 208  0
                     parentModule.removeChild(recentModule);
 209  
                 }
 210  
             }
 211  59
         }
 212  
 
 213  
     }
 214  
 
 215  
     /** the SAX document handler */
 216  
     private final InternalLoader mSaxHandler;
 217  
 
 218  
     /** property resolver **/
 219  
     private final PropertyResolver mOverridePropsResolver;
 220  
     /** the loaded configurations **/
 221  11
     private final FastStack<DefaultConfiguration> mConfigStack =
 222  
         FastStack.newInstance();
 223  
     /** the Configuration that is being built */
 224  
     private Configuration mConfiguration;
 225  
 
 226  
     /** flags if modules with the severity 'ignore' should be omitted. */
 227  
     private final boolean mOmitIgnoredModules;
 228  
 
 229  
     /**
 230  
      * Creates mapping between local resources and dtd ids.
 231  
      * @return map between local resources and dtd ids.
 232  
      */
 233  
     private static Map<String, String> createIdToResourceNameMap()
 234  
     {
 235  11
         final Map<String, String> map = Maps.newHashMap();
 236  11
         map.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
 237  11
         map.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
 238  11
         map.put(DTD_PUBLIC_ID_1_2, DTD_RESOURCE_NAME_1_2);
 239  11
         map.put(DTD_PUBLIC_ID_1_3, DTD_RESOURCE_NAME_1_3);
 240  11
         return map;
 241  
     }
 242  
 
 243  
     /**
 244  
      * Creates a new <code>ConfigurationLoader</code> instance.
 245  
      * @param aOverrideProps resolver for overriding properties
 246  
      * @param aOmitIgnoredModules <code>true</code> if ignored modules should be
 247  
      *         omitted
 248  
      * @throws ParserConfigurationException if an error occurs
 249  
      * @throws SAXException if an error occurs
 250  
      */
 251  
     private ConfigurationLoader(final PropertyResolver aOverrideProps,
 252  
                                 final boolean aOmitIgnoredModules)
 253  
         throws ParserConfigurationException, SAXException
 254  11
     {
 255  11
         mSaxHandler = new InternalLoader();
 256  11
         mOverridePropsResolver = aOverrideProps;
 257  11
         mOmitIgnoredModules = aOmitIgnoredModules;
 258  11
     }
 259  
 
 260  
     /**
 261  
      * Parses the specified input source loading the configuration information.
 262  
      * The stream wrapped inside the source, if any, is NOT
 263  
      * explicitely closed after parsing, it is the responsibility of
 264  
      * the caller to close the stream.
 265  
      *
 266  
      * @param aSource the source that contains the configuration data
 267  
      * @throws IOException if an error occurs
 268  
      * @throws SAXException if an error occurs
 269  
      */
 270  
     private void parseInputSource(InputSource aSource)
 271  
         throws IOException, SAXException
 272  
     {
 273  11
         mSaxHandler.parseInputSource(aSource);
 274  7
     }
 275  
 
 276  
     /**
 277  
      * Returns the module configurations in a specified file.
 278  
      * @param aConfig location of config file, can be either a URL or a filename
 279  
      * @param aOverridePropsResolver overriding properties
 280  
      * @return the check configurations
 281  
      * @throws CheckstyleException if an error occurs
 282  
      */
 283  
     public static Configuration loadConfiguration(String aConfig,
 284  
             PropertyResolver aOverridePropsResolver) throws CheckstyleException
 285  
     {
 286  11
         return loadConfiguration(aConfig, aOverridePropsResolver, false);
 287  
     }
 288  
 
 289  
     /**
 290  
      * Returns the module configurations in a specified file.
 291  
      *
 292  
      * @param aConfig location of config file, can be either a URL or a filename
 293  
      * @param aOverridePropsResolver overriding properties
 294  
      * @param aOmitIgnoredModules <code>true</code> if modules with severity
 295  
      *            'ignore' should be omitted, <code>false</code> otherwise
 296  
      * @return the check configurations
 297  
      * @throws CheckstyleException if an error occurs
 298  
      */
 299  
     public static Configuration loadConfiguration(String aConfig,
 300  
         PropertyResolver aOverridePropsResolver, boolean aOmitIgnoredModules)
 301  
         throws CheckstyleException
 302  
     {
 303  
         try {
 304  
             // figure out if this is a File or a URL
 305  
             URI uri;
 306  
             try {
 307  11
                 final URL url = new URL(aConfig);
 308  1
                 uri = url.toURI();
 309  
             }
 310  10
             catch (final MalformedURLException ex) {
 311  10
                 uri = null;
 312  
             }
 313  0
             catch (final URISyntaxException ex) {
 314  
                 // URL violating RFC 2396
 315  0
                 uri = null;
 316  11
             }
 317  11
             if (uri == null) {
 318  10
                 final File file = new File(aConfig);
 319  10
                 if (file.exists()) {
 320  9
                     uri = file.toURI();
 321  
                 }
 322  
                 else {
 323  
                     // check to see if the file is in the classpath
 324  
                     try {
 325  1
                         final URL configUrl = ConfigurationLoader.class
 326  
                                 .getResource(aConfig);
 327  1
                         if (configUrl == null) {
 328  0
                             throw new FileNotFoundException(aConfig);
 329  
                         }
 330  1
                         uri = configUrl.toURI();
 331  
                     }
 332  0
                     catch (final URISyntaxException e) {
 333  0
                         throw new FileNotFoundException(aConfig);
 334  1
                     }
 335  
                 }
 336  
             }
 337  11
             final InputSource source = new InputSource(uri.toString());
 338  11
             return loadConfiguration(source, aOverridePropsResolver,
 339  
                     aOmitIgnoredModules);
 340  
         }
 341  0
         catch (final FileNotFoundException e) {
 342  0
             throw new CheckstyleException("unable to find " + aConfig, e);
 343  
         }
 344  4
         catch (final CheckstyleException e) {
 345  
                 //wrap again to add file name info
 346  4
             throw new CheckstyleException("unable to read " + aConfig + " - "
 347  
                     + e.getMessage(), e);
 348  
         }
 349  
     }
 350  
 
 351  
     /**
 352  
      * Returns the module configurations from a specified input stream.
 353  
      * Note that clients are required to close the given stream by themselves
 354  
      *
 355  
      * @param aConfigStream the input stream to the Checkstyle configuration
 356  
      * @param aOverridePropsResolver overriding properties
 357  
      * @param aOmitIgnoredModules <code>true</code> if modules with severity
 358  
      *            'ignore' should be omitted, <code>false</code> otherwise
 359  
      * @return the check configurations
 360  
      * @throws CheckstyleException if an error occurs
 361  
      *
 362  
      * @deprecated As this method does not provide a valid system ID,
 363  
      *   preventing resolution of external entities, a
 364  
      *   {@link #loadConfiguration(InputSource,PropertyResolver,boolean)
 365  
      *          version using an InputSource}
 366  
      *   should be used instead
 367  
      */
 368  
     @Deprecated
 369  
     public static Configuration loadConfiguration(InputStream aConfigStream,
 370  
         PropertyResolver aOverridePropsResolver, boolean aOmitIgnoredModules)
 371  
         throws CheckstyleException
 372  
     {
 373  0
         return loadConfiguration(new InputSource(aConfigStream),
 374  
                                  aOverridePropsResolver, aOmitIgnoredModules);
 375  
     }
 376  
 
 377  
     /**
 378  
      * Returns the module configurations from a specified input source.
 379  
      * Note that if the source does wrap an open byte or character
 380  
      * stream, clients are required to close that stream by themselves
 381  
      *
 382  
      * @param aConfigSource the input stream to the Checkstyle configuration
 383  
      * @param aOverridePropsResolver overriding properties
 384  
      * @param aOmitIgnoredModules <code>true</code> if modules with severity
 385  
      *            'ignore' should be omitted, <code>false</code> otherwise
 386  
      * @return the check configurations
 387  
      * @throws CheckstyleException if an error occurs
 388  
      */
 389  
     public static Configuration loadConfiguration(InputSource aConfigSource,
 390  
         PropertyResolver aOverridePropsResolver, boolean aOmitIgnoredModules)
 391  
         throws CheckstyleException
 392  
     {
 393  
         try {
 394  11
             final ConfigurationLoader loader =
 395  
                 new ConfigurationLoader(aOverridePropsResolver,
 396  
                                         aOmitIgnoredModules);
 397  11
             loader.parseInputSource(aConfigSource);
 398  7
             return loader.getConfiguration();
 399  
         }
 400  0
         catch (final ParserConfigurationException e) {
 401  0
             throw new CheckstyleException(
 402  
                 "unable to parse configuration stream", e);
 403  
         }
 404  4
         catch (final SAXParseException e) {
 405  4
             throw new CheckstyleException("unable to parse configuration stream"
 406  
                     + " - " + e.getMessage() + ":" + e.getLineNumber()
 407  
                     + ":" + e.getColumnNumber(), e);
 408  
         }
 409  0
         catch (final SAXException e) {
 410  0
             throw new CheckstyleException("unable to parse configuration stream"
 411  
                     + " - " + e.getMessage(), e);
 412  
         }
 413  0
         catch (final IOException e) {
 414  0
             throw new CheckstyleException("unable to read from stream", e);
 415  
         }
 416  
     }
 417  
 
 418  
     /**
 419  
      * Returns the configuration in the last file parsed.
 420  
      * @return Configuration object
 421  
      */
 422  
     private Configuration getConfiguration()
 423  
     {
 424  7
         return mConfiguration;
 425  
     }
 426  
 
 427  
     /**
 428  
      * Replaces <code>${xxx}</code> style constructions in the given value
 429  
      * with the string value of the corresponding data types.
 430  
      *
 431  
      * The method is package visible to facilitate testing.
 432  
      *
 433  
      * @param aValue The string to be scanned for property references.
 434  
      *              May be <code>null</code>, in which case this
 435  
      *              method returns immediately with no effect.
 436  
      * @param aProps  Mapping (String to String) of property names to their
 437  
      *              values. Must not be <code>null</code>.
 438  
      * @param aDefaultValue default to use if one of the properties in aValue
 439  
      *              cannot be resolved from aProps.
 440  
      *
 441  
      * @throws CheckstyleException if the string contains an opening
 442  
      *                           <code>${</code> without a closing
 443  
      *                           <code>}</code>
 444  
      * @return the original string with the properties replaced, or
 445  
      *         <code>null</code> if the original string is <code>null</code>.
 446  
      *
 447  
      * Code copied from ant -
 448  
      * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
 449  
      */
 450  
     // Package visible for testing purposes
 451  
     static String replaceProperties(
 452  
             String aValue, PropertyResolver aProps, String aDefaultValue)
 453  
         throws CheckstyleException
 454  
     {
 455  45
         if (aValue == null) {
 456  1
             return null;
 457  
         }
 458  
 
 459  44
         final List<String> fragments = Lists.newArrayList();
 460  44
         final List<String> propertyRefs = Lists.newArrayList();
 461  44
         parsePropertyString(aValue, fragments, propertyRefs);
 462  
 
 463  43
         final StringBuffer sb = new StringBuffer();
 464  43
         final Iterator<String> i = fragments.iterator();
 465  43
         final Iterator<String> j = propertyRefs.iterator();
 466  115
         while (i.hasNext()) {
 467  73
             String fragment = i.next();
 468  73
             if (fragment == null) {
 469  25
                 final String propertyName = j.next();
 470  25
                 fragment = aProps.resolve(propertyName);
 471  25
                 if (fragment == null) {
 472  1
                     if (aDefaultValue != null) {
 473  0
                         return aDefaultValue;
 474  
                     }
 475  1
                     throw new CheckstyleException(
 476  
                         "Property ${" + propertyName + "} has not been set");
 477  
                 }
 478  
             }
 479  72
             sb.append(fragment);
 480  72
         }
 481  
 
 482  42
         return sb.toString();
 483  
     }
 484  
 
 485  
     /**
 486  
      * Parses a string containing <code>${xxx}</code> style property
 487  
      * references into two lists. The first list is a collection
 488  
      * of text fragments, while the other is a set of string property names.
 489  
      * <code>null</code> entries in the first list indicate a property
 490  
      * reference from the second list.
 491  
      *
 492  
      * @param aValue     Text to parse. Must not be <code>null</code>.
 493  
      * @param aFragments List to add text fragments to.
 494  
      *                  Must not be <code>null</code>.
 495  
      * @param aPropertyRefs List to add property names to.
 496  
      *                     Must not be <code>null</code>.
 497  
      *
 498  
      * @throws CheckstyleException if the string contains an opening
 499  
      *                           <code>${</code> without a closing
 500  
      *                           <code>}</code>
 501  
      * Code copied from ant -
 502  
      * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
 503  
      */
 504  
     private static void parsePropertyString(String aValue,
 505  
                                            List<String> aFragments,
 506  
                                            List<String> aPropertyRefs)
 507  
         throws CheckstyleException
 508  
     {
 509  44
         int prev = 0;
 510  
         int pos;
 511  
         //search for the next instance of $ from the 'prev' position
 512  77
         while ((pos = aValue.indexOf("$", prev)) >= 0) {
 513  
 
 514  
             //if there was any text before this, add it as a fragment
 515  
             //TODO, this check could be modified to go if pos>prev;
 516  
             //seems like this current version could stick empty strings
 517  
             //into the list
 518  34
             if (pos > 0) {
 519  17
                 aFragments.add(aValue.substring(prev, pos));
 520  
             }
 521  
             //if we are at the end of the string, we tack on a $
 522  
             //then move past it
 523  34
             if (pos == (aValue.length() - 1)) {
 524  4
                 aFragments.add("$");
 525  4
                 prev = pos + 1;
 526  
             }
 527  30
             else if (aValue.charAt(pos + 1) != '{') {
 528  
                 //peek ahead to see if the next char is a property or not
 529  
                 //not a property: insert the char as a literal
 530  
                 /*
 531  
                 fragments.addElement(value.substring(pos + 1, pos + 2));
 532  
                 prev = pos + 2;
 533  
                 */
 534  4
                 if (aValue.charAt(pos + 1) == '$') {
 535  
                     //backwards compatibility two $ map to one mode
 536  1
                     aFragments.add("$");
 537  1
                     prev = pos + 2;
 538  
                 }
 539  
                 else {
 540  
                     //new behaviour: $X maps to $X for all values of X!='$'
 541  3
                     aFragments.add(aValue.substring(pos, pos + 2));
 542  3
                     prev = pos + 2;
 543  
                 }
 544  
 
 545  
             }
 546  
             else {
 547  
                 //property found, extract its name or bail on a typo
 548  26
                 final int endName = aValue.indexOf('}', pos);
 549  26
                 if (endName < 0) {
 550  1
                     throw new CheckstyleException("Syntax error in property: "
 551  
                                                     + aValue);
 552  
                 }
 553  25
                 final String propertyName = aValue.substring(pos + 2, endName);
 554  25
                 aFragments.add(null);
 555  25
                 aPropertyRefs.add(propertyName);
 556  25
                 prev = endName + 1;
 557  25
             }
 558  
         }
 559  
         //no more $ signs found
 560  
         //if there is any tail to the file, append it
 561  43
         if (prev < aValue.length()) {
 562  23
             aFragments.add(aValue.substring(prev));
 563  
         }
 564  43
     }
 565  
 }