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 static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertFalse;
021    import static org.junit.Assert.assertNotNull;
022    import static org.junit.Assert.assertNull;
023    import static org.junit.Assert.assertSame;
024    import static org.junit.Assert.assertTrue;
025    import static org.junit.Assert.fail;
026    
027    import java.io.Reader;
028    import java.io.StringReader;
029    import java.io.StringWriter;
030    import java.util.Iterator;
031    
032    import org.apache.commons.configuration.event.ConfigurationEvent;
033    import org.apache.commons.configuration.event.ConfigurationListener;
034    import org.junit.Before;
035    import org.junit.Test;
036    
037    /**
038     * Test class for PropertiesConfigurationLayout.
039     *
040     * @author <a
041     * href="http://commons.apache.org/configuration/team-list.html">Commons
042     * Configuration team</a>
043     * @version $Id: TestPropertiesConfigurationLayout.java 1301996 2012-03-17 20:30:39Z sebb $
044     */
045    public class TestPropertiesConfigurationLayout
046    {
047        /** Constant for the line break character. */
048        private static final String CR = System.getProperty("line.separator");
049    
050        /** Constant for the normalized line break character. */
051        private static final String CRNORM = "\n";
052    
053        /** Constant for a test property key. */
054        static final String TEST_KEY = "myProperty";
055    
056        /** Constant for a test comment. */
057        static final String TEST_COMMENT = "A comment for my test property";
058    
059        /** Constant for a test property value. */
060        static final String TEST_VALUE = "myPropertyValue";
061    
062        /** The layout object under test. */
063        PropertiesConfigurationLayout layout;
064    
065        /** The associated configuration object. */
066        LayoutTestConfiguration config;
067    
068        /** A properties builder that can be used for testing. */
069        PropertiesBuilder builder;
070    
071        @Before
072        public void setUp() throws Exception
073        {
074            config = new LayoutTestConfiguration();
075            layout = new PropertiesConfigurationLayout(config);
076            config.setLayout(layout);
077            builder = new PropertiesBuilder();
078        }
079    
080        /**
081         * Tests a newly created instance.
082         */
083        @Test
084        public void testInit()
085        {
086            assertTrue("Object contains keys", layout.getKeys().isEmpty());
087            assertNull("Header comment not null", layout.getHeaderComment());
088            Iterator<ConfigurationListener> it = config.getConfigurationListeners().iterator();
089            assertTrue("No event listener registered", it.hasNext());
090            assertSame("Layout not registered as event listener", layout, it.next());
091            assertFalse("Multiple event listeners registered", it.hasNext());
092            assertSame("Configuration not stored", config, layout
093                    .getConfiguration());
094            assertFalse("Force single line flag set", layout.isForceSingleLine());
095            assertNull("Got a global separator", layout.getGlobalSeparator());
096        }
097    
098        /**
099         * Tests creating a layout object with a null configuration. This should
100         * cause an exception.
101         */
102        @Test(expected = IllegalArgumentException.class)
103        public void testInitNull()
104        {
105            new PropertiesConfigurationLayout(null);
106        }
107    
108        /**
109         * Tests reading a simple properties file.
110         */
111        @Test
112        public void testReadSimple() throws ConfigurationException
113        {
114            builder.addComment(TEST_COMMENT);
115            builder.addProperty(TEST_KEY, TEST_VALUE);
116            layout.load(builder.getReader());
117            assertNull("A header comment was found", layout.getHeaderComment());
118            assertEquals("Wrong number of properties", 1, layout.getKeys().size());
119            assertTrue("Property not found", layout.getKeys().contains(TEST_KEY));
120            assertEquals("Comment not found", TEST_COMMENT, layout
121                    .getCanonicalComment(TEST_KEY, false));
122            assertEquals("Wrong number of blanc lines", 0, layout
123                    .getBlancLinesBefore(TEST_KEY));
124            assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
125            assertEquals("Property not stored in config", TEST_VALUE, config
126                    .getString(TEST_KEY));
127        }
128    
129        /**
130         * Tests whether blanc lines before a property are correctly detected.
131         */
132        @Test
133        public void testBlancLines() throws ConfigurationException
134        {
135            builder.addProperty("prop", "value");
136            builder.addComment(null);
137            builder.addComment(null);
138            builder.addComment(TEST_COMMENT);
139            builder.addComment(null);
140            builder.addProperty(TEST_KEY, TEST_VALUE);
141            layout.load(builder.getReader());
142            assertEquals("Wrong number of blanc lines", 2, layout
143                    .getBlancLinesBefore(TEST_KEY));
144            assertEquals("Wrong comment", TEST_COMMENT + CRNORM, layout
145                    .getCanonicalComment(TEST_KEY, false));
146            assertEquals("Wrong property value", TEST_VALUE, config
147                    .getString(TEST_KEY));
148        }
149    
150        /**
151         * Tests the single line flag for a simple property definition.
152         */
153        @Test
154        public void testIsSingleLine() throws ConfigurationException
155        {
156            builder.addProperty(TEST_KEY, TEST_VALUE + "," + TEST_VALUE + "2");
157            layout.load(builder.getReader());
158            assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
159            assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
160                    .size());
161        }
162    
163        /**
164         * Tests the single line flag if there are multiple property definitions.
165         */
166        @Test
167        public void testIsSingleLineMulti() throws ConfigurationException
168        {
169            builder.addProperty(TEST_KEY, TEST_VALUE);
170            builder.addProperty("anotherProp", "a value");
171            builder.addProperty(TEST_KEY, TEST_VALUE + "2");
172            layout.load(builder.getReader());
173            assertFalse("Wrong single line flag", layout.isSingleLine(TEST_KEY));
174            assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
175                    .size());
176        }
177    
178        /**
179         * Tests whether comments are combined for multiple occurrences.
180         */
181        @Test
182        public void testCombineComments() throws ConfigurationException
183        {
184            builder.addComment(TEST_COMMENT);
185            builder.addProperty(TEST_KEY, TEST_VALUE);
186            builder.addComment(null);
187            builder.addComment(TEST_COMMENT);
188            builder.addProperty(TEST_KEY, TEST_VALUE + "2");
189            layout.load(builder.getReader());
190            assertEquals("Wrong combined comment",
191                    TEST_COMMENT + CRNORM + TEST_COMMENT, layout.getCanonicalComment(
192                            TEST_KEY, false));
193            assertEquals("Wrong combined blanc numbers", 0, layout
194                    .getBlancLinesBefore(TEST_KEY));
195        }
196    
197        /**
198         * Tests if a header comment is detected.
199         */
200        @Test
201        public void testHeaderComment() throws ConfigurationException
202        {
203            builder.addComment(TEST_COMMENT);
204            builder.addComment(null);
205            builder.addProperty(TEST_KEY, TEST_VALUE);
206            layout.load(builder.getReader());
207            assertEquals("Wrong header comment", TEST_COMMENT, layout
208                    .getCanonicalHeaderComment(false));
209            assertNull("Wrong comment for property", layout.getCanonicalComment(
210                    TEST_KEY, false));
211        }
212    
213        /**
214         * Tests if a header comment containing blanc lines is correctly detected.
215         */
216        @Test
217        public void testHeaderCommentWithBlancs() throws ConfigurationException
218        {
219            builder.addComment(TEST_COMMENT);
220            builder.addComment(null);
221            builder.addComment(TEST_COMMENT);
222            builder.addComment(null);
223            builder.addProperty(TEST_KEY, TEST_VALUE);
224            layout.load(builder.getReader());
225            assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
226                    + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
227            assertNull("Wrong comment for property", layout.getComment(TEST_KEY));
228        }
229    
230        /**
231         * Tests if a header comment is correctly detected when it contains blanc
232         * lines and the first property has a comment, too.
233         */
234        @Test
235        public void testHeaderCommentWithBlancsAndPropComment()
236                throws ConfigurationException
237        {
238            builder.addComment(TEST_COMMENT);
239            builder.addComment(null);
240            builder.addComment(TEST_COMMENT);
241            builder.addComment(null);
242            builder.addComment(TEST_COMMENT);
243            builder.addProperty(TEST_KEY, TEST_VALUE);
244            layout.load(builder.getReader());
245            assertEquals("Wrong header comment", TEST_COMMENT + CRNORM + CRNORM
246                    + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
247            assertEquals("Wrong comment for property", TEST_COMMENT, layout
248                    .getCanonicalComment(TEST_KEY, false));
249        }
250    
251        /**
252         * Tests fetching a canonical header comment when no comment is set.
253         */
254        @Test
255        public void testHeaderCommentNull()
256        {
257            assertNull("No null comment with comment chars", layout
258                    .getCanonicalHeaderComment(true));
259            assertNull("No null comment without comment chars", layout
260                    .getCanonicalHeaderComment(false));
261        }
262    
263        /**
264         * Tests if a property add event is correctly processed.
265         */
266        @Test
267        public void testEventAdd()
268        {
269            ConfigurationEvent event = new ConfigurationEvent(this,
270                    AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
271                    false);
272            layout.configurationChanged(event);
273            assertTrue("Property not stored", layout.getKeys().contains(TEST_KEY));
274            assertEquals("Blanc lines before new property", 0, layout
275                    .getBlancLinesBefore(TEST_KEY));
276            assertTrue("No single line property", layout.isSingleLine(TEST_KEY));
277            assertEquals("Wrong separator", " = ", layout.getSeparator(TEST_KEY));
278        }
279    
280        /**
281         * Tests adding a property multiple time through an event. The property
282         * should then be a multi-line property.
283         */
284        @Test
285        public void testEventAddMultiple()
286        {
287            ConfigurationEvent event = new ConfigurationEvent(this,
288                    AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
289                    false);
290            layout.configurationChanged(event);
291            layout.configurationChanged(event);
292            assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
293        }
294    
295        /**
296         * Tests if an add event is correctly processed if the affected property is
297         * already stored in the layout object.
298         */
299        @Test
300        public void testEventAddExisting() throws ConfigurationException
301        {
302            builder.addComment(TEST_COMMENT);
303            builder.addProperty(TEST_KEY, TEST_VALUE);
304            layout.load(builder.getReader());
305            ConfigurationEvent event = new ConfigurationEvent(this,
306                    AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
307                    false);
308            layout.configurationChanged(event);
309            assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
310            assertEquals("Comment was modified", TEST_COMMENT, layout
311                    .getCanonicalComment(TEST_KEY, false));
312        }
313    
314        /**
315         * Tests if a set property event for a non existing property is correctly
316         * handled.
317         */
318        @Test
319        public void testEventSetNonExisting()
320        {
321            ConfigurationEvent event = new ConfigurationEvent(this,
322                    AbstractConfiguration.EVENT_SET_PROPERTY, TEST_KEY, TEST_VALUE,
323                    false);
324            layout.configurationChanged(event);
325            assertTrue("New property was not found", layout.getKeys().contains(
326                    TEST_KEY));
327        }
328    
329        /**
330         * Tests if a property delete event is correctly processed.
331         */
332        @Test
333        public void testEventDelete()
334        {
335            ConfigurationEvent event = new ConfigurationEvent(this,
336                    AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
337                    false);
338            layout.configurationChanged(event);
339            event = new ConfigurationEvent(this,
340                    AbstractConfiguration.EVENT_CLEAR_PROPERTY, TEST_KEY, null,
341                    false);
342            layout.configurationChanged(event);
343            assertFalse("Property still existing", layout.getKeys().contains(
344                    TEST_KEY));
345        }
346    
347        /**
348         * Tests if a clear event is correctly processed.
349         */
350        @Test
351        public void testEventClearConfig() throws Exception
352        {
353            fillLayout();
354            ConfigurationEvent event = new ConfigurationEvent(this,
355                    AbstractConfiguration.EVENT_CLEAR, null, null, false);
356            layout.configurationChanged(event);
357            assertTrue("Keys not empty", layout.getKeys().isEmpty());
358            assertNull("Header comment was not reset", layout.getHeaderComment());
359        }
360    
361        /**
362         * Tests if a before update event is correctly ignored.
363         */
364        @Test
365        public void testEventAddBefore()
366        {
367            ConfigurationEvent event = new ConfigurationEvent(this,
368                    AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
369                    true);
370            layout.configurationChanged(event);
371            assertFalse("Property already stored", layout.getKeys().contains(
372                    TEST_KEY));
373        }
374    
375        /**
376         * Tests if a reload update is correctly processed.
377         */
378        @Test
379        public void testEventReload()
380        {
381            fillLayout();
382            ConfigurationEvent event = new ConfigurationEvent(this,
383                    AbstractFileConfiguration.EVENT_RELOAD, null, null, true);
384            layout.configurationChanged(event);
385            assertTrue("Keys not empty", layout.getKeys().isEmpty());
386            assertNull("Header comment was not reset", layout.getHeaderComment());
387        }
388    
389        /**
390         * Tests the event after a reload has been performed. This should be
391         * ignored.
392         */
393        @Test
394        public void testEventReloadAfter()
395        {
396            fillLayout();
397            ConfigurationEvent event = new ConfigurationEvent(this,
398                    AbstractFileConfiguration.EVENT_RELOAD, null, null, false);
399            layout.configurationChanged(event);
400            assertFalse("Keys are empty", layout.getKeys().isEmpty());
401            assertNotNull("Header comment was reset", layout.getHeaderComment());
402        }
403    
404        /**
405         * Tests a recursive load call.
406         */
407        @Test
408        public void testRecursiveLoadCall() throws ConfigurationException
409        {
410            PropertiesBuilder b = new PropertiesBuilder();
411            b.addComment("A nested header comment.");
412            b.addComment("With multiple lines");
413            b.addComment(null);
414            b.addComment("Second comment");
415            b.addProperty(TEST_KEY, TEST_VALUE);
416            b.addProperty(TEST_KEY + "2", TEST_VALUE + "2");
417            config.builder = b;
418    
419            builder.addComment("Header comment");
420            builder.addComment(null);
421            builder.addComment(TEST_COMMENT);
422            builder.addProperty(TEST_KEY, TEST_VALUE);
423            builder.addComment("Include file");
424            builder.addProperty(PropertiesConfiguration.getInclude(), "test");
425    
426            layout.load(builder.getReader());
427    
428            assertEquals("Wrong header comment", "Header comment", layout
429                    .getCanonicalHeaderComment(false));
430            assertFalse("Include property was stored", layout.getKeys().contains(
431                    PropertiesConfiguration.getInclude()));
432            assertEquals("Wrong comment for property", TEST_COMMENT + CRNORM
433                    + "A nested header comment." + CRNORM + "With multiple lines" + CRNORM
434                    + CRNORM + "Second comment", layout.getCanonicalComment(TEST_KEY,
435                    false));
436        }
437    
438        /**
439         * Tests whether the output of the layout object is identical to the source
440         * file (at least for simple properties files).
441         */
442        @Test
443        public void testReadAndWrite() throws ConfigurationException
444        {
445            builder.addComment("This is my test properties file,");
446            builder.addComment("which contains a header comment.");
447            builder.addComment(null);
448            builder.addComment(TEST_COMMENT);
449            builder.addProperty(TEST_KEY, TEST_COMMENT);
450            builder.addComment(null);
451            builder.addComment(null);
452            builder.addComment("Another comment");
453            builder.addProperty("property", "and a value");
454            layout.load(builder.getReader());
455            checkLayoutString(builder.toString());
456        }
457    
458        /**
459         * Tests if the content of the layout object is correctly written.
460         */
461        @Test
462        public void testSave() throws ConfigurationException
463        {
464            config.addProperty(TEST_KEY, TEST_VALUE);
465            layout.setComment(TEST_KEY, TEST_COMMENT);
466            config.addProperty(TEST_KEY, TEST_VALUE + "2");
467            config.addProperty("AnotherProperty", "AnotherValue");
468            config.addProperty("AnotherProperty", "3rdValue");
469            layout.setComment("AnotherProperty", "AnotherComment");
470            layout.setBlancLinesBefore("AnotherProperty", 2);
471            layout.setSingleLine("AnotherProperty", true);
472            layout.setHeaderComment("A header comment" + CRNORM + "for my properties");
473            checkLayoutString("# A header comment" + CR + "# for my properties"
474                    + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = "
475                    + TEST_VALUE + CR + TEST_KEY + " = " + TEST_VALUE + "2" + CR
476                    + CR + CR + "# AnotherComment" + CR
477                    + "AnotherProperty = AnotherValue,3rdValue" + CR);
478        }
479    
480        /**
481         * Tests the force single line flag.
482         */
483        @Test
484        public void testSaveForceSingleLine() throws ConfigurationException
485        {
486            config.setListDelimiter(';');
487            config.addProperty(TEST_KEY, TEST_VALUE);
488            config.addProperty(TEST_KEY, TEST_VALUE + "2");
489            config.addProperty("AnotherProperty", "value1;value2;value3");
490            layout.setComment(TEST_KEY, TEST_COMMENT);
491            layout.setForceSingleLine(true);
492            checkLayoutString("# " + TEST_COMMENT + CR + TEST_KEY + " = "
493                    + TEST_VALUE + ';' + TEST_VALUE + "2" + CR
494                    + "AnotherProperty = value1;value2;value3" + CR);
495        }
496    
497        /**
498         * Tests the trimComment method.
499         */
500        @Test
501        public void testTrimComment()
502        {
503            assertEquals("Wrong trimmed comment", "This is a comment" + CR
504                    + "that spans multiple" + CR + "lines in a" + CR
505                    + " complex way.", PropertiesConfigurationLayout.trimComment(
506                    "   # This is a comment" + CR + "that spans multiple" + CR
507                            + "!lines in a" + CR + " complex way.", false));
508        }
509    
510        /**
511         * Tests trimming a comment with trailing CRs.
512         */
513        @Test
514        public void testTrimCommentTrainlingCR()
515        {
516            assertEquals("Wrong trimmed comment", "Comment with" + CR
517                    + "trailing CR" + CR, PropertiesConfigurationLayout
518                    .trimComment("Comment with" + CR + "! trailing CR" + CR, false));
519        }
520    
521        /**
522         * Tests enforcing comment characters in a comment.
523         */
524        @Test
525        public void testTrimCommentFalse()
526        {
527            assertEquals("Wrong trimmed comment", "# Comment with" + CR
528                    + " ! some mixed " + CR + "#comment" + CR + "# lines",
529                    PropertiesConfigurationLayout.trimComment("Comment with" + CR
530                            + " ! some mixed " + CR + "#comment" + CR + "lines",
531                            true));
532        }
533    
534        /**
535         * Tests accessing data for a property, which is not stored.
536         */
537        @Test
538        public void testGetNonExistingLayouData()
539        {
540            assertNull("A comment was found", layout.getComment("unknown"));
541            assertTrue("A multi-line property", layout.isSingleLine("unknown"));
542            assertEquals("Leading blanc lines", 0, layout
543                    .getBlancLinesBefore("unknown"));
544        }
545    
546        /**
547         * Tests accessing a property with a null key. This should throw an
548         * exception.
549         */
550        @Test(expected = IllegalArgumentException.class)
551        public void testGetNullLayouttData()
552        {
553            layout.setComment(null, TEST_COMMENT);
554        }
555    
556        /**
557         * Tests resetting a comment.
558         */
559        @Test
560        public void testSetNullComment()
561        {
562            fillLayout();
563            layout.setComment(TEST_KEY, null);
564            assertNull("Comment was not reset", layout.getComment(TEST_KEY));
565        }
566    
567        /**
568         * Tests saving when a comment for a non existing property is contained in
569         * the layout object. This comment should be ignored.
570         */
571        @Test
572        public void testSaveCommentForUnexistingProperty()
573                throws ConfigurationException
574        {
575            fillLayout();
576            layout.setComment("NonExistingKey", "NonExistingComment");
577            String output = getLayoutString();
578            assertTrue("Non existing key was found", output
579                    .indexOf("NonExistingKey") < 0);
580            assertTrue("Non existing comment was found", output
581                    .indexOf("NonExistingComment") < 0);
582        }
583    
584        /**
585         * Tests saving an empty layout object.
586         */
587        @Test
588        public void testSaveEmptyLayout() throws ConfigurationException
589        {
590            checkLayoutString("");
591        }
592    
593        /**
594         * Tests the copy constructor.
595         */
596        @Test
597        public void testInitCopy()
598        {
599            fillLayout();
600            PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
601                    config, layout);
602            assertEquals("Wrong number of keys", layout.getKeys().size(), l2
603                    .getKeys().size());
604            for (Iterator<String> it = layout.getKeys().iterator(); it.hasNext();)
605            {
606                Object key = it.next();
607                assertTrue("Key was not found: " + key, l2.getKeys().contains(key));
608            }
609        }
610    
611        /**
612         * Tests if the copy and the original are independent from each other.
613         */
614        @Test
615        public void testInitCopyModify()
616        {
617            fillLayout();
618            PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
619                    config, layout);
620            assertEquals("Comments are not equal", layout.getComment(TEST_KEY), l2
621                    .getComment(TEST_KEY));
622            layout.setComment(TEST_KEY, "A new comment");
623            assertEquals("Comment was changed", TEST_COMMENT, l2
624                    .getCanonicalComment(TEST_KEY, false));
625            l2.setBlancLinesBefore(TEST_KEY, l2.getBlancLinesBefore(TEST_KEY) + 1);
626            assertFalse("Blanc lines do not differ", layout
627                    .getBlancLinesBefore(TEST_KEY) == l2
628                    .getBlancLinesBefore(TEST_KEY));
629        }
630    
631        /**
632         * Tests changing the separator for a property.
633         */
634        @Test
635        public void testSetSeparator() throws ConfigurationException
636        {
637            config.addProperty(TEST_KEY, TEST_VALUE);
638            layout.setSeparator(TEST_KEY, ":");
639            checkLayoutString(TEST_KEY + ":" + TEST_VALUE + CR);
640        }
641    
642        /**
643         * Tests setting the global separator. This separator should override the
644         * separators for all properties.
645         */
646        @Test
647        public void testSetGlobalSeparator() throws ConfigurationException
648        {
649            final String sep = "=";
650            config.addProperty(TEST_KEY, TEST_VALUE);
651            config.addProperty("key2", "value2");
652            layout.setSeparator(TEST_KEY, " : ");
653            layout.setGlobalSeparator(sep);
654            checkLayoutString(TEST_KEY + sep + TEST_VALUE + CR + "key2" + sep
655                    + "value2" + CR);
656        }
657    
658        /**
659         * Tests setting the line separator.
660         */
661        @Test
662        public void testSetLineSeparator() throws ConfigurationException
663        {
664            final String lf = CR + CR;
665            config.addProperty(TEST_KEY, TEST_VALUE);
666            layout.setBlancLinesBefore(TEST_KEY, 2);
667            layout.setComment(TEST_KEY, TEST_COMMENT);
668            layout.setHeaderComment("Header comment");
669            layout.setLineSeparator(lf);
670            checkLayoutString("# Header comment" + lf + lf + lf + lf + "# "
671                    + TEST_COMMENT + lf + TEST_KEY + " = " + TEST_VALUE + lf);
672        }
673    
674        /**
675         * Tests whether the line separator is also taken into account within
676         * comments.
677         */
678        @Test
679        public void testSetLineSeparatorInComments() throws ConfigurationException
680        {
681            final String lf = "<-\n";
682            config.addProperty(TEST_KEY, TEST_VALUE);
683            layout.setComment(TEST_KEY, TEST_COMMENT + "\nMore comment");
684            layout.setHeaderComment("Header\ncomment");
685            layout.setLineSeparator(lf);
686            checkLayoutString("# Header" + lf + "# comment" + lf + lf + "# "
687                    + TEST_COMMENT + lf + "# More comment" + lf + TEST_KEY + " = "
688                    + TEST_VALUE + lf);
689        }
690    
691        /**
692         * Helper method for filling the layout object with some properties.
693         */
694        private void fillLayout()
695        {
696            builder.addComment("A header comment");
697            builder.addComment(null);
698            builder.addProperty("prop", "value");
699            builder.addComment(TEST_COMMENT);
700            builder.addProperty(TEST_KEY, TEST_VALUE);
701            builder.addProperty("anotherProp", "anotherValue");
702            try
703            {
704                layout.load(builder.getReader());
705            }
706            catch (ConfigurationException cex)
707            {
708                // should not happen
709                fail("Exception was thrown: " + cex);
710            }
711        }
712    
713        /**
714         * Writes the layout's data into a string.
715         *
716         * @return the layout file's content as string
717         * @throws ConfigurationException if an error occurs
718         */
719        private String getLayoutString() throws ConfigurationException
720        {
721            StringWriter out = new StringWriter();
722            layout.save(out);
723            return out.toString();
724        }
725    
726        /**
727         * Checks if the layout's output is correct.
728         *
729         * @param expected the expected result
730         * @throws ConfigurationException if an error occurs
731         */
732        private void checkLayoutString(String expected)
733                throws ConfigurationException
734        {
735            assertEquals("Wrong layout file content", expected, getLayoutString());
736        }
737    
738        /**
739         * A helper class used for constructing test properties files.
740         */
741        static class PropertiesBuilder
742        {
743            /** A buffer for storing the data. */
744            private StringBuilder buf = new StringBuilder();
745    
746            /** A counter for varying the comment character. */
747            private int commentCounter;
748    
749            /**
750             * Adds a property to the simulated file.
751             *
752             * @param key the property key
753             * @param value the value
754             */
755            public void addProperty(String key, String value)
756            {
757                buf.append(key).append(" = ").append(value).append(CR);
758            }
759    
760            /**
761             * Adds a comment line.
762             *
763             * @param s the comment (can be <b>null</b>, then a blanc line is
764             * added)
765             */
766            public void addComment(String s)
767            {
768                if (s != null)
769                {
770                    if (commentCounter % 2 == 0)
771                    {
772                        buf.append("# ");
773                    }
774                    else
775                    {
776                        buf.append("! ");
777                    }
778                    buf.append(s);
779                    commentCounter++;
780                }
781                buf.append(CR);
782            }
783    
784            /**
785             * Returns a reader for the simulated properties.
786             *
787             * @return a reader
788             */
789            public Reader getReader()
790            {
791                return new StringReader(buf.toString());
792            }
793    
794            /**
795             * Returns a string representation of the buffer's content.
796             *
797             * @return the buffer as string
798             */
799            @Override
800            public String toString()
801            {
802                return buf.toString();
803            }
804        }
805    
806        /**
807         * A mock properties configuration implementation that is used to check
808         * whether some expected methods are called.
809         */
810        static class LayoutTestConfiguration extends PropertiesConfiguration
811        {
812            /** Stores a builder object. */
813            public PropertiesBuilder builder;
814    
815            /**
816             * Simulates the propertyLoaded() callback. If a builder was set, a
817             * load() call on the layout is invoked.
818             */
819            @Override
820            boolean propertyLoaded(String key, String value)
821                    throws ConfigurationException
822            {
823                if (builder == null)
824                {
825                    return super.propertyLoaded(key, value);
826                }
827                else
828                {
829                    if (PropertiesConfiguration.getInclude().equals(key))
830                    {
831                        getLayout().load(builder.getReader());
832                        return false;
833                    }
834                    else
835                    {
836                        return true;
837                    }
838                }
839            }
840        }
841    }