AbstractDevsModel.java
package nl.tudelft.simulation.dsol.formalisms.devs.esdevs;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.djutils.event.EventType;
import org.djutils.event.LocalEventProducer;
import org.djutils.metadata.MetaData;
import org.djutils.metadata.ObjectDescriptor;
import org.djutils.reflection.ClassUtil;
import nl.tudelft.simulation.dsol.simulators.DevsSimulatorInterface;
/**
* AbstractDevsModel class. The basic model or component from which the AtomicModel, the CoupledModel, and the AbstractEntity
* are derived. The DevsModel provides basic functionality for reporting its state changes through the publish/subscribe
* mechanism.
* <p>
* Copyright (c) 2009-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
* https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
* </p>
* @author <a href="http://tudelft.nl/mseck">Mamadou Seck</a><br>
* @author <a href="http://tudelft.nl/averbraeck">Alexander Verbraeck</a><br>
* @param <T> the time type
* @since 1.5
*/
public abstract class AbstractDevsModel<T extends Number & Comparable<T>> extends LocalEventProducer
{
/** the default serial version UId. */
private static final long serialVersionUID = 1L;
/** the parent model we are part of. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected CoupledModel<T> parentModel;
/** the simulator this model or component will schedule its events on. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected DevsSimulatorInterface<T> simulator;
/** all DEVS models are named - this is the component name. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected String modelName;
/** all DEVS models are named - this is the full name with dot notation. */
@SuppressWarnings("checkstyle:visibilitymodifier")
protected String fullName;
/** event for listeners about state update. */
public static final EventType STATE_UPDATE = new EventType(new MetaData("STATE_UPDATE", "State update",
new ObjectDescriptor("stateUpdate", "State update", StateUpdate.class)));
/** map of call classes and fields for which the state will be reported. */
private static Map<Class<?>, Set<Field>> stateFieldMap = new LinkedHashMap<Class<?>, Set<Field>>();
/** set of fields for this class which the state will be reported. */
private Set<Field> stateFieldSet = null;
/** the fields of the AtomicModel. */
private static Set<Field> atomicFields = new LinkedHashSet<Field>();
/** the fields of the CoupledModel. */
private static Set<Field> coupledFields = new LinkedHashSet<Field>();
/** the fields of the AbstractEntity. */
private static Set<Field> entityFields = new LinkedHashSet<Field>();
/** the fields of the AbstractDEVSMOdel. */
private static Set<Field> abstractDEVSFields = new LinkedHashSet<Field>();
/**
* Static constructor. Takes care of filling the static constants the first time an extension of the AbstractDevsModel is
* created.
*/
static
{
AbstractDevsModel.atomicFields = ClassUtil.getAllFields(AtomicModel.class);
AbstractDevsModel.coupledFields = ClassUtil.getAllFields(CoupledModel.class);
AbstractDevsModel.entityFields = ClassUtil.getAllFields(AbstractEntity.class);
AbstractDevsModel.abstractDEVSFields = ClassUtil.getAllFields(AbstractDevsModel.class);
}
/**
* Constructor for an abstract DEVS model: we have to indicate the simulator to schedule the events on, and the parent model
* we are part of. A parent model of null means that we are the top model.
* @param modelName String; the name of this component
* @param simulator DevsSimulatorInterface<T>; the simulator to schedule the events on.
* @param parentModel CoupledModel<T>; the parent model we are part of.
*/
public AbstractDevsModel(final String modelName, final DevsSimulatorInterface<T> simulator,
final CoupledModel<T> parentModel)
{
this.modelName = modelName;
if (parentModel != null)
{
this.fullName = parentModel.getFullName() + ".";
}
this.fullName += this.modelName;
this.simulator = simulator;
this.parentModel = parentModel;
if (!AbstractDevsModel.stateFieldMap.containsKey(this.getClass()))
{
this.createStateFieldSet();
}
this.stateFieldSet = AbstractDevsModel.stateFieldMap.get(this.getClass());
}
/**
* @return the simulator this model schedules its events on.
*/
public DevsSimulatorInterface<T> getSimulator()
{
return this.simulator;
}
/**
* @param simulator DevsSimulatorInterface<T>; the simulator to use from now on
*/
public void setSimulator(final DevsSimulatorInterface<T> simulator)
{
this.simulator = simulator;
}
/**
* @return the parent model we are part of.
*/
public CoupledModel<T> getParentModel()
{
return this.parentModel;
}
/**
* @return the name of the model.
*/
public String getModelName()
{
return this.modelName;
}
/**
* @return the full name of the model in dot notation.
*/
public String getFullName()
{
return this.fullName;
}
/** {@inheritDoc} */
@Override
public String toString()
{
return this.fullName;
}
/**
* Print the model, preceded by a user provided string.
* @param header String; the user provided string to print in front of the model (e.g. newlines, header).
*/
public abstract void printModel(String header);
// ///////////////////////////////////////////////////////////////////////////
// STATE CHANGE UPDATES, STATE SAVES
// ///////////////////////////////////////////////////////////////////////////
/**
* Create state field set. For this first version, take all the fields into account as state variables. The method substract
* the fields that are on the level of AbstractModel or Atomic Model or higher; only leave the non-static fields that are
* part of the descendents of the abstract models.
*/
private void createStateFieldSet()
{
Set<Field> fieldSet = ClassUtil.getAllFields(this.getClass());
if (this instanceof AtomicModel)
{
fieldSet.removeAll(AbstractDevsModel.atomicFields);
}
else if (this instanceof CoupledModel)
{
fieldSet.removeAll(AbstractDevsModel.coupledFields);
}
else if (this instanceof AbstractEntity)
{
fieldSet.removeAll(AbstractDevsModel.entityFields);
}
else
{
fieldSet.removeAll(AbstractDevsModel.abstractDEVSFields);
}
// we can now do something with the annotations, but that comes later
// put the results in the map
AbstractDevsModel.stateFieldMap.put(this.getClass(), fieldSet);
}
/**
* Fire a state update. At this moment, all state variables are reported for an atomic model when it fires its
* delta_internal or delta_external method. More intelligence can be added here later. For simple types, a comparison with
* the old value (state map) is possible. For complex variables (objects) this is more difficult as a deep clone should be
* saved as old state, followed by a full comparison. This does not seem practical, and more expensive than firing the state
* change of all state variables. The intelligence to detect real state changes then has to be built in at the receiver's
* side.
*/
protected void fireUpdatedState()
{
for (Field field : this.stateFieldSet)
{
try
{
field.setAccessible(true);
StateUpdate stateUpdate = new StateUpdate(this.getModelName(), field.getName(), field.get(this));
this.fireTimedEvent(AbstractDevsModel.STATE_UPDATE, stateUpdate, getSimulator().getSimulatorTime());
}
catch (IllegalAccessException exception)
{
this.simulator.getLogger().always().error("Tried to fire update for variable {} but got an exception.",
field.getName());
System.err.println(this.getModelName() + " - fireUpdateState: Tried to fire update for variable "
+ field.getName() + " but got an exception.");
}
}
}
/**
* StateUpdate class. Reports a state update. Right now, it is a modelname - variable name - value tuple.
* <p>
* Copyright (c) 2009-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
* See for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>.
* The DSOL project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
* https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
* </p>
* @author <a href="http://tudelft.nl/mseck">Mamadou Seck</a><br>
* @author <a href="http://tudelft.nl/averbraeck">Alexander Verbraeck</a><br>
*/
public static class StateUpdate implements Serializable
{
/** the default serial version UId. */
private static final long serialVersionUID = 1L;
/** the name of the model. */
private String model;
/** the name of the variable. */
private String variable;
/** the value of the variable. */
private Object value;
/**
* Construct a StateUPdate tuple to report a state update.
* @param modelName String; the name of the model
* @param variableName String; the name of the variable
* @param value Object; the value
*/
public StateUpdate(final String modelName, final String variableName, final Object value)
{
super();
this.model = modelName;
this.variable = variableName;
this.value = value;
}
/**
* @return the modelName
*/
public String getModel()
{
return this.model;
}
/**
* @return the variableName
*/
public String getVariable()
{
return this.variable;
}
/**
* @return the value
*/
public Object getValue()
{
return this.value;
}
}
}