blob: 4b0afedb671e5a425c255518044a5994a2575300 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2008 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.emc.simulink.engine;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.epsilon.emc.simulink.exception.EpsilonSimulinkInternalException;
import org.eclipse.epsilon.emc.simulink.exception.MatlabException;
import org.eclipse.epsilon.emc.simulink.exception.MatlabRuntimeException;
import org.eclipse.epsilon.emc.simulink.model.IGenericSimulinkModel;
import org.eclipse.epsilon.emc.simulink.util.MatlabEngineUtil;
import org.eclipse.epsilon.profiling.Stopwatch;
@SuppressWarnings("unchecked")
public class MatlabEngine {
/**
*
*/
private static final String NL = System.lineSeparator();
/* MATHWOWRKS CLASS */
private static final String MATLAB_ENGINE_CLASS = "com.mathworks.engine.MatlabEngine";
/* ENGINE STATIC METHODS */
private static final String CONNECT_MATLAB_METHOD = "connectMatlab";
private static final String START_MATLAB_METHOD = "startMatlab";
private static final String FIND_MATLAB_METHOD = "findMatlab";
/* ASYNC
private static final String CONNECT_MATLAB_ASYNC_METHOD = "connectMatlabAsync";
private static final String START_MATLAB_ASYNC_METHOD = "connectMatlabAsync";
private static final String FIND_MATLAB_ASYNC_METHOD = "connectMatlabAsync";
*/
/* ENGINE METHODS */
private static final String GET_VARIABLE_METHOD = "getVariable";
private static final String PUT_VARIABLE_METHOD = "putVariable";
private static final String EVAL_METHOD = "eval";
private static final String EVAL_ASYNC_METHOD = "evalAsync";
private static final String FEVAL_METHOD = "feval";
private static final String DISCONNECT_METHOD = "disconnect";
private static final String QUIT_METHOD = "quit";
private static final String CLOSE_METHOD = "close";
private static final String RESULT = "result";
private static final String ASIGN = " = ";
private static final String PARAM_REGEX = "[?]";
/* ASYNC
private static final String GET_VARIABLE_ASYNC_METHOD = "getVariableAsync";
private static final String PUT_VARIABLE_ASYNC_METHOD = "putVariableAsync";
private static final String DISCONNECT_ASYNC_METHOD = "disconnectAsync";
private static final String QUIT_ASYNC_METHOD = "quitAsync";
*/
private static final String FEVAL_ASYNC_METHOD = "fevalAsync";
/* ERRORS AND LOGS */
private static final String ERROR_INVALID_PARAMETER_NUMBER = "%d parameters were expected but %d were provided";
/* VARIABLES */
protected Object engine;
private static Class<?> engine_class;
/* METHODS */
protected Method getVariableMethod;
protected Method getVariableAsyncMethod;
protected Method putVariableMethod;
protected Method putVariableAsyncMethod;
protected Method evalMethod;
protected Method evalAsyncMethod;
protected Method fevalMethod;
protected Method fevalWithVariableOutputsMethod;
protected Method fevalAsyncMethod;
protected Method closeMethod;
protected Method quitMethod;
protected Method disconnectMethod;
protected String project;
protected Set<IGenericSimulinkModel> models = new HashSet<>();
protected StringBuilder evalCommandQueue = new StringBuilder();
protected Boolean tryCatchEnabled = true;
protected StringBuilder sb;
protected Stopwatch stopWatch;
private boolean reduceExchanges;
public boolean isDisconnected() throws Exception{
Field declaredField = engine_class.getDeclaredField("fDisconnected");
declaredField.setAccessible(true);
AtomicBoolean disconnected = (AtomicBoolean) declaredField.get(engine);
return disconnected.get();
}
/**
* @return the stopWatch
*/
public Stopwatch getStopWatch() {
return stopWatch;
}
public static void setEngineClass(Class<?> matlabEngineClass) {
engine_class = matlabEngineClass;
}
public void trackApi(boolean track){
sb = (track) ? new StringBuilder() : null;
}
public StringBuilder getStream(){
return sb;
}
public void setProject(String project) throws MatlabException {
this.project = project;
if (project.equals("current")) {
eval("currentProject;");
} else {
File proj = new File(project);
eval("cd '?';", proj.getParent());
String location = proj.getName().substring(0, proj.getName().indexOf('.'));
eval("simulinkproject('?');", location);
}
}
public void addModel(IGenericSimulinkModel model) {
this.models.add(model);
}
public String getProject() {
return project;
}
public void release(IGenericSimulinkModel model) throws MatlabRuntimeException {
if (project != null && !models.isEmpty()) {
models.remove(model);
}
if (models.isEmpty()) {
this.project = null;
MatlabEnginePool.getInstance().release(this);
}
}
public MatlabEngine() throws Exception {
if (engine_class == null) throw new IllegalStateException("Engine Class not yet set");
try {
engine = engine_class.getMethod(CONNECT_MATLAB_METHOD).invoke(null);
} catch (InvocationTargetException e) {
try {
engine = engine_class.getMethod(START_MATLAB_METHOD).invoke(null);
} catch (InvocationTargetException ex) {
throw new MatlabException(e);
} catch (IllegalAccessException | IllegalArgumentException | NoSuchMethodException | SecurityException ex) {
throw new EpsilonSimulinkInternalException(e);
}
} catch (IllegalAccessException | IllegalArgumentException | NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
public void enableTryCatch(boolean enableTryCatch) {
this.tryCatchEnabled = enableTryCatch;
}
public Boolean isTryCatchEnabled() {
return tryCatchEnabled;
}
public Object evalWithSetupAndResult(String setup, String cmd, Object... parameters) throws MatlabException {
eval(setup + (setup.endsWith(";") ? "" : ";") + RESULT + ASIGN + cmd, parameters);
return getVariable(RESULT);
}
public Object evalWithResult(String cmd) throws MatlabException {
eval(RESULT + ASIGN + cmd);
return getVariable(RESULT);
}
public Object evalWithResult(String cmd, Object... parameters) throws MatlabException {
eval(RESULT + ASIGN + cmd, parameters);
return getVariable(RESULT);
}
/**
* This method is now lazily executed. All commands are stored in a queue and are only dispatched when
* the method {@link #flush()} is invoked.
* @param cmd
* @param parameters
* @throws MatlabException
*/
public void eval(String cmd, Object... parameters) throws MatlabException {
cmd = " " + cmd + " ";
String[] parts = cmd.split(PARAM_REGEX);
if (parts.length != parameters.length + 1) {
String error = String.format(ERROR_INVALID_PARAMETER_NUMBER, parts.length - 1, parameters.length);
throw new RuntimeException(error);
}
cmd = parts[0];
for (int i = 0; i < parameters.length; i++) {
cmd += String.valueOf(parameters[i]).replace("'", "''").replace("\n", "\\n") + parts[i + 1];
}
cmd = cmd.substring(1, cmd.length() - 1);
eval(cmd);
}
// CLASS HELPERS
public static boolean is(Object obj) {
return (getMatlabClass() == null) ? false : getMatlabClass().isInstance(obj);
}
protected static Class<?> getMatlabClass() {
if (engine_class == null) {
try {
engine_class = MatlabClassLoader.getInstance().loadMatlabClass(MATLAB_ENGINE_CLASS);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return engine_class;
}
public MatlabEngine(Object engine) {
if (is(engine)) {
this.engine = engine;
}
}
protected Object processInputObject(Object o) {
return MatlabEngineUtil.formatForMatlabEngine(o);
}
protected Object[] processInputObject(Object[] objects) {
return Arrays.stream(objects).map(this::processInputObject).toArray();
}
// CLASS METHODS
public static MatlabEngine startMatlab() throws MatlabException {
try {
Method startMatlabMethod = engine_class.getMethod(START_MATLAB_METHOD);
Object returnedEngine = startMatlabMethod.invoke(null);
return new MatlabEngine(returnedEngine);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new EpsilonSimulinkInternalException(e);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
}
}
public static MatlabEngine startMatlab(String[] options) throws MatlabException {
try {
Method startMatlabMethod = engine_class.getMethod(START_MATLAB_METHOD, String[].class);
Object returnedEngine = startMatlabMethod.invoke(null, (Object) options);
return new MatlabEngine(returnedEngine);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new EpsilonSimulinkInternalException(e);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
}
}
public static String[] findMatlab() throws MatlabException {
try {
Method findMatlabMethod = engine_class.getMethod(FIND_MATLAB_METHOD);
return (String[]) findMatlabMethod.invoke(null);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new EpsilonSimulinkInternalException(e);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
}
}
public static MatlabEngine connectMatlab() throws MatlabException {
try {
Method connectMatlabMethod = engine_class.getMethod(CONNECT_MATLAB_METHOD);
Object returnedEngine = connectMatlabMethod.invoke(null);
return new MatlabEngine(returnedEngine);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new EpsilonSimulinkInternalException(e);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
}
}
public static MatlabEngine connectMatlab(String name) throws MatlabException {
try {
Method connectMatlabMethod = engine_class.getMethod(CONNECT_MATLAB_METHOD, String.class);
Object returnedEngine = connectMatlabMethod.invoke(null, name);
return new MatlabEngine(returnedEngine);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new EpsilonSimulinkInternalException(e);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
}
}
private void start() {
if (sb !=null) {
if (stopWatch == null) {
stopWatch = new Stopwatch();
} else {
stopWatch.resume();
}
}
}
private void stop() {
if (sb !=null) {
stopWatch.pause();
}
}
public void resetTimer() {
stopWatch = null;
}
// OBJECT METHODS
public void eval(String cmd) throws MatlabException {
if (evalMethod == null) {
try {
evalMethod = engine.getClass().getMethod(EVAL_METHOD, String.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isTryCatchEnabled()) {
cmd = "try\n" + cmd + "\ncatch ME \ne = MException(ME.identifier,ME.message); throw(e); \nend\n";
}
if (sb !=null) {
sb.append("%eval").append(NL).append(cmd).append(NL);
}
if (isReduceExchanges()) {
evalCommandQueue.append(cmd);
} else {
try {
start();
evalMethod.invoke(engine, cmd);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
}
public Future<Void> evalAsync(String cmd) throws MatlabException {
if (evalAsyncMethod == null) {
try {
evalAsyncMethod = engine.getClass().getMethod(EVAL_ASYNC_METHOD, String.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
if (sb !=null) {
sb.append("%evalAsync").append(NL).append(cmd).append(NL);
}
start();
return (Future<Void>) evalAsyncMethod.invoke(engine, cmd);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public Object getVariable(String variable) throws MatlabException {
if (getVariableMethod == null) {
try {
getVariableMethod = engine.getClass().getMethod(GET_VARIABLE_METHOD, String.class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
if (sb !=null) {
sb.append("%getVariable").append(NL).append(variable).append(NL);
}
start();
Object invoke = getVariableMethod.invoke(engine, variable);
stop();
return MatlabEngineUtil.parseMatlabEngineVariable(invoke);
} catch (InvocationTargetException e) {
String className= (String)evalWithResult("class(?);", variable);
if (className.equals("Simulink.SimulationOutput")) {
HashMap<String, Object> output = new HashMap<>();
Object evalWithResult = evalWithResult("?.ErrorMessage;",variable);
output.put("ErrorMessage", evalWithResult);
evalWithResult = evalWithResult("?.simout;",variable);
output.put("simout", evalWithResult);
evalWithResult = evalWithResult("?.tout;",variable);
output.put("tout", evalWithResult);
eval("meta = ?.SimulationMetadata;",variable);
HashMap<String, Object> meta = new HashMap<>();
eval("meta.ModelInfo;");
evalWithResult = getVariable("ans");
meta.put("ModelInfo", evalWithResult);
eval("meta.TimingInfo;");
evalWithResult = getVariable("ans");
meta.put("TimingInfo", evalWithResult);
eval("meta.ExecutionInfo;");
evalWithResult = getVariable("ans");
meta.put("ExecutionInfo", evalWithResult);
eval("meta.UserString;");
evalWithResult = getVariable("ans");
meta.put("UserString", evalWithResult);
eval("meta.UserData;");
evalWithResult = getVariable("ans");
meta.put("UserData", evalWithResult);
return output;
}
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public Object fevalWithResult(int numberOfOutputs, String function, Object...handles) throws MatlabException {
if (fevalWithVariableOutputsMethod == null) {
try {
fevalWithVariableOutputsMethod = engine.getClass().getMethod(FEVAL_METHOD, int.class, String.class, Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
Object[] processInputObject = processInputObject(handles);
start();
Object res = fevalWithVariableOutputsMethod.invoke(engine, numberOfOutputs, function, processInputObject);
stop();
if (res != null) {
return MatlabEngineUtil.parseMatlabEngineVariable(res);
}
return null;
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void feval(int numberOfOutputs, String function, Object...handles) throws MatlabException {
if (fevalWithVariableOutputsMethod == null) {
try {
fevalWithVariableOutputsMethod = engine.getClass().getMethod(FEVAL_METHOD, int.class, String.class, Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
Object[] processInputObject = processInputObject(handles);
start();
fevalWithVariableOutputsMethod.invoke(engine, numberOfOutputs, function, processInputObject);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public Object fevalWithResult(String function, Object... handles) throws MatlabException {
if (fevalMethod == null) {
try {
fevalMethod = engine.getClass().getMethod(FEVAL_METHOD, String.class, Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
Object[] processInputObject = processInputObject(handles);
start();
Object res = fevalMethod.invoke(engine, function, processInputObject);
stop();
return MatlabEngineUtil.parseMatlabEngineVariable(res);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void feval(String function, Object... handles) throws MatlabException {
if (fevalMethod == null) {
try {
fevalMethod = engine.getClass().getMethod(FEVAL_METHOD, String.class, Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
Object[] processInputObject = processInputObject(handles);
start();
fevalMethod.invoke(engine, function, processInputObject);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void putVariable(String variableName, Object value) throws MatlabException {
if (putVariableMethod == null) {
try {
putVariableMethod = engine.getClass().getMethod(PUT_VARIABLE_METHOD, String.class, Object.class);
}catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
Object processInputObject = processInputObject(value);
start();
putVariableMethod.invoke(engine, variableName, processInputObject);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void fevalAsync(String function, Object... handles) throws MatlabException {
if (fevalAsyncMethod == null) {
try {
fevalAsyncMethod = engine.getClass().getMethod(FEVAL_ASYNC_METHOD, String.class, Object[].class);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
start();
fevalAsyncMethod.invoke(engine, function, handles);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void close() throws MatlabException {
if (closeMethod == null) {
try {
closeMethod = engine.getClass().getMethod(CLOSE_METHOD);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
start();
closeMethod.invoke(engine);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void quit() throws MatlabException {
if (quitMethod == null) {
try {
quitMethod = engine.getClass().getMethod(QUIT_METHOD);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
start();
quitMethod.invoke(engine);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
public void disconnect() throws MatlabException {
if (disconnectMethod == null) {
try {
disconnectMethod = engine.getClass().getMethod(DISCONNECT_METHOD);
} catch (NoSuchMethodException | SecurityException e) {
throw new EpsilonSimulinkInternalException(e);
}
}
if (isReduceExchanges()) flush();
try {
start();
disconnectMethod.invoke(engine);
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
}
}
/**
* Executes all commands to be executed in the eval method that have been stored in a queue.
* @throws MatlabException
*/
public void flush() throws MatlabException {
if (isReduceExchanges()) {
String cmd = evalCommandQueue.toString();
try {
if (!cmd.isEmpty()) {
start();
evalMethod.invoke(engine, cmd);
}
} catch (InvocationTargetException e) {
throw new MatlabException(e);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new EpsilonSimulinkInternalException(e);
} finally {
stop();
evalCommandQueue = new StringBuilder();
}
}
}
public boolean isEvalCommandQueueEmpty(){
return evalCommandQueue.toString().isEmpty();
}
/**
* @param reduceExchanges
*/
public void setReduceExchanges(boolean reduceExchanges) {
this.reduceExchanges = reduceExchanges;
}
public boolean isReduceExchanges() {
return reduceExchanges;
}
}