/**
 *
 * 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.openejb.util;

import junit.framework.TestCase;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

public class PropertiesTest extends TestCase {

    public void testProperties() throws Exception {
        final Properties properties = createProperties();

        // empty
        assertTrue(properties.isEmpty());
        assertEquals(0, properties.size());
        assertTrue(properties.keySet().isEmpty());
        assertEquals(0, properties.keySet().size());
        assertTrue(properties.values().isEmpty());
        assertEquals(0, properties.values().size());
        assertTrue(properties.entrySet().isEmpty());
        assertEquals(0, properties.entrySet().size());
        assertFalse(properties.keys().hasMoreElements());
        assertFalse(properties.propertyNames().hasMoreElements());
        assertFalse(properties.elements().hasMoreElements());
        assertNull(properties.get("foo"));
        assertNull(properties.getProperty("foo"));
        assertEquals("default", properties.getProperty("foo", "default"));

        // one entry
        assertNull(properties.put("foo", "bar"));
        assertFalse(properties.isEmpty());
        assertEquals(1, properties.size());
        assertFalse(properties.keySet().isEmpty());
        assertEquals(1, properties.keySet().size());
        assertEquals(Collections.singleton("foo"), properties.keySet());
        assertFalse(properties.values().isEmpty());
        assertEquals(1, properties.values().size());
        assertFalse(properties.entrySet().isEmpty());
        assertEquals(1, properties.entrySet().size());
        assertTrue(properties.keys().hasMoreElements());
        assertEquals("foo", properties.keys().nextElement());
        assertTrue(properties.propertyNames().hasMoreElements());
        assertEquals("foo", properties.keys().nextElement());
        assertTrue(properties.elements().hasMoreElements());
        assertEquals("bar", properties.get("foo"));
        assertEquals("bar", properties.getProperty("foo"));
        assertEquals("bar", properties.getProperty("foo", "default"));

    }

    public void testSimpleLoad() throws Exception {
        Properties properties;

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo:bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo = bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo : bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo\t \t=\t \tbar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo\t \t:\t \tbar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo:".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo =".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo :".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo = ".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo : ".getBytes()));
        assertEquals(singletonProperty("foo", ""), properties);

        //
        // Invalid key valid separator (results in separator in value)
        //

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo := bar".getBytes()));
        assertEquals(singletonProperty("foo", "= bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo == bar".getBytes()));
        assertEquals(singletonProperty("foo", "= bar"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo :: bar".getBytes()));
        assertEquals(singletonProperty("foo", ": bar"), properties);

    }

    public void testUnicode() throws Exception {
        Properties properties = createProperties();
        properties.load(new ByteArrayInputStream("a=\\u1234z".getBytes()));

        try {
            properties = createProperties();
            properties.load(new ByteArrayInputStream("a=\\u123".getBytes()));
            fail("Expected IllegalArgumentException due to invalid unicode sequence");
        } catch (final IllegalArgumentException expected) {
        }

        try {
            properties = createProperties();
            properties.load(new ByteArrayInputStream("a=\\u123z".getBytes()));
            fail("Expected IllegalArgumentException due to invalid unicode sequence");
        } catch (final IllegalArgumentException expected) {
        }

        properties = new SuperProperties();
        properties.load(new ByteArrayInputStream("a=\\".getBytes()));
        assertEquals(singletonProperty("a", "\u0000"), properties);

        properties = createProperties();
        properties.load(new ByteArrayInputStream("a=\\q".getBytes()));
        assertEquals(singletonProperty("a", "q"), properties);
    }

    public void testKeyLineContinuation() throws Exception {
        Properties properties;

        // line continuation (\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\noo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation with white space (\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\n  oo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\n\t\too=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\n \t oo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\roo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r) with white space
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r  oo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r\t\too=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r \t oo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r\noo=bar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        //
        // Invalid contunuations (results in a break)
        //

        // line continuation (\n\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\n\noo=bar".getBytes()));
        assertEquals(properties("f", "", "oo", "bar"), properties);

        // line continuation (\r\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r\roo=bar".getBytes()));
        assertEquals(properties("f", "", "oo", "bar"), properties);

        // line continuation (\r\n\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r\n\noo=bar".getBytes()));
        assertEquals(properties("f", "", "oo", "bar"), properties);

        // line continuation (\r\n\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("f\\\r\n\roo=bar".getBytes()));
        assertEquals(properties("f", "", "oo", "bar"), properties);
    }

    public void testValueLineContinuation() throws Exception {
        Properties properties;

        // line continuation (\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\nar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation with white space (\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\n  ar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\n\t\tar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\n \t ar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\rar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r) with white space
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r  ar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r\t\tar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r \t ar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        // line continuation (\r\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r\nar".getBytes()));
        assertEquals(singletonProperty("foo", "bar"), properties);

        //
        // Invalid contunuations (results in a break)
        //

        // line continuation (\n\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\n\nar".getBytes()));
        assertEquals(properties("foo", "b", "ar", ""), properties);

        // line continuation (\r\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r\rar".getBytes()));
        assertEquals(properties("foo", "b", "ar", ""), properties);

        // line continuation (\r\n\n)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r\n\nar".getBytes()));
        assertEquals(properties("foo", "b", "ar", ""), properties);

        // line continuation (\r\n\r)
        properties = createProperties();
        properties.load(new ByteArrayInputStream("foo=b\\\r\n\rar".getBytes()));
        assertEquals(properties("foo", "b", "ar", ""), properties);
    }

    public void testEscapedKeyValueSeparators() throws Exception {
        Properties properties = createProperties();

        // test put and get
        properties.put("\t\r\n my: \t\n\rkey=", "foo");
        assertEquals("foo", properties.get("\t\r\n my: \t\n\rkey="));

        // test store
        String text = store(properties);
        if (text.startsWith("#")) text = text.split("\\n", 2)[1];
        text = text.trim();
        assertEquals("\\t\\r\\n\\ my\\:\\ \\t\\n\\rkey\\==foo", text);

        // test load
        properties = createProperties();
        properties.load(new ByteArrayInputStream("\\t\\r\\n\\ my\\:\\ \\t\\n\\rkey\\==foo".getBytes()));
        assertEquals("foo", properties.get("\t\r\n my: \t\n\rkey="));
    }

    protected Properties createProperties() {
        return new Properties();
    }

    protected static Properties singletonProperty(final String key, final String value) {
        return properties(key, value);
    }

    protected static Properties properties(final String... keysAndValues) {
        final Properties properties = new Properties();
        for (int i = 0; i + 1 < keysAndValues.length; i += 2) {
            final String key = keysAndValues[i];
            final String value = keysAndValues[i + 1];
            properties.put(key, value);
        }
        return properties;
    }

    @SuppressWarnings({"unchecked"})
    protected static void assertProperties(final Properties expected, final Properties actual) {
        if (expected.equals(actual)) return;

        final StringBuilder message = new StringBuilder().append("\n");

        final Set<String> keys = new TreeSet<>();
        keys.addAll(new HashSet(expected.keySet()));
        keys.addAll(new HashSet(actual.keySet()));
        for (final String key : keys) {
            if (!expected.containsKey(key)) {
                message.append("A ").append(key).append("=").append(actual.get(key)).append("\n");
            } else if (!actual.containsKey(key)) {
                message.append("R ").append(key).append("=").append(expected.get(key)).append("\n");
            } else {
                final Object expectedValue = expected.get(key);
                final Object actualValue = actual.get(key);
                if (actualValue != expectedValue && (expectedValue == null || !expectedValue.equals(actualValue))) {
                    message.append("C ").append(key).append("=").append(expectedValue).append("\n");
                    message.append("  ").append(key).append("=").append(actualValue).append("\n");
                }

            }
        }

        fail(message.toString());
    }

    protected String store(final Properties properties) throws IOException {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        properties.store(out, null);
        return new String(out.toByteArray());
    }
}
