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:david@markley.cc">David Markley</a> * @version $Date: 2002/06/05 $ $Revision: #8 $ */ 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("<"); } else if('>' == tmp) { strbuf.append(">"); } 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 force * If true, the file will be opened for edit even if it isn't the * most recent version. * @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 force, boolean lock, Change chng) throws Exception { if(sync) { FileEntry.syncWorkspace(env, path); } FileEntry fent = new FileEntry(env, path); fent.openForEdit(force, lock, chng); return fent; } /** * Opens this file for edit. * * @see #openForEdit(Env, String, boolean, boolean, boolean, Change) */ public void openForEdit() throws Exception { openForEdit(true, false, null); } /** * Opens this file for edit. * * @see #openForEdit(Env, String, boolean, boolean, boolean, Change) */ public void openForEdit(boolean force, boolean lock) throws Exception { openForEdit(force, lock, null); } /** * Opens this file for edit. * * @see #openForEdit(Env, String, boolean, boolean, boolean, Change) */ public void openForEdit(boolean force, boolean lock, Change chng) throws Exception { String[] cmd1; String[] cmd2; String l; P4Process p; int i = 0; sync(); if(force) { cmd1 = new String[4]; cmd1[2] = "-f"; } else { cmd1 = new String[3]; } cmd1[0] = "p4"; cmd1[1] = "sync"; cmd1[cmd1.length - 1] = getDepotPath(); cmd2 = new String[(null == chng) ? 3 : 5]; cmd2[i++] = "p4"; cmd2[i++] = "edit"; if(null != chng) { cmd2[i++] = "-c"; cmd2[i++] = String.valueOf(chng.getNumber()); } cmd2[i++] = getClientPath(); 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(); 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(); } /** * 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 { String[] cmd; if(null == path || path.trim().equals("")) { cmd = new String[3]; cmd[2] = path; } else { cmd = new String[2]; } 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(); } }