/*
 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.gauss;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import javolution37.javolution.xml.ObjectReader;

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.JSCConstants;
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.FArg;
import com.jstatcom.util.UString;

/**
 * Links to an instance of the software package Gauss (www.aptech.com). This
 * object performs the whole communication with Gauss and has control over it,
 * i.e. it starts, commands, and ends Gauss.
 * 

* This class enforces the Singleton property by a static initializer. Currently * it runs only on the Windows OS. * * @author Markus Kraetzig */ public final class GaussEngine implements Engine { private static final Logger log = Logger.getLogger(GaussEngine.class); private static String sysSuffix = ".dll"; static { // check whether not windows, assume so if (System.getProperty("os.name", "Windows").toUpperCase().indexOf( "WIN") < 0) sysSuffix = ".so"; } private volatile boolean stopped = false; private volatile boolean isRunning = false; private static GaussEngine gauss = null; private static ConfigHolder cf = null; private List libList = new ArrayList(); private StringBuffer libBuffer = new StringBuffer(); private List dlibList = null; private StringBuffer dlibBuffer = null; private String gaussLibName = null; private static String tmpDir = null; /** * GaussEngine constructor comment. */ private GaussEngine() { super(); } /** * Compiles the source files defined in the XML file for compilation. * * @return null if ok, error string otherwise */ public String compileGCG() { String failed = "GAUSS gcg compilation failed:\n"; String error = null; // Create tmp file. File tmpFile = null; try { tmpFile = GaussEngineType.INSTANCE.getTempFile(); } catch (RuntimeException ex) { error = failed + ex.getMessage(); return error; } GCGSet gcgSet = null; ObjectReader constructor = new ObjectReader(); try { gcgSet = (GCGSet) constructor.read(new FileInputStream(new File( EngineTypes.GAUSS.DIR_NAME + "/" + cf.getConfig(GaussConfigKeys.COMPILE_CFG)))); } catch (Throwable ex) { error = failed + ex.getMessage(); return error; } // Write to tmp file. BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(tmpFile)); for (Iterator it = gcgSet.iterator(); it.hasNext();) { String fName = it.next() + ""; File srcFile = new File(EngineTypes.GAUSS.DIR_NAME + "/" + fName); out .write("#include \"" + srcFile.getAbsolutePath() + "\";\r\n"); } out.close(); } catch (IOException e) { error = failed + e.getMessage(); return error; } if (!isRunning) start(true); // Run compilation. File gcgFile = new File(EngineTypes.GAUSS.DIR_NAME + "/" + gcgSet.GCG_NAME); gcgFile.delete(); StringBuffer execBuffer = new StringBuffer(); String libraries = gcgSet.LIBS; if (libraries != null) { libraries = libraries.trim(); if (libraries.length() > 0) execBuffer.append("library " + libraries + ";\n"); } execBuffer.append("compile \"" + tmpFile.getAbsolutePath() + "\" \"" + gcgFile.getAbsolutePath() + "\";\n"); String exec = UString.replaceAllSubStrings(execBuffer.toString(), "\\", "\\\\"); log.info("\nGAUSS gcg compilation:\n" + exec); if ((error = executeCommandEx(exec)) != null) return failed + error; return null; } /** * Creates the GAUSS libraries defined in the respective XML file * * @return null if ok, error string otherwise */ public String createLibs() { String failed = "GAUSS library generation failed:\n"; String error = null; LIBSet libSet = null; ObjectReader constructor = new ObjectReader(); try { libSet = (LIBSet) constructor.read(new FileInputStream(new File( EngineTypes.GAUSS.DIR_NAME + "/" + cf.getConfig(GaussConfigKeys.LIB_CFG)))); } catch (Throwable ex) { error = failed + ex.getMessage(); return error; } // Run library creation. String libraries = libSet.LIBS; if (libraries != null) { libraries = libraries.trim(); if (libraries.length() > 0) load(libraries, GaussLoadTypes.LIB); } log.info("\nGAUSS library generation:\n"); for (Iterator it = libSet.iterator(); it.hasNext();) { LIBSet.SRCLibPair pair = (LIBSet.SRCLibPair) it.next(); File srcFile = new File(EngineTypes.GAUSS.DIR_NAME + "/" + pair.srcFile); String exec = "lib " + pair.libraryName + " \"" + UString.replaceAllSubStrings(srcFile.getAbsolutePath(), "\\", "\\\\") + "\""; log.info(exec); if ((error = executeCommandEx(exec)) != null) { return failed + error; } log.info(" ... ok"); } return null; } /** * Executes a GAUSS command. If this engine is in debug mode, the command is * also printed to System.out. * * @param command * string with GAUSS code, for example "y=rndn(100,1)" * @throws IllegalArgumentException * if (command == null) * @throws RuntimeException * if execution of command failed, use * getMessage to retrieve the error string */ public void execute(String command) { if (command == null) throw new IllegalArgumentException("Command was null."); if (!isRunning) start(true); command = command.trim(); if (command.length() == 0) return; // Ending ";" is taken care of by jgauss library. if (command.charAt(command.length() - 1) == ';') command = command.substring(0, command.length() - 1); log.debug("command:\n" + command + ";\n"); String error = null; if ((error = executeCommandEx(command)) != null) throw new RuntimeException(error); checkStopped(); } /** * Executes a procedure with input and return parameters specified. The * results are set to the data objects specified in retData. * Listeners registered to these data objects receive notifications if * something changed. *

* Please note:
* If a data object of type JSCTypes.INT is specified as * return, then any double scalar will silently be cast to an int. It may * happen that information is lost during that operation. * * @param procName * procedure name to be executed by this engine * @param args * the data to be used as input, can be null or * emtpy if no arguments needed * @param retData * the data array to store results can be null or * emtpy if no returns needed * @throws IllegalArgumentException * if args or retData contain * elements that are null * @throws RuntimeException * if return types are not compatible with returns */ public void call(String procName, JSCData[] args, JSCData[] retData) { if (args == null) args = new JSCData[0]; if (retData == null) retData = new JSCData[0]; String names[] = new String[retData.length]; JSCTypes retTypes[] = new JSCTypes[retData.length]; StringBuffer exec = new StringBuffer(); for (int i = 0; i < retData.length; i++) { if (i == 0) exec.append("{"); if (retData[i] == null) throw new IllegalArgumentException("Return at array position [" + i + "] is null."); names[i] = retData[i].name(); retTypes[i] = retData[i].type(); if (i > 0) exec.append(", "); exec.append(names[i]); if (i == retData.length - 1) exec.append("} = "); } exec.append(procName); for (int i = 0; i < args.length; i++) { if (i == 0) exec.append("("); if (args[i] == null) throw new IllegalArgumentException( "Argument at array position [" + i + "] is null."); if (i > 0) exec.append(", "); exec.append(args[i].name()); if (i == args.length - 1) exec.append(")"); } write(args); checkStopped(); execute(exec.toString()); checkStopped(); read(retData); } /** * Low level command execution for a GAUSS command. * * @param command * string with GAUSS code, for example "y=rndn(100,1)" * @return null if execution completed ok, otherwise error * message */ private native String executeCommandEx(String command); /** * Returns an instance of the GaussEngine that is a * Singleton. Whenever this method is called, the respective system library * is loaded. * * @return initialized GAUSS instance, not yet started * @throws RuntimeException * if operating system is not supported or if something went * wrong during the init process * @throws UnsatisfiedLinkError * if system libraries cannot be found */ public static GaussEngine getInstance() { if (gauss != null) return gauss; gauss = new GaussEngine(); cf = ConfigHolder.valueOf(EngineTypes.GAUSS); // Collect the necessary information from ConfigHolder and OS // for loading the correct library. String osName = System.getProperty("os.name"); if (osName == null) throw new RuntimeException("OS name is null."); if (osName.indexOf("Win") > -1) { String dllName = null; if (cf.getConfig(GaussConfigKeys.GAUSS_VERSION).equals("3.2")) { gauss.gaussLibName = "glm1.dll"; dllName = "jgauss1.dll"; } else { gauss.gaussLibName = "glm2.dll"; dllName = "jgauss2.dll"; } String loadName = EngineTypes.GAUSS.DIR_NAME + "/" + gauss.gaussLibName; File sysFile = new File(loadName); String sysPath = sysFile.getAbsolutePath(); if (!sysFile.isFile()) throw new RuntimeException("System library " + sysPath + " does not exist."); gauss.dlibList = new ArrayList(); gauss.dlibList.add(sysPath); gauss.dlibBuffer = new StringBuffer(sysPath); System.load(new File(EngineTypes.GAUSS.DIR_NAME + "/" + dllName) .getAbsolutePath()); } else throw new RuntimeException("OS " + osName + " is not supported."); // Temp dir. Use system temp if empty or does not exist. tmpDir = cf.getConfig(GaussConfigKeys.TEMP_DIR); if (!new File(tmpDir).isDirectory()) tmpDir = JSCConstants.getSystemTemp(); // Shared mem. int memSize = Integer.parseInt(cf .getConfig(GaussConfigKeys.SHARED_MEM_SIZE)); // Init environment. String error = gauss.initializeGaussEx(cf .getConfig(GaussConfigKeys.EXE_LOCATION), tmpDir, memSize, cf .getConfig(GaussConfigKeys.GAUSS_VERSION)); if (error != null) throw new RuntimeException(error); return gauss; } /** * Gets the instance name of the associateted GAUSS program. * * @return the name of this instance */ // private native String getInstanceNameEx(); /** * Gets whether GAUSS is currently busy. * * @return true if GAUSS is busy, false * otherwise */ private native boolean isExecutingEx(); /** * Initializes Gauss environment. Needs to be called before Gauss can be * started. * * @param gaussExeDir * the directory of the gauss executable * @param tempDir * the temporary directory * @param sharedMemSize * the size of the shared memory area * @return null if ok, otherwise error string */ private native String initializeGaussEx(String gaussExeDir, String tempDir, int sharedMemSize, String versString); /** * @see com.jstatcom.engine.Engine */ 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; } /** * Loads a module according to the load type. All filenames must be * specified relative to the resource directory for the GAUSS engine. The * args argument is ignored. * * @throws IllegalArgumentException * if (loadName == null || loadType == null) or * if (loadName.length() == 0) or if * loadName is not valid * @see com.jstatcom.engine.Engine */ public void load(String loadName, LoadTypes loadType, JSCData... args) { if (loadName == null || loadType == null) throw new IllegalArgumentException("Argument was null."); if (loadName.length() == 0) throw new IllegalArgumentException("Module name was empty."); if (!isRunning) start(true); // Loads GCG file if not in debug mode. if (loadType == GaussLoadTypes.GCG) loadGCG(loadName); // Loads libraries if in debug mode. if (loadType == GaussLoadTypes.LIB) loadLIB(loadName); // Loads system libraries. if (loadType == GaussLoadTypes.SYS) loadSYS(loadName); // Sets a global symbol. if (loadType == GaussLoadTypes.GLOBAL) { if (args == null) throw new IllegalArgumentException( "Global symbol agument missing."); if (args.length != 1) throw new IllegalArgumentException( "Global symbol agument length was " + args.length + " instead of 1."); JSCConstants.checkNameThrowEx(loadName); write(args); execute(loadName + " = " + args[0].name()); } checkStopped(); } /** * Loads GCG. * * @param toLoad * String */ private void loadGCG(String toLoad) { if (JSCConstants.DEBUG) return; String loadName = EngineTypes.GAUSS.DIR_NAME + "/" + toLoad.trim(); String error = null; File gcgFile = new File(loadName); if (!gcgFile.isFile()) if ((error = compileGCG()) != null) // Compilation. throw new RuntimeException(error); gcgFile = new File(loadName); if (!gcgFile.isFile()) throw new RuntimeException("File " + gcgFile.getAbsolutePath() + " could not be created."); String exec = UString.replaceAllSubStrings(gcgFile.getAbsolutePath(), "\\", "\\\\"); log.debug("\nload gcg file:\nuse " + exec + ";\n"); if ((error = executeCommandEx("use " + exec)) != null) // Use gcg. if ((error = compileGCG()) != null) // Compile gcg. throw new RuntimeException(error); // Try to use gcg again. if ((error = executeCommandEx("use " + exec)) != null) throw new RuntimeException(error); } /** * Loads LIB. * * @param toLoad * String */ private void loadLIB(String toLoad) { if (!JSCConstants.DEBUG) return; String loadName = toLoad.toLowerCase().trim(); List oldList = new ArrayList(libList); StringBuffer oldBuffer = new StringBuffer(libBuffer.toString()); StringTokenizer st = new StringTokenizer(loadName, ","); while (st.hasMoreTokens()) { String toPut = st.nextToken().trim(); if (!libList.contains(toPut) && toPut.length() > 0) { libList.add(toPut); if (libBuffer.length() > 0) libBuffer.append(", " + toPut); else libBuffer.append(toPut); } } String error = null; log.debug("\nload libraries: " + libBuffer.toString()); if ((error = executeCommandEx("library " + libBuffer.toString())) != null) { libBuffer = oldBuffer; libList = oldList; throw new RuntimeException(error); } } /** * Loads SYS. * * @param toLoad * the name of the library file relative to the resource dir * @throws RuntimeException * if GAUSS is busy or if the system library does not exist or * if GAUSS gives a load errorD */ private void loadSYS(String toLoad) { if (isExecutingEx()) throw new RuntimeException("Gauss is busy."); List oldList = new ArrayList(dlibList); StringBuffer oldBuffer = new StringBuffer(dlibBuffer.toString()); String error = null; StringTokenizer st = new StringTokenizer(toLoad, ","); while (st.hasMoreTokens()) { String toPut = st.nextToken().trim(); if (!toPut.toLowerCase().endsWith(sysSuffix)) toPut = toPut + sysSuffix; String loadName = EngineTypes.GAUSS.DIR_NAME + "/" + toPut; File sysFile = new File(loadName); String sysPath = sysFile.getAbsolutePath(); if (!sysFile.isFile()) throw new RuntimeException("System library " + sysPath + " does not exist."); if (!dlibList.contains(sysPath)) { dlibList.add(sysPath); if (dlibBuffer.length() > 0) dlibBuffer.append(", " + sysPath); else dlibBuffer.append(sysPath); } } // Gauss needs to be restarted before new dlibs can be loaded. // This is to make sure that every dll is unloaded. // isRunning = false; /* * if ((error = terminateGaussEx()) != null) throw new * RuntimeException(error); */ // start(); String exec = "dlibrary " + dlibBuffer.toString(); exec = UString.replaceAllSubStrings(exec, "\\", "\\\\"); log.debug("\nload system library:\n" + exec + ";\n"); if ((error = executeCommandEx(exec)) != null) { dlibBuffer = oldBuffer; dlibList = oldList; throw new RuntimeException(error); } } /** * Reads all variables defined in varNames from GAUSS. The * varTypes are necessary to enforce type savety. * * @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 */ public void read(JSCData[] rtArray) { if (rtArray == null) throw new IllegalArgumentException("Argument was null."); if (!isRunning) start(true); for (int i = 0; i < rtArray.length; i++) { if (rtArray[i] == null) throw new IllegalArgumentException("Return at array position [" + i + "] was null"); JSCTypes type = rtArray[i].type(); String name = rtArray[i].name(); log.debug("read: name=" + name + ", type=" + type); if (!isValid(type)) throw new IllegalArgumentException("Return type " + type + " at array position [" + i + "] cannot be handled by GAUSS"); Object readObj = null; try { readObj = readDataEx(name); } catch (Throwable ex) { throw new RuntimeException("Reading variable " + name + " failed with error message:\n\n" + ex.getMessage()); } checkStopped(); if (readObj == null) rtArray[i].clear(); else if (type == JSCTypes.NARRAY) { if (!(readObj instanceof double[][])) throw new RuntimeException("Values for variable " + name + " do not match with type " + type + "."); ((JSCNArray) rtArray[i]).setVal((double[][]) readObj); } else if (type == JSCTypes.SARRAY) { if (!(readObj instanceof String[][]) && !(readObj instanceof String)) throw new RuntimeException("Values for variable " + name + " do not match with type " + type + "."); if (readObj instanceof String[][]) ((JSCSArray) rtArray[i]).setVal((String[][]) readObj); else ((JSCSArray) rtArray[i]) .setVal(new String[][] { { (String) readObj } }); } else if (type == JSCTypes.NUMBER || type == JSCTypes.INT) { if (!(readObj instanceof double[][])) throw new RuntimeException("Values for variable " + name + " do not match with type " + type + "."); double[][] ret = (double[][]) readObj; if (ret.length < 1 || ret[0].length < 1 || ret.length > 1 || ret[0].length > 1) throw new RuntimeException("Variable " + name + " is not a 1x1 scalar for type " + type + "."); if (type == JSCTypes.NUMBER) ((JSCNumber) rtArray[i]).setVal(ret[0][0]); else ((JSCInt) rtArray[i]).setVal(new Double(ret[0][0]) .intValue()); } else if (type == JSCTypes.STRING) { if (!(readObj instanceof String)) throw new RuntimeException("Values for variable " + name + " do not match with type " + type + "."); ((JSCString) rtArray[i]).setVal((String) readObj); } } return; } /** * Reads symbols from Gauss if the argument is a variable in Gauss. * * @param arg * name of the variable in the Gauss program * @return the data array from GAUSS */ private native Object readDataEx(String arg); /** * Starts an instance of GAUSS. If in DEBUG mode, then it uses the library * system, otherwise the specified .gcg file is loaded. If the load of the * .gcg file fails then a compilation of the source files is carried out. *

* Nothing happens if GAUSS is already running. * * @param compileIfNecessary * compiles gcg file if it cannot be loaded * @throws RuntimeException * if start fails */ public void start(boolean compileIfNecessary) { // GAUSS is already running. if (isRunning) return; String error = null; if ((error = startGaussEx()) != null) throw new RuntimeException(error); isRunning = true; try { log.info(FArg.sprintf("%-20s%s", new FArg("GAUSS version:").add(cf .getConfig(GaussConfigKeys.GAUSS_VERSION)))); log.info(FArg.sprintf("%-20s%s", new FArg("GAUSS tempdir:").add(cf .getConfig(GaussConfigKeys.TEMP_DIR)))); log.info(FArg.sprintf("%-20s%s", new FArg("GAUSS executable:") .add(cf.getConfig(GaussConfigKeys.EXE_LOCATION)))); checkStopped(); if (!JSCConstants.DEBUG && compileIfNecessary) load(cf.getConfig(GaussConfigKeys.GCG_FILENAME), GaussLoadTypes.GCG); else load("jgauss, pgraph", GaussLoadTypes.LIB); Thread outPut = new Thread() { public void run() { startGaussOutEx(); return; } }; outPut.start(); } catch (RuntimeException ex) { // Clean up. terminateGaussEx(); isRunning = false; throw ex; } } /** * Start new instance of GAUSS program and attach to this object. * * @return null if ok, otherwise error string */ private native String startGaussEx(); /** * Only for Gauss version >= 3.5. Starts the GaussOut thread that reads all * output from Gauss and writes it to System.out. */ private native void startGaussOutEx(); /** * @see com.jstatcom.engine.Engine */ public void stop() { String error = null; stopped = true; if ((error = stopExecutionEx()) != null) throw new RuntimeException(error); } /** * Low level stop method. It stops a running GAUSS program. * * @return null if ok, otherwise error string */ private native String stopExecutionEx(); /** * Only for Gauss version >= 3.5. Stops the GaussOut thread that reads all * output from Gauss and writes it to the applications * System.out. */ private native void stopGaussOutEx(); /** * @see com.jstatcom.engine.Engine */ public void shutdown() { String error = null; if ((error = terminateGaussEx()) != null) throw new RuntimeException(error); stopGaussOutEx(); } /** * Low level termination method. It finishes GAUSS without checking for * running program execution and waiting calls in the worker queue. * * @return null if ok, otherwise error string */ private native String terminateGaussEx(); /** * Write a data array to GAUSS. If GAUSS is not yet running, the * start method is called. * * @param engineData * the data array to be written to GAUSS * @throws IllegalArgumentException * if one of the data types cannot be handled by the GAUSS * engine or if (engineData == null) or if one * element of engineData is null * @throws RuntimeException * if write failed of if GAUSS could not be started if it was * not running before * * @see com.jstatcom.engine.Engine */ public void write(JSCData[] engineData) { if (engineData == null) throw new IllegalArgumentException("Argument was null."); if (!isRunning) start(true); String error = null; for (int i = 0; i < engineData.length; i++) { checkStopped(); JSCData data = engineData[i]; if (data == null) throw new IllegalArgumentException( "Argument at array position [" + i + "] was null."); log.debug("write: " + data); JSCTypes type = data.type(); if (!isValid(type)) throw new IllegalArgumentException("Argument type " + type + " at array position [" + i + "] cannot be handled by GAUSS."); // Empty if (data.isEmpty()) { if ((error = executeCommandEx(data.name() + "=0;\n" + data.name() + "={}")) != null) throw new RuntimeException(error); } // Number array else if (type == JSCTypes.NARRAY) { double[][] dataVal = ((JSCNArray) data).doubleArray(); if ((error = writeDoubleArrayEx(data.name(), dataVal)) != null) throw new RuntimeException(error); } // Number else if (type == JSCTypes.NUMBER || type == JSCTypes.INT) { double number; if (type == JSCTypes.NUMBER) number = ((Double) data.value()).doubleValue(); else number = ((Integer) data.value()).doubleValue(); if ((error = writeDoubleArrayEx(data.name(), new double[][] { { number } })) != null) throw new RuntimeException(error); } // String array else if (type == JSCTypes.SARRAY) { if ((error = writeStringArrayEx(data.name(), (String[][]) data .value())) != null) throw new RuntimeException(error); } // String else if (type == JSCTypes.STRING) { String val = "\"" + (String) data.value() + "\""; if ((error = executeCommandEx(data.name() + " = " + val)) != null) throw new RuntimeException(error); } } } /** * Checks whether stop was called and throws exception to notify caller. The * flag is reset. */ private void checkStopped() { if (stopped) { stopped = false; throw new RuntimeException(Thread.currentThread().getName() + " was interrupted."); } } /** * Low level write method that writes a number array to Gauss. * * @param name * the name of the variable to write * @param arg * number array that should be written to Gauss * @return null if ok, otherwise error message */ private native String writeDoubleArrayEx(String name, double[][] arg); /** * Low level write method that writes a string array to Gauss. * * @param name * the name of the variable to write * @param arg * string array that should be written to Gauss * @return null if ok, otherwise error message */ private native String writeStringArrayEx(String name, String[][] arg); }

Ads help maintain this website.