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

Classes in this File | Line Coverage | Branch Coverage | Complexity
PropertiesConfigurationLayout |
97% | 151/155
100% | 35/35
| 2.4444444444444446;2,444
PropertiesConfigurationLayout$PropertyLayoutData |
93% | 25/27
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://commons.apache.org/configuration/team-list.html">Commons
114 |  |
      * Configuration team</a>
115 |  |
      * @version $Id: PropertiesConfigurationLayout.java 589380 2007-10-28 16:37:35Z oheger $
116 |  |
      * @since 1.3
117 |  |
      */
118 | 813 |
     public class PropertiesConfigurationLayout implements ConfigurationListener
119 |  |
     {
120 |  |
         /** Constant for the line break character. */
121 | 23 |
         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 | 493 |
             this(config, null);
150 | 492 |
         }
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 | 499 |
         {
163 | 499 |
             if (config == null)
164 |  |
             {
165 | 1 |
                 throw new IllegalArgumentException(
166 |  |
                         "Configuration must not be null!");
167 |  |
             }
168 | 498 |
             configuration = config;
169 | 498 |
             layoutData = new LinkedMap();
170 | 498 |
             config.addConfigurationListener(this);
171 |  |
172 | 498 |
             if (c != null)
173 |  |
             {
174 | 6 |
                 copyFrom(c);
175 |  |
             }
176 | 498 |
         }
177 |  |
178 |  |
         /**
179 |  |
          * Returns the associated configuration object.
180 |  |
          *
181 |  |
          * @return the associated configuration
182 |  |
          */
183 |  |
         public PropertiesConfiguration getConfiguration()
184 |  |
         {
185 | 25027 |
             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 | 70 |
             String comment = getComment(key);
204 | 70 |
             if (comment == null)
205 |  |
             {
206 | 1 |
                 return null;
207 |  |
             }
208 |  |
             else
209 |  |
             {
210 | 69 |
                 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 | 611 |
             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 | 747 |
             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 | 12712 |
             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 | 423 |
             headerComment = comment;
306 | 423 |
         }
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 | 541 |
             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 | 537 |
             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. This
352 |  |
          * mode has no effect if the list delimiter parsing is disabled.
353 |  |
          *
354 |  |
          * @param f the force single line flag
355 |  |
          */
356 |  |
         public void setForceSingleLine(boolean f)
357 |  |
         {
358 | 1 |
             forceSingleLine = f;
359 | 1 |
         }
360 |  |
361 |  |
         /**
362 |  |
          * Returns a set with all property keys managed by this object.
363 |  |
          *
364 |  |
          * @return a set with all contained property keys
365 |  |
          */
366 |  |
         public Set getKeys()
367 |  |
         {
368 | 23 |
             return layoutData.keySet();
369 |  |
         }
370 |  |
371 |  |
         /**
372 |  |
          * Reads a properties file and stores its internal structure. The found
373 |  |
          * properties will be added to the associated configuration object.
374 |  |
          *
375 |  |
          * @param in the reader to the properties file
376 |  |
          * @throws ConfigurationException if an error occurs
377 |  |
          */
378 |  |
         public void load(Reader in) throws ConfigurationException
379 |  |
         {
380 | 931 |
             if (++loadCounter == 1)
381 |  |
             {
382 | 391 |
                 getConfiguration().removeConfigurationListener(this);
383 |  |
             }
384 | 931 |
             PropertiesConfiguration.PropertiesReader reader = new PropertiesConfiguration.PropertiesReader(
385 |  |
                     in, getConfiguration().getListDelimiter());
386 |  |
387 |  |
             try
388 |  |
             {
389 | 22680 |
                 while (reader.nextProperty())
390 |  |
                 {
391 | 21750 |
                     if (getConfiguration().propertyLoaded(reader.getPropertyName(),
392 |  |
                             reader.getPropertyValue()))
393 |  |
                     {
394 | 21371 |
                         boolean contained = layoutData.containsKey(reader
395 |  |
                                 .getPropertyName());
396 | 21371 |
                         int blancLines = 0;
397 | 21371 |
                         int idx = checkHeaderComment(reader.getCommentLines());
398 |  |
                         while (idx < reader.getCommentLines().size()
399 |  |
                                 && ((String) reader.getCommentLines().get(idx))
400 | 29327 |
                                         .length() < 1)
401 |  |
                         {
402 | 7956 |
                             idx++;
403 | 7956 |
                             blancLines++;
404 | 7956 |
                         }
405 | 21371 |
                         String comment = extractComment(reader.getCommentLines(),
406 |  |
                                 idx, reader.getCommentLines().size() - 1);
407 | 21371 |
                         PropertyLayoutData data = fetchLayoutData(reader
408 |  |
                                 .getPropertyName());
409 | 21371 |
                         if (contained)
410 |  |
                         {
411 | 11355 |
                             data.addComment(comment);
412 | 11355 |
                             data.setSingleLine(false);
413 | 11355 |
                         }
414 |  |
                         else
415 |  |
                         {
416 | 10016 |
                             data.setComment(comment);
417 | 10016 |
                             data.setBlancLines(blancLines);
418 |  |
                         }
419 | 21371 |
                     }
420 |  |
                 }
421 |  |
             }
422 | 0 |
             catch (IOException ioex)
423 |  |
             {
424 | 0 |
                 throw new ConfigurationException(ioex);
425 |  |
             }
426 |  |
             finally
427 |  |
             {
428 | 931 |
                 if (--loadCounter == 0)
429 |  |
                 {
430 | 391 |
                     getConfiguration().addConfigurationListener(this);
431 | 390 |
                 }
432 | 1 |
             }
433 | 930 |
         }
434 |  |
435 |  |
         /**
436 |  |
          * Writes the properties file to the given writer, preserving as much of its
437 |  |
          * structure as possible.
438 |  |
          *
439 |  |
          * @param out the writer
440 |  |
          * @throws ConfigurationException if an error occurs
441 |  |
          */
442 |  |
         public void save(Writer out) throws ConfigurationException
443 |  |
         {
444 |  |
             try
445 |  |
             {
446 | 39 |
                 char delimiter = getConfiguration().isDelimiterParsingDisabled() ? 0
447 |  |
                         : getConfiguration().getListDelimiter();
448 | 39 |
                 PropertiesConfiguration.PropertiesWriter writer = new PropertiesConfiguration.PropertiesWriter(
449 |  |
                         out, delimiter);
450 | 39 |
                 if (headerComment != null)
451 |  |
                 {
452 | 5 |
                     writer.writeln(getCanonicalHeaderComment(true));
453 | 5 |
                     writer.writeln(null);
454 |  |
                 }
455 |  |
456 | 39 |
                 for (Iterator it = layoutData.keySet().iterator(); it.hasNext();)
457 |  |
                 {
458 | 538 |
                     String key = (String) it.next();
459 | 538 |
                     if (getConfiguration().containsKey(key))
460 |  |
                     {
461 |  |
462 |  |
                         // Output blank lines before property
463 | 739 |
                         for (int i = 0; i < getBlancLinesBefore(key); i++)
464 |  |
                         {
465 | 203 |
                             writer.writeln(null);
466 |  |
                         }
467 |  |
468 |  |
                         // Output the comment
469 | 536 |
                         if (getComment(key) != null)
470 |  |
                         {
471 | 62 |
                             writer.writeln(getCanonicalComment(key, true));
472 |  |
                         }
473 |  |
474 |  |
                         // Output the property and its value
475 | 536 |
                         boolean singleLine = (isForceSingleLine() || isSingleLine(key))
476 |  |
                                 && !getConfiguration().isDelimiterParsingDisabled();
477 | 536 |
                         writer.writeProperty(key, getConfiguration().getProperty(
478 |  |
                                 key), singleLine);
479 |  |
                     }
480 | 538 |
                 }
481 | 39 |
                 writer.flush();
482 |  |
             }
483 | 0 |
             catch (IOException ioex)
484 |  |
             {
485 | 0 |
                 throw new ConfigurationException(ioex);
486 | 39 |
             }
487 | 39 |
         }
488 |  |
489 |  |
         /**
490 |  |
          * The event listener callback. Here event notifications of the
491 |  |
          * configuration object are processed to update the layout object properly.
492 |  |
          *
493 |  |
          * @param event the event object
494 |  |
          */
495 |  |
         public void configurationChanged(ConfigurationEvent event)
496 |  |
         {
497 | 431 |
             if (event.isBeforeUpdate())
498 |  |
             {
499 | 213 |
                 if (AbstractFileConfiguration.EVENT_RELOAD == event.getType())
500 |  |
                 {
501 | 27 |
                     clear();
502 | 27 |
                 }
503 |  |
             }
504 |  |
505 |  |
             else
506 |  |
             {
507 | 218 |
                 switch (event.getType())
508 |  |
                 {
509 |  |
                 case AbstractConfiguration.EVENT_ADD_PROPERTY:
510 | 121 |
                     boolean contained = layoutData.containsKey(event
511 |  |
                             .getPropertyName());
512 | 121 |
                     PropertyLayoutData data = fetchLayoutData(event
513 |  |
                             .getPropertyName());
514 | 121 |
                     data.setSingleLine(!contained);
515 | 121 |
                     break;
516 |  |
                 case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
517 | 23 |
                     layoutData.remove(event.getPropertyName());
518 | 23 |
                     break;
519 |  |
                 case AbstractConfiguration.EVENT_CLEAR:
520 | 5 |
                     clear();
521 | 5 |
                     break;
522 |  |
                 case AbstractConfiguration.EVENT_SET_PROPERTY:
523 | 43 |
                     fetchLayoutData(event.getPropertyName());
524 |  |
                     break;
525 |  |
                 }
526 |  |
             }
527 | 431 |
         }
528 |  |
529 |  |
         /**
530 |  |
          * Returns a layout data object for the specified key. If this is a new key,
531 |  |
          * a new object is created and initialized with default values.
532 |  |
          *
533 |  |
          * @param key the key
534 |  |
          * @return the corresponding layout data object
535 |  |
          */
536 |  |
         private PropertyLayoutData fetchLayoutData(String key)
537 |  |
         {
538 | 23444 |
             if (key == null)
539 |  |
             {
540 | 1 |
                 throw new IllegalArgumentException("Property key must not be null!");
541 |  |
             }
542 |  |
543 | 23443 |
             PropertyLayoutData data = (PropertyLayoutData) layoutData.get(key);
544 | 23443 |
             if (data == null)
545 |  |
             {
546 | 10169 |
                 data = new PropertyLayoutData();
547 | 10169 |
                 data.setSingleLine(true);
548 | 10169 |
                 layoutData.put(key, data);
549 |  |
             }
550 |  |
551 | 23443 |
             return data;
552 |  |
         }
553 |  |
554 |  |
         /**
555 |  |
          * Removes all content from this layout object.
556 |  |
          */
557 |  |
         private void clear()
558 |  |
         {
559 | 32 |
             layoutData.clear();
560 | 32 |
             setHeaderComment(null);
561 | 32 |
         }
562 |  |
563 |  |
         /**
564 |  |
          * Tests whether a line is a comment, i.e. whether it starts with a comment
565 |  |
          * character.
566 |  |
          *
567 |  |
          * @param line the line
568 |  |
          * @return a flag if this is a comment line
569 |  |
          */
570 |  |
         static boolean isCommentLine(String line)
571 |  |
         {
572 | 264 |
             return PropertiesConfiguration.isCommentLine(line);
573 |  |
         }
574 |  |
575 |  |
         /**
576 |  |
          * Trims a comment. This method either removes all comment characters from
577 |  |
          * the given string, leaving only the plain comment text or ensures that
578 |  |
          * every line starts with a valid comment character.
579 |  |
          *
580 |  |
          * @param s the string to be processed
581 |  |
          * @param comment if <b>true</b>, a comment character will always be
582 |  |
          * enforced; if <b>false</b>, it will be removed
583 |  |
          * @return the trimmed comment
584 |  |
          */
585 |  |
         static String trimComment(String s, boolean comment)
586 |  |
         {
587 | 81 |
             StringBuffer buf = new StringBuffer(s.length());
588 | 81 |
             int lastPos = 0;
589 |  |
             int pos;
590 |  |
591 |  |
             do
592 |  |
             {
593 | 346 |
                 pos = s.indexOf(CR, lastPos);
594 | 346 |
                 if (pos >= 0)
595 |  |
                 {
596 | 265 |
                     String line = s.substring(lastPos, pos);
597 | 265 |
                     buf.append(stripCommentChar(line, comment)).append(CR);
598 | 265 |
                     lastPos = pos + CR.length();
599 |  |
                 }
600 | 346 |
             } while (pos >= 0);
601 |  |
602 | 81 |
             if (lastPos < s.length())
603 |  |
             {
604 | 24 |
                 buf.append(stripCommentChar(s.substring(lastPos), comment));
605 |  |
             }
606 | 81 |
             return buf.toString();
607 |  |
         }
608 |  |
609 |  |
         /**
610 |  |
          * Either removes the comment character from the given comment line or
611 |  |
          * ensures that the line starts with a comment character.
612 |  |
          *
613 |  |
          * @param s the comment line
614 |  |
          * @param comment if <b>true</b>, a comment character will always be
615 |  |
          * enforced; if <b>false</b>, it will be removed
616 |  |
          * @return the line without comment character
617 |  |
          */
618 |  |
         static String stripCommentChar(String s, boolean comment)
619 |  |
         {
620 | 289 |
             if (s.length() < 1 || (isCommentLine(s) == comment))
621 |  |
             {
622 | 261 |
                 return s;
623 |  |
             }
624 |  |
625 |  |
             else
626 |  |
             {
627 | 28 |
                 if (!comment)
628 |  |
                 {
629 | 20 |
                     int pos = 0;
630 |  |
                     // find first comment character
631 |  |
                     while (PropertiesConfiguration.COMMENT_CHARS.indexOf(s
632 | 23 |
                             .charAt(pos)) < 0)
633 |  |
                     {
634 | 3 |
                         pos++;
635 | 3 |
                     }
636 |  |
637 |  |
                     // Remove leading spaces
638 | 20 |
                     pos++;
639 |  |
                     while (pos < s.length()
640 | 39 |
                             && Character.isWhitespace(s.charAt(pos)))
641 |  |
                     {
642 | 19 |
                         pos++;
643 | 19 |
                     }
644 |  |
645 | 20 |
                     return (pos < s.length()) ? s.substring(pos)
646 |  |
                             : StringUtils.EMPTY;
647 |  |
                 }
648 |  |
                 else
649 |  |
                 {
650 | 8 |
                     return COMMENT_PREFIX + s;
651 |  |
                 }
652 |  |
             }
653 |  |
         }
654 |  |
655 |  |
         /**
656 |  |
          * Extracts a comment string from the given range of the specified comment
657 |  |
          * lines. The single lines are added using a line feed as separator.
658 |  |
          *
659 |  |
          * @param commentLines a list with comment lines
660 |  |
          * @param from the start index
661 |  |
          * @param to the end index (inclusive)
662 |  |
          * @return the comment string (<b>null</b> if it is undefined)
663 |  |
          */
664 |  |
         private String extractComment(List commentLines, int from, int to)
665 |  |
         {
666 | 21748 |
             if (to < from)
667 |  |
             {
668 | 19669 |
                 return null;
669 |  |
             }
670 |  |
671 |  |
             else
672 |  |
             {
673 | 2079 |
                 StringBuffer buf = new StringBuffer((String) commentLines.get(from));
674 | 10807 |
                 for (int i = from + 1; i <= to; i++)
675 |  |
                 {
676 | 8728 |
                     buf.append(CR);
677 | 8728 |
                     buf.append(commentLines.get(i));
678 |  |
                 }
679 | 2079 |
                 return buf.toString();
680 |  |
             }
681 |  |
         }
682 |  |
683 |  |
         /**
684 |  |
          * Checks if parts of the passed in comment can be used as header comment.
685 |  |
          * This method checks whether a header comment can be defined (i.e. whether
686 |  |
          * this is the first comment in the loaded file). If this is the case, it is
687 |  |
          * searched for the lates blanc line. This line will mark the end of the
688 |  |
          * header comment. The return value is the index of the first line in the
689 |  |
          * passed in list, which does not belong to the header comment.
690 |  |
          *
691 |  |
          * @param commentLines the comment lines
692 |  |
          * @return the index of the next line after the header comment
693 |  |
          */
694 |  |
         private int checkHeaderComment(List commentLines)
695 |  |
         {
696 | 21371 |
             if (loadCounter == 1 && getHeaderComment() == null
697 |  |
                     && layoutData.isEmpty())
698 |  |
             {
699 |  |
                 // This is the first comment. Search for blanc lines.
700 | 377 |
                 int index = commentLines.size() - 1;
701 |  |
                 while (index >= 0
702 | 385 |
                         && ((String) commentLines.get(index)).length() > 0)
703 |  |
                 {
704 | 8 |
                     index--;
705 | 8 |
                 }
706 | 377 |
                 setHeaderComment(extractComment(commentLines, 0, index - 1));
707 | 377 |
                 return index + 1;
708 |  |
             }
709 |  |
             else
710 |  |
             {
711 | 20994 |
                 return 0;
712 |  |
             }
713 |  |
         }
714 |  |
715 |  |
         /**
716 |  |
          * Copies the data from the given layout object.
717 |  |
          *
718 |  |
          * @param c the layout object to copy
719 |  |
          */
720 |  |
         private void copyFrom(PropertiesConfigurationLayout c)
721 |  |
         {
722 | 6 |
             for (Iterator it = c.getKeys().iterator(); it.hasNext();)
723 |  |
             {
724 | 47 |
                 String key = (String) it.next();
725 | 47 |
                 PropertyLayoutData data = (PropertyLayoutData) c.layoutData
726 |  |
                         .get(key);
727 | 47 |
                 layoutData.put(key, data.clone());
728 | 47 |
             }
729 | 6 |
         }
730 |  |
731 |  |
         /**
732 |  |
          * A helper class for storing all layout related information for a
733 |  |
          * configuration property.
734 |  |
          */
735 |  |
         static class PropertyLayoutData implements Cloneable
736 |  |
         {
737 |  |
             /** Stores the comment for the property. */
738 |  |
             private StringBuffer comment;
739 |  |
740 |  |
             /** Stores the number of blanc lines before this property. */
741 |  |
             private int blancLines;
742 |  |
743 |  |
             /** Stores the single line property. */
744 |  |
             private boolean singleLine;
745 |  |
746 |  |
             /**
747 |  |
              * Creates a new instance of <code>PropertyLayoutData</code>.
748 |  |
              */
749 |  |
             public PropertyLayoutData()
750 | 10169 |
             {
751 | 10169 |
                 singleLine = true;
752 | 10169 |
             }
753 |  |
754 |  |
             /**
755 |  |
              * Returns the number of blanc lines before this property.
756 |  |
              *
757 |  |
              * @return the number of blanc lines before this property
758 |  |
              */
759 |  |
             public int getBlancLines()
760 |  |
             {
761 | 747 |
                 return blancLines;
762 |  |
             }
763 |  |
764 |  |
             /**
765 |  |
              * Sets the number of properties before this property.
766 |  |
              *
767 |  |
              * @param blancLines the number of properties before this property
768 |  |
              */
769 |  |
             public void setBlancLines(int blancLines)
770 |  |
             {
771 | 10018 |
                 this.blancLines = blancLines;
772 | 10018 |
             }
773 |  |
774 |  |
             /**
775 |  |
              * Returns the single line flag.
776 |  |
              *
777 |  |
              * @return the single line flag
778 |  |
              */
779 |  |
             public boolean isSingleLine()
780 |  |
             {
781 | 541 |
                 return singleLine;
782 |  |
             }
783 |  |
784 |  |
             /**
785 |  |
              * Sets the single line flag.
786 |  |
              *
787 |  |
              * @param singleLine the single line flag
788 |  |
              */
789 |  |
             public void setSingleLine(boolean singleLine)
790 |  |
             {
791 | 21646 |
                 this.singleLine = singleLine;
792 | 21646 |
             }
793 |  |
794 |  |
             /**
795 |  |
              * Adds a comment for this property. If already a comment exists, the
796 |  |
              * new comment is added (separated by a newline).
797 |  |
              *
798 |  |
              * @param s the comment to add
799 |  |
              */
800 |  |
             public void addComment(String s)
801 |  |
             {
802 | 11355 |
                 if (s != null)
803 |  |
                 {
804 | 830 |
                     if (comment == null)
805 |  |
                     {
806 | 17 |
                         comment = new StringBuffer(s);
807 | 17 |
                     }
808 |  |
                     else
809 |  |
                     {
810 | 813 |
                         comment.append(CR).append(s);
811 |  |
                     }
812 |  |
                 }
813 | 11355 |
             }
814 |  |
815 |  |
             /**
816 |  |
              * Sets the comment for this property.
817 |  |
              *
818 |  |
              * @param s the new comment (can be <b>null</b>)
819 |  |
              */
820 |  |
             public void setComment(String s)
821 |  |
             {
822 | 10022 |
                 if (s == null)
823 |  |
                 {
824 | 8793 |
                     comment = null;
825 | 8793 |
                 }
826 |  |
                 else
827 |  |
                 {
828 | 1229 |
                     comment = new StringBuffer(s);
829 |  |
                 }
830 | 10022 |
             }
831 |  |
832 |  |
             /**
833 |  |
              * Returns the comment for this property. The comment is returned as it
834 |  |
              * is, without processing of comment characters.
835 |  |
              *
836 |  |
              * @return the comment (can be <b>null</b>)
837 |  |
              */
838 |  |
             public String getComment()
839 |  |
             {
840 | 618 |
                 return (comment == null) ? null : comment.toString();
841 |  |
             }
842 |  |
843 |  |
             /**
844 |  |
              * Creates a copy of this object.
845 |  |
              *
846 |  |
              * @return the copy
847 |  |
              */
848 |  |
             public Object clone()
849 |  |
             {
850 |  |
                 try
851 |  |
                 {
852 | 47 |
                     PropertyLayoutData copy = (PropertyLayoutData) super.clone();
853 | 47 |
                     if (comment != null)
854 |  |
                     {
855 |  |
                         // must copy string buffer, too
856 | 7 |
                         copy.comment = new StringBuffer(getComment());
857 |  |
                     }
858 | 47 |
                     return copy;
859 |  |
                 }
860 | 0 |
                 catch (CloneNotSupportedException cnex)
861 |  |
                 {
862 |  |
                     // This cannot happen!
863 | 0 |
                     throw new ConfigurationRuntimeException(cnex);
864 |  |
                 }
865 |  |
             }
866 |  |
         }
867 |  |
     }