package net.sf.openrocket.simulation.extension.impl; import java.util.HashSet; import java.util.Set; import javax.script.Invocable; import javax.script.ScriptException; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorId; import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationListenerException; import net.sf.openrocket.simulation.listeners.SimulationComputationListener; import net.sf.openrocket.simulation.listeners.SimulationEventListener; import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ScriptingSimulationListener implements SimulationListener, SimulationComputationListener, SimulationEventListener, Cloneable { private final static Logger logger = LoggerFactory.getLogger(ScriptingSimulationListener.class); /* * NOTE: This class is used instead of using the scripting interface API * so that unimplemented script methods are not called unnecessarily. */ private Invocable invocable; private Set<String> missing = new HashSet<String>(); public ScriptingSimulationListener(Invocable invocable) { this.invocable = invocable; } @Override public boolean isSystemListener() { return false; } @Override public SimulationListener clone() { try { ScriptingSimulationListener clone = (ScriptingSimulationListener) super.clone(); clone.missing = new HashSet<String>(missing); return clone; } catch (CloneNotSupportedException e) { throw new BugException(e); } } //// SimulationListener //// @Override public void startSimulation(SimulationStatus status) throws SimulationException { invoke(Void.class, null, "startSimulation", status); } @Override public void endSimulation(SimulationStatus status, SimulationException exception) { try { invoke(Void.class, null, "endSimulation", status, exception); } catch (SimulationException e) { } } @Override public boolean preStep(SimulationStatus status) throws SimulationException { return invoke(Boolean.class, true, "preStep", status); } @Override public void postStep(SimulationStatus status) throws SimulationException { invoke(Void.class, null, "postStep", status); } //// SimulationEventListener //// @Override public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException { return invoke(Boolean.class, true, "addFlightEvent", status, event); } @Override public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException { return invoke(Boolean.class, true, "handleFlightEvent", status, event); } @Override public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException { return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance); } @Override public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException { return invoke(Boolean.class, true, "recoveryDeviceDeployment", status, recoveryDevice); } //// SimulationComputationListener //// @Override public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException { return invoke(AccelerationData.class, null, "preAccelerationCalculation", status); } @Override public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException { return invoke(AerodynamicForces.class, null, "preAerodynamicCalculation", status); } @Override public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException { return invoke(AtmosphericConditions.class, null, "preAtmosphericModel", status); } @Override public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException { return invoke(FlightConditions.class, null, "preFlightConditions", status); } @Override public double preGravityModel(SimulationStatus status) throws SimulationException { return invoke(Double.class, Double.NaN, "preGravityModel", status); } @Override public MassData preMassCalculation(SimulationStatus status) throws SimulationException { return invoke(MassData.class, null, "preMassCalculation", status); } @Override public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException { return invoke(Double.class, Double.NaN, "preSimpleThrustCalculation", status); } @Override public Coordinate preWindModel(SimulationStatus status) throws SimulationException { return invoke(Coordinate.class, null, "preWindModel", status); } @Override public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException { return invoke(AccelerationData.class, null, "postAccelerationCalculation", status, acceleration); } @Override public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException { return invoke(AerodynamicForces.class, null, "postAerodynamicCalculation", status, forces); } @Override public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException { return invoke(AtmosphericConditions.class, null, "postAtmosphericModel", status, atmosphericConditions); } @Override public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException { return invoke(FlightConditions.class, null, "postFlightConditions", status, flightConditions); } @Override public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException { return invoke(Double.class, Double.NaN, "postGravityModel", status, gravity); } @Override public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException { return invoke(MassData.class, null, "postMassCalculation", status, massData); } @Override public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException { return invoke(Double.class, Double.NaN, "postSimpleThrustCalculation", status, thrust); } @Override public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException { return invoke(Coordinate.class, null, "postWindModel", status, wind); } @SuppressWarnings("unchecked") private <T> T invoke(Class<T> retType, T def, String method, Object... args) throws SimulationException { try { if (!missing.contains(method)) { Object o = invocable.invokeFunction(method, args); if (o == null) { // Use default/null if function returns nothing return def; } else if (!o.getClass().equals(retType)) { throw new SimulationListenerException("Custom script function " + method + " returned type " + o.getClass().getSimpleName() + ", expected " + retType.getSimpleName()); } else { return (T) o; } } } catch (NoSuchMethodException e) { missing.add(method); // fall-through } catch (ScriptException e) { logger.warn("Script exception in " + method + ": " + e, e); throw new SimulationException("Script failed: " + e.getMessage()); } return def; } }