CCommon.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.hash.Hasher;
import com.google.common.hash.Hashing;
import com.rits.cloning.Cloner;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.apache.commons.compress.compressors.deflate.DeflateCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.pack200.Pack200CompressorOutputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.lightjason.agentspeak.agent.IAgent;
import org.lightjason.agentspeak.error.CIllegalArgumentException;
import org.lightjason.agentspeak.error.CIllegalStateException;
import org.lightjason.agentspeak.language.execution.CContext;
import org.lightjason.agentspeak.language.execution.IContext;
import org.lightjason.agentspeak.language.instantiable.IInstantiable;
import org.lightjason.agentspeak.language.instantiable.plan.statistic.IPlanStatistic;
import org.lightjason.agentspeak.language.instantiable.plan.trigger.ITrigger;
import org.lightjason.agentspeak.language.unify.IUnifier;
import org.lightjason.agentspeak.language.variable.IVariable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* common structure for execution definition
*/
public final class CCommon
{
/**
* cloner
*/
private static final Cloner CLONER = new Cloner();
/**
* private ctor - avoid instantiation
*/
private CCommon()
{
}
//--- plan / rule instantiation ----------------------------------------------------------------------------------------------------------------------------
/**
* updates within an instance context all variables of the stream
*
* @param p_context context
* @param p_unifiedvariables unified variables as stream
* @return context reference
*/
@Nonnull
public static IContext updatecontext( @Nonnull final IContext p_context, @Nonnull final Stream<IVariable<?>> p_unifiedvariables )
{
p_unifiedvariables.parallel().forEach( i -> p_context.instancevariables().get( i.fqnfunctor() ).set( i.raw() ) );
return p_context;
}
/**
* creates the instantiate execution context with default variables
*
* @param p_instance instance object
* @param p_agent agent
* @param p_variable variable stream
* @return context object
*/
@Nonnull
public static IContext instantiate( @Nonnull final IInstantiable p_instance, @Nonnull final IAgent<?> p_agent, @Nonnull final Stream<IVariable<?>> p_variable )
{
final Set<IVariable<?>> l_variables = p_instance.variables().parallel().map( i -> i.shallowcopy() ).collect( Collectors.toSet() );
Stream.concat(
p_variable,
p_agent.variablebuilder().apply( p_agent, p_instance )
)
.peek( l_variables::remove )
.forEach( l_variables::add );
return new CContext( p_agent, p_instance, Collections.unmodifiableSet( l_variables ) );
}
/**
* unifies trigger and creates the set of variables
*
* @note target trigger literal must be cloned to avoid variable overwriting
* @param p_unifier unifier
* @param p_source input trigger (with values)
* @param p_target trigger (of a plan / rule)
* @return pair of valid unification and unified variables
*/
@Nonnull
public static Pair<Boolean, Set<IVariable<?>>> unifytrigger( @Nonnull final IUnifier p_unifier,
@Nonnull final ITrigger p_source, @Nonnull final ITrigger p_target )
{
// filter for avoid duplicated instantiation on non-existing values
if ( !( p_source.literal().emptyValues() == p_target.literal().emptyValues() ) )
return new ImmutablePair<>( false, Collections.emptySet() );
// unify variables, source trigger literal must be copied
final Set<IVariable<?>> l_variables = p_unifier.unify( p_source.literal(), p_target.literal().deepcopy().<ILiteral>raw() );
// check for completely unification (of all variables)
return l_variables.size() == CCommon.variablefrequency( p_target.literal() ).size()
? new ImmutablePair<>( true, l_variables )
: new ImmutablePair<>( false, Collections.emptySet() );
}
/**
* instantiate a plan with context and plan-specific variables
*
* @param p_planstatistic plan statistic for instatiation
* @param p_agent agent
* @param p_variables instantiated variables
* @return pair of planstatistic and context
*/
@Nonnull
public static Pair<IPlanStatistic, IContext> instantiateplan( @Nonnull final IPlanStatistic p_planstatistic,
@Nonnull final IAgent<?> p_agent, @Nonnull final Set<IVariable<?>> p_variables )
{
return new ImmutablePair<>(
p_planstatistic,
p_planstatistic.plan().instantiate(
p_agent,
Stream.concat( p_variables.stream(), p_planstatistic.variables() )
)
);
}
// --- variable / term helpers -----------------------------------------------------------------------------------------------------------------------------
/**
* concat multiple streams
*
* @param p_streams streams
* @tparam T any value type
* @return concated stream
*/
@Nonnull
@SafeVarargs
@SuppressWarnings( "varargs" )
public static <T> Stream<T> streamconcat( @Nonnull final Stream<T>... p_streams )
{
return Arrays.stream( p_streams ).reduce( Stream::concat ).orElseGet( Stream::empty );
}
/**
* consts the variables within a literal
*
* @param p_literal literal
* @return map with frequency
*/
@Nonnull
public static Map<IVariable<?>, Integer> variablefrequency( @Nonnull final ILiteral p_literal )
{
return Collections.unmodifiableMap(
flattenrecursive( p_literal.orderedvalues() )
.filter( i -> i instanceof IVariable<?> )
.map( i -> (IVariable<?>) i )
.collect( Collectors.toMap( i -> i, i -> 1, Integer::sum ) )
);
}
/**
* checks a term value for assignable class
*
* @param p_value any value type
* @param p_class assignable class
* @return term value or raw value
*/
@SuppressWarnings( "unchecked" )
public static <T> boolean rawvalueAssignableTo( @Nonnull final T p_value, @Nonnull final Class<?>... p_class )
{
if ( p_value instanceof IVariable<?> )
return ( (IVariable<?>) p_value ).valueassignableto( p_class );
if ( p_value instanceof IRawTerm<?> )
return ( (IRawTerm<?>) p_value ).valueassignableto( p_class );
return Arrays.stream( p_class ).anyMatch( i -> i.isAssignableFrom( p_value.getClass() ) );
}
/**
* replace variables with context variables
*
* @param p_context execution context
* @param p_terms replacing term list
* @return result term list
*/
@Nonnull
public static List<ITerm> replaceFromContext( @Nonnull final IContext p_context, @Nonnull final Collection<? extends ITerm> p_terms )
{
return p_terms.stream().map( i -> replaceFromContext( p_context, i ) ).collect( Collectors.toList() );
}
/**
* replace variable with context variable
* other values will be passed without context access
*
* @param p_context execution context
* @param p_term term
* @return replaces variable object
*/
@Nonnull
public static ITerm replaceFromContext( @Nonnull final IContext p_context, @Nonnull final ITerm p_term )
{
if ( !( p_term instanceof IVariable<?> ) )
return p_term;
final IVariable<?> l_variable = p_context.instancevariables().get( p_term.fqnfunctor() );
if ( Objects.nonNull( l_variable ) )
return l_variable;
throw new CIllegalArgumentException(
org.lightjason.agentspeak.common.CCommon.languagestring( CCommon.class, "variablenotfoundincontext", p_term.fqnfunctor() )
);
}
/**
* flat term-in-term collection into
* a straight term list
*
* @param p_terms term collection
* @return flat term stream
*/
@Nonnull
public static Stream<ITerm> flatten( @Nonnull final Collection<? extends ITerm> p_terms )
{
return flattenstream( p_terms.stream() );
}
/**
* flat term-in-term stream into
* a straight term list
*
* @param p_terms term stream
* @return flat term stream
*/
@Nonnull
public static Stream<ITerm> flatten( @Nonnull final Stream<? extends ITerm> p_terms )
{
return flattenstream( p_terms );
}
/**
* recursive stream of term values
*
* @param p_input term stream
* @return term stream
*/
@Nonnull
public static Stream<ITerm> flattenrecursive( @Nonnull final Stream<ITerm> p_input )
{
return p_input.flatMap( i -> i instanceof ILiteral ? flattenrecursive( ( i.<ILiteral>raw() ).orderedvalues() ) : Stream.of( i ) );
}
/*
* recursive flattering of a stream structure
*
* @param p_list any stream
* @return term stream
*/
@Nonnull
@SuppressWarnings( "unchecked" )
private static Stream<ITerm> flattenstream( @Nonnull final Stream<?> p_stream )
{
return p_stream.flatMap( i ->
{
final Object l_value = i instanceof ITerm ? ( (ITerm) i ).raw() : i;
return l_value instanceof Collection<?>
? flattenstream( ( (Collection<?>) l_value ).stream() )
: Stream.of( CRawTerm.from( l_value ) );
} );
}
/**
* returns the hasing function for term data
*
* @return hasher
*/
@Nonnull
public static Hasher getTermHashing()
{
return Hashing.sipHash24().newHasher();
}
/**
* creates a deep-clone of an object
*
* @param p_object input object
* @tparam T object type
* @return deep-copy
*/
@Nullable
@SuppressWarnings( "unchecked" )
public static <T> T deepclone( @Nullable final T p_object )
{
return Objects.isNull( p_object ) ? null : CLONER.deepClone( p_object );
}
// --- compression algorithm -------------------------------------------------------------------------------------------------------------------------------
/**
* calculates the levenshtein distance
*
* @param p_first first string
* @param p_second second string
* @param p_insertweight inserting weight
* @param p_replaceweight replace weight
* @param p_deleteweight delete weight
* @return distance
* @see https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Java
*/
public static double levenshtein( @Nonnull final String p_first, @Nonnull final String p_second, final double p_insertweight,
final double p_replaceweight, final double p_deleteweight )
{
// the array of distances
double[] l_cost = IntStream.range( 0, p_first.length() + 1 ).mapToDouble( i -> i ).toArray();
double[] l_newcost = new double[l_cost.length];
for ( int j = 1; j < p_second.length() + 1; j++ )
{
l_newcost[0] = j;
// calculate cost of operation for all characters
for ( int i = 1; i < l_cost.length; i++ )
l_newcost[i] = min(
l_cost[i - 1] + ( p_first.charAt( i - 1 ) == p_second.charAt( j - 1 ) ? 0 : p_replaceweight ),
l_newcost[i - 1] + p_deleteweight,
l_cost[i] + p_insertweight
);
final double[] l_swap = l_cost;
l_cost = l_newcost;
l_newcost = l_swap;
}
return l_cost[p_first.length()];
}
/**
* returns the minimum of three elemens
*
* @param p_first first value
* @param p_second second value
* @param p_third third value
* @return minimum
*/
public static double min( final double p_first, final double p_second, final double p_third )
{
return Math.min( Math.min( p_first, p_second ), p_third );
}
/**
* normalized-compression-distance
*
* @param p_compression compression algorithm
* @param p_first first string
* @param p_second second string
* @return distance in [0,1]
*/
public static double ncd( @Nonnull final ECompression p_compression, @Nonnull final String p_first, @Nonnull final String p_second )
{
if ( p_first.equals( p_second ) )
return 0;
final double l_first = compress( p_compression, p_first );
final double l_second = compress( p_compression, p_second );
return ( compress( p_compression, p_first + p_second ) - Math.min( l_first, l_second ) ) / Math.max( l_first, l_second );
}
/**
* compression algorithm
*
* @param p_compression compression algorithm
* @param p_input input string
* @return number of compression bytes
* @warning counting stream returns the correct number of bytes after flushing
*/
private static double compress( @Nonnull final ECompression p_compression, @Nonnull final String p_input )
{
final DataOutputStream l_counting = new DataOutputStream( new NullOutputStream() );
try (
final InputStream l_input = new ByteArrayInputStream( p_input.getBytes( StandardCharsets.UTF_8 ) );
final OutputStream l_compress = p_compression.get( l_counting )
)
{
IOUtils.copy( l_input, l_compress );
}
catch ( final IOException l_exception )
{
return 0;
}
return l_counting.size();
}
/**
* compression algorithm
*/
public enum ECompression
{
BZIP,
GZIP,
DEFLATE,
PACK200,
XZ;
/**
* enum names
*/
private static final Set<String> ALGORITHMS = Collections.unmodifiableSet(
Arrays.stream( ECompression.values() )
.map( i -> i.name().toUpperCase( Locale.ROOT ) )
.collect( Collectors.toSet() )
);
/**
* creates a compression stream
*
* @param p_datastream data-counting stream
* @return compression output stream
* @throws IOException throws on any io error
*/
@Nonnull
public final OutputStream get( @Nonnull final DataOutputStream p_datastream ) throws IOException
{
switch ( this )
{
case BZIP : return new BZip2CompressorOutputStream( p_datastream );
case GZIP : return new GzipCompressorOutputStream( p_datastream );
case DEFLATE : return new DeflateCompressorOutputStream( p_datastream );
case PACK200 : return new Pack200CompressorOutputStream( p_datastream );
case XZ : return new XZCompressorOutputStream( p_datastream );
default :
throw new CIllegalStateException( org.lightjason.agentspeak.common.CCommon.languagestring( this, "unknown", this ) );
}
}
/**
* returns a compression value
*
* @param p_value string name
* @return compression value
*/
@Nonnull
public static ECompression from( @Nonnull final String p_value )
{
return ECompression.valueOf( p_value.toUpperCase( Locale.ROOT ) );
}
/**
* checks if a compression exists
*
* @param p_value compression name
* @return existance flag
*/
public static boolean exist( @Nonnull final String p_value )
{
return ALGORITHMS.contains( p_value.toUpperCase( Locale.ROOT ) );
}
}
}