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

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

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