FileEntry.java #4

  • //
  • guest/
  • sivananda_poreddy/
  • perforce/
  • api/
  • java/
  • p4package/
  • com/
  • perforce/
  • api/
  • FileEntry.java
  • View
  • Commits
  • Open Download .zip Download (33 KB)
package com.perforce.api;

import java.io.*;
import java.util.*;
import java.text.*;

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

/**
 * Representation of a source control file.
 *
 * @see Hashtable
 * @author <a href="mailto:[email protected]">David Markley</a>
 * @author <a href="mailto:[email protected]">Sivananda Poreddy</a>
 * @version $Date: 2008/08/06 $ $Revision: #4 $
 */

public final class FileEntry extends SourceControlObject {
  private String depot_path = null;
  private String client_path = null;
  private String description = "";
  private String owner = "";
  private FileEntry source = null;
  private int head_change = -1;
  private int head_rev = 0;
  private String head_type = "unknown";
  private long head_time = 0;
  private int have_rev = 0;
  private int other_cnt = 0;
  private String head_action = "";
  private Vector others;
  private static HashDecay fentries;
  private String file_content = "";
  private DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.MEDIUM);


  /** Default no-argument constructor. */
  public FileEntry() {
    this((Env)null);
  }

  /**
   * Constructs a file entry using the environment.
   *
   * @param env Source control environement to use.
   */
  public FileEntry(Env env) {
    super(env);
    if (null == others) {
      others = new Vector();
    }
  }

  /**
   * Constructs a file entry using the environment and path.
   *
   * @param env Source control environement to use.
   * @param p Path to the file.
   */
  public FileEntry(Env env, String p) {
    this(env);
    if (p.startsWith("//")) {
      depot_path = p;
    } else {
      client_path = p;
    }
  }

  /**
   * Constructs a file entry using the path.
   *
   * @param p Path to the file.
   */
  public FileEntry(String p) {
    this(null, p);
  }

  private static HashDecay setCache() {
    if (null == fentries) {
      fentries = new HashDecay(120000);
      fentries.start();
    }
    return fentries;
  }

  public HashDecay getCache() {
    return setCache();
  }

  /** Sets the decription for this file */
  public void setDescription(String d) {
    description = d;
  }

  /** Returns the decription for this file */
  public String getDescription() {
    return description;
  }

  /** Sets the owner for this file */
  public void setOwner(String o) {
    int pos;
    owner = o;
    if (-1 != (pos = owner.indexOf('@'))) {
      owner = owner.substring(0,pos);
    }
  }

  /** Returns the owner for this file */
  public String getOwner() {
    return owner;
  }

  /** Sets the source file entry associated with this file. */
  public void setSource(FileEntry fent) {
    source = fent;
  }

  /** Returns the source file entry associated with this file. */
  public FileEntry getSource() {
    return source;
  }
 
  /** Sets the head revision type for this file. */
  public void setHeadType(String type) {
    this.head_type = type;
  }

  /** Returns the head revision type for this file. */
  public String getHeadType() {
    return this.head_type;
  }

  /**
   * Sets the head date for this file. The expected format for the date
   * is yyyy/MM/dd. The time will default to 12:00:00 AM.
   */
  public void setHeadDate(String date) {
    // Format the current time.
    SimpleDateFormat formatter = new SimpleDateFormat ("yyyy/MM/dd");
    // Parse the previous string back into a Date.
    ParsePosition pos = new ParsePosition(0);
    Date hDate = formatter.parse(date, pos);
    this.head_time = hDate.getTime()/1000;
  }

  /**
   * Returns a String representation of date for the head revsision of the
   * file. The format is yyyy/MM/dd.
   */
  public String getHeadDate() {
    // Format the current time.
    SimpleDateFormat formatter = new SimpleDateFormat ("yyyy/MM/dd");
    return formatter.format(new Date(this.head_time*1000));
  }

  /** Sets the head revision time for this file. */
  public void setHeadTime(long time) {
    this.head_time = time;
  }

  /** Returns the head revision time for this file. */
  public long getHeadTime() {
    return this.head_time;
  }

  /**
   * Sets the format used by the getHeadTimeString method. The format of
   * this string is that of the SimpleDateFormat class.
   * <p>
   * An example format would be setTimeFormat("MM/dd HH:mm:ss");
   *
   * @see SimpleDateFormat
   */
  public void setTimeFormat(String format) {
    if (null == format) return;
    fmt = new SimpleDateFormat(format);
  }

  /** Returns the head revision time as a <code>String</code> for this file. */
  public String getHeadTimeString() {
    Date d = new Date(this.head_time * 1000);

    if (null == fmt) {
      fmt = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.MEDIUM);
      fmt.setTimeZone(TimeZone.getTimeZone("EST"));
    }
    return fmt.format(d);
  }

  /** Sets the head revision action for this file. */
  public void setHeadAction(String action) {
    this.head_action = action;
  }

  /** Returns the head revision action for this file. */
  public String getHeadAction() {
    return this.head_action;
  }

  /** Sets the head revision change number for this file. */
  public void setHeadChange(int change) {
    this.head_change = change;
  }

  /** Returns the head revision change number for this file. */
  public int getHeadChange() {
    return this.head_change;
  }

  /** Sets the head revision number for this file. */
  public void setHeadRev(int rev) {
    this.head_rev = rev;
  }

  /** Returns the head revision number for this file. */
  public int getHeadRev() {
    return this.head_rev;
  }

  /** Sets the revision number the client has for this file. */
  public void setHaveRev(int rev) {
    this.have_rev = rev;
  }

  /** Returns the revision number the client has for this file. */
  public int getHaveRev() {
    return this.have_rev;
  }

  /**
   * Sets the depot path for this file.
   *
   * @param p  path for this file in the depot.
   */
  public void setDepotPath(String p) {
    this.depot_path = p;
  }

  /** Returns the depot path for this file. */
  public String getDepotPath() {
    return depot_path;
  }

  /** Returns the path in local format. Uses the local path delimeter. */
  public static String localizePath(String path) {
    return customizePath(path, '/', File.separatorChar);
  }

  /** Returns the path in depot format. Uses the depot delimeter: '/'. */
  public static String depotizePath(String path) {
    return customizePath(path, File.separatorChar, '/');
  }
    
  /**
   * Returns the path after converting characters.
   *
   * @param str String to convert.
   * @param from_char  Character to be changed from.
   * @param to_char  Character to be changed to.
   */
  public static String customizePath(String str, char from_char, char to_char) {
    StringBuffer strbuf = new StringBuffer();
    int beg = 0, end = 0;
    while (-1 != (end = str.indexOf(from_char, beg))) {
      strbuf.append(str.substring(beg,end));
      strbuf.append(to_char);
      beg = end+1;
    }
    strbuf.append(str.substring(beg));
    return strbuf.toString();
  }

  /**
   * Resolves this file. If the force flag is false, and auto-resolve is
   * attempted (p4 resolve -am). If the force flag is true, an "accept theirs"
   * resolve is completed (p4 resolve -at).
   *
   * @param force Indicates whether the resolve should be forced.
   */
  public String resolve(boolean force) throws IOException {
    StringBuffer sb = new StringBuffer();
    String l;
    String[] rescmd = { "p4", "resolve", "-am", "fileRev" };
    if (force || -1 != (getHeadType().indexOf("binary")) ||
	-1 != (getHeadType().indexOf("link"))) {
      rescmd[2] = "-at";
    } else {
      rescmd[2] = "-am";
    }
    rescmd[3] = getDepotPath();
    P4Process p = new P4Process(getEnv());
    p.exec(rescmd);
    while (null != (l = p.readLine())) {
      if (null != sb) {
	sb.append(l);
	sb.append('\n');
      }
    }
    p.close();
    return sb.toString();
  }

  /**
   * Forces a resolve on a set of files. The <code>Enumeration</code> contains
   * the set of <code>FileEntry</code> objects that need resolved.
   *
   * @param env Source control environment to use.
   * @param en <code>Enumeration</code> of <code>FileEntry</code>.
   */
  public static String resolveAT(Env env, Enumeration en) throws IOException {
    StringBuffer sb = new StringBuffer();
    FileEntry fent;
    String l;
    String[] rescmd = { "p4", "-x", "-", "resolve", "-at" };
    P4Process p = new P4Process(env);
    p.exec(rescmd);
    while (en.hasMoreElements()) {
      fent = (FileEntry)en.nextElement();
      p.println(fent.getDepotPath());
      Debug.notify("resolveAT(): "+fent.getDepotPath());
    }
    p.println("\032\n\032");
    p.flush();
    p.outClose();
    Debug.notify("FileEntry.resolveAT(): Reading more lines.");
    while (null != (l = p.readLine())) {
      if (null != sb) {
		sb.append(l);
		sb.append('\n');
      }
    }
    p.close();
    return sb.toString();
  }

  /**
   * Resolves all the files in the path. The flags are used by the 'p4 resolve'
   * command to resolve any files in the path. This is just a simple way to
   * execute the 'p4 resolve' command.
   *
   * @param env Source control environment to use.
   * @param flags 'p4 resolve' command flags.
   * @param path  Path over which to resolve. May include wildcards.
   */
  public static String resolveAll(Env env, String flags, String path) throws IOException {
    StringBuffer sb = new StringBuffer();
    FileEntry fent;
    String l;
    String[] rescmd = { "p4", "resolve", flags, path};
    P4Process p = new P4Process(env);
    p.exec(rescmd);
    Debug.notify("FileEntry.resolveAll(): Reading more lines.");
    while (null != (l = p.readLine())) {
      if (null != sb) {
		sb.append(l);
		sb.append('\n');
      }
    }
    p.close();
    return sb.toString();
  }

  /**
   * @deprecated Don't use this anymore.
   */
  public static String HTMLEncode(String str) {
    if (null == str) return null;
    StringBuffer strbuf = new StringBuffer(str.length());
    char tmp;
    for (int i = 0; i < str.length(); i++) {
      tmp = str.charAt(i);
      if ('<' == tmp) {
	strbuf.append("&lt;");
      } else if ('>' == tmp) {
	strbuf.append("&gt;");
      } else {
	strbuf.append(tmp);
      }
    }
    return strbuf.toString();
  }

  /** Returns the file name. */
  public String getName() {
    int pos;
    String path = getDepotPath();
    if (null == path) {
      path = getClientPath();
    }
    if (null == path) {
      return "";
    }
    if (-1 == (pos = path.lastIndexOf('/'))) {
      return path;
    }
    return path.substring(pos+1);
  }

  /**
   * Sets the client path for this file.
   *
   * @param p  path for this file on the client system.
   */
  public void setClientPath(String p) {
    this.client_path = p;
  }

  /** Returns the client path for this file. */
  public String getClientPath() {
    return client_path;
  }

  /**
   * Gets the file information for the specified path.
   *
   * @param p  Path of the file to gather information about.
   */
  public static synchronized FileEntry getFile(String p) {
    FileEntry f = new FileEntry(p);
    f.sync();
    return f;
  }

  /**
   * Returns the list of files for the path. The path may include wildcards.
   *
   * @param env Source control environment to use.
   * @param path Path for set of files.
   */
  public static Vector getFiles(Env env, String path) {
    Vector v = null;
    String[] cmd = { "p4", "fstat", path + "%1" };
    if (null == path) return null;

    try {
      P4Process p = new P4Process(env);
      p.exec(cmd);
      v = parseFstat(null, p, true);
      p.close();
    } catch (IOException ex) { Debug.out(Debug.ERROR, ex); }
    return v;
  }

  /**
   * Returns a list of <code>FileEntry</code> objects that represent the
   * history of the specified file.
   *
   * @param env Source control environment to use.
   * @param path  Path to the file. Must be specific. No wildcards.
   */
  public static Vector getFileLog(Env env, String path) {
    String[] cmd = { "p4", "filelog", path };
    String l, tmp;
    StringTokenizer st;
    P4Process p;
    FileEntry fent = null, tmpent = null;
    Vector v = new Vector();
    int beg, end;

    if (null == path) return v;
    try {
      p = new P4Process(env);
      p.setRawMode(true);
      p.exec(cmd);
      while (null != (l = p.readLine())) {
	l = l.trim();
	if (l.startsWith("info2: ") && null != fent) {
	  tmpent = new FileEntry(env);
	  beg = 8;
	  if (-1 == (end = l.indexOf(' ', beg))) {
	    continue;
	  }
	  tmpent.setHeadAction(l.substring(beg,end));
	  beg = end;
	  if (-1 == (end = l.indexOf("from "))) {
	    tmpent.setDepotPath(path);
	  } else {
	    beg = end + 5;
	    if (-1 == (end = l.indexOf('#', beg))) {
	      tmpent.setDepotPath(l.substring(beg));
	    } else {				  
	      tmpent.setDepotPath(l.substring(beg, end));
	    }
	  }
	  if (-1 != (end = l.lastIndexOf('#'))) {
	    if (-1 != (beg = l.lastIndexOf('#', end-1))) {
	      tmpent.setHaveRev(Integer.parseInt(l.substring(beg+1,end-1)));
	    }
	    tmpent.setHeadRev(Integer.parseInt(l.substring(end+1)));
	  }
	  fent.setSource(tmpent);
	} else if (l.startsWith("info1: ")) {
	  if (null != fent) {
	    v.addElement(fent);
	  }
	  fent = new FileEntry(env);
	  fent.setDepotPath(path);
	  st = new StringTokenizer(l.substring(8));
	  fent.setHeadRev(Integer.parseInt(st.nextToken()));
	  st.nextToken(); // change
	  fent.setHeadChange(Integer.parseInt(st.nextToken()));
	  fent.setHeadAction(st.nextToken());
	  st.nextToken(); // on
	  fent.setHeadDate(st.nextToken());
	  st.nextToken(); // by
	  fent.setOwner(st.nextToken());
	  tmp = st.nextToken();
	  fent.setHeadType(tmp.substring(1, tmp.length()-1));
	  if (1 < (end = l.lastIndexOf('\''))) {
	    if (-1 < (beg = l.lastIndexOf('\'',end-1))) {
	      if (end - beg - 1 > 0) {
		fent.setDescription(l.substring(beg+1,end-1));
	      }
	    }
	  }
	}
      }
      p.close();
    } catch (IOException ex) { Debug.out(Debug.ERROR, ex); }
    if (null != fent && null != fent.getDepotPath()) {
      v.addElement(fent);
    }
    return v;
  }

  /**
   * Opens the file on the path for edit under the change. If the change is
   * null, the file is opened under the default changelist.
   *
   * @param env  P4 Environment
   * @param path Depot or client path to the file being opened for edit.
   * @param sync If true, the file will be sync'd before opened for edit.
   * @param lock If true, the file will be locked once opened.
   * @param chng The change that the file will be opened for edit in.
   */
  public static FileEntry openForEdit(Env env, String path, boolean sync, boolean lock, Change chng) throws Exception {
    if (sync) {
      FileEntry.syncWorkspace(env, path);
    }
    FileEntry fent = new FileEntry(env, path);
    fent.openForEdit(lock, chng);
    return fent;
  }

  /**
   * Opens this file for edit.
   *
   * @see #openForEdit(Env, String, boolean, boolean, Change)
   */
  public void openForEdit() throws Exception {
    openForEdit(false, null);
  }

  /**
   * Opens this file for edit.
   *
   * @see #openForEdit(Env, String, boolean, boolean, Change)
   */
  public void openForEdit(boolean lock) throws Exception {
    openForEdit(lock, null);
  }

  /**
   * Opens this file for edit.
   *
   * @see #openForEdit(Env, String, boolean, boolean, Change)
   */
  public void openForEdit(boolean lock, Change chng) throws Exception {
    String[] cmd;
    String l;
    P4Process p;
    int i = 0;
    sync();

    cmd = new String[(null == chng) ? 3 : 5];
    cmd[i++] = "p4";
    cmd[i++] = "edit";
    if (null != chng) {
      cmd[i++] = "-c";
      cmd[i++] = String.valueOf(chng.getNumber());
    }
    cmd[i++] = getClientPath();

    p = new P4Process(getEnv());
    p.exec(cmd);
    while (null != (l = p.readLine())) {
    }
    p.close();
    if (lock) obtainLock();
  }

  /**
   * Obtains the lock for this file. The file must have been opened for
   * edit prior to this method being called.
   */
  public void obtainLock() throws Exception {
    String[] cmd = { "p4", "lock", getDepotPath() };
    String l;
    P4Process p;

    p = new P4Process(getEnv());
    p.exec(cmd);
    while (null != (l = p.readLine())) {
    }
    p.close();
  }

  /**
   * Opens the file on the path for add under the change. If the change is
   * null, the file is opened under the default changelist.
   *
   * @param env  P4 Environment
   * @param path Depot or client path to the file being opened for add.
   * @param chng The change that the file will be opened for add in.
   */
  public static FileEntry openForAdd(Env env, String path, Change chng) throws Exception {
    FileEntry fent = new FileEntry(env, path);
    fent.openForAdd(chng);
    return fent;
  }

  /**
   * Opens this file for addition.
   *
   * @see #openForAdd(Env, String, Change)
   */
  public void openForAdd() throws Exception {
    openForAdd(null);
  }

  /**
   * Opens this file for addition.
   *
   * @see #openForAdd(Env, String, Change)
   */
  public void openForAdd(Change chng) throws Exception {
    String[] cmd;
    int i = 0;
    cmd = new String[(null == chng) ? 3 : 5];
    cmd[i++] = "p4";
    cmd[i++] = "add";
    if (null != chng) {
      cmd[i++] = "-c";
      cmd[i++] = String.valueOf(chng.getNumber());
    }
    cmd[i++] = getClientPath();
    String l;
    P4Process p;

    if (null == getClientPath()) {
      throw new Exception("No Client Path");
    }

    p = new P4Process(getEnv());
    p.exec(cmd);
    while (null != (l = p.readLine())) {
    }
    p.close();
  }

  /**
   * Opens this file for deletion.
   *
   */
  public void openForDelete(Change chng) throws Exception {
    String[] cmd;
    int i = 0;
    cmd = new String[(null == chng) ? 3 : 5];
    cmd[i++] = "p4";
    cmd[i++] = "delete";
    if (null != chng) {
      cmd[i++] = "-c";
      cmd[i++] = String.valueOf(chng.getNumber());
    }
    cmd[i++] = getClientPath();
    String l;
    P4Process p;

    if (null == getClientPath()) {
      throw new Exception("No Client Path");
    }

    p = new P4Process(getEnv());
    p.exec(cmd);
    while (null != (l = p.readLine())) {
    }
    p.close();
  }

  /**
   * Checks in a file that has already been opened on the client using the
   * description given. A new changelist is created and used for this
   * submission. The returned <code>FileEntry</code> contains the latest
   * information for the checked-in file.
   */
  public static FileEntry checkIn(Env env, String path, String description) throws PerforceException {
    FileEntry fent = new FileEntry(env, path);
    Change chng = new Change(env);
    chng.setDescription(description);
    chng.addFile(fent);
    chng.submit();
    fent.sync();
    return fent;
  }

  /**
   * Reopens the file with the new type or in the new change list.
   */
  public void reopen(String type, Change chng) throws PerforceException {
    String[] cmd;
    int i = 0;
    String l;
    P4Process p = null;

    if (null == getClientPath()) {
      try {
	sync();
      } catch (Exception ex) { /* Ignored Exception */ }
      if (null == getClientPath()) {
	throw new PerforceException("No Client Path");
      }
    }

    if (null == type && null == chng) return;
    cmd = new String[(null == type || null == chng)?5:7];
    cmd[i++] = "p4";
    cmd[i++] = "reopen";
    if (null != type) {
      cmd[i++] = "-t";
      cmd[i++] = type;
    }
    if (null != chng) {
      cmd[i++] = "-c";
      cmd[i++] = String.valueOf(chng.getNumber());
    }
    cmd[i++] = getClientPath();

    try {
      p = new P4Process(getEnv());
      p.exec(cmd);
      while (null != (l = p.readLine())) {
	if ( (-1 != l.indexOf("not opened on this client")) ||
	     (-1 != l.indexOf("Invalid file type")) ||
	     (-1 != l.indexOf("unknown")) ) {
	  throw new PerforceException(l);
	}
      }
    } catch (Exception ex) {
      throw new PerforceException(ex.getMessage());
    } finally {
      if (null != p) {
	try { p.close(); } catch (IOException ioex) { /* Ignored Exception */ }
      }
    }
  }


  /**
   * Reverts this file.
   */
  public boolean revert() {
    String[] cmd1 = { "p4", "revert", getDepotPath() };
    String[] cmd2 = { "p4", "sync", getDepotPath()+"#none" };
    String l;
    P4Process p;

    try {
      p = new P4Process(getEnv());
      p.exec(cmd1);
      while (null != (l = p.readLine())) {
      }
      p.close();
      p = new P4Process(getEnv());
      p.exec(cmd2);
      while (null != (l = p.readLine())) {
      }
      p.close();
    } catch (IOException ex) {
      Debug.out(Debug.ERROR, ex);
      return false;
    }
    return true;
  }
  
  /**
   * Returns a list of files that are open for edit or add. The list is
   * a <code>Vectore</code> of <code>FileEntry</code> objects.
   * The only information that is valid for the object will be the path,
   * until the {@link #sync() sync} method is called.
   */
  public static Vector getOpened() {
    return getOpened(null, true, false, -1, null);
  }

  /**
   * Returns a list of files that are open for edit or add. The list is
   * a <code>Vectore</code> of <code>FileEntry</code> objects.
   *<p>
   * Getting the stats for each <code>FileEntry</code> is a more expensive
   * operation. By default, this is not done. What this means is that the
   * only information that is valid for the object will be the path, until the
   * {@link #sync() sync} method is called.
   *
   * @param env  Source control environment to use.
   * @param stat  Indicates that file statistics should be gathered.
   */
  public static Vector getOpened(Env env, boolean stat) {
    return getOpened(env, stat, false, -1, null);
  }

  /**
   * Returns a list of files that are open for edit or add. The list is
   * a <code>Vector</code> of <code>FileEntry</code> objects.
   *<p>
   * Getting the stats for each <code>FileEntry</code> is a more expensive
   * operation. By default, this is not done. What this means is that the
   * only information that is valid for the object will be the path, until the
   * {@link #sync() sync} method is called.
   *<p>
   * If changelist is 0, all the changes in the default changelist are
   * returned. If it is less than 0, all opened files are returned.
   *
   * @param env  Source control environment to use.
   * @param stat  Indicates that file statistics should be gathered.
   * @param all  Indicates that all open files should be returned.
   * @param changelist  If non-zero, show files open in this changelist.
   * @param files If non-null, show files open in this <code>Vector</code> of <code>FileEntry</code> objects.
   */
  public static Vector getOpened(Env env, boolean stat, boolean all,
				 int changelist, Vector files) {
    Vector v = new Vector();
    String l, str;
    StringTokenizer st;
    int i = 0, cnt = 2;
    String[] cmd;
    FileEntry fent;
    if (all) cnt++;
    if (0 <= changelist) cnt += 2;
    if (null != files) cnt += files.size();
    cmd = new String[cnt];
    cmd[i++] = "p4";
    cmd[i++] = "opened";
    if (all) cmd[i++] = "-a";
    if (0 <= changelist) {
      cmd[i++] = "-c";
      cmd[i++] = (0 == changelist) ? "default" : String.valueOf(changelist);
    }
    if (null != files) {
      Enumeration en = files.elements();
      while (en.hasMoreElements()) {
	cmd[i++] = (String)en.nextElement();
      }
    }
    try {
      P4Process p = new P4Process(env);
      p.exec(cmd);
      while (null != (l = p.readLine())) {
	if (! l.startsWith("//")) { continue; }
	st = new StringTokenizer(l, "#");
	if (null == (str = st.nextToken())) { continue; }
	fent = new FileEntry(env, str);
	if (null == (str = st.nextToken("# \t"))) { continue; }
	fent.setHeadRev(Integer.valueOf(str).intValue());
	st.nextToken(" \t"); // Should be the dash here.
	if (null == (str = st.nextToken())) { continue; }
	fent.setHeadAction(str);
	if (null == (str = st.nextToken())) { continue; }
	if (str.equals("default")) {
	  fent.setHeadChange(-1);
	  st.nextToken(); // Change here.
	} else if (str.equals("change")) {
	  if (null == (str = st.nextToken())) { continue; } // Change number
	  fent.setHeadChange(Integer.valueOf(str).intValue());
	}
	if (null == (str = st.nextToken(" \t()"))) { continue; }
	fent.setHeadType(str);
	// Insertion sort...slow but effective.
	for (i = 0; i < v.size(); i++) {
	  if (((FileEntry)v.elementAt(i)).getHeadChange() >
	      fent.getHeadChange()) break;
	}
	v.insertElementAt(fent,i);
      }
      p.close();
    } catch (IOException ex) { Debug.out(Debug.ERROR, ex); }
    if (stat) {
      Enumeration en = v.elements();
      while (en.hasMoreElements()) {
	fent = (FileEntry)en.nextElement();
	fent.setEnv(env);
	fent.sync();
      }
    }
    return v;
  }

  /**
   * No-op. This makes no sense for a FileEntry.
   */
  public void commit() {
  }

  /**
   * @deprecated
   * @see #syncWorkspace(Env, String)
   */
  public String syncMySpace(Env env, String path) throws IOException {
    return FileEntry.syncWorkspace(env, path);
  }

  /**
   * Returns a <code>Vector</code> of <code>FileEntry</code> objects that
   * reflect what files were changed by the sync process. If path is null,
   * the entire workspace is synchronized to the head revision. The path may
   * contain wildcard characters, as with the command line 'p4 sync' command.
   *
   * @param env  Source control environment.
   * @param path Path to synchronize. May include wildcards.
   */
  public static Vector synchronizeWorkspace(Env env, String path) throws IOException {
    String [] cmd;
    if (null == path || path.trim().equals("")) {
      cmd = new String[2];
    } else {
      cmd = new String[3];
      cmd[2] = path;
    }
    cmd[0] = "p4";
    cmd[1] = "sync";

    String l;
    int pos1, pos2;
    Vector v = new Vector();
    FileEntry fent = null;
    try {
      P4Process p = new P4Process(env);
      p.exec(cmd);
      while (null != (l = p.readLine())) {
	fent = null;
	if (! l.startsWith("//")) {
	  continue;
	}
	pos1 = 0;
	if (-1 == (pos2 = l.indexOf('#'))) continue;
	fent = new FileEntry(env, l.substring(pos1,pos2));
	pos1 = pos2+1;
	if (-1 == (pos2 = l.indexOf(' ', pos1))) continue;
	try {
	  fent.setHeadRev(Integer.parseInt(l.substring(pos1, pos2)));
	} catch (Exception ex) {
	  fent = null;
	  continue;
	}
	pos1 = pos2+1;
	if (-1 != (pos2 = l.indexOf("updating ")) ||
	    -1 != (pos2 = l.indexOf("added as "))) {
	  fent.setClientPath(l.substring(pos2+9).trim());
	}
	if (null != fent) {
	  v.addElement(fent);
	  fent = null;
	}
      }
      p.close();
    } catch (IOException ex) { 
      Debug.out(Debug.ERROR, ex); 
      throw ex;
    }
    return v;
  }

  /**
   * Synchronizes the workspace.
   *
   * @param env  Source control environment.
   * @param path Path to synchronize. May include wildcards.
   */
  public static String syncWorkspace(Env env, String path) throws IOException {
	return FileEntry.syncWorkspace(env, path, false);
  }

  /**
   * Synchronizes the workspace.
   *
   * @param env  Source control environment.
   * @param path Path to synchronize. May include wildcards.
   * @param force Whether to do a force sync  
   */
  public static String syncWorkspace(Env env, String path, boolean force) throws IOException {
    String [] cmd;
    if (null == path || path.trim().equals("")) {
	  if(force) {
	    cmd = new String[3];
		cmd[2] = "-f";
	  } else {
	    cmd = new String[2];
	  }
    } else {
	  if(force) {
	    cmd = new String[4];
		cmd[2] = "-f";
        cmd[3] = path;
	  } else {
	    cmd = new String[3];
        cmd[2] = path;
	  }
    }
    cmd[0] = "p4";
    cmd[1] = "sync";

    String l, str = "";
    try {
      P4Process p = new P4Process(env);
      p.exec(cmd);
      while (null != (l = p.readLine())) {
		str += l + "\n";
      }
      p.close();
    } catch (IOException ex) { 
      Debug.out(Debug.ERROR, ex); 
      throw ex;
    }
    return str;
  }

  /**
   * Returns a <code>String</code> that contains this file's contents. This
   * only works well for text files.
   */  
  public String getFileContents() {
    return getFileContents(getEnv(),getDepotPath()); 
  }
  
  /**
   * Returns a <code>String</code> that contains this file's contents. This
   * only works well for text files.
   *
   * @param env  Source control environment.
   * @param path Path to the file. Must be specific. No wildcards.
   */
  public String getFileContents(Env env, String path) {
    String l;
    StringBuffer ret = null;
    String[] cmd = { "p4", "print", path };
    try {
      P4Process p = new P4Process(env);
      p.setRawMode(true);
      p.exec(cmd);
      while(null != (l = p.readLine())) {
        if (null == ret) {
            ret = new StringBuffer();
        } else if (l.startsWith("text: ")) {
          ret.append(l.substring(6));
	  if (! l.endsWith("\n")) ret.append('\n');
        }
      }
      if (null == ret) {
	ret = new StringBuffer();
      }

      if (0 != p.close()) {
	throw new IOException("P4 exited with and error:" + p.getExitCode());
      }
    } catch (IOException ex) { 
      Debug.out(Debug.ERROR, ex); 
    }
    file_content = ret.toString();
    return file_content;
  }
  
  public void sync() {
    String l;
    String[] cmd = { "p4", "fstat", "path" };
    if (null != depot_path) {
      cmd[2] = depot_path;
    } else if (null != client_path) {
      cmd[2] = client_path;
    } else {
      return;
    }  
    if (0 != head_rev) {
      cmd[2] += "#"+head_rev;
    }
    try {
      P4Process p = new P4Process(getEnv());
      p.exec(cmd);
      parseFstat(this, p, false);
      if (0 != p.close()) {
        throw new IOException("P4 exited with an error:" + p.getExitCode());
      }
    } catch (IOException ex) { Debug.out(Debug.ERROR, ex); }
  }

  /**
   * Useful method for parsing that lovely fstat format information.
   */
  private static Vector parseFstat(FileEntry fe, P4Process p, boolean igndel) {
    FileEntry nfe;
    String l;
    Vector v = new Vector();
    String dataname, datavalue;
    boolean multiple = false;

    if (null == p) return null;
    if (null == (nfe = fe)) nfe = new FileEntry(p.getEnv());
    
    while (null != (l = p.readLine())) {
      StringTokenizer tokes = new StringTokenizer(l," ");
      
      dataname = (String)(tokes.hasMoreElements()?tokes.nextElement():null);
      datavalue = (String)(tokes.hasMoreElements()?tokes.nextElement():null);
      if (dataname.equals("clientFile")) {
	nfe.setClientPath(datavalue);
      } else if (dataname.equals("depotFile")) {
	if (multiple) nfe = new FileEntry(p.getEnv());
        nfe.setDepotPath(datavalue);
	v.add( nfe );
	multiple = true;
      } else if (dataname.equals("headAction")) {
	nfe.setHeadAction(datavalue);
      } else if (dataname.equals("headChange")) {
	nfe.setHeadChange(new Integer(datavalue).intValue());
      } else if (dataname.equals("headRev")) {
	nfe.setHeadRev(new Integer(datavalue).intValue());
      } else if (dataname.equals("headType")) {
	nfe.setHeadType(datavalue);
      } else if (dataname.equals("headTime")) {
	nfe.setHeadTime(new Long(datavalue).longValue());
      } else if (dataname.equals("haveRev")) {
	nfe.setHaveRev(new Integer(datavalue).intValue());
      } else if (dataname.equals("action")) {
             
      } else if (dataname.equals("change")) {
           
      } else if (dataname.equals("unresolved")) {
          
      } else if (dataname.equals("otherOpen")) {
          
      } else if (dataname.equals("otherLock")) {
        
      } else if (dataname.equals("ourLock")) {
         
      }
    }
    return v;
  }

  public String toString() {
    return depot_path + "\n" + client_path + "\nothers: " + other_cnt;
  }

  public String toXML() {
    StringBuffer sb = new StringBuffer("<file><have rev=\"");
    sb.append(getHaveRev());
    sb.append("\"/><head rev=\"");
    sb.append(getHeadRev());
    sb.append("\" change=\"");
    sb.append(getHeadChange());
    sb.append("\" type=\"");
    sb.append(getHeadType());
    sb.append("\" action=\"");
    sb.append(getHeadAction());
    sb.append("\" time=\"");
    sb.append(getHeadTimeString());
    sb.append("\"/>");
    sb.append("<path type=\"depot\">");;
    sb.append(getDepotPath());
    sb.append("</path>");
    sb.append("<path type=\"client\">");;
    sb.append(getClientPath());
    sb.append("</path>");
    if (null != getDescription()) {
      sb.append("<description>");;
      sb.append(getDescription());
      sb.append("</description>");
    }
    sb.append("</file>");
    return sb.toString();
  }
}
# Change User Description Committed
#4 6430 Sivananda Poreddy Change the references to email, fix obsolete/redundant information
#3 6008 Sivananda Poreddy Fixing up the issues with author and version fields
#2 5993 Sivananda Poreddy Updating the API code changes made until perforce 2006.2 version
#1 5992 Sivananda Poreddy Creating my own branch from original base
//public/perforce/api/java/p4package/com/perforce/api/FileEntry.java
#8 1888 David Markley Ingegrated the changes into the public depot.
#7 1826 David Markley Incorporating minor changes in support of upcoming p4jsp package.
#6 1403 David Markley Fixed another problem with the way Change determined what files were opened.
#5 1400 David Markley Fixed a very troublesome Change defect.
#4 1387 David Markley Added the ability to reopen files, add them to changelists, and updated the
documentation to reflect these changes.
#3 1382 David Markley Publicly changed Env and FileEntry
#2 1334 David Markley Added more example code.
Added the Counter class.
Corrected the filelog methods for the FileEntry class.
#1 1043 David Markley Initial branch to public area.
//guest/david_markley/p4package/com/perforce/api/FileEntry.java
#3 1041 David Markley Corrected a problem with the environment propogation.
When a new object is created from
the listing methods (like Brang.getBranches(env)), each of them will have the same
environment: the one that was passed int.
#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.