/* * 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()); } }