/*
 * 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.commons.configuration2.tree;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.NoSuchElementException;

import org.junit.Before;
import org.junit.Test;

/**
 * Test class for DefaultConfigurationKey.
 *
 */
public class TestDefaultConfigurationKey
{
    /** Constant for a test key. */
    private static final String TESTPROPS = "tables.table(0).fields.field(1)";

    /** Constant for a test attribute key. */
    private static final String TESTATTR = "[@dataType]";

    /** Constant for a complex attribute key. */
    private static final String TESTKEY = TESTPROPS + TESTATTR;

    /** Stores the expression engine of the key to test. */
    private DefaultExpressionEngine expressionEngine;

    /** Stores the object to be tested. */
    private DefaultConfigurationKey key;

    @Before
    public void setUp() throws Exception
    {
        expressionEngine = DefaultExpressionEngine.INSTANCE;
        key = new DefaultConfigurationKey(expressionEngine);
    }

    /**
     * Helper method to create a key instance with the given content.
     *
     * @param k the key for initialization
     * @return the newly created {@code DefaultConfigurationKey} instance
     */
    private DefaultConfigurationKey key(final String k)
    {
        return new DefaultConfigurationKey(expressionEngine, k);
    }

    /**
     * Tests setting the expression engine to null. This should not be allowed.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testSetNullExpressionEngine()
    {
        new DefaultConfigurationKey(null);
    }

    /**
     * Tests the isAttributeKey() method with several keys.
     */
    @Test
    public void testIsAttributeKey()
    {
        assertTrue("Attribute key not detected", key.isAttributeKey(TESTATTR));
        assertFalse("Property key considered as attribute", key
                .isAttributeKey(TESTPROPS));
        assertFalse("Null key considered as attribute", key
                .isAttributeKey(null));
    }

    /**
     * Returns a builder for symbols with default property settings.
     *
     * @return the initialized builder object
     */
    private DefaultExpressionEngineSymbols.Builder symbols()
    {
        return new DefaultExpressionEngineSymbols.Builder(
                expressionEngine.getSymbols());
    }

    /**
     * Tests if attribute keys are correctly detected if no end markers are set.
     * (In this test case we use the same delimiter for attributes as for simple
     * properties.)
     */
    @Test
    public void testIsAttributeKeyWithoutEndMarkers()
    {
        final DefaultExpressionEngineSymbols symbols =
                symbols()
                        .setAttributeEnd(null)
                        .setAttributeStart(
                                DefaultExpressionEngineSymbols.DEFAULT_PROPERTY_DELIMITER)
                        .create();
        expressionEngine = new DefaultExpressionEngine(symbols);
        key = new DefaultConfigurationKey(expressionEngine);
        assertTrue(
                "Attribute key not detected",
                key.isAttributeKey(DefaultExpressionEngineSymbols.DEFAULT_PROPERTY_DELIMITER
                        + "test"));
        assertFalse("Property key considered as attribute key",
                key.isAttributeKey(TESTATTR));
    }

    /**
     * Tests removing leading delimiters.
     */
    @Test
    public void testTrimLeft()
    {
        assertEquals("Key was not left trimmed", "test.", key
                .trimLeft(".test."));
        assertEquals("Too much left trimming", "..test.", key
                .trimLeft("..test."));
    }

    /**
     * Tests removing trailing delimiters.
     */
    @Test
    public void testTrimRight()
    {
        assertEquals("Key was not right trimmed", ".test", key
                .trimRight(".test."));
        assertEquals("Too much right trimming", ".test..", key
                .trimRight(".test.."));
    }

    /**
     * Tests removing delimiters.
     */
    @Test
    public void testTrim()
    {
        assertEquals("Key was not trimmed", "test", key.trim(".test."));
        assertEquals("Null key could not be processed", "", key.trim(null));
        assertEquals("Delimiter could not be processed", "", key
                .trim(DefaultExpressionEngineSymbols.DEFAULT_PROPERTY_DELIMITER));
    }

    /**
     * Tests appending keys.
     */
    @Test
    public void testAppend()
    {
        key.append("tables").append("table(0).");
        key.append("fields.").append("field(1)");
        key.append(null).append(TESTATTR);
        assertEquals("Wrong key", TESTKEY, key.toString());
    }

    /**
     * Tests appending keys that contain delimiters.
     */
    @Test
    public void testAppendDelimiters()
    {
        key.append("key..").append("test").append(".");
        key.append(".more").append("..tests");
        assertEquals("Wrong key", "key...test.more...tests", key.toString());
    }

    /**
     * Tests appending keys that contain delimiters when no escaped delimiter
     * is defined.
     */
    @Test
    public void testAppendDelimitersWithoutEscaping()
    {
        expressionEngine =
                new DefaultExpressionEngine(symbols().setEscapedDelimiter(null)
                        .create());
        key = new DefaultConfigurationKey(expressionEngine);
        key.append("key.......").append("test").append(".");
        key.append(".more").append("..tests");
        assertEquals("Wrong constructed key", "key.test.more.tests", key
                .toString());
    }

    /**
     * Tests calling append with the escape flag.
     */
    @Test
    public void testAppendWithEscapeFlag()
    {
        key.append(".key.test.", true);
        key.append(".more").append(".tests", true);
        assertEquals("Wrong constructed key", "..key..test...more...tests", key
                .toString());
    }

    /**
     * Tests constructing keys for attributes.
     */
    @Test
    public void testConstructAttributeKey()
    {
        assertEquals("Wrong attribute key", TESTATTR, key
                .constructAttributeKey("dataType"));
        assertEquals("Attribute key was incorrectly converted", TESTATTR, key
                .constructAttributeKey(TESTATTR));
        assertEquals("Null key could not be processed", "", key
                .constructAttributeKey(null));
    }

    /**
     * Tests constructing attribute keys when no end markers are defined. In
     * this test case we use the property delimiter as attribute prefix.
     */
    @Test
    public void testConstructAttributeKeyWithoutEndMarkers()
    {
        final DefaultExpressionEngineSymbols symbols =
                symbols()
                        .setAttributeEnd(null)
                        .setAttributeStart(
                                expressionEngine.getSymbols()
                                        .getPropertyDelimiter()).create();
        expressionEngine = new DefaultExpressionEngine(symbols);
        key = new DefaultConfigurationKey(expressionEngine);
        assertEquals("Wrong attribute key", ".test", key
                .constructAttributeKey("test"));
        assertEquals("Attribute key was incorrectly converted", ".test", key
                .constructAttributeKey(".test"));
    }

    /**
     * Tests appending attribute keys.
     */
    @Test
    public void testAppendAttribute()
    {
        key.appendAttribute("dataType");
        assertEquals("Attribute key not correctly appended", TESTATTR, key
                .toString());
    }

    /**
     * Tests appending an attribute key that is already decorated-
     */
    @Test
    public void testAppendDecoratedAttributeKey()
    {
        key.appendAttribute(TESTATTR);
        assertEquals("Decorated attribute key not correctly appended",
                TESTATTR, key.toString());
    }

    /**
     * Tests appending a null attribute key.
     */
    @Test
    public void testAppendNullAttributeKey()
    {
        key.appendAttribute(null);
        assertEquals("Null attribute key not correctly appended", "", key
                .toString());
    }

    /**
     * Tests appending an index to a key.
     */
    @Test
    public void testAppendIndex()
    {
        key.append("test").appendIndex(42);
        assertEquals("Index was not correctly appended", "test(42)", key
                .toString());
    }

    /**
     * Tests constructing a complex key by chaining multiple append operations.
     */
    @Test
    public void testAppendComplexKey()
    {
        key.append("tables").append("table.").appendIndex(0);
        key.append("fields.").append("field").appendIndex(1);
        key.appendAttribute("dataType");
        assertEquals("Wrong complex key", TESTKEY, key.toString());
    }

    /**
     * Tests getting and setting the key's length.
     */
    @Test
    public void testLength()
    {
        key.append(TESTPROPS);
        assertEquals("Wrong length", TESTPROPS.length(), key.length());
        key.appendAttribute("dataType");
        assertEquals("Wrong length", TESTKEY.length(), key.length());
        key.setLength(TESTPROPS.length());
        assertEquals("Wrong length after shortening", TESTPROPS.length(), key
                .length());
        assertEquals("Wrong resulting key", TESTPROPS, key.toString());
    }

    /**
     * Tests comparing configuration keys.
     */
    @Test
    public void testEquals()
    {
        final DefaultConfigurationKey k1 = key(TESTKEY);
        assertTrue("Key not equal to itself", k1.equals(k1));
        final DefaultConfigurationKey k2 = key(TESTKEY);
        assertTrue("Keys are not equal", k1.equals(k2));
        assertTrue("Not reflexiv", k2.equals(k1));
        assertEquals("Hash codes not equal", k1.hashCode(), k2.hashCode());
        k2.append("anotherPart");
        assertFalse("Keys considered equal", k1.equals(k2));
        assertFalse("Keys considered equal (2)", k2.equals(k1));
        assertFalse("Key equals null key", k1.equals(null));
        assertFalse("Equal with string", k1.equals(TESTKEY));
    }

    /**
     * Tests determining an attribute key's name.
     */
    @Test
    public void testAttributeName()
    {
        assertEquals("Plain key not detected", "test", key
                .attributeName("test"));
        assertEquals("Attribute markers not stripped", "dataType", key
                .attributeName(TESTATTR));
        assertNull("Null key not processed", key.attributeName(null));
    }

    /**
     * Tests to iterate over a simple key.
     */
    @Test
    public void testIterate()
    {
        key.append(TESTKEY);
        final DefaultConfigurationKey.KeyIterator it = key.iterator();
        assertTrue("No key parts", it.hasNext());
        assertEquals("Wrong key part", "tables", it.nextKey());
        assertEquals("Wrong key part", "table", it.nextKey());
        assertTrue("No index found", it.hasIndex());
        assertEquals("Wrong index", 0, it.getIndex());
        assertEquals("Wrong key part", "fields", it.nextKey());
        assertFalse("Found an index", it.hasIndex());
        assertEquals("Wrong key part", "field", it.nextKey(true));
        assertEquals("Wrong index", 1, it.getIndex());
        assertFalse("Found an attribute", it.isAttribute());
        assertEquals("Wrong current key", "field", it.currentKey(true));
        assertEquals("Wrong key part", "dataType", it.nextKey());
        assertEquals("Wrong decorated key part", "[@dataType]", it
                .currentKey(true));
        assertTrue("Attribute not found", it.isAttribute());
        assertFalse("Too many key parts", it.hasNext());
        try
        {
            it.next();
            fail("Could iterate over the iteration's end!");
        }
        catch (final NoSuchElementException nex)
        {
            // ok
        }
    }

    /**
     * Tests an iteration where the remove() method is called. This is not
     * supported.
     */
    @Test(expected = UnsupportedOperationException.class)
    public void testIterateWithRemove()
    {
        assertFalse(key.iterator().hasNext());
        key.append("simple");
        final DefaultConfigurationKey.KeyIterator it = key.iterator();
        assertTrue(it.hasNext());
        assertEquals("simple", it.next());
        it.remove();
    }

    /**
     * Tests iterating over some funny keys.
     */
    @Test
    public void testIterateStrangeKeys()
    {
        key = new DefaultConfigurationKey(expressionEngine, "key.");
        DefaultConfigurationKey.KeyIterator it = key.iterator();
        assertTrue("Too few key parts", it.hasNext());
        assertEquals("Wrong key part", "key", it.next());
        assertFalse("Too many key parts", it.hasNext());

        key = new DefaultConfigurationKey(expressionEngine, ".");
        it = key.iterator();
        assertFalse("Simple delimiter key has more parts", it.hasNext());

        key = new DefaultConfigurationKey(expressionEngine,
                "key().index()undefined(0).test");
        it = key.iterator();
        assertEquals("Wrong first part", "key()", it.next());
        assertFalse("Index detected in first part", it.hasIndex());
        assertEquals("Wrong second part", "index()undefined", it.nextKey(false));
        assertTrue("No index detected in second part", it.hasIndex());
        assertEquals("Wrong index value", 0, it.getIndex());
    }

    /**
     * Tests iterating over keys with escaped delimiters.
     */
    @Test
    public void testIterateEscapedDelimiters()
    {
        key.append("my..elem");
        key.append("trailing..dot..");
        key.append(".strange");
        assertEquals("my..elem.trailing..dot...strange", key.toString());
        final DefaultConfigurationKey.KeyIterator kit = key.iterator();
        assertEquals("Wrong first part", "my.elem", kit.nextKey());
        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
        assertEquals("Wrong third part", "strange", kit.nextKey());
        assertFalse("Too many parts", kit.hasNext());
    }

    /**
     * Tests iterating over keys when a different escaped delimiter is used.
     */
    @Test
    public void testIterateAlternativeEscapeDelimiter()
    {
        expressionEngine =
                new DefaultExpressionEngine(symbols()
                        .setEscapedDelimiter("\\.").create());
        key = new DefaultConfigurationKey(expressionEngine);
        key.append("\\.my\\.elem");
        key.append("trailing\\.dot\\.");
        key.append(".strange");
        assertEquals("\\.my\\.elem.trailing\\.dot\\..strange", key.toString());
        final DefaultConfigurationKey.KeyIterator kit = key.iterator();
        assertEquals("Wrong first part", ".my.elem", kit.nextKey());
        assertEquals("Wrong second part", "trailing.dot.", kit.nextKey());
        assertEquals("Wrong third part", "strange", kit.nextKey());
        assertFalse("Too many parts", kit.hasNext());
    }

    /**
     * Tests iterating when no escape delimiter is defined.
     */
    @Test
    public void testIterateWithoutEscapeDelimiter()
    {
        expressionEngine =
                new DefaultExpressionEngine(symbols()
                        .setEscapedDelimiter(null).create());
        key = new DefaultConfigurationKey(expressionEngine);
        key.append("..my..elem.trailing..dot...strange");
        assertEquals("Wrong key", "my..elem.trailing..dot...strange", key
                .toString());
        final DefaultConfigurationKey.KeyIterator kit = key.iterator();
        final String[] parts =
        { "my", "elem", "trailing", "dot", "strange"};
        for (int i = 0; i < parts.length; i++)
        {
            assertEquals("Wrong key part " + i, parts[i], kit.next());
        }
        assertFalse("Too many parts", kit.hasNext());
    }

    /**
     * Tests whether a key with brackets in it can be iterated over.
     */
    @Test
    public void testIterateWithBrackets()
    {
        key.append("directory.platform(x86).path");
        final DefaultConfigurationKey.KeyIterator kit = key.iterator();
        String part = kit.nextKey();
        assertEquals("Wrong part 1", "directory", part);
        assertFalse("Has index 1", kit.hasIndex());
        part = kit.nextKey();
        assertEquals("Wrong part 2", "platform(x86)", part);
        assertFalse("Has index 2", kit.hasIndex());
        part = kit.nextKey();
        assertEquals("Wrong part 3", "path", part);
        assertFalse("Has index 3", kit.hasIndex());
        assertFalse("Too many elements", kit.hasNext());
    }

    /**
     * Tests iterating over an attribute key that has an index.
     */
    @Test
    public void testAttributeKeyWithIndex()
    {
        key.append(TESTATTR);
        key.appendIndex(0);
        assertEquals("Wrong attribute key with index", TESTATTR + "(0)", key
                .toString());

        final DefaultConfigurationKey.KeyIterator it = key.iterator();
        assertTrue("No first element", it.hasNext());
        it.next();
        assertTrue("Index not found", it.hasIndex());
        assertEquals("Incorrect index", 0, it.getIndex());
        assertTrue("Attribute not found", it.isAttribute());
        assertEquals("Wrong plain key", "dataType", it.currentKey(false));
        assertEquals("Wrong decorated key", TESTATTR, it.currentKey(true));
    }

    /**
     * Tests iteration when the attribute markers equals the property delimiter.
     */
    @Test
    public void testIterateAttributeEqualsPropertyDelimiter()
    {
        expressionEngine =
                new DefaultExpressionEngine(
                        symbols()
                                .setAttributeEnd(null)
                                .setAttributeStart(
                                        DefaultExpressionEngineSymbols.DEFAULT_PROPERTY_DELIMITER)
                                .create());
        key = new DefaultConfigurationKey(expressionEngine);
        key.append("this.isa.key");
        final DefaultConfigurationKey.KeyIterator kit = key.iterator();
        assertEquals("Wrong first key part", "this", kit.next());
        assertFalse("First part is an attribute", kit.isAttribute());
        assertTrue("First part is not a property key", kit.isPropertyKey());
        assertEquals("Wrong second key part", "isa", kit.next());
        assertFalse("Second part is an attribute", kit.isAttribute());
        assertTrue("Second part is not a property key", kit.isPropertyKey());
        assertEquals("Wrong third key part", "key", kit.next());
        assertTrue("Third part is not an attribute", kit.isAttribute());
        assertTrue("Third part is not a property key", kit.isPropertyKey());
        assertEquals("Wrong decorated key part", "key", kit.currentKey(true));
    }

    /**
     * Tests whether common key parts can be extracted.
     */
    @Test
    public void testCommonKey()
    {
        final DefaultConfigurationKey k1 = key(TESTKEY);
        DefaultConfigurationKey k2 = key("tables.table(0).name");
        DefaultConfigurationKey kc = k1.commonKey(k2);
        assertEquals("Wrong common key (1)", key("tables.table(0)"), kc);
        assertEquals("Not symmetric", kc, k2.commonKey(k1));

        k2 = key("tables.table(1).fields.field(1)");
        kc = k1.commonKey(k2);
        assertEquals("Wrong common key (2)", key("tables"), kc);

        k2 = key("completely.different.key");
        kc = k1.commonKey(k2);
        assertEquals("Got a common key for different keys", 0, kc.length());

        kc = k1.commonKey(key);
        assertEquals("Got a common key for empty key", 0, kc.length());

        kc = k1.commonKey(k1);
        assertEquals("Wrong result for reflexiv invocation", kc, k1);
    }

    /**
     * Tries to call commonKey() with null input.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testCommonKeyNull()
    {
        key.commonKey(null);
    }

    /**
     * Tests differenceKey() on the same object.
     */
    @Test
    public void testDifferenceKeySame()
    {
        final DefaultConfigurationKey k1 = key(TESTKEY);
        final DefaultConfigurationKey kd = k1.differenceKey(k1);
        assertEquals("Got difference for same keys", 0, kd.length());
    }

    /**
     * Tests the differenceKey() method.
     */
    @Test
    public void testDifferenceKey()
    {
        final DefaultConfigurationKey k1 = key(TESTKEY);
        DefaultConfigurationKey k2 = key("tables.table(0).name");
        DefaultConfigurationKey kd = k1.differenceKey(k2);
        assertEquals("Wrong difference (1)", "name", kd.toString());

        k2 = key("tables.table(1).fields.field(1)");
        kd = k1.differenceKey(k2);
        assertEquals("Wrong difference (2)", "table(1).fields.field(1)", kd.toString());

        k2 = key("completely.different.key");
        kd = k1.differenceKey(k2);
        assertEquals("Wrong difference (3)", k2, kd);
    }
}
