##### Coverage Report - org.apache.commons.configuration.PropertiesConfigurationLayout

Classes in this File | Line Coverage | Branch Coverage | Complexity
PropertiesConfigurationLayout |
99% | 143/145
100% | 35/35
| 2.4444444444444446;2,444
PropertiesConfigurationLayout$PropertyLayoutData |
96% | 23/24
100% | 5/5
| 2.4444444444444446;2,444

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 |  |
     package org.apache.commons.configuration;
18 |  |
19 |  |
     import java.io.IOException;
20 |  |
     import java.io.Reader;
21 |  |
     import java.io.Writer;
22 |  |
     import java.util.Iterator;
23 |  |
     import java.util.List;
24 |  |
     import java.util.Map;
25 |  |
     import java.util.Set;
26 |  |
27 |  |
     import org.apache.commons.collections.map.LinkedMap;
28 |  |
     import org.apache.commons.configuration.event.ConfigurationEvent;
29 |  |
     import org.apache.commons.configuration.event.ConfigurationListener;
30 |  |
     import org.apache.commons.lang.StringUtils;
31 |  |
32 |  |
     /**
33 |  |
      * <p>
34 |  |
      * A helper class used by <code>{@link PropertiesConfiguration}</code> to keep
35 |  |
      * the layout of a properties file.
36 |  |
      * </p>
37 |  |
      * <p>
38 |  |
      * Instances of this class are associated with a
39 |  |
      * <code>PropertiesConfiguration</code> object. They are responsible for
40 |  |
      * analyzing properties files and for extracting as much information about the
41 |  |
      * file layout (e.g. empty lines, comments) as possible. When the properties
42 |  |
      * file is written back again it should be close to the original.
43 |  |
      * </p>
44 |  |
      * <p>
45 |  |
      * The <code>PropertiesConfigurationLayout</code> object associated with a
46 |  |
      * <code>PropertiesConfiguration</code> object can be obtained using the
47 |  |
      * <code>getLayout()</code> method of the configuration. Then the methods
48 |  |
      * provided by this class can be used to alter the properties file's layout.
49 |  |
      * </p>
50 |  |
      * <p>
51 |  |
      * Implementation note: This is a very simple implementation, which is far away
52 |  |
      * from being perfect, i.e. the original layout of a properties file won't be
53 |  |
      * reproduced in all cases. One limitation is that comments for multi-valued
54 |  |
      * property keys are concatenated. Maybe this implementation can later be
55 |  |
      * improved.
56 |  |
      * </p>
57 |  |
      * <p>
58 |  |
      * To get an impression how this class works consider the following properties
59 |  |
      * file:
60 |  |
      * </p>
61 |  |
      * <p>
62 |  |
      *
63 |  |
      * <pre>
64 |  |
      * # A demo configuration file
65 |  |
      * # for Demo App 1.42
66 |  |
      *
67 |  |
      * # Application name
68 |  |
      * AppName=Demo App
69 |  |
      *
70 |  |
      * # Application vendor
71 |  |
      * AppVendor=DemoSoft
72 |  |
      *
73 |  |
      *
74 |  |
      * # GUI properties
75 |  |
      * # Window Color
76 |  |
      * windowColors=0xFFFFFF,0x000000
77 |  |
      *
78 |  |
      * # Include some setting
79 |  |
      * include=settings.properties
80 |  |
      * # Another vendor
81 |  |
      * AppVendor=TestSoft
82 |  |
      * </pre>
83 |  |
      *
84 |  |
      * </p>
85 |  |
      * <p>
86 |  |
      * For this example the following points are relevant:
87 |  |
      * </p>
88 |  |
      * <p>
89 |  |
      * <ul>
90 |  |
      * <li>The first two lines are set as header comment. The header comment is
91 |  |
      * determined by the last blanc line before the first property definition.</li>
92 |  |
      * <li>For the property <code>AppName</code> one comment line and one
93 |  |
      * leading blanc line is stored.</li>
94 |  |
      * <li>For the property <code>windowColors</code> two comment lines and two
95 |  |
      * leading blanc lines are stored.</li>
96 |  |
      * <li>Include files is something this class cannot deal with well. When saving
97 |  |
      * the properties configuration back, the included properties are simply
98 |  |
      * contained in the original file. The comment before the include property is
99 |  |
      * skipped.</li>
100 |  |
      * <li>For all properties except for <code>AppVendor</code> the &quot;single
101 |  |
      * line&quot; flag is set. This is relevant only for <code>windowColors</code>,
102 |  |
      * which has multiple values defined in one line using the separator character.</li>
103 |  |
      * <li>The <code>AppVendor</code> property appears twice. The comment lines
104 |  |
      * are concatenated, so that <code>layout.getComment("AppVendor");</code> will
105 |  |
      * result in <code>Application vendor&lt;CR&gt;Another vendor</code>, whith
106 |  |
      * <code>&lt;CR&gt;</code> meaning the line separator. In addition the
107 |  |
      * &quot;single line&quot; flag is set to <b>false</b> for this property. When
108 |  |
      * the file is saved, two property definitions will be written (in series).</li>
109 |  |
      * </ul>
110 |  |
      * </p>
111 |  |
      *
112 |  |
      * @author <a
113 |  |
      * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
114 |  |
      * Configuration team</a>
115 |  |
      * @version $Id: PropertiesConfigurationLayout.java 439648 2006-09-02 20:42:10Z oheger $
116 |  |
      * @since 1.3
117 |  |
      */
118 | 813 |
     public class PropertiesConfigurationLayout implements ConfigurationListener
119 |  |
     {
120 |  |
         /** Constant for the line break character. */
121 | 20 |
         private static final String CR = System.getProperty("line.separator");
122 |  |
123 |  |
         /** Constant for the default comment prefix. */
124 |  |
         private static final String COMMENT_PREFIX = "# ";
125 |  |
126 |  |
         /** Stores the associated configuration object. */
127 |  |
         private PropertiesConfiguration configuration;
128 |  |
129 |  |
         /** Stores a map with the contained layout information. */
130 |  |
         private Map layoutData;
131 |  |
132 |  |
         /** Stores the header comment. */
133 |  |
         private String headerComment;
134 |  |
135 |  |
         /** A counter for determining nested load calls. */
136 |  |
         private int loadCounter;
137 |  |
138 |  |
         /** Stores the force single line flag. */
139 |  |
         private boolean forceSingleLine;
140 |  |
141 |  |
         /**
142 |  |
          * Creates a new instance of <code>PropertiesConfigurationLayout</code>
143 |  |
          * and initializes it with the associated configuration object.
144 |  |
          *
145 |  |
          * @param config the configuration (must not be <b>null</b>)
146 |  |
          */
147 |  |
         public PropertiesConfigurationLayout(PropertiesConfiguration config)
148 |  |
         {
149 | 417 |
             this(config, null);
150 | 416 |
         }
151 |  |
152 |  |
         /**
153 |  |
          * Creates a new instance of <code>PropertiesConfigurationLayout</code>
154 |  |
          * and initializes it with the given configuration object. The data of the
155 |  |
          * specified layout object is copied.
156 |  |
          *
157 |  |
          * @param config the configuration (must not be <b>null</b>)
158 |  |
          * @param c the layout object to be copied
159 |  |
          */
160 |  |
         public PropertiesConfigurationLayout(PropertiesConfiguration config,
161 |  |
                 PropertiesConfigurationLayout c)
162 | 423 |
         {
163 | 423 |
             if (config == null)
164 |  |
             {
165 | 1 |
                 throw new IllegalArgumentException(
166 |  |
                         "Configuration must not be null!");
167 |  |
             }
168 | 422 |
             configuration = config;
169 | 422 |
             layoutData = new LinkedMap();
170 | 422 |
             config.addConfigurationListener(this);
171 |  |
172 | 422 |
             if (c != null)
173 |  |
             {
174 | 6 |
                 copyFrom(c);
175 |  |
             }
176 | 422 |
         }
177 |  |
178 |  |
         /**
179 |  |
          * Returns the associated configuration object.
180 |  |
          *
181 |  |
          * @return the associated configuration
182 |  |
          */
183 |  |
         public PropertiesConfiguration getConfiguration()
184 |  |
         {
185 | 21730 |
             return configuration;
186 |  |
         }
187 |  |
188 |  |
         /**
189 |  |
          * Returns the comment for the specified property key in a cononical form.
190 |  |
          * &quot;Canonical&quot; means that either all lines start with a comment
191 |  |
          * character or none. The <code>commentChar</code> parameter is <b>false</b>,
192 |  |
          * all comment characters are removed, so that the result is only the plain
193 |  |
          * text of the comment. Otherwise it is ensured that each line of the
194 |  |
          * comment starts with a comment character.
195 |  |
          *
196 |  |
          * @param key the key of the property
197 |  |
          * @param commentChar determines whether all lines should start with comment
198 |  |
          * characters or not
199 |  |
          * @return the canonical comment for this key (can be <b>null</b>)
200 |  |
          */
201 |  |
         public String getCanonicalComment(String key, boolean commentChar)
202 |  |
         {
203 | 40 |
             String comment = getComment(key);
204 | 40 |
             if (comment == null)
205 |  |
             {
206 | 1 |
                 return null;
207 |  |
             }
208 |  |
             else
209 |  |
             {
210 | 39 |
                 return trimComment(comment, commentChar);
211 |  |
             }
212 |  |
         }
213 |  |
214 |  |
         /**
215 |  |
          * Returns the comment for the specified property key. The comment is
216 |  |
          * returned as it was set (either manually by calling
217 |  |
          * <code>setComment()</code> or when it was loaded from a properties
218 |  |
          * file). No modifications are performed.
219 |  |
          *
220 |  |
          * @param key the key of the property
221 |  |
          * @return the comment for this key (can be <b>null</b>)
222 |  |
          */
223 |  |
         public String getComment(String key)
224 |  |
         {
225 | 293 |
             return fetchLayoutData(key).getComment();
226 |  |
         }
227 |  |
228 |  |
         /**
229 |  |
          * Sets the comment for the specified property key. The comment (or its
230 |  |
          * single lines if it is a multi-line comment) can start with a comment
231 |  |
          * character. If this is the case, it will be written without changes.
232 |  |
          * Otherwise a default comment character is added automatically.
233 |  |
          *
234 |  |
          * @param key the key of the property
235 |  |
          * @param comment the comment for this key (can be <b>null</b>, then the
236 |  |
          * comment will be removed)
237 |  |
          */
238 |  |
         public void setComment(String key, String comment)
239 |  |
         {
240 | 7 |
             fetchLayoutData(key).setComment(comment);
241 | 6 |
         }
242 |  |
243 |  |
         /**
244 |  |
          * Returns the number of blanc lines before this property key. If this key
245 |  |
          * does not exist, 0 will be returned.
246 |  |
          *
247 |  |
          * @param key the property key
248 |  |
          * @return the number of blanc lines before the property definition for this
249 |  |
          * key
250 |  |
          */
251 |  |
         public int getBlancLinesBefore(String key)
252 |  |
         {
253 | 351 |
             return fetchLayoutData(key).getBlancLines();
254 |  |
         }
255 |  |
256 |  |
         /**
257 |  |
          * Sets the number of blanc lines before the given property key. This can be
258 |  |
          * used for a logical grouping of properties.
259 |  |
          *
260 |  |
          * @param key the property key
261 |  |
          * @param number the number of blanc lines to add before this property
262 |  |
          * definition
263 |  |
          */
264 |  |
         public void setBlancLinesBefore(String key, int number)
265 |  |
         {
266 | 2 |
             fetchLayoutData(key).setBlancLines(number);
267 | 2 |
         }
268 |  |
269 |  |
         /**
270 |  |
          * Returns the header comment of the represented properties file in a
271 |  |
          * canonical form. With the <code>commentChar</code> parameter it can be
272 |  |
          * specified whether comment characters should be stripped or be always
273 |  |
          * present.
274 |  |
          *
275 |  |
          * @param commentChar determines the presence of comment characters
276 |  |
          * @return the header comment (can be <b>null</b>)
277 |  |
          */
278 |  |
         public String getCanonicalHeaderComment(boolean commentChar)
279 |  |
         {
280 | 11 |
             return (getHeaderComment() == null) ? null : trimComment(
281 |  |
                     getHeaderComment(), commentChar);
282 |  |
         }
283 |  |
284 |  |
         /**
285 |  |
          * Returns the header comment of the represented properties file. This
286 |  |
          * method returns the header comment exactly as it was set using
287 |  |
          * <code>setHeaderComment()</code> or extracted from the loaded properties
288 |  |
          * file.
289 |  |
          *
290 |  |
          * @return the header comment (can be <b>null</b>)
291 |  |
          */
292 |  |
         public String getHeaderComment()
293 |  |
         {
294 | 10769 |
             return headerComment;
295 |  |
         }
296 |  |
297 |  |
         /**
298 |  |
          * Sets the header comment for the represented properties file. This comment
299 |  |
          * will be output on top of the file.
300 |  |
          *
301 |  |
          * @param comment the comment
302 |  |
          */
303 |  |
         public void setHeaderComment(String comment)
304 |  |
         {
305 | 333 |
             headerComment = comment;
306 | 333 |
         }
307 |  |
308 |  |
         /**
309 |  |
          * Returns a flag whether the specified property is defined on a single
310 |  |
          * line. This is meaningful only if this property has multiple values.
311 |  |
          *
312 |  |
          * @param key the property key
313 |  |
          * @return a flag if this property is defined on a single line
314 |  |
          */
315 |  |
         public boolean isSingleLine(String key)
316 |  |
         {
317 | 253 |
             return fetchLayoutData(key).isSingleLine();
318 |  |
         }
319 |  |
320 |  |
         /**
321 |  |
          * Sets the &quot;single line flag&quot; for the specified property key.
322 |  |
          * This flag is evaluated if the property has multiple values (i.e. if it is
323 |  |
          * a list property). In this case, if the flag is set, all values will be
324 |  |
          * written in a single property definition using the list delimiter as
325 |  |
          * separator. Otherwise multiple lines will be written for this property,
326 |  |
          * each line containing one property value.
327 |  |
          *
328 |  |
          * @param key the property key
329 |  |
          * @param f the single line flag
330 |  |
          */
331 |  |
         public void setSingleLine(String key, boolean f)
332 |  |
         {
333 | 1 |
             fetchLayoutData(key).setSingleLine(f);
334 | 1 |
         }
335 |  |
336 |  |
         /**
337 |  |
          * Returns the &quot;force single line&quot; flag.
338 |  |
          *
339 |  |
          * @return the force single line flag
340 |  |
          * @see #setForceSingleLine(boolean)
341 |  |
          */
342 |  |
         public boolean isForceSingleLine()
343 |  |
         {
344 | 249 |
             return forceSingleLine;
345 |  |
         }
346 |  |
347 |  |
         /**
348 |  |
          * Sets the &quot;force single line&quot; flag. If this flag is set, all
349 |  |
          * properties with multiple values are written on single lines. This mode
350 |  |
          * provides more compatibility with <code>java.lang.Properties</code>,
351 |  |
          * which cannot deal with multiple definitions of a single property.
352 |  |
          *
353 |  |
          * @param f the force single line flag
354 |  |
          */
355 |  |
         public void setForceSingleLine(boolean f)
356 |  |
         {
357 | 1 |
             forceSingleLine = f;
358 | 1 |
         }
359 |  |
360 |  |
         /**
361 |  |
          * Returns a set with all property keys managed by this object.
362 |  |
          *
363 |  |
          * @return a set with all contained property keys
364 |  |
          */
365 |  |
         public Set getKeys()
366 |  |
         {
367 | 23 |
             return layoutData.keySet();
368 |  |
         }
369 |  |
370 |  |
         /**
371 |  |
          * Reads a properties file and stores its internal structure. The found
372 |  |
          * properties will be added to the associated configuration object.
373 |  |
          *
374 |  |
          * @param in the reader to the properties file
375 |  |
          * @throws ConfigurationException if an error occurs
376 |  |
          */
377 |  |
         public void load(Reader in) throws ConfigurationException
378 |  |
         {
379 | 833 |
             if (++loadCounter == 1)
380 |  |
             {
381 | 320 |
                 getConfiguration().removeConfigurationListener(this);
382 |  |
             }
383 | 833 |
             PropertiesConfiguration.PropertiesReader reader = new PropertiesConfiguration.PropertiesReader(
384 |  |
                     in, getConfiguration().getListDelimiter());
385 |  |
386 |  |
             try
387 |  |
             {
388 | 21393 |
                 while (reader.nextProperty())
389 |  |
                 {
390 | 19728 |
                     if (getConfiguration().propertyLoaded(reader.getPropertyName(),
391 |  |
                             reader.getPropertyValue()))
392 |  |
                     {
393 | 19376 |
                         boolean contained = layoutData.containsKey(reader
394 |  |
                                 .getPropertyName());
395 | 19376 |
                         int blancLines = 0;
396 | 19376 |
                         int idx = checkHeaderComment(reader.getCommentLines());
397 | 19376 |
                         while (idx < reader.getCommentLines().size()
398 |  |
                                 && ((String) reader.getCommentLines().get(idx))
399 | 26608 |
                                         .length() < 1)
400 |  |
                         {
401 | 7232 |
                             idx++;
402 | 7232 |
                             blancLines++;
403 |  |
                         }
404 | 19376 |
                         String comment = extractComment(reader.getCommentLines(),
405 |  |
                                 idx, reader.getCommentLines().size() - 1);
406 | 19376 |
                         PropertyLayoutData data = fetchLayoutData(reader
407 |  |
                                 .getPropertyName());
408 | 19376 |
                         if (contained)
409 |  |
                         {
410 | 10881 |
                             data.addComment(comment);
411 | 10881 |
                             data.setSingleLine(false);
412 |  |
                         }
413 |  |
                         else
414 |  |
                         {
415 | 8495 |
                             data.setComment(comment);
416 | 8495 |
                             data.setBlancLines(blancLines);
417 |  |
                         }
418 |  |
                     }
419 |  |
                 }
420 | 832 |
             }
421 |  |
             catch (IOException ioex)
422 |  |
             {
423 | 0 |
                 throw new ConfigurationException(ioex);
424 |  |
             }
425 |  |
             finally
426 |  |
             {
427 | 1 |
                 if (--loadCounter == 0)
428 |  |
                 {
429 | 320 |
                     getConfiguration().addConfigurationListener(this);
430 |  |
                 }
431 |  |
             }
432 | 832 |
         }
433 |  |
434 |  |
         /**
435 |  |
          * Writes the properties file to the given writer, preserving as much of its
436 |  |
          * structure as possible.
437 |  |
          *
438 |  |
          * @param out the writer
439 |  |
          * @throws ConfigurationException if an error occurs
440 |  |
          */
441 |  |
         public void save(Writer out) throws ConfigurationException
442 |  |
         {
443 |  |
             try
444 |  |
             {
445 | 30 |
                 PropertiesConfiguration.PropertiesWriter writer = new PropertiesConfiguration.PropertiesWriter(
446 |  |
                         out, getConfiguration().getListDelimiter());
447 | 30 |
                 if (headerComment != null)
448 |  |
                 {
449 | 5 |
                     writer.writeln(getCanonicalHeaderComment(true));
450 | 5 |
                     writer.writeln(null);
451 |  |
                 }
452 |  |
453 | 310 |
                 for (Iterator it = layoutData.keySet().iterator(); it.hasNext();)
454 |  |
                 {
455 | 250 |
                     String key = (String) it.next();
456 | 250 |
                     if (getConfiguration().containsKey(key))
457 |  |
                     {
458 |  |
459 |  |
                         // Output blanc lines before property
460 | 343 |
                         for (int i = 0; i < getBlancLinesBefore(key); i++)
461 |  |
                         {
462 | 95 |
                             writer.writeln(null);
463 |  |
                         }
464 |  |
465 |  |
                         // Output the comment
466 | 248 |
                         if (getComment(key) != null)
467 |  |
                         {
468 | 32 |
                             writer.writeln(getCanonicalComment(key, true));
469 |  |
                         }
470 |  |
471 |  |
                         // Output the property and its value
472 | 248 |
                         writer.writeProperty(key, getConfiguration().getProperty(
473 |  |
                                 key), isForceSingleLine() || isSingleLine(key));
474 |  |
                     }
475 |  |
                 }
476 | 30 |
                 writer.flush();
477 | 30 |
             }
478 |  |
             catch (IOException ioex)
479 |  |
             {
480 | 0 |
                 throw new ConfigurationException(ioex);
481 |  |
             }
482 | 30 |
         }
483 |  |
484 |  |
         /**
485 |  |
          * The event listener callback. Here event notifications of the
486 |  |
          * configuration object are processed to update the layout object properly.
487 |  |
          *
488 |  |
          * @param event the event object
489 |  |
          */
490 |  |
         public void configurationChanged(ConfigurationEvent event)
491 |  |
         {
492 | 249 |
             if (event.isBeforeUpdate())
493 |  |
             {
494 | 121 |
                 if (AbstractFileConfiguration.EVENT_RELOAD == event.getType())
495 |  |
                 {
496 | 6 |
                     clear();
497 |  |
                 }
498 |  |
             }
499 |  |
500 |  |
             else
501 |  |
             {
502 | 128 |
                 switch (event.getType())
503 |  |
                 {
504 |  |
                 case AbstractConfiguration.EVENT_ADD_PROPERTY:
505 | 78 |
                     boolean contained = layoutData.containsKey(event
506 |  |
                             .getPropertyName());
507 | 78 |
                     PropertyLayoutData data = fetchLayoutData(event
508 |  |
                             .getPropertyName());
509 | 78 |
                     data.setSingleLine(!contained);
510 | 78 |
                     break;
511 |  |
                 case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
512 | 22 |
                     layoutData.remove(event.getPropertyName());
513 | 22 |
                     break;
514 |  |
                 case AbstractConfiguration.EVENT_CLEAR:
515 | 4 |
                     clear();
516 | 4 |
                     break;
517 |  |
                 case AbstractConfiguration.EVENT_SET_PROPERTY:
518 | 18 |
                     fetchLayoutData(event.getPropertyName());
519 |  |
                     break;
520 |  |
                 }
521 |  |
             }
522 | 249 |
         }
523 |  |
524 |  |
         /**
525 |  |
          * Returns a layout data object for the specified key. If this is a new key,
526 |  |
          * a new object is created and initialized with default values.
527 |  |
          *
528 |  |
          * @param key the key
529 |  |
          * @return the corresponding layout data object
530 |  |
          */
531 |  |
         private PropertyLayoutData fetchLayoutData(String key)
532 |  |
         {
533 | 20379 |
             if (key == null)
534 |  |
             {
535 | 1 |
                 throw new IllegalArgumentException("Property key must not be null!");
536 |  |
             }
537 |  |
538 | 20378 |
             PropertyLayoutData data = (PropertyLayoutData) layoutData.get(key);
539 | 20378 |
             if (data == null)
540 |  |
             {
541 | 8581 |
                 data = new PropertyLayoutData();
542 | 8581 |
                 data.setSingleLine(true);
543 | 8581 |
                 layoutData.put(key, data);
544 |  |
             }
545 |  |
546 | 20378 |
             return data;
547 |  |
         }
548 |  |
549 |  |
         /**
550 |  |
          * Removes all content from this layout object.
551 |  |
          */
552 |  |
         private void clear()
553 |  |
         {
554 | 10 |
             layoutData.clear();
555 | 10 |
             setHeaderComment(null);
556 | 10 |
         }
557 |  |
558 |  |
         /**
559 |  |
          * Tests whether a line is a comment, i.e. whether it starts with a comment
560 |  |
          * character.
561 |  |
          *
562 |  |
          * @param line the line
563 |  |
          * @return a flag if this is a comment line
564 |  |
          */
565 |  |
         static boolean isCommentLine(String line)
566 |  |
         {
567 | 144 |
             return PropertiesConfiguration.isCommentLine(line);
568 |  |
         }
569 |  |
570 |  |
         /**
571 |  |
          * Trims a comment. This method either removes all comment characters from
572 |  |
          * the given string, leaving only the plain comment text or ensures that
573 |  |
          * every line starts with a valid comment character.
574 |  |
          *
575 |  |
          * @param s the string to be processed
576 |  |
          * @param comment if <b>true</b>, a comment character will always be
577 |  |
          * enforced; if <b>false</b>, it will be removed
578 |  |
          * @return the trimmed comment
579 |  |
          */
580 |  |
         static String trimComment(String s, boolean comment)
581 |  |
         {
582 | 51 |
             StringBuffer buf = new StringBuffer(s.length());
583 | 51 |
             int lastPos = 0;
584 |  |
             int pos;
585 |  |
586 |  |
             do
587 |  |
             {
588 | 184 |
                 pos = s.indexOf(CR, lastPos);
589 | 184 |
                 if (pos >= 0)
590 |  |
                 {
591 | 133 |
                     String line = s.substring(lastPos, pos);
592 | 133 |
                     buf.append(stripCommentChar(line, comment)).append(CR);
593 | 133 |
                     lastPos = pos + CR.length();
594 |  |
                 }
595 | 184 |
             } while (pos >= 0);
596 |  |
597 | 51 |
             if (lastPos < s.length())
598 |  |
             {
599 | 24 |
                 buf.append(stripCommentChar(s.substring(lastPos), comment));
600 |  |
             }
601 | 51 |
             return buf.toString();
602 |  |
         }
603 |  |
604 |  |
         /**
605 |  |
          * Either removes the comment character from the given comment line or
606 |  |
          * ensures that the line starts with a comment character.
607 |  |
          *
608 |  |
          * @param s the comment line
609 |  |
          * @param comment if <b>true</b>, a comment character will always be
610 |  |
          * enforced; if <b>false</b>, it will be removed
611 |  |
          * @return the line without comment character
612 |  |
          */
613 |  |
         static String stripCommentChar(String s, boolean comment)
614 |  |
         {
615 | 157 |
             if (s.length() < 1 || (isCommentLine(s) == comment))
616 |  |
             {
617 | 129 |
                 return s;
618 |  |
             }
619 |  |
620 |  |
             else
621 |  |
             {
622 | 28 |
                 if (!comment)
623 |  |
                 {
624 | 20 |
                     int pos = 0;
625 |  |
                     // find first comment character
626 | 20 |
                     while (PropertiesConfiguration.COMMENT_CHARS.indexOf(s
627 | 23 |
                             .charAt(pos)) < 0)
628 |  |
                     {
629 | 3 |
                         pos++;
630 |  |
                     }
631 |  |
632 |  |
                     // Remove leading spaces
633 | 20 |
                     pos++;
634 | 20 |
                     while (pos < s.length()
635 | 39 |
                             && Character.isWhitespace(s.charAt(pos)))
636 |  |
                     {
637 | 19 |
                         pos++;
638 |  |
                     }
639 |  |
640 | 20 |
                     return (pos < s.length()) ? s.substring(pos)
641 |  |
                             : StringUtils.EMPTY;
642 |  |
                 }
643 |  |
                 else
644 |  |
                 {
645 | 8 |
                     return COMMENT_PREFIX + s;
646 |  |
                 }
647 |  |
             }
648 |  |
         }
649 |  |
650 |  |
         /**
651 |  |
          * Extracts a comment string from the given range of the specified comment
652 |  |
          * lines. The single lines are added using a line feed as separator.
653 |  |
          *
654 |  |
          * @param commentLines a list with comment lines
655 |  |
          * @param from the start index
656 |  |
          * @param to the end index (inclusive)
657 |  |
          * @return the comment string (<b>null</b> if it is undefined)
658 |  |
          */
659 |  |
         private String extractComment(List commentLines, int from, int to)
660 |  |
         {
661 | 19685 |
             if (to < from)
662 |  |
             {
663 | 17785 |
                 return null;
664 |  |
             }
665 |  |
666 |  |
             else
667 |  |
             {
668 | 1900 |
                 StringBuffer buf = new StringBuffer((String) commentLines.get(from));
669 | 9883 |
                 for (int i = from + 1; i <= to; i++)
670 |  |
                 {
671 | 7983 |
                     buf.append(CR);
672 | 7983 |
                     buf.append(commentLines.get(i));
673 |  |
                 }
674 | 1900 |
                 return buf.toString();
675 |  |
             }
676 |  |
         }
677 |  |
678 |  |
         /**
679 |  |
          * Checks if parts of the passed in comment can be used as header comment.
680 |  |
          * This method checks whether a header comment can be defined (i.e. whether
681 |  |
          * this is the first comment in the loaded file). If this is the case, it is
682 |  |
          * searched for the lates blanc line. This line will mark the end of the
683 |  |
          * header comment. The return value is the index of the first line in the
684 |  |
          * passed in list, which does not belong to the header comment.
685 |  |
          *
686 |  |
          * @param commentLines the comment lines
687 |  |
          * @return the index of the next line after the header comment
688 |  |
          */
689 |  |
         private int checkHeaderComment(List commentLines)
690 |  |
         {
691 | 19376 |
             if (loadCounter == 1 && getHeaderComment() == null
692 |  |
                     && layoutData.isEmpty())
693 |  |
             {
694 |  |
                 // This is the first comment. Search for blanc lines.
695 | 309 |
                 int index = commentLines.size() - 1;
696 | 309 |
                 while (index >= 0
697 | 317 |
                         && ((String) commentLines.get(index)).length() > 0)
698 |  |
                 {
699 | 8 |
                     index--;
700 |  |
                 }
701 | 309 |
                 setHeaderComment(extractComment(commentLines, 0, index - 1));
702 | 309 |
                 return index + 1;
703 |  |
             }
704 |  |
             else
705 |  |
             {
706 | 19067 |
                 return 0;
707 |  |
             }
708 |  |
         }
709 |  |
710 |  |
         /**
711 |  |
          * Copies the data from the given layout object.
712 |  |
          *
713 |  |
          * @param c the layout object to copy
714 |  |
          */
715 |  |
         private void copyFrom(PropertiesConfigurationLayout c)
716 |  |
         {
717 | 59 |
             for (Iterator it = c.getKeys().iterator(); it.hasNext();)
718 |  |
             {
719 | 47 |
                 String key = (String) it.next();
720 | 47 |
                 PropertyLayoutData data = (PropertyLayoutData) c.layoutData
721 |  |
                         .get(key);
722 | 47 |
                 layoutData.put(key, data.clone());
723 |  |
             }
724 | 6 |
         }
725 |  |
726 |  |
         /**
727 |  |
          * A helper class for storing all layout related information for a
728 |  |
          * configuration property.
729 |  |
          */
730 |  |
         static class PropertyLayoutData implements Cloneable
731 |  |
         {
732 |  |
             /** Stores the comment for the property. */
733 |  |
             private StringBuffer comment;
734 |  |
735 |  |
             /** Stores the number of blanc lines before this property. */
736 |  |
             private int blancLines;
737 |  |
738 |  |
             /** Stores the single line property. */
739 |  |
             private boolean singleLine;
740 |  |
741 |  |
             /**
742 |  |
              * Creates a new instance of <code>PropertyLayoutData</code>.
743 |  |
              */
744 |  |
             public PropertyLayoutData()
745 | 8581 |
             {
746 | 8581 |
                 singleLine = true;
747 | 8581 |
             }
748 |  |
749 |  |
             /**
750 |  |
              * Returns the number of blanc lines before this property.
751 |  |
              *
752 |  |
              * @return the number of blanc lines before this property
753 |  |
              */
754 |  |
             public int getBlancLines()
755 |  |
             {
756 | 351 |
                 return blancLines;
757 |  |
             }
758 |  |
759 |  |
             /**
760 |  |
              * Sets the number of properties before this property.
761 |  |
              *
762 |  |
              * @param blancLines the number of properties before this property
763 |  |
              */
764 |  |
             public void setBlancLines(int blancLines)
765 |  |
             {
766 | 8497 |
                 this.blancLines = blancLines;
767 | 8497 |
             }
768 |  |
769 |  |
             /**
770 |  |
              * Returns the single line flag.
771 |  |
              *
772 |  |
              * @return the single line flag
773 |  |
              */
774 |  |
             public boolean isSingleLine()
775 |  |
             {
776 | 253 |
                 return singleLine;
777 |  |
             }
778 |  |
779 |  |
             /**
780 |  |
              * Sets the single line flag.
781 |  |
              *
782 |  |
              * @param singleLine the single line flag
783 |  |
              */
784 |  |
             public void setSingleLine(boolean singleLine)
785 |  |
             {
786 | 19541 |
                 this.singleLine = singleLine;
787 | 19541 |
             }
788 |  |
789 |  |
             /**
790 |  |
              * Adds a comment for this property. If already a comment exists, the
791 |  |
              * new comment is added (separated by a newline).
792 |  |
              *
793 |  |
              * @param s the comment to add
794 |  |
              */
795 |  |
             public void addComment(String s)
796 |  |
             {
797 | 10881 |
                 if (s != null)
798 |  |
                 {
799 | 829 |
                     if (comment == null)
800 |  |
                     {
801 | 16 |
                         comment = new StringBuffer(s);
802 |  |
                     }
803 |  |
                     else
804 |  |
                     {
805 | 813 |
                         comment.append(CR).append(s);
806 |  |
                     }
807 |  |
                 }
808 | 10881 |
             }
809 |  |
810 |  |
             /**
811 |  |
              * Sets the comment for this property.
812 |  |
              *
813 |  |
              * @param s the new comment (can be <b>null</b>)
814 |  |
              */
815 |  |
             public void setComment(String s)
816 |  |
             {
817 | 8501 |
                 if (s == null)
818 |  |
                 {
819 | 7450 |
                     comment = null;
820 |  |
                 }
821 |  |
                 else
822 |  |
                 {
823 | 1051 |
                     comment = new StringBuffer(s);
824 |  |
                 }
825 | 8501 |
             }
826 |  |
827 |  |
             /**
828 |  |
              * Returns the comment for this property. The comment is returned as it
829 |  |
              * is, without processing of comment characters.
830 |  |
              *
831 |  |
              * @return the comment (can be <b>null</b>)
832 |  |
              */
833 |  |
             public String getComment()
834 |  |
             {
835 | 300 |
                 return (comment == null) ? null : comment.toString();
836 |  |
             }
837 |  |
838 |  |
             /**
839 |  |
              * Creates a copy of this object.
840 |  |
              *
841 |  |
              * @return the copy
842 |  |
              */
843 |  |
             public Object clone()
844 |  |
             {
845 |  |
                 try
846 |  |
                 {
847 | 47 |
                     PropertyLayoutData copy = (PropertyLayoutData) super.clone();
848 | 47 |
                     if (comment != null)
849 |  |
                     {
850 |  |
                         // must copy string buffer, too
851 | 7 |
                         copy.comment = new StringBuffer(getComment());
852 |  |
                     }
853 | 47 |
                     return copy;
854 |  |
                 }
855 |  |
                 catch (CloneNotSupportedException cnex)
856 |  |
                 {
857 |  |
                     // This cannot happen!
858 | 0 |
                     throw new ConfigurationRuntimeException(cnex);
859 |  |
                 }
860 |  |
             }
861 |  |
         }
862 |  |
     }