/* * JaamSim Discrete Event Simulation * Copyright (C) 2002-2011 Ausenco Engineering Canada Inc. * Copyright (C) 2016 JaamSim Software 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.jaamsim.basicsim; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLong; import com.jaamsim.Samples.SampleInput; import com.jaamsim.datatypes.DoubleVector; import com.jaamsim.events.Conditional; import com.jaamsim.events.EventHandle; import com.jaamsim.events.EventManager; import com.jaamsim.events.ProcessTarget; import com.jaamsim.input.AttributeDefinitionListInput; import com.jaamsim.input.AttributeHandle; import com.jaamsim.input.BooleanInput; import com.jaamsim.input.ExpError; import com.jaamsim.input.ExpResType; import com.jaamsim.input.ExpResult; import com.jaamsim.input.ExpressionHandle; import com.jaamsim.input.Input; import com.jaamsim.input.InputAgent; import com.jaamsim.input.InputErrorException; import com.jaamsim.input.Keyword; import com.jaamsim.input.KeywordIndex; import com.jaamsim.input.NamedExpression; import com.jaamsim.input.NamedExpressionListInput; import com.jaamsim.input.Output; import com.jaamsim.input.OutputHandle; import com.jaamsim.input.StringInput; import com.jaamsim.input.SynonymInput; import com.jaamsim.ui.FrameBox; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.TimeUnit; import com.jaamsim.units.Unit; import com.jaamsim.units.UserSpecifiedUnit; /** * Abstract class that encapsulates the methods and data needed to create a * simulation object. Encapsulates the basic system objects to achieve discrete * event execution. */ public class Entity { private static AtomicLong entityCount = new AtomicLong(0); private static final ArrayList<Entity> allInstances; private static final HashMap<String, Entity> namedEntities; private String entityName; private final long entityNumber; //public static final int FLAG_TRACE = 0x01; // reserved in case we want to treat tracing like the other flags //public static final int FLAG_TRACEREQUIRED = 0x02; //public static final int FLAG_TRACESTATE = 0x04; public static final int FLAG_LOCKED = 0x08; //public static final int FLAG_TRACKEVENTS = 0x10; public static final int FLAG_ADDED = 0x20; public static final int FLAG_EDITED = 0x40; public static final int FLAG_GENERATED = 0x80; public static final int FLAG_DEAD = 0x0100; private int flags; private boolean traceFlag = false; private final ArrayList<Input<?>> inpList = new ArrayList<>(); private final HashMap<String, AttributeHandle> attributeMap = new LinkedHashMap<>(); private final HashMap<String, ExpressionHandle> customOutputMap = new LinkedHashMap<>(); @Keyword(description = "Provides the programmer with a detailed trace of the logic executed " + "by the entity. Trace information is sent to standard out.", exampleList = {"TRUE"}) protected final BooleanInput trace; @Keyword(description = "A free form string describing the Entity", exampleList = {"'A very useful entity'"}) protected final StringInput desc; @Keyword(description = "The list of user defined attributes for this entity.\n" + " The attribute name is followed by its initial value. The unit provided for" + "this value will determine the attribute's unit type.", exampleList = {"{ A 20.0 s } { alpha 42 }"}) public final AttributeDefinitionListInput attributeDefinitionList; @Keyword(description = "The list of user defined custom outputs for this entity.\n" + " The output name is followed by an expression to be evaluated as the output and the unit type of the expression.", exampleList = {"{ TwiceSimTime '2*this.SimTime' TimeUnit } { CargoVolume 'this.Cargo/this.CargoDensity' VolumeUnit }"}) public final NamedExpressionListInput namedExpressionInput; static { allInstances = new ArrayList<>(100); namedEntities = new HashMap<>(100); } { trace = new BooleanInput("Trace", "Key Inputs", false); trace.setHidden(true); this.addInput(trace); desc = new StringInput("Description", "Key Inputs", ""); desc.setHidden(true); this.addInput(desc); attributeDefinitionList = new AttributeDefinitionListInput(this, "AttributeDefinitionList", "Key Inputs", new ArrayList<AttributeHandle>()); attributeDefinitionList.setHidden(false); this.addInput(attributeDefinitionList); namedExpressionInput = new NamedExpressionListInput(this, "CustomOutputList", "Key Inputs", new ArrayList<NamedExpression>()); namedExpressionInput.setHidden(false); this.addInput(namedExpressionInput); } /** * Constructor for entity initializing members. */ public Entity() { entityNumber = getNextID(); synchronized(allInstances) { allInstances.add(this); } flags = 0; } private static long getNextID() { return entityCount.incrementAndGet(); } public static ArrayList<? extends Entity> getAll() { synchronized(allInstances) { return allInstances; } } public static <T extends Entity> InstanceIterable<T> getInstanceIterator(Class<T> proto){ return new InstanceIterable<>(proto); } public static <T extends Entity> ClonesOfIterable<T> getClonesOfIterator(Class<T> proto){ return new ClonesOfIterable<>(proto); } /** * Returns an iterator over the given proto class, but also filters only those * objects that implement the given interface class. * @return */ public static <T extends Entity> ClonesOfIterableInterface<T> getClonesOfIterator(Class<T> proto, Class<?> iface){ return new ClonesOfIterableInterface<>(proto, iface); } public static Entity idToEntity(long id) { synchronized (allInstances) { for (Entity e : allInstances) { if (e.getEntityNumber() == id) { return e; } } return null; } } public void validate() throws InputErrorException { for (Input<?> in : inpList) { in.validate(); } } /** * Initialises the entity prior to the start of the model run. * <p> * This method must not depend on any other entities so that it can be * called for each entity in any sequence. */ public void earlyInit() { // Reset the attributes to their initial values for (AttributeHandle h : attributeMap.values()) { h.setValue(h.getInitialValue()); } } /** * Initialises the entity prior to the start of the model run. * <p> * This method assumes other entities have already called earlyInit. */ public void lateInit() {} /** * Starts the execution of the model run for this entity. * <p> * If required, initialisation that depends on another entity can be * performed in this method. It is called after earlyInit(). */ public void startUp() {} /** * Resets the statistics collected by the entity. */ public void clearStatistics() {} /** * Assigns input values that are helpful when the entity is dragged and * dropped into a model. */ public void setInputsForDragAndDrop() {} private static class EntityComparator implements Comparator<Entity> { @Override public int compare(Entity e1, Entity e2) { return Long.compare(e1.getEntityNumber(), e2.getEntityNumber()); } } private static final EntityComparator entityComparator = new EntityComparator(); public void kill() { synchronized (allInstances) { int index = Collections.binarySearch(allInstances, this, entityComparator); if (index >= 0) allInstances.remove(index); } if (!testFlag(FLAG_GENERATED)) { synchronized (namedEntities) { if (namedEntities.get(entityName) == this) namedEntities.remove(entityName); entityName = null; } } setFlag(FLAG_DEAD); // Remove any references to the deleted entity from the inputs to other entities if (!testFlag(FLAG_GENERATED)) { for (Entity ent : Entity.getClonesOfIterator(Entity.class)) { for (Input<?> in : ent.getEditableInputs()) { in.removeReferences(this); } } } } /** * Performs any actions that are required at the end of the simulation run, e.g. to create an output report. */ public void doEnd() {} public static long getEntitySequence() { long seq = (long)allInstances.size() << 32; seq += entityCount.get(); return seq; } /** * Get the current Simulation ticks value. * @return the current simulation tick */ public final long getSimTicks() { return EventManager.simTicks(); } /** * Get the current Simulation time. * @return the current time in seconds */ public final double getSimTime() { return EventManager.simSeconds(); } protected void addInput(Input<?> in) { inpList.add(in); } protected void addSynonym(Input<?> in, String synonym) { inpList.add(new SynonymInput(synonym, in)); } public final Input<?> getInput(String key) { for (int i = 0; i < inpList.size(); i++) { Input<?> in = inpList.get(i); if (key.equals(in.getKeyword())) { if (in.isSynonym()) return ((SynonymInput)in).input; else return in; } } return null; } /** * Copy the inputs for each keyword to the caller. Any inputs that have already * been set for the caller are overwritten by those for the entity being copied. * @param ent = entity whose inputs are to be copied */ public void copyInputs(Entity ent) { ArrayList<String> tmp = new ArrayList<>(); for (Input<?> sourceInput : ent.inpList) { if (sourceInput.isDefault() || sourceInput.isSynonym()) { continue; } tmp.clear(); sourceInput.getValueTokens(tmp); KeywordIndex kw = new KeywordIndex(sourceInput.getKeyword(), tmp, null); InputAgent.apply(this, kw); } } /** * Creates an exact copy of the specified entity. * <p> * All the entity's inputs are copied to the new entity, but its internal * properties are left uninitialised. * @param ent - entity to be copied. * @param name - name of the copied entity. * @return - copied entity. */ public static <T extends Entity> T fastCopy(T ent, String name) { // Create the new entity @SuppressWarnings("unchecked") T ret = (T)InputAgent.generateEntityWithName(ent.getClass(), name); // Loop through the original entity's inputs ArrayList<Input<?>> orig = ent.getEditableInputs(); for (int i = 0; i < orig.size(); i++) { Input<?> sourceInput = orig.get(i); // Default values do not need to be copied if (sourceInput.isDefault() || sourceInput.isSynonym()) continue; // Get the matching input for the new entity Input<?> targetInput = ret.getEditableInputs().get(i); // SampleInputs need to know their entity for "this" to work correctly if (sourceInput instanceof SampleInput) { ((SampleInput)targetInput).setEntity(ret); } // Assign the value to the copied entity's input targetInput.copyFrom(sourceInput); // Further processing related to this input ret.updateForInput(targetInput); } return ret; } public void setFlag(int flag) { flags |= flag; } public void clearFlag(int flag) { flags &= ~flag; } public boolean testFlag(int flag) { return (flags & flag) != 0; } public final void setTraceFlag() { traceFlag = true; } public final void clearTraceFlag() { traceFlag = false; } public final boolean isTraceFlag() { return traceFlag; } /** * Method to return the name of the entity. * Note that the name of the entity may not be the unique identifier used in the namedEntityHashMap; see Entity.toString() */ public final String getName() { return entityName; } /** * Get the unique number for this entity * @return */ public long getEntityNumber() { return entityNumber; } /** * Method to return the unique identifier of the entity. Used when building Edit tree labels * @return entityName */ @Override public String toString() { return getName(); } public static Entity getNamedEntity(String name) { synchronized (namedEntities) { return namedEntities.get(name); } } /** * Method to set the input name of the entity. */ public void setName(String newName) { if (testFlag(FLAG_GENERATED)) { entityName = newName; return; } synchronized (namedEntities) { namedEntities.remove(entityName); entityName = newName; namedEntities.put(entityName, this); } } /** * This method updates the Entity for changes in the given input */ public void updateForInput( Input<?> in ) { if (in == trace) { if (trace.getValue()) this.setTraceFlag(); else this.clearTraceFlag(); return; } if (in == attributeDefinitionList) { attributeMap.clear(); for (AttributeHandle h : attributeDefinitionList.getValue()) { this.addAttribute(h.getName(), h); } // Update the OutputBox FrameBox.reSelectEntity(); return; } if (in == namedExpressionInput) { customOutputMap.clear(); for (NamedExpression ne : namedExpressionInput.getValue()) { ExpressionHandle eh = new ExpressionHandle(this, ne.getExpression(), ne.getName()); eh.setUnitType(ne.getUnitType()); customOutputMap.put(ne.getName(), eh); } // Update the OutputBox FrameBox.reSelectEntity(); return; } } public final void startProcess(String methodName, Object... args) { EventManager.startProcess(new ReflectionTarget(this, methodName, args)); } public final void startProcess(ProcessTarget t) { EventManager.startProcess(t); } public final void scheduleProcess(double secs, int priority, ProcessTarget t) { EventManager.scheduleSeconds(secs, priority, false, t, null); } public final void scheduleProcess(double secs, int priority, String methodName, Object... args) { EventManager.scheduleSeconds(secs, priority, false, new ReflectionTarget(this, methodName, args), null); } public final void scheduleProcess(double secs, int priority, ProcessTarget t, EventHandle handle) { EventManager.scheduleSeconds(secs, priority, false, t, handle); } public final void scheduleProcess(double secs, int priority, boolean fifo, ProcessTarget t, EventHandle handle) { EventManager.scheduleSeconds(secs, priority, fifo, t, handle); } public final void scheduleProcessTicks(long ticks, int priority, boolean fifo, ProcessTarget t, EventHandle h) { EventManager.scheduleTicks(ticks, priority, fifo, t, h); } public final void scheduleProcessTicks(long ticks, int priority, ProcessTarget t) { EventManager.scheduleTicks(ticks, priority, false, t, null); } public final void scheduleProcessTicks(long ticks, int priority, String methodName, Object... args) { EventManager.scheduleTicks(ticks, priority, false, new ReflectionTarget(this, methodName, args), null); } public final void waitUntil(Conditional cond, EventHandle handle) { // Don't actually wait if the condition is already true if (cond.evaluate()) return; EventManager.waitUntil(cond, handle); } /** * Wait a number of simulated seconds and a given priority. * @param secs * @param priority */ public final void simWait(double secs, int priority) { EventManager.waitSeconds(secs, priority, false, null); } /** * Wait a number of simulated seconds and a given priority. * @param secs * @param priority */ public final void simWait(double secs, int priority, EventHandle handle) { EventManager.waitSeconds(secs, priority, false, handle); } /** * Wait a number of simulated seconds and a given priority. * @param secs * @param priority */ public final void simWait(double secs, int priority, boolean fifo, EventHandle handle) { EventManager.waitSeconds(secs, priority, fifo, handle); } /** * Wait a number of discrete simulation ticks and a given priority. * @param secs * @param priority */ public final void simWaitTicks(long ticks, int priority) { EventManager.waitTicks(ticks, priority, false, null); } /** * Wait a number of discrete simulation ticks and a given priority. * @param secs * @param priority * @param fifo * @param handle */ public final void simWaitTicks(long ticks, int priority, boolean fifo, EventHandle handle) { EventManager.waitTicks(ticks, priority, fifo, handle); } public void handleSelectionLost() {} // ****************************************************************************************************** // EDIT TABLE METHODS // ****************************************************************************************************** public ArrayList<Input<?>> getEditableInputs() { return inpList; } // ****************************************************************************************************** // TRACING METHODS // ****************************************************************************************************** /** * Prints a trace statement for the given subroutine. * The entity name is included in the output. * @param indent - number of tabs with which to indent the text * @param fmt - format string for the trace data (include the method name) * @param args - trace data */ public void trace(int indent, String fmt, Object... args) { InputAgent.trace(indent, this, fmt, args); } /** * Prints an additional line of trace info. * The entity name is NOT included in the output * @param indent - number of tabs with which to indent the text * @param fmt - format string for the trace data * @param args - trace data */ public void traceLine(int indent, String fmt, Object... args) { InputAgent.trace(indent, null, fmt, args); } /** * Throws an ErrorException for this entity with the specified message. * @param fmt - format string for the error message * @param args - objects used by the format string * @throws ErrorException */ public void error(String fmt, Object... args) throws ErrorException { throw new ErrorException(this, String.format(fmt, args)); } /** * Returns a user specific unit type. This is needed for entity types like distributions that may change the unit type * that is returned at runtime. * @return */ public Class<? extends Unit> getUserUnitType() { return DimensionlessUnit.class; } public final OutputHandle getOutputHandle(String outputName) { if (hasAttribute(outputName)) return attributeMap.get(outputName); if (customOutputMap.containsKey(outputName)) return customOutputMap.get(outputName); if (hasOutput(outputName)) { OutputHandle ret = new OutputHandle(this, outputName); if (ret.getUnitType() == UserSpecifiedUnit.class) ret.setUnitType(getUserUnitType()); return ret; } return null; } /** * Optimized version of getOutputHandle() for output names that are known to be interned * @param outputName * @return */ public final OutputHandle getOutputHandleInterned(String outputName) { if (hasAttribute(outputName)) return attributeMap.get(outputName); if (customOutputMap.containsKey(outputName)) return customOutputMap.get(outputName); if (OutputHandle.hasOutputInterned(this.getClass(), outputName)) { OutputHandle ret = new OutputHandle(this, outputName); if (ret.getUnitType() == UserSpecifiedUnit.class) ret.setUnitType(getUserUnitType()); return ret; } return null; } public boolean hasOutput(String outputName) { if (OutputHandle.hasOutput(this.getClass(), outputName)) return true; if (attributeMap.containsKey(outputName)) return true; if (customOutputMap.containsKey(outputName)) return true; return false; } private static final String OUTPUT_FORMAT = "%s\t%s\t%s\t%s%n"; private static final String LIST_OUTPUT_FORMAT = "%s\t%s[%s]\t%s\t%s%n"; /** * Writes the entry in the output report for this entity. * @param file - the file in which the outputs are written * @param simTime - simulation time at which the outputs are evaluated */ public void printReport(FileEntity file, double simTime) { // Loop through the outputs ArrayList<OutputHandle> handles = OutputHandle.getOutputHandleList(this); for (OutputHandle out : handles) { // Should this output appear in the report? if (!out.isReportable()) continue; // Determine the preferred unit for this output Class<? extends Unit> ut = out.getUnitType(); double factor = Unit.getDisplayedUnitFactor(ut); String unitString = Unit.getDisplayedUnit(ut); if (ut == Unit.class || ut == DimensionlessUnit.class) unitString = "-"; // Numerical output if (out.isNumericValue()) { try { double val = out.getValueAsDouble(simTime, Double.NaN)/factor; file.format(OUTPUT_FORMAT, this.getName(), out.getName(), val, unitString); } catch (Exception e) { file.format(OUTPUT_FORMAT, this.getName(), out.getName(), Double.NaN, unitString); } } // DoubleVector output else if (out.getReturnType() == DoubleVector.class) { DoubleVector vec = out.getValue(simTime, DoubleVector.class); for (int i=0; i<vec.size(); i++) { double val = vec.get(i); file.format(LIST_OUTPUT_FORMAT, this.getName(), out.getName(), i, val/factor, unitString); } } // ArrayList output else if (out.getReturnType() == ArrayList.class) { ArrayList<?> array = out.getValue(simTime, ArrayList.class); for (int i=0; i<array.size(); i++) { Object obj = array.get(i); if (obj instanceof Double) { double val = (Double)obj; file.format(LIST_OUTPUT_FORMAT, this.getName(), out.getName(), i, val/factor, unitString); } else { file.format(LIST_OUTPUT_FORMAT, this.getName(), out.getName(), i, obj, unitString); } } } // Keyed output else if (out.getReturnType() == LinkedHashMap.class) { LinkedHashMap<?, ?> map = out.getValue(simTime, LinkedHashMap.class); for (Entry<?, ?> mapEntry : map.entrySet()) { Object obj = mapEntry.getValue(); if (obj instanceof Double) { double val = (Double)obj; file.format(LIST_OUTPUT_FORMAT, this.getName(), out.getName(), mapEntry.getKey(), val/factor, unitString); } else { file.format(LIST_OUTPUT_FORMAT, this.getName(), out.getName(), mapEntry.getKey(), obj, unitString); } } } // Expression based custom outputs else if (out.getReturnType() == ExpResult.class) { String val = InputAgent.getValueAsString(out, simTime, "%s", factor); file.format(OUTPUT_FORMAT, this.getName(), out.getName(), val, unitString); } // All other outputs else { if (ut != Unit.class && ut != DimensionlessUnit.class) unitString = Unit.getSIUnit(ut); // other outputs are not converted to preferred units String str = out.getValue(simTime, out.getReturnType()).toString(); file.format(OUTPUT_FORMAT, this.getName(), out.getName(), str, unitString); } } } /** * Returns true if there are any outputs that will be printed to the output report. */ public boolean isReportable() { return OutputHandle.isReportable(getClass()); } public String getDescription() { return desc.getValue(); } private void addAttribute(String name, AttributeHandle h) { attributeMap.put(name, h); } public boolean hasAttribute(String name) { return attributeMap.containsKey(name); } public Class<? extends Unit> getAttributeUnitType(String name) { AttributeHandle h = attributeMap.get(name); if (h == null) return null; return h.getUnitType(); } public void setAttribute(String name, ExpResult index, ExpResult value) { AttributeHandle h = attributeMap.get(name); if (h == null) this.error("Invalid attribute name: %s", name); if (index != null) { ExpResult attribValue = h.getValue(getSimTime(), ExpResult.class); if (attribValue.type != ExpResType.COLLECTION) { this.error("Trying to set attribute: %s with an index, but it is not a collection", name); } ExpResult.Collection col = attribValue.colVal; try { col.assign(index, value.getCopy()); } catch (ExpError err) { this.error("Error during assignment: %s", err.getMessage()); } return; } if (value.type == ExpResType.NUMBER && h.getUnitType() != value.unitType) this.error("Invalid unit returned by an expression. Received: %s, expected: %s", value.unitType.getSimpleName(), h.getUnitType().getSimpleName(), ""); h.setValue(value.getCopy()); } public ArrayList<String> getAttributeNames(){ ArrayList<String> ret = new ArrayList<>(); for (String name : attributeMap.keySet()) { ret.add(name); } return ret; } public ArrayList<String> getCustomOutputNames(){ ArrayList<String> ret = new ArrayList<>(); for (String name : customOutputMap.keySet()) { ret.add(name); } return ret; } public ObjectType getObjectType() { return ObjectType.getObjectTypeForClass(this.getClass()); } @Output(name = "Name", description = "The unique input name for this entity.", sequence = 0) public String getNameOutput(double simTime) { return entityName; } @Output(name = "ObjectType", description = "The class of objects that this entity belongs to.", sequence = 1) public String getObjectTypeName(double simTime) { ObjectType ot = this.getObjectType(); if (ot == null) return null; return ot.getName(); } @Output(name = "SimTime", description = "The present simulation time.", unitType = TimeUnit.class, sequence = 2) public double getSimTime(double simTime) { return simTime; } }