/*
* Copyright 2011 University of California, San Diego.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edu.ucsd.hep.rootrunnerutil;
import edu.ucsd.hep.rootrunnerutil.misc.InputStreamDumper;
import edu.ucsd.hep.rootrunnerutil.misc.InputStreamFanOut;
import edu.ucsd.hep.rootrunnerutil.shell.ShellPipeCommandRunner;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* creates and follows the ROOT process (older implementation)
* @author holzner
*/
public class ROOTRunnerOld extends ROOTRunner
{
/** the output (temporary) directory where ROOT should write the
* output files to
*/
private File outputStoreDir;
//----------------------------------------------------------------------
public ROOTRunnerOld()
{
this((List<PipeCommandRunnerListener>) null, null, null, null);
}
//----------------------------------------------------------------------
/** @param shell_initialization_commands is prepended to the root command
* (without any spaces or separators)
*
* @param commandRunnerListenerFactories is a list of factories for creating
* objects that will be notified whenever some lines are read or written
* to the ROOT process. Can be null (this is equivalent to
* an empty list)
*
* @param shell_initialization_commands commands executed on the shell before starting ROOT
*/
public ROOTRunnerOld(List<PipeCommandRunnerListener> commandRunnerListeners,
String root_cmd_args,
String shell_initialization_commands,
PipeCommandRunnerWorkerFactory runnerFactory)
{
if (commandRunnerListeners == null)
this.commandRunnerListeners = new ArrayList<PipeCommandRunnerListener>();
else
this.commandRunnerListeners = new ArrayList<PipeCommandRunnerListener>(commandRunnerListeners);
// remove all null listeners (which can come in from the other constructor
// being called with a null pointer)
for (Iterator<PipeCommandRunnerListener> it = this.commandRunnerListeners.iterator();
it.hasNext();)
{
if (it.next() == null)
it.remove();
}
this.runnerFactory = runnerFactory;
if (root_cmd_args != null)
this.root_cmd_args = root_cmd_args;
else
this.root_cmd_args = "-b";
this.initialization_commands = shell_initialization_commands;
// start the pipe to ROOT right now
getROOTPipe();
// TODO: we should make sure that the ROOT process is killed
// on program exit: see http://www.exampledepot.com/egs/java.lang/ExitHook.html
}
//----------------------------------------------------------------------
/** convenience constructor for just one PipeCommandRunnerListenerFactory */
public ROOTRunnerOld(PipeCommandRunnerListener commandRunnerListener,
String root_cmd_args,
String shell_initialization_commands,
PipeCommandRunnerWorkerFactory runnerFactory)
{
this(AHUtils.makeList(commandRunnerListener),
root_cmd_args,
shell_initialization_commands,
runnerFactory);
}
//----------------------------------------------------------------------
private File makeROOTstartFile(String fullCmd) throws IOException
{
File cmd_script = AHUtils.writeTextToTemporaryFile(
"#!/bin/sh" + "\n"
+ fullCmd + " 2>&1\n",
"cmd",
".sh"); // merge stdout and stderr for the moment
cmd_script.setExecutable(true);
cmd_script.deleteOnExit();
return cmd_script;
}
//----------------------------------------------------------------------
/** default implementation for starting ROOT */
protected void startROOT() throws Exception
{
// String cmd = rootCmd + " " + (root_cmd_args != null ? root_cmd_args : "");
// if (initialization_commands != null)
// cmd = initialization_commands + cmd;
//
// if (mergeStdoutStderr)
// cmd += " 2>&1";
String cmd = rootCmd + " " + (root_cmd_args != null ? root_cmd_args : "");
if (initialization_commands != null)
cmd = initialization_commands + "\n" + cmd;
// File cmd_script = makeROOTstartFile(cmd);
if (this.runnerFactory != null)
root_pipe = new PipeCommandRunner(runnerFactory.makeWorker(
cmd
// cmd_script.getAbsolutePath()
)
);
else
root_pipe = new PipeCommandRunner(new ShellPipeCommandRunner(
cmd
//cmd_script.getAbsolutePath()
)
);
// if (this.runnerFactory != null)
// root_pipe = new PipeCommandRunner(runnerFactory.makeWorker("root"));
// else
// root_pipe = new PipeCommandRunner(new ShellPipeCommandRunnerWithCommonsExec("root"));
this.root_stdout_for_checkpointing = root_pipe.getCommandStdoutStream();//.getROOTStdoutStream();
// for debugging
{
List<InputStream> isClones = InputStreamFanOut.makeClones(root_stdout_for_checkpointing, 2);
root_stdout_for_checkpointing = isClones.get(0);
InputStreamDumper debugDumper = new InputStreamDumper(System.err, isClones.get(1), "GOT FROM ROOT:");
debugDumper.start();
}
// for each listener factory, create a listener
for (PipeCommandRunnerListener listener : this.commandRunnerListeners)
{
root_pipe.addListener(listener);
}
// System.out.println("STARTING ROOT: " + cmd);
// root_pipe.writeLine(cmd);
// wait for ROOT prompt
this.waitForLine(Pattern.compile(Pattern.quote("W E L C O M E to R O O T")));
}
//----------------------------------------------------------------------
public File getOutputStoreDir()
{
if (this.outputStoreDir == null)
outputStoreDir = getROOTPipe().makeTemporaryDirectory();
return outputStoreDir;
}
//----------------------------------------------------------------------
/** this is not used anywhere current in RooFitExplorer and maybe intended when running root
* over ssh ? */
public byte[] getOutputFileContent(String fname)
{
return getROOTPipe().getOutputFileContent(fname);
}
//----------------------------------------------------------------------
public void waitForCommandOutput() throws IOException
{
while (this.root_stdout_for_checkpointing.available() <= 0)
{
try
{
Thread.sleep(100);
// check whether the process died
if (this.root_pipe.hasExited())
throw new Error("root process already exited while waiting for completion");
} catch (InterruptedException ex)
{
}
}
}
//----------------------------------------------------------------------
public String waitForLine(Pattern regexp) throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(this.root_stdout_for_checkpointing));
try
{
while (true)
{
while (!reader.ready())
{
try
{
Thread.sleep(100);
// check whether the process died
if (this.root_pipe.hasExited())
throw new Error("root process already exited while waiting for completion");
} catch (InterruptedException ex)
{
}
}
// this alone should already be sufficient as this will block
// until the stream is 'ready'
String line = reader.readLine();
if (line == null)
throw new Error("end program output reached");
// TODO: we should deal with other situations here:
// - crashes
// - EOF
if (regexp.matcher(line).find())
return line;
} // eternal loop
}
finally
{
// reader.close();
}
}
//----------------------------------------------------------------------
/** waits for the current command pipes to complete. Does this
* by telling root to write a string to the output stream
* and waits until it can read it from ROOT's stdout
*/
public String waitForCompletion()
{
// period after which another 'MARKER' is sent (seems to
// be ignored or not printed when it is sent too fast)
final long retryPeriod = 100000;
// period between checks for available input
final long waitingPeriod = 10;
try
{
BufferedReader reader = new BufferedReader(new InputStreamReader(this.root_stdout_for_checkpointing));
List<String> linesRead = new ArrayList<String>();
while (true)
{
if (this.root_pipe.hasExited())
throw new Error("root process already exited");
long nextMarkerSending = new Date().getTime() + retryPeriod;
Marker marker = this.sendMarker();
while (new Date().getTime() < nextMarkerSending)
{
while (new Date().getTime() < nextMarkerSending && !reader.ready())
{
try
{
Thread.sleep(waitingPeriod);
// check whether the process died
if (this.root_pipe.hasExited())
throw new Error("root process already exited while waiting for completion");
} catch (InterruptedException ex)
{
}
}
if (! reader.ready())
break;
// this alone should already be sufficient as this will block
// until the stream is 'ready'
String line = reader.readLine();
if (line == null)
continue;
// TODO: we should deal with other situations here:
// - crashes
// - EOF
if (line.equals(marker.getString()))
{
return AHUtils.linesToSingleString(linesRead);
}
linesRead.add(line);
} // while we should not send a new marker
} // eternal loop
} catch (IOException ex)
{
Logger.getLogger(ROOTRunnerOld.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
/** runs one single command and retrieves its output.
*
* Currently this is implemented by using ROOT's redirection after
* the semicolon, so the given command must be a simple statement,
* no loops or if statements and must not contain a semicolon grouping
* several statements.
*
* @param string
* @return
*/
private synchronized String getCommandOutputClassic(String cmd) throws IOException
{
// create a temporary file to be written to
//
// note: redirecting into a separate file each time
// this method is called does not work for numbers
// above around 1000 times. This is because of
// a bug in TUnixSystem::RedirectOutput(..)
// which does not close the previous file in all cases
// (see http://savannah.cern.ch/bugs/?96935 )
//
// The bug seems not to appear when not running
// with output redirection into a pipe or a file
//
// Note that it's also quite cumbersome to leave
// the output file redirected to always the same file:
// we would have to scan for the marker line mixed with
// the output (as we would not redirect back to the
// normal stdout for the marker)
File fout = File.createTempFile("ROOTRunner", ".txt");
// run the command
// this seems to be problematic when waiting for completion
// afterwards ?!
// this.writeLine(cmd + "; >" + fout.getAbsolutePath());
// this.writeLine(cmd);
// see http://root.cern.ch/root/html/TSystem.html#TSystem:RedirectOutput
//
// note that in this form this does not play well with
// calling code which also modifies the output
//
this.writeLine("gSystem->RedirectOutput(\"" + fout.getAbsolutePath() + "\");");
this.writeLine(cmd);
// re-establish stdout
this.writeLine("gSystem->RedirectOutput(0);");
this.waitForCompletion();
// read the temporary file and return its content
// TODO: who deletes the temporary file ?
return AHUtils.readFile(fout.getAbsolutePath());
}
//----------------------------------------------------------------------
/** version which tries to avoid using gSystem->RedirectOutput(..).
* This is actually quite similar to what expect is doing...
*/
private synchronized String getCommandOutputBetter(String cmd) throws IOException
{
clearPrompt();
// synchronize first
System.out.println("first synchronization");
this.waitForCompletion();
this.writeLine(cmd);
System.out.println("second synchronization");
return this.waitForCompletion();
}
//----------------------------------------------------------------------
/** note that this is NOT intended for binary output but
* only for line-oriented output.
*/
public String getCommandOutput(String cmd) throws IOException
{
return this.getCommandOutputBetter(cmd);
}
//----------------------------------------------------------------------
/** this is mostly for testing/debugging purposes: add the possibility
* to add a PipeCommandRunnerListener to the pipe to ROOT.
* @param listener
*/
public void addCommandPipeListener(PipeCommandRunnerListener listener)
{
this.root_pipe.addListener(listener);
}
@Override
public List<String> getMultipleCommandsOutputBatch(List<String> cmds) throws IOException
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public byte[] readFile(String fname) throws IOException
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String createTempFile(String prefix, String suffix) throws IOException
{
throw new UnsupportedOperationException("Not supported yet.");
}
}