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 }