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.configuration;
18  
19  import java.io.Reader;
20  import java.io.StringReader;
21  import java.io.StringWriter;
22  import java.util.Iterator;
23  
24  import org.apache.commons.configuration.event.ConfigurationEvent;
25  
26  import junit.framework.TestCase;
27  
28  /***
29   * Test class for PropertiesConfigurationLayout.
30   *
31   * @author <a
32   * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
33   * Configuration team</a>
34   * @version $Id: TestPropertiesConfigurationLayout.java 439648 2006-09-02 20:42:10Z oheger $
35   */
36  public class TestPropertiesConfigurationLayout extends TestCase
37  {
38      /*** Constant for the line break character. */
39      static final String CR = System.getProperty("line.separator");
40  
41      /*** Constant for a test property key. */
42      static final String TEST_KEY = "myProperty";
43  
44      /*** Constant for a test comment. */
45      static final String TEST_COMMENT = "A comment for my test property";
46  
47      /*** Constant for a test property value. */
48      static final String TEST_VALUE = "myPropertyValue";
49  
50      /*** The layout object under test. */
51      PropertiesConfigurationLayout layout;
52  
53      /*** The associated configuration object. */
54      LayoutTestConfiguration config;
55  
56      /*** A properties builder that can be used for testing. */
57      PropertiesBuilder builder;
58  
59      protected void setUp() throws Exception
60      {
61          super.setUp();
62          config = new LayoutTestConfiguration();
63          layout = new PropertiesConfigurationLayout(config);
64          config.setLayout(layout);
65          builder = new PropertiesBuilder();
66      }
67  
68      /***
69       * Tests a newly created instance.
70       */
71      public void testInit()
72      {
73          assertTrue("Object contains keys", layout.getKeys().isEmpty());
74          assertNull("Header comment not null", layout.getHeaderComment());
75          Iterator it = config.getConfigurationListeners().iterator();
76          assertTrue("No event listener registered", it.hasNext());
77          assertSame("Layout not registered as event listener", layout, it.next());
78          assertFalse("Multiple event listeners registered", it.hasNext());
79          assertSame("Configuration not stored", config, layout
80                  .getConfiguration());
81          assertFalse("Force single line flag set", layout.isForceSingleLine());
82      }
83  
84      /***
85       * Tests creating a layout object with a null configuration. This should
86       * cause an exception.
87       */
88      public void testInitNull()
89      {
90          try
91          {
92              new PropertiesConfigurationLayout(null);
93              fail("Could create instance with null config!");
94          }
95          catch (IllegalArgumentException iex)
96          {
97              // ok
98          }
99      }
100 
101     /***
102      * Tests reading a simple properties file.
103      */
104     public void testReadSimple() throws ConfigurationException
105     {
106         builder.addComment(TEST_COMMENT);
107         builder.addProperty(TEST_KEY, TEST_VALUE);
108         layout.load(builder.getReader());
109         assertNull("A header comment was found", layout.getHeaderComment());
110         assertEquals("Wrong number of properties", 1, layout.getKeys().size());
111         assertTrue("Property not found", layout.getKeys().contains(TEST_KEY));
112         assertEquals("Comment not found", TEST_COMMENT, layout
113                 .getCanonicalComment(TEST_KEY, false));
114         assertEquals("Wrong number of blanc lines", 0, layout
115                 .getBlancLinesBefore(TEST_KEY));
116         assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
117         assertEquals("Property not stored in config", TEST_VALUE, config
118                 .getString(TEST_KEY));
119     }
120 
121     /***
122      * Tests whether blanc lines before a property are correctly detected.
123      */
124     public void testBlancLines() throws ConfigurationException
125     {
126         builder.addProperty("prop", "value");
127         builder.addComment(null);
128         builder.addComment(null);
129         builder.addComment(TEST_COMMENT);
130         builder.addComment(null);
131         builder.addProperty(TEST_KEY, TEST_VALUE);
132         layout.load(builder.getReader());
133         assertEquals("Wrong number of blanc lines", 2, layout
134                 .getBlancLinesBefore(TEST_KEY));
135         assertEquals("Wrong comment", TEST_COMMENT + CR, layout
136                 .getCanonicalComment(TEST_KEY, false));
137         assertEquals("Wrong property value", TEST_VALUE, config
138                 .getString(TEST_KEY));
139     }
140 
141     /***
142      * Tests the single line flag for a simple property definition.
143      */
144     public void testIsSingleLine() throws ConfigurationException
145     {
146         builder.addProperty(TEST_KEY, TEST_VALUE + "," + TEST_VALUE + "2");
147         layout.load(builder.getReader());
148         assertTrue("Wrong single line flag", layout.isSingleLine(TEST_KEY));
149         assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
150                 .size());
151     }
152 
153     /***
154      * Tests the single line flag if there are multiple property definitions.
155      */
156     public void testIsSingleLineMulti() throws ConfigurationException
157     {
158         builder.addProperty(TEST_KEY, TEST_VALUE);
159         builder.addProperty("anotherProp", "a value");
160         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
161         layout.load(builder.getReader());
162         assertFalse("Wrong single line flag", layout.isSingleLine(TEST_KEY));
163         assertEquals("Wrong number of values", 2, config.getList(TEST_KEY)
164                 .size());
165     }
166 
167     /***
168      * Tests whether comments are combined for multiple occurrences.
169      */
170     public void testCombineComments() throws ConfigurationException
171     {
172         builder.addComment(TEST_COMMENT);
173         builder.addProperty(TEST_KEY, TEST_VALUE);
174         builder.addComment(null);
175         builder.addComment(TEST_COMMENT);
176         builder.addProperty(TEST_KEY, TEST_VALUE + "2");
177         layout.load(builder.getReader());
178         assertEquals("Wrong combined comment",
179                 TEST_COMMENT + CR + TEST_COMMENT, layout.getCanonicalComment(
180                         TEST_KEY, false));
181         assertEquals("Wrong combined blanc numbers", 0, layout
182                 .getBlancLinesBefore(TEST_KEY));
183     }
184 
185     /***
186      * Tests if a header comment is detected.
187      */
188     public void testHeaderComment() throws ConfigurationException
189     {
190         builder.addComment(TEST_COMMENT);
191         builder.addComment(null);
192         builder.addProperty(TEST_KEY, TEST_VALUE);
193         layout.load(builder.getReader());
194         assertEquals("Wrong header comment", TEST_COMMENT, layout
195                 .getCanonicalHeaderComment(false));
196         assertNull("Wrong comment for property", layout.getCanonicalComment(
197                 TEST_KEY, false));
198     }
199 
200     /***
201      * Tests if a header comment containing blanc lines is correctly detected.
202      */
203     public void testHeaderCommentWithBlancs() throws ConfigurationException
204     {
205         builder.addComment(TEST_COMMENT);
206         builder.addComment(null);
207         builder.addComment(TEST_COMMENT);
208         builder.addComment(null);
209         builder.addProperty(TEST_KEY, TEST_VALUE);
210         layout.load(builder.getReader());
211         assertEquals("Wrong header comment", TEST_COMMENT + CR + CR
212                 + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
213         assertNull("Wrong comment for property", layout.getComment(TEST_KEY));
214     }
215 
216     /***
217      * Tests if a header comment is correctly detected when it contains blanc
218      * lines and the first property has a comment, too.
219      */
220     public void testHeaderCommentWithBlancsAndPropComment()
221             throws ConfigurationException
222     {
223         builder.addComment(TEST_COMMENT);
224         builder.addComment(null);
225         builder.addComment(TEST_COMMENT);
226         builder.addComment(null);
227         builder.addComment(TEST_COMMENT);
228         builder.addProperty(TEST_KEY, TEST_VALUE);
229         layout.load(builder.getReader());
230         assertEquals("Wrong header comment", TEST_COMMENT + CR + CR
231                 + TEST_COMMENT, layout.getCanonicalHeaderComment(false));
232         assertEquals("Wrong comment for property", TEST_COMMENT, layout
233                 .getCanonicalComment(TEST_KEY, false));
234     }
235 
236     /***
237      * Tests fetching a canonical header comment when no comment is set.
238      */
239     public void testHeaderCommentNull()
240     {
241         assertNull("No null comment with comment chars", layout
242                 .getCanonicalHeaderComment(true));
243         assertNull("No null comment without comment chars", layout
244                 .getCanonicalHeaderComment(false));
245     }
246 
247     /***
248      * Tests if a property add event is correctly processed.
249      */
250     public void testEventAdd()
251     {
252         ConfigurationEvent event = new ConfigurationEvent(this,
253                 AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
254                 false);
255         layout.configurationChanged(event);
256         assertTrue("Property not stored", layout.getKeys().contains(TEST_KEY));
257         assertEquals("Blanc lines before new property", 0, layout
258                 .getBlancLinesBefore(TEST_KEY));
259         assertTrue("No single line property", layout.isSingleLine(TEST_KEY));
260     }
261 
262     /***
263      * Tests adding a property multiple time through an event. The property
264      * should then be a multi-line property.
265      */
266     public void testEventAddMultiple()
267     {
268         ConfigurationEvent event = new ConfigurationEvent(this,
269                 AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
270                 false);
271         layout.configurationChanged(event);
272         layout.configurationChanged(event);
273         assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
274     }
275 
276     /***
277      * Tests if an add event is correctly processed if the affected property is
278      * already stored in the layout object.
279      */
280     public void testEventAddExisting() throws ConfigurationException
281     {
282         builder.addComment(TEST_COMMENT);
283         builder.addProperty(TEST_KEY, TEST_VALUE);
284         layout.load(builder.getReader());
285         ConfigurationEvent event = new ConfigurationEvent(this,
286                 AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
287                 false);
288         layout.configurationChanged(event);
289         assertFalse("No multi-line property", layout.isSingleLine(TEST_KEY));
290         assertEquals("Comment was modified", TEST_COMMENT, layout
291                 .getCanonicalComment(TEST_KEY, false));
292     }
293 
294     /***
295      * Tests if a set property event for a non existing property is correctly
296      * handled.
297      */
298     public void testEventSetNonExisting()
299     {
300         ConfigurationEvent event = new ConfigurationEvent(this,
301                 AbstractConfiguration.EVENT_SET_PROPERTY, TEST_KEY, TEST_VALUE,
302                 false);
303         layout.configurationChanged(event);
304         assertTrue("New property was not found", layout.getKeys().contains(
305                 TEST_KEY));
306     }
307 
308     /***
309      * Tests if a property delete event is correctly processed.
310      */
311     public void testEventDelete()
312     {
313         ConfigurationEvent event = new ConfigurationEvent(this,
314                 AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
315                 false);
316         layout.configurationChanged(event);
317         event = new ConfigurationEvent(this,
318                 AbstractConfiguration.EVENT_CLEAR_PROPERTY, TEST_KEY, null,
319                 false);
320         layout.configurationChanged(event);
321         assertFalse("Property still existing", layout.getKeys().contains(
322                 TEST_KEY));
323     }
324 
325     /***
326      * Tests if a clear event is correctly processed.
327      */
328     public void testEventClearConfig() throws ConfigurationException
329     {
330         fillLayout();
331         ConfigurationEvent event = new ConfigurationEvent(this,
332                 AbstractConfiguration.EVENT_CLEAR, null, null, false);
333         layout.configurationChanged(event);
334         assertTrue("Keys not empty", layout.getKeys().isEmpty());
335         assertNull("Header comment was not reset", layout.getHeaderComment());
336     }
337 
338     /***
339      * Tests if a before update event is correctly ignored.
340      */
341     public void testEventAddBefore()
342     {
343         ConfigurationEvent event = new ConfigurationEvent(this,
344                 AbstractConfiguration.EVENT_ADD_PROPERTY, TEST_KEY, TEST_VALUE,
345                 true);
346         layout.configurationChanged(event);
347         assertFalse("Property already stored", layout.getKeys().contains(
348                 TEST_KEY));
349     }
350 
351     /***
352      * Tests if a reload update is correctly processed.
353      */
354     public void testEventReload()
355     {
356         fillLayout();
357         ConfigurationEvent event = new ConfigurationEvent(this,
358                 AbstractFileConfiguration.EVENT_RELOAD, null, null, true);
359         layout.configurationChanged(event);
360         assertTrue("Keys not empty", layout.getKeys().isEmpty());
361         assertNull("Header comment was not reset", layout.getHeaderComment());
362     }
363 
364     /***
365      * Tests the event after a reload has been performed. This should be
366      * ignored.
367      */
368     public void testEventReloadAfter()
369     {
370         fillLayout();
371         ConfigurationEvent event = new ConfigurationEvent(this,
372                 AbstractFileConfiguration.EVENT_RELOAD, null, null, false);
373         layout.configurationChanged(event);
374         assertFalse("Keys are empty", layout.getKeys().isEmpty());
375         assertNotNull("Header comment was reset", layout.getHeaderComment());
376     }
377 
378     /***
379      * Tests a recursive load call.
380      */
381     public void testRecursiveLoadCall() throws ConfigurationException
382     {
383         PropertiesBuilder b = new PropertiesBuilder();
384         b.addComment("A nested header comment.");
385         b.addComment("With multiple lines");
386         b.addComment(null);
387         b.addComment("Second comment");
388         b.addProperty(TEST_KEY, TEST_VALUE);
389         b.addProperty(TEST_KEY + "2", TEST_VALUE + "2");
390         config.builder = b;
391 
392         builder.addComment("Header comment");
393         builder.addComment(null);
394         builder.addComment(TEST_COMMENT);
395         builder.addProperty(TEST_KEY, TEST_VALUE);
396         builder.addComment("Include file");
397         builder.addProperty(PropertiesConfiguration.getInclude(), "test");
398 
399         layout.load(builder.getReader());
400 
401         assertEquals("Wrong header comment", "Header comment", layout
402                 .getCanonicalHeaderComment(false));
403         assertFalse("Include property was stored", layout.getKeys().contains(
404                 PropertiesConfiguration.getInclude()));
405         assertEquals("Wrong comment for property", TEST_COMMENT + CR
406                 + "A nested header comment." + CR + "With multiple lines" + CR
407                 + CR + "Second comment", layout.getCanonicalComment(TEST_KEY,
408                 false));
409     }
410 
411     /***
412      * Tests whether the output of the layout object is identical to the source
413      * file (at least for simple properties files).
414      */
415     public void testReadAndWrite() throws ConfigurationException
416     {
417         builder.addComment("This is my test properties file,");
418         builder.addComment("which contains a header comment.");
419         builder.addComment(null);
420         builder.addComment(TEST_COMMENT);
421         builder.addProperty(TEST_KEY, TEST_COMMENT);
422         builder.addComment(null);
423         builder.addComment(null);
424         builder.addComment("Another comment");
425         builder.addProperty("property", "and a value");
426         layout.load(builder.getReader());
427         checkLayoutString(builder.toString());
428     }
429 
430     /***
431      * Tests if the content of the layout object is correctly written.
432      */
433     public void testSave() throws ConfigurationException
434     {
435         config.addProperty(TEST_KEY, TEST_VALUE);
436         layout.setComment(TEST_KEY, TEST_COMMENT);
437         config.addProperty(TEST_KEY, TEST_VALUE + "2");
438         config.addProperty("AnotherProperty", "AnotherValue");
439         config.addProperty("AnotherProperty", "3rdValue");
440         layout.setComment("AnotherProperty", "AnotherComment");
441         layout.setBlancLinesBefore("AnotherProperty", 2);
442         layout.setSingleLine("AnotherProperty", true);
443         layout.setHeaderComment("A header comment" + CR + "for my properties");
444         checkLayoutString("# A header comment" + CR + "# for my properties"
445                 + CR + CR + "# " + TEST_COMMENT + CR + TEST_KEY + " = "
446                 + TEST_VALUE + CR + TEST_KEY + " = " + TEST_VALUE + "2" + CR
447                 + CR + CR + "# AnotherComment" + CR
448                 + "AnotherProperty = AnotherValue,3rdValue" + CR);
449     }
450 
451     /***
452      * Tests the force single line flag.
453      */
454     public void testSaveForceSingleLine() throws ConfigurationException
455     {
456         config.setListDelimiter(';');
457         config.addProperty(TEST_KEY, TEST_VALUE);
458         config.addProperty(TEST_KEY, TEST_VALUE + "2");
459         config.addProperty("AnotherProperty", "value1;value2;value3");
460         layout.setComment(TEST_KEY, TEST_COMMENT);
461         layout.setForceSingleLine(true);
462         checkLayoutString("# " + TEST_COMMENT + CR + TEST_KEY + " = "
463                 + TEST_VALUE + ';' + TEST_VALUE + "2" + CR
464                 + "AnotherProperty = value1;value2;value3" + CR);
465     }
466 
467     /***
468      * Tests the trimComment method.
469      */
470     public void testTrimComment()
471     {
472         assertEquals("Wrong trimmed comment", "This is a comment" + CR
473                 + "that spans multiple" + CR + "lines in a" + CR
474                 + " complex way.", PropertiesConfigurationLayout.trimComment(
475                 "   # This is a comment" + CR + "that spans multiple" + CR
476                         + "!lines in a" + CR + " complex way.", false));
477     }
478 
479     /***
480      * Tests trimming a comment with trailing CRs.
481      */
482     public void testTrimCommentTrainlingCR()
483     {
484         assertEquals("Wrong trimmed comment", "Comment with" + CR
485                 + "trailing CR" + CR, PropertiesConfigurationLayout
486                 .trimComment("Comment with" + CR + "! trailing CR" + CR, false));
487     }
488 
489     /***
490      * Tests enforcing comment characters in a comment.
491      */
492     public void testTrimCommentFalse()
493     {
494         assertEquals("Wrong trimmed comment", "# Comment with" + CR
495                 + " ! some mixed " + CR + "#comment" + CR + "# lines",
496                 PropertiesConfigurationLayout.trimComment("Comment with" + CR
497                         + " ! some mixed " + CR + "#comment" + CR + "lines",
498                         true));
499     }
500 
501     /***
502      * Tests accessing data for a property, which is not stored.
503      */
504     public void testGetNonExistingLayouData()
505     {
506         assertNull("A comment was found", layout.getComment("unknown"));
507         assertTrue("A multi-line property", layout.isSingleLine("unknown"));
508         assertEquals("Leading blanc lines", 0, layout
509                 .getBlancLinesBefore("unknown"));
510     }
511 
512     /***
513      * Tests accessing a property with a null key. This should throw an
514      * exception.
515      */
516     public void testGetNullLayouttData()
517     {
518         try
519         {
520             layout.setComment(null, TEST_COMMENT);
521             fail("Could access null property key!");
522         }
523         catch (IllegalArgumentException iex)
524         {
525             // ok
526         }
527     }
528 
529     /***
530      * Tests resetting a comment.
531      */
532     public void testSetNullComment()
533     {
534         fillLayout();
535         layout.setComment(TEST_KEY, null);
536         assertNull("Comment was not reset", layout.getComment(TEST_KEY));
537     }
538 
539     /***
540      * Tests saving when a comment for a non existing property is contained in
541      * the layout object. This comment should be ignored.
542      */
543     public void testSaveCommentForUnexistingProperty()
544             throws ConfigurationException
545     {
546         fillLayout();
547         layout.setComment("NonExistingKey", "NonExistingComment");
548         String output = getLayoutString();
549         assertTrue("Non existing key was found", output
550                 .indexOf("NonExistingKey") < 0);
551         assertTrue("Non existing comment was found", output
552                 .indexOf("NonExistingComment") < 0);
553     }
554 
555     /***
556      * Tests saving an empty layout object.
557      */
558     public void testSaveEmptyLayout() throws ConfigurationException
559     {
560         checkLayoutString("");
561     }
562 
563     /***
564      * Tests the copy constructor.
565      */
566     public void testInitCopy()
567     {
568         fillLayout();
569         PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
570                 config, layout);
571         assertEquals("Wrong number of keys", layout.getKeys().size(), l2
572                 .getKeys().size());
573         for (Iterator it = layout.getKeys().iterator(); it.hasNext();)
574         {
575             Object key = it.next();
576             assertTrue("Key was not found: " + key, l2.getKeys().contains(key));
577         }
578     }
579 
580     /***
581      * Tests if the copy and the original are independend from each other.
582      */
583     public void testInitCopyModify()
584     {
585         fillLayout();
586         PropertiesConfigurationLayout l2 = new PropertiesConfigurationLayout(
587                 config, layout);
588         assertEquals("Comments are not equal", layout.getComment(TEST_KEY), l2
589                 .getComment(TEST_KEY));
590         layout.setComment(TEST_KEY, "A new comment");
591         assertEquals("Comment was changed", TEST_COMMENT, l2
592                 .getCanonicalComment(TEST_KEY, false));
593         l2.setBlancLinesBefore(TEST_KEY, l2.getBlancLinesBefore(TEST_KEY) + 1);
594         assertFalse("Blanc lines do not differ", layout
595                 .getBlancLinesBefore(TEST_KEY) == l2
596                 .getBlancLinesBefore(TEST_KEY));
597     }
598 
599     /***
600      * Helper method for filling the layout object with some properties.
601      */
602     private void fillLayout()
603     {
604         builder.addComment("A header comment");
605         builder.addComment(null);
606         builder.addProperty("prop", "value");
607         builder.addComment(TEST_COMMENT);
608         builder.addProperty(TEST_KEY, TEST_VALUE);
609         builder.addProperty("anotherProp", "anotherValue");
610         try
611         {
612             layout.load(builder.getReader());
613         }
614         catch (ConfigurationException cex)
615         {
616             // should not happen
617             fail("Exception was thrown: " + cex);
618         }
619     }
620 
621     /***
622      * Writes the layout's data into a string.
623      *
624      * @return the layout file's content as string
625      * @throws ConfigurationException if an error occurs
626      */
627     private String getLayoutString() throws ConfigurationException
628     {
629         StringWriter out = new StringWriter();
630         layout.save(out);
631         return out.toString();
632     }
633 
634     /***
635      * Checks if the layout's output is correct.
636      *
637      * @param expected the expected result
638      * @throws ConfigurationException if an error occurs
639      */
640     private void checkLayoutString(String expected)
641             throws ConfigurationException
642     {
643         assertEquals("Wrong layout file content", expected, getLayoutString());
644     }
645 
646     /***
647      * A helper class used for constructing test properties files.
648      */
649     static class PropertiesBuilder
650     {
651         /*** A buffer for storing the data. */
652         private StringBuffer buf = new StringBuffer();
653 
654         /*** A counter for varying the comment character. */
655         private int commentCounter;
656 
657         /***
658          * Adds a property to the simulated file.
659          *
660          * @param key the property key
661          * @param value the value
662          */
663         public void addProperty(String key, String value)
664         {
665             buf.append(key).append(" = ").append(value).append(CR);
666         }
667 
668         /***
669          * Adds a comment line.
670          *
671          * @param s the comment (can be <b>null</b>, then a blanc line is
672          * added)
673          */
674         public void addComment(String s)
675         {
676             if (s != null)
677             {
678                 if (commentCounter % 2 == 0)
679                 {
680                     buf.append("# ");
681                 }
682                 else
683                 {
684                     buf.append("! ");
685                 }
686                 buf.append(s);
687             }
688             buf.append(CR);
689         }
690 
691         /***
692          * Returns a reader for the simulated properties.
693          *
694          * @return a reader
695          */
696         public Reader getReader()
697         {
698             return new StringReader(buf.toString());
699         }
700 
701         /***
702          * Returns a string representation of the buffer's content.
703          *
704          * @return the buffer as string
705          */
706         public String toString()
707         {
708             return buf.toString();
709         }
710     }
711 
712     /***
713      * A mock properties configuration implementation that is used to check
714      * whether some expected methods are called.
715      */
716     static class LayoutTestConfiguration extends PropertiesConfiguration
717     {
718         /*** Stores a builder object. */
719         public PropertiesBuilder builder;
720 
721         /***
722          * Simulates the propertyLoaded() callback. If a builder was set, a
723          * load() call on the layout is invoked.
724          */
725         boolean propertyLoaded(String key, String value)
726                 throws ConfigurationException
727         {
728             if (builder == null)
729             {
730                 return super.propertyLoaded(key, value);
731             }
732             else
733             {
734                 if (PropertiesConfiguration.getInclude().equals(key))
735                 {
736                     getLayout().load(builder.getReader());
737                     return false;
738                 }
739                 else
740                 {
741                     return true;
742                 }
743             }
744         }
745     }
746 }