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