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 }