View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertSame;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.Reader;
29  import java.io.StringReader;
30  import java.io.StringWriter;
31  import java.net.URL;
32  import java.util.Deque;
33  import java.util.Iterator;
34  
35  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
36  import org.apache.commons.configuration2.convert.LegacyListDelimiterHandler;
37  import org.apache.commons.configuration2.event.ConfigurationEvent;
38  import org.apache.commons.configuration2.event.EventListener;
39  import org.apache.commons.configuration2.ex.ConfigurationException;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * Test class for PropertiesConfigurationLayout.
45   */
46  public class TestPropertiesConfigurationLayout {
47      /**
48       * A mock properties configuration implementation that is used to check whether some expected methods are called.
49       */
50      static class LayoutTestConfiguration extends PropertiesConfiguration {
51          /** Stores a builder object. */
52          public PropertiesBuilder builder;
53  
54          /**
55           * Simulates the propertyLoaded() callback. If a builder was set, a load() call on the layout is invoked.
56           */
57          @Override
58          boolean propertyLoaded(final String key, final String value, final Deque<URL> seenStack) throws ConfigurationException {
59              if (builder == null) {
60                  return super.propertyLoaded(key, value, seenStack);
61              }
62              if (PropertiesConfiguration.getInclude().equals(key)) {
63                  getLayout().load(this, builder.getReader());
64                  return false;
65              }
66              return true;
67          }
68      }
69  
70      /**
71       * A helper class used for constructing test properties files.
72       */
73      static class PropertiesBuilder {
74          /** A buffer for storing the data. */
75          private final StringBuilder buf = new StringBuilder();
76  
77          /** A counter for varying the comment character. */
78          private int commentCounter;
79  
80          /**
81           * Adds a comment line.
82           *
83           * @param s the comment (can be <b>null</b>, then a blank line is added)
84           */
85          public void addComment(final String s) {
86              if (s != null) {
87                  if (commentCounter % 2 == 0) {
88                      buf.append("# ");
89                  } else {
90                      buf.append("! ");
91                  }
92                  buf.append(s);
93                  commentCounter++;
94              }
95              buf.append(CR);
96          }
97  
98          /**
99           * Adds a line verbatim to the simulated file.
100          *
101          * @param s the content of the line
102          */
103         public void addLine(final String s) {
104             buf.append(s).append(CR);
105         }
106 
107         /**
108          * Adds a property to the simulated file.
109          *
110          * @param key the property key
111          * @param value the value
112          */
113         public void addProperty(final String key, final String value) {
114             buf.append(key).append(" = ").append(value).append(CR);
115         }
116 
117         /**
118          * Returns a reader for the simulated properties.
119          *
120          * @return a reader
121          */
122         public Reader getReader() {
123             return new StringReader(buf.toString());
124         }
125 
126         /**
127          * Returns a string representation of the buffer's content.
128          *
129          * @return the buffer as string
130          */
131         @Override
132         public String toString() {
133             return buf.toString();
134         }
135     }
136 
137     /** Constant for the line break character. */
138     private static final String CR = System.lineSeparator();
139 
140     /** Constant for the normalized line break character. */
141     private static final String CRNORM = "\n";
142 
143     /** Constant for a test property key. */
144     private static final String TEST_KEY = "myProperty";
145 
146     /** Constant for a test comment. */
147     private static final String TEST_COMMENT = "A comment for my test property";
148 
149     /** Constant for a test property value. */
150     private static final String TEST_VALUE = "myPropertyValue";
151 
152     /** The layout object under test. */
153     private PropertiesConfigurationLayout layout;
154 
155     /** The associated configuration object. */
156     private LayoutTestConfiguration config;
157 
158     /** A properties builder that can be used for testing. */
159     private PropertiesBuilder builder;
160 
161     /**
162      * Checks if the layout's output is correct.
163      *
164      * @param expected the expected result
165      * @throws ConfigurationException if an error occurs
166      */
167     private void checkLayoutString(final String expected) throws ConfigurationException {
168         assertEquals(expected, getLayoutString());
169     }
170 
171     /**
172      * Helper method for filling the layout object with some properties.
173      */
174     private void fillLayout() {
175         builder.addComment("A header comment");
176         builder.addComment(null);
177         builder.addProperty("prop", "value");
178         builder.addComment(TEST_COMMENT);
179         builder.addProperty(TEST_KEY, TEST_VALUE);
180         builder.addProperty("anotherProp", "anotherValue");
181         builder.addComment("A footer comment");
182         assertDoesNotThrow(() -> layout.load(config, builder.getReader()));
183     }
184 
185     /**
186      * Writes the layout's data into a string.
187      *
188      * @return the layout file's content as string
189      * @throws ConfigurationException if an error occurs
190      */
191     private String getLayoutString() throws ConfigurationException {
192         final StringWriter out = new StringWriter();
193         layout.save(config, out);
194         return out.toString();
195     }
196 
197     @BeforeEach
198     public void setUp() throws Exception {
199         config = new LayoutTestConfiguration();
200         config.setListDelimiterHandler(new LegacyListDelimiterHandler(','));
201         layout = new PropertiesConfigurationLayout();
202         config.setLayout(layout);
203         builder = new PropertiesBuilder();
204     }
205 
206     /**
207      * Tests whether blank lines before a property are correctly detected.
208      */
209     @Test
210     public void testBlankLines() throws ConfigurationException {
211         builder.addProperty("prop", "value");
212         builder.addComment(null);
213         builder.addComment(null);
214         builder.addComment(TEST_COMMENT);
215         builder.addComment(null);
216         builder.addProperty(TEST_KEY, TEST_VALUE);
217         layout.load(config, builder.getReader());
218         assertEquals(2, layout.getBlankLinesBefore(TEST_KEY));
219         assertEquals(TEST_COMMENT + CRNORM, layout.getCanonicalComment(TEST_KEY, false));
220         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
221     }
222 
223     /**
224      * Tests whether blank lines before a property are correctly detected if a header comment is present
225      */
226     @Test
227     public void testBlankLinesWithHeaderComment() throws ConfigurationException {
228         builder.addComment(TEST_COMMENT);
229         builder.addComment(null);
230         builder.addComment(null);
231         builder.addComment(TEST_COMMENT);
232         builder.addProperty(TEST_KEY, TEST_VALUE);
233         layout.load(config, builder.getReader());
234         assertEquals(2, layout.getBlankLinesBefore(TEST_KEY));
235         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
236         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
237     }
238 
239     /**
240      * Tests whether comments are combined for multiple occurrences.
241      */
242     @Test
243     public void testCombineComments() throws ConfigurationException {
244         builder.addComment(TEST_COMMENT);
245         builder.addProperty(TEST_KEY, TEST_VALUE);
246         builder.addComment(null);
247         builder.addComment(TEST_COMMENT);
248         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
249         layout.load(config, builder.getReader());
250         assertEquals(TEST_COMMENT + CRNORM + TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
251         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
252     }
253 
254     /**
255      * Tests if a property add event is correctly processed.
256      */
257     @Test
258     public void testEventAdd() {
259         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
260         layout.onEvent(event);
261         assertTrue(layout.getKeys().contains(TEST_KEY));
262         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
263         assertTrue(layout.isSingleLine(TEST_KEY));
264         assertEquals(" = ", layout.getSeparator(TEST_KEY));
265     }
266 
267     /**
268      * Tests if a before update event is correctly ignored.
269      */
270     @Test
271     public void testEventAddBefore() {
272         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, true);
273         layout.onEvent(event);
274         assertFalse(layout.getKeys().contains(TEST_KEY));
275     }
276 
277     /**
278      * Tests if an add event is correctly processed if the affected property is already stored in the layout object.
279      */
280     @Test
281     public void testEventAddExisting() throws ConfigurationException {
282         builder.addComment(TEST_COMMENT);
283         builder.addProperty(TEST_KEY, TEST_VALUE);
284         layout.load(config, builder.getReader());
285         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
286         layout.onEvent(event);
287         assertFalse(layout.isSingleLine(TEST_KEY));
288         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
289     }
290 
291     /**
292      * Tests adding a property multiple time through an event. The property should then be a multi-line property.
293      */
294     @Test
295     public void testEventAddMultiple() {
296         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
297         layout.onEvent(event);
298         layout.onEvent(event);
299         assertFalse(layout.isSingleLine(TEST_KEY));
300     }
301 
302     /**
303      * Tests if a clear event is correctly processed.
304      */
305     @Test
306     public void testEventClearConfig() throws Exception {
307         fillLayout();
308         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.CLEAR, null, null, false);
309         layout.onEvent(event);
310         assertTrue(layout.getKeys().isEmpty());
311         assertNull(layout.getHeaderComment());
312     }
313 
314     /**
315      * Tests if a property delete event is correctly processed.
316      */
317     @Test
318     public void testEventDelete() {
319         ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.ADD_PROPERTY, TEST_KEY, TEST_VALUE, false);
320         layout.onEvent(event);
321         event = new ConfigurationEvent(this, ConfigurationEvent.CLEAR_PROPERTY, TEST_KEY, null, false);
322         layout.onEvent(event);
323         assertFalse(layout.getKeys().contains(TEST_KEY));
324     }
325 
326     /**
327      * Tests if a set property event for a non existing property is correctly handled.
328      */
329     @Test
330     public void testEventSetNonExisting() {
331         final ConfigurationEvent event = new ConfigurationEvent(this, ConfigurationEvent.SET_PROPERTY, TEST_KEY, TEST_VALUE, false);
332         layout.onEvent(event);
333         assertTrue(layout.getKeys().contains(TEST_KEY));
334     }
335 
336     /**
337      * Tests accessing data for a property, which is not stored.
338      */
339     @Test
340     public void testGetNonExistingLayouData() {
341         assertNull(layout.getComment("unknown"));
342         assertTrue(layout.isSingleLine("unknown"));
343         assertEquals(0, layout.getBlankLinesBefore("unknown"));
344     }
345 
346     /**
347      * Tests accessing a property with a null key. This should throw an exception.
348      */
349     @Test
350     public void testGetNullLayouttData() {
351         assertThrows(IllegalArgumentException.class, () -> layout.setComment(null, TEST_COMMENT));
352     }
353 
354     /**
355      * Tests if a header comment is detected.
356      */
357     @Test
358     public void testHeaderComment() throws ConfigurationException {
359         builder.addComment(TEST_COMMENT);
360         builder.addComment(null);
361         builder.addProperty(TEST_KEY, TEST_VALUE);
362         layout.load(config, builder.getReader());
363         assertEquals(TEST_COMMENT, layout.getCanonicalHeaderComment(false));
364         assertNull(layout.getCanonicalComment(TEST_KEY, false));
365     }
366 
367     /**
368      * Tests fetching a canonical header comment when no comment is set.
369      */
370     @Test
371     public void testHeaderCommentNull() {
372         assertNull(layout.getCanonicalHeaderComment(true));
373         assertNull(layout.getCanonicalHeaderComment(false));
374     }
375 
376     /**
377      * Tests if a header comment containing blank lines is correctly detected.
378      */
379     @Test
380     public void testHeaderCommentWithBlanks() throws ConfigurationException {
381         builder.addComment(TEST_COMMENT);
382         builder.addComment(null);
383         builder.addComment(TEST_COMMENT);
384         builder.addComment(null);
385         builder.addProperty(TEST_KEY, TEST_VALUE);
386         layout.load(config, builder.getReader());
387         assertEquals(TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
388         assertNull(layout.getComment(TEST_KEY));
389     }
390 
391     /**
392      * Tests if a header comment containing blank lines is correctly detected and doesn't overflow into the property comment
393      * in the case that the header comment is already set
394      */
395     @Test
396     public void testHeaderCommentWithBlanksAndPresetHeaderComment() throws ConfigurationException {
397         final String presetHeaderComment = "preset" + TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT;
398         builder.addComment(TEST_COMMENT);
399         builder.addComment(null);
400         builder.addComment(TEST_COMMENT);
401         builder.addComment(null);
402         builder.addProperty(TEST_KEY, TEST_VALUE);
403         layout.setHeaderComment(presetHeaderComment);
404         layout.load(config, builder.getReader());
405         assertEquals(presetHeaderComment, layout.getCanonicalHeaderComment(false));
406         assertNull(layout.getComment(TEST_KEY));
407     }
408 
409     /**
410      * Tests if a header comment is correctly detected when it contains blank lines and the first property has a comment,
411      * too.
412      */
413     @Test
414     public void testHeaderCommentWithBlanksAndPropComment() throws ConfigurationException {
415         builder.addComment(TEST_COMMENT);
416         builder.addComment(null);
417         builder.addComment(TEST_COMMENT);
418         builder.addComment(null);
419         builder.addComment(TEST_COMMENT);
420         builder.addProperty(TEST_KEY, TEST_VALUE);
421         layout.load(config, builder.getReader());
422         assertEquals(TEST_COMMENT + CRNORM + CRNORM + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
423         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
424     }
425 
426     /**
427      * Tests a newly created instance.
428      */
429     @Test
430     public void testInit() {
431         assertTrue(layout.getKeys().isEmpty());
432         assertNull(layout.getHeaderComment());
433         final Iterator<EventListener<? super ConfigurationEvent>> it = config.getEventListeners(ConfigurationEvent.ANY).iterator();
434         assertTrue(it.hasNext());
435         assertSame(layout, it.next());
436         assertFalse(it.hasNext());
437         assertFalse(layout.isForceSingleLine());
438         assertNull(layout.getGlobalSeparator());
439     }
440 
441     /**
442      * Tests the copy constructor.
443      */
444     @Test
445     public void testInitCopy() {
446         fillLayout();
447         final PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(layout);
448         assertEquals(l2.getKeys(), layout.getKeys());
449         assertEquals(layout.getHeaderComment(), l2.getHeaderComment());
450         assertEquals(layout.getFooterComment(), l2.getFooterComment());
451     }
452 
453     /**
454      * Tests if the copy and the original are independent from each other.
455      */
456     @Test
457     public void testInitCopyModify() {
458         fillLayout();
459         final PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(layout);
460         assertEquals(layout.getComment(TEST_KEY), l2.getComment(TEST_KEY));
461         layout.setComment(TEST_KEY, "A new comment");
462         assertEquals(TEST_COMMENT, l2.getCanonicalComment(TEST_KEY, false));
463         l2.setBlankLinesBefore(TEST_KEY, l2.getBlankLinesBefore(TEST_KEY) + 1);
464         assertNotEquals(layout.getBlankLinesBefore(TEST_KEY), l2.getBlankLinesBefore(TEST_KEY));
465     }
466 
467     /**
468      * Tests the copy constructor if no other layout object is passed.
469      */
470     @Test
471     public void testInitNull() {
472         layout = new PropertiesConfigurationLayout(null);
473         assertTrue(layout.getKeys().isEmpty());
474     }
475 
476     /**
477      * Tests the single line flag for a simple property definition.
478      */
479     @Test
480     public void testIsSingleLine() throws ConfigurationException {
481         builder.addProperty(TEST_KEY, TEST_VALUE + "," + TEST_VALUE + "2");
482         layout.load(config, builder.getReader());
483         assertTrue(layout.isSingleLine(TEST_KEY));
484         assertEquals(2, config.getList(TEST_KEY).size());
485     }
486 
487     /**
488      * Tests the single line flag if there are multiple property definitions.
489      */
490     @Test
491     public void testIsSingleLineMulti() throws ConfigurationException {
492         builder.addProperty(TEST_KEY, TEST_VALUE);
493         builder.addProperty("anotherProp", "a value");
494         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
495         layout.load(config, builder.getReader());
496         assertFalse(layout.isSingleLine(TEST_KEY));
497         assertEquals(2, config.getList(TEST_KEY).size());
498     }
499 
500     /**
501      * Tests whether a line with whitespace is handled correctly. This is related to CONFIGURATION-582.
502      */
503     @Test
504     public void testLineWithBlank() throws ConfigurationException {
505         builder.addComment(TEST_COMMENT);
506         builder.addLine(" ");
507         builder.addProperty(TEST_KEY, TEST_VALUE);
508         layout.load(config, builder.getReader());
509         assertEquals(TEST_COMMENT + CRNORM + " ", layout.getCanonicalComment(TEST_KEY, false));
510     }
511 
512     /**
513      * Tests whether the output of the layout object is identical to the source file (at least for simple properties files).
514      */
515     @Test
516     public void testReadAndWrite() throws ConfigurationException {
517         builder.addComment("This is my test properties file,");
518         builder.addComment("which contains a header comment.");
519         builder.addComment(null);
520         builder.addComment(null);
521         builder.addComment(TEST_COMMENT);
522         builder.addProperty(TEST_KEY, TEST_COMMENT);
523         builder.addComment(null);
524         builder.addComment(null);
525         builder.addComment("Another comment");
526         builder.addProperty("property", "and a value");
527         layout.load(config, builder.getReader());
528         checkLayoutString(builder.toString());
529     }
530 
531     /**
532      * Tests reading a simple properties file.
533      */
534     @Test
535     public void testReadSimple() throws ConfigurationException {
536         builder.addComment(TEST_COMMENT);
537         builder.addProperty(TEST_KEY, TEST_VALUE);
538         layout.load(config, builder.getReader());
539         assertNull(layout.getHeaderComment());
540         assertEquals(1, layout.getKeys().size());
541         assertTrue(layout.getKeys().contains(TEST_KEY));
542         assertEquals(TEST_COMMENT, layout.getCanonicalComment(TEST_KEY, false));
543         assertEquals(0, layout.getBlankLinesBefore(TEST_KEY));
544         assertTrue(layout.isSingleLine(TEST_KEY));
545         assertEquals(TEST_VALUE, config.getString(TEST_KEY));
546     }
547 
548     /**
549      * Tests a recursive load call.
550      */
551     @Test
552     public void testRecursiveLoadCall() throws ConfigurationException {
553         final PropertiesBuilder b = new PropertiesBuilder();
554         b.addComment("A nested header comment.");
555         b.addComment("With multiple lines");
556         b.addComment(null);
557         b.addComment("Second comment");
558         b.addProperty(TEST_KEY, TEST_VALUE);
559         b.addProperty(TEST_KEY + "2", TEST_VALUE + "2");
560         config.builder = b;
561 
562         builder.addComment("Header comment");
563         builder.addComment(null);
564         builder.addComment(TEST_COMMENT);
565         builder.addProperty(TEST_KEY, TEST_VALUE);
566         builder.addComment("Include file");
567         builder.addProperty(PropertiesConfiguration.getInclude(), "test");
568 
569         layout.load(config, builder.getReader());
570 
571         assertEquals("Header comment", layout.getCanonicalHeaderComment(false));
572         assertFalse(layout.getKeys().contains(PropertiesConfiguration.getInclude()));
573         assertEquals(TEST_COMMENT + CRNORM + "A nested header comment." + CRNORM + "With multiple lines" + CRNORM + CRNORM + "Second comment",
574                 layout.getCanonicalComment(TEST_KEY, false));
575     }
576 
577     /**
578      * Tests if the content of the layout object is correctly written.
579      */
580     @Test
581     public void testSave() throws ConfigurationException {
582         config.addProperty(TEST_KEY, TEST_VALUE);
583         layout.setComment(TEST_KEY, TEST_COMMENT);
584         config.addProperty(TEST_KEY, TEST_VALUE + "2");
585         config.addProperty("AnotherProperty", "AnotherValue");
586         config.addProperty("AnotherProperty", "3rdValue");
587         layout.setComment("AnotherProperty", "AnotherComment");
588         layout.setBlankLinesBefore("AnotherProperty", 2);
589         layout.setSingleLine("AnotherProperty", true);
590         layout.setHeaderComment("A header comment" + CRNORM + "for my properties");
591         checkLayoutString("# A header comment" + CR + "# for my properties" + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = " + TEST_VALUE + CR + TEST_KEY
592             + " = " + TEST_VALUE + "2" + CR + CR + CR + "# AnotherComment" + CR + "AnotherProperty = AnotherValue,3rdValue" + CR);
593     }
594 
595     /**
596      * Tests saving when a comment for a non existing property is contained in the layout object. This comment should be
597      * ignored.
598      */
599     @Test
600     public void testSaveCommentForUnexistingProperty() throws ConfigurationException {
601         fillLayout();
602         layout.setComment("NonExistingKey", "NonExistingComment");
603         final String output = getLayoutString();
604         assertFalse(output.contains("NonExistingKey"));
605         assertFalse(output.contains("NonExistingComment"));
606     }
607 
608     /**
609      * Tests saving an empty layout object.
610      */
611     @Test
612     public void testSaveEmptyLayout() throws ConfigurationException {
613         checkLayoutString("");
614     }
615 
616     /**
617      * Tests the force single line flag.
618      */
619     @Test
620     public void testSaveForceSingleLine() throws ConfigurationException {
621         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
622         config.addProperty(TEST_KEY, TEST_VALUE);
623         config.addProperty(TEST_KEY, TEST_VALUE + "2");
624         config.addProperty("AnotherProperty", "value1;value2;value3");
625         layout.setComment(TEST_KEY, TEST_COMMENT);
626         layout.setForceSingleLine(true);
627         checkLayoutString(
628             "# " + TEST_COMMENT + CR + TEST_KEY + " = " + TEST_VALUE + ';' + TEST_VALUE + "2" + CR + "AnotherProperty = value1;value2;value3" + CR);
629     }
630 
631     /**
632      * Tests setting the global separator. This separator should override the separators for all properties.
633      */
634     @Test
635     public void testSetGlobalSeparator() throws ConfigurationException {
636         final String sep = "=";
637         config.addProperty(TEST_KEY, TEST_VALUE);
638         config.addProperty("key2", "value2");
639         layout.setSeparator(TEST_KEY, " : ");
640         layout.setGlobalSeparator(sep);
641         checkLayoutString(TEST_KEY + sep + TEST_VALUE + CR + "key2" + sep + "value2" + CR);
642     }
643 
644     /**
645      * Tests setting the line separator.
646      */
647     @Test
648     public void testSetLineSeparator() throws ConfigurationException {
649         final String lf = CR + CR;
650         config.addProperty(TEST_KEY, TEST_VALUE);
651         layout.setBlankLinesBefore(TEST_KEY, 2);
652         layout.setComment(TEST_KEY, TEST_COMMENT);
653         layout.setHeaderComment("Header comment");
654         layout.setLineSeparator(lf);
655         checkLayoutString("# Header comment" + lf + lf + lf + "# " + TEST_COMMENT + lf + TEST_KEY + " = " + TEST_VALUE + lf);
656     }
657 
658     /**
659      * Tests whether the line separator is also taken into account within comments.
660      */
661     @Test
662     public void testSetLineSeparatorInComments() throws ConfigurationException {
663         final String lf = "<-\n";
664         config.addProperty(TEST_KEY, TEST_VALUE);
665         layout.setComment(TEST_KEY, TEST_COMMENT + "\nMore comment");
666         layout.setHeaderComment("Header\ncomment");
667         layout.setLineSeparator(lf);
668         checkLayoutString("# Header" + lf + "# comment" + lf + lf + "# " + TEST_COMMENT + lf + "# More comment" + lf + TEST_KEY + " = " + TEST_VALUE + lf);
669     }
670 
671     /**
672      * Tests resetting a comment.
673      */
674     @Test
675     public void testSetNullComment() {
676         fillLayout();
677         layout.setComment(TEST_KEY, null);
678         assertNull(layout.getComment(TEST_KEY));
679     }
680 
681     /**
682      * Tests changing the separator for a property.
683      */
684     @Test
685     public void testSetSeparator() throws ConfigurationException {
686         config.addProperty(TEST_KEY, TEST_VALUE);
687         layout.setSeparator(TEST_KEY, ":");
688         checkLayoutString(TEST_KEY + ":" + TEST_VALUE + CR);
689     }
690 
691     /**
692      * Tests the trimComment method.
693      */
694     @Test
695     public void testTrimComment() {
696         assertEquals("This is a comment" + CR + "that spans multiple" + CR + "lines in a" + CR + " complex way.",
697                 PropertiesConfigurationLayout.trimComment(
698                         "   # This is a comment" + CR + "that spans multiple" + CR + "!lines in a" + CR + " complex way.",
699                         false
700                 ));
701     }
702 
703     /**
704      * Tests enforcing comment characters in a comment.
705      */
706     @Test
707     public void testTrimCommentFalse() {
708         assertEquals("# Comment with" + CR + " ! some mixed " + CR + "#comment" + CR + "# lines",
709                 PropertiesConfigurationLayout.trimComment("Comment with" + CR + " ! some mixed " + CR + "#comment" + CR + "lines", true));
710     }
711 
712     /**
713      * Tests trimming a comment with trailing CRs.
714      */
715     @Test
716     public void testTrimCommentTrainlingCR() {
717         assertEquals("Comment with" + CR + "trailing CR" + CR,
718                 PropertiesConfigurationLayout.trimComment("Comment with" + CR + "! trailing CR" + CR, false));
719     }
720 }