/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Markus Kraetzig
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.engine.matlab;
import java.io.File;
import org.apache.log4j.Logger;
import com.jstatcom.engine.ConfigHolder;
import com.jstatcom.engine.Engine;
import com.jstatcom.engine.EngineTypes;
import com.jstatcom.engine.LoadTypes;
import com.jstatcom.model.JSCData;
import com.jstatcom.model.JSCInt;
import com.jstatcom.model.JSCNArray;
import com.jstatcom.model.JSCNumber;
import com.jstatcom.model.JSCSArray;
import com.jstatcom.model.JSCString;
import com.jstatcom.model.JSCTypes;
/**
* Engine for calling Matlab Compiler generated dlls from .m source files.
* Allows full control over execution, especially stopping is supported.
*
* @author Markus Kraetzig
*/
public final class MatlabEngine implements Engine {
private static final Logger log = Logger.getLogger(MatlabEngine.class);
// the standard
private OutErrHandler outErrHandler = new LogHandler();
private ConfigHolder cf = null;
private static MatlabEngine mlabMaster = null;
private String modName = null;
private String systemLib = null;
/**
* Create a MLabEngine.
*
* @throws RuntimeException
* if the support library cannot be found
*/
private MatlabEngine() {
super();
cf = ConfigHolder.valueOf(EngineTypes.MATLAB);
String compiler_version = cf
.getConfig(MatlabConfigKeys.MATLAB_COMPILER_VERSION);
int sharedMemSize = Integer.parseInt(cf
.getConfig(MatlabConfigKeys.SHARED_MEM_SIZE));
if (!compiler_version.startsWith("4")) // currently not supported
throw new RuntimeException("Only Compiler Version 4 is supperted.");
String mlabLib = EngineTypes.MATLAB.DIR_NAME + File.separator
+ System.mapLibraryName("jmlabmaster");
File f = new File(mlabLib);
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
try {
Runtime.getRuntime().load(f.getAbsolutePath());
} catch (Throwable ex) {
throw new RuntimeException("Could not load system library: "
+ f.getAbsolutePath(), ex);
}
String dllName = cf.getConfig(MatlabConfigKeys.DLL_NAME);
String runtimeSubDir = cf.getConfig(MatlabConfigKeys.RUNTIME_SUBDIR);
String rtn = null;
try {
if (log.isDebugEnabled())
log.debug("init Matlab with shared memory size "
+ sharedMemSize);
rtn = initMLabMasterEx(sharedMemSize, dllName, runtimeSubDir);
} catch (Throwable ex) {
throw new RuntimeException(ex);
}
if (rtn != null)
throw new RuntimeException("Error initializing \""
+ f.getAbsolutePath() + "\":\n" + rtn);
}
/**
* Returns an instance of the MLabEngine that is a Singleton.
*
* @return an engine instance
* @throws RuntimeException
* if the native support library cannot be found
*/
public static MatlabEngine getInstance() {
if (mlabMaster == null) {
mlabMaster = new MatlabEngine();
}
return mlabMaster;
}
/**
* Makes a call to procName in the native library that was
* loaded before. It is assumed that the library was created with the MatLab
* compiler from an .m file. This call uses the mlx interface
* function. It also takes care of initialization and termination tasks.
*
* Usage Note
* The name of the procedure and the input/return parameters must conform to
* the specification in the MatLab .m file. All input and return parameters
* will first be transformed to a mxArray. Afterwards the
* return values are read back and set to the specified data objects. The
* datatypes must be correctly specified.
*
*
* @param procName
* name of the procedure to call, must be defined in the
* currently loaded library, must correspond to the name in the
* .m file (lowercase)
* @param args
* arguments for the procedure
* @param retData
* return types
* @throws IllegalArgumentException
*
* - if
args or retData contain
* null elements or unsupported types
* - if
retData contains more than 1 element
* -
*
if (procName == null || procName.length() == 0)
* or
* - if an element of
args represents
* SARRAY and contains null
* strings
*
* @throws RuntimeException
* if procName adress could not be found in the
* loaded library
* @throws UnsatisfiedLinkError
* if native support routines cannot be found
* @throws IllegalStateException
* if no system library was loaded before
*/
public void call(String procName, JSCData[] args, JSCData[] retData) {
if (procName == null || procName.length() == 0)
throw new IllegalArgumentException("Procedure name was empty.");
if (args == null)
args = new JSCData[0];
if (retData == null)
retData = new JSCData[0];
// 4th letter uppercase, the rest remains as it is
// should work for R14SP2 and higher, to make sure
// it works always, use lowercase only
procName = "mlx" + procName.substring(0, 1).toUpperCase()
+ procName.substring(1);
// calling sequence must always be in that order
synchronized (this) {
write(args);
executeMLabProc(procName, retData.length);
read(retData);
}
}
/**
* Invokes call to a Matlab routine. The number of return parameters must be
* set before to initialize the return array.
*
* @param procName
* @param rtnArgs
* @throws IllegalArgumentException
*/
private void executeMLabProc(String procName, int rtnArgs) {
if (procName == null)
throw new IllegalArgumentException("Argument was null.");
if (rtnArgs < 0)
throw new IllegalArgumentException("Negative number of returns.");
String error = null;
if (log.isDebugEnabled())
log.debug("call: " + procName);
if ((error = setRtnArgsLengthEx(rtnArgs)) != null)
throw new RuntimeException("Error setting return arg length:\n"
+ error);
if ((error = callMLabProcEx(procName)) != null)
throw new RuntimeException("Error executing \"" + procName
+ "\":\n" + error);
}
/**
* @see com.jstatcom.engine.Engine#isValid(com.jstatcom.model.JSCTypes)
*/
public boolean isValid(JSCTypes type) {
if (type == JSCTypes.NARRAY || type == JSCTypes.NUMBER
|| type == JSCTypes.INT || type == JSCTypes.STRING
|| type == JSCTypes.SARRAY)
return true;
return false;
}
/**
* Sets the name of the native library to be used or loads an executable.
* The library is then used in the next call to a function.
*
* if (loadType == MLabLoadTypes.USERLIB) then the library is
* searched in the jmlab subdirectory.
*
* @param module
* the name of the system library to load without the system
* specific extension
* @param loadType
* the load type
* @param args
* ignored
* @throws IllegalArgumentException
*
* if (loadName == null || loadType == null)
* or
* if (module.length() == 0) or
* - if no user library/exectuable with that name exists in
* the
jmlab subdir or
* - if
EXE is specified and args
* contains other types than JSCTypes.STRING or
* - if
args contains null
* elements
*
* @throws RuntimeException
* if operating system is not supported or if the system
* library/exectuable with the given name cannot be found or if
* execution returned other value than 0
* @see com.jstatcom.engine.Engine#load(String,
* com.jstatcom.engine.LoadTypes, com.jstatcom.model.JSCData[])
*/
public void load(String module, LoadTypes loadType, JSCData... args) {
if (module == null || loadType == null)
throw new IllegalArgumentException("Argument was null.");
if (module.length() == 0)
throw new IllegalArgumentException("Library name was empty.");
modName = module.toLowerCase();
if (loadType == MatlabLoadTypes.USERLIB) {
File f = new File(EngineTypes.MATLAB.DIR_NAME + File.separator
+ System.mapLibraryName(modName));
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
systemLib = f.getAbsolutePath();
String error = loadMLabLibraryEx(systemLib);
if (error != null)
throw new RuntimeException("Error loading Matlab module \""
+ systemLib + "\":\n" + error);
}
}
/**
* Gets the system library that is currently used by this engine. It can be
* changed via the load method.
*
* @return the absolute path of the current system library or null if
* none has been loaded so far
*/
public String getCurrentSysLib() {
return systemLib;
}
/**
* @see com.jstatcom.engine.Engine
*/
public void stop() {
String error = null;
if ((error = stopEx()) != null)
throw new RuntimeException(error);
}
/**
* Empty method, no termination code.
*
* @see com.jstatcom.engine.Engine#shutdown()
*/
public void shutdown() {
// there is nothing to do
}
/**
* Write a data array to MLabSlave.
*
* @param engineData
* the data array to be written
* @throws IllegalArgumentException
* if one of the data types cannot be handled by this engine or
* if (engineData == null) or if one element of
* engineData is null
* @throws RuntimeException
* if write failed
*
* @see com.jstatcom.engine.Engine
*/
private void write(JSCData[] engineData) {
if (engineData == null)
throw new IllegalArgumentException("Argument was null.");
String error = null;
if ((error = setInArgsLengthEx(engineData.length)) != null)
throw new RuntimeException("Error setting input args length:\n"
+ error);
for (int i = 0; i < engineData.length; i++) {
JSCData data = engineData[i];
if (data == null)
throw new IllegalArgumentException(
"Argument at array position [" + i
+ "] (zero based) was null.");
if (log.isDebugEnabled())
log.debug("write: " + data);
JSCTypes type = data.type();
if (!isValid(type))
throw new IllegalArgumentException("Argument type " + type
+ " at array position [" + i
+ "] (zero based) cannot be handled by MatlabEngine.");
if (data.isEmpty()) {
if ((error = writeEmptyDataEx()) != null)
throw new RuntimeException("Error writing empty \""
+ data.name() + "\":\n" + error);
}
// Number array
else if (type == JSCTypes.NARRAY) {
if ((error = writeDoubleArrayEx((double[][]) data.value())) != null)
throw new RuntimeException("Error writing \"" + data.name()
+ "\":\n" + error);
}
// Number
else if (type == JSCTypes.NUMBER) {
if ((error = writeDoubleEx(((Double) data.value())
.doubleValue())) != null)
throw new RuntimeException("Error writing \"" + data.name()
+ "\":\n" + error);
}
// Int
else if (type == JSCTypes.INT) {
if ((error = writeIntEx(((Integer) data.value()).intValue())) != null)
throw new RuntimeException("Error writing \"" + data.name()
+ "\":\n" + error);
}
// String array
else if (type == JSCTypes.SARRAY) {
if ((error = writeStringArrayEx((String[][]) data.value())) != null)
throw new RuntimeException("Error writing \"" + data.name()
+ "\":\n" + error);
}
// String
else if (type == JSCTypes.STRING) {
if ((error = writeStringEx((String) data.value())) != null)
throw new RuntimeException("Error writing \"" + data.name()
+ "\":\n" + error);
}
}
}
/**
* Reads all variables Matlab slave. The
*
* @param rtArray
* the data to be read
* @throws IllegalArgumentException
* if (rtArray == nul) or if rtArray
* contains null elements
* @throws RuntimeException
* if the variable could not be read or if an invalid type was
* specified or if the read variable does not match with the
* respective type
*/
private void read(JSCData[] rtArray) {
if (rtArray == null)
throw new IllegalArgumentException("Argument was null.");
for (int i = 0; i < rtArray.length; i++) {
if (rtArray[i] == null)
throw new IllegalArgumentException("Return at array position ["
+ i + "] (zero based) was null.");
JSCTypes type = rtArray[i].type();
String name = rtArray[i].name();
if (!isValid(type))
throw new IllegalArgumentException("Return type " + type
+ " at array position [" + i
+ "] (zero based) cannot be handled by Matlab.");
try {
if (type == JSCTypes.NARRAY) {
double[][] rtn = null;
if ((rtn = readDoubleArrayEx()) == null)
rtArray[i].clear();
else
((JSCNArray) rtArray[i]).setVal(rtn);
} else if (type == JSCTypes.SARRAY) {
String[][] rtn = null;
if ((rtn = readStringArrayEx()) == null)
rtArray[i].clear();
else
((JSCSArray) rtArray[i]).setVal(rtn);
} else if (type == JSCTypes.NUMBER || type == JSCTypes.INT) {
Double rtn = null;
if ((rtn = readDoubleEx()) == null)
rtArray[i].clear();
else {
if (type == JSCTypes.NUMBER)
((JSCNumber) rtArray[i]).setVal(rtn);
else {
if (rtn.intValue() != rtn)
log.warn("Matlab return \"" + name
+ "\": Converting double " + rtn
+ " to integer.");
((JSCInt) rtArray[i]).setVal(rtn.intValue());
}
}
} else if (type == JSCTypes.STRING) {
String rtn = null;
if ((rtn = readStringEx()) == null)
rtArray[i].clear();
else
((JSCString) rtArray[i]).setVal(rtn);
}
if (log.isDebugEnabled())
log.debug("read " + rtArray[i]);
} catch (Throwable ex) {
throw new RuntimeException("Reading variable \"" + name
+ "\" failed with error message:\n" + ex.getMessage());
}
}
}
/**
* Writes a number array to Matlab.
*
* @param arg
* number array that should be written to Matlab
* @return null if ok, otherwise error message
*/
private native String writeDoubleArrayEx(double[][] arg);
/**
* Writes a string array to Matlab.
*
* @param arg
* string array that should be written to Matlab
* @return null if ok, otherwise error message
*/
private native String writeStringArrayEx(String[][] arg);
/**
* Writes a string to Matlab.
*
* @param arg
* string that should be written to Matlab
* @return null if ok, otherwise error message
*/
private native String writeStringEx(String arg);
/**
* Writes a double to Matlab.
*
* @param arg
* double that should be written to Matlab
* @return null if ok, otherwise error message
*/
private native String writeDoubleEx(double arg);
/**
* Writes an int to Matlab.
*
* @param arg
* int that should be written to Matlab
* @return null if ok, otherwise error message
*/
private native String writeIntEx(int arg);
/**
* Sets length of input argument list for Matlab procedure call.
*
* @param arg
* length of input args list
* @return null if ok, otherwise error message
*/
private native String setInArgsLengthEx(int arg);
/**
* Sets length of return argument list for Matlab procedure call.
*
* @param arg
* length of output args list
* @return null if ok, otherwise error message
*/
private native String setRtnArgsLengthEx(int arg);
/**
* Stops the computation.
*
* @return null if ok, otherwise error message
*/
private native String stopEx();
/**
* Calls a Matlab procedure with the arguments that have been provided.
*
* @param command
* string with MLab proceudre
* @return null if execution completed ok, otherwise error
* message
*/
private native String callMLabProcEx(String command);
/**
* Loads a matlab library with the procedures to be called.
*
* @param library
* name
* @return null if execution completed ok, otherwise error
* message
*/
private native String loadMLabLibraryEx(String library);
/**
* Calls init routines for the communications library.
*
* @param sharedMemSize
* size of the shared memory area
* @param mlabDll
* name of the Matlab dll to load
* @param runtimeSubDir
* subdirectory with Matlab runtime (can be null)
* @return null if execution completed ok, otherwise error
* message
*/
private native String initMLabMasterEx(int sharedMemSize, String mlabDll,
String runtimeSubDir);
/**
* Sets an empty input argument.
*
* @return null if execution completed ok, otherwise error
* message
*/
private native String writeEmptyDataEx();
/**
* Reads a return resulting from a previous Matlab call.
*
* @return Double or null if empty
* @throws RuntimeException from the native side if something
* went wrong
*/
private native Double readDoubleEx();
/**
* Reads a return resulting from a previous Matlab call.
*
* @return double[][] or null if empty
* @throws RuntimeException from the native side if something
* went wrong
*/
private native double[][] readDoubleArrayEx();
/**
* Reads a return resulting from a previous Matlab call.
*
* @return String or null if empty
* @throws RuntimeException from the native side if something
* went wrong
*/
private native String readStringEx();
/**
* Reads a return resulting from a previous Matlab call.
*
* @return String[][] or null if empty
* @throws RuntimeException from the native side if something
* went wrong
*/
private native String[][] readStringArrayEx();
/**
* Not to be invoked. Called from JNI to deal with output from Matlab.
*
* @param s
*/
void printOutCallBack(String s) {
outErrHandler.handleOutput(s);
}
/**
* Not to be invoked. Called from JNI to deal with errors from Matlab.
*
* @param s
*/
void printErrCallBack(String s) {
outErrHandler.handleError(s);
}
/**
* Sets a new output error handler.
*
* @param outErrHandler
* new implementation for dealing with output and errors
* @throws IllegalArgumentException
* if null argument
*/
public void setOutErrHandler(OutErrHandler outErrHandler) {
if (outErrHandler == null)
throw new IllegalArgumentException("Argument was null.");
this.outErrHandler = outErrHandler;
}
}
Ads help maintain this website.