All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gennbo.NBOService Maven / Gradle / Ivy

There is a newer version: 14.31.10
Show newest version
/* $RCSfile$
 * $Author: hansonr $
 * $Date: 2014-12-13 22:43:17 -0600 (Sat, 13 Dec 2014) $
 * $Revision: 20162 $
 *
 * Copyright (C) 2002-2005  The Jmol Development Team
 *
 * Contact: [email protected]
 *
 *  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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.gennbo;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Queue;

import javajs.util.AU;
import javajs.util.PT;

import org.jmol.util.Logger;
import org.jmol.viewer.Viewer;

/**
 * A service for interacting with NBOServe (experimental)
 * 
 * TODO: figure out how to manage time-consuming asynchronous requests
 * 
 * 
 */
public class NBOService {

  /// BH: observations: 
  // 
  // 1) NBOServe creates jmol_infile.txt, jmol_molfile.txt, and jmol_outfile.txt. 
  // 
  // 2) Of these three, jmol_outfile is locked for writing during NBOServe sessions 
  //    and stays locked until the program finishes. 
  //    
  //    
  // 3) When View starts up, a new jmol_outfile is created, and it can be deleted.
  // 

  // modes of operation

  static final int MODE_ERROR          = -1;
  static final int MODE_RAW            = 0;// leave this 0; it is referred to that in StatusListener

  // these are for postToNBO; n * 10 + module value(s)
  

  protected Viewer vwr;
  protected Process nboServer;
  protected Thread nboListener;
  protected NBODialog dialog;
  protected NBORequest currentRequest;
  protected Object lock;
  protected Queue requestQueue;  
  
  private PrintWriter nboIn;
  protected BufferedInputStream nboOut;

  private boolean cantStartServer;  
  private String serverPath;

  private String exeName = "NBOServe.exe";
  private boolean doConnect;
  
  private boolean isReady;

  protected void setReady(boolean tf) {
    //System.out.println("isready = " + tf);
    isReady = tf;
  }
  /**
   * A class to manage communication between Jmol and NBOServe.
   * 
   * @param nboDialog 
   * 
   * @param vwr
   *        The interacting display we are reproducing (source of view angle
   *        info etc)
   * @param doConnect 
   */
  public NBOService(NBODialog nboDialog, Viewer vwr, boolean doConnect) {
    this.dialog = nboDialog;
    this.vwr = vwr;
    this.doConnect = doConnect;
    setServerPath(nboDialog.nboPlugin.getNBOProperty("serverPath", System.getProperty("user.home")
        + "/NBOServe"));
    requestQueue = new ArrayDeque();
    lock = new Object();
  }

  ////////////////////// Initialization //////////////////////////
  

  
  /**
   * Check to see if we have tried and are not able to make contact with NBOServe at the designated location.
   * 
   * @return true if we hvae found that we cannot start the server.
   * 
   */
  boolean isOffLine() {
    return cantStartServer;
  }

  /**
   * Return path to NBOServe directory.
   * 
   * @param fileName
   *        or null for path itself, without slash
   * 
   * @return path
   */
  String getServerPath(String fileName) {
    return (fileName == null ? serverPath : serverPath + "/" + fileName);
  }

  /**
   * Set path to NBOServe directory
   * 
   * @param path
   */
  protected void setServerPath(String path) {
    serverPath = path.replace('\\',  '/');
    dialog.nboPlugin.setNBOProperty("serverPath", path);
  }

  /**
   * Check to see that the service has been initialized.
   * 
   * @return true if the path to the server has been set.
   */
  protected boolean isEnabled() {
    return serverPath != null;
  }

  /**
   * Check to see if there is a current request running
   * 
   * @return true if there is a current request
   * 
   */
  public boolean isWorking() {
    return (currentRequest != null);
  }  
  
  ////////////////////// NBOServe Process //////////////////////////
  
  byte[] buffer = new byte[1024];
  String cachedReply = "";

  private NBORunnable nboRunnable;
  private boolean haveLicense;

  /**
   * Start the ProcessBuilder for NBOServe and listen to its stdout (Fortran LFN
   * 6, a Java BufferedInputStream). We simply look for available bytes and listen
   * for a 10-ms gap, which should be sufficient, since all these are done via a 
   * single flush;
   * 
   * 
   * Expected packets are of one of the following three forms:
   * 
   * *start*
   * 
   * [one or more lines]
   * 
   * *end*
   * 
   * or
   * 
   * ***errmess***
   * 
   * [one error message line]
   * 
   * or
   * 
   * [some sort of identifiable Fortran or system error message most likely
   * indicating NBO has died]
   * 
   * 
   * @return a caught exception message, or null if we are not connected or we
   *         are successful
   */
  String startProcess() {
    try {
      cantStartServer = true;
      if (!doConnect)
        return null;
      nboListener = null;
      String path = getServerPath(exeName);
      
      System.out.println("startProcess: " + path);

      ProcessBuilder builder = new ProcessBuilder(path);
      
      builder.directory(new File(new File(path).getParent())); // root folder for executable 
      builder.redirectErrorStream(true);
      nboServer = builder.start();
      
      // pick up the NBO stdout
      
      nboOut = (BufferedInputStream) nboServer.getInputStream();
      System.out.println("startProcess:" + nboServer);

      // start a listener
      
      nboListener = new Thread(nboRunnable = new NBORunnable());
      nboListener.setName("NBOServiceThread" + System.currentTimeMillis());
      nboListener.start();
      
      // open a channel to NBOServe's stdin.
      
      nboIn = new PrintWriter(nboServer.getOutputStream());
      
    } catch (IOException e) {
      String s = e.getMessage();
      System.out.println(s);
      if (s.contains("error=1455"))
         s = "Jmol can't do that - low on memory";
      dialog.logError(s);
      return s;
    }
    cantStartServer = false;
    return null;
  }

  /**
   * Close the process and all channels associated with it. 
   * @param andPause 
   */
  public void closeProcess(boolean andPause) {
    
    if (nboRunnable != null) {
      nboRunnable.destroyed = true;
      andPause = true;
    }
    if (andPause) {
      try {
        Thread.sleep(50);
      } catch (InterruptedException e1) {
      }
      

    }
    setReady(false);
    nboOut = null;
    
    try {
      nboIn.close();
    } catch (Exception e) {
    }
    nboIn = null;
    
    try {
      nboListener.interrupt();
    } catch (Exception e) {
      System.out.println("can't interrupt");
    }
    nboListener = null;
    
    try {
      nboServer.destroy();
    } catch (Exception e) {
      // we don't care
    }
    nboServer = null;
    currentRequest = null;
  }

  /**
   * Restart the process from scratch.
   * 
   * @return null or an error message
   * 
   */
  String restart() {
    closeProcess(true);
    return startProcess();
  }

  /**
   * Restart the processor only if necessary
   * 
   * @return true if successful
   */
  public boolean restartIfNecessary() {
    if (nboServer == null)
      startProcess();
    return (nboServer != null);
  }

  /**
   * Take a quick look to see that gennbo.bat is present and notify the user if it is not.
   * 
   * Note that this is the only method that is marginally Windows-dependent.
   * 
   * @return true if gennbo.bat exists.
   * 
   */
  public boolean haveGenNBO() {
    if (!doConnect)
      return true;
    File f = new File(getServerPath("gennbo.bat"));
    if (!f.exists()) {
      vwr.alert(f + " not found, make sure gennbo.bat is in same directory as "
          + exeName);
      return false;
    }
    return true;
  }

  ////////////////////// Request Queue //////////////////////////
  
  /**
   * The interface for ALL communication with NBOServe from NBODialog.
   * 
   * @param request
   * 
   */
  protected void postToNBO(NBORequest request) {
    synchronized (lock) {
      restartIfNecessary();
      if (isReady && requestQueue.isEmpty() && currentRequest == null) {
        currentRequest = request;
        requestQueue.add(currentRequest);
        startRequest(currentRequest);
      } else {
        requestQueue.add(request);
      }
    }
  }
  
  /**
   * Clear the request queue
   * 
   */
  public void clearQueue() {
    requestQueue.clear();
  }


  //////////////////////////// Send Request to NBOServe ///////////////////////////

  /**
   * Start the current request by writing its metacommands to disk and sending a
   * command to NBOServe directing it to that file via its stdin.
   * 
   * We allow the Jmol command
   * 
   * set TESTFLAG2 TRUE
   * 
   * to give us an alert just prior to sending the command to NBOServe. This
   * allows us to edit these files in PSPad (which will scan for changed files
   * and load them again if requested) and thus send any modification we want to
   * NBOServe for testing.
   * 
   * @param request
   */
  protected void startRequest(NBORequest request) {
    if (request == null || nboRunnable.destroyed)
      return;

    System.out.println("starting request for " + request.statusInfo + " " + request);
    if (request.timeStamp != 0) {
      System.out.println("SENDING TWICE?");
      nboRunnable.destroyed = true;
      return;
    }
    request.timeStamp = System.currentTimeMillis();

    currentRequest = request;

    String cmdFileName = null, data = null;
    for (int i = 2, n = request.fileData.length; i < n + 2; i += 2) {

      cmdFileName = request.fileData[i % n];
      data = request.fileData[(i + 1) % n];
      if (cmdFileName != null) {
        dialog.inputFileHandler.writeToFile(getServerPath(cmdFileName), data);
        System.out.println("saved file " + cmdFileName + "\n" + data + "\n");
      }
    }
    dialog.setStatus(request.statusInfo);
    String cmd = "<" + cmdFileName + ">";

    System.out.println("sending " + cmd);

    if (nboIn == null)
      restart();
    nboIn.println(cmd);
    nboIn.flush();
  }

  //////////////////////////// Process NBO's Reply ///////////////////////////

  /**
   * Process the return from NBOServe.
   * 
   * @param s
   * @return true if we are done
   */
  protected boolean processServerReturn(String s) {

    // Check for the worst

    if (s.indexOf("FORTRAN STOP") >= 0) {
      System.out.println(s);
      dialog.alertError("NBOServe has stopped working - restarting");
      currentRequest = null;
      clearQueue();
      restart();
      return true;
    }

    if (isFortranError(s) || s.indexOf("missing or invalid") >= 0) {
      if (!s.contains("end of file")) {
        dialog.alertError(s);
      }
      currentRequest = null;
      clearQueue();
      restart();
      return true;
    }

    int pt;

    // We don't always remove the request, because in the case of RUN, 
    // we are prone to get additional sysout message from other processes,
    // particularly gennbo.bat. These will come BEFORE the start message.

    boolean removeRequest = true;

    try { // with finally clause to remove request from queue

      if ((pt = s.indexOf("***errmess***")) >= 0) {
        System.out.println(s);
        try {
          s = PT.split(s, "\n")[2];
          logServerLine(s.substring(s.indexOf("\n") + 1), Logger.LEVEL_ERROR);
        } catch (Exception e) {
          // ignore
        }
        logServerLine("NBOPro can't do that.", Logger.LEVEL_WARN);
        return true;
      }

      if ((pt = s.lastIndexOf("*start*")) < 0) {

        // Note that RUN can dump all kinds of things to SYSOUT prior to completion.

        if (currentRequest == null)
          return (removeRequest = true);
        logServerLine(s, (currentRequest.isMessy ? Logger.LEVEL_DEBUG
            : Logger.LEVEL_ERROR));
        return (removeRequest = !currentRequest.isMessy);
      }
      s = s.substring(pt + 8); // includes \n
      pt = s.indexOf("*end*");
      if (pt < 0) {
        System.out.println("...listening...");
        //System.out.println("bad start/end packet from NBOServe: !!!!!!!>>" + s + "<= 0
        || line.indexOf("PGFIO-F") >= 0 
        || line.indexOf("Invalid command") >= 0;
  }

  class NBORunnable implements Runnable {

    protected boolean destroyed;

    @Override
    public void run() {

      String s;

      while (!destroyed && !Thread.currentThread().isInterrupted()) {
        try {
          Thread.sleep(10);

          // Get and process the return, continuing if incomplete or no activity.

          if (destroyed || (s = getNBOMessage()) == null || !processServerReturn(s))
            continue;

          // Success!

          cachedReply = "";

          // Get the next request. Note that we leave the currentRequest on 
          // the Queue because that indicates we are still working. 
          // Note that we FIRST check for messages, as NBOServe will have an
          // unsolicited startup message for us providing license information.
          if (destroyed)
            continue;
          currentRequest = requestQueue.peek();
          startRequest(currentRequest);

        } catch (Throwable e1) {
          clearQueue();
          e1.printStackTrace();
          dialog.setStatus(e1.getMessage());
          continue;
          // includes thread death
        }

      }
      if (destroyed)
        closeProcess(false);
    }
    
    /**
     * Retrieve a message from NBOServe by monitoring its sysout (a
     * BufferedInputStream for us).
     * 
     * @return new NBO output, or null if no activity
     * 
     * @throws IOException
     * @throws InterruptedException
     */
    protected synchronized String getNBOMessage() throws IOException, InterruptedException {
      
      // 1. Check for available bytes.
      
      int n = (nboOut == null ? 0 : nboOut.available());
      if (n <= 0) {
        return null;
      }
      setReady(true);
      while (n > buffer.length) {
        buffer = AU.doubleLengthByte(buffer);
      }
      
      // 3. Read the bytes into a single string and deliver that. 
      //    No need for a line buffer here.
      
      // (The problem we were having originally is that we were using 
      //  a BufferedReader, and it was blocking.)

      // 4. Deliver standard Java format without carriage return.
      
      n = nboOut.read(buffer, 0, n);    
      String s = PT.rep(new String(buffer, 0, n), "\r", "");
      System.out.println(">> " + s + "<<");
      return cachedReply = cachedReply + s;
    }


  }
}







© 2015 - 2024 Weber Informatics LLC | Privacy Policy