Coverage report

| %line | %branch
--- | --- | ---
org.apache.commons.configuration.PropertiesConfiguration |
85% |
100% |

1 |  |
     /*
2 |  |
      * Copyright 2001-2004 The Apache Software Foundation.
3 |  |
      *
4 |  |
      * Licensed under the Apache License, Version 2.0 (the "License")
5 |  |
      * you may not use this file except in compliance with the License.
6 |  |
      * You may obtain a copy of the License at
7 |  |
      *
8 |  |
      *     http://www.apache.org/licenses/LICENSE-2.0
9 |  |
      *
10 |  |
      * Unless required by applicable law or agreed to in writing, software
11 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
12 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  |
      * See the License for the specific language governing permissions and
14 |  |
      * limitations under the License.
15 |  |
      */
16 |  |
17 |  |
     package org.apache.commons.configuration;
18 |  |
19 |  |
     import java.io.File;
20 |  |
     import java.io.FilterWriter;
21 |  |
     import java.io.IOException;
22 |  |
     import java.io.LineNumberReader;
23 |  |
     import java.io.Reader;
24 |  |
     import java.io.Writer;
25 |  |
     import java.net.URL;
26 |  |
     import java.util.Date;
27 |  |
     import java.util.Iterator;
28 |  |
     import java.util.List;
29 |  |
30 |  |
     import org.apache.commons.lang.StringEscapeUtils;
31 |  |
     import org.apache.commons.lang.StringUtils;
32 |  |
33 |  |
     /**
34 |  |
      * This is the "classic" Properties loader which loads the values from
35 |  |
      * a single or multiple files (which can be chained with "include =".
36 |  |
      * All given path references are either absolute or relative to the
37 |  |
      * file name supplied in the Constructor.
38 |  |
      * <p>
39 |  |
      * In this class, empty PropertyConfigurations can be built, properties
40 |  |
      * added and later saved. include statements are (obviously) not supported
41 |  |
      * if you don't construct a PropertyConfiguration from a file.
42 |  |
      *
43 |  |
      * <p>The properties file syntax is explained here:
44 |  |
      *
45 |  |
      * <ul>
46 |  |
      *  <li>
47 |  |
      *   Each property has the syntax <code>key = value</code>
48 |  |
      *  </li>
49 |  |
      *  <li>
50 |  |
      *   The <i>key</i> may use any character but the equal sign '='.
51 |  |
      *  </li>
52 |  |
      *  <li>
53 |  |
      *   <i>value</i> may be separated on different lines if a backslash
54 |  |
      *   is placed at the end of the line that continues below.
55 |  |
      *  </li>
56 |  |
      *  <li>
57 |  |
      *   If <i>value</i> is a list of strings, each token is separated
58 |  |
      *   by a comma ',' by default.
59 |  |
      *  </li>
60 |  |
      *  <li>
61 |  |
      *   Commas in each token are escaped placing a backslash right before
62 |  |
      *   the comma.
63 |  |
      *  </li>
64 |  |
      *  <li>
65 |  |
      *   If a <i>key</i> is used more than once, the values are appended
66 |  |
      *   like if they were on the same line separated with commas.
67 |  |
      *  </li>
68 |  |
      *  <li>
69 |  |
      *   Blank lines and lines starting with character '#' are skipped.
70 |  |
      *  </li>
71 |  |
      *  <li>
72 |  |
      *   If a property is named "include" (or whatever is defined by
73 |  |
      *   setInclude() and getInclude() and the value of that property is
74 |  |
      *   the full path to a file on disk, that file will be included into
75 |  |
      *   the ConfigurationsRepository. You can also pull in files relative
76 |  |
      *   to the parent configuration file. So if you have something
77 |  |
      *   like the following:
78 |  |
      *
79 |  |
      *   include = additional.properties
80 |  |
      *
81 |  |
      *   Then "additional.properties" is expected to be in the same
82 |  |
      *   directory as the parent configuration file.
83 |  |
      *
84 |  |
      *   Duplicate name values will be replaced, so be careful.
85 |  |
      *
86 |  |
      *  </li>
87 |  |
      * </ul>
88 |  |
      *
89 |  |
      * <p>Here is an example of a valid extended properties file:
90 |  |
      *
91 |  |
      * <p><pre>
92 |  |
      *      # lines starting with # are comments
93 |  |
      *
94 |  |
      *      # This is the simplest property
95 |  |
      *      key = value
96 |  |
      *
97 |  |
      *      # A long property may be separated on multiple lines
98 |  |
      *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
99 |  |
      *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
100 |  |
      *
101 |  |
      *      # This is a property with many tokens
102 |  |
      *      tokens_on_a_line = first token, second token
103 |  |
      *
104 |  |
      *      # This sequence generates exactly the same result
105 |  |
      *      tokens_on_multiple_lines = first token
106 |  |
      *      tokens_on_multiple_lines = second token
107 |  |
      *
108 |  |
      *      # commas may be escaped in tokens
109 |  |
      *      commas.excaped = Hi\, what'up?
110 |  |
      *
111 |  |
      *      # properties can reference other properties
112 |  |
      *      base.prop = /base
113 |  |
      *      first.prop = ${base.prop}/first
114 |  |
      *      second.prop = ${first.prop}/second
115 |  |
      * </pre>
116 |  |
      *
117 |  |
      * @author <a href="mailto:e.bourg@cross-systems.com">Emmanuel Bourg</a>
118 |  |
      * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
119 |  |
      * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
120 |  |
      * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
121 |  |
      * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
122 |  |
      * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
123 |  |
      * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
124 |  |
      * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
125 |  |
      * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
126 |  |
      * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
127 |  |
      * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
128 |  |
      * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
129 |  |
      * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
130 |  |
      * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
131 |  |
      * @version $Id: PropertiesConfiguration.java,v 1.15 2004/09/23 11:45:07 ebourg Exp $
132 |  |
      */
133 |  |
     public class PropertiesConfiguration extends AbstractFileConfiguration
134 |  |
     {
135 |  |
         /**
136 |  |
          * This is the name of the property that can point to other
137 |  |
          * properties file for including other properties files.
138 |  |
          */
139 | 11 |
         static String include = "include";
140 |  |
141 |  |
         /** Allow file inclusion or not */
142 |  |
         private boolean includesAllowed;
143 |  |
144 |  |
         /**
145 |  |
          * Creates an empty PropertyConfiguration object which can be
146 |  |
          * used to synthesize a new Properties file by adding values and
147 |  |
          * then saving(). An object constructed by this C'tor can not be
148 |  |
          * tickled into loading included files because it cannot supply a
149 |  |
          * base for relative includes.
150 |  |
          */
151 |  |
         public PropertiesConfiguration()
152 | 31 |
         {
153 | 31 |
             setIncludesAllowed(false);
154 | 31 |
         }
155 |  |
156 |  |
         /**
157 |  |
          * Creates and loads the extended properties from the specified file.
158 |  |
          * The specified file can contain "include = " properties which then
159 |  |
          * are loaded and merged into the properties.
160 |  |
          *
161 |  |
          * @param fileName The name of the properties file to load.
162 |  |
          * @throws ConfigurationException Error while loading the properties file
163 |  |
          */
164 |  |
         public PropertiesConfiguration(String fileName) throws ConfigurationException
165 | 182 |
         {
166 |  |
             // enable includes
167 | 182 |
             setIncludesAllowed(true);
168 |  |
169 |  |
             // store the file name
170 | 182 |
             this.fileName = fileName;
171 |  |
172 |  |
             // locate the resource
173 | 182 |
             url = ConfigurationUtils.locate(fileName);
174 |  |
175 |  |
             // update the base path
176 | 182 |
             setBasePath(ConfigurationUtils.getBasePath(url));
177 |  |
178 |  |
             // load the file
179 | 182 |
             load();
180 | 182 |
         }
181 |  |
182 |  |
         /**
183 |  |
          * Creates and loads the extended properties from the specified file.
184 |  |
          * The specified file can contain "include = " properties which then
185 |  |
          * are loaded and merged into the properties.
186 |  |
          *
187 |  |
          * @param file The properties file to load.
188 |  |
          * @throws ConfigurationException Error while loading the properties file
189 |  |
          */
190 |  |
         public PropertiesConfiguration(File file) throws ConfigurationException
191 | 1 |
         {
192 |  |
             // enable includes
193 | 1 |
             setIncludesAllowed(true);
194 |  |
195 |  |
             // set the file and update the url, the base path and the file name
196 | 1 |
             setFile(file);
197 |  |
198 |  |
             // load the file
199 | 1 |
             load();
200 | 1 |
         }
201 |  |
202 |  |
         /**
203 |  |
          * Creates and loads the extended properties from the specified URL.
204 |  |
          * The specified file can contain "include = " properties which then
205 |  |
          * are loaded and merged into the properties.
206 |  |
          *
207 |  |
          * @param url The location of the properties file to load.
208 |  |
          * @throws ConfigurationException Error while loading the properties file
209 |  |
          */
210 |  |
         public PropertiesConfiguration(URL url) throws ConfigurationException
211 | 0 |
         {
212 |  |
             // enable includes
213 | 0 |
             setIncludesAllowed(true);
214 |  |
215 |  |
             // set the URL and update the base path and the file name
216 | 0 |
             setURL(url);
217 |  |
218 |  |
             // load the file
219 | 0 |
             load();
220 | 0 |
         }
221 |  |
222 |  |
         /**
223 |  |
          * Gets the property value for including other properties files.
224 |  |
          * By default it is "include".
225 |  |
          *
226 |  |
          * @return A String.
227 |  |
          */
228 |  |
         public static String getInclude()
229 |  |
         {
230 | 13136 |
             return PropertiesConfiguration.include;
231 |  |
         }
232 |  |
233 |  |
         /**
234 |  |
          * Sets the property value for including other properties files.
235 |  |
          * By default it is "include".
236 |  |
          *
237 |  |
          * @param inc A String.
238 |  |
          */
239 |  |
         public static void setInclude(String inc)
240 |  |
         {
241 | 2 |
             PropertiesConfiguration.include = inc;
242 | 2 |
         }
243 |  |
244 |  |
         /**
245 |  |
          * Controls whether additional files can be loaded by the include = <xxx>
246 |  |
          * statement or not. Base rule is, that objects created by the empty
247 |  |
          * C'tor can not have included files.
248 |  |
          *
249 |  |
          * @param includesAllowed includesAllowed True if Includes are allowed.
250 |  |
          */
251 |  |
         protected void setIncludesAllowed(boolean includesAllowed)
252 |  |
         {
253 | 420 |
             this.includesAllowed = includesAllowed;
254 | 420 |
         }
255 |  |
256 |  |
         /**
257 |  |
          * Reports the status of file inclusion.
258 |  |
          *
259 |  |
          * @return True if include files are loaded.
260 |  |
          */
261 |  |
         public boolean getIncludesAllowed()
262 |  |
         {
263 | 130 |
             return this.includesAllowed;
264 |  |
         }
265 |  |
266 |  |
         /**
267 |  |
          * Load the properties from the given input stream and using the specified
268 |  |
          * encoding.
269 |  |
          *
270 |  |
          * @param in An InputStream.
271 |  |
          *
272 |  |
          * @throws ConfigurationException
273 |  |
          */
274 |  |
         public synchronized void load(Reader in) throws ConfigurationException
275 |  |
         {
276 | 338 |
             PropertiesReader reader = new PropertiesReader(in);
277 |  |
278 |  |
             try
279 |  |
             {
280 |  |
                 while (true)
281 |  |
                 {
282 | 6906 |
                     String line = reader.readProperty();
283 |  |
284 | 6906 |
                     if (line == null)
285 |  |
                     {
286 | 338 |
                         break; // EOF
287 |  |
                     }
288 |  |
289 | 6568 |
                     int equalSign = line.indexOf('=');
290 | 6568 |
                     if (equalSign > 0)
291 |  |
                     {
292 | 6568 |
                         String key = line.substring(0, equalSign).trim();
293 | 6568 |
                         String value = line.substring(equalSign + 1).trim();
294 |  |
295 |  |
                         // Though some software (e.g. autoconf) may produce
296 |  |
                         // empty values like foo=\n, emulate the behavior of
297 |  |
                         // java.util.Properties by setting the value to the
298 |  |
                         // empty string.
299 |  |
300 | 6568 |
                         if (StringUtils.isNotEmpty(getInclude())
301 |  |
                             && key.equalsIgnoreCase(getInclude()))
302 |  |
                         {
303 | 130 |
                             if (getIncludesAllowed() && url != null)
304 |  |
                             {
305 | 128 |
                                 String [] files = StringUtils.split(value, getDelimiter());
306 | 256 |
                                 for (int i = 0; i < files.length; i++)
307 |  |
                                 {
308 | 128 |
                                     load(ConfigurationUtils.locate(getBasePath(), files[i].trim()));
309 |  |
                                 }
310 |  |
                             }
311 |  |
                         }
312 |  |
                         else
313 |  |
                         {
314 | 6438 |
                             addProperty(key, unescapeJava(value));
315 |  |
                         }
316 |  |
                     }
317 |  |
                 }
318 |  |
             }
319 | 0 |
             catch (IOException ioe)
320 |  |
             {
321 | 0 |
                 throw new ConfigurationException("Could not load configuration from input stream.", ioe);
322 | 338 |
             }
323 | 338 |
         }
324 |  |
325 |  |
         /**
326 |  |
          * Save the configuration to the specified stream.
327 |  |
          *
328 |  |
          * @param writer the output stream used to save the configuration
329 |  |
          */
330 |  |
         public void save(Writer writer) throws ConfigurationException
331 |  |
         {
332 |  |
             try
333 |  |
             {
334 | 2 |
                 PropertiesWriter out = new PropertiesWriter(writer);
335 |  |
336 | 2 |
                 out.writeComment("written by PropertiesConfiguration");
337 | 2 |
                 out.writeComment(new Date().toString());
338 |  |
339 | 2 |
                 Iterator keys = getKeys();
340 | 62 |
                 while (keys.hasNext())
341 |  |
                 {
342 | 60 |
                     String key = (String) keys.next();
343 | 60 |
                     Object value = getProperty(key);
344 |  |
345 | 60 |
                     if (value instanceof List)
346 |  |
                     {
347 | 22 |
                         out.writeProperty(key, (List) value);
348 |  |
                     }
349 |  |
                     else
350 |  |
                     {
351 | 38 |
                         out.writeProperty(key, value);
352 |  |
                     }
353 |  |
                 }
354 |  |
355 | 2 |
                 out.flush();
356 |  |
             }
357 | 0 |
             catch (IOException e)
358 |  |
             {
359 | 0 |
                 throw new ConfigurationException(e.getMessage(), e);
360 | 2 |
             }
361 | 2 |
         }
362 |  |
363 |  |
         /**
364 |  |
          * Extend the setBasePath method to turn includes
365 |  |
          * on and off based on the existence of a base path.
366 |  |
          *
367 |  |
          * @param basePath The new basePath to set.
368 |  |
          */
369 |  |
         public void setBasePath(String basePath)
370 |  |
         {
371 | 205 |
             super.setBasePath(basePath);
372 | 205 |
             setIncludesAllowed(StringUtils.isNotEmpty(basePath));
373 | 205 |
         }
374 |  |
375 |  |
         /**
376 |  |
          * This class is used to read properties lines.  These lines do
377 |  |
          * not terminate with new-line chars but rather when there is no
378 |  |
          * backslash sign a the end of the line.  This is used to
379 |  |
          * concatenate multiple lines for readability.
380 |  |
          */
381 |  |
         public static class PropertiesReader extends LineNumberReader
382 |  |
         {
383 |  |
             /**
384 |  |
              * Constructor.
385 |  |
              *
386 |  |
              * @param reader A Reader.
387 |  |
              */
388 |  |
             public PropertiesReader(Reader reader)
389 |  |
             {
390 |  |
                 super(reader);
391 |  |
             }
392 |  |
393 |  |
             /**
394 |  |
              * Read a property. Returns null if Stream is
395 |  |
              * at EOF. Concatenates lines ending with "\".
396 |  |
              * Skips lines beginning with "#" and empty lines.
397 |  |
              *
398 |  |
              * @return A string containing a property value or null
399 |  |
              *
400 |  |
              * @throws IOException
401 |  |
              */
402 |  |
             public String readProperty() throws IOException
403 |  |
             {
404 |  |
                 StringBuffer buffer = new StringBuffer();
405 |  |
406 |  |
                 while (true)
407 |  |
                 {
408 |  |
                     String line = readLine();
409 |  |
                     if (line == null)
410 |  |
                     {
411 |  |
                         // EOF
412 |  |
                         return null;
413 |  |
                     }
414 |  |
415 |  |
                     line = line.trim();
416 |  |
417 |  |
                     if (StringUtils.isEmpty(line)
418 |  |
                             || (line.charAt(0) == '#'))
419 |  |
                     {
420 |  |
                         continue;
421 |  |
                     }
422 |  |
423 |  |
                     if (line.endsWith("\\"))
424 |  |
                     {
425 |  |
                         line = line.substring(0, line.length() - 1);
426 |  |
                         buffer.append(line);
427 |  |
                     }
428 |  |
                     else
429 |  |
                     {
430 |  |
                         buffer.append(line);
431 |  |
                         break;
432 |  |
                     }
433 |  |
                 }
434 |  |
                 return buffer.toString();
435 |  |
             }
436 |  |
         } // class PropertiesReader
437 |  |
438 |  |
         /**
439 |  |
          * This class is used to write properties lines.
440 |  |
          */
441 |  |
         public static class PropertiesWriter extends FilterWriter
442 |  |
         {
443 |  |
             /**
444 |  |
              * Constructor.
445 |  |
              *
446 |  |
              * @param writer a Writer object providing the underlying stream
447 |  |
              */
448 |  |
             public PropertiesWriter(Writer writer) throws IOException
449 |  |
             {
450 |  |
                 super(writer);
451 |  |
             }
452 |  |
453 |  |
             /**
454 |  |
              * Write a property.
455 |  |
              *
456 |  |
              * @param key
457 |  |
              * @param value
458 |  |
              * @throws IOException
459 |  |
              */
460 |  |
             public void writeProperty(String key, Object value) throws IOException
461 |  |
             {
462 |  |
                 write(key);
463 |  |
                 write(" = ");
464 |  |
                 if (value != null)
465 |  |
                 {
466 |  |
                     String v = StringEscapeUtils.escapeJava(String.valueOf(value));
467 |  |
                     v = StringUtils.replace(v, String.valueOf(getDelimiter()), "\\" + getDelimiter());
468 |  |
                     write(v);
469 |  |
                 }
470 |  |
471 |  |
                 write('\n');
472 |  |
             }
473 |  |
474 |  |
             /**
475 |  |
              * Write a property.
476 |  |
              *
477 |  |
              * @param key The key of the property
478 |  |
              * @param values The array of values of the property
479 |  |
              */
480 |  |
             public void writeProperty(String key, List values) throws IOException
481 |  |
             {
482 |  |
                 for (int i = 0; i < values.size(); i++)
483 |  |
                 {
484 |  |
                     writeProperty(key, values.get(i));
485 |  |
                 }
486 |  |
             }
487 |  |
488 |  |
             /**
489 |  |
              * Write a comment.
490 |  |
              *
491 |  |
              * @param comment
492 |  |
              * @throws IOException
493 |  |
              */
494 |  |
             public void writeComment(String comment) throws IOException
495 |  |
             {
496 |  |
                 write("# " + comment + "\n");
497 |  |
             }
498 |  |
         } // class PropertiesWriter
499 |  |
500 |  |
         /**
501 |  |
          * <p>Unescapes any Java literals found in the <code>String</code> to a
502 |  |
          * <code>Writer</code>.</p> This is a slightly modified version of the
503 |  |
          * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
504 |  |
          * drop escaped commas (i.e '\,').
505 |  |
          *
506 |  |
          * @param str  the <code>String</code> to unescape, may be null
507 |  |
          *
508 |  |
          * @throws IllegalArgumentException if the Writer is <code>null</code>
509 |  |
          */
510 |  |
         protected static String unescapeJava(String str)
511 |  |
         {
512 | 6439 |
             if (str == null)
513 |  |
             {
514 | 0 |
                 return null;
515 |  |
             }
516 | 6439 |
             int sz = str.length();
517 | 6439 |
             StringBuffer out = new StringBuffer(sz);
518 | 6439 |
             StringBuffer unicode = new StringBuffer(4);
519 | 6439 |
             boolean hadSlash = false;
520 | 6439 |
             boolean inUnicode = false;
521 | 68269 |
             for (int i = 0; i < sz; i++)
522 |  |
             {
523 | 61830 |
                 char ch = str.class="keyword">charAt(i);
524 | 61830 |
                 if (inUnicode)
525 |  |
                 {
526 |  |
                     // if in unicode, then we're reading unicode
527 |  |
                     // values in somehow
528 | 524 |
                     unicode.append(ch);
529 | 524 |
                     if (unicode.length() == 4)
530 |  |
                     {
531 |  |
                         // unicode now contains the four hex digits
532 |  |
                         // which represents our unicode character
533 |  |
                         try
534 |  |
                         {
535 | 131 |
                             int value = Integer.parseInt(unicode.toString(), 16);
536 | 131 |
                             out.append((char) value);
537 | 131 |
                             unicode.setLength(0);
538 | 131 |
                             inUnicode = false;
539 | 131 |
                             hadSlash = false;
540 |  |
                         }
541 | 0 |
                         catch (NumberFormatException nfe)
542 |  |
                         {
543 | 0 |
                             throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);
544 | 131 |
                         }
545 |  |
                     }
546 |  |
                     continue;
547 |  |
                 }
548 |  |
549 | 61306 |
                 if (hadSlash)
550 |  |
                 {
551 |  |
                     // handle an escaped value
552 | 792 |
                     hadSlash = false;
553 |  |
554 | 792 |
                     if (ch=='\\'){
555 | 132 |
                         out.append('\\');
556 |  |
                     }
557 | 660 |
                     else if (ch=='\''){
558 | 0 |
                         out.append('\'');
559 |  |
                     }
560 | 660 |
                     else if (ch=='\"'){
561 | 132 |
                         out.append('"');
562 |  |
                     }
563 | 528 |
                     else if (ch=='r'){
564 | 0 |
                         out.append('\r');
565 |  |
                     }
566 | 528 |
                     else if (ch=='f'){
567 | 0 |
                         out.append('\f');
568 |  |
                     }
569 | 528 |
                     else if (ch=='t'){
570 | 132 |
                         out.append('\t');
571 |  |
                     }
572 | 396 |
                     else if (ch=='n'){
573 | 132 |
                         out.append('\n');
574 |  |
                     }
575 | 264 |
                     else if (ch=='b'){
576 | 0 |
                         out.append('\b');
577 |  |
                     }
578 | 264 |
                     else if (ch==getDelimiter()){
579 | 132 |
                         out.append('\\');
580 | 132 |
                         out.append(getDelimiter());
581 |  |
                     }
582 | 132 |
                     else if (ch=='u'){
583 |  |
                         //                  uh-oh, we're in unicode country....
584 | 131 |
                         inUnicode = true;
585 |  |
                     }
586 |  |
                     else {
587 | 1 |
                         out.append(ch);
588 |  |
                     }
589 |  |
590 | 1 |
                     continue;
591 |  |
                 }
592 | 60514 |
                 else if (ch == '\\')
593 |  |
                 {
594 | 792 |
                     hadSlash = true;
595 | 792 |
                     continue;
596 |  |
                 }
597 | 59722 |
                 out.append(ch);
598 |  |
             }
599 |  |
600 | 6439 |
             if (hadSlash)
601 |  |
             {
602 |  |
                 // then we're in the weird case of a \ at the end of the
603 |  |
                 // string, let's output it anyway.
604 | 0 |
                 out.append('\\');
605 |  |
             }
606 |  |
607 | 6439 |
             return out.toString();
608 |  |
         }
609 |  |
610 |  |
     }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.