/* * JaamSim Discrete Event Simulation * Copyright (C) 2014 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.input; import java.util.HashMap; import java.util.Map; import com.jaamsim.basicsim.Entity; import com.jaamsim.input.ExpParser.Assigner; import com.jaamsim.input.ExpParser.EvalContext; import com.jaamsim.input.ExpParser.OutputResolver; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.Unit; /** * Utility class to bridge the expression parser and attribute assignment * @author Matt Chudleigh * */ public class ExpEvaluator { private static ExpResType getTypeForClass(Class<?> klass) { if (klass == String.class) { return ExpResType.STRING; } else if (Entity.class.isAssignableFrom(klass)){ return ExpResType.ENTITY; } else if (OutputHandle.isNumericType(klass) || klass == boolean.class || klass == Boolean.class) { return ExpResType.NUMBER; } else if (ExpCollections.isCollectionClass(klass)){ return ExpResType.COLLECTION; } else { return null; } } private static ExpResult getResultFromOutput(OutputHandle oh, double simTime) { Class<?> retType = oh.getReturnType(); if (retType == ExpResult.class) { // This is already an expression, so return it return oh.getValue(simTime, ExpResult.class); } if (retType == String.class) { return ExpResult.makeStringResult(oh.getValue(simTime, String.class)); } if (Entity.class.isAssignableFrom(retType)) { return ExpResult.makeEntityResult(oh.getValue(simTime, Entity.class)); } if ( OutputHandle.isNumericType(retType) || retType == boolean.class || retType == Boolean.class) { return ExpResult.makeNumResult(oh.getValueAsDouble(simTime, 0), oh.getUnitType()); } if (ExpCollections.isCollectionClass(retType)) { return ExpCollections.getCollection(oh.getValue(simTime, retType), oh.getUnitType()); } // No known type return null; } public static ExpResult getResultFromObject(Object val, Class<? extends Unit> unitType) throws ExpError { if (ExpResult.class.isAssignableFrom(val.getClass())) { return (ExpResult)val; } if (String.class.isAssignableFrom(val.getClass())) { return ExpResult.makeStringResult((String)val); } if (Entity.class.isAssignableFrom(val.getClass())) { return ExpResult.makeEntityResult((Entity)val); } if (Double.class.isAssignableFrom(val.getClass())) { return ExpResult.makeNumResult((Double)val, unitType); } if (ExpCollections.isCollectionClass(val.getClass())) { return ExpCollections.getCollection(val, unitType); } throw new ExpError(null, 0, "Unknown type in expression: %s", val.getClass().getSimpleName()); } public static class EntityParseContext implements ExpParser.ParseContext { private final String source; private final Entity thisEnt; private final HashMap<Entity, String> entityReferences = new HashMap<>(); private void addEntityReference(Entity ent) { entityReferences.put(ent, ent.getName()); } // Return a version of the expression string updated for an entities that have changed their names // since the expression was parsed public String getUpdatedSource() { String ret = source; for (Map.Entry<Entity, String> entEntry : entityReferences.entrySet()) { Entity ent = entEntry.getKey(); String oldName = entEntry.getValue(); if (ent.getName() != null && ent.getName().equals(oldName)) { // This name did not change continue; } String newName = ent.getName(); if (ent.getName() == null) { // An entity with a null name means the entity has been deleted newName = "**DeletedEntity**"; } ret = ret.replace("["+oldName+"]", "["+newName+"]"); } return ret; } public EntityParseContext(Entity thisEnt, String source) { this.thisEnt = thisEnt; this.source = source; } @Override public ExpParser.UnitData getUnitByName(String name) { Unit unit = Input.tryParseUnit(name, Unit.class); if (unit == null) { return null; } ExpParser.UnitData ret = new ExpParser.UnitData(); ret.scaleFactor = unit.getConversionFactorToSI(); ret.unitType = unit.getClass(); addEntityReference(unit); return ret; } @Override public Class<? extends Unit> multUnitTypes(Class<? extends Unit> a, Class<? extends Unit> b) { return Unit.getMultUnitType(a, b); } @Override public Class<? extends Unit> divUnitTypes(Class<? extends Unit> num, Class<? extends Unit> denom) { return Unit.getDivUnitType(num, denom); } @Override public ExpResult getValFromName(String name, String source, int pos) throws ExpError { Entity ent; if (name.equals("this")) ent = thisEnt; else { ent = Entity.getNamedEntity(name); if (ent != null) { addEntityReference(ent); } } if (ent == null) { throw new ExpError(source, pos, "Could not find entity: %s", name); } return ExpResult.makeEntityResult(ent); } @Override public OutputResolver getOutputResolver(String name) throws ExpError { return new EntityResolver(name); } @Override public OutputResolver getConstOutputResolver(ExpResult constEnt, String name) throws ExpError { if (constEnt.type != ExpResType.ENTITY) { throw new ExpError(null, 0, "Can not index a non-entity type"); } if (constEnt.entVal == null) { throw new ExpError(null, 0, "Trying to resolve output on null entity"); } OutputHandle oh = constEnt.entVal.getOutputHandle(name); if (oh == null) { throw new ExpError(null, 0, "Could not find output '%s' on entity '%s'", name, constEnt.entVal.getName()); } if (oh.canCache()) { return new CachedResolver(oh); } else { return new EntityResolver(name); } } @Override public Assigner getAssigner(String attribName) throws ExpError { return new EntityAssigner(attribName); } @Override public Assigner getConstAssigner(ExpResult constEnt, String attribName) throws ExpError { // TODO: const optimization return new EntityAssigner(attribName); } } private static class CachedResolver implements ExpParser.OutputResolver { private final OutputHandle handle; private final ExpResType type; public CachedResolver(OutputHandle oh) throws ExpError { handle = oh; Class<?> retType = oh.getReturnType(); type = getTypeForClass(retType); if (type == null) { throw new ExpError(null, 0, "Output '%s' on entity '%s does not return a type compatible with the expression engine'", oh.getName(), oh.ent.getName()); } } @Override public ExpResult resolve(EvalContext ec, ExpResult ent) throws ExpError { double simTime = 0; if (ec != null) { EntityEvalContext eec = (EntityEvalContext)ec; simTime = eec.simTime; } switch (type) { case NUMBER: double val = handle.getValueAsDouble(simTime, 0); return ExpResult.makeNumResult(val, handle.getUnitType()); case ENTITY: return ExpResult.makeEntityResult(handle.getValue(simTime, Entity.class)); case STRING: return ExpResult.makeStringResult(handle.getValue(simTime, String.class)); case COLLECTION: return ExpCollections.getCollection(handle.getValue(simTime, handle.getReturnType()), handle.getUnitType()); default: assert(false); return ExpResult.makeNumResult(handle.getValueAsDouble(simTime, 0), handle.getUnitType()); } } @Override public ExpValResult validate(ExpValResult entValRes) { if (handle == null) { // There is no cached output handle, so we can not decide return ExpValResult.makeUndecidableRes(); } Class<? extends Unit> ut = DimensionlessUnit.class; if (type == ExpResType.NUMBER) ut = handle.getUnitType(); return ExpValResult.makeValidRes(type, ut); } } private static class EntityResolver implements ExpParser.OutputResolver { private final String outputName; public EntityResolver(String name) { outputName = name.intern(); } @Override public ExpResult resolve(EvalContext ec, ExpResult entRes) throws ExpError { double simTime = 0; if (ec != null) { EntityEvalContext eec = (EntityEvalContext)ec; simTime = eec.simTime; } if (entRes.type != ExpResType.ENTITY) { throw new ExpError(null, 0, "Can not look up output on non-entity type"); } Entity ent = entRes.entVal; if (ent == null) { throw new ExpError(null, 0, "Trying to resolve output on null entity"); } OutputHandle oh = ent.getOutputHandleInterned(outputName); if (oh == null) { throw new ExpError(null, 0, "Could not find output '%s' on entity '%s'", outputName, ent.getName()); } ExpResult res = getResultFromOutput(oh, simTime); if (res == null) throw new ExpError(null, 0, "Output %s, on entity %s does not return a type compatible with expressions.", oh.getName(), oh.ent.getName()); return res; } @Override public ExpValResult validate(ExpValResult entValRes) { if (entValRes.type != ExpResType.ENTITY) { return ExpValResult.makeErrorRes(new ExpError(null, 0, "Can not evalutate output on non-entity type")); } return ExpValResult.makeUndecidableRes(); } } private static class EntityAssigner implements ExpParser.Assigner { private final String attribName; EntityAssigner(String attribName) { this.attribName = attribName; } @Override public void assign(ExpResult ent, ExpResult index, ExpResult val) throws ExpError { if (ent.type != ExpResType.ENTITY) { throw new ExpError(null, 0, "Can not execute assignment, not assigning to an entity"); } Entity assignEnt = ent.entVal; if (assignEnt == null) { throw new ExpError(null, 0, "Trying to assign to a null entity"); } assignEnt.setAttribute(attribName, index, val); } } private static class EntityEvalContext implements ExpParser.EvalContext { private final double simTime; public EntityEvalContext(double simTime) { this.simTime = simTime; } } public static EntityParseContext getParseContext(Entity thisEnt, String source) { return new EntityParseContext(thisEnt, source); } public static ExpResult evaluateExpression(ExpParser.Expression exp, double simTime) throws ExpError { EntityEvalContext evalContext = new EntityEvalContext(simTime); return exp.evaluate(evalContext); } }