Skip to content

Commit acc5c19

Browse files
committed
PropertyFile bugfix: when loading from Properties, read values directly rather than re-parsing (since this caused decode errors on special chars due to a difference between Properties and PropertyFile escaping)
1 parent 55d804f commit acc5c19

3 files changed

Lines changed: 133 additions & 25 deletions

File tree

‎guice/common/src/test/java/com/peterphi/std/guice/common/serviceprops/composite/GuiceConfigTest.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.junit.Test;
44

5+
import java.util.Properties;
6+
57
import static org.junit.Assert.assertEquals;
68

79
public class GuiceConfigTest
@@ -49,4 +51,18 @@ public void testVariableRef()
4951
assertEquals("getRaw", "a-${var}-b", props.getRaw("name", null));
5052
assertEquals("get", "a-123-b", props.get("name", null));
5153
}
54+
55+
56+
@Test
57+
public void testSetAllPropertiesPreservesLoggingPattern()
58+
{
59+
Properties properties = new Properties();
60+
String name = "log4j.appender.logconsole.layout.ConversionPattern";
61+
String pattern = "%-5p %d: café = #! %m%n";
62+
properties.setProperty(name, pattern);
63+
64+
props.setAll(properties);
65+
66+
assertEquals(pattern, props.get(name, null));
67+
}
5268
}

‎stdlib/src/main/java/com/peterphi/std/io/PropertyFile.java‎

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
import java.io.File;
1010
import java.io.FileReader;
1111
import java.io.FileWriter;
12-
import java.io.IOError;
1312
import java.io.IOException;
1413
import java.io.InputStream;
1514
import java.io.InputStreamReader;
1615
import java.io.OutputStream;
1716
import java.io.OutputStreamWriter;
1817
import java.io.Reader;
1918
import java.io.StringReader;
20-
import java.io.StringWriter;
2119
import java.io.Writer;
2220
import java.net.URI;
2321
import java.net.URISyntaxException;
@@ -82,7 +80,6 @@ public static PropertyFile find(final String fileName)
8280
*
8381
* @param classloader
8482
* @param fileNames
85-
*
8683
* @return
8784
*/
8885
public static PropertyFile find(final ClassLoader classloader, final String... fileNames)
@@ -201,7 +198,6 @@ public static PropertyFile openResource(final ClassLoader classloader, final URL
201198
* Find all property files by the name <code>name</code> by searching the classloader that owns PropertyFile
202199
*
203200
* @param name
204-
*
205201
* @return
206202
*/
207203
public static PropertyFile[] findAll(final String name)
@@ -215,7 +211,6 @@ public static PropertyFile[] findAll(final String name)
215211
*
216212
* @param loader
217213
* @param name
218-
*
219214
* @return
220215
*/
221216
public static PropertyFile[] findAll(ClassLoader loader, final String name)
@@ -423,21 +418,18 @@ public void load(Map<String, String> map)
423418

424419
public void load(Properties p)
425420
{
426-
try
427-
{
428-
StringWriter sw = new StringWriter();
429-
p.store(sw, null);
430-
431-
StringBuffer sb = sw.getBuffer();
432-
433-
StringReader sr = new StringReader(sb.toString());
421+
entries.clear();
422+
vars.clear();
434423

435-
load(sr);
436-
}
437-
catch (IOException e)
424+
// Properties already contains decoded strings
425+
for (String name : p.stringPropertyNames())
438426
{
439-
throw new IOError(e);
427+
NameValuePair nvp = new NameValuePair(name, p.getProperty(name));
428+
entries.add(nvp);
429+
vars.put(caseSensitive ? name : name.toLowerCase(), nvp);
440430
}
431+
432+
hook_loaded();
441433
}
442434

443435

@@ -498,6 +490,7 @@ public void save(String comment, OutputStream os) throws IOException
498490
// MANIPULATION
499491
// ////////////////////////////
500492

493+
501494
/**
502495
* Make this PropertyFile object read-only
503496
*/
@@ -896,13 +889,9 @@ protected void _rm(String name)
896889
* Creates a read-only version of a property file<br />
897890
* Fails if the file does not exist.
898891
*
899-
* @param filename
900-
* the filename to load
901-
*
892+
* @param filename the filename to load
902893
* @return
903-
*
904-
* @throws IOException
905-
* if the file cannot be loaded
894+
* @throws IOException if the file cannot be loaded
906895
*/
907896
public static PropertyFile readOnly(final File filename) throws IOException
908897
{
@@ -916,7 +905,8 @@ public static PropertyFile readOnly(final File filename) throws IOException
916905

917906
////////////////////////////////////
918907
// Property File entry types
919-
////////////////////////////////////
908+
909+
/// /////////////////////////////////
920910

921911

922912
protected abstract class Entry

‎stdlib/src/test/java/com/peterphi/std/io/PropertyFileTest.java‎

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Properties;
77

88
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertFalse;
910

1011
public class PropertyFileTest
1112
{
@@ -26,8 +27,109 @@ public void testToProperties()
2627
f.set("a", "b");
2728

2829
Properties properties = new Properties();
29-
properties.put("a","b");
30+
properties.put("a", "b");
3031

3132
assertEquals(properties, f.toProperties());
3233
}
34+
35+
36+
@Test
37+
public void testPropertiesPreserveDecodedCharacters()
38+
{
39+
Properties properties = new Properties();
40+
properties.setProperty("pattern", "%-5p %d: café = #! %m%n");
41+
properties.setProperty("special:key=#!\\path", " leading space\ttab\nnewline\rcarriage return\\literal\\n");
42+
43+
PropertyFile file = new PropertyFile(properties, true);
44+
45+
assertEquals(properties, file.toProperties());
46+
}
47+
48+
49+
@Test
50+
public void testPropertiesIncludeDefaultsAndOverrides()
51+
{
52+
Properties defaults = new Properties();
53+
defaults.setProperty("inherited", "default: value");
54+
defaults.setProperty("overridden", "default");
55+
Properties properties = new Properties(defaults);
56+
properties.setProperty("overridden", "explicit");
57+
58+
PropertyFile file = new PropertyFile(properties);
59+
60+
assertEquals("default: value", file.get("inherited", null));
61+
assertEquals("explicit", file.get("overridden", null));
62+
assertEquals(2, file.getVariableCount());
63+
}
64+
65+
66+
@Test
67+
public void testLoadPropertiesReplacesExistingContentsAndMarksClean() throws Exception
68+
{
69+
PropertyFile file = new PropertyFile(new java.io.StringReader("# old comment\nold=value\n"));
70+
file.set("changed", "value");
71+
Properties properties = new Properties();
72+
properties.setProperty("new", "value: with = punctuation");
73+
74+
file.load(properties);
75+
76+
assertEquals(properties, file.toProperties());
77+
assertEquals(1, file.getLineCount());
78+
assertFalse(file.dirty);
79+
80+
file.load(new Properties());
81+
82+
assertEquals(0, file.getVariableCount());
83+
assertEquals(0, file.getLineCount());
84+
assertFalse(file.dirty);
85+
}
86+
87+
88+
@Test
89+
public void testPropertiesRespectCaseSensitivity()
90+
{
91+
Properties properties = new Properties();
92+
properties.setProperty("MixedCase", "value");
93+
94+
assertEquals("value", new PropertyFile(properties).get("mixedcase", null));
95+
PropertyFile sensitive = new PropertyFile(properties, true);
96+
assertEquals("value", sensitive.get("MixedCase", null));
97+
assertFalse(sensitive.keySet().contains("mixedcase"));
98+
}
99+
100+
101+
@Test
102+
public void testLoadPropertiesOnlyCallsLoadedHook()
103+
{
104+
class ObservedPropertyFile extends PropertyFile
105+
{
106+
int loads;
107+
int additions;
108+
109+
110+
@Override
111+
protected void hook_loaded()
112+
{
113+
loads++;
114+
super.hook_loaded();
115+
}
116+
117+
118+
@Override
119+
protected void hook_added(String name, String value)
120+
{
121+
additions++;
122+
super.hook_added(name, value);
123+
}
124+
}
125+
ObservedPropertyFile file = new ObservedPropertyFile();
126+
Properties properties = new Properties();
127+
properties.setProperty("name", "value");
128+
129+
file.load(properties);
130+
131+
assertEquals(1, file.loads);
132+
assertEquals(0, file.additions);
133+
assertFalse(file.dirty);
134+
}
33135
}

0 commit comments

Comments
 (0)