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

import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;

import com.thesett.aima.state.BaseType;
import com.thesett.aima.state.InfiniteValuesException;
import com.thesett.aima.state.RandomInstanceNotSupportedException;
import com.thesett.aima.state.Type;
import com.thesett.aima.state.restriction.NotNullRestriction;
import com.thesett.aima.state.restriction.TypeRestriction;

/**
 * JavaType is a {@link Type} for basic Java types.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Encapsulate basic Java types.
 *

 *
 * @author Rupert Smith
 */
public class JavaType<T> extends BaseType<T> implements Type<T>, Serializable
{
    /** The default value of a boolean. */
    public static final boolean DEFAULT_BOOLEAN = false;

    /** The default value of a char. */
    public static final char DEFAULT_CHARACTER = '\u0000';

    /** The default value of a byte. */
    public static final byte DEFAULT_BYTE = 0;

    /** The default value of a short. */
    public static final short DEFAULT_SHORT = 0;

    /** The default value of an int. */
    public static final int DEFAULT_INTEGER = 0;

    /** The default value of a long. */
    public static final long DEFAULT_LONG = 0;

    /** The default value of a float. */
    public static final float DEFAULT_FLOAT = 0.0f;

    /** The default value of a double. */
    public static final double DEFAULT_DOUBLE = 0.0d;

    /** Ready made boolean type. */
    public static final Type<Boolean> BOOLEAN_TYPE = new JavaType<Boolean>(Boolean.class);

    /** Ready made boolean type. */
    public static final Type<Boolean> BOOLEAN_TYPE_NOT_NULL = new JavaType<Boolean>(Boolean.class, true);

    /** Ready made character type. */
    public static final Type<Character> CHARACTER_TYPE = new JavaType<Character>(Character.class);

    /** Ready made byte type. */
    public static final Type<Byte> BYTE_TYPE = new JavaType<Byte>(Byte.class);

    /** Ready made short type. */
    public static final Type<Short> SHORT_TYPE = new JavaType<Short>(Short.class);

    /** Ready made integer type. */
    public static final Type<Integer> INTEGER_TYPE = new JavaType<Integer>(Integer.class);

    /** Ready made integer type. */
    public static final Type<Integer> INTEGER_TYPE_NOT_NULL = new JavaType<Integer>(Integer.class, true);

    /** Ready made long type. */
    public static final Type<Long> LONG_TYPE = new JavaType<Long>(Long.class);

    /** Ready made float type. */
    public static final Type<Float> FLOAT_TYPE = new JavaType<Float>(Float.class);

    /** Ready made float type. */
    public static final Type<Float> FLOAT_TYPE_NOT_NULL = new JavaType<Float>(Float.class, true);

    /** Ready made double type. */
    public static final Type<Double> DOUBLE_TYPE = new JavaType<Double>(Double.class);

    /** Ready made string type. */
    public static final Type<String> STRING_TYPE = new JavaType<String>(String.class);

    /** Ready made string type. */
    public static final Type<String> STRING_TYPE_NOT_NULL = new JavaType<String>(String.class, true);

    /** Ready made date type. */
    public static final Type<Date> DATE_TYPE = new JavaType<Date>(Date.class);

    /** Used to generate random instances. */
    public static final Random RANDOM = new Random();

    /** Enumeration of the basic Java types. */
    public static enum BasicTypes
    {
        /** Boolean type. */
        BOOLEAN,

        /** Character type. */
        CHARACTER,

        /** Byte type. */
        BYTE,

        /** Short type. */
        SHORT,

        /** Integer type. */
        INTEGER,

        /** Long type. */
        LONG,

        /** Float type. */
        FLOAT,

        /** Double type. */
        DOUBLE,

        /** Object type. */
        OTHER
    }

    /** Holds the underlying class of the type. */
    private Class<T> underlyingClass;

    /** Holds the basic type that this type represents. */
    private BasicTypes type;

    /**
     * Creates a type for a java class.
     *
     * @param c The class to create a type wrapper for.
     */
    public JavaType(Class c)
    {
        underlyingClass = c;

        // Work out the basic java type based on the underlying class.
        setBasicType(underlyingClass);
    }

    /**
     * Creates a type for a java class, with the additional possible restriction that instantces of this type must not
     * be allowed to be null.
     *
     * @param c       The class to create a type wrapper for.
     * @param notNull <tt>true</tt> iff instances of this type are not legal if null.
     */
    public JavaType(Class c, boolean notNull)
    {
        underlyingClass = c;

        // Work out the basic java type based on the underlying class.
        setBasicType(underlyingClass);

        if (notNull)
        {
            restrictions = new LinkedList<TypeRestriction>();
            restrictions.add(new NotNullRestriction());
        }
    }

    /**
     * Creates a type for a java object.
     *
     * @param o The object to create a type wrapper for.
     */
    public JavaType(T o)
    {
        underlyingClass = (Class<T>) o.getClass();

        // Work out the basic java type based on the underlying class.
        setBasicType(underlyingClass);
    }

    /** {@inheritDoc} */
    public T getDefaultInstance()
    {
        Object result;

        // For basic Java types return the standard default value. For others, try the default constructor.
        switch (type)
        {
        case BOOLEAN:
            result = DEFAULT_BOOLEAN;
            break;

        case CHARACTER:
            result = DEFAULT_CHARACTER;
            break;

        case BYTE:
            result = DEFAULT_BYTE;
            break;

        case SHORT:
            result = DEFAULT_SHORT;
            break;

        case INTEGER:
            result = DEFAULT_INTEGER;
            break;

        case LONG:
            result = DEFAULT_LONG;
            break;

        case FLOAT:
            result = DEFAULT_FLOAT;
            break;

        case DOUBLE:
            result = DEFAULT_DOUBLE;
            break;

        case OTHER:
        default:

            try
            {
                result = underlyingClass.newInstance();
            }
            catch (InstantiationException e)
            {
                throw new IllegalStateException(e);
            }
            catch (IllegalAccessException e)
            {
                throw new IllegalStateException(e);
            }

            break;
        }

        return (T) result;
    }

    /** {@inheritDoc} */
    public T getRandomInstance() throws RandomInstanceNotSupportedException
    {
        Object result = null;

        // For basic Java types return the standard default value. For others, try the default constructor.
        switch (type)
        {
        case BOOLEAN:
            result = RANDOM.nextBoolean();
            break;

        case CHARACTER:
            result = (char) RANDOM.nextInt();
            break;

        case BYTE:
            result = (byte) RANDOM.nextInt();
            break;

        case SHORT:
            result = (short) RANDOM.nextInt();
            break;

        case INTEGER:
            result = RANDOM.nextInt();
            break;

        case LONG:
            result = RANDOM.nextLong();
            break;

        case FLOAT:
            result = RANDOM.nextFloat();
            break;

        case DOUBLE:
            result = RANDOM.nextDouble();
            break;

        case OTHER:
        default:

            if (String.class.equals(underlyingClass))
            {
                byte[] bytes = new byte[6];
                RANDOM.nextBytes(bytes);

                result = new String(bytes);
            }

            break;
        }

        if (result != null)
        {
            return (T) result;
        }
        else
        {
            throw new RandomInstanceNotSupportedException("Type does not support random instance creation.", null);
        }
    }

    /**
     * Should return a name that uniquely identifies the type.
     *
     * @return The name of the attribute type.
     */
    public String getName()
    {
        return underlyingClass.getName();
    }

    /**
     * Returns the underlying Java class that this is the type for.
     *
     * @return The underlying Java class that this is the type for.
     */
    public Class<T> getBaseClass()
    {
        return underlyingClass;
    }

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

    /**
     * Should determine how many different values an instance of the implementations type can take on.
     *
     * @return The number of possible values that an instance of this attribute can take on. If the value is -1 then
     *         this is to be interpreted as infinity.
     */
    public int getNumPossibleValues()
    {
        return -1;
    }

    /**
     * Should return all the different values that an instance of this type can take on.
     *
     * @return A set of values defining the possible value set for this attribute if this is finite.
     *
     * @throws InfiniteValuesException If the set of values cannot be listed because it is infinite.
     */
    public Set<T> getAllPossibleValuesSet() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("JavaType has infinite values.", null);
    }

    /**
     * Should return all the different values that an instance of this type can take on as an iterator over these
     * values. The set of values may be infinte if the iterator can lazily generate them as needed. If the number is
     * expected to be large it may be better to use this method to list the values than the
     * {@link #getAllPossibleValuesSet} if a lazy iterator is used because this will avoid generating a large collection
     * to hold all the possible values.
     *
     * @return An iterator over the set of attributes defining the possible value set for this attribute if this is
     *         finite or can be generated as required.
     *
     * @throws InfiniteValuesException If the set of values cannot be listed because it is infinite.
     */
    public Iterator<T> getAllPossibleValuesIterator() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("JavaType has infinite values.", null);
    }

    /**
     * Sets the basic type of this type depending on the class.
     *
     * @param c The underlying class.
     */
    private void setBasicType(Class c)
    {
        if (Boolean.class.equals(c))
        {
            type = BasicTypes.BOOLEAN;
        }
        else if (Character.class.equals(c))
        {
            type = BasicTypes.CHARACTER;
        }
        else if (Byte.class.equals(c))
        {
            type = BasicTypes.BYTE;
        }
        else if (Short.class.equals(c))
        {
            type = BasicTypes.SHORT;
        }
        else if (Integer.class.equals(c))
        {
            type = BasicTypes.INTEGER;
        }
        else if (Long.class.equals(c))
        {
            type = BasicTypes.LONG;
        }
        else if (Float.class.equals(c))
        {
            type = BasicTypes.FLOAT;
        }
        else if (Double.class.equals(c))
        {
            type = BasicTypes.DOUBLE;
        }
        else
        {
            type = BasicTypes.OTHER;
        }
    }
}


/*
 * 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.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.thesett.aima.attribute.impl.IdAttribute;
import com.thesett.aima.logic.fol.Clause;
import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.LinkageException;
import com.thesett.aima.logic.fol.LogicCompiler;
import com.thesett.aima.logic.fol.LogicCompilerObserver;
import com.thesett.aima.logic.fol.OpSymbol;
import com.thesett.aima.logic.fol.Parser;
import com.thesett.aima.logic.fol.Resolver;
import com.thesett.aima.logic.fol.Sentence;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
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.util.Filterator;
import com.thesett.common.util.Function;
import com.thesett.common.util.Source;

/**
 * ResolutionEngine combines together a logic {@link Parser}, a {@link VariableAndFunctorInterner} that acts as a symbol
 * table, a {@link LogicCompiler} and a {@link Resolver}, into a single unit.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *

 *
 * @param  <S> The source term type that the parser produces.
 * @param  <T> The compiled program type that the compiler produces.
 * @param  <Q> The compiled query type that the compiler produces.
 *
 * @author Rupert Smith
 */
public abstract class ResolutionEngine<S extends Clause, T, Q> implements VariableAndFunctorInterner, Parser<S, Token>,
    LogicCompiler<S, T, Q>, Resolver<T, Q>
{
    /** Holds the parser. */
    protected Parser<S, Token> parser;

    /** Holds the variable and functor symbol table. */
    protected VariableAndFunctorInterner interner;

    /** Holds the compiler. */
    protected LogicCompiler<S, T, Q> compiler;

    /** Holds the resolver. */
    protected Resolver<T, Q> resolver;

    /** Holds the observer for compiler outputs. */
    protected ChainedCompilerObserver chainedObserver = new ChainedCompilerObserver();

    /**
     * Builds an logical resolution engine from a parser, interner, compiler and resolver.
     *
     * @param parser   The parser.
     * @param interner The interner.
     * @param compiler The compiler.
     * @param resolver The resolver.
     */
    public ResolutionEngine(Parser<S, Token> parser, VariableAndFunctorInterner interner,
        LogicCompiler<S, T, Q> compiler, Resolver<T, Q> resolver)
    {
        this.parser = parser;
        this.interner = interner;
        this.compiler = compiler;
        this.resolver = resolver;

        compiler.setCompilerObserver(chainedObserver);
    }

    /**
     * Non-initializing constructor for the engine. This is provided so that engine implementations that cannot easily
     * set up their components in the call the the super constructor, can create the engine with this constructor and
     * take responsibility for setting up the components themselves.
     */
    protected ResolutionEngine()
    {
    }

    /**
     * Resets the engine to its default state. This will typically load any bootstrapping libraries of built-ins that
     * the engine requires, but otherwise set its domain to empty.
     */
    public abstract void reset();

    /**
     * Provides the resolution engines parser.
     *
     * @return The resolution engines parser.
     */
    public Parser<S, Token> getParser()
    {
        return parser;
    }

    /**
     * Provides the resolution engines interner.
     *
     * @return The resolution engines interner.
     */
    public VariableAndFunctorInterner getInterner()
    {
        return interner;
    }

    /**
     * Provides the resolution engines compiler.
     *
     * @return The resolution engines compiler.
     */
    public LogicCompiler<S, T, Q> getCompiler()
    {
        return compiler;
    }

    /**
     * Provides the resolution engines resolver.
     *
     * @return The resolution engines resolver.
     */
    public Resolver<T, Q> getResolver()
    {
        return resolver;
    }

    /**
     * Consults an input stream, reading first order logic clauses from it, and inserting them into the resolvers
     * knowledge base.
     *
     * @param  stream The input stream to consult.
     *
     * @throws SourceCodeException If any code read from the input stream fails to parse, compile or link.
     */
    public void consultInputStream(InputStream stream) throws SourceCodeException
    {
        // Create a token source to read from the specified input stream.
        Source<Token> tokenSource = TokenSource.getTokenSourceForInputStream(stream);
        getParser().setTokenSource(tokenSource);

        // Consult the type checking rules and add them to the knowledge base.
        while (true)
        {
            Sentence<S> sentence = getParser().parse();

            if (sentence == null)
            {
                break;
            }

            getCompiler().compile(sentence);
        }
    }

    /**
     * Prints all of the logic variables in the results of a query.
     *
     * @param  solution An iterable over the variables in the solution.
     *
     * @return All the variables printed as a string, one per line.
     */
    public String printSolution(Iterable<Variable> solution)
    {
        String result = "";

        for (Variable var : solution)
        {
            result += printVariableBinding(var) + "\n";
        }

        return result;
    }

    /**
     * Prints all of the logic variables in the results of a query.
     *
     * @param  variables An iterable over the variables in the solution.
     *
     * @return All the variables printed as a string, one per line.
     */
    public String printSolution(Map<String, Variable> variables)
    {
        String result = "";

        for (Map.Entry<String, Variable> entry : variables.entrySet())
        {
            result += printVariableBinding(entry.getValue()) + "\n";
        }

        return result;
    }

    /**
     * Prints a variable binding in the form 'Var = value'.
     *
     * @param  var The variable to print.
     *
     * @return The variable binding in the form 'Var = value'.
     */
    public String printVariableBinding(Term var)
    {
        return var.toString(getInterner(), true, false) + " = " + var.getValue().toString(getInterner(), false, true);
    }

    /**
     * Transforms an iterator over sets of variable bindings, resulting from a query, to an iterator over a map from the
     * string name of variables to their bindings, for the same sequence of query solutions.
     *
     * @param  solutions The resolution solutions to convert to map form.
     *
     * @return An iterator over a map from the string name of variables to their bindings, for the solutions.
     */
    public Iterable<Map<String, Variable>> expandResultSetToMap(Iterator<Set<Variable>> solutions)
    {
        return new Filterator<Set<Variable>, Map<String, Variable>>(solutions,
            new Function<Set<Variable>, Map<String, Variable>>()
            {
                public Map<String, Variable> apply(Set<Variable> variables)
                {
                    Map<String, Variable> results = new HashMap<String, Variable>();

                    for (Variable var : variables)
                    {
                        String varName = getInterner().getVariableName(var.getName());
                        results.put(varName, var);
                    }

                    return results;
                }
            });
    }

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

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

    /** {@inheritDoc} */
    public int internFunctorName(String name, int numArgs)
    {
        return interner.internFunctorName(name, numArgs);
    }

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

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

    /** {@inheritDoc} */
    public String getVariableName(int name)
    {
        return interner.getVariableName(name);
    }

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

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

    /** {@inheritDoc} */
    public String getFunctorName(int name)
    {
        return interner.getFunctorName(name);
    }

    /** {@inheritDoc} */
    public int getFunctorArity(int name)
    {
        return interner.getFunctorArity(name);
    }

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

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

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

    /** {@inheritDoc} */
    public void setTokenSource(Source<Token> tokenSource)
    {
        parser.setTokenSource(tokenSource);
    }

    /** {@inheritDoc} */
    public Sentence<S> parse() throws SourceCodeException
    {
        return parser.parse();
    }

    /** {@inheritDoc} */
    public void setOperator(String operatorName, int priority, OpSymbol.Associativity associativity)
    {
        parser.setOperator(operatorName, priority, associativity);
    }

    /** {@inheritDoc} */
    public void compile(Sentence<S> sentence) throws SourceCodeException
    {
        compiler.compile(sentence);
    }

    /** {@inheritDoc} */
    public void addToDomain(T term) throws LinkageException
    {
        resolver.addToDomain(term);
    }

    /** {@inheritDoc} */
    public void setQuery(Q query) throws LinkageException
    {
        resolver.setQuery(query);
    }

    /** {@inheritDoc} */
    public Set<Variable> resolve()
    {
        return resolver.resolve();
    }

    /** {@inheritDoc} */
    public Iterator<Set<Variable>> iterator()
    {
        return resolver.iterator();
    }

    /** {@inheritDoc} */
    public void setCompilerObserver(LogicCompilerObserver<T, Q> observer)
    {
        chainedObserver.setCompilerObserver(observer);
    }

    /** {@inheritDoc} */
    public void endScope() throws SourceCodeException
    {
        compiler.endScope();
    }

    /**
     * ChainedCompilerObserver implements the compiler observer for this resolution engine. Compiled programs are added
     * to the resolvers domain. Compiled queries are executed.
     *
     *
If a chained observer is set up, all compiler outputs are forwarded onto it.
     */
    private class ChainedCompilerObserver implements LogicCompilerObserver<T, Q>
    {
        /** Holds the chained observer for compiler outputs. */
        private LogicCompilerObserver<T, Q> observer;

        /**
         * Sets the chained observer for compiler outputs.
         *
         * @param observer The chained observer.
         */
        public void setCompilerObserver(LogicCompilerObserver<T, Q> observer)
        {
            this.observer = observer;
        }

        /** {@inheritDoc} */
        public void onCompilation(Sentence<T> sentence) throws SourceCodeException
        {
            if (observer != null)
            {
                observer.onCompilation(sentence);
            }

            getResolver().addToDomain(sentence.getT());
        }

        /** {@inheritDoc} */
        public void onQueryCompilation(Sentence<Q> sentence) throws SourceCodeException
        {
            if (observer != null)
            {
                observer.onQueryCompilation(sentence);
            }

            getResolver().setQuery(sentence.getT());
        }
    }
}


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

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.wam.compiler.DefaultBuiltIn;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import com.thesett.common.util.SizeableLinkedList;

/**
 * BaseBuiltIn provides a base-class on which built-in predicates may be implemented.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *

 *
 * @author Rupert Smith
 */
public abstract class BaseBuiltIn extends BuiltInFunctor
{
    /** Holds the default built in, for standard compilation and interners and symbol tables. */
    protected final DefaultBuiltIn defaultBuiltIn;

    /**
     * Creates the base built in on the specified functor.
     *
     * @param functor        The functor to create a built-in for.
     * @param defaultBuiltIn The default built in, for standard compilation and interners and symbol tables.
     */
    protected BaseBuiltIn(Functor functor, DefaultBuiltIn defaultBuiltIn)
    {
        super(functor);

        this.defaultBuiltIn = defaultBuiltIn;
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyArguments(Functor expression, boolean isFirstBody,
        FunctorName clauseName, int bodyNumber)
    {
        return null;
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyCall(Functor expression, boolean isFirstBody,
        boolean isLastBody, boolean chainRule, int permVarsRemaining)
    {
        return null;
    }
}


/**
 * $RCSfile$
 * $Revision: 2597 $
 * $Date: 2005-03-31 15:05:58 -0800 (Thu, 31 Mar 2005) $
 *
 * Copyright 2005 Jive Software.
 *
 * All rights reserved. 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.jivesoftware.whack;

import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.*;

import java.io.EOFException;
import java.net.SocketException;

/**
 * Reads XMPP XML packets from a socket and asks the component to process the
 * packets.
 *
 * @author Gaston Dombiak
 */
class SocketReadThread extends Thread {

    private ExternalComponent component;
    private boolean shutdown = false;

    XPPPacketReader reader = null;

    /**
     * Create dedicated read thread for this socket.
     *
     * @param component
     *            The component for which this thread is reading for
     * @param reader
     *            The reader to use for reading
     */
    public SocketReadThread(ExternalComponent component, XPPPacketReader reader) {
        super("Component socket reader");
        this.component = component;
        this.reader = reader;
    }

    /**
     * A dedicated thread loop for reading the stream and sending incoming
     * packets to the appropriate router.
     */
    public void run() {
        try {
            readStream();
        } catch (EOFException eof) {
            // Normal disconnect
        } catch (SocketException se) {
            // Do nothing if the exception occured while shutting down the
            // component otherwise
            // log the error and try to establish a new connection
            if (!shutdown) {
                component.getManager().getLog().error(se);
                component.connectionLost();
            }
        } catch (XmlPullParserException ie) {
            component.getManager().getLog().error(ie);
        } catch (Exception e) {
            component.getManager().getLog().warn(e);
        }
    }

    /**
     * Read the incoming stream until it ends.
     */
    private void readStream() throws Exception {
        while (!shutdown) {
            Element doc = reader.parseDocument().getRootElement();

            if (doc == null) {
                // Stop reading the stream since the server has sent an end of
                // stream element and
                // probably closed the connection
                return;
            }

            Packet packet;
            String tag = doc.getName();
            if ("message".equals(tag)) {
                packet = new Message(doc);
            } else if ("presence".equals(tag)) {
                packet = new Presence(doc);
            } else if ("iq".equals(tag)) {
                packet = getIQ(doc);
            } else {
                throw new XmlPullParserException(
                        "Unknown packet type was read: " + tag);
            }
            // Request the component to process the received packet
            component.processPacket(packet);
        }
    }

    private IQ getIQ(Element doc) {
        Element query = doc.element("query");
        if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
            return new Roster(doc);
        } else {
            return new IQ(doc);
        }
    }

    /**
     * Aks the thread to stop reading packets. The thread may not stop
     * immediatelly so if a socket exception occurs because the connection was
     * lost then no exception will be logged nor the component will try to
     * reestablish the connection.
     * <p>
     *
     * Once this method was sent this instance should be discarded and created a
     * new one if a new connection with the server is established.
     */
    public void shutdown() {
        shutdown = true;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.sakaiproject.nakamura.api.lite.util;

import org.apache.commons.lang.exception.NestableRuntimeException;

import java.io.IOException;
import java.net.InetAddress;
import java.security.SecureRandom;
import java.util.Random;

/**
 * This code is borrowed from OpenJPA for the Type1 UUI Generator
 *
 * UUID value generator. Type 1 generator is based on the time-based generator
 * in the Apache Commons Id project: http://jakarta.apache.org/commons/sandbox
 * /id/uuid.html The type 4 generator uses the standard Java UUID generator.
 *
 * The type 1 code has been vastly simplified and modified to replace the
 * ethernet address of the host machine with the IP, since we do not want to
 * require native libs and Java cannot access the MAC address directly.
 *
 * In spirit, implements the IETF UUID draft specification, found here:<br />
 * http://www1.ics.uci.edu/~ejw/authoring/uuid-guid/draft-leach-uuids-guids-01
 * .txt
 *
 * @author Abe White, Kevin Sutter
 * @nojavadoc
 * @since 0.3.3
 */
public class Type1UUID {

    // supported UUID types
    public static final int TYPE1 = 1;
    public static final int TYPE4 = 4;

    // indexes within the uuid array for certain boundaries
    private static final byte IDX_TIME_HI = 6;
    private static final byte IDX_TYPE = 6; // multiplexed
    private static final byte IDX_TIME_MID = 4;
    private static final byte IDX_TIME_LO = 0;
    private static final byte IDX_TIME_SEQ = 8;
    private static final byte IDX_VARIATION = 8; // multiplexed

    // indexes and lengths within the timestamp for certain boundaries
    private static final byte TS_TIME_LO_IDX = 4;
    private static final byte TS_TIME_LO_LEN = 4;
    private static final byte TS_TIME_MID_IDX = 2;
    private static final byte TS_TIME_MID_LEN = 2;
    private static final byte TS_TIME_HI_IDX = 0;
    private static final byte TS_TIME_HI_LEN = 2;

    // offset to move from 1/1/1970, which is 0-time for Java, to gregorian
    // 0-time 10/15/1582, and multiplier to go from 100nsec to msec units
    private static final long GREG_OFFSET = 0xB1D069B5400L;
    private static final long MILLI_MULT = 10000L;

    // type of UUID -- time based
    private final static byte TYPE_TIME_BASED = 0x10;

    // random number generator used to reduce conflicts with other JVMs, and
    // hasher for strings.
    private static Random RANDOM;

    // 4-byte IP address + 2 random bytes to compensate for the fact that
    // the MAC address is usually 6 bytes
    private static byte[] IP;

    // counter is initialized to 0 and is incremented for each uuid request
    // within the same timestamp window.
    private static int _counter;

    // current timestamp (used to detect multiple uuid requests within same
    // timestamp)
    private static long _currentMillis;

    // last used millis time, and a semi-random sequence that gets reset
    // when it overflows
    private static long _lastMillis = 0L;
    private static final int MAX_14BIT = 0x3FFF;
    private static short _seq = 0;

    private static boolean type1Initialized = false;

    /*
     * Initializer for type 1 UUIDs. Creates random generator and genenerates
     * the node portion of the UUID using the IP address.
     */
    private static synchronized void initializeForType1() {
        if (type1Initialized == true) {
            return;
        }
        // note that secure random is very slow the first time
        // it is used; consider switching to a standard random
        RANDOM = new SecureRandom();
        _seq = (short) RANDOM.nextInt(MAX_14BIT);

        byte[] ip = null;
        try {
            ip = InetAddress.getLocalHost().getAddress();
        } catch (IOException ioe) {
            throw new NestableRuntimeException(ioe);
        }

        IP = new byte[6];
        RANDOM.nextBytes(IP);
        System.arraycopy(ip, 0, IP, 2, ip.length);
        type1Initialized = true;
    }

    /*
     * Creates a type 1 UUID
     */
    public static byte[] next() {
        if (type1Initialized == false) {
            initializeForType1();
        }
        // set ip addr
        byte[] uuid = new byte[16];
        System.arraycopy(IP, 0, uuid, 10, IP.length);

        // Set time info. Have to do this processing within a synchronized
        // block because of the statics...
        long now = 0;
        synchronized (Type1UUID.class) {
            // Get the time to use for this uuid. This method has the side
            // effect of modifying the clock sequence, as well.
            now = getTime();

            // Insert the resulting clock sequence into the uuid
            uuid[IDX_TIME_SEQ] = (byte) ((_seq & 0x3F00) >>> 8);
            uuid[IDX_VARIATION] |= 0x80;
            uuid[IDX_TIME_SEQ + 1] = (byte) (_seq & 0xFF);

        }

        // have to break up time because bytes are spread through uuid
        byte[] timeBytes = toBytes(now);

        // Copy time low
        System.arraycopy(timeBytes, TS_TIME_LO_IDX, uuid, IDX_TIME_LO, TS_TIME_LO_LEN);
        // Copy time mid
        System.arraycopy(timeBytes, TS_TIME_MID_IDX, uuid, IDX_TIME_MID, TS_TIME_MID_LEN);
        // Copy time hi
        System.arraycopy(timeBytes, TS_TIME_HI_IDX, uuid, IDX_TIME_HI, TS_TIME_HI_LEN);
        // Set version (time-based)
        uuid[IDX_TYPE] |= TYPE_TIME_BASED; // 0001 0000

        return uuid;
    }

    private static byte[] toBytes(long n) {
        byte[] b = new byte[8];
        b[7] = (byte) (n);
        n >>>= 8;
        b[6] = (byte) (n);
        n >>>= 8;
        b[5] = (byte) (n);
        n >>>= 8;
        b[4] = (byte) (n);
        n >>>= 8;
        b[3] = (byte) (n);
        n >>>= 8;
        b[2] = (byte) (n);
        n >>>= 8;
        b[1] = (byte) (n);
        n >>>= 8;
        b[0] = (byte) (n);

        return b;
    }

    /**
     * Get the timestamp to be used for this uuid. Must be called from a
     * synchronized block.
     *
     * @return long timestamp
     */
    // package-visibility for testing
    static long getTime() {
        if (RANDOM == null)
            initializeForType1();
        long newTime = getUUIDTime();
        if (newTime <= _lastMillis) {
            incrementSequence();
            newTime = getUUIDTime();
        }
        _lastMillis = newTime;
        return newTime;
    }

    /**
     * Gets the appropriately modified timestamep for the UUID. Must be called
     * from a synchronized block.
     *
     * @return long timestamp in 100ns intervals since the Gregorian change
     *         offset
     */
    private static long getUUIDTime() {
        if (_currentMillis != System.currentTimeMillis()) {
            _currentMillis = System.currentTimeMillis();
            _counter = 0; // reset counter
        }

        // check to see if we have created too many uuid's for this timestamp
        if (_counter + 1 >= MILLI_MULT) {
            // Original algorithm threw exception. Seemed like overkill.
            // Let's just increment the timestamp instead and start over...
            _currentMillis++;
            _counter = 0;
        }

        // calculate time as current millis plus offset times 100 ns ticks
        long currentTime = (_currentMillis + GREG_OFFSET) * MILLI_MULT;

        // return the uuid time plus the artificial tick counter incremented
        return currentTime + _counter++;
    }

    /**
     * Increments the clock sequence for this uuid. Must be called from a
     * synchronized block.
     */
    private static void incrementSequence() {
        // increment, but if it's greater than its 14-bits, reset it
        if (++_seq > MAX_14BIT) {
            _seq = (short) RANDOM.nextInt(MAX_14BIT); // semi-random
        }
    }

}


package org.intellimate.izou.sdk.frameworks.music.player.template;

import org.intellimate.izou.events.EventListenerModel;
import org.intellimate.izou.events.EventModel;
import org.intellimate.izou.identification.Identifiable;
import org.intellimate.izou.identification.Identification;
import org.intellimate.izou.identification.IdentificationManagerM;
import org.intellimate.izou.resource.ResourceBuilderModel;
import org.intellimate.izou.resource.ResourceModel;
import org.intellimate.izou.sdk.Context;
import org.intellimate.izou.sdk.frameworks.common.resources.SelectorResource;
import org.intellimate.izou.sdk.frameworks.music.Capabilities;
import org.intellimate.izou.sdk.frameworks.music.events.PlayerCommand;
import org.intellimate.izou.sdk.frameworks.music.events.PlayerError;
import org.intellimate.izou.sdk.frameworks.music.events.StartMusicRequest;
import org.intellimate.izou.sdk.frameworks.music.events.StopMusic;
import org.intellimate.izou.sdk.frameworks.music.player.*;
import org.intellimate.izou.sdk.frameworks.music.resources.*;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.MuteEvent;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.StopEvent;
import org.intellimate.izou.sdk.frameworks.permanentSoundOutput.events.UnMuteEvent;
import org.intellimate.izou.sdk.frameworks.presence.events.LeavingEvent;
import org.intellimate.izou.sdk.output.OutputPlugin;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * use this class to actually play music.
 *

 * How to use it:

 * Usage is pretty straightforward. Extend this class an override the abstract methods.

 *  - To register further commands, use the getCommandHandler() method in the constructor.

 *  - To start playing, use the PlayerController.startPlaying() methods, never call the play() method directly!
 * Also, don't call the stopSound() method directly, use stopMusicPlayback().
 * All basic methods are implemented in this class,but to code special behaviour fell free to explore the other
 * classes and interfaces.

 * Note: the playback automatically stops when the user left.

 *

 * @author LeanderK
 * @version 1.0
 */
public abstract class Player<T> extends OutputPlugin<T> implements MusicProvider, ResourceBuilderModel, MusicHelper, EventListenerModel {
    private Playlist playlist = new Playlist(new ArrayList<>());
    private Volume volume = Volume.createVolume(50).orElse(null);
    private Progress progress = new Progress(0,0);
    private PlaybackState playbackState = PlaybackState.PLAY;
    private final Capabilities capabilities;
    private CompletableFuture<?> playingThread = null;
    private final boolean runsInPlay;
    private boolean isRunning = false;
    private boolean isPlaying = false;
    private final List<Identifiable> activators;
    private final CommandHandler commandHandler;
    private final InformationProvider informationProvider;
    final boolean isUsingJava;
    private final Lock lock = new ReentrantLock();
    Condition blockRequest = null;

    /**
     * creates a new output-plugin with a new id
     *  @param context context
     * @param id      the id of the new output-plugin. The ID of the InformationProvider is also based on this id
     *                (id + ".InformationProvider")
     * @param runsInPlay whether the termination of the play method should be treated as the termination the
     *                   music (STOP not PAUSE)
     * @param activators the activators which are able to start the Player if the Player is not able to start from
     *                   request from other addons
     * @param providesTrackInfo whether the Player provides TrackInfo
     * @param playbackShuffle whether the player is able to provide the info that the playback is shuffling
     * @param playbackRepeat whether the player is able to provide the info that the playback is repeating
     * @param playbackRepeatSong whether the player is able to provide the info that the playback is repeating the song
     * @param isUsingJava true if using java, false if not (and for example a C-library)
     */
    public Player(Context context, String id, boolean runsInPlay, List<Identifiable> activators,
                  boolean providesTrackInfo, boolean playbackShuffle, boolean playbackRepeat,
                  boolean playbackRepeatSong, boolean isUsingJava) {
        super(context, id);
        this.runsInPlay = runsInPlay;
        this.activators = activators;
        this.isUsingJava = isUsingJava;
        capabilities = new Capabilities();
        if (providesTrackInfo)
            capabilities.setProvidesTrackInfo(true);
        if (playbackShuffle)
            capabilities.setPlaybackShuffle(true);
        if (playbackRepeat)
            capabilities.setPlaybackRepeat(true);
        if (playbackRepeatSong)
            capabilities.setPlaybackRepeatSong(true);
        commandHandler = createCommandHandler();
        informationProvider = new InformationProvider(getContext(), getID(), this,
                commandHandler);
        getContext().getEvents().registerEventListener(Collections.singletonList(LeavingEvent.ID), this);
    }

    /**
     * creates a new output-plugin with a new id
     *  @param context context
     * @param id      the id of the new output-plugin. The ID of the InformationProvider is also based on this id
     *                (id + ".InformationProvider")
     * @param runsInPlay whether the termination of the play method should be treated as the termination the
     *                   music (STOP not PAUSE)
     * @param activator the activator which is able to start the Player if the Player is not able to start from
     *                   request from other addons
     * @param providesTrackInfo whether the Player provides TrackInfo
     * @param playbackShuffle whether the player is able to provide the info that the playback is shuffling
     * @param playbackRepeat whether the player is able to provide the info that the playback is repeating
     * @param playbackRepeatSong whether the player is able to provide the info that the playback is repeating the song
     * @param isUsingJava true if using java, false if not (and for example a C-library)
     */
    @SuppressWarnings("unused")
    public Player(Context context, String id, boolean runsInPlay, Identifiable activator, boolean providesTrackInfo,
                  boolean playbackShuffle, boolean playbackRepeat, boolean playbackRepeatSong, boolean isUsingJava) {
        this(context, id, runsInPlay, Collections.singletonList(activator), providesTrackInfo,
                playbackShuffle, playbackRepeat, playbackRepeatSong, isUsingJava);
    }

    /**
     * creates a new output-plugin with a new id
     *  @param context context
     * @param id      the id of the new output-plugin
     * @param runsInPlay whether the termination of the play method should be treated as the termination of playing the
     *                   music
     * @param playRequestTrackInfo whether the player is able to process PlayRequests with TrackInfo
     * @param providesTrackInfo whether the Player provides TrackInfo
     * @param playbackShuffle whether the player is able to provide the info that the playback is shuffling
     * @param playbackRepeat whether the player is able to provide the info that the playback is repeating
     * @param playbackRepeatSong whether the player is able to provide the info that the playback is repeating the song
     * @param isUsingJava true if using java, false if not (and for example a C-library)
     */
    @SuppressWarnings("unused")
    public Player(Context context, String id, boolean runsInPlay, boolean playRequestTrackInfo, boolean providesTrackInfo,
                  boolean playbackShuffle, boolean playbackRepeat, boolean playbackRepeatSong, boolean isUsingJava) {
        super(context, id);
        this.runsInPlay = runsInPlay;
        this.isUsingJava = isUsingJava;
        activators = null;
        capabilities = new Capabilities();
        capabilities.setPlayRequestOutside(true);
        if (playRequestTrackInfo)
            capabilities.setPlayRequestDetailed(true);
        if (providesTrackInfo)
            capabilities.setProvidesTrackInfo(true);
        if (playbackShuffle)
            capabilities.setPlaybackShuffle(true);
        if (playbackRepeat)
            capabilities.setPlaybackRepeat(true);
        if (playbackRepeatSong)
            capabilities.setPlaybackRepeatSong(true);
        commandHandler = createCommandHandler();
        informationProvider = new InformationProvider(getContext(), getID(), this,
                commandHandler);
    }

    /**
     * returns the CommandHandler
     * @return the CommandHandler
     */
    public CommandHandler getCommandHandler() {
        return commandHandler;
    }

    /**
     * true if playing and false if not
     *
     * @return tre if playing
     */
    @Override
    public boolean isOutputRunning() {
        if (runsInPlay) {
            return playingThread != null && !playingThread.isDone();
        } else {
            return isRunning;
        }
    }

    /**
     * true if playing and false if not
     *
     * @return tre if playing
     */
    @Override
    public boolean isPlaying() {
        if (runsInPlay)
            return isRunning;
        return isPlaying;
    }

    /**
     * true if using java, false if not (and for example a C-library)
     *
     * @return true if using java
     */
    @Override
    public boolean isUsingJava() {
        return isUsingJava;
    }

    /**
     * stops the playing/indicates that the playing stopped.
     *

     * this method has no effect if runsInPlay is enabled in the constructor.

     *

     */
    public void stopMusicPlayback() {
        if (runsInPlay || !isPlaying)
            return;
        lock.lock();
        try {
            if (blockRequest != null)
                blockRequest.signal();
        } finally {
            lock.unlock();
        }
        isPlaying = false;
        stopSound();
        endedSound();
        rollBackToDefault();
        super.stop();
    }

    /**
     * gets the current Playlist
     *
     * @return the current Playlist
     */
    @Override
    public Playlist getCurrentPlaylist() {
        return playlist;
    }

    /**
     * fires an update event which notifies that parameters have changed
     *
     * @param playlist the optional playlist
     */
    @Override
    public void updatePlayInfo(Playlist playlist) {
        this.playlist = playlist;
        MusicHelper.super.updatePlayInfo(playlist);
    }

    /**
     * checks if the trackInfo is an update and fires the appropriate Event. This method should not be called
     * without an active playlist, or an NullPointerException will be thrown.
     * this method:

     *     - fires nothing if the trackInfo equals the current trackInfo.

     *     - fires an trackInfoUpdate if the trackInfo contains information not found in the current.

     */
    @SuppressWarnings("unused")
    public void updateCurrentTrackInfo(TrackInfo trackInfo) {
        if (playlist.getCurrent().equals(trackInfo) ||
                playlist.getCurrent().isNew(trackInfo))
            return;
        this.playlist = playlist.update(playlist.getCurrent(), trackInfo);
        trackInfoUpdate(playlist, trackInfo);
    }

    /**
     * call this method if the trackInfo object in the playlist was updated. Only the trackinfo object will be sent via
     * Event
     * @param playlist the playlist
     * @param info the new trackInfo object
     */
    @SuppressWarnings("unused")
    public void trackInfoUpdate(Playlist playlist, TrackInfo info) {
        this.playlist = playlist;
        updatePlayInfo(info);
    }

    /**
     * gets the Volume
     *
     * @return the volume
     */
    @Override
    public Volume getVolume() {
        return volume;
    }

    /**
     * fires an update event which notifies that parameters have changed
     *
     * @param volume the optional volume
     */
    @Override
    public void updatePlayInfo(Volume volume) {
        if (this.volume.equals(volume))
            return;
        this.volume = volume;
        MusicHelper.super.updatePlayInfo(volume);
    }

    /**
     * gets the Progress
     *
     * @return the Progress
     */
    @Override
    public Progress getCurrentProgress() {
        return progress;
    }

    /**
     * fires an update event which notifies that parameters have changed
     *
     * @param progress the optional progress
     */
    @Override
    public void updatePlayInfo(Progress progress) {
        this.progress = progress;
        MusicHelper.super.updatePlayInfo(progress);
    }

    /**
     * gets the PlaybackState of the Player
     *
     * @return the PlaybackState
     */
    @Override
    public PlaybackState getPlaybackState() {
        return playbackState;
    }

    /**
     * signals that the playing paused
     */
    @SuppressWarnings("unused")
    public void pausePlaying() {
        switch (playbackState) {
            case PAUSE: return;
            default: playbackState = PlaybackState.PAUSE;
                updateStateInfo(playbackState);
        }
    }

    /**
     * signals that the playing resumed
     */
    @SuppressWarnings("unused")
    public void resumePlaying() {
        switch (playbackState) {
            case PLAY: return;
            default: playbackState = PlaybackState.PLAY;
                updateStateInfo(playbackState);
        }
    }

    /**
     * updates the Info about the current song
     * @param playlist the playlist, or null
     * @param progress the progress, or null
     * @param volume the volume, or null
     */
    @SuppressWarnings("unused")
    public void updatePlayInfo(Playlist playlist, Progress progress, Volume volume) {
        if (playlist != null)
            this.playlist = playlist;
        if (progress != null)
            this.progress = progress;
        if (volume != null)
            this.volume = volume;
        updatePlayInfo(playlist, progress, null, volume);
    }

    /**
     * fires an update event which notifies that parameters have changed
     *
     * @param playlist  the optional playlist
     * @param progress  the optional progress
     * @param trackInfo the optional trackInfo
     * @param volume    the optional volume
     */
    @Override
    public void updatePlayInfo(Playlist playlist, Progress progress, TrackInfo trackInfo, Volume volume) {
        if (playlist != null)
            this.playlist = playlist;
        if (progress != null)
            this.progress = progress;
        if (volume != null)
            this.volume = volume;
        MusicHelper.super.updatePlayInfo(playlist, progress, null, volume);
    }

    /**
     * gets the Capabilities of the Player
     *
     * @return and instance of Capabilities
     */
    @Override
    public Capabilities getCapabilities() {
        return capabilities;
    }

    /**
     * This method is called to register what resources the object provides.

     * just pass a List of Resources without Data in it.
     *
     * @return a List containing the resources the object provides
     */
    @Override
    public List<? extends ResourceModel> announceResources() {
        return informationProvider.announceResources();
    }

    /**
     * this method is called to register for what Events it wants to provide Resources.
     *

     * The Event has to be in the following format: It should contain only one Descriptor and and one Resource with the
     * ID "description", which contains an description of the Event.
     *

     *
     * @return a List containing ID's for the Events
     */
    @Override
    public List<? extends EventModel<?>> announceEvents() {
        return informationProvider.announceEvents();
    }

    /**
     * This method is called when an object wants to get a Resource.
     *

     * Don't use the Resources provided as arguments, they are just the requests.
     * There is a timeout after 1 second.
     *

     *
     * @param resources a list of resources without data
     * @param event     if an event caused the action, it gets passed. It can also be null.
     * @return a list of resources with data
     */
    @Override
    public List<ResourceModel> provideResource(List<? extends ResourceModel> resources, Optional<EventModel> event) {
        return informationProvider.provideResource(resources, event);
    }

    /**
     * method that uses the data from the OutputExtensions to generate a final output that will then be rendered.
     *
     * @param data       the data generated
     * @param eventModel the Event which caused the whole thing
     */
    @Override
    public void renderFinalOutput(List<T> data, EventModel eventModel) {
        if (StartMusicRequest.verify(eventModel, capabilities, this, activators)) {
            if (isOutputRunning()) {
                playerError(PlayerError.ERROR_ALREADY_PLAYING, eventModel.getSource());
            } else {
                handleEventRequest(eventModel);
            }
        } else if (eventModel.getListResourceContainer()
                .providesResource(Collections.singletonList(MusicUsageResource.ID))){
            if (isOutputRunning()) {
                eventModel.getListResourceContainer()
                        .provideResource(MusicUsageResource.ID)
                        .forEach(resourceModel ->
                                playerError(PlayerError.ERROR_ALREADY_PLAYING, resourceModel.getProvider()));
            } else {
                handleResourceRequest(eventModel);
            }
        } else {
            handleCommands(eventModel);
        }
    }

    /**
     * handles the a request to start playing music via Resource
     * @param eventModel the eventModel
     */
    private void handleResourceRequest(EventModel eventModel) {
        if (MusicUsageResource.isPermanent(eventModel)) {
            ResourceModel resourceModel = eventModel.getListResourceContainer()
                    .provideResource(MusicUsageResource.ID)
                    .stream()
                    .filter(MusicUsageResource::isPermanent)
                    .findAny()
                    .orElse(null);//should not happen

            //a partially applied function which takes an Identification an returns an Optional StartMusicRequest
            Function<Identification, Optional<StartMusicRequest>> getStartMusicRequest = own ->
                    StartMusicRequest.createStartMusicRequest(resourceModel.getProvider(), own);

            //if we have a trackInfo we create it with the trackInfo as a parameter
            getStartMusicRequest = TrackInfoResource.getTrackInfo(eventModel)
                    .map(trackInfo -> (Function<Identification, Optional<StartMusicRequest>>) own ->
                            StartMusicRequest.createStartMusicRequest(resourceModel.getProvider(), own, trackInfo))
                    .orElse(getStartMusicRequest);

            //if we have a trackInfo we create it with the playlist as a parameter
            getStartMusicRequest = PlaylistResource.getPlaylist(eventModel)
                    .map(playlist -> (Function<Identification, Optional<StartMusicRequest>>) own ->
                            StartMusicRequest.createStartMusicRequest(resourceModel.getProvider(), own, playlist))
                    .orElse(getStartMusicRequest);

            //composes a new Function which appends the Volume to the result
            getStartMusicRequest = getStartMusicRequest.andThen(
                    VolumeResource.getVolume(eventModel)
                    .flatMap(volume -> IdentificationManagerM.getInstance().getIdentification(this)
                            .map(identification -> new VolumeResource(identification, volume)))
                    .map(resource -> (Function<Optional<StartMusicRequest>, Optional<StartMusicRequest>>) opt ->
                                    opt.map(event -> (StartMusicRequest) event.addResource(resource))
                    )
                    .orElse(Function.identity())::apply);

            IdentificationManagerM.getInstance().getIdentification(this)
                    .flatMap(getStartMusicRequest::apply)
                    .ifPresent(this::fire);
        } else {
            play(eventModel);
            if (!runsInPlay) {
                blockRequest = lock.newCondition();
                lock.lock();
                try {
                    blockRequest.await(10, TimeUnit.MINUTES);
                } catch (InterruptedException e) {
                    debug("interrupted", e);
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * handles the commands encoded as Resources/EventIds
     * @param eventModel the eventModel to check
     */
    private void handleCommands(EventModel eventModel) {
        Consumer<Runnable> checkOrCall = runnable -> {
            List<ResourceModel> resourceModels = eventModel.getListResourceContainer()
                    .provideResource(SelectorResource.RESOURCE_ID);
            if (resourceModels.isEmpty()) {
                runnable.run();
            } else {
                resourceModels.stream()
                        .map(resourceModel -> resourceModel.getResource() instanceof Identification ?
                                ((Identification) resourceModel.getResource()) : null)
                        .filter(Objects::nonNull)
                        .filter(this::isOwner)
                        .findAny()
                        .ifPresent(id -> runnable.run());
            }
        };
        if (eventModel.containsDescriptor(MuteEvent.ID)) {
            checkOrCall.accept(this::mute);
        }
        if (eventModel.containsDescriptor(UnMuteEvent.ID)) {
            checkOrCall.accept(this::unMute);
        }
        if (eventModel.containsDescriptor(StopEvent.ID)) {
            checkOrCall.accept(this::stopMusicPlayback);
        }
        if (StopMusic.verify(eventModel, this)) {
            stopMusicPlayback();
        }
        if (PlayerCommand.verify(eventModel, this)) {
            getCommandHandler().handleCommandResources(eventModel);
        }
    }

    /**
     * handles the a request to start playing music via Event
     * @param eventModel the StartMusicRequest
     */
    private void handleEventRequest(EventModel eventModel) {
        playingThread = submit((Runnable) () -> {
                    //noinspection RedundantIfStatement
                    if (runsInPlay) {
                        isRunning = false;
                    } else {
                        isRunning = true;
                    }
                    isPlaying = true;
                    fireStartMusicRequest(eventModel);
                })
                .thenRun(() -> play(eventModel))
                .thenRun(() -> {
                    if (runsInPlay) {
                        isRunning = false;
                        isPlaying = false;
                        endedSound();
                    }
                });
    }

    /**
     * override this method if you want to change the command handler
     * @return the command handler
     */
    protected CommandHandler createCommandHandler() {
        return new CommandHandler(this, this, this, capabilities);
    }

    /**
     * this method will be called to create and fire the StartMusicRequest
     * @param eventModel the cause
     */
    protected void fireStartMusicRequest(EventModel eventModel) {
        Optional<Playlist> playlist = PlaylistResource.getPlaylist(eventModel);
        Optional<Progress> progress = ProgressResource.getProgress(eventModel);
        Optional<TrackInfo> trackInfo = TrackInfoResource.getTrackInfo(eventModel);
        Optional<Volume> volume = VolumeResource.getVolume(eventModel);
        startedSound(playlist.orElse(null), progress.orElse(null), trackInfo.orElse(null), volume.orElse(null), isUsingJava);
    }

    @Override
    public void stop() {
        stopMusicPlayback();
    }

    @Override
    public void eventFired(EventModel eventModel) {
        if (eventModel.containsDescriptor(LeavingEvent.GENERAL_DESCRIPTOR))
            stopMusicPlayback();
    }

    /**
     * sets every information into its default state (playlist, volume, etc...)
     */
    public void rollBackToDefault() {
        playlist = new Playlist(new ArrayList<>());
        volume = Volume.createVolume(50).orElse(null);
        progress = new Progress(0,0);
        playbackState = PlaybackState.PLAY;
        if (playingThread != null)
            playingThread.cancel(true);
        isRunning = false;
        isPlaying = false;
    }

    /**
     * this method call must mute the plugin.
     */
    public abstract void mute();

    /**
     * this method call must un-mute the plugin.
     */
    public abstract void unMute();

    /**
     * this method call must stop the sound.

     * NEVER CALL THIS METHOD DIRECTLY, USE {@link #stopMusicPlayback()}.
     */
    public abstract void stopSound();

    /**
     * this method will be called if a request was cached which was eligible to start the music.

     * please check the events resources for parameters (if expected).
     * @param eventModel the cause
     */
    public abstract void play(EventModel eventModel);
}


/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. 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.jivesoftware.smack;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jamppa.client.plugin.Plugin;
import org.jivesoftware.smack.Connection.ListenerWrapper;
import org.jivesoftware.smack.packet.Authentication;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.ping.packet.Ping;
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.PacketError.Condition;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;

/**
 * Listens for XML traffic from the XMPP server and parses it into packet
 * objects. The packet reader also invokes all packet listeners and collectors.
 *

 *
 * @see Connection#createPacketCollector
 * @see Connection#addPacketListener
 * @author Matt Tucker
 */
class PacketReader {

    private static Logger LOGGER = Logger.getLogger(PacketReader.class
            .getName());

    private Thread readerThread;
    private ExecutorService listenerExecutor;

    private XMPPConnection connection;
    private XPPPacketReader innerReader;
    private boolean reset = false;
    volatile boolean done;

    private String connectionID = null;

    protected PacketReader(final XMPPConnection connection) {
        this.connection = connection;
        this.init();
    }

    /**
     * Initializes the reader in order to be used. The reader is initialized
     * during the first connection and when reconnecting due to an abruptly
     * disconnection.
     */
    protected void init() {
        done = false;
        connectionID = null;

        readerThread = new Thread() {
            public void run() {
                parsePackets(this);
            }
        };
        readerThread.setName("Smack Packet Reader ("
                + connection.connectionCounterValue + ")");
        readerThread.setDaemon(true);

        // Create an executor to deliver incoming packets to listeners. We'll
        // use a single
        // thread with an unbounded queue.
        listenerExecutor = Executors
                .newSingleThreadExecutor(new ThreadFactory() {

                    public Thread newThread(Runnable runnable) {
                        Thread thread = new Thread(runnable,
                                "Smack Listener Processor ("
                                        + connection.connectionCounterValue
                                        + ")");
                        thread.setDaemon(true);
                        return thread;
                    }
                });

        resetParser();
    }

    /**
     * Starts the packet reader thread and returns once a connection to the
     * server has been established. A connection will be attempted for a maximum
     * of five seconds. An XMPPException will be thrown if the connection fails.
     *
     * @throws XMPPException
     *             if the server fails to send an opening stream back for more
     *             than five seconds.
     */
    synchronized public void startup() throws XMPPException {
        final List<Exception> errors = new LinkedList<Exception>();
        AbstractConnectionListener connectionErrorListener = new AbstractConnectionListener() {
            @Override
            public void connectionClosedOnError(Exception e) {
                errors.add(e);
            }
        };
        connection.addConnectionListener(connectionErrorListener);

        readerThread.start();
        // Wait for stream tag before returning. We'll wait a couple of seconds
        // before
        // giving up and throwing an error.
        try {
            // A waiting thread may be woken up before the wait time or a notify
            // (although this is a rare thing). Therefore, we continue waiting
            // until either a connectionID has been set (and hence a notify was
            // made) or the total wait time has elapsed.
            int waitTime = SmackConfiguration.getPacketReplyTimeout();
            wait(3 * waitTime);
        } catch (InterruptedException ie) {
            // Ignore.
        }

        connection.removeConnectionListener(connectionErrorListener);

        if (connectionID == null) {
            throw new XMPPException(
                    "Connection failed. No response from server.");
        } else if (!errors.isEmpty()) {
            throw new XMPPException(errors.iterator().next());
        } else {
            connection.connectionID = connectionID;
        }
    }

    /**
     * Shuts the packet reader down.
     */
    public void shutdown() {
        // Notify connection listeners of the connection closing if done hasn't
        // already been set.
        if (!done) {
            for (ConnectionListener listener : connection
                    .getConnectionListeners()) {
                try {
                    listener.connectionClosed();
                } catch (Exception e) {
                    // Catch and print any exception so we can recover
                    // from a faulty listener and finish the shutdown process
                    LOGGER.log(Level.ERROR,
                            "Error in listener while closing connection", e);
                }
            }
        }
        done = true;

        // Shut down the listener executor.
        listenerExecutor.shutdown();
    }

    /**
     * Resets the parser using the latest connection's reader. Reseting the
     * parser is necessary when the plain connection has been secured or when a
     * new opening stream element is going to be sent by the server.
     */
    private void resetParser() {
        try {
            innerReader = new XPPPacketReader();
            innerReader.setXPPFactory(XmlPullParserFactory.newInstance());
            innerReader.getXPPParser().setInput(connection.reader);
            reset = true;
        } catch (Exception xppe) {
            LOGGER.log(Level.WARN, "Error while resetting parser", xppe);
        }
    }

    private void startStream() throws XmlPullParserException, IOException {
        XmlPullParser xpp = innerReader.getXPPParser();
        for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
            eventType = xpp.next();
        }
        parseStreamStart(xpp);
    }

    private void parseStreamStart(XmlPullParser parser) {
        if ("jabber:client".equals(parser.getNamespace(null))) {
            for (int i = 0; i < parser.getAttributeCount(); i++) {
                if (parser.getAttributeName(i).equals("id")) {
                    connectionID = parser.getAttributeValue(i);
                    if (!"1.0".equals(parser.getAttributeValue("", "version"))) {
                        releaseConnectionIDLock();
                    }
                } else if (parser.getAttributeName(i).equals("from")) {
                    connection.config.setServiceName(parser
                            .getAttributeValue(i));
                }
            }
        }
    }

    /**
     * Parse top-level packets in order to process them further.
     *
     * @param thread
     *            the thread that is being used by the reader to parse incoming
     *            packets.
     */
    private void parsePackets(Thread thread) {
        try {
            while (!done) {
                if (reset) {
                    startStream();
                    LOGGER.debug("Started xmlstream...");
                    reset = false;
                    continue;
                }
                Element doc = innerReader.parseDocument().getRootElement();
                if (doc == null) {
                    connection.disconnect();
                    LOGGER.debug("End of xmlstream.");
                    continue;
                }
                Packet packet = null;
                LOGGER.debug("Processing packet " + doc.asXML());
                packet = parseFromPlugins(doc, packet);
                if (packet == null) {
                    packet = parseFromCore(doc);
                }
                if (packet != null) {
                    processPacket(packet);
                }
            }
        } catch (Exception e) {
            if (!done && !connection.isSocketClosed()) {
                connection.notifyConnectionError(e);
                if (!connection.isConnected()) {
                    releaseConnectionIDLock();
                }
            }
        }
    }

    private Packet parseFromCore(Element doc) throws XMPPException,
            IOException, XmlPullParserException, Exception {
        String tag = doc.getName();
        Packet packet = null;
        if ("message".equals(tag)) {
            packet = new Message(doc);
        } else if ("presence".equals(tag)) {
            packet = new Presence(doc);
        } else if ("iq".equals(tag)) {
            packet = parseIQ(doc);
        } else if ("error".equals(tag)) {
            throw new XMPPException(PacketParserUtils.parseStreamError(doc));
        } else if ("features".equals(tag)) {
            parseFeatures(doc);
        } else if ("proceed".equals(tag)) {
            connection.proceedTLSReceived();
            resetParser();
        } else if ("failure".equals(tag)) {
            parseFailure(doc);
        } else if ("challenge".equals(tag)) {
            parseChallenge(doc);
        } else if ("success".equals(tag)) {
            parseSuccess(doc);
        } else if ("compressed".equals(tag)) {
            connection.startStreamCompression();
            resetParser();
        } else {
            throw new XmlPullParserException("Unknown packet type was read: "
                    + tag);
        }
        return packet;
    }

    private Packet parseFromPlugins(Element doc, Packet packet) {
        for (Plugin plugin : connection.getPlugins()) {
            packet = plugin.parse(doc);
            if (packet != null) {
                break;
            }
        }
        return packet;
    }

    private void parseChallenge(Element doc) throws IOException {
        String challengeData = doc.getText();
        processPacket(new Challenge(doc));
        connection.getSASLAuthentication().challengeReceived(challengeData);
    }

    private void parseSuccess(Element doc) throws IOException {
        processPacket(new Success(doc));
        connection.packetWriter.openStream();
        resetParser();
        connection.getSASLAuthentication().authenticated();
    }

    private void parseFailure(Element failureEl) throws Exception {
        if ("urn:ietf:params:xml:ns:xmpp-tls".equals(failureEl.getNamespace()
                .getURI())) {
            throw new Exception("TLS negotiation has failed");
        } else if ("http://jabber.org/protocol/compress".equals(failureEl
                .getNamespace().getURI())) {
            connection.streamCompressionDenied();
        } else {
            final Failure failure = new Failure(failureEl);
            processPacket(failure);
            connection.getSASLAuthentication().authenticationFailed(
                    failure.getCondition());
        }
    }

    private IQ parseIQ(Element doc) {
        Element pingEl = doc.element(Ping.ELEMENT);
        if (pingEl != null
                && pingEl.getNamespace().getURI().equals(Ping.NAMESPACE)) {
            return new Ping(doc);
        }

        Element bindEl = doc.element(Bind.ELEMENT);
        if (bindEl != null
                && bindEl.getNamespace().getURI().equals(Bind.NAMESPACE)) {
            return new Bind(doc);
        }

        Element query = doc.element("query");
        if (query != null) {
            if ("jabber:iq:roster".equals(query.getNamespaceURI())) {
                return new Roster(doc);
            }
            if ("jabber:iq:auth".equals(query.getNamespaceURI())) {
                return new Authentication(doc);
            }
        }

        return new IQ(doc);
    }

    /**
     * Releases the connection ID lock so that the thread that was waiting can
     * resume. The lock will be released when one of the following three
     * conditions is met:
     *

     *
     * 1) An opening stream was sent from a non XMPP 1.0 compliant server 2)
     * Stream features were received from an XMPP 1.0 compliant server that does
     * not support TLS 3) TLS negotiation was successful
     *
     */
    synchronized private void releaseConnectionIDLock() {
        notify();
    }

    /**
     * Processes a packet after it's been fully parsed by looping through the
     * installed packet collectors and listeners and letting them examine the
     * packet to see if they are a match with the filter.
     *
     * @param packet
     *            the packet to process.
     */
    private void processPacket(Packet packet) {
        if (packet == null) {
            return;
        }

        // Loop through all collectors and notify the appropriate ones.
        for (PacketCollector collector : connection.getPacketCollectors()) {
            collector.processPacket(packet);
        }

        // Deliver the incoming packet to listeners.
        listenerExecutor.submit(new ListenerNotification(packet));
    }

    private void parseFeatures(Element doc) throws Exception {
        boolean startTLSReceived = false;
        Element startTLSEl = doc.element("starttls");
        if (startTLSEl != null
                && startTLSEl.getNamespace().getURI()
                        .equals("urn:ietf:params:xml:ns:xmpp-tls")) {
            startTLSReceived = true;
            connection.startTLSReceived(startTLSEl.element("required") != null);
        }

        Element mechanismsEl = doc.element("mechanisms");
        if (mechanismsEl != null
                && mechanismsEl.getNamespace().getURI()
                        .equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
            connection.getSASLAuthentication().setAvailableSASLMethods(
                    PacketParserUtils.parseMechanisms(mechanismsEl));
        }

        Element bindEl = doc.element("bind");
        if (bindEl != null
                && bindEl.getNamespace().getURI()
                        .equals("urn:ietf:params:xml:ns:xmpp-bind")) {
            connection.getSASLAuthentication().bindingRequired();
        }

        Element cEl = doc.element("c");
        if (cEl != null
                && cEl.getNamespace().getURI()
                        .equals("http://jabber.org/protocol/caps")) {
            String node = doc.attributeValue("node");
            String ver = doc.attributeValue("ver");
            if (ver != null && node != null) {
                String capsNode = node + "#" + ver;
                connection.setServiceCapsNode(capsNode);
            }
        }

        Element sessionEl = doc.element("session");
        if (sessionEl != null
                && sessionEl.getNamespace().getURI()
                        .equals("urn:ietf:params:xml:ns:xmpp-session")) {
            connection.getSASLAuthentication().sessionsSupported();
        }

        Element verEl = doc.element("ver");
        if (verEl != null
                && verEl.getNamespace().getURI()
                        .equals("urn:xmpp:features:rosterver")) {
            connection.setRosterVersioningSupported();
        }

        Element compressionEl = doc.element("compression");
        if (compressionEl != null
                && compressionEl.getNamespace().getURI()
                        .equals("http://jabber.org/features/compress")) {
            connection.setAvailableCompressionMethods(PacketParserUtils
                    .parseCompressionMethods(compressionEl));
        }

        for (Plugin plugin : connection.getPlugins()) {
            plugin.checkSupport(doc);
        }

        if (!connection.isSecureConnection()) {
            if (!startTLSReceived
                    && connection.getConfiguration().getSecurityMode() == ConnectionConfiguration.SecurityMode.required) {
                throw new XMPPException(
                        "Server does not support security (TLS), "
                                + "but security required by connection configuration.",
                        new PacketError(Condition.forbidden));
            }
        }

        if (!startTLSReceived
                || connection.getConfiguration().getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) {
            releaseConnectionIDLock();
        }
    }

    /**
     * A runnable to notify all listeners of a packet.
     */
    private class ListenerNotification implements Runnable {

        private Packet packet;

        public ListenerNotification(Packet packet) {
            this.packet = packet;
        }

        public void run() {
            for (ListenerWrapper listenerWrapper : connection.recvListeners
                    .values()) {
                try {
                    listenerWrapper.notifyListener(packet);
                } catch (Exception e) {
                    LOGGER.log(Level.ERROR, "Exception in packet listener", e);
                }
            }
        }
    }
}


/* Copyright 2014 Sven van der Meer <vdmeer.sven@mykolab.com>
 *
 * 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 de.vandermeer.execs.options;

import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

/**
 * Abstract (but fully featured) implementation of {@link ApplicationOption}.
 *
 * @author     Sven van der Meer <vdmeer.sven@mykolab.com>
 * @version    v0.4.0 build 170413 (13-Apr-17) for Java 1.8
 * @since      v0.2.0
 */
public abstract class AbstractApplicationOption<T> implements ApplicationOption<T> {

	/** A generated CLI option. */
	protected Option cliOption;

	/** The option description, as a short form, if CLI option is set identical to that description. */
	protected String descr;

	/** Long description for the option. */
	protected String descrLong;

	/** Option value read from CLI, blank or null if not set. */
	protected T valueCli;

	/** Option value set as default value, if none of the other values is set, can itself be blank. */
	protected T ValueDefault;

	/** Option value set by or from a property file or object, blank or null if not set. */
	protected T valueProperty;

	/** Flag stating if the option was in a parsed command line. */
	protected boolean inCli;

	/** Key used for this option for example in a property file or object or in a map. */
	protected String optionKey;

	/**
	 * Returns a new application option with description and long description but without option key and default value.
	 * @param description option description
	 * @param longDescription option long description
	 * @throws NullPointerException - if any description parameter is null
	 * @throws IllegalArgumentException - if any description parameter is empty
	 */
	public AbstractApplicationOption(String description, String longDescription){
		Validate.notBlank(description, "description cannot be empty");
		Validate.notBlank(longDescription, "long description cannot be empty");

		this.descr = description;
		this.descrLong = longDescription;
	}

	/**
	 * Returns a new application option with description, long description, default value, and option key.
	 * @param optionKey option key
	 * @param defaultValue option default value
	 * @param description option description
	 * @param longDescription option long description
	 * @throws NullPointerException - if optioneKey or any description parameter is null
	 * @throws IllegalArgumentException - if optionKey or any description parameter is empty
	 */
	public AbstractApplicationOption(String optionKey, T defaultValue, String description, String longDescription){
		this(defaultValue, description, longDescription);

		Validate.notBlank(optionKey, "option key cannot be empty");
		this.optionKey = optionKey;
	}

	/**
	 * Returns a new application option with description, long description, and default value but without option key.
	 * @param defaultValue option default value
	 * @param description option description
	 * @param longDescription option long description
	 * @throws NullPointerException - if any description parameter is null
	 * @throws IllegalArgumentException - if any description parameter is empty
	 */
	public AbstractApplicationOption(T defaultValue, String description, String longDescription){
		this(description, longDescription);
		this.ValueDefault = defaultValue;
	}

	@Override
	public Option getCliOption(){
		return this.cliOption;
	}

	@Override
	public T getCliValue(){
		return this.valueCli;
	}

	@Override
	public T getDefaultValue(){
		return this.ValueDefault;
	}

	@Override
	public String getDescription(){
		return this.descr;
	}

	@Override
	public String getDescriptionLong(){
		return this.descrLong;
	}

	@Override
	public String getOptionKey(){
		return this.optionKey;
	}

	@Override
	public T getPropertValue(){
		return this.valueProperty;
	}

	@Override
	public boolean inCli(){
		return this.inCli;
	}

	/**
	 * Sets the CLI option.
	 * @param option CLI option, only used if not null
	 */
	protected final void setCliOption(Option option){
		if(option!=null){
			this.cliOption = option;
		}
		if(this.cliOption.getDescription()!=null){
			this.descr = this.cliOption.getDescription();
		}
		else{
			this.cliOption.setDescription(this.descr);
		}
	}

	@Override
	public int setCliValue(CommandLine cmdLine){
		if(!cmdLine.hasOption(this.cliOptionAsString())){
			return 0;
		}

		String arg = cmdLine.getOptionValue(this.cliOptionAsString());
		this.inCli = cmdLine.hasOption(this.cliOptionAsString());

		if(StringUtils.isBlank(arg)){
			if(this.cliOption.hasArg() && this.cliOption.hasOptionalArg()==false){
				return -1;
			}
			return 0;
		}
		this.valueCli = this.convertValue(arg);
		return 0;
	}

	@Override
	public void setDefaultValue(T value){
		if(value!=null){
			this.ValueDefault = value;
		}
	}

	@Override
	public void setDescriptionLong(String longDescription){
		Validate.notBlank(longDescription, "long description cannot be empty");
		this.descrLong = longDescription;
	}

	@Override
	public int setPropertyValue(Properties properties) {
		if(properties==null || properties.get(this.getOptionKey())==null){
			if(this.cliOption.hasArg()){
				return -1;
			}
			return 0;
		}
		this.valueProperty = this.convertValue(properties.get(this.getOptionKey()));
		return 0;
	}
}