/*
* Copyright 2012 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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.language.DefaultTemplateLexer;
/**
* Newer implementation of a ROOTRunner
* @author holzner
*/
public class ROOTRunnerImpl extends ROOTRunner
{
//----------------------------------------------------------------------
private InputStreamSplitter markerFinder;
/** if specified, this command will be used transfer
* a remote file locally */
private String transferRemoteFileToLocalCmd;
/** if non-null, this command is used to create a temporary file
* (e.g. remotely) */
private String createTempFileCmd;
//----------------------------------------------------------------------
/** @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 ROOTRunnerImpl(List<PipeCommandRunnerListener> commandRunnerListeners,
String root_cmd_args,
String shell_initialization_commands,
PipeCommandRunnerWorkerFactory runnerFactory,
String rootCmd)
{
if (rootCmd != null)
this.setROOTCommand(rootCmd);
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 ROOTRunnerImpl(PipeCommandRunnerListener commandRunnerListener,
String root_cmd_args,
String shell_initialization_commands,
PipeCommandRunnerWorkerFactory runnerFactory,
String rootCmd)
{
this(AHUtils.makeList(commandRunnerListener),
root_cmd_args,
shell_initialization_commands,
runnerFactory,
rootCmd);
}
//----------------------------------------------------------------------
/** @return the characters read before the command completed. Does NOT
reset the recording buffer of markerFinder */
@Override
public String waitForCompletion()
{
Marker marker = sendMarker();
return waitForString(marker.getString());
}
//----------------------------------------------------------------------
public String waitForString(String str)
{
// TODO: add support for detecting when ROOT crashed
// and produced a stack trace
try
{
markerFinder.enableRecording(true);
this.markerFinder.readToString(str);
return this.markerFinder.getRecordings();
} catch (IOException ex)
{
Logger.getLogger(ROOTRunnerOld.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
//----------------------------------------------------------------------
/** 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 + "\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));
this.root_stdout_for_checkpointing = root_pipe.getCommandStdoutStream();//.getROOTStdoutStream();
// for debugging
if (false)
{
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);
}
//----------
// DIFFERENCE TO THE OLD IMPLEMENTATION
//----------
this.markerFinder = new InputStreamSplitter(this.root_stdout_for_checkpointing);
//----------
// wait for ROOT prompt
this.waitForString("W E L C O M E to R O O T");
}
//----------------------------------------------------------------------
@Override
public String getCommandOutput(String cmd) throws IOException
{
clearPrompt();
// synchronize first
System.out.println("first synchronization");
this.waitForCompletion();
this.markerFinder.clearRecordings();
this.writeLine(cmd);
System.out.println("second synchronization");
return this.waitForCompletion();
}
//----------------------------------------------------------------------
/** each command in cmds should end with a semicolon, no semicolon
* is appended here */
@Override
public List<String> getMultipleCommandsOutputBatch(List<String> cmds) throws IOException
{
List<String> retval = new ArrayList<String>();
if (cmds.isEmpty())
// no commands, no output (we catch this here because later
// on we assume that the output file contains at least one marker)
return retval;
List<String> markers = new ArrayList<String>();
// note that we have to put the code inside braces for ROOT to run
// it properly but variables defined at the interactive prompt
// are visible in the macro
// TODO: will this be deleted at program exit ?
File macroFile = File.createTempFile("rootRunner", ".C");
//-----
// create a macro file containing all the commands
// add markers (which are drawn from random numbers rather
// than from time stamps because what we do here is quite
// close in time)
//-----
PrintStream out = new PrintStream(new FileOutputStream(macroFile));
out.println("{");
for (String cmd : cmds)
{
out.println(cmd);
// write the marker generating command
Marker marker = new Marker();
out.println(marker.getCommand());
markers.add(marker.getString());
}
out.println("}");
// the file to collect the output
File outputFile = File.createTempFile("rootRunner", null);
//-----
// then run it (with instantaneous compilation using ACLIC)
//-----
// System.out.println("MACRO=" + macroFile.getAbsolutePath());
// System.out.println("output=" + outputFile.getAbsolutePath());
// redirecting everytghing to a single output file
// seems NOT to work; when we send a marker afterwards
// this marker seems never to be printed
markerFinder.clearRecordings();
markerFinder.enableRecording(true);
this.writeLine(".x " + macroFile.getAbsolutePath()
/* + "+ >" + outputFile.getAbsolutePath() */);
// running waitForCompletion(..) immediately seems not to work
//
// trying a workaround: wait until the output file has non-zero
// size (we write at least one marker to the file, so
// the output file must have non-zero size)
// File.length() returns 0 if the file does not exist
// while (outputFile.length() == 0)
// {
// AHUtils.sleepSeconds(0.1);
// }
//
// wait for each of the markers
int i = 1;
for (String marker : markers)
{
// System.out.println("ZZZ " + marker + " " + i + " of " + markers.size() + " " + cmds.get(i-1));
this.markerFinder.readToString(marker);
retval.add(this.markerFinder.getRecordings());
this.markerFinder.clearRecordings();
++i;
}
// // this.waitForString("root [");
// // only now send the marker command
// this.waitForCompletion();
//
// //-----
// // parse the results (i.e. split by markers)
// //-----
// InputStreamSplitter markerReader = new InputStreamSplitter(new FileInputStream(outputFile));
//
//
// for (String marker : markers)
// {
// markerReader.enableRecording(true);
// markerReader.readToString(marker);
// retval.add(markerReader.getRecordings());
//
// markerReader.clearRecordings();
// }
//
return retval;
}
//----------------------------------------------------------------------
public void setTransferRemoteFileToLocalCmd(String transferRemoteFileToLocalCmd)
{
this.transferRemoteFileToLocalCmd = transferRemoteFileToLocalCmd;
}
public void setCreateTempFileCmd(String createTempFileCmd)
{
this.createTempFileCmd = createTempFileCmd;
}
//----------------------------------------------------------------------
@Override
public byte[] readFile(String fname) throws IOException
{
// check if we have a special command to transfer a remote file locally
if (transferRemoteFileToLocalCmd != null)
{
// first transfer the file locally into a temporary file, then
// read it
File localTempFile = File.createTempFile("rootRunner", null);
System.err.println("transferRemoteFileToLocalCmd=" + transferRemoteFileToLocalCmd);
StringTemplate template = new StringTemplate(transferRemoteFileToLocalCmd, DefaultTemplateLexer.class);
template.setAttribute("remotefile", fname);
template.setAttribute("localfile", localTempFile.getAbsolutePath());
String cmd = template.toString();
System.err.println("executing command to copy a file locally: " + cmd);
// execute the command
Process proc = Runtime.getRuntime().exec(cmd);
AHUtils.waitForCommandCompletion(proc);
byte[] retval = AHUtils.readFileToByteArray(localTempFile.getAbsolutePath());
// delete the local temporary file
localTempFile.delete();
return retval;
}
else
return AHUtils.readFileToByteArray(fname);
}
//----------------------------------------------------------------------
@Override
public String createTempFile(String prefix, String suffix) throws IOException
{
if (this.createTempFileCmd != null)
{
// run the command and read the first line of its output
System.err.println("executing a command to create a (remote) temporary file: " + this.createTempFileCmd);
String output = AHUtils.readCommandOutput(this.createTempFileCmd);
System.out.println("GOT OUTPUT: '" + output + "'");
List<String> lines = AHUtils.splitToLines(output);
return lines.get(0);
}
else
{
File tempFile = File.createTempFile(prefix, suffix);
return tempFile.getAbsolutePath();
}
}
//----------------------------------------------------------------------
}