/*
 * Licensed 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.hobsoft.microbrowser.tck;

import java.net.MalformedURLException;

import org.hamcrest.Matcher;
import org.hobsoft.microbrowser.MicrodataDocument;
import org.junit.Test;

import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.RecordedRequest;

import static org.hamcrest.CoreMatchers.is;
import static org.hobsoft.microbrowser.tck.support.MicrobrowserMatchers.item;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.takeRequest;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.url;
import static org.junit.Assert.assertThat;

/**
 * TCK for {@code Form.submit} using a specific HTTP method.
 */
public abstract class FormMethodTck extends AbstractMicrobrowserTest
{
	// ----------------------------------------------------------------------------------------------------------------
	// submit tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void submitSubmitsRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/x'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/x")));
	}

	@Test
	public void submitSubmitsHiddenControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='hidden' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsTextControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='text' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsTextControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='text' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "x")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsPasswordControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='password' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsPasswordControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='password' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "x")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsCheckedCheckboxControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=on")));
	}

	@Test
	public void submitSubmitsCheckedCheckboxControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "on")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=on")));
	}

	@Test
	public void submitDoesNotSubmitUncheckedCheckboxControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitDoesNotSubmitUncheckedCheckboxControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitSubmitsValuedCheckedCheckboxControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' value='x' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsValuedCheckedCheckboxControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "x")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitDoesNotSubmitValuedUncheckedCheckboxControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitDoesNotSubmitValuedUncheckedCheckboxControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='checkbox' name='c' value='x' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitSubmitsCheckedRadioControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=on")));
	}

	@Test
	public void submitSubmitsCheckedRadioControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "on")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=on")));
	}

	@Test
	public void submitSubmitsCheckedRadioControlsLastValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' checked/>"
			+ "<input type='radio' name='c' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=on")));
	}

	@Test
	public void submitDoesNotSubmitUncheckedRadioControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitSubmitsValuedCheckedRadioControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' value='x' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsValuedCheckedRadioControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "x")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsValuedCheckedRadioControlsLastValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' value='x' checked/>"
			+ "<input type='radio' name='c' value='y' checked/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=y")));
	}

	@Test
	public void submitDoesNotSubmitValuedUncheckedRadioControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='radio' name='c' value='x'/>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "")));
	}

	@Test
	public void submitSubmitsTextAreaControlInitialValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<textarea name='c'>x</textarea>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSubmitsTextAreaControlSetValue() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<textarea name='c'></textarea>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.setControlValue("c", "x")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(method("/a", "c=x")));
	}

	@Test
	public void submitSetsCookie()
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse().addHeader("Set-Cookie", "x=y"));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.submit()
			.getCookie("x");

		assertThat("cookie", actual, is("y"));
	}

	@Test
	public void submitSendsCookie() throws InterruptedException
	{
		server().enqueue(new MockResponse().addHeader("Set-Cookie", "x=y").setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("cookie", takeRequest(server()).getHeader("Cookie"), is("x=y"));
	}

	@Test
	public void submitSendsPreviousCookie() throws InterruptedException
	{
		server().enqueue(new MockResponse().addHeader("Set-Cookie", "x=y").setBody(""
			+ "a"
			+ ""));
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getLink("r")
			.follow()
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("cookie", takeRequest(server()).getHeader("Cookie"), is("x=y"));
	}

	@Test
	public void submitReturnsResponse() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(String.format(""
			+ "<form name='f' method='%s' action='/a'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ "", getMethod())));
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""));

		MicrodataDocument actual = newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		assertThat("response", actual.getItem("http://i"), is(item("http://x")));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// protected methods
	// ----------------------------------------------------------------------------------------------------------------

	protected abstract String getMethod();

	protected abstract Matcher<RecordedRequest> method(String expectedPath);

	protected abstract Matcher<RecordedRequest> method(String expectedPath, String expectedData);
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.aima.logic.fol;

import com.thesett.common.util.logic.UnaryPredicate;

/**
 * FunctorTermPredicate is a unary predicate over logical terms that picks out all terms that are free non-anonymous
 * variables. It is generally used for conditioning the results of unification or resolution procedures that produce
 * listings of all bound variables resulting from a unification or resolution. By first creating the set of variables
 * that are free and non-anonymous in a query, the results may then be filtered to report only these bindings.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Determines whether a term is a free non-anonymous variable or not.
 *

 *
 * @author Rupert Smith
 */
public class FreeNonAnonymousVariablePredicate implements UnaryPredicate<Term>
{
    /**
     * Determine whether a term is a free variable.
     *
     * @param  term The object to test for predicate membership.
     *
     * @return <tt>true</tt> if the term is a free variable, <tt>false</tt> otherwise.
     */
    public boolean evaluate(Term term)
    {
        if (term.isVar() && (term instanceof Variable))
        {
            Variable var = (Variable) term;

            return !var.isBound() && !var.isAnonymous();
        }

        return false;
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.aima.logic.fol.wam.optimizer;

import java.util.List;

import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import com.thesett.aima.logic.fol.wam.compiler.WAMOptimizeableListing;
import com.thesett.common.util.SizeableLinkedList;
import com.thesett.common.util.SizeableList;
import com.thesett.common.util.doublemaps.SymbolTable;

/**
 * WAMOptimizer performs peephole optimization on a list of WAM instructions.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Optimize a WAM instruction listing.
 *

 *
 * @author Rupert Smith
 */
public class WAMOptimizer implements Optimizer
{
    /** The symbol table. */
    protected final SymbolTable<Integer, String, Object> symbolTable;

    /** Holds the variable and functor name interner for the machine. */
    private final VariableAndFunctorInterner interner;

    /**
     * Builds a WAM instruction optimizer.
     *
     * @param symbolTable The symbol table to get instruction analysis information from.
     * @param interner    The variable and functor name interner for the machine.
     */
    public WAMOptimizer(SymbolTable<Integer, String, Object> symbolTable, VariableAndFunctorInterner interner)
    {
        this.symbolTable = symbolTable;
        this.interner = interner;
    }

    /**
     * {@inheritDoc}
     *
     *
Runs the instruction listing through an optimizer pass, and replaces the original instruction listing with
     * the optimized version.
     */
    public <T extends WAMOptimizeableListing> T apply(WAMOptimizeableListing listing)
    {
        SizeableList<WAMInstruction> optListing = optimize(listing.getInstructions());
        listing.setOptimizedInstructions(optListing);

        return (T) listing;
    }

    /**
     * Performs an optimization pass for specialized instructions.
     *
     *
The following instruction sequences can be optimized:
     *
     *

     * unify_var Xi, get_struc a/0, Xi -> unify_const a/0
     * get_struc a/0, Xi -> get_const a/0
     * put_struc a/0, Xi, set_var Xi -> set_const a/0
     * put_struc a/0, Xi -> put_const a/0
     *

     *
     * @param  instructions The instructions to optimize.
     *
     * @return An list of optimized instructions.
     */
    private SizeableList<WAMInstruction> optimize(List<WAMInstruction> instructions)
    {
        StateMachine optimizeConstants = new OptimizeInstructions(symbolTable, interner);
        Iterable<WAMInstruction> matcher =
            new Matcher<WAMInstruction, WAMInstruction>(instructions.iterator(), optimizeConstants);

        SizeableList<WAMInstruction> result = new SizeableLinkedList<WAMInstruction>();

        for (WAMInstruction instruction : matcher)
        {
            result.add(instruction);
        }

        return result;
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.common.util;

import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;

/**
 * Compares collections element by element using a specified comparator over the elements, in order to determine the
 * lexicographic ordering of the collections. The collections iterator determines the order in which its elements are
 * scanned.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Lexicographically order two collections.
 *

 *
 * @author Rupert Smith
 */
public class LexicographicalCollectionComparator<T> implements Comparator<Collection<T>>
{
    /** Holds the element comparator for comparing elements of collections. */
    Comparator<T> comparator;

    /**
     * Creates a lexicographic ordering comparator over collections base on an underlying comparator for the elements of
     * the collections.
     *
     * @param comparator The element comparator.
     */
    public LexicographicalCollectionComparator(Comparator<T> comparator)
    {
        // Keep the element comparator.
        this.comparator = comparator;
    }

    /**
     * Compares two collections using lexicographic ordering based on a comparator of their elements.
     *
     * @param  c1 The first collection to compare.
     * @param  c2 The second collection to compare.
     *
     * @return A negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater
     *         than the second.
     */
    public int compare(Collection<T> c1, Collection<T> c2)
    {
        // Simultaneously iterator over both collections until one runs out.
        Iterator<T> i1 = c1.iterator();
        Iterator<T> i2 = c2.iterator();

        while (i1.hasNext() && i2.hasNext())
        {
            T t1 = i1.next();
            T t2 = i2.next();

            // Compare t1 and t2.
            int comp = comparator.compare(t1, t2);

            // Check if t1 < t2 in which case c1 < c2.
            if (comp < 0)
            {
                return -1;
            }

            // Check if t2 < t1 in which case c2 < c1.
            else if (comp > 0)
            {
                return 1;
            }

            // Otherwise t1 = t2 in which case further elements must be examined in order to determine the ordering.
        }

        // If this point is reached then one of the collections ran out of elements before the ordering was determined.

        // Check if c1 ran out and c2 still has elements, in which case c1 < c2.
        if (!i1.hasNext() && i2.hasNext())
        {
            return -1;
        }

        // Check if c2 ran out and c1 still has elements, in which case c2 < c1.
        if (i1.hasNext() && !i2.hasNext())
        {
            return 1;
        }

        // Otherwise both ran out in which case c1 = c2.
        return 0;
    }

    /**
     * Checks if another comparator is the same as this one.
     *
     * @param  o The object to compare to.
     *
     * @return <tt>true</tt> only if the specified object is also a comparator and it imposes the same ordering as this
     *         comparator.
     */
    public boolean equals(Object o)
    {
        return (o instanceof LexicographicalCollectionComparator) &&
            ((LexicographicalCollectionComparator) o).comparator.equals(comparator);
    }
}


/*
 * Licensed 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.hobsoft.microbrowser.tck;

import org.hobsoft.microbrowser.MicrodataProperty;
import org.junit.Ignore;
import org.junit.Test;

import com.squareup.okhttp.mockwebserver.MockResponse;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.url;
import static org.junit.Assert.assertThat;

/**
 * TCK for {@code MicrodataProperty}.
 */
public abstract class MicrodataPropertyTck extends AbstractMicrobrowserTest
{
	// ----------------------------------------------------------------------------------------------------------------
	// getName tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getNameReturnsName()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("x")
			.getName();

		assertThat("item property name", actual, is("x"));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getValueWhenMetaReturnsContent()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<meta itemprop='p' content='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("x"));
	}

	@Test
	public void getValueWhenMetaAndNoContentReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<meta itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenAudioAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<audio itemprop='p' src='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenAudioAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<audio itemprop='p' src='x'/>"
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenAudioAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<audio itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenEmbedAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<embed itemprop='p' src='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenEmbedAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<embed itemprop='p' src='x'/>"
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenEmbedAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<embed itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenIframeAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenIframeAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenIframeAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenImgAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenImgAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenImgAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenSourceAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<source itemprop='p' src='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenSourceAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<source itemprop='p' src='x'/>"
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenSourceAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<source itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenTrackAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<track itemprop='p' src='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	// TODO: fix for Selenium
	@Ignore
	@Test
	public void getValueWhenTrackAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<track itemprop='p' src='x'/>"
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenTrackAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<track itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenVideoAndAbsoluteSrcReturnsSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<video itemprop='p' src='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenVideoAndRelativeSrcReturnsAbsoluteSrc()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<video itemprop='p' src='x'/>"
			+ "
"
			+ ""));
		server().enqueue(new MockResponse());

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenVideoAndNoSrcReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<video itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenAnchorAndAbsoluteHrefReturnsUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenAnchorAndRelativeHrefReturnsAbsoluteUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenAnchorAndNoHrefReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenAreaAndAbsoluteHrefReturnsUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<area itemprop='p' href='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenAreaAndRelativeHrefReturnsAbsoluteUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<area itemprop='p' href='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenAreaAndNoHrefReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<area itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenLinkAndAbsoluteHrefReturnsUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<link itemprop='p' href='http://x/'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("http://x/"));
	}

	@Test
	public void getValueWhenLinkAndRelativeHrefReturnsAbsoluteUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<link itemprop='p' href='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenLinkAndNoHrefReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<link itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	// TODO: fix for Selenium
	@Ignore
	@Test
	public void getValueWhenObjectReturnsAbsoluteUrl()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<object itemprop='p' data='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, equalToIgnoringCase(url(server(), "/x")));
	}

	@Test
	public void getValueWhenObjectAndNoDataReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<object itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenDataReturnsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<data itemprop='p' value='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("x"));
	}

	@Test
	public void getValueWhenDataAndNoValueReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<data itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenMeterReturnsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<meter itemprop='p' value='1'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("1"));
	}

	@Test
	public void getValueWhenMeterAndNoValueReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<meter itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("0"));
	}

	@Test
	public void getValueWhenTimeReturnsDatetime()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<time itemprop='p' datetime='x'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("x"));
	}

	@Test
	public void getValueWhenTimeAndNoDatetimeReturnsText()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<time itemprop='p'>x</time>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("x"));
	}

	@Test
	public void getValueWhenTimeAndNoTextReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "<time itemprop='p'/>"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenUnknownReturnsText()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, is("x"));
	}

	@Test
	public void getValueWhenUnknownAndNoTextReturnsEmpty()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getValue();

		assertThat("item property value", actual, isEmptyString());
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getBooleanValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getBooleanValueWhenTrueReturnsTrue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
true
"
			+ "
"
			+ ""));

		boolean actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getBooleanValue();

		assertThat("item property value", actual, is(true));
	}

	@Test
	public void getBooleanValueWhenFalseReturnsFalse()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
false
"
			+ "
"
			+ ""));

		boolean actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getBooleanValue();

		assertThat("item property value", actual, is(false));
	}

	@Test
	public void getBooleanValueWhenEmptyReturnsFalse()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		boolean actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getBooleanValue();

		assertThat("item property value", actual, is(false));
	}

	@Test
	public void getBooleanValueWhenInvalidReturnsFalse()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		boolean actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getBooleanValue();

		assertThat("item property value", actual, is(false));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getIntValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getIntValueReturnsInteger()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
1
"
			+ "
"
			+ ""));

		int actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getIntValue();

		assertThat("item property value", actual, is(1));
	}

	@Test
	public void getIntValueWhenEmptyReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		int actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getIntValue();

		assertThat("item property value", actual, is(0));
	}

	@Test
	public void getIntValueWhenInvalidReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		int actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getIntValue();

		assertThat("item property value", actual, is(0));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getLongValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getLongValueReturnsLong()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
1
"
			+ "
"
			+ ""));

		long actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getLongValue();

		assertThat("item property value", actual, is(1L));
	}

	@Test
	public void getLongValueWhenEmptyReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		long actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getLongValue();

		assertThat("item property value", actual, is(0L));
	}

	@Test
	public void getLongValueWhenInvalidReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		long actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getLongValue();

		assertThat("item property value", actual, is(0L));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getFloatValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getFloatValueReturnsFloat()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
1
"
			+ "
"
			+ ""));

		float actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getFloatValue();

		assertThat("item property value", actual, is(1f));
	}

	@Test
	public void getFloatValueWhenEmptyReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		float actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getFloatValue();

		assertThat("item property value", actual, is(0f));
	}

	@Test
	public void getFloatValueWhenInvalidReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		float actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getFloatValue();

		assertThat("item property value", actual, is(0f));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getDoubleValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getDoubleValueReturnsDouble()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
1
"
			+ "
"
			+ ""));

		double actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getDoubleValue();

		assertThat("item property value", actual, is(1d));
	}

	@Test
	public void getDoubleValueWhenEmptyReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		double actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getDoubleValue();

		assertThat("item property value", actual, is(0d));
	}

	@Test
	public void getDoubleValueWhenInvalidReturnsZero()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
x
"
			+ "
"
			+ ""));

		double actual = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("p")
			.getDoubleValue();

		assertThat("item property value", actual, is(0d));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// unwrap tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void unwrapWithUnknownTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ "
"
			+ ""));

		MicrodataProperty property = newBrowser().get(url(server()))
			.getItem("http://i")
			.getProperty("x");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Cannot unwrap to: class java.lang.Void");

		property.unwrap(Void.class);
	}
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.common.webapp.servlets;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * DataStreamServlet is used to stream chunks of data in a response. The servlet generates its data from an input
 * stream, a content type attribute and a content disposition attribute (both optional) which are passed to this servlet
 * as attributes or parameters of the request.
 *
 *
The names of the variables that are passed as attributes in the request are:
 *
 *

 *
 contentType  the content type of the data to be streamed
 *
 inputStream  the input stream where this servlet reads its data from
 *

 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Stream a block of data to an HTTP client.
 *

 *
 * @author Rupert Smith
 * @todo   Add parameters to select byte ranges which is useful for streaming PDF files.
 */
public class DataStreamServlet extends HttpServlet
{
    /** Used for logging. */
    private static final java.util.logging.Logger log =
        java.util.logging.Logger.getLogger(DataStreamServlet.class.getName());

    /**
     * Processes the http request that is directed to this servlet.
     *
     * @param  request  The HTTP request.
     * @param  response The HTTP response.
     *
     * @throws IOException If the is an I/O error whilst writing data to the HTTP client through the remote output
     *                     stream.
     */
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        log.fine("void service(HttpServletRequest, HttpServletResponse): called");

        // Read the parameters and attributes from the request
        String contentType = (String) request.getAttribute("contentType");
        String contentDisposition = (String) request.getAttribute("contentDisposition");
        InputStream inputStream = (InputStream) request.getAttribute("inputStream");

        // Build the response header
        // response.addHeader("Content-disposition", "attachment; filename=" + fileName);
        if (contentType != null)
        {
            response.setContentType(contentType);
        }

        if (contentDisposition != null)
        {
            response.addHeader("Content-disposition", contentDisposition);
        }

        // response.setContentLength((int)f.length());

        // Create a stream to write the data out to
        BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());

        // Read the entire input stream until no more bytes can be read and write the results into the response
        // This is done in chunks of 8k at a time.
        int length = -1;
        byte[] chunk = new byte[8192];

        while ((length = inputStream.read(chunk)) != -1)
        {
            outputStream.write(chunk, 0, length);
        }

        // Clear up any open stream and ensure that they are flushed
        outputStream.flush();
        inputStream.close();
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.common.webapp.tags;

import java.net.MalformedURLException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import org.apache.struts.taglib.TagUtils;

import com.thesett.common.util.PagedList;

/**
 * PageControlTag renders a paging control for navigating amongst multiple pages. The information to be split into pages
 * should be held in a {@link com.thesett.common.util.PagedList} which is stored in a web session scope variable (it has
 * to be session or application scope to persist over multiple pages/requests). There is some flexibility in the way
 * that the paging control is rendered.
 *
 *
It is possible to specify a maximum number of pages to display at once. If there are more pages than this then a
 * more button will be rendered to go to the next group of pages. The more button passes an index parameter to the page
 * action to tell it to update the index of the {@link com.thesett.common.util.PagedList}.
 *
 *
The forward and back buttons will only be rendered if the forward and back parameters are set to true. The
 * forward and back buttons will also pass an index parameter to the paging control action if the maximum number of
 * pages is specified and if the forward or back button points to a page outside of those currently rendered. This will
 * adjust the current index similarly to the more button.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Render a paging control  {@link com.thesett.common.util.PagedList}
 *

 *
 * @author Rupert Smith
 *
 * @jsp.tag
 *      name = "pageControl"
 */
public class PageControlTag extends TagSupport
{
    /** Used for logging. */
    private static final java.util.logging.Logger log =
        java.util.logging.Logger.getLogger(PageControlTag.class.getName());

    /** Defines the default opening delimeter. */
    public static final String OPEN_DELIM = "";

    /** Defines the default opening delimeter to use for the first page button. */
    public static final String OPEN_DELIM_FIRST = "";

    /** Defines the default opening delimeter to use for the forward button. */
    public static final String OPEN_DELIM_FORWARD = "";

    /** Defines the default opening delimeter to use for the numnber buttons. */
    public static final String OPEN_DELIM_NUMBER = "";

    /** Defines the default opening delimeter to use for the current button. */
    public static final String OPEN_DELIM_CURRENT = "";

    /** Defines the default opening delimeter to use for the more button. */
    public static final String OPEN_DELIM_MORE = "";

    /** Defines the default opening delimeter to use for the back button. */
    public static final String OPEN_DELIM_BACK = "";

    /** Defines the default opening delimeter to use for the last button. */
    public static final String OPEN_DELIM_LAST = "";

    /** Defines the default closing delimeter. */
    public static final String CLOSE_DELIM = "";

    /** Defines the default first button text. */
    public static final String FIRST = "|<";

    /** Defines the default back button text. */
    public static final String BACK = "previous";

    /** Defines the default more button text. */
    public static final String MORE = "more...";

    /** Defines the default forward button text. */
    public static final String FORWARD = "next";

    /** Defines the default last button text. */
    public static final String LAST = ">|";

    /** Used to hold the name of the variable that holds the PagedList. */
    private String name;

    /** Used to hold the scope of the variable that holds the PagedList. */
    private String scope;

    /** Used to hold the property of the named variable to get the PagedList from. */
    private String property;

    /**
     * Used to hold the URL of the {@link com.thesett.common.webapp.actions.PageAction} action to process the paging
     * events.
     */
    private String action;

    /** Used to hold the maximum number of pages to display in the control at any one time. */
    private int maxPages;

    /** Used to indicate that a first page button should be rendered. */
    private boolean renderFirst = true;

    /** Used to indicate that a first page button should be rendered. */
    private boolean renderBack = true;

    /** Used to indicate that a first page button should be rendered. */
    private boolean renderForward = true;

    /** Used to indicate that a first page button should be rendered. */
    private boolean renderLast = true;

    /** Holds the optional opening delimeter to use for rendering the first page button. */
    private String openDelimFirst = OPEN_DELIM_FIRST;

    /** Holds the optional opening delimeter to use for rendering the forward page button. */
    private String openDelimForward = OPEN_DELIM_FORWARD;

    /** Holds the optional opening delimeter to use for rendering the number page button. */
    private String openDelimNumber = OPEN_DELIM_NUMBER;

    /** Holds the optional opening delimeter to use for rendering the current page button. */
    private String openDelimCurrent = OPEN_DELIM_CURRENT;

    /** Holds the optional opening delimeter to use for rendering the more page button. */
    private String openDelimMore = OPEN_DELIM_MORE;

    /** Holds the optional opening delimeter to use for rendering the back page button. */
    private String openDelimBack = OPEN_DELIM_BACK;

    /** Holds the optional opening delimeter to use for rendering the last page button. */
    private String openDelimLast = OPEN_DELIM_LAST;

    /** Holds the optioanl closing delimter to place after all buttons. */
    private String closeDelim = CLOSE_DELIM;

    /** Holds the optional string to render a first button. */
    private String firstText = FIRST;

    /** Holds the optional string to render a back button. */
    private String backText = BACK;

    /** Holds the optional string to render a more button. */
    private String moreText = MORE;

    /** Holds the optional string to render a forward button. */
    private String forwardText = FORWARD;

    /** Holds the optional string to render a last button. */
    private String lastText = LAST;

    /**
     * Sets the name of the variable to get the pages list from.
     *
     * @param         name The name of the variable to get the pages list from.
     *
     * @jsp:attribute required="true" rtexprvalue="true" type="java.lang.String"
     */
    public void setName(String name)
    {
        this.name = name;
    }

    /**
     * Sets the scope of the variable to get the paged list from.
     *
     * @param         scope The scope of the variable to get the paged list from.
     *
     * @jsp:attribute required="true" rtexprvalue="true" type="java.lang.String"
     */
    public void setScope(String scope)
    {
        this.scope = scope;
    }

    /**
     * Sets the (optional) property of the variable to get the paged list from.
     *
     * @param         property The property of the variable to get the paged list from.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setProperty(String property)
    {
        this.property = property;
    }

    /**
     * Sets the name of the action to handle the paging requests.
     *
     * @param         action The name of the action to handle the paging requests.
     *
     * @jsp:attribute required="true" rtexprvalue="true" type="java.lang.String"
     */
    public void setAction(String action)
    {
        this.action = action;
    }

    /**
     * Setst the maximum number of pages to render in the control.
     *
     * @param         maxPages The maximum number of pages to render in the control.
     *
     * @jsp:attribute required="true" rtexprvalue="true" type="java.lang.String"
     */
    public void setMaxPages(int maxPages)
    {
        this.maxPages = maxPages;
    }

    /**
     * Determines whether of not to render the first button.
     *
     * @param         renderFirst Determines whether of not to render the first button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="boolean"
     */
    public void setRenderFirst(boolean renderFirst)
    {
        this.renderFirst = renderFirst;
    }

    /**
     * Determines whether of not to render the back button.
     *
     * @param         renderBack Determines whether of not to render the back button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="boolean"
     */
    public void setRenderBack(boolean renderBack)
    {
        this.renderBack = renderBack;
    }

    /**
     * Determines whether of not to render the forward button.
     *
     * @param         renderForward Determines whether of not to render the forward button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="boolean"
     */
    public void setRenderForward(boolean renderForward)
    {
        this.renderForward = renderForward;
    }

    /**
     * Determines whether of not to render the last button.
     *
     * @param         renderLast Determines whether of not to render the last button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="boolean"
     */
    public void setRenderLast(boolean renderLast)
    {
        this.renderLast = renderLast;
    }

    /**
     * Sets the optional openining delimeter for the first button.
     *
     * @param         openDelimFirst The optional openining delimeter for the first button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimFirst(String openDelimFirst)
    {
        this.openDelimFirst = openDelimFirst;
    }

    /**
     * Sets the optional openining delimeter for the forward button.
     *
     * @param         openDelimForward The optional openining delimeter for the forward button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimForward(String openDelimForward)
    {
        this.openDelimForward = openDelimForward;
    }

    /**
     * Sets the optional openining delimeter for the number button.
     *
     * @param         openDelimNumber The optional openining delimeter for the number button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimNumber(String openDelimNumber)
    {
        this.openDelimNumber = openDelimNumber;
    }

    /**
     * Sets the optional openining delimeter for the current button.
     *
     * @param         openDelimCurrent The optional openining delimeter for the current button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimCurrent(String openDelimCurrent)
    {
        this.openDelimCurrent = openDelimCurrent;
    }

    /**
     * Sets the optional openining delimeter for the more button.
     *
     * @param         openDelimMore The optional openining delimeter for the more button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimMore(String openDelimMore)
    {
        this.openDelimMore = openDelimMore;
    }

    /**
     * Sets the optional openining delimeter for the back button.
     *
     * @param         openDelimBack The optional openining delimeter for the back button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimBack(String openDelimBack)
    {
        this.openDelimBack = openDelimBack;
    }

    /**
     * Sets the optional openining delimeter for the last button.
     *
     * @param         openDelimLast The optional openining delimeter for the last button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setOpenDelimLast(String openDelimLast)
    {
        this.openDelimLast = openDelimLast;
    }

    /**
     * Sets the optional closing delimeter.
     *
     * @param         closeDelim The optional closing delimeter.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setCloseDelim(String closeDelim)
    {
        this.closeDelim = closeDelim;
    }

    /**
     * Sets the optional text for the first button.
     *
     * @param         firstText The optional text for the first button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setFirstText(String firstText)
    {
        this.firstText = firstText;
    }

    /**
     * Sets the optional text for the back button.
     *
     * @param         backText The optional text for the back button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setBackText(String backText)
    {
        this.backText = backText;
    }

    /**
     * Sets the optional text for the more button.
     *
     * @param         moreText The optional text for the more button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setMoreText(String moreText)
    {
        this.moreText = moreText;
    }

    /**
     * Sets the optional text for the forward button.
     *
     * @param         forwardText The optional text for the forward button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setForwardText(String forwardText)
    {
        this.forwardText = forwardText;
    }

    /**
     * Sets the optional text for the last button.
     *
     * @param         lastText The optional text for the last button.
     *
     * @jsp:attribute required="false" rtexprvalue="true" type="java.lang.String"
     */
    public void setLastText(String lastText)
    {
        this.lastText = lastText;
    }

    /**
     * Renders the paging control.
     *
     * @return Always SKIP_BODY;
     *
     * @throws JspException If the named paged list variable cannot be found.
     */
    public int doStartTag() throws JspException
    {
        log.fine("public int doStartTag(): called");

        TagUtils tagUtils = TagUtils.getInstance();

        // Get a reference to the PagedList.
        PagedList list = (PagedList) tagUtils.lookup(pageContext, name, property, scope);
        log.fine("list = " + list);

        // Work out what the URL of the action to handle the paging events is.
        String url;

        try
        {
            url = tagUtils.computeURL(pageContext, null, null, null, action, null, null, null, false);
        }
        catch (MalformedURLException e)
        {
            throw new JspException("Got malformed URL exception: ", e);
        }

        // Optionally render the first page button.
        renderButton(renderFirst, 0, 0, openDelimFirst, url, firstText, list.getCurrentPage() != 0);

        // Optionally render the back button.
        renderButton(renderBack, list.getCurrentPage() - 1,
            ((list.getCurrentPage() - 1) < list.getCurrentIndex()) ? (list.getCurrentIndex() - maxPages)
                                                                   : list.getCurrentIndex(), openDelimBack, url,
            backText, (list.getCurrentPage() - 1) >= 0);

        // Render links for pages from the current index to the current index plus the maximum number of pages.
        int from = list.getCurrentIndex();
        int to = list.getCurrentIndex() + maxPages;

        for (int i = from; (i < list.size()) && (i < to); i++)
        {
            renderButton(true, i, list.getCurrentIndex(),
                (i == list.getCurrentPage()) ? openDelimCurrent : openDelimNumber, url, "" + (i + 1),
                i != list.getCurrentPage());
        }

        // Optionally render a more button. The more button should only be rendered if the current index plus
        // the maximum number of pages is less than the total number of pages so there are pages beyond those that
        // have numeric link to them already.
        renderButton((list.getCurrentIndex() + maxPages) < list.size(), list.getCurrentPage() + maxPages,
            list.getCurrentPage() + maxPages, openDelimMore, url, moreText, true);

        // Optionally render a forward button.
        renderButton(renderForward, list.getCurrentPage() + 1,
            ((list.getCurrentPage() + 1) >= (list.getCurrentIndex() + maxPages)) ? (list.getCurrentIndex() + maxPages)
                                                                                : list.getCurrentIndex(),
            openDelimForward, url, forwardText, (list.getCurrentPage() + 1) < list.size());

        // Optionally render a last page button.
        renderButton(renderLast, list.size() - 1, (list.size() / maxPages) * maxPages, openDelimLast, url, lastText,
            list.getCurrentPage() != (list.size() - 1));

        return SKIP_BODY;
    }

    /**
     * Renders a button control as a hyperlink for the page control.
     *
     * @param  render    Whether or not to render the control.
     * @param  page      The page number that the control links to.
     * @param  index     The page secondary index that the control links to.
     * @param  openDelim The opening delimeter to put around the control. (A snippet of HTML).
     * @param  url       The url of the page action handler.
     * @param  text      The text to render the control.
     * @param  active    Whether or not the control should be made active.
     *
     * @throws JspException If an error occurs whilst writing out the response.
     */
    private void renderButton(boolean render, int page, int index, String openDelim, String url, String text,
        boolean active) throws JspException
    {
        log.fine(
            "private void renderButton(boolean render, int page, int index, String openDelim, String url, String text, boolean active): called");
        log.fine("render = " + render);
        log.fine("page = " + page);
        log.fine("index = " + index);
        log.fine("openDelim = " + openDelim);
        log.fine("url = " + url);
        log.fine("text = " + text);
        log.fine("active = " + active);

        TagUtils tagUtils = TagUtils.getInstance();

        if (render)
        {
            tagUtils.write(pageContext, openDelim);

            // Only render the button as active if the active flag is set.
            if (active)
            {
                tagUtils.write(pageContext,
                    "" + text +
                    "");
            }

            // Render an inactive button.
            else
            {
                tagUtils.write(pageContext, text);
            }

            tagUtils.write(pageContext, closeDelim);
        }
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.aima.logic.fol;

import com.thesett.aima.attribute.impl.IdAttribute;

/**
 * VariableAndFunctorInternerImpl provides interners for variable and functor names in specified namespaces.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide symbol table for functors names.
 *
 Provide symbol table for variable names.
 *

 *
 * @author Rupert Smith
 */
public class VariableAndFunctorInternerImpl implements VariableAndFunctorInterner
{
    /** Holds the interner that turns variable names into indexed integers. */
    private final IdAttribute.IdAttributeFactory<String> variableInterningFactory;

    /** Holds the interner that turns functor names and arities into indexed integers. */
    private final IdAttribute.IdAttributeFactory<FunctorName> functorInterningFactory;

    /**
     * Creates an interner for variable and functor names, with the names created under the specified name spaces.
     *
     * @param variableNameSpace The name space for variables.
     * @param functorNameSpace  The name space for functors.
     */
    public VariableAndFunctorInternerImpl(String variableNameSpace, String functorNameSpace)
    {
        variableInterningFactory = IdAttribute.getFactoryForClass(variableNameSpace);
        functorInterningFactory = IdAttribute.getFactoryForClass(functorNameSpace);
    }

    /** {@inheritDoc} */
    public IdAttribute.IdAttributeFactory<FunctorName> getFunctorInterner()
    {
        return functorInterningFactory;
    }

    /** {@inheritDoc} */
    public IdAttribute.IdAttributeFactory<String> getVariableInterner()
    {
        return variableInterningFactory;
    }

    /** {@inheritDoc} */
    public int internFunctorName(String name, int numArgs)
    {
        FunctorName functorName = new FunctorName(name, numArgs);

        return getFunctorInterner().createIdAttribute(functorName).ordinal();
    }

    /** {@inheritDoc} */
    public int internFunctorName(FunctorName name)
    {
        return getFunctorInterner().createIdAttribute(name).ordinal();
    }

    /** {@inheritDoc} */
    public int internVariableName(String name)
    {
        return getVariableInterner().createIdAttribute(name).ordinal();
    }

    /** {@inheritDoc} */
    public String getVariableName(int name)
    {
        if (name < 0)
        {
            return "_G" + (-name);
        }
        else
        {
            return getVariableInterner().getAttributeFromInt(name).getValue();
        }
    }

    /** {@inheritDoc} */
    public String getVariableName(Variable variable)
    {
        return getVariableName(variable.getName());
    }

    /** {@inheritDoc} */
    public FunctorName getDeinternedFunctorName(int name)
    {
        return getFunctorInterner().getAttributeFromInt(name).getValue();
    }

    /** {@inheritDoc} */
    public String getFunctorName(int name)
    {
        return getFunctorInterner().getAttributeFromInt(name).getValue().getName();
    }

    /** {@inheritDoc} */
    public int getFunctorArity(int name)
    {
        return getFunctorInterner().getAttributeFromInt(name).getValue().getArity();
    }

    /** {@inheritDoc} */
    public FunctorName getFunctorFunctorName(Functor functor)
    {
        return getDeinternedFunctorName(functor.getName());
    }

    /** {@inheritDoc} */
    public String getFunctorName(Functor functor)
    {
        return getFunctorName(functor.getName());
    }

    /** {@inheritDoc} */
    public int getFunctorArity(Functor functor)
    {
        return getFunctorArity(functor.getName());
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.aima.logic.fol.prolog.builtins;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.Term;

/**
 * Call implements the Prolog 'call' operator. Call resolves its argument as a query. The argument to call may be a
 * variable, but must be bound to a callable functor or atom at the time the call is made in order to be valid.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Call a callable functor or atom as a query for resolution.  {@link ResolutionState}.
 *

 *
 * @author Rupert Smith
 */
public class Call extends BaseBuiltIn implements BuiltIn
{
    /**
     * Creates a call built-in to implement the specified functor.
     *
     * @param functor The functor to implement as a built-in.
     */
    public Call(Functor functor)
    {
        super(functor);
    }

    /** {@inheritDoc} */
    public boolean proofStep(ResolutionState state)
    {
        Functor goalTerm = state.getGoalStack().poll().getFunctor();
        Term argument = goalTerm.getArgument(0).getValue();

        // Check that the argument is not a free variable.
        if (argument.isVar())
        {
            throw new IllegalStateException(
                "instantiation_error, 'call' expects a fully instantiated term to unify against.");
        }

        // Check that the argument is callable.
        if (!argument.isFunctor() && !argument.isAtom())
        {
            throw new IllegalStateException("type_error, callable expected as argument to 'call'.");
        }

        // Set up the argument to call as a new goal.
        state.getGoalStack().offer(state.getBuiltInTransform().apply((Functor) argument));

        return true;
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * Licensed 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 com.thesett.aima.learning;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import com.thesett.aima.state.State;

/**
 * AbstractLearningMethod provides an implementation of the convenience methods of {@link LearningMethod} and provides a
 * base class from which to derive learning methods.
 *
 *

CRC Card

 *
 Responsibilities
 *
 Accept an input example set.
 *
 Accept a property set to learn how to classify.
 *
 Accept a property set to use to learn the classification from.
 *
 Accept a classifying machine to train.
 *
 Accept a maximum number of learning steps allowable.
 *
 Reset the learning method.
 *

 *
 * @author Rupert Smith
 */
public abstract class AbstractLearningMethod implements LearningMethod
{
    /* Used for logging. */
    /* private static final Logger log = Logger.getLogger(AbstractLearningMethod.class.getName()); */

    /** Holds the maximum number of steps that the learning algorithm should attempt before quiting. */
    protected int maxSteps;

    /** Holds the classifying machine to train. */
    protected ClassifyingMachine machineToTrain;

    /** Holds the input example collection. */
    protected Collection<State> inputExamples;

    /** Holds the set of input properties to use. */
    protected Set<String> inputProperties;

    /** Holds the set of output properties to learn. */
    protected Set<String> outputProperties;

    /**
     * Used to indicate that a specific set of input properties has been set, rather than the default of all properties
     * of the example states (minus the goal ones).
     */
    protected boolean inputPropertiesSet;

    /** Used to indicate that properties other than the 'goal' property have been set as the output properties. */
    protected boolean outputPropertiesSet;

    /** Default constructor that initializes the data structures. */
    public AbstractLearningMethod()
    {
        inputExamples = new ArrayList<State>();
        inputProperties = new HashSet<String>();
        outputProperties = new HashSet<String>();
    }

    /**
     * Implement this abstract method to provide the actual learning algorithm.
     *
     * @return A trained {@link ClassifyingMachine}.
     *
     * @throws LearningFailureException if the learning process fails for any reason.
     */
    public abstract ClassifyingMachine learn() throws LearningFailureException;

    /*
     * Sets the maximum number of search steps that a learning method may take. If it fails to find a solution before
     * this number of steps has been reached its {@link #learn} method should either return the best complete classifying
     * machine that it can or fail with an exception. What exactly constitutes a single step, and the granularity of the
     * step size, is open to different interpretation by different search algorithms. The recommended guideline is that
     * this is the maximum number of test examples that the learning algorithm should examine.
     *
     * @param max the maximum number of test examples that the learning algorithm should examine before failing.
     */
    // public void setMaxSteps(int max) { // Keep the maximum steps this.maxSteps = max;}

    /**
     * Adds an example state to learn from to this learning method.
     *
     * @param exampleState An example state to learn from.
     */
    public void addExampleState(State exampleState)
    {
        // Add the example to the existing ones.
        inputExamples.add(exampleState);
    }

    /**
     * Adds many examples states to learn from to this learning method.
     *
     * @param exampleStates A collection of example states to learn from.
     */
    public void addExampleStates(Collection<? extends State> exampleStates)
    {
        // Add the examples to the existing ones.
        inputExamples.addAll(exampleStates);
    }

    /**
     * Adds a property of the example states that the learning algorithm should use to learn its classification from.
     * Generally, algorithms are allowed to use any of the example states properties (except for cheating by using the
     * goal properties). Setting input properties with this method should restrict the learning algorithm to using just
     * those properties to learn its classification from.
     *
     * @param property The name of a property in the example states that is to be considered as input to the learning
     *                 process.
     */
    public void addInputProperty(String property)
    {
        inputPropertiesSet = true;

        // Add the input property to the existing ones.
        inputProperties.add(property);
    }

    /**
     * Adds many properties of the example states that the learning algorithm should use to learn its classification
     * from. Generally, algorithms are allowed to use any of the example states properties (except for cheating by using
     * the goal properties). Setting input properties with this method should restrict the learning algorithm to using
     * just those properties to learn its classification from.
     *
     * @param properties A collection of property names in the example states that are to be considered as input to the
     *                   learning process.
     */
    public void addInputProperties(Collection<String> properties)
    {
        inputPropertiesSet = true;

        // Add the input properties to the existing ones.
        inputProperties.addAll(properties);
    }

    /**
     * Adds a property of the example states that a classification is to be learnt for. If this is an unsupervised
     * learning method then it may not accept some properties to classify by as its goal will be to find clusterings of
     * the example states by the properties it chooses. If this is the case then the method should throw an exception to
     * indicate that it is not supported.
     *
     *
All states support the 'goal' boolean property. Generally, algorithms will assume that this is the property to
     * learn how to classify for unless different properties are set by calling this method.
     *
     * @param  property The name of a property in the set of example states that is to be considered the output to learn
     *                  for.
     *
     * @throws UnsupportedOperationException if the learning method does not accept goal properties.
     */
    public void addGoalProperty(String property)
    {
        outputPropertiesSet = true;

        // Add the goal property to the existing ones.
        outputProperties.add(property);
    }

    /**
     * Adds many properties of the example states that a classification is to be learnt for. If this is an unsupervised
     * learning method then it may not accept some properties to classify by as its goal will be to find clusterings of
     * the example states by the properties it chooses. If this is the case then the method should throw an exception to
     * indicate that it is not supported.
     *
     *
All states support the 'goal' boolean property. Generally, algorithms will assume that this is the property to
     * learn how to classify for unless different properties are set by calling this method.
     *
     * @param  properties A collection of property names in the set of example state that are considered to be the
     *                    output to learn for.
     *
     * @throws UnsupportedOperationException if the learning method does not accept goal properties.
     */
    public void addGoalProperties(Collection<String> properties)
    {
        outputPropertiesSet = true;

        // Add the goal properties to the existing ones.
        outputProperties.addAll(properties);
    }

    /**
     * Accepts a classifying machine to train. Not all learning methods can train all classifying machines. Generally,
     * the learning method and the machine must be compatible.
     *
     * @param machineToTrain A classifying machine that is to be trained by this learning method.
     */
    public void setMachineToTrain(ClassifyingMachine machineToTrain)
    {
        // Keep the machine to train.
        this.machineToTrain = machineToTrain;
    }

    /**
     * Resets the learning method. This should clear all the examples, properties to learn from and for and the input
     * machine to train.
     */
    public void reset()
    {
        maxSteps = 0;
        machineToTrain = null;
        inputExamples = new ArrayList<State>();
        inputProperties = new HashSet<String>();
        outputProperties = new HashSet<String>();
        inputPropertiesSet = false;
        outputPropertiesSet = false;
    }

    /**
     * This should be called at the start of the learn method to initialize the input and output property sets.
     *
     * @throws LearningFailureException If the set of training data and input and output properties are malformed.
     *                                  Either because the training data set is empty or because there are input or
     *                                  output properties that are not found in the training data set.
     */
    protected void initialize() throws LearningFailureException
    {
        // Check that at least one training example has been set.
        if (inputExamples.isEmpty())
        {
            throw new LearningFailureException("No training examples to learn from.", null);
        }

        // Check if an output property set to override the default was not set.
        if (!outputPropertiesSet)
        {
            // Set the 'goal' property as the default.
            addGoalProperty("goal");
        }

        // Check if an input property set to override the default was not set.
        if (!inputPropertiesSet)
        {
            // Extract all properties from the first example in the training data set as the input property set,
            // automatically excluding any properties which are in the output set.
            State example = inputExamples.iterator().next();
            Set<String> allProperties = example.getComponentType().getAllPropertyNames();

            inputProperties = new HashSet<String>(allProperties);
            inputProperties.removeAll(outputProperties);

            inputPropertiesSet = true;
        }

        // Check all the training examples have all the required input and output properties.
        for (State example : inputExamples)
        {
            Set<String> properties = example.getComponentType().getAllPropertyNames();

            String errorMessage = "";

            for (String inputProperty : inputProperties)
            {
                if (!properties.contains(inputProperty))
                {
                    errorMessage +=
                        "The training example, " + example + " does not contain the specified input property, " +
                        inputProperty + "\n";
                }
            }

            for (String outputProperty : outputProperties)
            {
                if (!properties.contains(outputProperty))
                {
                    errorMessage +=
                        "The training example, " + example + " does not contain the specified output property, " +
                        outputProperty + "\n";
                }
            }

            if (!"".equals(errorMessage))
            {
                throw new LearningFailureException(errorMessage, null);
            }
        }
    }
}