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

import java.util.concurrent.TimeUnit;

/**
 * BaseUnaryCondition provides a base implementation of {@link UnaryCondition}. It provides methods to wait on condition
 * evaluations using the {@link #evaluateWithWaitTimeNanos(Object)} method and a mechanism to signal waiting threads as
 * the condition favourable changes. It can also accept favourable condition change signals from an external resource
 * and propagates them to any {@link Signalable} resource that is dependant on this one.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Wait for a condition to evaluate to true.
 *  {@link UnaryCondition}
 *
Notify dependant signalable resources when the condition favourably changes.  {@link Signalable}
 *
Allow this resource to be registered with an external notifying resource.  {@link Notifying}
 *
Wake up threads blocking on this condition when conditions favourably change to re-evaluate the condition.
 *

 *
 * @author Rupert Smith
 */
public abstract class BaseUnaryCondition<T> implements UnaryCondition<T>
{
    /** Used as the thread monitor for threads to wait on this condition. */
    private final Object monitor = new Object();

    /** Holds the signalable resource to notify when conditions change. */
    Signalable signalable;

    /**
     * Wait for the predicate to become true on the specified object.
     *
     * @param  t The object to test for predicate membership.
     *
     * @throws InterruptedException If interrupted whilst waiting.
     */
    public void await(T t) throws InterruptedException
    {
        synchronized (monitor)
        {
            long waitNanos = evaluateWithWaitTimeNanos(t);

            // Loop forever until all conditions pass or the thread is interrupted.
            while (waitNanos > 0)
            {
                // If some conditions failed, then wait for the shortest wait time, or until the thread is woken up by
                // a signal, before re-evaluating conditions.
                long milliPause = waitNanos / 1000000;
                int nanoPause = (int) (waitNanos % 1000000);

                monitor.wait(milliPause, nanoPause);

                waitNanos = evaluateWithWaitTimeNanos(t);
            }
        }
    }

    /**
     * Wait for up to a timeout limit for the predicate to become true on the specified object.
     *
     * @param  t       The object to test for predicate membership.
     * @param  timeout The amount of time to wait.
     * @param  unit    The units of time used.
     *
     * @return <tt>true</tt> if the predicate became true before the timeout expired, <tt>false</tt> otherwise.
     *
     * @throws InterruptedException If interrupted whilst waiting.
     */
    public boolean await(T t, long timeout, TimeUnit unit) throws InterruptedException
    {
        synchronized (monitor)
        {
            // Holds the absolute time when the timeout expires.
            long expiryTimeNanos = System.nanoTime() + unit.toNanos(timeout);

            // Used to hold the estimated wait time until the condition may pass.
            long waitNanos = evaluateWithWaitTimeNanos(t);

            // Loop forever until all conditions pass, the timeout expires, or the thread is interrupted.
            while (waitNanos > 0)
            {
                // Check how much time remains until the timeout expires.
                long remainingTimeNanos = expiryTimeNanos - System.nanoTime();

                // Check if the timeout has expired.
                if (remainingTimeNanos <= 0)
                {
                    return false;
                }

                // If some conditions failed, then wait for the shortest of the wait time or the remaining time until the
                // timout expires, or until the thread is woken up by a signal, before re-evaluating conditions.
                long timeToPauseNanos = (waitNanos < remainingTimeNanos) ? waitNanos : remainingTimeNanos;

                long milliPause = timeToPauseNanos / 1000000;
                int nanoPause = (int) (timeToPauseNanos % 1000000);

                monitor.wait(milliPause, nanoPause);

                // Re-evelaute the condition and obtain a new estimate of how long until it may pass.
                waitNanos = evaluateWithWaitTimeNanos(t);
            }
        }

        // All conditions have passed when the above loop terminates.
        return true;
    }

    /**
     * Signals to one waiting thread on this predicate, that conditions have changed such that it may now be possible
     * for the thread to proceed. This wakes up one waiting thread, in order for it to re-check the predicate.
     */
    public void signal()
    {
        synchronized (monitor)
        {
            monitor.notify();
        }

        if (signalable != null)
        {
            signalable.signal();
        }
    }

    /**
     * Signals to all waiting threads on this predicate, that conditions have changed such that it may now be possible
     * for the thread to proceed. This wakes up all waiting threads, in order for them to re-check the predicate.
     */
    public void signalAll()
    {
        synchronized (monitor)
        {
            monitor.notifyAll();
        }

        if (signalable != null)
        {
            signalable.signalAll();
        }
    }

    /**
     * Sets a signallable resource on this condition that the condition can use to notify threads waiting on this
     * condition that they should wake up and recheck the condition in order to see if they can proceed.
     *
     * @param signalable The signallable resource to notify when threads may be able to proceed.
     */
    public void setSignalableResource(Signalable signalable)
    {
        this.signalable = signalable;
    }
}


/*
 * 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.maven.run;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;

/**
 *

CRC Card

 *
 Responsibilities  Collaborations
 *

 *
 * @author                       Rupert Smith
 * @goal                         script
 * @phase                        package
 * @requiresDependencyResolution test
 */
public class ScriptGenMojo extends AbstractMojo
{
    protected static final String UNIX_SCRIPT_LANGUAGE = "#!/bin/bash\n\n";

    protected static final String UNIX_OPT_PARSER =
        "# Parse arguements taking all - prefixed args as JAVA_OPTS\n" + "for arg in \"$@\"; do\n" +
        "    if [[ $arg == -java#* ]]; then\n" + "        JAVA_OPTS=\"${JAVA_OPTS}-`echo $arg|cut -d '#' -f 2`  \"\n" +
        "    else\n" + "        ARGS=\"${ARGS}$arg \"\n" + "    fi\n" + "done\n\n";

    /** Used for debugging purposes. */
    protected final Log log = getLog();

    /**
     * Where to write out the scripts.
     *
     * @parameter
     */
    protected String scriptOutDirectory;

    /**
     * The build artifact as a .jar.
     *
     * @parameter property="project.build.finalName"
     */
    protected String outputJar;

    /**
     * The system properties to pass to java runtime.
     *
     * @parameter
     */
    protected Properties systemproperties;

    /**
     * The classpath elements of the project being tested.
     *
     * @parameter property="project.testClasspathElements"
     * @readonly
     * @required
     */
    protected List classpathElements;

    /**
     * The TKTest runner command lines. There are passed directly to the TKTestRunner main method.
     *
     * @parameter
     */
    protected final Map<String, String> commands = new LinkedHashMap<String, String>();

    /**
     * Use 'start' to launch under windows, allowing .bat file to return immediately.
     *
     * @parameter
     */
    protected boolean winStart;

    /**
     * Use 'javaw' to launch under windows, hiding java conole output.
     *
     * @parameter
     */
    protected boolean winJavaw;

    /**
     * Implementation of the script goal.
     *
     * @throws MojoExecutionException
     */
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        //log.debug("public void execute() throws MojoExecutionException: called");

        // Turn each of the test runner command lines into a script.
        for (String commandName : commands.keySet())
        {
            if (scriptOutDirectory != null)
            {
                writeUnixScript(commandName, scriptOutDirectory);
                writeWindowsScript(commandName, scriptOutDirectory);
            }
        }
    }

    /**
     * Appends the classpath onto the command line.
     *
     * @param  commandLine The command line to append to.
     * @param  unix        <tt>true</tt> to use unix paths, <tt>false</tt> to use windows.
     *
     * @return The appended to command line.
     */
    protected String appendClasspath(String commandLine, boolean unix)
    {
        String pathSeperator;
        String seperator;

        if (unix)
        {
            pathSeperator = "/";
            seperator = ":";
        }
        else
        {
            pathSeperator = "\\";
            seperator = ";";
        }

        for (Iterator i = classpathElements.iterator(); i.hasNext();)
        {
            String cpPath = (String) i.next();

            cpPath = cpPath.replace("/", pathSeperator);

            commandLine += cpPath + (i.hasNext() ? seperator : "");
        }

        return commandLine;
    }

    protected void writeWindowsScript(String commandName, String outputDirectory)
    {
        String testOptions = commands.get(commandName);
        String commandLine = new String();

        if (winStart)
        {
            commandLine += "start ";
        }

        if (winJavaw)
        {
            commandLine += "javaw ";
        }
        else
        {
            commandLine += "java ";
        }

        String logdir = null;

        if (systemproperties != null)
        {
            for (Object key : systemproperties.keySet())
            {
                String keyString = (String) key;
                String value = systemproperties.getProperty(keyString);

                if (keyString.equals("logdir"))
                {
                    logdir = value;
                }
                else
                {
                    if (keyString.startsWith("-X"))
                    {
                        commandLine += keyString + value + " ";
                    }
                    else
                    {
                        commandLine += "-D" + keyString + "=" + value + " ";
                    }
                }
            }
        }

        commandLine += "-cp ";
        commandLine = appendClasspath(commandLine, false);
        commandLine += " " + ((testOptions != null) ? testOptions : "") + " %*";

        log.info("Generating Script for command: " + commandName);
        // log.debug(commandLine);

        // Ensure that the output directory for the script exists.
        boolean madeDirectory = new File(outputDirectory).mkdir();

        String fileName = outputDirectory + "/" + commandName + ".bat";

        try
        {
            File scriptFile = new File(fileName);
            Writer scriptWriter = new FileWriter(scriptFile);

            if (logdir != null)
            {
                scriptWriter.write("mkdir -p " + logdir + "\n");
            }

            scriptWriter.write(commandLine);
            scriptWriter.flush();
            scriptWriter.close();
        }
        catch (IOException e)
        {
            getLog().error("Failed to write: " + fileName);
        }
    }

    protected void writeUnixScript(String commandName, String outputDirectory)
    {
        String testOptions = commands.get(commandName);
        String commandLine = "java ";

        String logdir = null;

        if (systemproperties != null)
        {
            for (Object key : systemproperties.keySet())
            {
                String keyString = (String) key;
                String value = systemproperties.getProperty(keyString);

                if (keyString.equals("logdir"))
                {
                    logdir = value;
                }
                else
                {
                    if (keyString.startsWith("-X"))
                    {
                        commandLine += keyString + value + " ";
                    }
                    else
                    {
                        commandLine += "-D" + keyString + "=" + value + " ";
                    }
                }
            }
        }

        commandLine += "${JAVA_OPTS} -cp ";
        commandLine = appendClasspath(commandLine, true);
        commandLine += " " + ((testOptions != null) ? testOptions : "") + " ${ARGS}";

        log.info("Generating Script for command: " + commandName);
        // log.debug(commandLine);

        // Ensure that the output directory for the script exists.
        boolean madeDirectory = new File(outputDirectory).mkdir();

        String fileName = outputDirectory + "/" + commandName;

        try
        {
            File scriptFile = new File(fileName);
            Writer scriptWriter = new FileWriter(scriptFile);
            scriptWriter.write(UNIX_SCRIPT_LANGUAGE);
            scriptWriter.write(UNIX_OPT_PARSER);

            if (logdir != null)
            {
                scriptWriter.write("mkdir -p " + logdir + "\n");
            }

            scriptWriter.write(commandLine);
            scriptWriter.flush();
            scriptWriter.close();
        }
        catch (IOException e)
        {
            getLog().error("Failed to write: " + fileName);
        }
    }
}


/*
 * 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.attribute.impl;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.thesett.aima.attribute.time.DateOnly;
import com.thesett.aima.state.BaseType;
import com.thesett.aima.state.InfiniteValuesException;
import com.thesett.aima.state.RandomInstanceFactory;
import com.thesett.aima.state.Type;
import com.thesett.aima.state.TypeVisitor;
import com.thesett.common.error.NotImplementedException;

/**
 * Implements an integer range type. This allows a sequential range of ints to be defined as a named type.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Maintain a set of named int range types.
 *
 Provide all integers within the types range.
 *
 Provide a default value that lies in the range.
 *
 Check an integer value against the range type.
 *

 *
 * @author Rupert Smith
 */
public class DateRangeType extends BaseType<DateOnly> implements Type<DateOnly>, RandomInstanceFactory<DateOnly>,
    Serializable
{
    /** Holds all of the named date range types that have been defined. */
    private static final Map<String, DateRangeType> DATE_RANGE_TYPES = new HashMap<String, DateRangeType>();

    /** Holds the minimum value an instance of this type can take. */
    private final DateOnly from;

    /** Holds the maximum value an instance of this type can take. */
    private final DateOnly to;

    /** Used to hold the name of this date range type. */
    private final String typeName;

    /**
     * Creates an instance of an integer range type with the specified name and min and max values.
     *
     * @param name The name of the type.
     * @param from The minimum value an instance of the type can take.
     * @param to   The maximum value an instance of the type can take.
     */
    private DateRangeType(String name, DateOnly from, DateOnly to)
    {
        typeName = name;
        this.from = from;
        this.to = to;
    }

    /**
     * Gets the named integer range type, if one exists.
     *
     * @param  name The name of the date range type to get.
     *
     * @return The named date range type, or <tt>null</tt> if none matching the name exists.
     */
    public static DateRangeType getInstance(String name)
    {
        synchronized (DATE_RANGE_TYPES)
        {
            return DATE_RANGE_TYPES.get(name);
        }
    }

    /**
     * Creates a new date range type with the specified name, if it does not already exist.
     *
     * @param  name The name of the date range type to create.
     * @param  from The minimum value that an instance of the type can take.
     * @param  to   The maximum value that an instance of teh type can take.
     *
     * @return A new date range type.
     *
     * @throws IllegalArgumentException If min is not less than or equal to max, or the named type already exists.
     */
    public static Type createInstance(String name, DateOnly from, DateOnly to)
    {
        // Ensure that min is less than or equal to max.
        if ((from != null) && (to != null) && (from.compareTo(to) > 0))
        {
            throw new IllegalArgumentException("'min' must be less than or equal to 'max'.");
        }

        synchronized (DATE_RANGE_TYPES)
        {
            // Add the newly created type to the map of all types.
            DateRangeType newType = new DateRangeType(name, from, to);

            // Ensure that the named type does not already exist, unless it has an identical definition already, in which
            // case the old definition can be re-used and the new one discarded.
            DateRangeType oldType = DATE_RANGE_TYPES.get(name);

            if ((oldType != null) && !oldType.equals(newType))
            {
                throw new IllegalArgumentException("The type '" + name + "' already exists and cannot be redefined.");
            }
            else if ((oldType != null) && oldType.equals(newType))
            {
                return oldType;
            }
            else
            {
                DATE_RANGE_TYPES.put(name, newType);

                return newType;
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     *
The default value of a date range type 'min'.
     */
    public DateOnly getDefaultInstance()
    {
        return from;
    }

    /**
     * {@inheritDoc}
     *
     *
A random value of this type lies between 'min' and 'max'.
     */
    public DateOnly createRandomInstance()
    {
        throw new NotImplementedException();
    }

    /** {@inheritDoc} */
    public String getName()
    {
        return typeName;
    }

    /** {@inheritDoc} */
    public Class<DateOnly> getBaseClass()
    {
        return DateOnly.class;
    }

    /** {@inheritDoc} */
    public String getBaseClassName()
    {
        return getBaseClass().getName();
    }

    /** {@inheritDoc} */
    public int getNumPossibleValues()
    {
        return -1;
    }

    /**
     * Checks an integer value against this type, to determine if it is a valid instance of the type.
     *
     * @param  value The value to check.
     *
     * @return <tt>true</tt> if the value lies in the types range, <tt>false</tt> if it does not.
     */
    public boolean isInstance(Comparable value)
    {
        return (value.compareTo(from) >= 0) && (value.compareTo(to) <= 0);
    }

    /**
     * {@inheritDoc}
     *
     *
Beware that as this operation creates a fully expanded set of the date range in memory, large ranges may use
     * a lot of memory. Consider using the {@link #getAllPossibleValuesIterator()} method instead, as it lazily creates
     * the values on demand.
     */
    public Set<DateOnly> getAllPossibleValuesSet() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("DateRangeType has too many values to enumerate.", null);
    }

    /** {@inheritDoc} */
    public Iterator<DateOnly> getAllPossibleValuesIterator() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("DateRangeType has too many values to enumerate.", null);
    }

    /** {@inheritDoc} */
    public void acceptVisitor(TypeVisitor visitor)
    {
        if (visitor instanceof DateRangeTypeVisitor)
        {
            ((DateRangeTypeVisitor) visitor).visit(this);
        }
        else
        {
            super.acceptVisitor(visitor);
        }
    }

    /**
     * Checks two date range types for equality, based on their name and date range.
     *
     * @param  o The object to compare to.
     *
     * @return <tt>true</tt> if the comparator is an date range type with the same name and range as this one.
     */
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }

        if (!(o instanceof DateRangeType))
        {
            return false;
        }

        DateRangeType that = (DateRangeType) o;

        return (to == that.to) && (from == that.from) &&
            !((typeName != null) ? (!typeName.equals(that.typeName)) : (that.typeName != null));
    }

    /**
     * Provides a hashcode based on the name, and date range of this type.
     *
     * @return A hashcode based on the name, and date range of this type.
     */
    public int hashCode()
    {
        int result;
        result = (from != null) ? from.hashCode() : 0;
        result = (31 * result) + ((to != null) ? to.hashCode() : 0);
        result = (31 * result) + ((typeName != null) ? typeName.hashCode() : 0);

        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.aima.logic.fol.prolog;

import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableBindingContext;

/**
 * VariableBindingStackFrame is a stack frame for holding the bindings of {@link StackVariable}s in. It holds an array
 * of {@link Variable} bindings slots, and provides these as the storage cells to stack variables depending on the stack
 * variable's assigned stack position.
 *
 *

CRC Card

 *
 Responsibilities
 Responsibilities
 *
 Initialize an array of storage cells.
 *
 Provide storage cells for stack variables dependant on their relative storage positions.
 *  {@link StackVariable}.
 *

 *
 * @author Rupert Smith
 */
public class VariableBindingStackFrame implements VariableBindingContext<StackVariable>
{
    /** Holds the variable bindings in this stack frame. */
    Variable[] bindings;

    /**
     * Creates a stack frame of the specified size.
     *
     * @param size The size of the stack frame to create.
     */
    public VariableBindingStackFrame(int size)
    {
        bindings = new Variable[size];

        for (int i = 0; i < size; i++)
        {
            bindings[i] = new Variable(-1, null, true);
        }
    }

    /**
     * Provides the storage cell for the specified variable. Some types of variable may defer their storage onto a
     * storage cell other than themselves, other variable types may simply return themselves as their own storage cells.
     *
     * @param  variable The variable to get the storage cell for.
     *
     * @return The storage cell where the specified variable sets its bindings.
     */
    public Variable getStorageCell(StackVariable variable)
    {
        return bindings[variable.getPosition()];
    }

    /**
     * Frees all variables held in this stack frame, and sets all the stack slots to <tt>null</tt>. Note that as the
     * stack slots are <tt>null</tt> the {@link #getStorageCell(StackVariable)} method will not return valid storage
     * cells any more. This method should therefore only be used to clean up the stack frame when it is no longer
     * needed.
     */
    public void free()
    {
        for (int i = 0; i < bindings.length; i++)
        {
            if (bindings[i] != null)
            {
                bindings[i].free();
                bindings[i] = null;
            }
        }
    }
}


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

import java.net.URL;
import java.util.List;

import org.hobsoft.microbrowser.Form;
import org.hobsoft.microbrowser.FormNotFoundException;
import org.hobsoft.microbrowser.Link;
import org.hobsoft.microbrowser.MicrodataItem;
import org.hobsoft.microbrowser.MicrodataProperty;
import org.hobsoft.microbrowser.MicrodataPropertyNotFoundException;
import org.hobsoft.microbrowser.spi.AbstractHypermedia;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import static org.hobsoft.microbrowser.spi.Urls.newUrlOrNull;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * {@code MicrodataItem} adapter to a Selenium {@code WebElement}.
 */
class SeleniumMicrodataItem extends AbstractHypermedia implements MicrodataItem
{
	// ----------------------------------------------------------------------------------------------------------------
	// fields
	// ----------------------------------------------------------------------------------------------------------------

	private final WebDriver driver;

	private final WebElement element;

	// ----------------------------------------------------------------------------------------------------------------
	// constructors
	// ----------------------------------------------------------------------------------------------------------------

	SeleniumMicrodataItem(WebDriver driver, WebElement element)
	{
		this.driver = checkNotNull(driver, "driver");
		this.element = checkNotNull(element, "element");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// MicrodataItem methods
	// ----------------------------------------------------------------------------------------------------------------

	public URL getId()
	{
		return newUrlOrNull(element.getAttribute("itemid"));
	}

	public URL getType()
	{
		return newUrlOrNull(element.getAttribute("itemtype"));
	}

	public MicrodataProperty getProperty(String name)
	{
		List<WebElement> elements = element.findElements(byItemProp(name));

		if (elements.isEmpty())
		{
			throw new MicrodataPropertyNotFoundException(name);
		}

		return new SeleniumMicrodataProperty(elements.get(0));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// Hypermedia methods
	// ----------------------------------------------------------------------------------------------------------------

	public List<Link> getLinks(String rel)
	{
		List<WebElement> elements = element.findElements(byLink(rel));

		return Lists.transform(elements, new Function<WebElement, Link>()
		{
			public Link apply(WebElement element)
			{
				return new SeleniumLink(driver, element);
			}
		});
	}

	public Form getForm(String name)
	{
		List<WebElement> elements = element.findElements(byForm(name));

		if (elements.isEmpty())
		{
			throw new FormNotFoundException(name);
		}

		return new SeleniumForm(driver, elements.get(0));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// Unwrappable methods
	// ----------------------------------------------------------------------------------------------------------------

	public <T> T unwrap(Class<T> type)
	{
		checkArgument(WebElement.class.equals(type), "Cannot unwrap to: %s", type);

		return type.cast(element);
	}

	// ----------------------------------------------------------------------------------------------------------------
	// private methods
	// ----------------------------------------------------------------------------------------------------------------

	private static By byItemProp(String itemProp)
	{
		return By.cssSelector(String.format("[itemprop='%s']", itemProp));
	}

	private static By byLink(String rel)
	{
		return By.cssSelector(String.format("a[rel='%1$s'], link[rel='%1$s']", rel));
	}

	private static By byForm(String name)
	{
		return By.cssSelector(String.format("form[name='%s']", name));
	}
}


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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;

import jline.ConsoleReader;

import com.thesett.aima.logic.fol.Clause;
import com.thesett.aima.logic.fol.Parser;
import com.thesett.aima.logic.fol.Sentence;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.isoprologparser.PrologParser;
import com.thesett.aima.logic.fol.isoprologparser.PrologParserConstants;
import com.thesett.aima.logic.fol.isoprologparser.Token;
import com.thesett.aima.logic.fol.isoprologparser.TokenSource;
import com.thesett.common.parsing.SourceCodeException;
import com.thesett.common.parsing.SourceCodePosition;
import com.thesett.common.util.Sink;
import com.thesett.common.util.Source;

/**
 * ResolutionInterpreter implements an interactive Prolog like interpreter, built on top of a {@link ResolutionEngine}.
 * It implements a top-level interpreter loop where queries or domain clauses may be entered. Queries are resolved
 * against the current domain using the resolver, after they have been compiled.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Parse text into first order logic clauses.  {@link com.thesett.aima.logic.fol.Parser}.
 *
 Compile clauses down to their compiled form.  {@link Compiler}.
 *
 Add facts to the current knowledge base.  {@link com.thesett.aima.logic.fol.Resolver}.
 *
 Resolve queries against the current knowledge base.  {@link com.thesett.aima.logic.fol.Resolver}.
 *
 Print the variable bindings resulting from resolution.
 *      {@link com.thesett.aima.logic.fol.VariableAndFunctorInterner}.
 *

 *
 * @param  <T> The compiled clause type that the compiler produces.
 * @param  <Q> The compiled query type that the compiler produces.
 *
 * @author Rupert Smith
 */
public class ResolutionInterpreter<T, Q>
{
    /** Used for debugging purposes. */
    /* private static final Logger log = Logger.getLogger(ResolutionInterpreter.class.getName()); */

    /** The prompt to use in query mode. */
    public static final String QUERY_PROMPT = "?- ";

    /** The line continuation prompt for query mode. */
    public static final String MULTI_LINE_QUERY_PROMPT = "   ";

    /** The prompt to use in program mode. */
    public static final String PROGRAM_PROMPT = "";

    /** The line continuation prompt for program mode. */
    public static final String MULTI_LINE_PROGRAM_PROMPT = "   ";

    /** ASCII for a semicolon. */
    public static final int SEMICOLON = 59;

    /** Describes the possible parsing modes. */
    public enum Mode
    {
        Program(PROGRAM_PROMPT), ProgramMultiLine(MULTI_LINE_PROGRAM_PROMPT), Query(QUERY_PROMPT),
        QueryMultiLine(MULTI_LINE_QUERY_PROMPT);

        private final String prompt;

        Mode(String prompt)
        {
            this.prompt = prompt;
        }
    }

    /** Holds the JLine console reader. */
    private ConsoleReader consoleReader;

    /** Holds the resolution engine that the interpreter loop runs on. */
    ResolutionEngine<Clause, T, Q> engine;

    /** Holds the interactive parser that the interpreter loop runs on. */
    private final InteractiveParser parser;

    /** Holds the name of the predicate currently being parsed, clause by clause. */
    private Integer currentPredicateName;

    /** Holds the current interaction mode. */
    private Mode mode = Mode.Query;

    /**
     * Builds an interactive logical resolution interpreter from a parser, interner, compiler and resolver, encapsulated
     * as a resolution engine.
     *
     * @param engine The resolution engine. This must be using an {@link InteractiveParser}.
     */
    public ResolutionInterpreter(ResolutionEngine<Clause, T, Q> engine)
    {
        this.engine = engine;

        Parser<Clause, Token> parser = engine.getParser();

        if (!(parser instanceof InteractiveParser))
        {
            throw new IllegalArgumentException("'engine' must be built on an InteractiveParser.");
        }

        this.parser = (InteractiveParser) parser;
    }

    /**
     * Implements the top-level interpreter loop. This will parse and evaluate sentences until it encounters an CTRL-D
     * in query mode, at which point the interpreter will terminate.
     *
     * @throws SourceCodeException If malformed code is encountered.
     * @throws IOException         If an IO error is encountered whilst reading the source code.
     */
    public void interpreterLoop() throws IOException
    {
        // Display the welcome message.
        printIntroduction();

        // Initialize the JLine console.
        consoleReader = initializeCommandLineReader();

        // Used to buffer input, and only feed it to the parser when a PERIOD is encountered.
        TokenBuffer tokenBuffer = new TokenBuffer();

        // Used to hold the currently buffered lines of input, for the purpose of presenting this back to the user
        // in the event of a syntax or other error in the input.
        ArrayList<String> inputLines = new ArrayList<String>();

        // Used to count the number of lines entered.
        int lineNo = 0;

        while (true)
        {
            String line = null;

            try
            {
                line = consoleReader.readLine(mode.prompt);
                inputLines.add(line);

                // JLine returns null if CTRL-D is pressed. Exit program mode back to query mode, or exit the
                // interpreter completely from query mode.
                if ((line == null) && ((mode == Mode.Query) || (mode == Mode.QueryMultiLine)))
                {
                    /*log.fine("CTRL-D in query mode, exiting.");*/

                    System.out.println();

                    break;
                }
                else if ((line == null) && ((mode == Mode.Program) || (mode == Mode.ProgramMultiLine)))
                {
                    /*log.fine("CTRL-D in program mode, returning to query mode.");*/

                    System.out.println();
                    mode = Mode.Query;

                    continue;
                }

                // Check the input to see if a system directive was input. This is only allowed in query mode, and is
                // handled differently to normal queries.
                if (mode == Mode.Query)
                {
                    Source<Token> tokenSource =
                        new OffsettingTokenSource(TokenSource.getTokenSourceForString(line), lineNo);
                    parser.setTokenSource(tokenSource);

                    PrologParser.Directive directive = parser.peekAndConsumeDirective();

                    if (directive != null)
                    {
                        switch (directive)
                        {
                        case Trace:

                            /*log.fine("Got trace directive.");*/
                            break;

                        case Info:

                            /*log.fine("Got info directive.");*/
                            break;

                        case User:

                            /*log.fine("Got user directive, entering program mode.");*/
                            mode = Mode.Program;
                            break;
                        }

                        inputLines.clear();

                        continue;
                    }
                }

                // For normal queries, the query functor '?-' begins every statement, this is not passed back from
                // JLine even though it is used as the command prompt.
                if (mode == Mode.Query)
                {
                    line = QUERY_PROMPT + line;
                    inputLines.set(inputLines.size() - 1, line);
                }

                // Buffer input tokens until EOL is reached, of the input is terminated with a PERIOD.
                Source<Token> tokenSource =
                    new OffsettingTokenSource(TokenSource.getTokenSourceForString(line), lineNo);
                Token nextToken;

                while (true)
                {
                    nextToken = tokenSource.poll();

                    if (nextToken == null)
                    {
                        break;
                    }

                    if (nextToken.kind == PrologParserConstants.PERIOD)
                    {
                        /*log.fine("Token was PERIOD.");*/
                        mode = (mode == Mode.QueryMultiLine) ? Mode.Query : mode;
                        mode = (mode == Mode.ProgramMultiLine) ? Mode.Program : mode;

                        tokenBuffer.offer(nextToken);

                        break;
                    }
                    else if (nextToken.kind == PrologParserConstants.EOF)
                    {
                        /*log.fine("Token was EOF.");*/
                        mode = (mode == Mode.Query) ? Mode.QueryMultiLine : mode;
                        mode = (mode == Mode.Program) ? Mode.ProgramMultiLine : mode;

                        lineNo++;

                        break;
                    }

                    tokenBuffer.offer(nextToken);
                }

                // Evaluate the current token buffer, whenever the input is terminated with a PERIOD.
                if ((nextToken != null) && (nextToken.kind == PrologParserConstants.PERIOD))
                {
                    parser.setTokenSource(tokenBuffer);

                    // Parse the next clause.
                    Sentence<Clause> nextParsing = parser.parse();

                    /*log.fine(nextParsing.toString());*/
                    evaluate(nextParsing);

                    inputLines.clear();
                }
            }
            catch (SourceCodeException e)
            {
                SourceCodePosition sourceCodePosition = e.getSourceCodePosition().asZeroOffsetPosition();
                int startLine = sourceCodePosition.getStartLine();
                int endLine = sourceCodePosition.getEndLine();
                int startColumn = sourceCodePosition.getStartColumn();
                int endColumn = sourceCodePosition.getEndColumn();

                System.out.println("[(" + startLine + ", " + startColumn + "), (" + endLine + ", " + endColumn + ")]");

                for (int i = 0; i < inputLines.size(); i++)
                {
                    String errorLine = inputLines.get(i);
                    System.out.println(errorLine);

                    // Check if the line has the error somewhere in it, and mark the part of it that contains the error.
                    int pos = 0;

                    if (i == startLine)
                    {
                        for (; pos < startColumn; pos++)
                        {
                            System.out.print(" ");
                        }
                    }

                    if (i == endLine)
                    {
                        for (; pos <= endColumn; pos++)
                        {
                            System.out.print("^");
                        }

                        System.out.println();
                    }

                    if ((i > startLine) && (i < endLine))
                    {
                        for (; pos < errorLine.length(); pos++)
                        {
                            System.out.print("^");
                        }

                        System.out.println();
                    }
                }

                System.out.println();
                System.out.println(e.getMessage());
                System.out.println();

                inputLines.clear();
                tokenBuffer.clear();
            }
        }
    }

    /** Prints a welcome message. */
    private void printIntroduction()
    {
        System.out.println("| LoJiX Prolog.");
        System.out.println("| Copyright The Sett Ltd.");
        System.out.println("| Licensed under the Apache License, Version 2.0.");
        System.out.println("| //www.apache.org/licenses/LICENSE-2.0");
        System.out.println();
    }

    /**
     * Sets up the JLine console reader.
     *
     * @return A JLine console reader.
     *
     * @throws IOException If an IO error is encountered while reading the input.
     */
    private ConsoleReader initializeCommandLineReader() throws IOException
    {
        ConsoleReader reader = new ConsoleReader();
        reader.setBellEnabled(false);

        return reader;
    }

    /**
     * Evaluates a query against the resolver or adds a clause to the resolvers domain.
     *
     * @param  sentence The clausal sentence to run as a query or as a clause to add to the domain.
     *
     * @throws SourceCodeException If the query or domain clause fails to compile or link into the resolver.
     */
    private void evaluate(Sentence<Clause> sentence) throws SourceCodeException
    {
        Clause clause = sentence.getT();

        if (clause.isQuery())
        {
            engine.endScope();
            engine.compile(sentence);
            evaluateQuery();
        }
        else
        {
            // Check if the program clause is new, or a continuation of the current predicate.
            int name = clause.getHead().getName();

            if ((currentPredicateName == null) || (currentPredicateName != name))
            {
                engine.endScope();
                currentPredicateName = name;
            }

            addProgramClause(sentence);
        }
    }

    /**
     * Evaluates a query. In the case of queries, the interner is used to recover textual names for the resulting
     * variable bindings. The user is queried through the parser to if more than one solution is required.
     */
    private void evaluateQuery()
    {
        /*log.fine("Read query from input.");*/

        // Create an iterator to generate all solutions on demand with. Iteration will stop if the request to
        // the parser for the more ';' token fails.
        Iterator<Set<Variable>> i = engine.iterator();

        if (!i.hasNext())
        {
            System.out.println("false. ");

            return;
        }

        for (; i.hasNext();)
        {
            Set<Variable> solution = i.next();

            if (solution.isEmpty())
            {
                System.out.print("true");
            }
            else
            {
                for (Iterator<Variable> j = solution.iterator(); j.hasNext();)
                {
                    Variable nextVar = j.next();

                    String varName = engine.getVariableName(nextVar.getName());

                    System.out.print(varName + " = " + nextVar.getValue().toString(engine, true, false));

                    if (j.hasNext())
                    {
                        System.out.println();
                    }
                }
            }

            // Finish automatically if there are no more solutions.
            if (!i.hasNext())
            {
                System.out.println(".");

                break;
            }

            // Check if the user wants more solutions.
            try
            {
                int key = consoleReader.readVirtualKey();

                if (key == SEMICOLON)
                {
                    System.out.println(" ;");
                }
                else
                {
                    System.out.println();

                    break;
                }
            }
            catch (IOException e)
            {
                throw new IllegalStateException(e);
            }
        }
    }

    /**
     * Adds a program clause to the domain. Multiple program clauses making up a predicate are compiled as a unit, and
     * not individually. For this reason, Prolog expects clauses for the same predicate to appear together in source
     * code. When a clause with a name and arity not seen before is encountered, a new compiler scope is entered into,
     * and this compiler scope is closed at the EOF of the current input stream, or when another clause with a different
     * name and arity is seen.
     *
     * @param sentence The clause to add to the domain.
     */
    private void addProgramClause(Sentence<Clause> sentence) throws SourceCodeException
    {
        /*log.fine("Read program clause from input.");*/

        engine.compile(sentence);
    }

    /**
     * Used to buffer tokens.
     */
    private class TokenBuffer implements Source<Token>, Sink<Token>
    {
        LinkedList<Token> tokens = new LinkedList<Token>();

        public boolean offer(Token o)
        {
            return tokens.offer(o);
        }

        public Token poll()
        {
            return tokens.poll();
        }

        public Token peek()
        {
            return tokens.peek();
        }

        public void clear()
        {
            tokens.clear();
        }
    }

    /**
     * OffsettingTokenSource is a token source that automatically adds in line offsets to all tokens, to assist the
     * parser when operating interactively line-at-a-time.
     */
    private class OffsettingTokenSource implements Source<Token>
    {
        /** Holds the underlying token source. */
        private final Source<Token> source;

        /** Holds the current line offset to add to all tokens. */
        private final int lineOffset;

        /**
         * Wraps another token source.
         *
         * @param source The token source to wrap.
         */
        private OffsettingTokenSource(Source<Token> source, int lineOffset)
        {
            this.source = source;
            this.lineOffset = lineOffset;
        }

        /** {@inheritDoc} */
        public Token poll()
        {
            return addOffset(copyToken(source.poll()));
        }

        /** {@inheritDoc} */
        public Token peek()
        {
            return addOffset(copyToken(source.peek()));
        }

        private Token addOffset(Token token)
        {
            token.beginLine += lineOffset;
            token.endLine += lineOffset;

            return token;
        }

        private Token copyToken(Token token)
        {
            Token newToken = new Token();

            newToken.kind = token.kind;
            newToken.beginLine = token.beginLine;
            newToken.beginColumn = token.beginColumn;
            newToken.endLine = token.endLine;
            newToken.endColumn = token.endColumn;
            newToken.image = token.image;
            newToken.next = token.next;
            newToken.specialToken = token.specialToken;

            return newToken;
        }
    }
}


/*
 * 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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * TypeConverter provides methods to convert data between the different java primitive types. Some types can always be
 * converted to, for example anything can be converted to a String by calling its toString() method, provided that the
 * implementation of that method does not throw a runtime exception, or for primitive types by using the built in
 * convertion or the methods provided by the primitive wrapper classes. Other data can often be converted to other types
 * but only dependant on its value. For example, the integer 10 can be converted to the byte 10, but the integer 1000
 * cannot be converted to a byte because it is out of range.
 *
 *
TypeConverter provides methods to examine the primitive data types, plus Object and String, to see what other
 * types they can be converted to. The data will be converted to all the possible types that it can be converted to and
 * all of these will be returned packaged in a {@link MultiTypeData} object. This object will have a set of type flags
 * indicating which of its fields contain valid data as well as a native type flag that indicates what the type of the
 * original piece of data was.
 *
 *
Objects will be converted to other types by using their toString() methods; they will be convertable to the same
 * set of types that the String resulting from that mthod can be converted to. So if an objects toString() method return
 * the string "1" it will also be converted to a char, byte, short, int, long, float and double.
 *
 *
TypeConverter also handles the convertion back from multi type data objects into data objects with only one type,
 * provided such a convertion is possible. Object types are never converted to except in the case an object was
 * converted from an object and convertion back to an object of the same class is requested. Even though a class may
 * have a constructor that could be called with a primitive data type, for example ArrayList has a constructor taking a
 * single int argument specifying the initial size of list to create, the type converter would not attempt to call that
 * constructor for a multi type data object that can take a legal int value.
 *
 *
Convertion to simply typed objects always returns wrapper class objects. For example the following class would
 * both return objects of class Integer (provided such a convertion is possible) even though one requests an object of
 * class int.class, because the primitive type int cannot be returned as an Object:
 *
 *

 * Object result = TypeConverter.convert(data, int.class);
 * Object result = TypeConverter.convert(data, Integer.class);
 *

 *
 *
In some cases it is desirable to know what the best convertion from a multi type data object to a set of possible
 * classes is. For example if a bean has multiple setter methods for a given property that can take different argument
 * types, which is the best type convertion to use to be able to call one of the setter methods? The method
 * {@link #bestMatchingConversion} decides this. It tries to match the native type of the data to one of the classes and
 * if this is not possible it tries to find a convertion to one of the types, Object, boolean, byte, char, short, int,
 * long, float, double, String, amongst the set of classes in that order.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Convert simple Java types into multi typed data objects.
 *
Convert mutli typed data objects back into simply typed Java objects.
 *
Find the best convertion from a multi typed object to a set of possible classes.
 *

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

    /** Flag used to indicate that a multi typed data object can have the boolean type. */
    public static final int BOOLEAN = 1;

    /** Flag used to indicate that a multi typed data object can have the byte type. */
    public static final int BYTE = 2;

    /** Flag used to indicate that a multi typed data object can have the char type. */
    public static final int CHAR = 4;

    /** Flag used to indicate that a multi typed data object can have the short type. */
    public static final int SHORT = 8;

    /** Flag used to indicate that a multi typed data object can have the int type. */
    public static final int INT = 16;

    /** Flag used to indicate that a multi typed data object can have the long type. */
    public static final int LONG = 32;

    /** Flag used to indicate that a multi typed data object can have the float type. */
    public static final int FLOAT = 64;

    /** Flag used to indicate that a multi typed data object can have the double type. */
    public static final int DOUBLE = 128;

    /** Flag used to indicate that a multi typed data object can have the String type. */
    public static final int STRING = 256;

    /** Flag used to indicate that a multi typed data object can have the Object type. */
    public static final int OBJECT = 512;

    /**
     * Converts a boolean into a multi type data object.
     *
     * @param  b The boolean to convert.
     *
     * @return A multi type data object holding all the types that the boolean can be converted into.
     */
    public static MultiTypeData getMultiTypeData(boolean b)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Boolean.toString(b));
        result.nativeType = BOOLEAN;

        return result;
    }

    /**
     * Converts a byte into a multi type data object.
     *
     * @param  b The byte to convert.
     *
     * @return A multi type data object holding all the types that the byte can be converted into.
     */
    public static MultiTypeData getMultiTypeData(byte b)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Byte.toString(b));
        result.nativeType = BYTE;

        return result;
    }

    /**
     * Converts a char into a multi type data object.
     *
     * @param  c The char to convert.
     *
     * @return A multi type data object holding all the types that the char can be converted into.
     */
    public static MultiTypeData getMultiTypeData(char c)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Character.toString(c));
        result.nativeType = CHAR;

        return result;
    }

    /**
     * Converts a short into a multi type data object.
     *
     * @param  s The short to convert.
     *
     * @return A multi type data object holding all the types that the short can be converted into.
     */
    public static MultiTypeData getMultiTypeData(short s)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Short.toString(s));
        result.nativeType = SHORT;

        return result;
    }

    /**
     * Converts a int into a multi type data object.
     *
     * @param  i The int to convert.
     *
     * @return A multi type data object holding all the types that the int can be converted into.
     */
    public static MultiTypeData getMultiTypeData(int i)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Integer.toString(i));
        result.nativeType = INT;

        return result;
    }

    /**
     * Converts a long into a multi type data object.
     *
     * @param  l The long to convert.
     *
     * @return A multi type data object holding all the types that the long can be converted into.
     */
    public static MultiTypeData getMultiTypeData(long l)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Long.toString(l));
        result.nativeType = LONG;

        return result;
    }

    /**
     * Converts a float into a multi type data object.
     *
     * @param  f The float to convert.
     *
     * @return A multi type data object holding all the types that the float can be converted into.
     */
    public static MultiTypeData getMultiTypeData(float f)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Float.toString(f));
        result.nativeType = FLOAT;

        return result;
    }

    /**
     * Converts a double into a multi type data object.
     *
     * @param  d The double to convert.
     *
     * @return A multi type data object holding all the types that the double can be converted into.
     */
    public static MultiTypeData getMultiTypeData(double d)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(Double.toString(d));
        result.nativeType = DOUBLE;

        return result;
    }

    /**
     * Converts a String into a multi type data object.
     *
     *
This method will return the native type as String. Methods that use this one to convert other types via
     * String should override this with the correct native type.
     *
     * @param  s The String to convert.
     *
     * @return A multi type data object holding all the types that the String can be converted into.
     */
    public static MultiTypeData getMultiTypeData(String s)
    {
        MultiTypeData result = new MultiTypeData();

        // Start by assuming that the String can only be converted to a String.
        result.typeFlags = STRING;
        result.stringValue = s;

        // Assume that the native type is String. It is up to methods that call this one to override this if this is
        // not the case.
        result.nativeType = STRING;

        // Check if the string can be converted to a boolean.
        if ("true".equals(s))
        {
            result.booleanValue = true;
            result.typeFlags |= BOOLEAN;
        }
        else if ("false".equals(s))
        {
            result.booleanValue = false;
            result.typeFlags |= BOOLEAN;
        }

        // Check if the string can be converted to an int.
        try
        {
            result.intValue = Integer.parseInt(s);
            result.typeFlags |= INT;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags &= (Integer.MAX_VALUE - INT);
        }

        // Check if the string can be converted to a byte.
        try
        {
            result.byteValue = Byte.parseByte(s);
            result.typeFlags |= BYTE;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags = (Integer.MAX_VALUE - BYTE);
        }

        // Check if the string can be converted to a char.
        if (s.length() == 1)
        {
            result.charValue = s.charAt(0);
            result.typeFlags |= CHAR;
        }

        // Check if the string can be converted to a short.
        try
        {
            result.shortValue = Short.parseShort(s);
            result.typeFlags |= SHORT;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags = (Integer.MAX_VALUE - SHORT);
        }

        // Check if the string can be converted to a long.
        try
        {
            result.longValue = Long.parseLong(s);
            result.typeFlags |= LONG;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags = (Integer.MAX_VALUE - LONG);
        }

        // Check if the string can be converted to a float.
        try
        {
            result.floatValue = Float.parseFloat(s);
            result.typeFlags |= FLOAT;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags = (Integer.MAX_VALUE - FLOAT);
        }

        // Check if the string can be converted to a double.
        try
        {
            result.doubleValue = Double.parseDouble(s);
            result.typeFlags |= DOUBLE;
        }
        catch (NumberFormatException e)
        {
            // Exception noted so can be ignored.
            e = null;

            result.typeFlags = (Integer.MAX_VALUE - DOUBLE);
        }

        // Assume the string can never be converted to an object.
        return result;
    }

    /**
     * Converts a Object into a multi type data object.
     *
     * @param  o The Object to convert.
     *
     * @return A multi type data object holding all the types that the Object can be converted into.
     */
    public static MultiTypeData getMultiTypeData(Object o)
    {
        // Convert the value to a String and return the set of types that that String can be converted to.
        MultiTypeData result = getMultiTypeData(o.toString());
        result.nativeType = OBJECT;

        return result;
    }

    /**
     * Given a multi type data object and a class representing a type, this method attemps to return an object of that
     * class created from the multi type data. The exception to this rule is if the specified data type is a primtive
     * type, such as int.clas, then the returned object will be of the equivalent wrapper class type, Integer.class in
     * this case. This is because a primitive cannot be returned under an Object return type.
     *
     * @param     d The multi type data object to convert.
     * @param     c The class to convert to.
     *
     * @return    An object of the specified class which is the result of converting the multi type data object to that
     *            class.
     *
     * @exception ClassCastException Is thrown if the multi type data object cannot be converted to the specified class.
     */
    public static Object convert(MultiTypeData d, Class c)
    {
        // Check if it is an boolean convertion.
        if (((d.typeFlags & BOOLEAN) != 0) && (Boolean.TYPE.equals(c) || Boolean.class.equals(c)))
        {
            return d.booleanValue;
        }

        // Check if it is an int convertion.
        else if (((d.typeFlags & INT) != 0) && (Integer.TYPE.equals(c) || Integer.class.equals(c)))
        {
            return d.intValue;
        }

        // Check if it is an char convertion.
        else if (((d.typeFlags & CHAR) != 0) && (Character.TYPE.equals(c) || Character.class.equals(c)))
        {
            return d.charValue;
        }

        // Check if it is an byte convertion.
        else if (((d.typeFlags & BYTE) != 0) && (Byte.TYPE.equals(c) || Byte.class.equals(c)))
        {
            return d.byteValue;
        }

        // Check if it is an short convertion.
        else if (((d.typeFlags & SHORT) != 0) && (Short.TYPE.equals(c) || Short.class.equals(c)))
        {
            return d.shortValue;
        }

        // Check if it is an long convertion.
        else if (((d.typeFlags & LONG) != 0) && (Long.TYPE.equals(c) || Long.class.equals(c)))
        {
            return d.longValue;
        }

        // Check if it is an float convertion.
        else if (((d.typeFlags & FLOAT) != 0) && (Float.TYPE.equals(c) || Float.class.equals(c)))
        {
            return d.floatValue;
        }

        // Check if it is an double convertion.
        else if (((d.typeFlags & DOUBLE) != 0) && (Double.TYPE.equals(c) || Double.class.equals(c)))
        {
            return d.doubleValue;
        }

        // Check if it is a string convertion.
        else if (((d.typeFlags & STRING) != 0) && String.class.equals(c))
        {
            return d.stringValue;
        }

        // Check if it is an object convertion and th object types match.
        else if (((d.typeFlags & OBJECT) != 0) && d.objectValue.getClass().equals(c))
        {
            return d.objectValue;
        }

        // Throw a class cast exception if the multi data type cannot be converted to the specified class.
        else
        {
            throw new ClassCastException("The multi data type, " + d + ", cannot be converted to the class, " + c +
                ".");
        }
    }

    /**
     * For a set of types this method selects the best type to convert a given multi type data object into. This method
     * can be usefull when deciding which of several setter methods on a bean is the best one to convert the multi type
     * data obejct into before calling the beans setter method.
     *
     *
This method tries first to match the native type of the multi type data and then tries the other types in
     * this order: Object, boolean, char, byte, short, int, long, float, double, String. The Object type is only matched
     * if the multi type data object has a native Object type and the class of that object exactly matches one of the
     * possible classes to convert to.
     *
     *
If the set of classes to convert to contains a primitive class type, such as int.class, and this is matched
     * the primitive class will be returned, int.class in this case. If the set of classes to convert to contains a
     * wrapper class, such as Integer.class, and this is matched then this will be returned. The primitive classes are
     * always matched first so if both the primitive and wrapper versions are in the set to be matched then the
     * primitive version will be returned.
     *
     * @param  d     The multi type data object to test for best convertion.
     * @param  types The possible set of classes to convert to.
     *
     * @return The best matching class or null if none can be matched.
     */
    public static Class bestMatchingConversion(MultiTypeData d, Collection<Class> types)
    {
        /*log.fine("public static Class bestMatchingConvertion(MultiTypeData d, Set<Class> types): called");*/
        /*log.fine("d = " + d);*/
        /*log.fine("types = " + types);*/

        // Try to match the native type first before trying the convertions.
        switch (d.nativeType)
        {
        case OBJECT:
        {
            // Check if the matching Object class can be found in the set of possible classes.
            if (types.contains(d.objectValue.getClass()))
            {
                return d.objectValue.getClass();
            }

            break;
        }

        case BOOLEAN:
        {
            // Check if boolean is in the set of possible classes to match.
            if (types.contains(boolean.class))
            {
                return boolean.class;
            }
            else if (types.contains(Boolean.class))
            {
                return Boolean.class;
            }

            break;
        }

        case CHAR:
        {
            // Check if char is in the set of possible classes to match.
            if (types.contains(char.class))
            {
                return char.class;
            }
            else if (types.contains(Character.class))
            {
                return Character.class;
            }

            break;
        }

        case BYTE:
        {
            // Check if byte is in the set of possible classes to match.
            if (types.contains(byte.class))
            {
                return byte.class;
            }
            else if (types.contains(Byte.class))
            {
                return Byte.class;
            }

            break;
        }

        case SHORT:
        {
            // Check if short is in the set of possible classes to match.
            if (types.contains(short.class))
            {
                return short.class;
            }
            else if (types.contains(Short.class))
            {
                return Short.class;
            }

            break;
        }

        case INT:
        {
            // Check if int is in the set of possible classes to match.
            if (types.contains(int.class))
            {
                return int.class;
            }
            else if (types.contains(Integer.class))
            {
                return Integer.class;
            }

            break;
        }

        case LONG:
        {
            // Check if long is in the set of possible classes to match.
            if (types.contains(long.class))
            {
                return long.class;
            }
            else if (types.contains(Long.class))
            {
                return Long.class;
            }

            break;
        }

        case FLOAT:
        {
            // Check if float is in the set of possible classes to match.
            if (types.contains(float.class))
            {
                return float.class;
            }
            else if (types.contains(Float.class))
            {
                return Float.class;
            }

            break;
        }

        case DOUBLE:
        {
            // Check if double is in the set of possible classes to match.
            if (types.contains(double.class))
            {
                return double.class;
            }
            else if (types.contains(Double.class))
            {
                return Double.class;
            }

            break;
        }

        case STRING:
        {
            // Check if String is in the set of possible classes to match.
            if (types.contains(String.class))
            {
                return String.class;
            }

            break;
        }

        default:
        {
            throw new IllegalStateException("Unknown MultiTypeData type.");
        }
        }

        // Check if the multi type can be converted to a boolean and boolean is in the set of possible convertions.
        if (((d.typeFlags & BOOLEAN) != 0) && types.contains(boolean.class))
        {
            return boolean.class;
        }
        else if (((d.typeFlags & BOOLEAN) != 0) && types.contains(Boolean.class))
        {
            return Boolean.class;
        }

        // Check if the multi type can be converted to a byte and byte is in the set of possible convertions.
        else if (((d.typeFlags & BYTE) != 0) && types.contains(byte.class))
        {
            return byte.class;
        }
        else if (((d.typeFlags & BYTE) != 0) && types.contains(Byte.class))
        {
            return Byte.class;
        }

        // Check if the multi type can be converted to a char and char is in the set of possible convertions.
        else if (((d.typeFlags & CHAR) != 0) && types.contains(char.class))
        {
            return char.class;
        }
        else if (((d.typeFlags & CHAR) != 0) && types.contains(Character.class))
        {
            return Character.class;
        }

        // Check if the multi type can be converted to a short and short is in the set of possible convertions.
        else if (((d.typeFlags & SHORT) != 0) && types.contains(short.class))
        {
            return short.class;
        }
        else if (((d.typeFlags & SHORT) != 0) && types.contains(Short.class))
        {
            return Short.class;
        }

        // Check if the multi type can be converted to a int and int is in the set of possible convertions.
        else if (((d.typeFlags & INT) != 0) && types.contains(int.class))
        {
            return int.class;
        }
        else if (((d.typeFlags & INT) != 0) && types.contains(Integer.class))
        {
            return Integer.class;
        }

        // Check if the multi type can be converted to a long and long is in the set of possible convertions.
        else if (((d.typeFlags & LONG) != 0) && types.contains(long.class))
        {
            return long.class;
        }
        else if (((d.typeFlags & LONG) != 0) && types.contains(Long.class))
        {
            return Long.class;
        }

        // Check if the multi type can be converted to a float and float is in the set of possible convertions.
        else if (((d.typeFlags & FLOAT) != 0) && types.contains(float.class))
        {
            return float.class;
        }
        else if (((d.typeFlags & FLOAT) != 0) && types.contains(Float.class))
        {
            return Float.class;
        }

        // Check if the multi type can be converted to a double and double is in the set of possible convertions.
        else if (((d.typeFlags & DOUBLE) != 0) && types.contains(double.class))
        {
            return double.class;
        }
        else if (((d.typeFlags & DOUBLE) != 0) && types.contains(Double.class))
        {
            return Double.class;
        }

        // Check if the multi type can be converted to a String and String is in the set of possible convertions.
        else if (((d.typeFlags & STRING) != 0) && types.contains(String.class))
        {
            return String.class;
        }

        // No matching type convertion found so return null.
        else
        {
            return null;
        }
    }

    /**
     * MultiTypeData encapsulates the primitive Java data types plus Object and String. It is used to hold data where it
     * is possible that the data could be represented using different types. For example 1 can be a byte, a character, a
     * short, an int, a long or a string, 6000000000 could only be a long or a string. All the possible types that a
     * piece of data can legally have are populated with that data and the set of type flags are used to indicate which
     * data types are populated.
     */
    public static class MultiTypeData
    {
        /** Holds the boolean value of this multi type object if it has one. */
        public boolean booleanValue;

        /** Holds the byte value of this multi type object if it has one. */
        public byte byteValue;

        /** Holds the char value of this multi type object if it has one. */
        public char charValue;

        /** Holds the short value of this multi type object if it has one. */
        public short shortValue;

        /** Holds the int value of this multi type object if it has one. */
        public int intValue;

        /** Holds the long value of this multi type object if it has one. */
        public long longValue;

        /** Holds the float value of this multi type object if it has one. */
        public float floatValue;

        /** Holds the double value of this multi type object if it has one. */
        public double doubleValue;

        /** Holds the String value of this multi type object if it has one. */
        public String stringValue;

        /** Holds the Object value of this multi type object if it has one. */
        public Object objectValue;

        /** Holds the set of type flags indicating which types this multi type object can take. */
        public int typeFlags;

        /** Holds the type flag indicating the type of the original data that this object was built from. */
        public int nativeType;

        /**
         * Prints the contents of this mutli type data to a string. This can be very usefull for debugging purposes.
         *
         * @return A string representation of this multi type data object.
         */
        public String toString()
        {
            List<String> resultList = new ArrayList<String>();

            if ((typeFlags & BOOLEAN) != 0)
            {
                resultList.add(booleanValue + " : boolean");
            }

            if ((typeFlags & CHAR) != 0)
            {
                resultList.add(charValue + " : char");
            }

            if ((typeFlags & BYTE) != 0)
            {
                resultList.add(byteValue + " : byte");
            }

            if ((typeFlags & SHORT) != 0)
            {
                resultList.add(shortValue + " : short");
            }

            if ((typeFlags & INT) != 0)
            {
                resultList.add(intValue + " : int");
            }

            if ((typeFlags & LONG) != 0)
            {
                resultList.add(longValue + " : long");
            }

            if ((typeFlags & FLOAT) != 0)
            {
                resultList.add(floatValue + " : float");
            }

            if ((typeFlags & DOUBLE) != 0)
            {
                resultList.add(doubleValue + " : double");
            }

            if ((typeFlags & STRING) != 0)
            {
                resultList.add(stringValue + " : String");
            }

            if ((typeFlags & OBJECT) != 0)
            {
                resultList.add(objectValue.toString() + " : Object");
            }

            String result = "{ ";

            for (Iterator<String> i = resultList.iterator(); i.hasNext();)
            {
                result += i.next() + (i.hasNext() ? ", " : "");
            }

            result += " }";

            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.webapp.tags;

import javax.servlet.jsp.PageContext;

/**
 * ScopeHelper sets up an interface on top of web variable scopes, abstracting them as simply 'get' and 'put'. By
 * specifying a scope name, 'page', 'request', 'session' or 'application' and a refering page context, this abstraction
 * is set up.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Read from web scopes.
 *
 Write to web scopes.
 *

 *
 * @author Rupert Smith
 */
public class ScopeHelper
{
    /** Holds the web scope identifier. */
    private int scope;

    /** Holds the page context that the web scopes are refered to from. */
    private final PageContext pageContext;

    /**
     * Creates a scope helper on the named scope from the specified page context.
     *
     * @param scope       The name of the web scope.
     * @param pageContext The page context refering to the scope.
     */
    public ScopeHelper(String scope, PageContext pageContext)
    {
        // Keep the page context.
        this.pageContext = pageContext;

        // Set up the scope identifier.
        if (scope.equalsIgnoreCase("page"))
        {
            this.scope = PageContext.PAGE_SCOPE;
        }
        else if (scope.equalsIgnoreCase("request"))
        {
            this.scope = PageContext.REQUEST_SCOPE;
        }
        else if (scope.equalsIgnoreCase("session"))
        {
            this.scope = PageContext.SESSION_SCOPE;
        }
        else if (scope.equalsIgnoreCase("application"))
        {
            this.scope = PageContext.APPLICATION_SCOPE;
        }
        else
        {
            throw new IllegalArgumentException("Scope must be one of: page, request, session or application.");
        }
    }

    /**
     * Inserts an object into the scope.
     *
     * @param name  The name of the variable to set.
     * @param value The value to set.
     */
    public void put(String name, Object value)
    {
        pageContext.setAttribute(name, value, scope);
    }

    /**
     * Retrieves an object from the scope, or null if it cannot be found.
     *
     * @param  name The name of the variable to fetch.
     *
     * @return The variables value from the scope, or null if it cannot be found.
     */
    public Object get(String name)
    {
        return pageContext.getAttribute(name, scope);
    }
}


/*
Copyright (c) 2013, Groupon, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

Neither the name of GROUPON nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.groupon.novie.internal.engine.schema;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.base.Optional;
import com.google.common.collect.Maps;
import com.groupon.novie.engine.ColumnDataType;

public class DimensionTable extends SqlTable {

    private String dimensionName;
    private String displayTemplate;
    private AbstractSqlColumn defaultGroupByColumn;
    private AbstractSqlColumn defaultSearchColumn;
    private Optional<AbstractSqlColumn> defaultSortColumn;
    private Map<String, AbstractSqlColumn> informationNameToColumn;

    public DimensionTable(String tableName, String dimensionName) {
        super(tableName);
        this.dimensionName = dimensionName;
        informationNameToColumn = Maps.newLinkedHashMap();
        defaultSortColumn = Optional.absent();
    }

    public String getDisplayTemplate() {
        return displayTemplate;
    }

    public void setDisplayTemplate(String displayTemplate) {
        this.displayTemplate = displayTemplate;
    }

    public String getDimensionName() {
        return dimensionName;
    }

    public AbstractSqlColumn getDefaultGroupByColumn() {
        return defaultGroupByColumn;
    }

    public void setDefaultGroupByKey(String groupByColumnName) {
        if (!containsColumn(groupByColumnName)) {
            throw new IllegalArgumentException("Column " + groupByColumnName + " is not defined in table " + getTableName() + ".");
        }
        this.defaultGroupByColumn = getColumnByName(groupByColumnName).get();
    }

    public AbstractSqlColumn getDefaultSearchColumn() {
        return defaultSearchColumn;
    }

    public Optional<AbstractSqlColumn> getDefaultSortColumn() {
        return defaultSortColumn;
    }

    public void setDefaultSearchColumn(String defaultSearchColumnName) {
        Optional<AbstractSqlColumn> defaultSearch = getColumnByName(defaultSearchColumnName);
        if (!defaultSearch.isPresent()) {
            throw new IllegalArgumentException("Error while setting default search column: Column " + defaultSearchColumnName + " is not present in "
                    + this.getTableName() + ".");
        }
        this.defaultSearchColumn = getColumnByName(defaultSearchColumnName).get();
    }

    public void setDefaultSortColumn(String defaultSortColumnName) {
        Optional<AbstractSqlColumn> defaultSearch = getColumnByName(defaultSortColumnName);
        if (!defaultSearch.isPresent()) {
            throw new IllegalArgumentException("Error while setting default search column: Column " + defaultSortColumnName + " is not present in "
                    + this.getTableName() + ".");
        }
        this.defaultSortColumn = getColumnByName(defaultSortColumnName);
    }

    public void addTableColumn(String columnName, ColumnDataType type, String functionalName) {
        // TODO Sanity checks + exception
        PhysicalSqlColumn col = new PhysicalSqlColumn(this, columnName, functionalName, type, getNextColumnOrder());
        informationNameToColumn.put(functionalName.toUpperCase(), col);
        super.addTableColumn(col);
    }

    public AbstractSqlColumn getSqlTableColumnByInformationName(String functionalName) {
        return informationNameToColumn.get(functionalName.toUpperCase());
    }

    public List<AbstractSqlColumn> getInformationColumns() {
        List<AbstractSqlColumn> returnValue = new ArrayList<AbstractSqlColumn>(informationNameToColumn.size());
        for (Entry<String, AbstractSqlColumn> e : informationNameToColumn.entrySet()) {
            returnValue.add(e.getValue());
        }
        return Collections.unmodifiableList(returnValue);
    }

    @Override
    public String toString() {
        StringBuilder returnValue = new StringBuilder("Dimension ");
        returnValue.append(dimensionName);
        returnValue.append(" ");
        returnValue.append(this.getTableName());
        return returnValue.toString();
    }

    /**
     * Checks is the specified dimension is equivalent. Equivalent means same
     * informations (including type), same defaults, same links
     *
     * @param otherDim  The {@link DimensionTable} to compare for equivalence against
     * @param errors    An {@link Error} collection that will be populated with any
     *                  additional {@link Error}s
     * @return true if equivalent false otherwise
     */
    public boolean isEquivalent(DimensionTable otherDim, Collection<Error> errors) {
        if (this.informationNameToColumn.size() != otherDim.informationNameToColumn.size()) {
            errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - Not the same number of informations."));
            return false;
        }
        for (Entry<String, AbstractSqlColumn> entry : this.informationNameToColumn.entrySet()) {
            AbstractSqlColumn otherCol = otherDim.informationNameToColumn.get(entry.getKey());
            if (otherCol == null) {
                errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - Information named " + entry.getKey()
                        + " not found in " + otherDim.getDimensionName() + "."));
                return false;
            }
            if (!otherCol.getBusinessName().equals(entry.getValue().getBusinessName())) {
                errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - Information named " + entry.getKey()
                        + " have not the same name."));
                return false;
            }
            if (otherCol.getColumnType() != entry.getValue().getColumnType()) {
                errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - Information named " + entry.getKey()
                        + " are not the same type."));
                return false;
            }
        }
        if (!otherDim.getDefaultSearchColumn().getBusinessName().equals(this.getDefaultSearchColumn().getBusinessName())) {
            errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - \"default search\" are not the same."));
            return false;
        }
        if (!otherDim.getDefaultGroupByColumn().getBusinessName().equals(this.getDefaultGroupByColumn().getBusinessName())) {
            errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - \"default group by\" are not the same."));
            return false;
        }
        if (!otherDim.getLinkedTables().equals(this.getLinkedTables())) {
            errors.add(new Error("[" + this.getDimensionName() + "-" + otherDim.getDimensionName() + "] - Have not the same linked tables."));
            return false;
        }
        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.logic.fol.wam.printer;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.compiler.PositionalTermTraverser;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.STACK_ADDR;
import com.thesett.common.util.doublemaps.SymbolTable;
import com.thesett.text.api.model.TextTableModel;

/**
 * PositionPrinter prints some positional context information about functors and how they relate to their compiled form.
 * The position printer will prints the name of the functor, argument or variable and whether the current position is
 * within the functor head or last body element.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Print the positional context information.
 *

 *
 * @author Rupert Smith
 */
public class PositionPrinter extends BasePrinter
{
    /**
     * Creates a printer.
     *
     * @param interner    The name interner.
     * @param symbolTable The compiler symbol table.
     * @param traverser   The positional context traverser.
     * @param column      The column to print to.
     * @param table       The table to inform of cell sizes and positions.
     */
    public PositionPrinter(VariableAndFunctorInterner interner, SymbolTable<Integer, String, Object> symbolTable,
        PositionalTermTraverser traverser, int column, TextTableModel table)
    {
        super(interner, symbolTable, traverser, column, table);
    }

    /** {@inheritDoc} */
    protected void enterFunctor(Functor functor)
    {
        String head = traverser.isInHead() ? "/head" : "";
        String last = traverser.isLastBodyFunctor() ? "/last" : "";
        String symKey = functor.getSymbolKey().toString();

        if (traverser.isTopLevel())
        {
            addLineToRow("functor(" + symKey + ")" + head + last);
        }
        else
        {
            addLineToRow("arg(" + symKey + ")");
        }

        nextRow();
    }

    /** {@inheritDoc} */
    protected void leaveFunctor(Functor functor)
    {
        nextRow();
    }

    /** {@inheritDoc} */
    protected void enterVariable(Variable variable)
    {
        Integer allocation = (Integer) symbolTable.get(variable.getSymbolKey(), "allocation");
        String symKey = variable.getSymbolKey().toString();

        String allocString = "";

        if (allocation != null)
        {
            int slot = (allocation & (0xff));
            int mode = allocation >> 8;

            allocString = ((mode == STACK_ADDR) ? "Y" : "X") + slot;
        }

        addLineToRow("arg/var(" + symKey + ") " + allocString);
        nextRow();
    }
}