CLiteral.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;

import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.hash.Hasher;
import org.lightjason.agentspeak.common.CPath;
import org.lightjason.agentspeak.common.IPath;
import org.lightjason.agentspeak.grammar.CASTVisitorType;
import org.lightjason.agentspeak.grammar.CErrorListener;
import org.lightjason.agentspeak.grammar.IASTVisitorType;
import org.lightjason.agentspeak.grammar.IBaseParser;
import org.lightjason.agentspeak.grammar.TypeLexer;
import org.lightjason.agentspeak.grammar.TypeParser;
import org.lightjason.agentspeak.language.execution.IContext;
import org.lightjason.agentspeak.language.variable.IVariable;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * default generic literal class for agent beliefs
 * a literal consists of a functor, an optional list of values
 * e.g. velocity(50)
 */
public final class CLiteral implements ILiteral
{
    /**
     * serial id
     */
    private static final long serialVersionUID = -3253396471300120109L;
    /**
     * negation symbol
     */
    private static final String NEGATION = "~";
    /**
     * at symbol
     */
    private static final String AT = "@";
    /**
     * literal values
     */
    private final ImmutableMultimap<IPath, ITerm> m_values;
    /**
     * literal values as list
     */
    private final List<ITerm> m_orderedvalues;
    /**
     * literals functor
     */
    private final IPath m_functor;
    /**
     * negated option
     */
    private final boolean m_negated;
    /**
     * @ prefix is set
     */
    private final boolean m_at;
    /**
     * hash code
     */
    private final int m_hash;
    /**
     * hash of the structure
     */
    private final int m_structurehash;



    /**
     * ctor
     *
     * @param p_at @ prefix is set
     * @param p_negated negated flag
     * @param p_functor functor of the literal
     * @param p_values initial list of values
     */
    public CLiteral( final boolean p_at, final boolean p_negated, @Nonnull final IPath p_functor, @Nonnull final Collection<ITerm> p_values )
    {
        m_at = p_at;
        m_negated = p_negated;
        // create a full copy of the functor, because concurrency modification
        m_functor = new CPath( p_functor );

        // create immutable structures
        if ( !p_values.isEmpty() )
        {
            final Multimap<IPath, ITerm> l_values = LinkedListMultimap.create();
            p_values.forEach( i -> l_values.put( i.fqnfunctor(), i ) );
            m_values = ImmutableListMultimap.copyOf( l_values );
            m_orderedvalues = Collections.unmodifiableList( new ArrayList<>( p_values ) );
        }
        else
        {
            m_orderedvalues = Collections.emptyList();
            m_values = ImmutableListMultimap.of();
        }

        // calculates object hash value
        final Hasher l_hasher = CCommon.getTermHashing();
        l_hasher.putInt( m_functor.hashCode() );
        l_hasher.putBoolean( m_negated );
        l_hasher.putBoolean( m_at );
        m_orderedvalues.forEach( i -> l_hasher.putInt( i.hashCode() ) );
        m_hash = l_hasher.hash().asInt();

        // calculates the structure hash value of the value definition (need to start with value definition)
        final Hasher l_valuehasher = CCommon.getTermHashing();
        m_orderedvalues.forEach( i -> l_valuehasher.putInt( i.structurehash() ) );
        l_valuehasher.putBoolean( m_negated );
        l_valuehasher.putString( p_functor.path(), Charsets.UTF_8 );
        m_structurehash = l_valuehasher.hash().asInt();
    }

    /**
     * factory
     *
     * @param p_functor functor string
     * @param p_values value term
     * @return literal
     */
    public static ILiteral from( @Nonnull final String p_functor, @Nullable final ITerm... p_values )
    {
        return from(
            p_functor,
            ( Objects.isNull( p_values ) ) || ( p_values.length == 0 )
            ? Collections.emptySet()
            : Arrays.stream( p_values ).collect( Collectors.toList() )
        );
    }

    /**
     * factory
     *
     * @param p_functor functor string
     * @param p_values value term
     * @return literal
     */
    public static ILiteral from( @Nonnull final String p_functor, @Nonnull final Collection<ITerm> p_values )
    {
        return new CLiteral(
            p_functor.contains( AT ), p_functor.contains( NEGATION ), CPath.from( p_functor.replace( AT, "" ).replace( NEGATION, "" ) ),
            p_values
        );
    }

    /**
     * stream factory
     *
     * @param p_functor functor
     * @param p_values value stream
     * @return literal
     */
    public static ILiteral from( @Nonnull final String p_functor, @Nonnull final Stream<ITerm> p_values )
    {
        return from( p_functor, p_values.collect( Collectors.toList() ) );
    }

    /**
     * factory
     *
     * @param p_functor functor path
     * @param p_values values
     * @return literal
     */
    public static ILiteral from( @Nonnull final IPath p_functor, @Nullable final ITerm... p_values )
    {
        return from( false, false, p_functor, p_values );
    }

    /**
     * factory
     *
     * @param p_functor functor path
     * @param p_values values
     * @return literal
     */
    public static ILiteral from( @Nonnull final IPath p_functor, @Nonnull final Stream<ITerm> p_values )
    {
        return from( false, false, p_functor, p_values );
    }

    /**
     * factory
     *
     * @param p_at  at
     * @param p_negated negation
     * @param p_functor functor path
     * @param p_values vales
     * @return literal
     */
    public static ILiteral from( final boolean p_at, final boolean p_negated, @Nonnull final IPath p_functor, @Nullable final ITerm... p_values )
    {
        return from( p_at, p_negated, p_functor, ( Objects.isNull( p_values ) ) || ( p_values.length == 0 ) ? Stream.empty() : Arrays.stream( p_values ) );
    }

    /**
     * factory
     *
     * @param p_at  at
     * @param p_negated negation
     * @param p_functor functor path
     * @param p_values vales
     * @return literal
     */
    public static ILiteral from( final boolean p_at, final boolean p_negated, @Nonnull final IPath p_functor, @Nonnull final Stream<ITerm> p_values )
    {
        return new CLiteral( p_at, p_negated, p_functor, p_values.collect( Collectors.toList() ) );
    }

    /**
     * factory
     *
     * @param p_literal literal string
     * @return literal
     *
     * @throws Exception parsing and stream exception
     */
    @Nonnull
    public static ILiteral parse( @Nonnull final String p_literal ) throws Exception
    {
        return new CParser().parse( new ByteArrayInputStream( p_literal.getBytes( Charset.forName( "UTF-8" ) ) ) ).literal();
    }

    @Nonnull
    @Override
    public final Stream<ITerm> values( @Nullable final IPath... p_path )
    {
        return ( Objects.isNull( p_path ) ) || ( p_path.length < 1 )
               ? m_values.values().stream()
               : p_path.length == 1
                 ? m_values.asMap().get( p_path[0] ).stream()
                 : m_values.asMap().get( p_path[0] ).stream()
                           .filter( i -> i instanceof ILiteral )
                           .flatMap( i -> ( (ILiteral) i ).values( Arrays.copyOfRange( p_path, 1, p_path.length ) ) );
    }

    @Nonnull
    @Override
    public final Stream<ITerm> orderedvalues( @Nullable final IPath... p_path )
    {
        return ( Objects.isNull( p_path ) ) || ( p_path.length < 1 )
               ? m_orderedvalues.stream().sequential()
               : p_path.length == 1
                 ? m_orderedvalues.stream()
                                  .filter( i -> i.fqnfunctor().equals( p_path[0] ) ).sequential()
                 : m_orderedvalues.stream()
                                  .filter( i -> i.fqnfunctor().equals( p_path[0] ) )
                                  .filter( i -> i instanceof ILiteral )
                                  .map( ITerm::<ILiteral>raw )
                                  .filter( Objects::nonNull )
                                  .flatMap( i -> i.orderedvalues( Arrays.copyOfRange( p_path, 1, p_path.length ) ) );
    }

    @Override
    public final boolean emptyValues()
    {
        return m_values.isEmpty();
    }

    @Override
    public final int structurehash()
    {
        return m_structurehash;
    }

    @Override
    public final boolean negated()
    {
        return m_negated;
    }

    @Override
    public final boolean hasAt()
    {
        return m_at;
    }

    @Override
    public final boolean hasVariable()
    {
        return m_orderedvalues.parallelStream().anyMatch( ITerm::hasVariable );
    }

    @Nonnull
    @Override
    public final ILiteral unify( @Nonnull final IContext p_context )
    {
        return new CLiteral(
            m_at,
            m_negated,
            m_functor,
            m_orderedvalues.stream()
                           .map( i ->
                           {
                               if ( i instanceof IVariable<?> )
                               {
                                   final IVariable<?> l_variable = p_context.instancevariables().get( i.fqnfunctor() );
                                   return ( Objects.isNull( l_variable ) ) || ( l_variable.allocated() ) ? CRawTerm.from( l_variable ) : l_variable;
                               }
                               if ( i instanceof ILiteral )
                                   return ( (ILiteral) i ).unify( p_context );
                               return i;
                           } )
                           .collect( Collectors.toList() )
        );
    }

    @Nonnull
    @Override
    public final ILiteral allocate( @Nonnull final IContext p_context )
    {
        return new CLiteral(
            m_at,
            m_negated,
            m_functor,
            m_orderedvalues.stream()
                           .map( i ->
                           {
                               if ( i instanceof IVariable<?> )
                               {
                                   final IVariable<?> l_variable = p_context.instancevariables().get( i.fqnfunctor() );
                                   return Objects.isNull( l_variable )
                                          ? CRawTerm.EMPTY
                                          : l_variable;
                               }
                               if ( i instanceof ILiteral )
                                   return ( (ILiteral) i ).unify( p_context );
                               return i;
                           } )
                           .collect( Collectors.toList() )
        );
    }

    @Nonnull
    @Override
    public final String functor()
    {
        return m_functor.suffix();
    }

    @Nonnull
    @Override
    public final IPath functorpath()
    {
        return m_functor.subpath( 0, m_functor.size() - 1 );
    }

    @Nonnull
    @Override
    public final IPath fqnfunctor()
    {
        return m_functor;
    }

    @Nonnull
    @Override
    @SuppressWarnings( "unchecked" )
    public final <T> T raw()
    {
        return (T) this;
    }

    @Override
    public final int hashCode()
    {
        return m_hash;
    }

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

    @Nonnull
    @Override
    public final ILiteral shallowcopy( @Nullable final IPath... p_prefix )
    {
        return ( Objects.isNull( p_prefix ) ) || ( p_prefix.length == 0 )

               ? new CLiteral(
                   m_at, m_negated, m_functor,
                   m_values.values()
               )

               : new CLiteral(
                   m_at, m_negated, p_prefix[0].append( m_functor ),
                   m_values.values()
               );
    }

    @Nonnull
    @Override
    public final ILiteral shallowcopysuffix()
    {
        return new CLiteral(
            m_at, m_negated, CPath.from( m_functor.suffix() ),
            m_values.values()
        );
    }

    @Override
    public final String toString()
    {
        return MessageFormat.format( "{0}{1}{2}{3}", m_negated ? NEGATION : "", m_at ? AT : "", m_functor, m_orderedvalues );
    }

    @Override
    public final int compareTo( @Nonnull final ILiteral p_literal )
    {
        return Integer.compare( this.hashCode(), p_literal.hashCode() );
    }

    @Nonnull
    @Override
    public final synchronized ITerm deepcopy( @Nullable final IPath... p_prefix )
    {
        return ( Objects.isNull( p_prefix ) ) || ( p_prefix.length == 0 )

               ?
               new CLiteral(
                   m_at, m_negated, m_functor,
                   m_values.values().stream().map( i -> i.deepcopy() ).collect( Collectors.toList() )
               )

               :
               new CLiteral(
                   m_at, m_negated, p_prefix[0].append( m_functor ),
                   m_values.values().stream().map( i -> i.deepcopy() ).collect( Collectors.toList() )
               );
    }

    @Nonnull
    @Override
    public final synchronized ITerm deepcopysuffix()
    {
        return new CLiteral(
            m_at, m_negated, CPath.from( m_functor.suffix() ),
            m_values.values().stream().map( i -> i.deepcopy() ).collect( Collectors.toList() )
        );
    }


    /**
     * literal parser
     */
    private static final class CParser extends IBaseParser<IASTVisitorType, TypeLexer, TypeParser>
    {

        /**
         * ctor
         * @throws NoSuchMethodException on ctor-method call
         */
        CParser() throws NoSuchMethodException
        {
            super( new CErrorListener() );
        }

        @Nonnull
        @Override
        public final IASTVisitorType parse( @Nonnull final InputStream p_stream ) throws Exception
        {
            final IASTVisitorType l_visitor = new CASTVisitorType();
            l_visitor.visit( this.parser( p_stream ).literal_type() );
            return l_visitor;
        }

        @Override
        protected final Class<TypeLexer> lexerclass()
        {
            return TypeLexer.class;
        }

        @Override
        protected final Class<TypeParser> parserclass()
        {
            return TypeParser.class;
        }
    }
}