001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.configuration;
018
019 import java.io.IOException;
020 import java.io.Reader;
021 import java.io.Writer;
022 import java.util.LinkedHashMap;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.Set;
026
027 import org.apache.commons.configuration.event.ConfigurationEvent;
028 import org.apache.commons.configuration.event.ConfigurationListener;
029 import org.apache.commons.lang.StringUtils;
030
031 /**
032 * <p>
033 * A helper class used by {@link PropertiesConfiguration} to keep
034 * the layout of a properties file.
035 * </p>
036 * <p>
037 * Instances of this class are associated with a
038 * {@code PropertiesConfiguration} object. They are responsible for
039 * analyzing properties files and for extracting as much information about the
040 * file layout (e.g. empty lines, comments) as possible. When the properties
041 * file is written back again it should be close to the original.
042 * </p>
043 * <p>
044 * The {@code PropertiesConfigurationLayout} object associated with a
045 * {@code PropertiesConfiguration} object can be obtained using the
046 * {@code getLayout()} method of the configuration. Then the methods
047 * provided by this class can be used to alter the properties file's layout.
048 * </p>
049 * <p>
050 * Implementation note: This is a very simple implementation, which is far away
051 * from being perfect, i.e. the original layout of a properties file won't be
052 * reproduced in all cases. One limitation is that comments for multi-valued
053 * property keys are concatenated. Maybe this implementation can later be
054 * improved.
055 * </p>
056 * <p>
057 * To get an impression how this class works consider the following properties
058 * file:
059 * </p>
060 * <p>
061 *
062 * <pre>
063 * # A demo configuration file
064 * # for Demo App 1.42
065 *
066 * # Application name
067 * AppName=Demo App
068 *
069 * # Application vendor
070 * AppVendor=DemoSoft
071 *
072 *
073 * # GUI properties
074 * # Window Color
075 * windowColors=0xFFFFFF,0x000000
076 *
077 * # Include some setting
078 * include=settings.properties
079 * # Another vendor
080 * AppVendor=TestSoft
081 * </pre>
082 *
083 * </p>
084 * <p>
085 * For this example the following points are relevant:
086 * </p>
087 * <p>
088 * <ul>
089 * <li>The first two lines are set as header comment. The header comment is
090 * determined by the last blanc line before the first property definition.</li>
091 * <li>For the property {@code AppName} one comment line and one
092 * leading blanc line is stored.</li>
093 * <li>For the property {@code windowColors} two comment lines and two
094 * leading blanc lines are stored.</li>
095 * <li>Include files is something this class cannot deal with well. When saving
096 * the properties configuration back, the included properties are simply
097 * contained in the original file. The comment before the include property is
098 * skipped.</li>
099 * <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 1206295 2011-11-25 19:56:26Z oheger $
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 this(config, null);
155 }
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 {
168 if (config == null)
169 {
170 throw new IllegalArgumentException(
171 "Configuration must not be null!");
172 }
173 configuration = config;
174 layoutData = new LinkedHashMap<String, PropertyLayoutData>();
175 config.addConfigurationListener(this);
176
177 if (c != null)
178 {
179 copyFrom(c);
180 }
181 }
182
183 /**
184 * Returns the associated configuration object.
185 *
186 * @return the associated configuration
187 */
188 public PropertiesConfiguration getConfiguration()
189 {
190 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 String comment = getComment(key);
210 if (comment == null)
211 {
212 return null;
213 }
214 else
215 {
216 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 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 fetchLayoutData(key).setComment(comment);
247 }
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 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 fetchLayoutData(key).setBlancLines(number);
273 }
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 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 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 headerComment = comment;
312 }
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 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 fetchLayoutData(key).setSingleLine(f);
340 }
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 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 forceSingleLine = f;
365 }
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 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 fetchLayoutData(key).setSeparator(sep);
398 }
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 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 this.globalSeparator = globalSeparator;
428 }
429
430 /**
431 * Returns the line separator.
432 *
433 * @return the line separator
434 * @since 1.7
435 */
436 public String getLineSeparator()
437 {
438 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 this.lineSeparator = lineSeparator;
452 }
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 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 if (++loadCounter == 1)
474 {
475 getConfiguration().removeConfigurationListener(this);
476 }
477 PropertiesConfiguration.PropertiesReader reader = getConfiguration()
478 .getIOFactory().createPropertiesReader(in,
479 getConfiguration().getListDelimiter());
480
481 try
482 {
483 while (reader.nextProperty())
484 {
485 if (getConfiguration().propertyLoaded(reader.getPropertyName(),
486 reader.getPropertyValue()))
487 {
488 boolean contained = layoutData.containsKey(reader
489 .getPropertyName());
490 int blancLines = 0;
491 int idx = checkHeaderComment(reader.getCommentLines());
492 while (idx < reader.getCommentLines().size()
493 && ((String) reader.getCommentLines().get(idx))
494 .length() < 1)
495 {
496 idx++;
497 blancLines++;
498 }
499 String comment = extractComment(reader.getCommentLines(),
500 idx, reader.getCommentLines().size() - 1);
501 PropertyLayoutData data = fetchLayoutData(reader
502 .getPropertyName());
503 if (contained)
504 {
505 data.addComment(comment);
506 data.setSingleLine(false);
507 }
508 else
509 {
510 data.setComment(comment);
511 data.setBlancLines(blancLines);
512 data.setSeparator(reader.getPropertySeparator());
513 }
514 }
515 }
516 }
517 catch (IOException ioex)
518 {
519 throw new ConfigurationException(ioex);
520 }
521 finally
522 {
523 if (--loadCounter == 0)
524 {
525 getConfiguration().addConfigurationListener(this);
526 }
527 }
528 }
529
530 /**
531 * Writes the properties file to the given writer, preserving as much of its
532 * structure as possible.
533 *
534 * @param out the writer
535 * @throws ConfigurationException if an error occurs
536 */
537 public void save(Writer out) throws ConfigurationException
538 {
539 try
540 {
541 char delimiter = getConfiguration().isDelimiterParsingDisabled() ? 0
542 : getConfiguration().getListDelimiter();
543 PropertiesConfiguration.PropertiesWriter writer = getConfiguration()
544 .getIOFactory().createPropertiesWriter(out, delimiter);
545 writer.setGlobalSeparator(getGlobalSeparator());
546 if (getLineSeparator() != null)
547 {
548 writer.setLineSeparator(getLineSeparator());
549 }
550
551 if (headerComment != null)
552 {
553 writeComment(writer, getCanonicalHeaderComment(true));
554 writer.writeln(null);
555 }
556
557 for (String key : layoutData.keySet())
558 {
559 if (getConfiguration().containsKey(key))
560 {
561
562 // Output blank lines before property
563 for (int i = 0; i < getBlancLinesBefore(key); i++)
564 {
565 writer.writeln(null);
566 }
567
568 // Output the comment
569 writeComment(writer, getCanonicalComment(key, true));
570
571 // Output the property and its value
572 boolean singleLine = (isForceSingleLine() || isSingleLine(key))
573 && !getConfiguration().isDelimiterParsingDisabled();
574 writer.setCurrentSeparator(getSeparator(key));
575 writer.writeProperty(key, getConfiguration().getProperty(
576 key), singleLine);
577 }
578 }
579 writer.flush();
580 }
581 catch (IOException ioex)
582 {
583 throw new ConfigurationException(ioex);
584 }
585 }
586
587 /**
588 * The event listener callback. Here event notifications of the
589 * configuration object are processed to update the layout object properly.
590 *
591 * @param event the event object
592 */
593 public void configurationChanged(ConfigurationEvent event)
594 {
595 if (event.isBeforeUpdate())
596 {
597 if (AbstractFileConfiguration.EVENT_RELOAD == event.getType())
598 {
599 clear();
600 }
601 }
602
603 else
604 {
605 switch (event.getType())
606 {
607 case AbstractConfiguration.EVENT_ADD_PROPERTY:
608 boolean contained = layoutData.containsKey(event
609 .getPropertyName());
610 PropertyLayoutData data = fetchLayoutData(event
611 .getPropertyName());
612 data.setSingleLine(!contained);
613 break;
614 case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
615 layoutData.remove(event.getPropertyName());
616 break;
617 case AbstractConfiguration.EVENT_CLEAR:
618 clear();
619 break;
620 case AbstractConfiguration.EVENT_SET_PROPERTY:
621 fetchLayoutData(event.getPropertyName());
622 break;
623 }
624 }
625 }
626
627 /**
628 * Returns a layout data object for the specified key. If this is a new key,
629 * a new object is created and initialized with default values.
630 *
631 * @param key the key
632 * @return the corresponding layout data object
633 */
634 private PropertyLayoutData fetchLayoutData(String key)
635 {
636 if (key == null)
637 {
638 throw new IllegalArgumentException("Property key must not be null!");
639 }
640
641 PropertyLayoutData data = layoutData.get(key);
642 if (data == null)
643 {
644 data = new PropertyLayoutData();
645 data.setSingleLine(true);
646 layoutData.put(key, data);
647 }
648
649 return data;
650 }
651
652 /**
653 * Removes all content from this layout object.
654 */
655 private void clear()
656 {
657 layoutData.clear();
658 setHeaderComment(null);
659 }
660
661 /**
662 * Tests whether a line is a comment, i.e. whether it starts with a comment
663 * character.
664 *
665 * @param line the line
666 * @return a flag if this is a comment line
667 */
668 static boolean isCommentLine(String line)
669 {
670 return PropertiesConfiguration.isCommentLine(line);
671 }
672
673 /**
674 * Trims a comment. This method either removes all comment characters from
675 * the given string, leaving only the plain comment text or ensures that
676 * every line starts with a valid comment character.
677 *
678 * @param s the string to be processed
679 * @param comment if <b>true</b>, a comment character will always be
680 * enforced; if <b>false</b>, it will be removed
681 * @return the trimmed comment
682 */
683 static String trimComment(String s, boolean comment)
684 {
685 StringBuilder buf = new StringBuilder(s.length());
686 int lastPos = 0;
687 int pos;
688
689 do
690 {
691 pos = s.indexOf(CR, lastPos);
692 if (pos >= 0)
693 {
694 String line = s.substring(lastPos, pos);
695 buf.append(stripCommentChar(line, comment)).append(CR);
696 lastPos = pos + CR.length();
697 }
698 } while (pos >= 0);
699
700 if (lastPos < s.length())
701 {
702 buf.append(stripCommentChar(s.substring(lastPos), comment));
703 }
704 return buf.toString();
705 }
706
707 /**
708 * Either removes the comment character from the given comment line or
709 * ensures that the line starts with a comment character.
710 *
711 * @param s the comment line
712 * @param comment if <b>true</b>, a comment character will always be
713 * enforced; if <b>false</b>, it will be removed
714 * @return the line without comment character
715 */
716 static String stripCommentChar(String s, boolean comment)
717 {
718 if (s.length() < 1 || (isCommentLine(s) == comment))
719 {
720 return s;
721 }
722
723 else
724 {
725 if (!comment)
726 {
727 int pos = 0;
728 // find first comment character
729 while (PropertiesConfiguration.COMMENT_CHARS.indexOf(s
730 .charAt(pos)) < 0)
731 {
732 pos++;
733 }
734
735 // Remove leading spaces
736 pos++;
737 while (pos < s.length()
738 && Character.isWhitespace(s.charAt(pos)))
739 {
740 pos++;
741 }
742
743 return (pos < s.length()) ? s.substring(pos)
744 : StringUtils.EMPTY;
745 }
746 else
747 {
748 return COMMENT_PREFIX + s;
749 }
750 }
751 }
752
753 /**
754 * Extracts a comment string from the given range of the specified comment
755 * lines. The single lines are added using a line feed as separator.
756 *
757 * @param commentLines a list with comment lines
758 * @param from the start index
759 * @param to the end index (inclusive)
760 * @return the comment string (<b>null</b> if it is undefined)
761 */
762 private String extractComment(List<String> commentLines, int from, int to)
763 {
764 if (to < from)
765 {
766 return null;
767 }
768
769 else
770 {
771 StringBuilder buf = new StringBuilder(commentLines.get(from));
772 for (int i = from + 1; i <= to; i++)
773 {
774 buf.append(CR);
775 buf.append(commentLines.get(i));
776 }
777 return buf.toString();
778 }
779 }
780
781 /**
782 * Checks if parts of the passed in comment can be used as header comment.
783 * This method checks whether a header comment can be defined (i.e. whether
784 * this is the first comment in the loaded file). If this is the case, it is
785 * searched for the latest blanc line. This line will mark the end of the
786 * header comment. The return value is the index of the first line in the
787 * passed in list, which does not belong to the header comment.
788 *
789 * @param commentLines the comment lines
790 * @return the index of the next line after the header comment
791 */
792 private int checkHeaderComment(List<String> commentLines)
793 {
794 if (loadCounter == 1 && getHeaderComment() == null
795 && layoutData.isEmpty())
796 {
797 // This is the first comment. Search for blanc lines.
798 int index = commentLines.size() - 1;
799 while (index >= 0
800 && ((String) commentLines.get(index)).length() > 0)
801 {
802 index--;
803 }
804 setHeaderComment(extractComment(commentLines, 0, index - 1));
805 return index + 1;
806 }
807 else
808 {
809 return 0;
810 }
811 }
812
813 /**
814 * Copies the data from the given layout object.
815 *
816 * @param c the layout object to copy
817 */
818 private void copyFrom(PropertiesConfigurationLayout c)
819 {
820 for (String key : c.getKeys())
821 {
822 PropertyLayoutData data = (PropertyLayoutData) c.layoutData
823 .get(key);
824 layoutData.put(key, data.clone());
825 }
826 }
827
828 /**
829 * Helper method for writing a comment line. This method ensures that the
830 * correct line separator is used if the comment spans multiple lines.
831 *
832 * @param writer the writer
833 * @param comment the comment to write
834 * @throws IOException if an IO error occurs
835 */
836 private static void writeComment(
837 PropertiesConfiguration.PropertiesWriter writer, String comment)
838 throws IOException
839 {
840 if (comment != null)
841 {
842 writer.writeln(StringUtils.replace(comment, CR, writer
843 .getLineSeparator()));
844 }
845 }
846
847 /**
848 * A helper class for storing all layout related information for a
849 * configuration property.
850 */
851 static class PropertyLayoutData implements Cloneable
852 {
853 /** Stores the comment for the property. */
854 private StringBuffer comment;
855
856 /** The separator to be used for this property. */
857 private String separator;
858
859 /** Stores the number of blanc lines before this property. */
860 private int blancLines;
861
862 /** Stores the single line property. */
863 private boolean singleLine;
864
865 /**
866 * Creates a new instance of {@code PropertyLayoutData}.
867 */
868 public PropertyLayoutData()
869 {
870 singleLine = true;
871 separator = PropertiesConfiguration.DEFAULT_SEPARATOR;
872 }
873
874 /**
875 * Returns the number of blanc lines before this property.
876 *
877 * @return the number of blanc lines before this property
878 */
879 public int getBlancLines()
880 {
881 return blancLines;
882 }
883
884 /**
885 * Sets the number of properties before this property.
886 *
887 * @param blancLines the number of properties before this property
888 */
889 public void setBlancLines(int blancLines)
890 {
891 this.blancLines = blancLines;
892 }
893
894 /**
895 * Returns the single line flag.
896 *
897 * @return the single line flag
898 */
899 public boolean isSingleLine()
900 {
901 return singleLine;
902 }
903
904 /**
905 * Sets the single line flag.
906 *
907 * @param singleLine the single line flag
908 */
909 public void setSingleLine(boolean singleLine)
910 {
911 this.singleLine = singleLine;
912 }
913
914 /**
915 * Adds a comment for this property. If already a comment exists, the
916 * new comment is added (separated by a newline).
917 *
918 * @param s the comment to add
919 */
920 public void addComment(String s)
921 {
922 if (s != null)
923 {
924 if (comment == null)
925 {
926 comment = new StringBuffer(s);
927 }
928 else
929 {
930 comment.append(CR).append(s);
931 }
932 }
933 }
934
935 /**
936 * Sets the comment for this property.
937 *
938 * @param s the new comment (can be <b>null</b>)
939 */
940 public void setComment(String s)
941 {
942 if (s == null)
943 {
944 comment = null;
945 }
946 else
947 {
948 comment = new StringBuffer(s);
949 }
950 }
951
952 /**
953 * Returns the comment for this property. The comment is returned as it
954 * is, without processing of comment characters.
955 *
956 * @return the comment (can be <b>null</b>)
957 */
958 public String getComment()
959 {
960 return (comment == null) ? null : comment.toString();
961 }
962
963 /**
964 * Returns the separator that was used for this property.
965 *
966 * @return the property separator
967 */
968 public String getSeparator()
969 {
970 return separator;
971 }
972
973 /**
974 * Sets the separator to be used for the represented property.
975 *
976 * @param separator the property separator
977 */
978 public void setSeparator(String separator)
979 {
980 this.separator = separator;
981 }
982
983 /**
984 * Creates a copy of this object.
985 *
986 * @return the copy
987 */
988 @Override
989 public PropertyLayoutData clone()
990 {
991 try
992 {
993 PropertyLayoutData copy = (PropertyLayoutData) super.clone();
994 if (comment != null)
995 {
996 // must copy string buffer, too
997 copy.comment = new StringBuffer(getComment());
998 }
999 return copy;
1000 }
1001 catch (CloneNotSupportedException cnex)
1002 {
1003 // This cannot happen!
1004 throw new ConfigurationRuntimeException(cnex);
1005 }
1006 }
1007 }
1008 }