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 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 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 && reader.getCommentLines().get(idx).length() < 1)
494 {
495 idx++;
496 blancLines++;
497 }
498 String comment = extractComment(reader.getCommentLines(),
499 idx, reader.getCommentLines().size() - 1);
500 PropertyLayoutData data = fetchLayoutData(reader
501 .getPropertyName());
502 if (contained)
503 {
504 data.addComment(comment);
505 data.setSingleLine(false);
506 }
507 else
508 {
509 data.setComment(comment);
510 data.setBlancLines(blancLines);
511 data.setSeparator(reader.getPropertySeparator());
512 }
513 }
514 }
515 }
516 catch (IOException ioex)
517 {
518 throw new ConfigurationException(ioex);
519 }
520 finally
521 {
522 if (--loadCounter == 0)
523 {
524 getConfiguration().addConfigurationListener(this);
525 }
526 }
527 }
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 char delimiter = getConfiguration().isDelimiterParsingDisabled() ? 0
541 : getConfiguration().getListDelimiter();
542 PropertiesConfiguration.PropertiesWriter writer = getConfiguration()
543 .getIOFactory().createPropertiesWriter(out, delimiter);
544 writer.setGlobalSeparator(getGlobalSeparator());
545 if (getLineSeparator() != null)
546 {
547 writer.setLineSeparator(getLineSeparator());
548 }
549
550 if (headerComment != null)
551 {
552 writeComment(writer, getCanonicalHeaderComment(true));
553 writer.writeln(null);
554 }
555
556 for (String key : layoutData.keySet())
557 {
558 if (getConfiguration().containsKey(key))
559 {
560
561 // Output blank lines before property
562 for (int i = 0; i < getBlancLinesBefore(key); i++)
563 {
564 writer.writeln(null);
565 }
566
567 // Output the comment
568 writeComment(writer, getCanonicalComment(key, true));
569
570 // Output the property and its value
571 boolean singleLine = (isForceSingleLine() || isSingleLine(key))
572 && !getConfiguration().isDelimiterParsingDisabled();
573 writer.setCurrentSeparator(getSeparator(key));
574 writer.writeProperty(key, getConfiguration().getProperty(
575 key), singleLine);
576 }
577 }
578 writer.flush();
579 }
580 catch (IOException ioex)
581 {
582 throw new ConfigurationException(ioex);
583 }
584 }
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 if (event.isBeforeUpdate())
595 {
596 if (AbstractFileConfiguration.EVENT_RELOAD == event.getType())
597 {
598 clear();
599 }
600 }
601
602 else
603 {
604 switch (event.getType())
605 {
606 case AbstractConfiguration.EVENT_ADD_PROPERTY:
607 boolean contained = layoutData.containsKey(event
608 .getPropertyName());
609 PropertyLayoutData data = fetchLayoutData(event
610 .getPropertyName());
611 data.setSingleLine(!contained);
612 break;
613 case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
614 layoutData.remove(event.getPropertyName());
615 break;
616 case AbstractConfiguration.EVENT_CLEAR:
617 clear();
618 break;
619 case AbstractConfiguration.EVENT_SET_PROPERTY:
620 fetchLayoutData(event.getPropertyName());
621 break;
622 }
623 }
624 }
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 if (key == null)
636 {
637 throw new IllegalArgumentException("Property key must not be null!");
638 }
639
640 PropertyLayoutData data = layoutData.get(key);
641 if (data == null)
642 {
643 data = new PropertyLayoutData();
644 data.setSingleLine(true);
645 layoutData.put(key, data);
646 }
647
648 return data;
649 }
650
651 /**
652 * Removes all content from this layout object.
653 */
654 private void clear()
655 {
656 layoutData.clear();
657 setHeaderComment(null);
658 }
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 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 StringBuilder buf = new StringBuilder(s.length());
685 int lastPos = 0;
686 int pos;
687
688 do
689 {
690 pos = s.indexOf(CR, lastPos);
691 if (pos >= 0)
692 {
693 String line = s.substring(lastPos, pos);
694 buf.append(stripCommentChar(line, comment)).append(CR);
695 lastPos = pos + CR.length();
696 }
697 } while (pos >= 0);
698
699 if (lastPos < s.length())
700 {
701 buf.append(stripCommentChar(s.substring(lastPos), comment));
702 }
703 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 if (s.length() < 1 || (isCommentLine(s) == comment))
718 {
719 return s;
720 }
721
722 else
723 {
724 if (!comment)
725 {
726 int pos = 0;
727 // find first comment character
728 while (PropertiesConfiguration.COMMENT_CHARS.indexOf(s
729 .charAt(pos)) < 0)
730 {
731 pos++;
732 }
733
734 // Remove leading spaces
735 pos++;
736 while (pos < s.length()
737 && Character.isWhitespace(s.charAt(pos)))
738 {
739 pos++;
740 }
741
742 return (pos < s.length()) ? s.substring(pos)
743 : StringUtils.EMPTY;
744 }
745 else
746 {
747 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 if (to < from)
764 {
765 return null;
766 }
767
768 else
769 {
770 StringBuilder buf = new StringBuilder(commentLines.get(from));
771 for (int i = from + 1; i <= to; i++)
772 {
773 buf.append(CR);
774 buf.append(commentLines.get(i));
775 }
776 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 if (loadCounter == 1 && getHeaderComment() == null
794 && layoutData.isEmpty())
795 {
796 // This is the first comment. Search for blanc lines.
797 int index = commentLines.size() - 1;
798 while (index >= 0
799 && commentLines.get(index).length() > 0)
800 {
801 index--;
802 }
803 setHeaderComment(extractComment(commentLines, 0, index - 1));
804 return index + 1;
805 }
806 else
807 {
808 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 for (String key : c.getKeys())
820 {
821 PropertyLayoutData data = c.layoutData.get(key);
822 layoutData.put(key, data.clone());
823 }
824 }
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 if (comment != null)
839 {
840 writer.writeln(StringUtils.replace(comment, CR, writer
841 .getLineSeparator()));
842 }
843 }
844
845 /**
846 * A helper class for storing all layout related information for a
847 * configuration property.
848 */
849 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 {
868 singleLine = true;
869 separator = PropertiesConfiguration.DEFAULT_SEPARATOR;
870 }
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 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 this.blancLines = blancLines;
890 }
891
892 /**
893 * Returns the single line flag.
894 *
895 * @return the single line flag
896 */
897 public boolean isSingleLine()
898 {
899 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 this.singleLine = singleLine;
910 }
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 if (s != null)
921 {
922 if (comment == null)
923 {
924 comment = new StringBuffer(s);
925 }
926 else
927 {
928 comment.append(CR).append(s);
929 }
930 }
931 }
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 if (s == null)
941 {
942 comment = null;
943 }
944 else
945 {
946 comment = new StringBuffer(s);
947 }
948 }
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 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 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 this.separator = separator;
979 }
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 PropertyLayoutData copy = (PropertyLayoutData) super.clone();
992 if (comment != null)
993 {
994 // must copy string buffer, too
995 copy.comment = new StringBuffer(getComment());
996 }
997 return copy;
998 }
999 catch (CloneNotSupportedException cnex)
1000 {
1001 // This cannot happen!
1002 throw new ConfigurationRuntimeException(cnex);
1003 }
1004 }
1005 }
1006 }