/*
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.stub;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.jstatcom.engine.CFunc;
import com.jstatcom.engine.CMalloc;
import com.jstatcom.engine.CPtr;
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;
import com.jstatcom.util.UMatrix;
/**
* A StubEngine manages calls to methods defined in arbitrary
* system libraries. It works on Win32, Linux (i386), 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.
*
* The methods defined in the system library can take certain types as arguments
* and returns, which are
*
* - int, &int
*
- double, &double
*
- float, &float
*
- int*
*
- double*
*
- float*
*
- int**
*
- double**
*
- float**
*
- char*
*
- char**
*
- char***
*
* The argument and return types must be compatible with the
* JSCData objects defined in the call method.
*
* @author Markus Kraetzig
*/
public final class StubEngine implements Engine {
// Holds pointers to deallocate memory after call was made.
private final List pointerHolder = new ArrayList();
private static StubEngine stub = null;
private String systemLib = null;
/**
* Create a StubEngine.
*/
private StubEngine() {
super();
}
/**
* Returns an instance of the StubEngine that is a Singleton.
*
* @return an engine instance
*/
public static StubEngine getInstance() {
if (stub == null) {
stub = new StubEngine();
}
return stub;
}
/**
* Makes a call to procName in the native library that was
* loaded before. The method signature is created from the specified input
* and return parameters. The CSignTypes can be used to set
* certain properties (FLOAT, VECTOR, BYREF) on the corresponding data
* objects. This can be used to conform to a wide range of different method
* signatures. The following C-types correspond to JSCTypes:
*
* double - JSCNumber
* float - JSCNumber with FLOAT == true
* int - JSCInt
* &double - JSCNumber with
* BYREF == true
* &float - JSCNumber with
* FLOAT == true && BYREF == true
* &int - JSCInt with BYREF == true
* char* - JSCString
* char** - JSCSArray with VECTOR == true
* char*** - JSCSArray with VECTOR == false
* double* - JSCNArray with VECTOR == true
* double** - JSCSNrray with
* VECTOR == false
* float* - JSCNArray with
* VECTOR == true && FLOAT == true
* float** - JSCSNrray with
* VECTOR == false && FLOAT == true
*
*
* Input specification
* If a type corresponds to a C-pointer type (instead of a value), then the
* memory is allocated according to the rows and columns of the respective
* JSCData object. All input arguments are filled with the
* values of the corresponding pointers after the procedure call, which
* means that it is possible to use an argument that holds the results.
* Value arguments must not be empty.
*
* Return specification
* If retData == null or has zero length, then the procedure
* is assumed to return void. If the return type
* (retData[0].tpye()) is a pointer type, then it is assumed, that the
* returned pointer from the procedure call has the same dimensions as
* retData[0], because is not possible to retrieve the size
* from the returned pointer.
*
* Vectors
* Often C-functions have input arguments that correspond to a one
* dimensional array. To call functions with these types, the vector
* property of JSCSArray and JSCNArray must be
* set to true. The data object is then vectorized (similar
* to UMatrix.vec). After the function call the pointer is
* indirected to the data object again, the array elements are filled with
* the values from the pointer columnwise, keeping the original array
* dimension.
*
* @param procName
* name of the procedure to call, must be defined in the
* currently loaded library
* @param args
* arguments for the procedure, the method signature is derived
* from it
* @param retData
* return type, can contain at most 1 element, void
* if empty or null, if type is an array, it has
* to be set to the rows and cols of the returned pointer
* @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 and
* BYREF == false
* - 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 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];
if (retData.length > 1)
throw new IllegalArgumentException(
"Only one return parameter allowed for StubEngine (current:"
+ retData.length + ").");
CFunc funcPtr = null;
try {
funcPtr = new CFunc(systemLib, procName);
} catch (Throwable e) {
throw new RuntimeException("The adress of procedure\n\"" + procName
+ "\"\ncould not be found in library\n" + systemLib);
}
JSCTypes rtype = null;
// Check return type.
if (retData.length > 0) {
if (retData[0] == null)
throw new IllegalArgumentException("Return argument is null.");
rtype = retData[0].type();
if (!isValid(rtype))
throw new IllegalArgumentException("Return type "
+ retData[0].type()
+ " cannot be handled by StubEngine.");
}
Object[] argArray = prepareInputArgs(args);
// Call depending on return parameter types.
if (rtype == null) {
funcPtr.callVoid(argArray);
} else if (rtype == JSCTypes.NARRAY || rtype == JSCTypes.SARRAY) {
CPtr rtnPtr = funcPtr.callCPtr(argArray);
getDataFromPtr(new CPtr[] { rtnPtr }, retData);
} else if (rtype == JSCTypes.INT) {
int rtnInt = funcPtr.callInt(argArray);
((JSCInt) retData[0]).setVal(rtnInt);
} else if (rtype == JSCTypes.NUMBER) {
JSCNumber num = (JSCNumber) retData[0];
double rtnDouble = 0;
if (Boolean.valueOf(num.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue())
rtnDouble = funcPtr.callDouble(argArray);
else
rtnDouble = funcPtr.callFloat(argArray);
num.setVal(rtnDouble);
} else
throw new IllegalArgumentException("Return type " + rtype
+ " cannot be handled by StubEngine.");
// Input arguments might have been changed as well.
getDataFromPtr(argArray, args);
// Free allocated memory.
for (Iterator iter = pointerHolder.iterator(); iter.hasNext();)
((CMalloc) iter.next()).free();
pointerHolder.clear();
}
/**
* 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 Object[] prepareInputArgs(JSCData[] args) {
if (args == null)
throw new IllegalArgumentException("Argument was null.");
if (args.length == 0)
return new Object[0];
Object[] oArray = new Object[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 StubEngine.");
if (type == JSCTypes.STRING) {
if (args[i].isEmpty())
throw new IllegalArgumentException("Argument "
+ args[i].name() + " at array position [" + i
+ "] is empty.");
oArray[i] = args[i].value();
} else if (type == JSCTypes.INT) {
if (!Boolean.valueOf(
args[i].getJSCProperty(CSignTypes.BYREF) + "")
.booleanValue()) {
if (args[i].isEmpty())
throw new IllegalArgumentException("Argument "
+ args[i].name() + " at array position [" + i
+ "] is empty.");
oArray[i] = args[i].value();
} else {
CMalloc refPtr = new CMalloc(4);
pointerHolder.add(refPtr);
if (!args[i].isEmpty())
refPtr.setInt(0, ((JSCInt) args[i]).intVal());
oArray[i] = refPtr;
}
} else if (type == JSCTypes.NUMBER) {
JSCNumber num = (JSCNumber) args[i];
if (!Boolean.valueOf(
args[i].getJSCProperty(CSignTypes.BYREF) + "")
.booleanValue()) {
if (args[i].isEmpty())
throw new IllegalArgumentException("Argument "
+ args[i].name() + " at array position [" + i
+ "] is empty.");
Object toSet = args[i].value();
if (Boolean.valueOf(
num.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue())
toSet = new Float(num.doubleVal());
oArray[i] = toSet;
} else {
CMalloc refPtr = null;
new CMalloc(8);
if (!args[i].isEmpty())
if (Boolean.valueOf(
num.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue()) {
refPtr = new CMalloc(4);
refPtr.setFloat(0, num.getNumber().floatValue());
} else {
refPtr = new CMalloc(8);
refPtr.setDouble(0, num.doubleVal());
}
pointerHolder.add(refPtr);
oArray[i] = refPtr;
}
} else if (type == JSCTypes.NARRAY) {
JSCNArray nArray = (JSCNArray) args[i];
if (Boolean.valueOf(
nArray.getJSCProperty(CSignTypes.VECTOR) + "")
.booleanValue()) {
oArray[i] = getNArrayVecPtr(nArray);
} else {
oArray[i] = getNArrayPtr(nArray);
}
} else if (type == JSCTypes.SARRAY) {
JSCSArray sArray = (JSCSArray) args[i];
if (Boolean.valueOf(
sArray.getJSCProperty(CSignTypes.VECTOR) + "")
.booleanValue())
oArray[i] = getStringVecPtr(sArray);
else
oArray[i] = getStringArrayPtr(sArray);
} else
throw new RuntimeException("Unsupported type " + type
+ ". Code should not be reached.");
}
return oArray;
}
/**
* Gets a pointer from a JSCNArray.
*
* @param nArray
* @return pointer
*/
private CPtr getNArrayPtr(JSCNArray nArray) {
CMalloc argPtr = new CMalloc(CPtr.SIZE * nArray.rows());
CMalloc[] argsi = new CMalloc[nArray.rows()];
pointerHolder.add(argPtr);
if (!Boolean.valueOf(nArray.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue()) {
double[][] values = nArray.doubleArray();
pointerHolder.add(argPtr);
for (int j = 0; j < nArray.rows(); j++) {
// size of double is 8
argsi[j] = new CMalloc(8 * nArray.cols());
pointerHolder.add(argsi[j]);
argsi[j].copyIn(0, values[j], 0, nArray.cols());
argPtr.setCPtr(j * CPtr.SIZE, argsi[j]);
}
} else {
float[][] values = new float[nArray.rows()][nArray.cols()];
for (int i = 0; i < nArray.rows(); i++)
for (int j = 0; j < nArray.cols(); j++)
values[i][j] = (float) nArray.doubleAt(i, j);
pointerHolder.add(argPtr);
for (int j = 0; j < nArray.rows(); j++) {
// size of float is 4
argsi[j] = new CMalloc(4 * nArray.cols());
pointerHolder.add(argsi[j]);
argsi[j].copyIn(0, values[j], 0, nArray.cols());
argPtr.setCPtr(j * CPtr.SIZE, argsi[j]);
}
}
return argPtr;
}
/**
* Gets a pointer from a vector JSCNArray.
*
* @param nArray
* @return pointer
*/
private CPtr getNArrayVecPtr(JSCNArray nArray) {
CMalloc argPtr = null;
if (!Boolean.valueOf(nArray.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue()) {
double[] values = UMatrix.vec(nArray.doubleArray());
argPtr = new CMalloc(8 * values.length);
pointerHolder.add(argPtr);
argPtr.copyIn(0, values, 0, values.length);
} else {
float[] values = new float[nArray.rows() * nArray.cols()];
for (int i = 0, index = 0; i < nArray.rows(); i++)
for (int j = 0; j < nArray.cols(); j++)
values[index++] = (float) nArray.doubleAt(i, j);
argPtr = new CMalloc(4 * values.length);
pointerHolder.add(argPtr);
argPtr.copyIn(0, values, 0, values.length);
}
return argPtr;
}
/**
* Gets a pointer from a JSCSArray.
*
* @param sArray
* @return pointer
*/
private CPtr getStringArrayPtr(JSCSArray sArray) {
CMalloc argPtr = new CMalloc(CPtr.SIZE * sArray.rows());
pointerHolder.add(argPtr);
for (int j = 0; j < sArray.rows(); j++) {
CMalloc argPtri = new CMalloc(CPtr.SIZE * sArray.cols());
pointerHolder.add(argPtri);
CMalloc[] argsi = new CMalloc[sArray.cols()];
for (int k = 0; k < sArray.cols(); k++) {
String el = sArray.stringAt(j, k);
if (el == null)
throw new IllegalArgumentException("Argument "
+ sArray.name()
+ " contains null element at index [" + j + "," + k
+ "].");
argsi[k] = new CMalloc(el.length() + 1);
argsi[k].setString(0, el);
argPtri.setCPtr(k * CPtr.SIZE, argsi[k]);
pointerHolder.add(argsi[k]);
}
argPtr.setCPtr(CPtr.SIZE * j, argPtri);
}
return argPtr;
}
/**
* Gets a pointer from a vector JSCSArray.
*
* @param sArray
* @return pointer
*/
private CPtr getStringVecPtr(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;
}
/**
* Gets a data object from the given object array. Only those array elements
* are handled, that are of type CPtr. The type of the
* JSCData object defines the type of indirection used. The
* dimension of the data objects determines the assumed dimension of the
* pointers.
*
* @param args
* the pointer objects from which to extract the data
* @param rtns
* the data objects to store the values to, the type and
* dimension define the pointer lengths and the indirection used
*/
private void getDataFromPtr(Object[] 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 (args[i] instanceof CPtr) {
if (rtns[i] == null)
throw new RuntimeException("Argument at index " + i
+ " was null.");
JSCTypes type = rtns[i].type();
CPtr ptr = (CPtr) args[i];
if (type == JSCTypes.NARRAY) {
JSCNArray nArray = (JSCNArray) rtns[i];
setNArrayFromPtr(nArray, ptr);
} else if (type == JSCTypes.SARRAY) {
JSCSArray sArray = (JSCSArray) rtns[i];
String[][] vals = new String[sArray.rows()][sArray.cols()];
if (Boolean.valueOf(
sArray.getJSCProperty(CSignTypes.VECTOR) + "")
.booleanValue()) {
for (int j = 0, index = 0; j < sArray.cols(); j++)
for (int k = 0; k < sArray.rows(); k++) {
CPtr ptri = ptr.getCPtr(CPtr.SIZE * index++);
vals[k][j] = ptri.getString(0);
}
} else {
for (int j = 0; j < sArray.rows(); j++) {
CPtr ptrRow = ptr.getCPtr(CPtr.SIZE * j);
for (int k = 0; k < sArray.cols(); k++) {
CPtr ptri = ptrRow.getCPtr(CPtr.SIZE * k);
vals[j][k] = ptri.getString(0);
}
}
}
sArray.setVal(vals);
} else if (type == JSCTypes.STRING) {
((JSCString) rtns[i]).setVal(ptr.getString(0));
} else if (type == JSCTypes.INT
&& Boolean.valueOf(
rtns[i].getJSCProperty(CSignTypes.BYREF) + "")
.booleanValue()) {
JSCInt num = (JSCInt) rtns[i];
num.setVal(ptr.getInt(0));
} else if (type == JSCTypes.NUMBER
&& Boolean.valueOf(
rtns[i].getJSCProperty(CSignTypes.BYREF) + "")
.booleanValue()) {
JSCNumber num = (JSCNumber) rtns[i];
if (Boolean.valueOf(
rtns[i].getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue())
num.setVal(ptr.getFloat(0));
else
num.setVal(ptr.getDouble(0));
} else
throw new RuntimeException("Argument type " + type
+ " at index " + i
+ " does not correspond to pointer.");
}
}
}
private void setNArrayFromPtr(JSCNArray nArray, CPtr ptr) {
double[][] vals = new double[nArray.rows()][nArray.cols()];
if (!Boolean.valueOf(nArray.getJSCProperty(CSignTypes.FLOAT) + "")
.booleanValue()) {
if (Boolean.valueOf(nArray.getJSCProperty(CSignTypes.VECTOR) + "")
.booleanValue()) {
double[] values = new double[nArray.rows() * nArray.cols()];
ptr.copyOut(0, values, 0, values.length);
for (int j = 0, index = 0; j < nArray.rows(); j++)
for (int k = 0; k < nArray.cols(); k++)
vals[j][k] = values[index++];
} else
for (int j = 0; j < nArray.rows(); j++)
ptr.getCPtr(j * CPtr.SIZE).copyOut(0, vals[j], 0,
nArray.cols());
} else {
if (Boolean.valueOf(nArray.getJSCProperty(CSignTypes.VECTOR) + "")
.booleanValue()) {
float[] values = new float[nArray.rows() * nArray.cols()];
ptr.copyOut(0, values, 0, values.length);
for (int j = 0, index = 0; j < nArray.rows(); j++)
for (int k = 0; k < nArray.cols(); k++)
vals[j][k] = values[index++];
} else {
float[][] fvals = new float[nArray.rows()][nArray.cols()];
for (int j = 0; j < nArray.rows(); j++)
ptr.getCPtr(j * CPtr.SIZE).copyOut(0, fvals[j], 0,
nArray.cols());
for (int j = 0; j < nArray.rows(); j++)
for (int k = 0; k < nArray.cols(); k++)
vals[j][k] = fvals[j][k];
}
}
nArray.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.SARRAY
|| type == JSCTypes.STRING)
return true;
return false;
}
/**
* Sets the name of the native library to be used. The library is then used
* in the next call to a function.
*
* Usage Note:
* if (loadType == StubLoadTypes.USERLIB) then the library is
* searched in the jstub subdirectory. No system specific
* extension (.dll/.so) is required but will be appended automatically
* according to the running OS.
*
* if (loadType == StubLoadTypes.SYSLIB) then the library is
* searched in the library search path. The library name must be fully
* qualified with extension, because not all system libraries have a default
* suffix (e.g. libc.so.6).
*
* @param module
* the name of the system library to load with/without the system
* specific extension (SYSLIB/USERLIB)
* @param loadType
* the load type
* @param args
* always ignored
* @throws IllegalArgumentException
* if (loadName == null || loadType == null) or
* if (module.length() == 0) or if no user
* library with that name exists in the jstub
* subdir
* @throws RuntimeException
* if operating system is not supported or if the system library
* with the given name cannot be found
* @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.");
String osName = System.getProperty("os.name").toUpperCase();
String sysExt = null;
if (osName.indexOf("WIN") > -1)
sysExt = ".dll";
else if (osName.indexOf("SUNOS") > -1 || osName.indexOf("SOLARIS") > -1
|| osName.indexOf("LINUX") > -1)
sysExt = ".so";
else
throw new RuntimeException("Operating system "
+ System.getProperty("os.name")
+ " is not supported by StubEngine.");
if (loadType == StubLoadTypes.USERLIB) {
File f = new File(EngineTypes.STUB.DIR_NAME + File.separator
+ module + sysExt);
if (!f.isFile())
throw new RuntimeException("The system library "
+ f.getAbsolutePath() + " does not exist.");
systemLib = f.getAbsolutePath();
}
if (loadType == StubLoadTypes.SYSLIB)
systemLib = module;
}
/**
* 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.");
}
/**
* Does nothing.
*
* @see com.jstatcom.engine.Engine#shutdown()
*/
public void shutdown() {
}
}
Ads help maintain this website.