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  
18  package org.apache.commons.configuration;
19  
20  import java.io.File;
21  import java.io.FileWriter;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.io.Reader;
25  import java.io.StringReader;
26  import java.io.StringWriter;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
32  
33  import junit.framework.TestCase;
34  
35  /***
36   * Test for loading and saving properties files.
37   *
38   * @version $Id: TestPropertiesConfiguration.java 439648 2006-09-02 20:42:10Z oheger $
39   */
40  public class TestPropertiesConfiguration extends TestCase
41  {
42      private PropertiesConfiguration conf;
43  
44      /*** The File that we test with */
45      private String testProperties = new File("conf/test.properties").getAbsolutePath();
46  
47      private String testBasePath = new File("conf").getAbsolutePath();
48      private String testBasePath2 = new File("conf").getAbsoluteFile().getParentFile().getAbsolutePath();
49      private File testSavePropertiesFile = new File("target/testsave.properties");
50  
51      protected void setUp() throws Exception
52      {
53          conf = new PropertiesConfiguration(testProperties);
54      }
55  
56      public void testLoad() throws Exception
57      {
58          String loaded = conf.getString("configuration.loaded");
59          assertEquals("true", loaded);
60      }
61  
62      /***
63       * Tests if properties can be appended by simply calling load() another
64       * time.
65       */
66      public void testAppend() throws Exception
67      {
68          File file2 = new File("conf/threesome.properties");
69          conf.load(file2);
70          assertEquals("aaa", conf.getString("test.threesome.one"));
71          assertEquals("true", conf.getString("configuration.loaded"));
72      }
73  
74      /***
75       * Tests that empty properties are treated as the empty string
76       * (rather than as null).
77       */
78      public void testEmpty() throws Exception
79      {
80          String empty = conf.getString("test.empty");
81          assertNotNull(empty);
82          assertEquals("", empty);
83      }
84  
85      /***
86       * Tests that references to other properties work
87       */
88      public void testReference() throws Exception
89      {
90          assertEquals("baseextra", conf.getString("base.reference"));
91      }
92  
93      /***
94       * test if includes properties get loaded too
95       */
96      public void testLoadInclude() throws Exception
97      {
98          String loaded = conf.getString("include.loaded");
99          assertEquals("true", loaded);
100     }
101 
102     public void testSetInclude() throws Exception
103     {
104         // change the include key
105         PropertiesConfiguration.setInclude("import");
106 
107         // load the configuration
108         PropertiesConfiguration conf = new PropertiesConfiguration();
109         conf.load("conf/test.properties");
110 
111         // restore the previous value for the other tests
112         PropertiesConfiguration.setInclude("include");
113 
114         assertNull(conf.getString("include.loaded"));
115     }
116 
117     /***
118      * Tests <code>List</code> parsing.
119      */
120     public void testList() throws Exception
121     {
122         List packages = conf.getList("packages");
123         // we should get 3 packages here
124         assertEquals(3, packages.size());
125     }
126 
127     public void testSave() throws Exception
128     {
129         // remove the file previously saved if necessary
130         if (testSavePropertiesFile.exists())
131         {
132             assertTrue(testSavePropertiesFile.delete());
133         }
134 
135         // add an array of strings to the configuration
136         conf.addProperty("string", "value1");
137         List list = new ArrayList();
138         for (int i = 1; i < 5; i++)
139         {
140             list.add("value" + i);
141         }
142         conf.addProperty("array", list);
143 
144         // save the configuration
145         String filename = testSavePropertiesFile.getAbsolutePath();
146         conf.save(filename);
147 
148         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
149 
150         // read the configuration and compare the properties
151         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
152         for (Iterator i = conf.getKeys(); i.hasNext();)
153         {
154             String key = (String) i.next();
155             assertTrue("The saved configuration doesn't contain the key '" + key + "'", checkConfig.containsKey(key));
156             assertEquals("Value of the '" + key + "' property", conf.getProperty(key), checkConfig.getProperty(key));
157         }
158 
159         // Save it again, verifing a save with a filename works.
160         checkConfig.save();
161     }
162 
163     public void testInMemoryCreatedSave() throws Exception
164     {
165         // remove the file previously saved if necessary
166         if (testSavePropertiesFile.exists())
167         {
168             assertTrue(testSavePropertiesFile.delete());
169         }
170 
171         PropertiesConfiguration pc = new PropertiesConfiguration();
172         // add an array of strings to the configuration
173         pc.addProperty("string", "value1");
174         List list = new ArrayList();
175         for (int i = 1; i < 5; i++)
176         {
177             list.add("value" + i);
178         }
179         pc.addProperty("array", list);
180 
181         // save the configuration
182         String filename = testSavePropertiesFile.getAbsolutePath();
183         pc.save(filename);
184 
185         assertTrue("The saved file doesn't exist", testSavePropertiesFile.exists());
186 
187         // read the configuration and compare the properties
188         PropertiesConfiguration checkConfig = new PropertiesConfiguration(filename);
189         for (Iterator i = pc.getKeys(); i.hasNext();)
190         {
191             String key = (String) i.next();
192             assertTrue("The saved configuration doesn't contain the key '" + key + "'",
193                     checkConfig.containsKey(key));
194             assertEquals("Value of the '" + key + "' property",
195                     pc.getProperty(key), checkConfig.getProperty(key));
196         }
197 
198         // Save it again, verifing a save with a filename works.
199         checkConfig.save();
200     }
201 
202     public void testSaveMissingFilename()
203     {
204         PropertiesConfiguration pc = new PropertiesConfiguration();
205         try
206         {
207             pc.save();
208             fail("Should have throw ConfigurationException");
209         }
210         catch (ConfigurationException ce)
211         {
212             //good
213         }
214     }
215 
216     /***
217      * Tests if the base path is taken into account by the save() method.
218      * @throws Exception if an error occurs
219      */
220     public void testSaveWithBasePath() throws Exception
221     {
222         // remove the file previously saved if necessary
223         if (testSavePropertiesFile.exists())
224         {
225             assertTrue(testSavePropertiesFile.delete());
226         }
227 
228         conf.setProperty("test", "true");
229         conf.setBasePath(testSavePropertiesFile.getParentFile().toURL().toString());
230         conf.setFileName(testSavePropertiesFile.getName());
231         conf.save();
232         assertTrue(testSavePropertiesFile.exists());
233     }
234 
235     public void testLoadViaProperty() throws Exception
236     {
237         PropertiesConfiguration pc = new PropertiesConfiguration();
238         pc.setFileName(testProperties);
239         pc.load();
240 
241         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
242     }
243 
244     public void testLoadViaPropertyWithBasePath() throws Exception
245     {
246         PropertiesConfiguration pc = new PropertiesConfiguration();
247         pc.setBasePath(testBasePath);
248         pc.setFileName("test.properties");
249         pc.load();
250 
251         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
252     }
253 
254     public void testLoadViaPropertyWithBasePath2() throws Exception
255     {
256         PropertiesConfiguration pc = new PropertiesConfiguration();
257         pc.setBasePath(testBasePath2);
258         pc.setFileName("conf/test.properties");
259         pc.load();
260 
261         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
262 
263         pc = new PropertiesConfiguration();
264         pc.setBasePath(testBasePath2);
265         pc.setFileName("conf/test.properties");
266         pc.load();
267 
268         assertTrue("Make sure we have multiple keys", pc.getBoolean("test.boolean"));
269     }
270 
271     public void testLoadFromFile() throws Exception
272     {
273         File file = new File("conf/test.properties");
274         conf = new PropertiesConfiguration(file);
275 
276         assertEquals("true", conf.getString("configuration.loaded"));
277     }
278 
279     public void testLoadUnexistingFile()
280     {
281         try
282         {
283             conf = new PropertiesConfiguration("Unexisting file");
284             fail("Unexisting file was loaded.");
285         }
286         catch(ConfigurationException cex)
287         {
288             // fine
289         }
290     }
291 
292     /***
293      * Tests to load a file with enabled auto save mode.
294      */
295     public void testLoadWithAutoSave() throws Exception
296     {
297         setUpSavedProperties();
298     }
299 
300     /***
301      * Tests the auto save functionality when an existing property is modified.
302      */
303     public void testLoadWithAutoSaveAndSetExisting() throws Exception
304     {
305         setUpSavedProperties();
306         conf.setProperty("a", "moreThanOne");
307         checkSavedConfig();
308     }
309 
310     /***
311      * Tests the auto save functionality when a new property is added using the
312      * setProperty() method.
313      */
314     public void testLoadWithAutoSaveAndSetNew() throws Exception
315     {
316         setUpSavedProperties();
317         conf.setProperty("d", "four");
318         checkSavedConfig();
319     }
320 
321     /***
322      * Tests the auto save functionality when a new property is added using the
323      * addProperty() method.
324      */
325     public void testLoadWithAutoSaveAndAdd() throws Exception
326     {
327         setUpSavedProperties();
328         conf.addProperty("d", "four");
329         checkSavedConfig();
330     }
331 
332     /***
333      * Tests the auto save functionality when a property is removed.
334      */
335     public void testLoadWithAutoSaveAndClear() throws Exception
336     {
337         setUpSavedProperties();
338         conf.clearProperty("c");
339         PropertiesConfiguration checkConfig = checkSavedConfig();
340         assertFalse("The saved configuration contain the key '" + "c" + "'",
341                 checkConfig.containsKey("c"));
342     }
343 
344     /***
345      * Creates a properties file on disk. Used for testing load and save
346      * operations.
347      *
348      * @throws IOException if an I/O error occurs
349      */
350     private void setUpSavedProperties() throws IOException,
351             ConfigurationException
352     {
353         PrintWriter out = null;
354 
355         try
356         {
357             out = new PrintWriter(new FileWriter(testSavePropertiesFile));
358             out.println("a = one");
359             out.println("b = two");
360             out.println("c = three");
361             out.close();
362             out = null;
363 
364             conf = new PropertiesConfiguration();
365             conf.setAutoSave(true);
366             conf.setFile(testSavePropertiesFile);
367             conf.load();
368             assertEquals("one", conf.getString("a"));
369             assertEquals("two", conf.getString("b"));
370             assertEquals("three", conf.getString("c"));
371         }
372         finally
373         {
374             if (out != null)
375             {
376                 out.close();
377             }
378         }
379     }
380 
381     /***
382      * Helper method for testing a saved configuration. Reads in the file using
383      * a new instance and compares this instance with the original one.
384      *
385      * @return the newly created configuration instance
386      * @throws ConfigurationException if an error occurs
387      */
388     private PropertiesConfiguration checkSavedConfig()
389             throws ConfigurationException
390     {
391         PropertiesConfiguration checkConfig = new PropertiesConfiguration(
392                 testSavePropertiesFile);
393         for (Iterator i = conf.getKeys(); i.hasNext();)
394         {
395             String key = (String) i.next();
396             assertTrue("The saved configuration doesn't contain the key '"
397                     + key + "'", checkConfig.containsKey(key));
398             assertEquals("Value of the '" + key + "' property", conf
399                     .getProperty(key), checkConfig.getProperty(key));
400         }
401         return checkConfig;
402     }
403 
404     public void testGetStringWithEscapedChars()
405     {
406         String property = conf.getString("test.unescape");
407         assertEquals("String with escaped characters", "This \n string \t contains \" escaped // characters", property);
408     }
409 
410     public void testGetStringWithEscapedComma()
411     {
412         String property = conf.getString("test.unescape.list-separator");
413         assertEquals("String with an escaped list separator", "This string contains , an escaped list separator", property);
414     }
415 
416     public void testUnescapeJava()
417     {
418         assertEquals("test//,test", PropertiesConfiguration.unescapeJava("test//,test", ','));
419     }
420 
421     public void testEscapedKey() throws Exception
422     {
423         PropertiesConfiguration conf = new PropertiesConfiguration();
424         conf.load(new StringReader("//u0066//u006f//u006f=bar"));
425 
426         assertEquals("value of the 'foo' property", "bar", conf.getString("foo"));
427     }
428 
429     public void testMixedArray()
430     {
431         String[] array = conf.getStringArray("test.mixed.array");
432 
433         assertEquals("array length", 4, array.length);
434         assertEquals("1st element", "a", array[0]);
435         assertEquals("2nd element", "b", array[1]);
436         assertEquals("3rd element", "c", array[2]);
437         assertEquals("4th element", "d", array[3]);
438     }
439 
440     public void testMultilines()
441     {
442         String property = "This is a value spread out across several adjacent "
443                 + "natural lines by escaping the line terminator with "
444                 + "a backslash character.";
445 
446         assertEquals("'test.multilines' property", property, conf.getString("test.multilines"));
447     }
448 
449     public void testChangingDefaultListDelimiter() throws Exception
450     {
451         PropertiesConfiguration pc = new PropertiesConfiguration(testProperties);
452         assertEquals(4, pc.getList("test.mixed.array").size());
453 
454         char delimiter = PropertiesConfiguration.getDefaultListDelimiter();
455         PropertiesConfiguration.setDefaultListDelimiter('^');
456         pc = new PropertiesConfiguration(testProperties);
457         assertEquals(2, pc.getList("test.mixed.array").size());
458         PropertiesConfiguration.setDefaultListDelimiter(delimiter);
459     }
460 
461     public void testChangingListDelimiter() throws Exception
462     {
463         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
464         assertEquals(4, pc1.getList("test.mixed.array").size());
465 
466         PropertiesConfiguration pc2 = new PropertiesConfiguration();
467         pc2.setListDelimiter('^');
468         pc2.setFileName(testProperties);
469         pc2.load();
470         assertEquals("Should obtain the first value", "a", pc2.getString("test.mixed.array"));
471         assertEquals(2, pc2.getList("test.mixed.array").size());
472     }
473 
474     public void testDisableListDelimiter() throws Exception
475     {
476         PropertiesConfiguration pc1 = new PropertiesConfiguration(testProperties);
477         assertEquals(4, pc1.getList("test.mixed.array").size());
478 
479         PropertiesConfiguration pc2 = new PropertiesConfiguration();
480         pc2.setDelimiterParsingDisabled(true);
481         pc2.setFileName(testProperties);
482         pc2.load();
483         assertEquals(2, pc2.getList("test.mixed.array").size());
484     }
485 
486     /***
487      * Tests escaping of an end of line with a backslash.
488      */
489     public void testNewLineEscaping()
490     {
491         List list = conf.getList("test.path");
492         assertEquals(3, list.size());
493         assertEquals("C://path1//", list.get(0));
494         assertEquals("C://path2//", list.get(1));
495         assertEquals("C://path3//complex//test//", list.get(2));
496     }
497 
498     /***
499      * Tests if included files are loaded when the source lies in the class path.
500      */
501     public void testLoadIncludeFromClassPath() throws ConfigurationException
502     {
503         conf = new PropertiesConfiguration("test.properties");
504         assertEquals("true", conf.getString("include.loaded"));
505     }
506 
507     /***
508      * Test if the lines starting with # or ! are properly ignored.
509      */
510     public void testComment() {
511         assertFalse("comment line starting with '#' parsed as a property", conf.containsKey("#comment"));
512         assertFalse("comment line starting with '!' parsed as a property", conf.containsKey("!comment"));
513     }
514 
515     /***
516      * Check that key/value separators can be part of a key.
517      */
518     public void testEscapedKeyValueSeparator()
519     {
520         assertEquals("Escaped separator '=' not supported in keys", "foo", conf.getProperty("test.separator=in.key"));
521         assertEquals("Escaped separator ':' not supported in keys", "bar", conf.getProperty("test.separator:in.key"));
522         assertEquals("Escaped separator '//t' not supported in keys", "foo", conf.getProperty("test.separator\tin.key"));
523         assertEquals("Escaped separator '//f' not supported in keys", "bar", conf.getProperty("test.separator\fin.key"));
524         assertEquals("Escaped separator ' ' not supported in keys"  , "foo", conf.getProperty("test.separator in.key"));
525     }
526 
527     /***
528      * Test all acceptable key/value separators ('=', ':' or white spaces).
529      */
530     public void testKeyValueSeparators() {
531         assertEquals("equal separator not properly parsed",      "foo", conf.getProperty("test.separator.equal"));
532         assertEquals("colon separator not properly parsed",      "foo", conf.getProperty("test.separator.colon"));
533         assertEquals("tab separator not properly parsed",        "foo", conf.getProperty("test.separator.tab"));
534         assertEquals("formfeed separator not properly parsed",   "foo", conf.getProperty("test.separator.formfeed"));
535         assertEquals("whitespace separator not properly parsed", "foo", conf.getProperty("test.separator.whitespace"));
536     }
537 
538     /***
539      * Tests including properties when they are loaded from a nested directory
540      * structure.
541      */
542     public void testIncludeInSubDir() throws ConfigurationException
543     {
544         ConfigurationFactory factory = new ConfigurationFactory("conf/testFactoryPropertiesInclude.xml");
545         Configuration config = factory.getConfiguration();
546         assertEquals(true, config.getBoolean("deeptest"));
547         assertEquals(true, config.getBoolean("deepinclude"));
548         assertFalse(config.containsKey("deeptestinvalid"));
549     }
550 
551     /***
552      * Tests whether the correct line separator is used.
553      */
554     public void testLineSeparator() throws ConfigurationException
555     {
556         final String EOL = System.getProperty("line.separator");
557         conf = new PropertiesConfiguration();
558         conf.setHeader("My header");
559         conf.setProperty("prop", "value");
560 
561         StringWriter out = new StringWriter();
562         conf.save(out);
563         String content = out.toString();
564         assertTrue("Header could not be found", content.indexOf("# My header"
565                 + EOL + EOL) == 0);
566         assertTrue("Property could not be found", content
567                 .indexOf("prop = value" + EOL) > 0);
568     }
569 
570     /***
571      * Tests what happens if a reloading strategy's <code>reloadingRequired()</code>
572      * implementation accesses methods of the configuration that in turn cause a reload.
573      */
574     public void testReentrantReload()
575     {
576         conf.setProperty("shouldReload", Boolean.FALSE);
577         conf.setReloadingStrategy(new FileChangedReloadingStrategy()
578         {
579             public boolean reloadingRequired()
580             {
581                 return configuration.getBoolean("shouldReload");
582             }
583         });
584         assertFalse("Property has wrong value", conf.getBoolean("shouldReload"));
585     }
586 
587     /***
588      * Tests accessing the layout object.
589      */
590     public void testGetLayout()
591     {
592         PropertiesConfigurationLayout layout = conf.getLayout();
593         assertNotNull("Layout is null", layout);
594         assertSame("Different object returned", layout, conf.getLayout());
595         conf.setLayout(null);
596         PropertiesConfigurationLayout layout2 = conf.getLayout();
597         assertNotNull("Layout 2 is null", layout2);
598         assertNotSame("Same object returned", layout, layout2);
599     }
600 
601     /***
602      * Tests the propertyLoaded() method for a simple property.
603      */
604     public void testPropertyLoaded() throws ConfigurationException
605     {
606         DummyLayout layout = new DummyLayout(conf);
607         conf.setLayout(layout);
608         conf.propertyLoaded("layoutLoadedProperty", "yes");
609         assertEquals("Layout's load() was called", 0, layout.loadCalls);
610         assertEquals("Property not added", "yes", conf
611                 .getString("layoutLoadedProperty"));
612     }
613 
614     /***
615      * Tests the propertyLoaded() method for an include property.
616      */
617     public void testPropertyLoadedInclude() throws ConfigurationException
618     {
619         DummyLayout layout = new DummyLayout(conf);
620         conf.setLayout(layout);
621         conf.propertyLoaded(PropertiesConfiguration.getInclude(),
622                 "testClasspath.properties,testEqual.properties");
623         assertEquals("Layout's load() was not correctly called", 2,
624                 layout.loadCalls);
625         assertFalse("Property was added", conf
626                 .containsKey(PropertiesConfiguration.getInclude()));
627     }
628 
629     /***
630      * Tests propertyLoaded() for an include property, when includes are
631      * disabled.
632      */
633     public void testPropertyLoadedIncludeNotAllowed()
634             throws ConfigurationException
635     {
636         DummyLayout layout = new DummyLayout(conf);
637         conf.setLayout(layout);
638         conf.setIncludesAllowed(false);
639         conf.propertyLoaded(PropertiesConfiguration.getInclude(),
640                 "testClassPath.properties,testEqual.properties");
641         assertEquals("Layout's load() was called", 0, layout.loadCalls);
642         assertFalse("Property was added", conf
643                 .containsKey(PropertiesConfiguration.getInclude()));
644     }
645 
646     /***
647      * Tests whether comment lines are correctly detected.
648      */
649     public void testIsCommentLine()
650     {
651         assertTrue("Comment not detected", PropertiesConfiguration
652                 .isCommentLine("# a comment"));
653         assertTrue("Alternative comment not detected", PropertiesConfiguration
654                 .isCommentLine("! a comment"));
655         assertTrue("Comment with no space not detected",
656                 PropertiesConfiguration.isCommentLine("#a comment"));
657         assertTrue("Comment with leading space not detected",
658                 PropertiesConfiguration.isCommentLine("    ! a comment"));
659         assertFalse("Wrong comment", PropertiesConfiguration
660                 .isCommentLine("   a#comment"));
661     }
662 
663     /***
664      * Tests whether a properties configuration can be successfully cloned. It
665      * is especially checked whether the layout object is taken into account.
666      */
667     public void testClone() throws ConfigurationException
668     {
669         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
670         assertNotSame("Copy has same layout object", conf.getLayout(), copy
671                 .getLayout());
672         assertEquals("Wrong number of event listeners for original", 1, conf
673                 .getConfigurationListeners().size());
674         assertEquals("Wrong number of event listeners for clone", 1, copy
675                 .getConfigurationListeners().size());
676         assertSame("Wrong event listener for original", conf.getLayout(), conf
677                 .getConfigurationListeners().iterator().next());
678         assertSame("Wrong event listener for clone", copy.getLayout(), copy
679                 .getConfigurationListeners().iterator().next());
680         StringWriter outConf = new StringWriter();
681         conf.save(outConf);
682         StringWriter outCopy = new StringWriter();
683         copy.save(outCopy);
684         assertEquals("Output from copy is different", outConf.toString(),
685                 outCopy.toString());
686     }
687 
688     /***
689      * Tests the clone() method when no layout object exists yet.
690      */
691     public void testCloneNullLayout()
692     {
693         conf = new PropertiesConfiguration();
694         PropertiesConfiguration copy = (PropertiesConfiguration) conf.clone();
695         assertNotSame("Layout objects are the same", conf.getLayout(), copy
696                 .getLayout());
697     }
698 
699     /***
700      * A dummy layout implementation for checking whether certain methods are
701      * correctly called by the configuration.
702      */
703     static class DummyLayout extends PropertiesConfigurationLayout
704     {
705         /*** Stores the number how often load() was called. */
706         public int loadCalls;
707 
708         public DummyLayout(PropertiesConfiguration config)
709         {
710             super(config);
711         }
712 
713         public void load(Reader in) throws ConfigurationException
714         {
715             loadCalls++;
716         }
717     }
718 }