CUnifier.java

/*
 * @cond LICENSE
 * ######################################################################################
 * # LGPL License                                                                       #
 * #                                                                                    #
 * # This file is part of the LightJason AgentSpeak(L++)                                #
 * # Copyright (c) 2015-19, LightJason (info@lightjason.org)                            #
 * # This program is free software: you can redistribute it and/or modify               #
 * # it under the terms of the GNU Lesser General Public License as                     #
 * # published by the Free Software Foundation, either version 3 of the                 #
 * # License, or (at your option) any later version.                                    #
 * #                                                                                    #
 * # This program is distributed in the hope that it will be useful,                    #
 * # but WITHOUT ANY WARRANTY; without even the implied warranty of                     #
 * # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                      #
 * # GNU Lesser General Public License for more details.                                #
 * #                                                                                    #
 * # You should have received a copy of the GNU Lesser General Public License           #
 * # along with this program. If not, see http://www.gnu.org/licenses/                  #
 * ######################################################################################
 * @endcond
 */

package org.lightjason.agentspeak.language.unify;

import org.lightjason.agentspeak.agent.IAgent;
import org.lightjason.agentspeak.language.CCommon;
import org.lightjason.agentspeak.language.ILiteral;
import org.lightjason.agentspeak.language.ITerm;
import org.lightjason.agentspeak.language.execution.IContext;
import org.lightjason.agentspeak.language.execution.expression.IExpression;
import org.lightjason.agentspeak.language.fuzzy.CFuzzyValue;
import org.lightjason.agentspeak.language.fuzzy.IFuzzyValue;
import org.lightjason.agentspeak.language.variable.IVariable;

import javax.annotation.Nonnull;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * unification algorithm
 */
public final class CUnifier implements IUnifier
{
    /**
     * hash-based unify algorithm
     */
    private final IAlgorithm m_hashbased;
    /**
     * recursive unify algorithm
     */
    private final IAlgorithm m_recursive;

    /**
     * ctor
     */
    public CUnifier()
    {
        this( new CHash(), new CRecursive() );
    }

    /**
     * ctor
     *
     * @param p_hashbased hash-based unification algorithm
     * @param p_recursive recursive-based unification algorithm
     */
    public CUnifier( @Nonnull final IUnifier.IAlgorithm p_hashbased, @Nonnull final IUnifier.IAlgorithm p_recursive )
    {
        m_hashbased = p_hashbased;
        m_recursive = p_recursive;
    }

    // --- inheritance & context modification ------------------------------------------------------------------------------------------------------------------

    @Nonnull
    @Override
    public final Set<IVariable<?>> unify( @Nonnull final ILiteral p_source, @Nonnull final ILiteral p_target )
    {
        final Set<IVariable<?>> l_result = new HashSet<>();

        // try to unify exact or if not possible by recursive on the value set
        if ( !(
            p_target.structurehash() == p_source.structurehash()
            ? m_hashbased.unify(
                l_result,
                CCommon.flattenrecursive( p_source.orderedvalues() ),
                CCommon.flattenrecursive( p_target.orderedvalues() )
            )
            : m_recursive.unify(
                l_result,
                p_source.orderedvalues(),
                p_target.orderedvalues()
            ) ) )
            return Collections.emptySet();

        return l_result;
    }

    @Nonnull
    @Override
    public IFuzzyValue<Boolean> unify( @Nonnull final IContext p_context, @Nonnull final ILiteral p_literal, final long p_variables,
                                       @Nonnull final IExpression p_expression, final boolean p_parallel )
    {
        // get all possible variables
        final List<Set<IVariable<?>>> l_variables = this.variables( p_context.agent(), p_literal, p_variables );
        if ( l_variables.isEmpty() )
            return CFuzzyValue.from( false );

        // otherwise the expression must be checked, first match will be used
        final Set<IVariable<?>> l_result = parallelstream( l_variables.stream(), p_parallel )
                                                      .filter( i -> evaluateexpression( p_context, p_expression, i ) )
                                                      .findFirst()
                                                      .orElse( Collections.emptySet() );

        // if no match
        if ( l_result.isEmpty() )
            return CFuzzyValue.from( false );

        CCommon.updatecontext( p_context, l_result.stream() );
        return CFuzzyValue.from( true );
    }

    // ---------------------------------------------------------------------------------------------------------------------------------------------------------

    @Override
    public final int hashCode()
    {
        return m_hashbased.hashCode() ^ m_recursive.hashCode();
    }


    @Override
    public final boolean equals( final Object p_object )
    {
        return ( p_object instanceof IUnifier ) && ( this.hashCode() == p_object.hashCode() );
    }

    @Override
    public final String toString()
    {
        return MessageFormat.format( "hash-based unification: {0} / recursive unification: {1}", m_hashbased, m_recursive );
    }

    /**
     * execute stream in parallel
     *
     * @param p_stream stream
     * @param p_parallel parallel
     * @tparam T stream elements
     * @return modified stream
     */
    private static <T> Stream<T> parallelstream( final Stream<T> p_stream, final boolean p_parallel )
    {
        return p_parallel ? p_stream.parallel() : p_stream;
    }

    /**
     * evaluate expression
     *
     * @param p_context execution context (will be duplicated)
     * @param p_expression expression
     * @param p_variables current variables
     * @return boolean for correct evaluation
     */
    private static boolean evaluateexpression( final IContext p_context, final IExpression p_expression, final Set<IVariable<?>> p_variables )
    {
        final List<ITerm> l_return = new LinkedList<>();
        p_expression.execute(
            false, CCommon.updatecontext( p_context.duplicate(), p_variables.stream() ),
            Collections.emptyList(),
            l_return
        );

        return ( l_return.size() == 1 ) && ( l_return.get( 0 ).<Boolean>raw() );
    }

    /**
     * search all relevant literals within the agent beliefbase and unifies the variables
     *
     * @param p_agent agent
     * @param p_literal literal search
     * @param p_variablenumber number of unified variables
     * @return list of literal sets
     **/
    private List<Set<IVariable<?>>> variables( final IAgent<?> p_agent, final ILiteral p_literal, final long p_variablenumber )
    {
        return p_agent.beliefbase()
                      .stream( p_literal.negated(), p_literal.fqnfunctor() )
                      .filter( i -> i.emptyValues() == p_literal.emptyValues() )
                      .map( i -> this.unify( i, p_literal.deepcopy().<ILiteral>raw() ) )
                      .filter( i -> p_variablenumber == i.size() )
                      .collect( Collectors.toList() );
    }
}