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