Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ConfigurationLoader |
|
| 4.846153846153846;4.846 | ||||
ConfigurationLoader$1 |
|
| 4.846153846153846;4.846 | ||||
ConfigurationLoader$InternalLoader |
|
| 4.846153846153846;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 | } |