/*
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.mlab;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import com.jstatcom.engine.CFunc;
import com.jstatcom.engine.CMalloc;
import com.jstatcom.engine.CPtr;
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;
/**
* A MLabEngine manages calls to methods defined in native
* libraries created with the MatLab compiler from .m files. It works on Win32
* and Solaris. It uses the "Shared Stubs" example by Sheng Liang to dispatch
* method calls without the need to create a distinguished JNI wrapper for every
* library to be used.
*
* Every argument and return type is translated to a pointer to an
* mxArray object and the call is made via the mlx
* interface method. The argument and return types of the function must be
* compatible with the JSCData objects defined in the
* call method. The name of the procedure to be called is the
* same as specified in the MatLab .m file.
*
* @author Markus Kraetzig
*/
public final class StubMlabEngine implements Engine {
private static final Logger log = Logger.getLogger(StubMlabEngine.class);
private final ConfigHolder cf;
/**
* Gets a pointer from a vector JSCSArray.
*
* @param sArray
* @return pointer
*/
private CPtr getSArrayPtr(JSCSArray sArray) {
String[] values = new String[sArray.rows() * sArray.cols()];
for (int j = 0, index = 0; j < sArray.cols(); j++)
for (int k = 0; k < sArray.rows(); k++) {
String el = sArray.stringAt(k, j);
if (el == null)
throw new IllegalArgumentException("Argument "
+ sArray.name()
+ " contains null element at index [" + k + "," + j
+ "].");
values[index++] = el;
}
CMalloc argPtr = new CMalloc(CPtr.SIZE * values.length);
pointerHolder.add(argPtr);
CMalloc[] argsi = new CMalloc[values.length];
for (int j = 0; j < values.length; j++) {
// size of char is 1
argsi[j] = new CMalloc(values[j].length() + 1);
pointerHolder.add(argsi[j]);
argsi[j].setString(0, values[j]);
argPtr.setCPtr(CPtr.SIZE * j, argsi[j]);
}
return argPtr;
}
// Holds pointers to deallocate memory after call was made.
private final List pointerHolder = new ArrayList();
private static StubMlabEngine mlab = null;
private String mlabLib = null;
private String systemLib = null;
private String modName = null;
/**
* Create a MLabEngine.
*
* @throws RuntimeException
* if the support library cannot be found
*/
private StubMlabEngine() {
super();
cf = ConfigHolder.valueOf(EngineTypes.MLAB);
String compiler_version = cf
.getConfig(StubMlabConfigKeys.MATLAB_COMPILER_VERSION);
String libname = "jmlab_v3"; // default is version 3.0
if (compiler_version.startsWith("4")) { // currently not supported
libname = "jmlab_v4";
throw new RuntimeException(
"Currently Matlab compiler version 4.0 generated dlls cannot be called due to lack of JNI support.");
}
mlabLib = EngineTypes.MLAB.DIR_NAME + File.separator
+ System.mapLibraryName(libname);
File f = new File(mlabLib);
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
String mlabPathLib = EngineTypes.MLAB.DIR_NAME + File.separator
+ System.mapLibraryName("jmlabpath");
f = new File(mlabPathLib);
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
Runtime.getRuntime().load(f.getAbsolutePath());
if (!compiler_version.startsWith("4"))
return;
// only needed for compiler version 4
CFunc mlabInitCall = new CFunc(mlabLib, "jmlab_init");
if (mlabInitCall.callInt(new Object[0]) < 0)
throw new RuntimeException(
"Could not initialize Matlab via \"mclInitializeApplication\".");
}
/**
* 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 StubMlabEngine getInstance() {
if (mlab == null) {
mlab = new StubMlabEngine();
}
return mlab;
}
/**
* 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.
*
* Important
* Because of a special initialization/termination behaviour of matlab
* compiled libraries with graphics, it is currently not possible to use
* graphics functions in shared libraries. For this purpose, the
* load function together with the EXE load
* type must be used. Graphics must therefore be compiled as stand-alone
* applications. The data to display can be stored in a file, which can be
* handed over as a commandline argument. The file(s) with the data can be
* created in a previous call to a non graphic library.
*
* @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)
* - if an element of
args represents
* INT, NUMBER or STRING but is empty
* - 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 (systemLib == null)
throw new IllegalStateException("No system library was loaded.");
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];
procName = procName.toLowerCase();
String procFunc = "mlx" + procName.substring(0, 1).toUpperCase()
+ procName.substring(1);
String initFunc = modName + "Initialize";
String terminateFunc = modName + "Terminate";
CFunc funcPtr = null;
CFunc initPtr = null;
CFunc terminatePtr = null;
String current = null;
// try without leading "_" first then with
try {
current = initFunc;
try {
initPtr = new CFunc(systemLib, current);
} catch (Throwable t) {
current = "_" + current;
initPtr = new CFunc(systemLib, current);
}
current = procFunc;
try {
funcPtr = new CFunc(systemLib, current);
} catch (Throwable t) {
current = "_" + current;
funcPtr = new CFunc(systemLib, current);
}
current = terminateFunc;
try {
terminatePtr = new CFunc(systemLib, current);
} catch (Throwable t) {
current = "_" + current;
terminatePtr = new CFunc(systemLib, current);
}
} catch (Throwable e) {
throw new RuntimeException("The procedure\n\"" + current
+ "\"\ncould not be found in library\n" + systemLib);
}
// Convert JSCData to CPtr to mxArray
CPtr[] argArray = prepareInputArgs(args);
CPtr[] retArray = prepareOutputArgs(retData);
// Prepare return (lhs) pointer.
CMalloc outMxPtr = new CMalloc(CPtr.SIZE * retArray.length);
pointerHolder.add(outMxPtr);
for (int i = 0; i < retArray.length; i++)
outMxPtr.setCPtr(CPtr.SIZE * i, retArray[i]);
// Prepare return (rhs) pointer.
CMalloc argMxPtr = new CMalloc(CPtr.SIZE * argArray.length);
pointerHolder.add(argMxPtr);
for (int i = 0; i < argArray.length; i++)
argMxPtr.setCPtr(CPtr.SIZE * i, argArray[i]);
// Make mlx call. Do init and terminate.
Object[] callObject = new Object[4];
callObject[0] = new Integer(retArray.length);
callObject[1] = outMxPtr;
callObject[2] = new Integer(argArray.length);
callObject[3] = argMxPtr;
initPtr.callVoid(new Object[0]);
funcPtr.callVoid(callObject);
terminatePtr.callVoid(new Object[0]);
for (int i = 0; i < retArray.length; i++)
retArray[i] = outMxPtr.getCPtr(CPtr.SIZE * i);
// Set the results from the mxArray's.
getDataFromPtr(retArray, retData);
// Deallocate mxArrays. outMxPtr, argMxPtr
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayFree");
mlabCall
.callVoid(new Object[] { new Integer(retArray.length), outMxPtr });
mlabCall
.callVoid(new Object[] { new Integer(argArray.length), argMxPtr });
// Free allocated memory.
for (Iterator iter = pointerHolder.iterator(); iter.hasNext();)
((CMalloc) iter.next()).free();
pointerHolder.clear();
}
/**
* Creates CPtr objects to mxArray* 's.
*
* @param args
* data objects
* @return array of arguments to be used for method call
*/
private CPtr[] prepareOutputArgs(JSCData[] args) {
CPtr[] oArray = new CPtr[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null)
throw new IllegalArgumentException("Return at array position ["
+ i + "] is null.");
JSCTypes type = args[i].type();
if (!isValid(type))
throw new IllegalArgumentException("Return " + args[i].type()
+ " at array position [" + i
+ "] cannot be handled by MLabEngine.");
CPtr mxPtr = CPtr.NULL;
oArray[i] = mxPtr;
}
return oArray;
}
/**
* Creates CPtr objects for each argument that allocates memory in the C
* heap.
*
* @param args
* data objects
* @return array of arguments to be used for method call
*/
private CPtr[] prepareInputArgs(JSCData[] args) {
if (args == null)
throw new IllegalArgumentException("Argument was null.");
if (args.length == 0)
return new CPtr[0];
CPtr[] oArray = new CPtr[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null)
throw new IllegalArgumentException(
"Argument at array position [" + i + "] is null.");
JSCTypes type = args[i].type();
if (!isValid(type))
throw new IllegalArgumentException("Argument " + args[i].type()
+ " at array position [" + i
+ "] cannot be handled by MLabEngine.");
if (args[i].isEmpty())
throw new IllegalArgumentException("Argument " + args[i].name()
+ " at array position [" + i + "] is empty.");
if (type == JSCTypes.STRING) {
JSCString s = (JSCString) args[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayForString");
CPtr mxPtr = mlabCall.callCPtr(new Object[] { s.value() });
oArray[i] = mxPtr;
} else if (type == JSCTypes.INT) {
JSCInt num = (JSCInt) args[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayForInt");
CPtr mxPtr = mlabCall.callCPtr(new Object[] { num.value() });
oArray[i] = mxPtr;
} else if (type == JSCTypes.NUMBER) {
JSCNumber num = (JSCNumber) args[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayForNumber");
CPtr mxPtr = mlabCall.callCPtr(new Object[] { num.value() });
oArray[i] = mxPtr;
} else if (type == JSCTypes.NARRAY) {
JSCNArray nArray = (JSCNArray) args[i];
CPtr ptr = getNArrayVecPtr(nArray);
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayForNArray");
CPtr mxPtr = mlabCall
.callCPtr(new Object[] { ptr,
new Integer(nArray.rows()),
new Integer(nArray.cols()) });
oArray[i] = mxPtr;
} else if (type == JSCTypes.SARRAY) {
JSCSArray sArray = (JSCSArray) args[i];
CPtr ptr = getSArrayPtr(sArray);
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxArrayForSArray");
CPtr mxPtr = mlabCall
.callCPtr(new Object[] { ptr,
new Integer(sArray.rows()),
new Integer(sArray.cols()) });
oArray[i] = mxPtr;
} else
throw new RuntimeException("Unsupported type " + type
+ ". Code should not be reached.");
}
return oArray;
}
/**
* Gets a pointer from a vector JSCNArray.
*
* @param nArray
* @return pointer
*/
private CPtr getNArrayVecPtr(JSCNArray nArray) {
CMalloc argPtr = null;
argPtr = new CMalloc(8 * nArray.cols() * nArray.rows());
pointerHolder.add(argPtr);
for (int i = 0, index = 0; i < nArray.cols(); i++)
for (int j = 0; j < nArray.rows(); j++)
argPtr.setDouble(8 * index++, nArray.doubleAt(j, i));
return argPtr;
}
/**
* Gets a data object from the given object array. Only those array elements
* are handled, that are
*
* @param args
* the pointers to mxArray* objects
* @param rtns
* the data objects to store the values to
*/
private void getDataFromPtr(CPtr[] args, JSCData[] rtns) {
if (args == null || rtns == null)
throw new IllegalArgumentException("Argument was null.");
if (args.length != rtns.length)
throw new IllegalArgumentException("Array lengths do not match.");
for (int i = 0; i < args.length; i++) {
if (rtns[i] == null)
throw new RuntimeException("Argument at index " + i
+ " was null.");
JSCTypes type = rtns[i].type();
CPtr ptr = args[i];
if (ptr.equals(CPtr.NULL))
throw new RuntimeException("Pointer at index " + i
+ " was null.");
CFunc mlabCallEmpty = new CFunc(mlabLib, "jmlab_mxIsEmpty");
if (mlabCallEmpty.callInt(new Object[] { ptr }) == 1) {
rtns[i].clear();
} else if (type == JSCTypes.NARRAY) {
JSCNArray nArray = (JSCNArray) rtns[i];
setNArrayFromPtr(nArray, ptr);
} else if (type == JSCTypes.SARRAY) {
JSCSArray sArray = (JSCSArray) rtns[i];
setSArrayFromPtr(sArray, ptr);
} else if (type == JSCTypes.STRING) {
JSCString s = (JSCString) rtns[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxIsChar");
if (mlabCall.callInt(new Object[] { ptr }) == 0)
throw new RuntimeException(
"Return from matlab is not of type " + type + ".");
mlabCall = new CFunc(mlabLib, "jmlab_mxGetString");
CPtr ret = mlabCall.callCPtr(new Object[] { ptr });
s.setVal(ret.getString(0));
} else if (type == JSCTypes.INT) {
JSCInt num = (JSCInt) rtns[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxIsNumber");
if (mlabCall.callInt(new Object[] { ptr }) == 0)
throw new RuntimeException(
"Return from matlab is not of type " + type + ".");
mlabCall = new CFunc(mlabLib, "jmlab_mxGetInt");
int val = mlabCall.callInt(new Object[] { ptr });
num.setVal(val);
} else if (type == JSCTypes.NUMBER) {
JSCNumber num = (JSCNumber) rtns[i];
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxIsNumber");
if (mlabCall.callInt(new Object[] { ptr }) == 0)
throw new RuntimeException(
"Return from matlab is not of type " + type + ".");
mlabCall = new CFunc(mlabLib, "jmlab_mxGetDouble");
double val = mlabCall.callDouble(new Object[] { ptr });
num.setVal(val);
}
}
}
private void setNArrayFromPtr(JSCNArray nArray, CPtr ptr) {
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxIsNumber");
if (mlabCall.callInt(new Object[] { ptr }) == 0)
throw new RuntimeException("Return from matlab for "
+ nArray.name() + " is not of type " + nArray.type() + ".");
mlabCall = new CFunc(mlabLib, "jmlab_mxGetRows");
int rows = mlabCall.callInt(new Object[] { ptr });
mlabCall = new CFunc(mlabLib, "jmlab_mxGetCols");
int cols = mlabCall.callInt(new Object[] { ptr });
mlabCall = new CFunc(mlabLib, "jmlab_mxGetRealDouble");
CPtr ret = mlabCall.callCPtr(new Object[] { ptr });
double[][] vals = new double[rows][cols];
double[] values = new double[rows * cols];
ret.copyOut(0, values, 0, values.length);
for (int j = 0, index = 0; j < cols; j++)
for (int k = 0; k < rows; k++)
vals[k][j] = values[index++];
nArray.setVal(vals);
}
private void setSArrayFromPtr(JSCSArray sArray, CPtr ptr) {
CFunc mlabCall = new CFunc(mlabLib, "jmlab_mxIsCell");
if (mlabCall.callInt(new Object[] { ptr }) == 0)
throw new RuntimeException("Return from matlab for "
+ sArray.name() + " is not of type " + sArray.type() + ".");
mlabCall = new CFunc(mlabLib, "jmlab_mxGetRows");
int rows = mlabCall.callInt(new Object[] { ptr });
mlabCall = new CFunc(mlabLib, "jmlab_mxGetCols");
int cols = mlabCall.callInt(new Object[] { ptr });
String[][] vals = new String[rows][cols];
mlabCall = new CFunc(mlabLib, "jmlab_mxGetSArray");
CPtr ret = mlabCall.callCPtr(new Object[] { ptr });
for (int j = 0, index = 0; j < cols; j++)
for (int k = 0; k < rows; k++) {
CPtr ptri = ret.getCPtr(CPtr.SIZE * index++);
vals[k][j] = ptri.getString(0);
}
sArray.setVal(vals);
}
/**
* @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.
*
* if (loadType == MLabLoadTypes.SYSLIB) then the library is
* searched in the library search path.
*
* if (loadType == MLabLoadTypes.EXE) then the executable is
* searched in the jmlab subdirectory and executed with
* args as commandline arguments.
*
* @param module
* the name of the system library to load without the system
* specific extension
* @param loadType
* the load type
* @param args
* only used for EXE load type, must contain only
* JSCString objects, empty objects are treated as
* "", can be null for no
* arguments
* @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 == StubMlabLoadTypes.USERLIB) {
File f = new File(EngineTypes.MLAB.DIR_NAME + File.separator
+ System.mapLibraryName(module));
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
systemLib = f.getAbsolutePath();
return;
}
if (loadType == StubMlabLoadTypes.SYSLIB) {
systemLib = System.mapLibraryName(module);
return;
}
if (loadType == StubMlabLoadTypes.EXE) {
StringBuffer exec = new StringBuffer();
File f = new File(EngineTypes.MLAB.DIR_NAME + File.separator
+ module);
exec.append(f.getAbsolutePath());
if (args != null)
for (int i = 0; i < args.length; i++) {
if (!(args[i] instanceof JSCString))
throw new IllegalArgumentException(
"EXE argument at position " + i
+ " is not of type " + JSCTypes.STRING
+ ".");
exec.append(" \"" + args[i].value() + "\" ");
}
try {
Process proc = Runtime.getRuntime().exec(exec.toString());
int exitVal = proc.waitFor();
BufferedReader br = new BufferedReader(new InputStreamReader(
proc.getInputStream()));
String line;
line = br.readLine();
while (line != null) {
log.info(line);
line = br.readLine();
}
br.close();
if (exitVal != 0)
throw new RuntimeException("Execution failed for\n"
+ exec.toString());
} catch (Throwable e) {
throw new RuntimeException("Execution failed for\n"
+ exec.toString());
}
return;
}
}
/**
* 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;
}
/**
* Throws an Exception, because the task cannot be stopped.
*
* @throws UnsupportedOperationException
* @see com.jstatcom.engine.Engine#stop()
*/
public void stop() {
throw new UnsupportedOperationException(toString()
+ " tasks cannot be stopped.");
}
/**
* Calls termination code.
*
* @see com.jstatcom.engine.Engine#shutdown()
*/
public void shutdown() {
String compiler_version = cf
.getConfig(StubMlabConfigKeys.MATLAB_COMPILER_VERSION);
if (!compiler_version.startsWith("4"))
return;
CFunc mlabTermCall = new CFunc(mlabLib, "jmlab_terminate");
mlabTermCall.callVoid(new Object[0]);
}
}
Ads help maintain this website.