/*
 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 *

* @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 *

* @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.