/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.openjpa.lib.util; | |
import java.io.BufferedInputStream; | |
import java.io.BufferedReader; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.io.ObjectInputStream; | |
import java.io.ObjectOutputStream; | |
import java.io.StringBufferInputStream; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Properties; | |
import junit.framework.TestCase; | |
import org.apache.openjpa.lib.util.FormatPreservingProperties.DuplicateKeyException; | |
// things to test: | |
// - delimiters in keys | |
// - escape chars, including \:, \= in files(as generated by Properties) | |
// - unicode | |
// - non-String keys / vals | |
// - list() method behavior | |
public class TestPropertiesParser extends TestCase { | |
private static final String LS = System.getProperty( "line.separator" ); | |
public void testSimpleProperties() throws IOException { | |
StringBuffer buf = new StringBuffer(); | |
buf.append("key: value" + LS); | |
buf.append("key2: value2"); // no EOL -- this is intentional | |
Properties p = toProperties(buf.toString()); | |
assertProperties(new String[][]{ | |
{ "key", "value" }, { "key2", "value2" } }, p); | |
} | |
public void testComments() throws IOException { | |
StringBuffer buf = new StringBuffer(); | |
buf.append("# this is a comment" + LS); | |
buf.append(" # another one, with leading whitespace " + LS); | |
buf.append(" # and more with interesting whitespace " + LS); | |
buf.append("! and with a ! delimiter" + LS); | |
buf.append("! and with escape \t chars" + LS); | |
buf.append("#and a comment with no whitespace" + LS); | |
Properties p = toProperties(buf.toString()); | |
assertEquals(0, p.size()); | |
} | |
public void testMixedContent() throws IOException { | |
StringBuffer buf = new StringBuffer(); | |
buf.append("# this is a comment" + LS); | |
buf.append(" # another one, with leading whitespace " + LS); | |
buf.append("foo: bar#baz" + LS); | |
buf.append("! and with a ! delimiter" + LS); | |
buf.append("! and with escape \t chars" + LS); | |
Properties p = toProperties(buf.toString()); | |
assertProperties(new String[][]{ { "foo", "bar#baz" } }, p); | |
} | |
public void testMultiLineInput() throws IOException { | |
String s = "foo: bar\\" + LS + "more line goes here"; | |
Properties p = toProperties(s); | |
assertProperties( | |
new String[][]{ { "foo", "barmore line goes here" } }, p); | |
} | |
public void testEmptyLines() throws IOException { | |
Properties p = toProperties(LS + "foo: bar" + LS + LS + "baz: val"); | |
assertProperties(new String[][]{ { "foo", "bar" }, { "baz", "val" } }, | |
p); | |
} | |
public void testAddProperties() throws IOException { | |
// intentionally left out the trailing end line | |
String s = "foo: bar" + LS + "baz: val"; | |
Properties p = toProperties(s); | |
assertProperties(new String[][]{ { "foo", "bar" }, { "baz", "val" } }, | |
p); | |
p.put("new-key", "val1"); | |
p.put("new-key-2", "val2"); | |
p.put("another-new-key", "val3"); | |
assertRoundTrip(s + LS + "new-key: val1" + LS + "new-key-2: val2" + LS + | |
"another-new-key: val3" + LS, p); | |
} | |
public void testAddAndMutateProperties() throws IOException { | |
// intentionally left out the trailing end line | |
Properties p = toProperties("foo: bar" + LS + "baz: val"); | |
assertProperties(new String[][]{ { "foo", "bar" }, { "baz", "val" } }, | |
p); | |
p.put("new-key", "new value"); | |
p.put("foo", "barbar"); | |
assertRoundTrip("foo: barbar" + LS + "baz: val" + LS | |
+ "new-key: new value" + LS, p); | |
} | |
public void testEscapedEquals() throws IOException { | |
Properties p = toProperties("foo=bar\\=WARN,baz\\=TRACE"); | |
assertProperties(new String[][]{ { "foo", "bar=WARN,baz=TRACE" } }, p); | |
} | |
public void testLineTypes() throws IOException { | |
StringBuffer buf = new StringBuffer(); | |
buf.append(" !comment" + LS + " \t " + LS + "name = no" + LS + " " | |
+ "#morec\tomm\\" + LS + "ents" + LS + LS + " dog=no\\cat " + LS | |
+ "burps :" + LS + "test=" + LS + "date today" + LS + LS + LS | |
+ "long\\" + LS + " value=tryin \\" + LS + " " | |
+ "gto" + LS + "4:vier" + LS + "vier :4"); | |
Properties p = toProperties(buf.toString()); | |
assertProperties(new String[][]{ | |
{ "name", "no" }, { "ents", "" }, { "dog", "nocat " }, | |
{ "burps", "" }, { "test", "" }, { "date", "today" }, | |
{ "longvalue", "tryin gto" }, { "4", "vier" }, { "vier", "4" }, | |
}, p); | |
} | |
public void testSpecialChars() throws Throwable { | |
testSpecialChars(false, true); | |
testSpecialChars(true, true); | |
testSpecialChars(false, false); | |
testSpecialChars(true, false); | |
} | |
/** | |
* Test that special characters work. | |
* | |
* @param formattingProps if true, test against the | |
* FormatPreservingProperties, otherwise test | |
* against a normal Properties instance(for validation of the test case). | |
* @param value whether to test the key or the value | |
*/ | |
public void testSpecialChars(boolean formattingProps, boolean value) | |
throws Throwable { | |
List valueList = new ArrayList(Arrays.asList(new String[]{ | |
"xxyy", "xx\\yy", "xx" + LS + "yy", "xx\\nyy", "xx\tyy", "xx\\tyy", | |
"xx\ryy", "xx\\ryy", "xx\fyy", "xx\\fyy", "xx\r" + LS + "\\\t\r\t" | |
+ LS + "yy", | |
"xx\\r" + LS + "\\\t\\r\t\\nyy", | |
"xx\r" + LS + "\\\\\\\\\\\\\\\\\\\\\\\\\\\t\r\t" + LS + "yy", | |
"C:\\Program Files\\Some Application\\OpenJPA\\My File.dat", })); | |
// also store every individual character | |
for (char c = 'a'; c < 'Z'; c++) { | |
valueList.add(new String(new char[]{ c })); | |
valueList.add(new String(new char[]{ c, '\\', c })); | |
valueList.add(new String(new char[]{ '\\', c })); | |
} | |
String[] values = (String[]) valueList.toArray(new String[0]); | |
final String dummy = "XXX"; | |
for (int i = 0; i < values.length; i++) { | |
// test special characters in either keys or values | |
String val = value ? values[i] : dummy; | |
String key = value ? dummy : values[i]; | |
Properties p = formattingProps ? | |
new FormatPreservingProperties() : new Properties(); | |
if (p instanceof FormatPreservingProperties) { | |
// set these properties so we behave the same way as | |
// java.util.Properties | |
((FormatPreservingProperties) p).setDefaultEntryDelimiter('='); | |
((FormatPreservingProperties) p). | |
setAddWhitespaceAfterDelimiter(false); | |
} | |
p.setProperty(key, val); | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
p.store(out, null); | |
Properties copy = new Properties(); | |
copy.setProperty(key, val); | |
ByteArrayOutputStream copyOut = new ByteArrayOutputStream(); | |
copy.store(copyOut, null); | |
p = formattingProps ? | |
new FormatPreservingProperties() : new Properties(); | |
InputStream in = new BufferedInputStream | |
(new ByteArrayInputStream(out.toByteArray())); | |
try { | |
// make sure that the 2 properties serialized are the same | |
String copyOutString = stripComments(copyOut.toByteArray()); | |
String outString = stripComments(out.toByteArray()); | |
assertEquals(copyOutString, outString); | |
p.load(in); | |
assertNotNull("Property \"" + key + "\" was null", | |
p.getProperty(key)); | |
assertEquals(val.trim(), p.getProperty(key).trim()); | |
} catch (Throwable ioe) { | |
if (!formattingProps) | |
throw ioe; | |
// bug(1211, ioe, | |
// "Cannot store backslash in FormatPreservingProperties"); | |
throw ioe; | |
} | |
} | |
} | |
static Character randomChar() { | |
char [] TEST_CHAR_ARRAY = new char []{ | |
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', | |
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', | |
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', | |
'2', '3', '4', '5', '6', '7', '8', '9' }; | |
return new Character(TEST_CHAR_ARRAY[ | |
(int) (Math.random() * TEST_CHAR_ARRAY.length)]); | |
} | |
static String randomString(int len) { | |
StringBuffer buf = new StringBuffer(); | |
for (int i = 0; i < (int) (Math.random() * len) + 1; i++) | |
buf.append(randomChar()); | |
return buf.toString(); | |
} | |
public void testEquivalentStore() throws IOException { | |
Properties p1 = new Properties(); | |
FormatPreservingProperties p2 = new FormatPreservingProperties(); | |
((FormatPreservingProperties) p2).setDefaultEntryDelimiter('='); | |
((FormatPreservingProperties) p2).setAddWhitespaceAfterDelimiter(false); | |
String[] values = | |
new String[] { | |
"x", | |
"x" + LS + "y", | |
"x\\ny", | |
"x\ty", | |
"x\\ty", | |
"x\fy", | |
"x\\fy", | |
"x\ry", | |
"x\\ry", | |
"C:\\Foo Bar\\Baz", | |
randomString(5).replace('a', '\\'), | |
randomString(500).replace('a', '\\'), | |
randomString(5000).replace('a', '\\'), | |
}; | |
for (int i = 0; i < values.length; i++) { | |
p1.clear(); | |
p2.clear(); | |
p1.setProperty("xxx", values[i]); | |
p2.setProperty("xxx", values[i]); | |
ByteArrayOutputStream out1 = new ByteArrayOutputStream(); | |
ByteArrayOutputStream out2 = new ByteArrayOutputStream(); | |
p1.store(out1, null); | |
p2.store(out2, null); | |
String s1 = new String(out1.toByteArray()); | |
String s2 = new String(out2.toByteArray()); | |
assertTrue("Expected <" + s1 + "> but was <" + s2 + ">", | |
s1.indexOf(s2) != -1); | |
} | |
} | |
static String stripComments(byte[] bytes) throws IOException { | |
BufferedReader reader = new BufferedReader(new InputStreamReader | |
(new ByteArrayInputStream(bytes))); | |
StringBuffer sbuf = new StringBuffer(); | |
String line; | |
while ((line = reader.readLine()) != null) { | |
// skip comments | |
if (line.trim().startsWith("#")) | |
continue; | |
sbuf.append(line); | |
sbuf.append(LS); | |
} | |
return sbuf.toString(); | |
} | |
public void testDuplicateProperties() throws IOException { | |
FormatPreservingProperties p = new FormatPreservingProperties(); | |
try { | |
toProperties("foo=bar" + LS + "foo=baz", p); | |
fail("expected duplicate keys to cause exception"); | |
} catch (DuplicateKeyException e) { | |
// expected | |
} | |
// now test the expected behavior when duplicates are allowed. | |
p = new FormatPreservingProperties(); | |
p.setAllowDuplicates(true); | |
toProperties("foo=bar" + LS + "foo=baz", p); | |
assertProperties(new String[][]{ { "foo", "baz" } }, p); | |
} | |
public void testMultipleLoads() throws IOException { | |
String props = "foo=bar" + LS + "baz=quux"; | |
String props2 = "a=b" + LS + "c=d"; | |
Properties vanilla = new Properties(); | |
vanilla.load(new BufferedInputStream | |
(new StringBufferInputStream(props))); | |
vanilla.load(new BufferedInputStream | |
(new StringBufferInputStream(props2))); | |
Properties p = new FormatPreservingProperties(); | |
p.load(new BufferedInputStream(new StringBufferInputStream(props))); | |
p.load(new BufferedInputStream(new StringBufferInputStream(props2))); | |
assertPropertiesSame(vanilla, p); | |
} | |
protected FormatPreservingProperties toProperties(String s) | |
throws IOException { | |
return toProperties(s, new FormatPreservingProperties()); | |
} | |
protected FormatPreservingProperties toProperties(String s, | |
FormatPreservingProperties p) throws IOException { | |
Properties vanilla = new Properties(); | |
vanilla.load(new StringBufferInputStream(s)); | |
p.load(new StringBufferInputStream(s)); | |
assertRoundTrip(s, p); | |
assertPropertiesSame(vanilla, p); | |
return p; | |
} | |
private void assertRoundTrip(String s, Properties p) throws IOException { | |
ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
p.store(out, null); | |
assertEquals(s, out.toString()); | |
// also check serializable | |
ByteArrayOutputStream bout = new ByteArrayOutputStream(); | |
new ObjectOutputStream(bout).writeObject(p); | |
try { | |
FormatPreservingProperties deserialized = | |
(FormatPreservingProperties) new ObjectInputStream | |
(new ByteArrayInputStream(bout.toByteArray())). | |
readObject(); | |
assertEquals(p, deserialized); | |
out = new ByteArrayOutputStream(); | |
deserialized.store(out, null); | |
assertEquals(s, out.toString()); | |
} catch (ClassNotFoundException cnfe) { | |
fail(cnfe + ""); | |
} | |
} | |
public static void assertEquals(String expected, String actual) { | |
if (expected == actual) | |
return; | |
if (expected == null || !expected.equals(actual)) | |
fail("Expected <" + expected + "> but was <" + actual + ">"); | |
} | |
private void assertPropertiesSame(Properties vanilla, Properties p) { | |
assertEquals(vanilla, p); | |
} | |
protected void assertProperties(String[][] strings, Properties p) { | |
for (int i = 0; i < strings.length; i++) | |
assertEquals(strings[i][1], p.get(strings[i][0])); | |
assertEquals(strings.length, p.size()); | |
} | |
} |