package com.perforce.api; import java.io.*; import java.util.*; import java.text.DateFormat; /* * Copyright (c) 2001, Perforce Software, All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Handles the execution of all perforce commands. This class can be used * directly, but the preferred use of this API is through the * {@link com.perforce.api.SourceControlObject SourceControlObject} subclasses. * <p> * <b>Example Usage:</b> * <pre> * String l; * Env env = new Env(); * String[] cmd = { "p4", "branches"}; * try { * P4Process p = new P4Process(env); * p.exec(cmd); * while (null != (l = p.readLine())) { * // Parse the output. * } * p.close(); * } catch (Exception ex) { * throw new PerforceException(ex.getMessage()); * } * </pre> * * @author <a href="mailto:david@markley.cc">David Markley</a> * @version $Date: 2003/12/02 $ $Revision: #2 $ * @see Env * @see SourceControlObject * @see Thread */ public class P4Process { private static P4Process base = null; private P4JNI jni_proc = null; private boolean using_native = false; private Env environ = null; private Runtime rt = Runtime.getRuntime(); private Process p; private BufferedReader in, err; private BufferedWriter out; private int exit_code = 0; private EventLog log; private String P4_ERROR = null; private String[] new_cmd; private long threshold = 10000; // The default is 10 seconds; private boolean raw = false; /** * Default no-argument constructor. If the runtime has not been established, * this constructor will set it up. No environment is specified, so the base * environment will be used if it exists. * * @see #getBase() */ public P4Process() { this(null); } /** * Constructor that specifies the source control environment. * * @param e Source control environment to use. */ public P4Process(Env e) { super(); if (null == rt) { rt = Runtime.getRuntime(); } if (null == e) { if (null == base) { base = this; this.environ = new Env(); } else { this.environ = new Env(base.getEnv()); } } else { this.environ = e; } if (null != environ) this.threshold = environ.getServerTimeout(); } /** * Sets the environment to use. * * @param e Source control environment. */ public void setEnv(Env e) { this.environ = e; if (null != environ) this.threshold = environ.getServerTimeout(); } /** * Returns the environment in use by this process. * * @return Source control environment. */ public Env getEnv() { return this.environ; } /** * Returns the base process for this class. The base process is set when * this class is first instantiated. The base process is used when other * <code>P4Process</code> are instantiated to share settings, including the * {@link com.perforce.api.Env source control environment}. * * @see Env * @return Source control environment. */ public static P4Process getBase() { if (null != base) { return base; } else { return new P4Process(); } } /** * Sets the base process to be used when new processes are instantiated. * * @see #getBase() */ public static void setBase(P4Process b) { if (null != b) { base = b; } } /** * Returns the exit code returned when the underlying process exits. * * @return Typical UNIX style return code. */ public int getExitCode() { return exit_code; } /** * In raw mode, the process will return the prefix added by the "-s" command * line option. The default is false. */ public void setRawMode(boolean raw) { this.raw = raw; } /** * Returns the status of raw mode for this process. */ public boolean getRawMode() { return this.raw; } /** * Executes a p4 command. This uses the class environment information * to execute the p4 command specified in the String array. This array * contains all the command line arguments that will be specified for * execution, including "p4" in the first position. * * @param cmd Array of command line arguments ("p4" must be first). */ public synchronized void exec(String[] cmd) throws IOException { String[] pre_cmds = new String[12]; int i = 0; pre_cmds[i++] = cmd[0]; pre_cmds[i++] = "-s";//Forces all commands to use stdout for message reporting, no longer read stderr if (! getEnv().getPort().trim().equals("")) { pre_cmds[i++] = "-p"; pre_cmds[i++] = getEnv().getPort(); } if (! getEnv().getUser().trim().equals("")) { pre_cmds[i++] = "-u"; pre_cmds[i++] = getEnv().getUser(); } if (! getEnv().getClient().trim().equals("")) { pre_cmds[i++] = "-c"; pre_cmds[i++] = getEnv().getClient(); } if (! getEnv().getPassword().trim().equals("")) { pre_cmds[i++] = "-P"; pre_cmds[i++] = getEnv().getPassword(); } if (cmd[1].equals("-x")) { pre_cmds[i++] = "-x"; pre_cmds[i++] = cmd[2]; } new_cmd = new String[(i+cmd.length)-1]; for (int j = 0; j < (i+cmd.length)-1; j++) { if (j < i) { new_cmd[j] = pre_cmds[j]; } else { new_cmd[j] = cmd[(j-i)+1]; } } Debug.verbose("P4Process.exec: ", new_cmd); if (P4JNI.isValid()) { native_exec(new_cmd); using_native = true; } else { pure_exec(new_cmd); using_native = false; } } /** * Executes the command utilizing the P4API. This method will be used * only if the supporting Java Native Interface library could be loaded. */ private synchronized void native_exec(String[] cmd) throws IOException { jni_proc = new P4JNI(); // P4JNI tmp = new P4JNI(); jni_proc.runCommand(jni_proc, cmd, environ); in = jni_proc.getReader(); err = in; out = jni_proc.getWriter(); } /** * Executes the command through a system 'exec'. This method will be used * only if the supporting Java Native Interface library could not be loaded. */ private synchronized void pure_exec(String[] cmd) throws IOException { if (null != this.environ.getExecutable()) { cmd[0] = this.environ.getExecutable(); } p = rt.exec(cmd, this.environ.getEnvp()); InputStream is = p.getInputStream(); Debug.verbose("P4Process.exec().is: "+is); InputStreamReader isr = new InputStreamReader(is); Debug.verbose("P4Process.exec().isr: "+isr); in = new BufferedReader(isr); InputStream es = p.getErrorStream(); Debug.verbose("P4Process.exec().es: "+es); InputStreamReader esr = new InputStreamReader(es); Debug.verbose("P4Process.exec().esr: "+esr); err = new BufferedReader(esr); OutputStream os = p.getOutputStream(); Debug.verbose("P4Process.exec().os: "+os); OutputStreamWriter osw = new OutputStreamWriter(os); Debug.verbose("P4Process.exec().osw: "+osw); out = new BufferedWriter(osw); } /** * Sets the event log. Any events that should be logged will be logged * through the EventLog specified here. * * @param log Log for all events. */ public synchronized void setEventLog(EventLog log) { this.log = log; } /** * Logs the event message to the output stream. * * @param out Stream to which the message is logged. * @param event Message to be logged. */ private void log(PrintStream out, String event) { if (null == log) { out.println(event); out.flush(); } else { log.log(event); } } /** * Writes <code>line</code> to the standard input of the process. * * @param line Line to be written. */ public synchronized void print(String line) throws IOException { out.write(line); } /** * Writes <code>line</code> to the standard input of the process. A * newline is appended to the output. * * @param line Line to be written. */ public synchronized void println(String line) throws IOException { out.write(line); out.newLine(); } /** * Flushes the output stream to the process. */ public synchronized void flush() throws IOException { out.flush(); } /** * Flushes and closes the output stream to the process. */ public synchronized void outClose() throws IOException { out.flush(); out.close(); } /** * Returns the next line from the process, or null if the command has * completed its execution. */ public synchronized String readLine() { if (using_native && null != jni_proc && jni_proc.isPiped()) { return native_readLine(); } else { return pure_readLine(); } } /** * Reads the next line from the process. This method will be used * only if the supporting Java Native Interface library could be loaded. */ private synchronized String native_readLine() { try { return in.readLine(); } catch (IOException ex) { return null; } } /** * Reads the next line from the process. This method will be used * only if the supporting Java Native Interface library could not be loaded. */ private synchronized String pure_readLine() { String line; long current, timeout = ((new Date()).getTime())+threshold; if (null == p || null == in || null == err) return null; //Debug.verbose("P4Process.readLine()"); try { for (;;) { if (null == p || null == in || null == err) { Debug.error("P4Process.readLine(): Something went null"); return null; } current = (new Date()).getTime(); if (current >= timeout) { Debug.error("P4Process.readLine(): Timeout"); // If this was generating a new object from stdin, return an // empty string. Otherwise, return null. for (int i = 0; i < new_cmd.length; i++) { if (new_cmd[i].equals("-i")) return ""; } return null; } //Debug.verbose("P4Process.readLine().in: "+in); try { /** If there's something coming in from stdin, return it. *We assume that the p4 command was called with -s which sends all messages to standard out pre-pended with a string that *indicates what kind of messsage it is *error *warning *text *info *exit */ // Some errors still come in on Standard error while (err.ready()) { line = err.readLine(); if (null != line) { addP4Error(line + "\n"); } } if (in.ready()) { line = in.readLine(); Debug.verbose("From P4:" + line); if (line.startsWith("error")) { if (!line.trim().equals("") && (-1 == line.indexOf("up-to-date")) && (-1 == line.indexOf("no file(s) to resolve"))) { addP4Error(line); } } else if (line.startsWith("warning")) { } else if (line.startsWith("text")) { } else if (line.startsWith("info")) { } else if (line.startsWith("exit")) { int exit_code = new Integer(line.substring(line.indexOf(" ") + 1 , line.length())).intValue(); if (0 == exit_code) { Debug.verbose("P4 Exec Complete."); } else { Debug.error("P4 exited with an Error!"); } return null; } if (! raw) line = line.substring(line.indexOf(":")+1).trim(); Debug.verbose("P4Process.readLine(): "+line); return line; } } catch (NullPointerException ne) { } // If there's nothing on stdin or stderr, check to see if the // process has exited. If it has, return null. try { exit_code = p.exitValue(); return null; } catch (IllegalThreadStateException ie) { Debug.verbose("P4Process: Thread is not done yet."); } // Sleep for a second, so this thread can't become a CPU hog. try { Debug.verbose("P4Process: Sleeping..."); Thread.sleep(100); // Sleep for 1/10th of a second. } catch (InterruptedException ie) { } } } catch (IOException ex) { return null; } } /** * Waits for the process to exit and closes out the process. This method * should be called after the {@link #exec(java.lang.String[]) exec} * method in order to close things down properly. * * @param out The stream to which any errors should be sent. * @return The exit value of the underlying process. */ public synchronized int close(PrintStream out) throws IOException { if (using_native && null != jni_proc && jni_proc.isPiped()) { native_close(out); } else { pure_close(out); } /* if (0 != exit_code) { throw new IOException("P4Process ERROR: p4 sync exited with error ("+ exit_code+")"); }*/ if (null != P4_ERROR) { throw new IOException(P4_ERROR); } return exit_code; } /** * Closes down connections to the underlying process. This method will be * used only if the supporting Java Native Interface library could be loaded. */ private synchronized void native_close(PrintStream out) { try { in.close(); out.flush(); out.close(); } catch (IOException ioe) { } } /** * Closes down connections to the underlying process. This method will be * used only if the supporting Java Native Interface library could not be * loaded. */ private synchronized void pure_close(PrintStream out) { /* * Try to close this process for at least 30 seconds. */ //System.err.println("pure_close:"); try { for (int i = 0; i < 30; i++) { //System.err.println(" i = " + i); // try { // in.close(); // err.close(); // //out.flush(); // out.close(); // } catch (IOException ioe) { ioe.printStackTrace(); } //System.err.println(" wait for:"); try { exit_code = p.waitFor(); //System.err.println(" exit_code = " + exit_code); p.destroy(); break; } catch (InterruptedException ie) { ie.printStackTrace(); } try { Thread.sleep(1000); } catch (InterruptedException ie) { ie.printStackTrace(); } } } finally { //System.err.println("pure_close done."); } } /** * Waits for the underlying process to exit and closes it down. This method * should be called after the {@link #exec(java.lang.String[]) exec} * method in order to close things out properly. Errors are sent to * System.err. * * @see System * @return The exit value of the underlying process. */ public int close() throws IOException { return close(System.err); } /** * Sets the P4USER in the class information. * * @see Env#setUser(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setUser(String) setUser(String)} * @param user P4USER value. */ public void setUser(String user) { this.environ.setUser(user); } /** * Returns the P4USER * * @see Env#getUser() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getUser() getUser()} */ public String getUser() { return this.environ.getUser(); } /** * Sets the P4CLIENT in the class information. * * @see Env#getClient(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getClient(String) getClient(String)} * @param user P4CLIENT value. */ public void setClient(String client) { this.environ.setClient(client); } /** * Returns the P4CLIENT * * @see Env#getClient() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getClient() getClient()} */ public String getClient() { return this.environ.getClient(); } /** * Sets the P4PORT in the class information. * * @see Env#setPort(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setPort(String) setPort(String)} * @param user P4PORT value. */ public void setPort(String port) { this.environ.setPort(port); } /** * Returns the P4PORT. * * @see Env#getPort() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getPort() getPort()} */ public String getPort() { return this.environ.getPort(); } /** * Sets the P4PASSWD in the class information. * * @see Env#setPassword(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setPassword(String) setPassword(String)} * @param user P4PASSWD value. */ public void setPassword(String password) { this.environ.setPassword(password); } /** * Returns the P4PASSWORD. * * @see Env#getPassword() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getPassword() getPassword()} */ public String getPassword() { return this.environ.getPassword(); } /** * Sets the PATH in the class information. * * @see Env#setPath(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setPath(String) setPath(String)} * @param user PATH value. */ public void setPath(String path) { this.environ.setPath(path); } /** * Returns the P4PATH. * * @see Env#getPath() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getPath() getPath()} */ public String getPath() { return this.environ.getPath(); } /** * Sets the SystemDrive in the class information. This is only * meaningful under Windows. * * @see Env#setSystemDrive(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setSystemDrive(String) setSystemDrive(String)} * @param user SystemDrive value. */ public void setSystemDrive(String drive) { this.environ.setSystemDrive(drive); } /** * Sets the SystemRoot in the class information. This is only * meaningful under Windows. * * @see Env#setSystemRoot(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setSystemRoot(String) setSystemRoot(String)} * @param user SystemRoot value. */ public void setSystemRoot(String root) { this.environ.setSystemRoot(root); } /** * Sets up the path to reach the p4 executable. The full path passed in must * contain the executable or at least end in the system's file separator * character. This gotten from the file.separator property. For example: * <pre> * p4.executable=/usr/bin/p4 # This will work * p4.executable=/usr/bin/ # This will work * <font color=Red>p4.executable=/usr/bin # This won't work</font> * </pre> * * @see Env#setExecutable(String) * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#setExecutable(String) setExecutable(String)} * @param exe Full path to the p4 executable. */ public void setExecutable(String exe) { this.environ.setExecutable(exe); } /** * Returns the path to the executable. * @see Env#getExecutable() * @deprecated Replaced by {@link #getEnv() getEnv()}.{@link Env#getExecutable() getExecutable()} * */ public String getExecutable() { return this.environ.getExecutable(); } /** Set the server timeout threshold. */ public void setServerTimeout(long threshold) { this.threshold = threshold; } /** Return the server timeout threshold. */ public long getServerTimeout() { return threshold; } public String toString() { return this.environ.toString(); } private void addP4Error(String message) { if (null ==P4_ERROR) { P4_ERROR = message; } else { P4_ERROR += message; } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 3961 | Greg Abbas | various initial fixes | ||
#1 | 3960 | Greg Abbas | branch for edit | ||
//public/perforce/api/java/p4package/com/perforce/api/P4Process.java | |||||
#3 | 1334 | David Markley |
Added more example code. Added the Counter class. Corrected the filelog methods for the FileEntry class. |
||
#2 | 1149 | David Markley | Added more documentation, a few examples, a cleanUp method, and a server timeout threshold. | ||
#1 | 1043 | David Markley | Initial branch to public area. | ||
//guest/david_markley/p4package/com/perforce/api/P4Process.java | |||||
#2 | 1035 | David Markley | Changed the file type to provide for keyword expansion. | ||
#1 | 1034 | David Markley |
Added P4Package sources as subset of the P4WebPublisher project. Copyright (c) 2001, Perforce Software, All rights reserved. |