/* * Copyright 2009 Perforce Software Inc., All Rights Reserved. */ package com.perforce.p4java.impl.mapbased.rpc.func.client; import java.io.FileDescriptor; import java.io.IOException; import java.nio.charset.Charset; import com.perforce.p4java.CharsetDefs; import com.perforce.p4java.Log; import com.perforce.p4java.exception.NullPointerError; import com.perforce.p4java.impl.mapbased.rpc.func.client.ClientMerge.ResolveChoice; import com.perforce.p4java.impl.mapbased.rpc.func.helper.MD5Digester; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcOutputStream; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; /** * Helper class for carrying useful merge state around during the various merge * operations defined in ClientMerge. Modeled somewhat on the C++ API's * clientmerge3.cc object, but tuned more to our more limited purposes. * Also includes support for two-way merge, but this is currently less-well * exercised and tested.<p> * * Note: not particularly thread-safe, nor intended to be. */ public class ClientMergeState { public static final String TRACE_PREFIX = "ClientMergeState"; public static final String DEFAULT_TMPFILE_PFX = "p4j"; public static final String DEFAULT_TMPFILE_SFX = ".mrg"; /** * True if merging is being done from an external stream, e.g. * with the resolveFile() method rather than resolveFilesAuto or whatever */ private boolean externalStreamMerge = false; /** * Only used if externalStreamMerge is true. */ private String externalTmpFilename = null; private String tmpDir = null; private String clientPath = null; private String baseName = null; private String theirName = null; private String yourName = null; private String baseTmpFilename = null; private String theirTmpFilename = null; private String yourTmpFilename = null; private String resultTmpFilename = null; private RpcPerforceFile baseTmpFile = null; private RpcPerforceFile theirTmpFile = null; private RpcPerforceFile yourTmpFile = null; private RpcPerforceFile resultTmpFile = null; private RpcOutputStream baseTmpFileStream = null; private RpcOutputStream yourTmpFileStream = null; private RpcOutputStream theirTmpFileStream = null; private RpcOutputStream resultTmpFileStream = null; private int yourChunks = 0; private int theirChunks = 0; private int conflictChunks = 0; private int bothChunks = 0; private int bits = 0; private int oldBits = 0; private boolean safeMerge = false; private boolean autoMerge = false; private RpcPerforceFileType clientType = null; private RpcPerforceFileType resultType = null; private Charset charset = null; private boolean showAll = false; protected boolean twoWayMerge = false; // Set true iff is a two-way merge protected String baseDigest = null; protected String yourDigest = null; protected String theirDigest = null; /** * @param externalStreamMerge set true if this is a merge from an external stream * @param tmpDir the name of a suitable directory for creating temporary files in */ protected ClientMergeState(String clientPath, boolean externalStreamMerge, RpcPerforceFileType clientType, RpcPerforceFileType resultType, String tmpDir, Charset charset) { this.externalStreamMerge = externalStreamMerge; this.clientPath = clientPath; this.clientType = clientType; this.resultType = resultType; this.charset = charset; if (tmpDir == null) { throw new NullPointerError( "null tmpdir passed to ClientMergeState constructor"); } this.tmpDir = tmpDir; } /** * Open and / or create the necessary files for this merge. The "yours" * file is the original client file, and doesn't need opening. The rest * are opened as tmp files in the system tmp directory; this isn't exactly * the same as the C++ API's behaviour (which opens them in the target directory) * but it should be fairly safe.<p> * * Note that the file types for each file are copied from the C++ API usage; * I'm not entirely sure this arrangement always make sense... * * @throws IOException if there's been a problem opening any of the files. */ protected void openMergeFiles(boolean isUnicodeServer) throws IOException { this.baseTmpFilename = RpcPerforceFile.createTempFileName(this.tmpDir); this.baseTmpFile = new RpcPerforceFile(this.baseTmpFilename, clientType); this.baseTmpFileStream = new RpcOutputStream(this.baseTmpFile, this.charset, isUnicodeServer, false); this.theirTmpFilename = RpcPerforceFile.createTempFileName(this.tmpDir); this.theirTmpFile = new RpcPerforceFile(this.theirTmpFilename, resultType); this.theirTmpFileStream = new RpcOutputStream(this.theirTmpFile, this.charset, isUnicodeServer, false); this.yourTmpFilename = this.clientPath; this.yourTmpFile = new RpcPerforceFile(this.yourTmpFilename, clientType); this.resultTmpFilename = RpcPerforceFile.createTempFileName(this.tmpDir); this.resultTmpFile = new RpcPerforceFile(this.resultTmpFilename, resultType); this.resultTmpFileStream = new RpcOutputStream(this.resultTmpFile, this.charset, isUnicodeServer, false); } protected void writeMarker(String markerString) throws IOException { if (checkStream(resultTmpFileStream)) { // Convert the marker to UTF-8 since writeConverted assumes a UTF-8 // to local charset conversion resultTmpFileStream.writeConverted(markerString .getBytes(CharsetDefs.UTF8_NAME)); } else { throw new NullPointerError("bad stream in writeResultChunk"); } } protected void writeBaseChunk(byte[] bytes) throws IOException { if (checkStream(baseTmpFileStream)) { baseTmpFileStream.writeConverted(bytes); } else { throw new NullPointerError("bad stream in writeBaseChunk"); } } protected void writeTheirChunk(byte[] bytes) throws IOException { if (checkStream(theirTmpFileStream)) { theirTmpFileStream.writeConverted(bytes); } else { throw new NullPointerError("bad stream in writeTheirChunk"); } } protected void writeYourChunk(byte[] bytes) throws IOException { // We don't need to write anything. } protected void writeResultChunk(byte[] bytes) throws IOException { if (checkStream(resultTmpFileStream)) { resultTmpFileStream.writeConverted(bytes); } else { throw new NullPointerError("bad stream in writeResultChunk"); } } protected boolean finishMerge(ResolveChoice choice) throws IOException { boolean succeeded = false; try { if (checkStream(resultTmpFileStream)) { resultTmpFileStream.close(); } if (checkStream(baseTmpFileStream)) { baseTmpFileStream.close(); } if (checkStream(theirTmpFileStream)) { theirTmpFileStream.close(); } // Move correct file to target, if needed; switch (choice) { case THEIRS: // Move theirs to yours... succeeded = this.theirTmpFile.renameTo(this.yourTmpFile); break; case MERGED: // Move result to yours... succeeded = this.resultTmpFile.renameTo(this.yourTmpFile); break; case EDIT: succeeded = this.resultTmpFile.renameTo(this.yourTmpFile); break; default: succeeded = true; break; } } finally { try { // Try not to delete the target file here... if (this.baseTmpFile != null) this.baseTmpFile.delete(); if (this.theirTmpFile != null) this.theirTmpFile.delete(); if (this.resultTmpFile != null) this.resultTmpFile.delete(); } catch (Throwable thr) { Log.warn("unexpected exception in closeMerge: " + thr.getLocalizedMessage()); Log.exception(thr); } } return succeeded; } protected String getMergeDigestString() { // If the file has conflicts, do not report merge digest, otherwise // return result digest: if (conflictChunks == 0) { return new MD5Digester().digestFileAs32ByteHex(this.resultTmpFile, this.charset, true); } return null; } protected String getTheirDigestString() { return new MD5Digester().digestFileAs32ByteHex(this.theirTmpFile, this.charset, true); } protected String getYourDigestString() { return new MD5Digester().digestFileAs32ByteHex(this.yourTmpFile, this.charset, true); } protected int incrYourChunks() { return ++yourChunks; } protected int theirYourChunks() { return ++theirChunks; } protected int incrConflictChunks() { return ++conflictChunks; } protected int incrTheirChunks() { return ++theirChunks; } protected int incrBothChunks() { return ++bothChunks; } protected String getClientPath() { return this.clientPath; } protected void setClientPath(String clientPath) { this.clientPath = clientPath; } protected String getBaseName() { return this.baseName; } protected void setBaseName(String baseName) { this.baseName = baseName; } protected String getTheirName() { return this.theirName; } protected void setTheirName(String theirName) { this.theirName = theirName; } protected String getYourName() { return this.yourName; } protected void setYourName(String yourName) { this.yourName = yourName; } protected String getBaseTmpFilename() { return this.baseTmpFilename; } protected void setBaseTmpFilename(String baseTmpFilename) { this.baseTmpFilename = baseTmpFilename; } protected String getTheirTmpFilename() { return this.theirTmpFilename; } protected void setTheirTmpFilename(String theirTmpFilename) { this.theirTmpFilename = theirTmpFilename; } protected String getYourTmpFilename() { return this.yourTmpFilename; } protected void setYourTmpFilename(String yourTmpFilename) { this.yourTmpFilename = yourTmpFilename; } protected RpcPerforceFile getBaseTmpFile() { return this.baseTmpFile; } protected void setBaseTmpFile(RpcPerforceFile baseTmpFile) { this.baseTmpFile = baseTmpFile; } protected RpcPerforceFile getTheirTmpFile() { return this.theirTmpFile; } protected void setTheirTmpFile(RpcPerforceFile theirTmpFile) { this.theirTmpFile = theirTmpFile; } protected RpcPerforceFile getYourTmpFile() { return this.yourTmpFile; } protected void setYourTmpFile(RpcPerforceFile yourTmpFile) { this.yourTmpFile = yourTmpFile; } protected RpcOutputStream getBaseTmpFileStream() { return this.baseTmpFileStream; } protected void setBaseTmpFileStream(RpcOutputStream baseTmpFileStream) { this.baseTmpFileStream = baseTmpFileStream; } protected RpcOutputStream getYourTmpFileStream() { return this.yourTmpFileStream; } protected void setYourTmpFileStream(RpcOutputStream yourTmpFileStream) { this.yourTmpFileStream = yourTmpFileStream; } protected RpcOutputStream getTheirTmpFileStream() { return this.theirTmpFileStream; } protected void setTheirTmpFileStream(RpcOutputStream theirTmpFileStream) { this.theirTmpFileStream = theirTmpFileStream; } protected int getYourChunks() { return this.yourChunks; } protected void setYourChunks(int yourChunks) { this.yourChunks = yourChunks; } protected int getTheirChunks() { return this.theirChunks; } protected void setTheirChunks(int theirChunks) { this.theirChunks = theirChunks; } protected int getConflictChunks() { return this.conflictChunks; } protected void setConflictChunks(int conflictChunks) { this.conflictChunks = conflictChunks; } protected int getBothChunks() { return this.bothChunks; } protected void setBothChunks(int bothChunks) { this.bothChunks = bothChunks; } protected int getBits() { return this.bits; } protected void setBits(int bits) { this.bits = bits; } protected int getOldBits() { return this.oldBits; } protected void setOldBits(int oldBits) { this.oldBits = oldBits; } protected String getTmpDir() { return this.tmpDir; } protected void setTmpDir(String tmpDir) { this.tmpDir = tmpDir; } protected boolean isExternalStreamMerge() { return this.externalStreamMerge; } protected void setExternalStreamMerge(boolean externalStreamMerge) { this.externalStreamMerge = externalStreamMerge; } protected String getExternalTmpFilename() { return this.externalTmpFilename; } protected void setExternalTmpFilename(String externalTmpFilename) { this.externalTmpFilename = externalTmpFilename; } protected RpcPerforceFile getResultTmpFile() { return this.resultTmpFile; } protected void setResultTmpFile(RpcPerforceFile resultTmpFile) { this.resultTmpFile = resultTmpFile; } protected RpcOutputStream getResultTmpFileStream() { return this.resultTmpFileStream; } protected void setResultTmpFileStream(RpcOutputStream resultTmpFileStream) { this.resultTmpFileStream = resultTmpFileStream; } private boolean checkStream(RpcOutputStream stream) { try { if (stream != null) { FileDescriptor fd = stream.getFD(); if (fd != null) { if (fd.valid()) { return true; } } } } catch (IOException ioexc) { // Ignore for now other than to log it... Log.exception(ioexc); } return false; } protected boolean isSafeMerge() { return this.safeMerge; } protected void setSafeMerge(boolean safeMerge) { this.safeMerge = safeMerge; } protected boolean isAutoMerge() { return this.autoMerge; } protected void setAutoMerge(boolean autoMerge) { this.autoMerge = autoMerge; } protected boolean isShowAll() { return this.showAll; } protected void setShowAll(boolean showAll) { this.showAll = showAll; } protected boolean isTwoWayMerge() { return twoWayMerge; } protected void setTwoWayMerge(boolean twoWayMerge) { this.twoWayMerge = twoWayMerge; } protected String getBaseDigest() { return baseDigest; } protected void setBaseDigest(String baseDigest) { this.baseDigest = baseDigest; } protected String getYourDigest() { return yourDigest; } protected void setYourDigest(String yourDigest) { this.yourDigest = yourDigest; } protected String getTheirDigest() { return theirDigest; } protected void setTheirDigest(String theirDigest) { this.theirDigest = theirDigest; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19903 | stuartrowe |
Branching //guest/perforce_software/p4java/... to //guest/stuartrowe/p4java/... |
||
//guest/perforce_software/p4java/r14.1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/func/client/ClientMergeState.java | |||||
#1 | 12541 | Matt Attaway | Initial add of the 14.1 p4java source code |