/*******************************************************************************
* Copyright 2015 Analog Devices, Inc.
*
* 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.analog.lyric.dimple.solvers.core;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import com.analog.lyric.dimple.environment.DimpleEnvironment;
import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.factorfunctions.core.FactorFunction;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactor;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph;
import com.analog.lyric.options.IOptionValue;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
/**
* Extendible solver factor creation mappings for a Dimple solver.
* <p>
* This class maps strings representing factor functions to ordered lists of functional objects that
* can create an appropriate solver factor. The keys are either the fully qualified name of a
* {@link FactorFunction} class or an unqualified alias (mostly for backward compatibility).
* <p>
* Dimple solvers that support user-defined custom solver factors should extend this class
* and define an option using {@link CustomFactorsOptionKey}.
* <p>
* @param <SFactor> is the base class for solver factors that may be created
* @param <SGraph> is the base class for the solver graph for which solver factor is created
* <p>
* @since 0.08
* @author Christopher Barber
*/
public abstract class CustomFactors<SFactor extends ISolverFactor, SGraph extends ISolverFactorGraph>
implements IOptionValue, Cloneable, Serializable
{
private static final long serialVersionUID = 1L;
/*---------------
* Inner classes
*/
/**
* Implementation of {@link ISolverFactorCreator} that invokes target class's constructor reflectively.
* <p>
* @since 0.08
*/
protected static class ConstructorFactory<SFactor extends ISolverFactor, SGraph extends ISolverFactorGraph>
implements ISolverFactorCreator<SFactor, SGraph>
{
private final Constructor<? extends SFactor> _constructor;
ConstructorFactory(Class<? extends SFactor> customClass, Class<SGraph> sgraphClass)
throws NoSuchMethodException, SecurityException
{
_constructor = customClass.getDeclaredConstructor(Factor.class, sgraphClass);
}
@Override
public String toString()
{
return _constructor.getDeclaringClass().getName();
}
@Override
public @NonNull SFactor create(Factor factor, SGraph sgraph)
{
try
{
return _constructor.newInstance(factor, sgraph);
}
catch (InvocationTargetException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException)
{
throw (RuntimeException)cause;
}
else
{
throw new RuntimeException(cause);
}
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException ex)
{
throw new SolverFactorCreationException(ex, "Could not invoke %s reflectively: %s", _constructor, ex);
}
}
}
/*-------
* State
*/
/**
* Base type of solver factors supported by this object.
*/
private final Class<SFactor> _sfactorClass;
/**
* Base type of solver graphs for which solver factors are to be created.
*/
private final Class<SGraph> _sgraphClass;
/**
* Maps factor function names to creation objects. The key is either the fully-qualified class name
* of a {@link FactorFunction} class or an unqualified alias name.
*/
private ListMultimap<String, ISolverFactorCreator<SFactor, SGraph>> _map = LinkedListMultimap.create();
/*--------------
* Construction
*/
/**
* Base constructor
* <p>
* @param sfactorClass is the concrete class type of {@code SFactor}
* @param sgraphClass is the concrete class type of {@code SGraph}
* @since 0.08
*/
protected CustomFactors(Class<SFactor> sfactorClass, Class<SGraph> sgraphClass)
{
_sfactorClass = sfactorClass;
_sgraphClass = sgraphClass;
}
/**
* Copy constructor.
* <p>
* Subclass copy constructors should invoke this.
* <p>
* @since 0.08
*/
protected CustomFactors(CustomFactors<SFactor,SGraph> other)
{
this(other._sfactorClass, other._sgraphClass);
_map.putAll(other._map);
}
@Override
public abstract CustomFactors<SFactor,SGraph> clone();
/*----------------
* Object methods
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append("(");
boolean first = true;
for (Map.Entry<String, ISolverFactorCreator<SFactor,SGraph>> entry : _map.entries())
{
if (first)
first = false;
else
sb.append(",");
sb.append(String.format("\n\t%s = %s", entry.getKey(), entry.getValue()));
}
sb.append(")\n");
return sb.toString();
}
/*----------------------
* IOptionValue methods
*/
/**
* {@inheritDoc}
* <p>
* Returns true unless state has been frozen, which should be the case when used as default
* value for {@link CustomFactorsOptionKey}.
*/
@Override
public boolean isMutable()
{
return !(_map instanceof ImmutableListMultimap);
}
/*------------------
* Abstract methods
*/
/**
* Add built-in solver factors supported by concrete subclass.
* <p>
* This is used by {@link CustomFactorsOptionKey} to populate the default {@link CustomFactors}
* instance for a given key before freezing it.
* <p>
* Note that built-ins are not added automatically by the constructor.
* <p>
* @since 0.08
*/
public abstract void addBuiltins();
/**
* Create default solver factor.
* <p>
* This will be invoked when no other creator can be found to construct the
* solver factor.
* <p>
* @param factor is the factor for which the solver factor is to be created
* @param sgraph will be the immediate parent of the new solver factor
* @throw SolverFactorCreationException
* @since 0.08
*/
public abstract SFactor createDefault(Factor factor, SGraph sgraph);
/*---------------
* Local methods
*/
/**
* Add custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the end of the list.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param constructor is the custom factor creator to be associated with the specified key.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> add(String factorFunction, ISolverFactorCreator<SFactor,SGraph> constructor)
{
return addInternal(qualifiedFactorFunctionName(factorFunction), constructor);
}
/**
* Add custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the end of the list.
* <p>
* @param funcClass is the type of the factor function, its {@linkplain Class#getName() qualified name}
* will be used as the key.
* @param constructor is the custom factor creator to be associated with the specified key.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> add(Class<? extends FactorFunction> funcClass,
ISolverFactorCreator<SFactor,SGraph> constructor)
{
return addInternal(funcClass.getName(), constructor);
}
/**
* Add custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the end of the list.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param customClass is the concrete solver factor class to be created for the specified key. Because it
* will be invoked using reflection, it must be public and have a public constructor taking the arguments
* {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> add(String factorFunction, Class<? extends SFactor> customClass)
{
return add(factorFunction, defaultCreator(customClass));
}
/**
* Add custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the end of the list.
* <p>
* @param funcClass is the type of the factor function, its {@linkplain Class#getName() qualified name}
* will be used as the key.
* @param customClass is the concrete solver factor class to be created for the specified key. Because it
* will be invoked using reflection, it must be public and have a public constructor taking the arguments
* {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> add(Class<? extends FactorFunction> funcClass,
Class<? extends SFactor> customClass)
{
return add(funcClass, defaultCreator(customClass));
}
/**
* Add custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the end of the list.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param customClassName identifies the concrete solver factor class to be created for the specified key.
* It will be resolved using the {@link #getFactorClass(String)} method.
* Because it will be invoked using reflection, the class must be public and have a public constructor taking the
* arguments {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> add(String factorFunction, String customClassName)
{
return add(factorFunction, getFactorClass(customClassName));
}
/**
* Prepend custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the beginning of the list for that key.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param constructor is the custom factor creator to be associated with the specified key.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> addFirst(String factorFunction,
ISolverFactorCreator<SFactor,SGraph> constructor)
{
return addFirstInternal(qualifiedFactorFunctionName(factorFunction), constructor);
}
/**
* Prepend custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the beginning of the list for that key.
* <p>
* @param funcClass is the type of the factor function, its {@linkplain Class#getName() qualified name}
* will be used as the key.
* @param constructor is the custom factor creator to be associated with the specified key.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> addFirst(Class<? extends FactorFunction> funcClass,
ISolverFactorCreator<SFactor,SGraph> constructor)
{
return addFirstInternal(funcClass.getName(), constructor);
}
/**
* Prepend custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the beginning of the list for that key.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param customClass is the concrete solver factor class to be created for the specified key. Because it
* will be invoked using reflection, it must be public and have a public constructor taking the arguments
* {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> addFirst(String factorFunction, Class<? extends SFactor> customClass)
{
return addFirst(factorFunction, defaultCreator(customClass));
}
/**
* Prepend custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the beginning of the list for that key.
* <p>
* @param funcClass is the type of the factor function, its {@linkplain Class#getName() qualified name}
* will be used as the key.
* @param customClass is the concrete solver factor class to be created for the specified key. Because it
* will be invoked using reflection, it must be public and have a public constructor taking the arguments
* {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> addFirst(Class<? extends FactorFunction> funcClass,
Class<? extends SFactor> customClass)
{
return addFirst(funcClass, defaultCreator(customClass));
}
/**
* Prepend custom factor function mapping.
* <p>
* If there are already creators associated with the input key (after it has been fully qualified),
* this will be added to the beginning of the list for that key.
* <p>
* @param factorFunction is either the name of a {@link FactorFunction} class or an alias,
* see {@link #qualifiedFactorFunctionName(String)} for details.
* @param customClassName identifies the concrete solver factor class to be created for the specified key.
* It will be resolved using the {@link #getFactorClass(String)} method.
* Because it will be invoked using reflection, the class must be public and have a public constructor taking the
* arguments {@code SFactor} and {@code SGraph}.
* @return this object, which allows adds to be chained.
* @since 0.08
*/
public CustomFactors<SFactor,SGraph> addFirst(String factorFunction, String customClassName)
{
return addFirst(factorFunction, getFactorClass(customClassName));
}
/**
* Get the ordered list of solver factor creators registered for the key.
* <p>
* @param factorFunction is either a fully qualified name of a {@link FactorFunction} or an unqualified alias.
* @return Ordered list of creators for {@code factorFunction} key. Returns empty list if there are none.
* @since 0.08
*/
public List<ISolverFactorCreator<SFactor, SGraph>> get(String factorFunction)
{
return _map.get(factorFunction);
}
/**
* Resolves class for given name.
* <p>
* @param customClassName is either the fully qualified name of a {@link FactorFunction} class or
* a simple class name, in which case it is assumed to be in the {@code customFactors} subpackage of
* the package containing the definition of the concrete implementation of this class. For instance,
* if invoked on an instance of class {@code com.mycompany.myproject.MyCustomFactors} it will look
* in package {@code com.mycompany.myproject.customFactors}.
* @throws IllegalArgumentException if no class is found for given name
* @throws ClassCastException if class found for name is not a subclass of {@code SFactor}
* @since 0.08
*/
public Class<? extends SFactor> getFactorClass(String customClassName)
{
if (!customClassName.contains("."))
{
customClassName = String.format("%s.customFactors.%s", getClass().getPackage().getName(), customClassName);
}
try
{
return Class.forName(customClassName).asSubclass(_sfactorClass);
}
catch (ClassNotFoundException ex)
{
throw new IllegalArgumentException(ex);
}
}
/**
* Returns a solver factor creator that invokes the constructor by reflection.
* <p>
* @param customClassName identifies the Java class of the solver factor implementation,
* which will be resolved by {@link #getFactorClass(String)}.
* @return creator generated by {@link #defaultCreator(Class)}
* @since 0.08
*/
public ISolverFactorCreator<SFactor,SGraph> defaultCreator(String customClassName)
{
return defaultCreator(getFactorClass(customClassName));
}
/**
* Returns a solver factor creator that invokes the constructor by reflection.
* <p>
* @param customClass
* @since 0.08
*/
public ISolverFactorCreator<SFactor,SGraph> defaultCreator(Class<? extends SFactor> customClass)
{
try
{
return new ConstructorFactory<>(customClass, _sgraphClass);
}
catch (NoSuchMethodException | SecurityException ex)
{
throw new DimpleException(ex);
}
}
/**
* The set of factor function keys with registered creators.
* <p>
* The keys are not in any particular order.
* @since 0.08
*/
public Set<String> keySet()
{
return _map.keySet();
}
/**
* Converts input factor function name to fully qualified name if possible.
* <p>
* Looks up factor function in {@link DimpleEnvironment#factorFunctions()} cache in active environment
* to determine fully qualified name. If not found and name is not qualified, it will simply be returned.
* <p>
* @param factorFunction is one of:
* <ul>
* <li>a fully qualified class name of a {@link FactorFunction} subclass
* <li>a simple class name of a {@link FactorFunction} subclass that exists in a package registered with
* the environment's factor function cache.
* <li>an unqualified alias
* </ul>
* @throws IllegalArgumentException if input {@code factorFunction} name is qualified by a "." and no
* matching {@link FactorFunction} class can be loaded with that name.
* @since 0.08
*/
public String qualifiedFactorFunctionName(String factorFunction)
{
final Class<?> functionClass = DimpleEnvironment.active().factorFunctions().getClassOrNull(factorFunction);
if (functionClass != null)
{
return functionClass.getName();
}
if (factorFunction.contains("."))
{
throw new IllegalArgumentException(
String.format("Cannot find factor function with qualified class name '%s'", factorFunction));
}
return factorFunction;
}
/*-------------------
* Protected methods
*/
protected CustomFactors<SFactor,SGraph> addInternal(String factorFunction,
ISolverFactorCreator<SFactor,SGraph> constructor)
{
get(factorFunction).add(constructor);
return this;
}
protected CustomFactors<SFactor,SGraph> addFirstInternal(String factorFunction,
ISolverFactorCreator<SFactor,SGraph> constructor)
{
get(factorFunction).add(0, constructor);
return this;
}
/**
* Freezes state of object, preventing further changes.
* <p>
* Intended for use in defining built-in instances for {@link CustomFactorsOptionKey} default values.
* <p>
* @since 0.08
*/
protected void freeze()
{
_map = ImmutableListMultimap.copyOf(_map);
}
}