/* Copyright 2004-2014 Jim Voris * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qumasoft.server; import com.qumasoft.qvcslib.AbstractProjectProperties; import com.qumasoft.qvcslib.AccessList; import com.qumasoft.qvcslib.ArchiveAttributes; import com.qumasoft.qvcslib.CompareFilesEditInformation; import com.qumasoft.qvcslib.CompressionFactory; import com.qumasoft.qvcslib.Compressor; import com.qumasoft.qvcslib.LabelInfo; import com.qumasoft.qvcslib.LogFileHeaderInfo; import com.qumasoft.qvcslib.LogFileReadException; import com.qumasoft.qvcslib.LogfileInfo; import com.qumasoft.qvcslib.MutableByteArray; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.QVCSException; import com.qumasoft.qvcslib.QumaAssert; import com.qumasoft.qvcslib.RevisionDescriptor; import com.qumasoft.qvcslib.RevisionHeader; import com.qumasoft.qvcslib.RevisionInformation; import com.qumasoft.qvcslib.Utility; import com.qumasoft.qvcslib.WorkFile; import com.qumasoft.qvcslib.commandargs.CheckInCommandArgs; import com.qumasoft.qvcslib.commandargs.CheckOutCommandArgs; import com.qumasoft.qvcslib.commandargs.CreateArchiveCommandArgs; import com.qumasoft.qvcslib.commandargs.GetRevisionCommandArgs; import com.qumasoft.qvcslib.commandargs.LabelRevisionCommandArgs; import com.qumasoft.qvcslib.commandargs.LockRevisionCommandArgs; import com.qumasoft.qvcslib.commandargs.SetRevisionDescriptionCommandArgs; import com.qumasoft.qvcslib.commandargs.UnLabelRevisionCommandArgs; import com.qumasoft.qvcslib.commandargs.UnlockRevisionCommandArgs; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; /** * The logfile implementation class. * * @author Jim Voris */ public final class LogFileImpl { // Create our logger object private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server"); private static final String BRACKET_TO = "] to ["; private RandomAccessFile inStream; /** * Flag to indicate if file is already open */ private boolean isOpen; /** * Flag to indicate whether the header information has been read */ private boolean isHeaderInfoReadFlag; private boolean isRevisionInfoReadFlag; private RevisionInformation revisionInfo; private final String fullArchiveFileName; private final String shortArchiveName; private final java.io.File archiveFile; private final String tempFileName; private final java.io.File tempFile; private final String oldArchiveFileName; private final java.io.File oldArchiveFile; private final String shortWorkfileName; private LogFileHeaderInfo headerInfo; private AccessList accessList = null; private AccessList modifierList = null; private boolean mustReadArchiveFileFlag = true; private LogfileInfo logfileInfo = null; RevisionHeader getRevisionHeader(int index) { RevisionHeader revisionHeader = null; if (index < getRevisionCount()) { revisionHeader = revisionInfo.getRevisionHeader(index); } return revisionHeader; } RevisionInformation getRevisionInformation() { if (!isArchiveInformationRead()) { readInformation(); } return revisionInfo; } int getFileID() throws QVCSException { int fileID = -1; try { if (!isArchiveInformationRead()) { readInformation(); } fileID = getLogFileHeaderInfo().getSupplementalHeaderInfo().getFileID(); } catch (NullPointerException e) { LOGGER.log(Level.WARNING, "Failed to get fileID for: " + getFileName()); throw e; } return fileID; } void setFileIDAndRemoveViewAndBranchLabels(int fileID) throws QVCSException { try { if (!isArchiveInformationRead()) { readInformation(); } getLogFileHeaderInfo().getSupplementalHeaderInfo().setFileID(fileID); removeViewAndBranchLabels(); updateHeaderOnDisk(); } catch (NullPointerException e) { LOGGER.log(Level.WARNING, "Failed to set fileID for: " + getFileName()); throw e; } } LogfileInfo getLogfileInfo() throws QVCSException { if (logfileInfo == null) { logfileInfo = new LogfileInfo(getLogFileHeaderInfo(), getRevisionInformation(), getFileID(), getFileName()); } return logfileInfo; } synchronized boolean isArchiveInformationRead() { return isHeaderInfoReadFlag && isRevisionInfoReadFlag; } boolean isValidArchive() { if (!isArchiveInformationRead()) { readInformation(); } return isArchiveInformationRead(); } boolean getIsObsolete() { if (!isArchiveInformationRead()) { readInformation(); } return headerInfo.getIsObsolete(); } int getRevisionCount() { int revisionCount = -1; try { if (!isArchiveInformationRead()) { readInformation(); } revisionCount = headerInfo.getRevisionCount(); } catch (NullPointerException e) { LOGGER.log(Level.WARNING, "Failed to get revision count for: " + getFileName()); throw e; } return revisionCount; } String getShortWorkfileName() { return shortWorkfileName; } String getShortArchiveName() { return shortArchiveName; } int getLockCount() { if (!isArchiveInformationRead()) { readInformation(); } return headerInfo.getLogFileHeader().lockCount(); } File getFile() { return archiveFile; } String getFileName() { return fullArchiveFileName; } File getOldFile() { return oldArchiveFile; } File getTempFile() { return tempFile; } String getLockedByString() throws QVCSException { return getLogfileInfo().getLockedByString(); } /** * Return the user name(s) that hold any locks on this archive. The format of the returned string is for use within the GUI. If * there are multiple lockers, they are all returned. */ String getLockedByUser() { String returnString; if (!isArchiveInformationRead()) { readInformation(); } if (isArchiveInformationRead()) { if (headerInfo.getLogFileHeader().lockCount() > 0) { StringBuilder lockerString = new StringBuilder(); int revisionCount = headerInfo.getRevisionCount(); int lockCount = headerInfo.getLogFileHeader().lockCount(); for (int i = 0, j = 0; (i < revisionCount) && (j < lockCount); i++) { RevisionHeader revHeader = revisionInfo.getRevisionHeader(i); if (revHeader.isLocked()) { j++; lockerString.append(revHeader.getRevisionString()).append("-").append(indexToUsername(revHeader.getLockerIndex())); } } returnString = lockerString.toString(); } else { returnString = ""; } } else { returnString = ""; } return returnString; } String getWorkfileInLocation() { if (!isArchiveInformationRead()) { readInformation(); } String returnString = ""; if (headerInfo.getLogFileHeader().lockCount() > 0) { returnString = headerInfo.getWorkfileName(); } return returnString; } java.util.Date getLastCheckInDate() { RevisionHeader defaultRevision = getDefaultRevisionHeader(); return defaultRevision.getCheckInDate(); } String getLastEditBy() { RevisionHeader defaultRevision = getDefaultRevisionHeader(); return indexToUsername(defaultRevision.getCreatorIndex()); } RevisionHeader getDefaultRevisionHeader() { RevisionHeader returnHeader = null; if (!isArchiveInformationRead()) { readInformation(); } if (isArchiveInformationRead()) { // If the default branch isn't the trunk, then we have some work to do... if (headerInfo.getLogFileHeader().defaultDepth() > 0) { int revisionCount = headerInfo.getRevisionCount(); RevisionDescriptor defaultDescriptor = headerInfo.getDefaultRevisionDescriptor(); String defaultBranchString = defaultDescriptor.toString(); for (int i = 0; i < revisionCount; i++) { RevisionHeader revHeader = revisionInfo.getRevisionHeader(i); if (revHeader.getDepth() == defaultDescriptor.getElementCount() - 1) { if (revHeader.isTip()) { String revisionString = revHeader.getRevisionString(); int lastDot = revisionString.lastIndexOf('.'); String truncatedRevisionString = revisionString.substring(0, lastDot); if (truncatedRevisionString.compareToIgnoreCase(defaultBranchString) == 0) { returnHeader = revHeader; break; } } } } } else { // The default branch is the trunk. Things are simple here. returnHeader = revisionInfo.getRevisionHeader(0); } } else { returnHeader = null; } return returnHeader; } String indexToUsername(int index) { if (!isArchiveInformationRead()) { readInformation(); } return modifierList.indexToUser(index); } /** * This recursive routine will fetch the requested revision into the output file. This routine does handle compressed revisions, * but it does not perform keyword expansion. */ synchronized boolean fetchRevision(RevisionHeader revisionHeader, String outputFilename, boolean recurseFlag, MutableByteArray processedBuffer) { boolean bRetVal = false; FileOutputStream outStream = null; try { if ((revisionHeader.getDepth() == 0) && revisionHeader.isTip()) { // Read the archive file to retrieve the revision. byte[] unCompressedRevisionData; byte[] revisionData = new byte[revisionHeader.getRevisionSize()]; inStream.seek(revisionHeader.getRevisionDataStartPosition()); readRevisionData(revisionData); // Decompress the buffer if we need to. if (revisionHeader.isCompressed()) { unCompressedRevisionData = deCompressRevisionData(revisionHeader, revisionData); } else { unCompressedRevisionData = revisionData; } processedBuffer.setValue(unCompressedRevisionData); bRetVal = true; } else { // They are requesting an older revision. if (revisionHeader.getParentRevisionHeader() == null) { // We've reached the tip revision for the requested revision. // but we're recursing and this must be a non-tip revision. // All non-tip revisions MUST have a parent, so to get here // is a boo-boo. QumaAssert.isTrue(false); } else { // We're still working our way to the tip revision. bRetVal = fetchRevision(revisionHeader.getParentRevisionHeader(), outputFilename, true, processedBuffer); // We got our parent. Now apply our edits to our parent to get the result. if (bRetVal) { byte[] editBuffer = new byte[revisionHeader.getRevisionSize()]; byte[] uncompressedEditBuffer; inStream.seek(revisionHeader.getRevisionDataStartPosition()); readRevisionData(editBuffer); if (revisionHeader.isCompressed()) { uncompressedEditBuffer = deCompressRevisionData(revisionHeader, editBuffer); } else { uncompressedEditBuffer = editBuffer; } byte[] afterEdits = applyEdits(uncompressedEditBuffer, processedBuffer.getValue()); processedBuffer.setValue(afterEdits); bRetVal = true; } } } } catch (IOException | QVCSException e) { LOGGER.log(Level.WARNING, "Failed to fetch revision: " + revisionHeader.getRevisionString()); LOGGER.log(Level.WARNING, e.getLocalizedMessage()); LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); bRetVal = false; } // Write the result. if (!recurseFlag && bRetVal && (outputFilename != null)) { boolean streamIsOpen = false; try { WorkFile outputFile = new WorkFile(outputFilename); // Overwrite if it is write protected. if (outputFile.exists()) { if (!outputFile.canWrite()) { outputFile.setReadWrite(); } } outStream = new FileOutputStream(outputFile); streamIsOpen = true; Utility.writeDataToStream(processedBuffer.getValue(), outStream); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); bRetVal = false; } finally { if (streamIsOpen) { try { if (outStream != null) { outStream.close(); } } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } return bRetVal; } @SuppressWarnings("LoggerStringConcat") private byte[] applyEdits(byte[] edits, byte[] originalData) throws QVCSException { DataInputStream editStream = new DataInputStream(new ByteArrayInputStream(edits)); CompareFilesEditInformation editInfo = new CompareFilesEditInformation(); byte[] editedBuffer = new byte[edits.length + originalData.length]; // It can't be any bigger than this. byte[] returnedBuffer = null; int inIndex = 0; int outIndex = 0; int deletedBytesCount; int insertedBytesCount; int bytesTillChange = 0; try { while (editStream.available() > 0) { editInfo.read(editStream); bytesTillChange = (int) editInfo.getSeekPosition() - inIndex; System.arraycopy(originalData, inIndex, editedBuffer, outIndex, bytesTillChange); inIndex += bytesTillChange; outIndex += bytesTillChange; deletedBytesCount = (int) editInfo.getDeletedBytesCount(); insertedBytesCount = (int) editInfo.getInsertedBytesCount(); switch (editInfo.getEditType()) { case CompareFilesEditInformation.QVCS_EDIT_DELETE: /* * Delete input */ // Just skip over deleted bytes inIndex += deletedBytesCount; break; case CompareFilesEditInformation.QVCS_EDIT_INSERT: /* * Insert edit lines */ editStream.read(editedBuffer, outIndex, insertedBytesCount); outIndex += insertedBytesCount; break; case CompareFilesEditInformation.QVCS_EDIT_REPLACE: /* * Replace input line with edit line. * First skip over the bytes to be replaced, then copy the replacing bytes from the edit file to the output * file. */ inIndex += deletedBytesCount; editStream.read(editedBuffer, outIndex, insertedBytesCount); outIndex += insertedBytesCount; break; default: continue; } } // Copy the rest of the input "file" to the output "file". int remainingBytes = originalData.length - inIndex; if (remainingBytes > 0) { System.arraycopy(originalData, inIndex, editedBuffer, outIndex, remainingBytes); outIndex += remainingBytes; } returnedBuffer = new byte[outIndex]; System.arraycopy(editedBuffer, 0, returnedBuffer, 0, outIndex); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); LOGGER.log(Level.WARNING, " editInfo.seekPosition: " + editInfo.getSeekPosition() + " originalData.length: " + originalData.length + " inIndex: " + inIndex + " editedBuffer.length: " + editedBuffer.length + " outIndex: " + outIndex + " bytesTillChange: " + bytesTillChange); LOGGER.log(Level.WARNING, e.getLocalizedMessage()); throw new QVCSException("Internal error!! for " + getShortWorkfileName()); } finally { try { editStream.close(); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } return returnedBuffer; } /** * Find the requested revision within the archive. Return true if we find that revision; also return the index of that revision. * If the revision is not found, return false. */ synchronized boolean findRevision(String revision, AtomicInteger revisionIndex) { QumaAssert.isTrue(isHeaderInfoReadFlag); QumaAssert.isTrue(isRevisionInfoReadFlag); boolean bRetVal = false; int revisionCount = headerInfo.getRevisionCount(); for (int i = 0; i < revisionCount; i++) { RevisionHeader revHeader = revisionInfo.getRevisionHeader(i); String revisionString = revHeader.getRevisionString(); if (revision.compareTo(revisionString) == 0) { bRetVal = true; revisionIndex.set(i); break; } } return bRetVal; } private byte[] deCompressRevisionData(RevisionHeader revisionHeader, byte[] revisionData) { Compressor compressor = CompressionFactory.getCompressor(revisionHeader.getCompressionHeader()); return compressor.expand(revisionHeader.getCompressionHeader(), revisionData); } @Override public String toString() { StringBuilder returnString = new StringBuilder(); returnString.append("QVCS archive name:\t").append(fullArchiveFileName).append(headerInfo.toString()); return returnString.toString(); } /** * Create a logfile implementation instance. * @param fullArchiveFilename the full path to the file that we use as the archive file. */ public LogFileImpl(String fullArchiveFilename) { isOpen = false; isHeaderInfoReadFlag = false; isRevisionInfoReadFlag = false; // Set the filename fullArchiveFileName = fullArchiveFilename; archiveFile = new File(fullArchiveFileName); // Set the name of the temp file for the archive tempFileName = fullArchiveFileName + QVCSConstants.QVCS_ARCHIVE_TEMPFILE_SUFFIX; tempFile = new java.io.File(tempFileName); // Set the name of the old file for the archive oldArchiveFileName = fullArchiveFileName + QVCSConstants.QVCS_ARCHIVE_OLDFILE_SUFFIX; oldArchiveFile = new java.io.File(oldArchiveFileName); // Figure out the short workfile name shortWorkfileName = Utility.convertArchiveNameToShortWorkfileName(fullArchiveFilename); // Figure out the short archive name shortArchiveName = Utility.convertWorkfileNameToShortArchiveName(shortWorkfileName); } /** * open the archive file. */ synchronized boolean open() { if (!isOpen) { // Make sure the file exists if (archiveFile.exists()) { try { inStream = new RandomAccessFile(archiveFile, "r"); isOpen = true; } catch (FileNotFoundException e) { isOpen = false; LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } return isOpen; } protected synchronized void close() { if (isOpen) { try { inStream.close(); isOpen = false; } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } synchronized boolean reReadInformation() { isHeaderInfoReadFlag = false; isRevisionInfoReadFlag = false; logfileInfo = null; return readInformation(); } synchronized boolean readInformation() { boolean bRetValue = true; try { if (open()) { bRetValue = readHeaderInformation(); if (bRetValue) { bRetValue = readRevisionInformation(); } } } catch (Exception e) { bRetValue = false; } finally { close(); } return bRetValue; } /** * read revision information for this archive */ private synchronized boolean readRevisionInformation() { try { if (open() && !isHeaderInfoReadFlag) { headerInfo = new LogFileHeaderInfo(); headerInfo.read(inStream); } if (isHeaderInfoReadFlag && !isRevisionInfoReadFlag) { revisionInfo = new RevisionInformation(headerInfo.getRevisionCount(), new AccessList(headerInfo.getAccessList()), new AccessList(headerInfo.getModifierList())); revisionInfo.read(inStream); isRevisionInfoReadFlag = true; } } catch (LogFileReadException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); isRevisionInfoReadFlag = false; } return isRevisionInfoReadFlag; } /** * read header information for this archive */ private synchronized boolean readHeaderInformation() { try { if (open() && !isHeaderInfoReadFlag) { headerInfo = new LogFileHeaderInfo(); isHeaderInfoReadFlag = headerInfo.read(inStream); if (isHeaderInfoReadFlag) { accessList = new AccessList(headerInfo.getAccessList()); modifierList = new AccessList(headerInfo.getModifierList()); } } } catch (LogFileReadException e) { LOGGER.log(Level.WARNING, "Failed to read header for: " + getFileName() + ". Caught exception: " + e.getClass().toString() + ": " + e.getLocalizedMessage()); LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); isHeaderInfoReadFlag = false; } return isHeaderInfoReadFlag; } LogFileHeaderInfo getLogFileHeaderInfo() { if (!isArchiveInformationRead()) { readInformation(); } return headerInfo; } void setHeaderInfo(LogFileHeaderInfo hdrInfo) throws QVCSException { if (isArchiveInformationRead()) { throw new QVCSException("Invalid use of setHeaderInfo method!!"); } else { this.headerInfo = hdrInfo; mustReadArchiveFileFlag = false; } } boolean createArchive(CreateArchiveCommandArgs commandLineArgs, AbstractProjectProperties projectProperties, String filename) throws QVCSException { boolean retVal = false; // <editor-fold> Object[] args = new Object[4]; args[0] = this; args[1] = filename; args[2] = projectProperties; args[3] = commandLineArgs; // </editor-fold> LogFileOperationCreateArchive createArchiveOperation = new LogFileOperationCreateArchive(args); if (createArchiveOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean renameArchive(String userName, String appendedPath, String oldShortWorkfileName, String newShortWorkfilename, final Date date) { boolean retVal = false; try { // See if we can rename the archive file... String archiveDirectoryName = fullArchiveFileName.substring(0, fullArchiveFileName.length() - shortArchiveName.length()); String newShortArchiveFilename = Utility.convertWorkfileNameToShortArchiveName(newShortWorkfilename); String newFullArchiveFilename = archiveDirectoryName + newShortArchiveFilename; File newArchiveFile = new File(newFullArchiveFilename); if (!newArchiveFile.exists()) { // We need to create new revision (with no changes) that marks // the boundary for this rename. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the rename // operation. String checkInComment = QVCSConstants.QVCS_INTERNAL_FILE_RENAMED_FROM + oldShortWorkfileName + BRACKET_TO + newShortWorkfilename + "]."; LogFileOperationRenameArchive renameArchiveOperation = new LogFileOperationRenameArchive(this, userName, checkInComment, appendedPath, oldShortWorkfileName, date); if (renameArchiveOperation.execute()) { retVal = reReadInformation(); } // Rename the archive file... if (!archiveFile.renameTo(newArchiveFile)) { LOGGER.log(Level.WARNING, "Failed to rename [" + getFileName() + BRACKET_TO + newFullArchiveFilename + "]"); } } else { LOGGER.log(Level.WARNING, "Rename of archive failed because new archive name already exists: [" + newFullArchiveFilename + "]"); } } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } return retVal; } boolean moveArchive(String userName, String appendedPath, ArchiveDirManager targetArchiveDirManager, String shortWorkfilename, final Date date) { boolean retVal = false; try { // Make sure we have a fileID... int fileID = getFileID(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "File id in moveArchive is: [" + fileID + "]"); } // See if we can rename the archive file... String newFullArchiveFilename = targetArchiveDirManager.getArchiveDirectoryName() + File.separator + shortArchiveName; File newArchiveFile = new File(newFullArchiveFilename); if (!newArchiveFile.exists()) { // We need to create new revision (with no changes) that marks // the boundary for this rename. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the move // operation. String oldFullAppendedWorkfileName; if (appendedPath.length() > 0) { oldFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { oldFullAppendedWorkfileName = shortWorkfilename; } String newFullAppendedWorkfileName; if (targetArchiveDirManager.getAppendedPath().length() > 0) { newFullAppendedWorkfileName = targetArchiveDirManager.getAppendedPath() + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { newFullAppendedWorkfileName = shortWorkfilename; } String checkInComment = QVCSConstants.QVCS_INTERNAL_FILE_MOVED_FROM + oldFullAppendedWorkfileName + BRACKET_TO + newFullAppendedWorkfileName + "]."; LogFileOperationMoveArchive moveArchiveOperation = new LogFileOperationMoveArchive(this, userName, checkInComment, appendedPath, shortWorkfilename, date); if (moveArchiveOperation.execute()) { retVal = reReadInformation(); } // Move the archive file. if (retVal) { if (!archiveFile.renameTo(newArchiveFile)) { LOGGER.log(Level.WARNING, "Failed to move [" + getFileName() + BRACKET_TO + newFullArchiveFilename + "]"); retVal = false; } } else { LOGGER.log(Level.WARNING, "Move of archive failed. Failed to re-read archive information for: [" + oldFullAppendedWorkfileName + "]"); } } else { LOGGER.log(Level.WARNING, "Move of archive failed because new archive name already exists: [" + newFullArchiveFilename + "]"); } } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } return retVal; } boolean deleteArchiveOnTranslucentBranch(String userName, String appendedPath, String shortWorkfilename, final Date date, final String branchLabel, ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) { boolean retVal = false; // Check in a new revision (creating a file branch if required) marking the delete. String branchTipRevisionString = archiveInfoForTranslucentBranch.getBranchTipRevisionString(); if (branchTipRevisionString.length() > 0) { try { // We need to create new revision (with no changes) that marks // the boundary for this delete. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the delete // operation. String oldFullAppendedWorkfileName; if (appendedPath.length() > 0) { oldFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { oldFullAppendedWorkfileName = shortWorkfilename; } String checkInComment = "Deleted: '" + oldFullAppendedWorkfileName + "' to cemetery."; LogFileOperationDeleteArchiveOnTranslucentBranch deleteArchiveOperation = new LogFileOperationDeleteArchiveOnTranslucentBranch(archiveInfoForTranslucentBranch, userName, checkInComment, appendedPath, shortWorkfilename, date, branchLabel); retVal = deleteArchiveOperation.execute(); } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } } return retVal; } boolean moveArchiveOnTranslucentBranch(String userName, String appendedPath, ArchiveDirManagerForTranslucentBranch targetArchiveDirManager, String shortWorkfilename, final Date date, final String branchLabel, ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) { boolean retVal = false; // Check in a new revision (creating a file branch if required) marking the move. String branchTipRevisionString = archiveInfoForTranslucentBranch.getBranchTipRevisionString(); if (branchTipRevisionString.length() > 0) { try { // We need to create new revision (with no changes) that marks // the boundary for this move. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the move // operation. String oldFullAppendedWorkfileName; if (appendedPath.length() > 0) { oldFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { oldFullAppendedWorkfileName = shortWorkfilename; } String newFullAppendedWorkfileName; if (targetArchiveDirManager.getAppendedPath().length() > 0) { newFullAppendedWorkfileName = targetArchiveDirManager.getAppendedPath() + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { newFullAppendedWorkfileName = shortWorkfilename; } String checkInComment = QVCSConstants.QVCS_INTERNAL_FILE_MOVED_FROM + oldFullAppendedWorkfileName + " to [" + newFullAppendedWorkfileName + "]."; LogFileOperationMoveArchiveOnTranslucentBranch moveArchiveOperation = new LogFileOperationMoveArchiveOnTranslucentBranch(archiveInfoForTranslucentBranch, userName, checkInComment, appendedPath, shortWorkfilename, date, branchLabel); retVal = moveArchiveOperation.execute(); } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } } return retVal; } boolean renameArchiveOnTranslucentBranch(String userName, String appendedPath, String oldShortWorkfilename, String newShortWorkfileName, final Date date, final String branchLabel, ArchiveInfoForTranslucentBranch archiveInfoForTranslucentBranch) { boolean retVal = false; // Check in a new revision (creating a file branch if required) marking the rename. String branchTipRevisionString = archiveInfoForTranslucentBranch.getBranchTipRevisionString(); if (branchTipRevisionString.length() > 0) { try { // We need to create new revision (with no changes) that marks // the boundary for this rename. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the rename // operation. String oldFullAppendedWorkfileName; String newFullAppendedWorkfileName; if (appendedPath.length() > 0) { oldFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + oldShortWorkfilename; newFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + newShortWorkfileName; } else { oldFullAppendedWorkfileName = oldShortWorkfilename; newFullAppendedWorkfileName = newShortWorkfileName; } String checkInComment = QVCSConstants.QVCS_INTERNAL_FILE_RENAMED_FROM + oldFullAppendedWorkfileName + BRACKET_TO + newFullAppendedWorkfileName + "]."; LogFileOperationRenameArchiveOnTranslucentBranch renameArchiveOperation = new LogFileOperationRenameArchiveOnTranslucentBranch(archiveInfoForTranslucentBranch, userName, checkInComment, appendedPath, oldShortWorkfilename, date, branchLabel); retVal = renameArchiveOperation.execute(); } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } } return retVal; } boolean deleteArchive(String userName, String appendedPath, ArchiveDirManager cemeteryArchiveDirManager, String shortWorkfilename, final Date date) { boolean retVal = false; try { // Make sure we have a fileID... int fileID = getFileID(); // See if we can rename the archive file... String newShortArchiveName = Utility.createCemeteryShortArchiveName(fileID); String newFullArchiveFilename = cemeteryArchiveDirManager.getArchiveDirectoryName() + File.separator + newShortArchiveName; File newArchiveFile = new File(newFullArchiveFilename); if (!newArchiveFile.exists()) { // We need to create new revision (with no changes) that marks // the boundary for this delete. We need to do this so that // a 'get by label' will work correctly. We'll use a revision // comment that indicates the revision was created by the delete // operation. String oldFullAppendedWorkfileName; if (appendedPath.length() > 0) { oldFullAppendedWorkfileName = appendedPath + QVCSConstants.QVCS_STANDARD_PATH_SEPARATOR_STRING + shortWorkfilename; } else { oldFullAppendedWorkfileName = shortWorkfilename; } String checkInComment = "Deleted: '" + oldFullAppendedWorkfileName + "' to cemetery archive file: '" + newFullArchiveFilename + "'."; LogFileOperationMoveArchive moveArchiveOperation = new LogFileOperationMoveArchive(this, userName, checkInComment, appendedPath, shortWorkfilename, date); if (moveArchiveOperation.execute()) { retVal = reReadInformation(); } // Move the archive file. if (retVal) { // Make sure the cemetery exists. if (!newArchiveFile.getParentFile().exists()) { newArchiveFile.getParentFile().mkdirs(); } // Make sure the original archive is read/write. if (!archiveFile.canWrite()) { WorkFile oldFile = new WorkFile(archiveFile.getPath()); oldFile.setReadWrite(); } // And move the archive to the cemetery. if (!archiveFile.renameTo(newArchiveFile)) { LOGGER.log(Level.WARNING, "Failed to move [" + getFileName() + BRACKET_TO + newFullArchiveFilename + "] in project cemetery."); retVal = false; } } else { LOGGER.log(Level.WARNING, "Delete of archive failed. Failed to re-read archive information for: [" + oldFullAppendedWorkfileName + "]"); } } else { LOGGER.log(Level.WARNING, "Delete of archive failed because archive name already exists in project cemetery: [" + newFullArchiveFilename + "]"); } } catch (QVCSException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } return retVal; } /** * Get a revision from the archive file and write it into the filename provided. Return true if successful, false otherwise. * Keywords are NOT expanded by this method. */ boolean getRevision(GetRevisionCommandArgs commandLineArgs, String fetchToFileName) throws QVCSException { boolean retVal = false; // <editor-fold> Object[] args = new Object[3]; args[0] = this; args[1] = fetchToFileName; args[2] = commandLineArgs; // </editor-fold> LogFileOperationGetRevision getOperation = new LogFileOperationGetRevision(args); if (getOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean checkOutRevision(CheckOutCommandArgs commandLineArgs, String fetchToFileName) throws QVCSException { boolean retVal = false; // <editor-fold> Object[] args = new Object[3]; args[0] = this; args[1] = fetchToFileName; args[2] = commandLineArgs; // </editor-fold> LogFileOperationCheckOut checkOutOperation = new LogFileOperationCheckOut(args); if (checkOutOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean lockRevision(LockRevisionCommandArgs commandLineArgs) throws QVCSException { boolean retVal = false; Object[] args = new Object[2]; args[0] = this; args[1] = commandLineArgs; LogFileOperationLockRevision lockRevisionOperation = new LogFileOperationLockRevision(args); if (lockRevisionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean checkInRevision(CheckInCommandArgs commandLineArgs, String filename, boolean ignoreLocksToEnableBranchCheckInFlag) throws QVCSException { boolean retVal = false; // <editor-fold> Object[] args = new Object[3]; args[0] = this; args[1] = filename; args[2] = commandLineArgs; // </editor-fold> LogFileOperationCheckIn checkInOperation = new LogFileOperationCheckIn(args, ignoreLocksToEnableBranchCheckInFlag); if (checkInOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean unlockRevision(UnlockRevisionCommandArgs commandLineArgs) throws QVCSException { boolean retVal = false; Object[] args = new Object[2]; args[0] = this; args[1] = commandLineArgs; LogFileOperationUnlockRevision unlockRevisionOperation = new LogFileOperationUnlockRevision(args); if (unlockRevisionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean unlockRevision(String userName, AtomicReference<String> revisionString) throws QVCSException { boolean retVal = false; // Make sure user is on the access list. makeSureIsOnAccessList(userName); // Figure out the default revision string if we need to. if (0 == revisionString.get().compareTo(QVCSConstants.QVCS_DEFAULT_REVISION)) { revisionString.set(getDefaultRevisionHeader().getRevisionString()); } // Make sure the revision is already locked. AtomicInteger revisionIndex = new AtomicInteger(); if (!isRevisionLocked(revisionString, revisionIndex)) { throw new QVCSException("Revision [" + revisionString + "] is not locked for [" + getShortWorkfileName() + "]"); } // Make sure the current user holds the lock on the given revision. if (modifierList.userToIndex(userName) != getRevisionHeader(revisionIndex.get()).getLockerIndex()) { throw new QVCSException("Revision [" + revisionString + "] for [" + getShortWorkfileName() + "] is not locked by [" + userName + "]"); } // Make a copy of the archive. We'll operate on this copy. if (!createCopyOfArchive()) { throw new QVCSException("Unable to create temporary copy of archive for [" + getShortWorkfileName() + "]"); } java.io.RandomAccessFile ioStream = null; try { ioStream = new java.io.RandomAccessFile(tempFile, "rw"); // Seek to the location of this revision in the file. RevisionHeader revInfo = getRevisionHeader(revisionIndex.get()); ioStream.seek(revInfo.getRevisionStartPosition()); // Update the revision information before we write it out. revInfo.setIsLocked(false); revInfo.setLockerIndex(0); // Write the updated revision info. revInfo.updateInPlace(ioStream); // Update the header with info about this locker. getLogFileHeaderInfo().getLogFileHeader().decrementLockCount(); // Update the header information in the stream. getLogFileHeaderInfo().updateInPlace(ioStream); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } finally { try { if (ioStream != null) { ioStream.close(); } } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); retVal = false; } } // Remove any old archives. oldArchiveFile.delete(); if (archiveFile.renameTo(oldArchiveFile)) { if (tempFile.renameTo(archiveFile)) { retVal = true; oldArchiveFile.delete(); } else { oldArchiveFile.renameTo(archiveFile); throw new QVCSException("Unable to rename temporary copy of archive for [" + getShortWorkfileName() + "]"); } } else { throw new QVCSException("Unable to rename archive file for [" + getShortWorkfileName() + "]"); } return retVal; } boolean labelRevision(LabelRevisionCommandArgs commandLineArgs) throws QVCSException { boolean retVal = false; Object[] args = new Object[2]; args[0] = this; args[1] = commandLineArgs; LogFileOperationLabelRevision labelRevisionOperation = new LogFileOperationLabelRevision(args); if (labelRevisionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean unLabelRevision(UnLabelRevisionCommandArgs commandLineArgs) throws QVCSException { boolean retVal = false; Object[] args = new Object[2]; args[0] = this; args[1] = commandLineArgs; LogFileOperationUnLabelRevision unLabelRevisionOperation = new LogFileOperationUnLabelRevision(args); if (unLabelRevisionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } synchronized AccessList getAccessList() { if (!isArchiveInformationRead()) { readInformation(); } return accessList; } AccessList getModifierList() { if (!isArchiveInformationRead()) { readInformation(); } return modifierList; } /** * Make sure the given user is on the access list for this archive. If the user is not on the access list, this method will * automatically add the user to the access list (and modifier list), and update the archive file. */ void makeSureIsOnAccessList(String userName) throws QVCSException { boolean success = false; boolean accessListFlag = getAccessList().addUser(userName); boolean modifierListFlag = getModifierList().addUser(userName); // If we updated either access list or modifier list, we need // to re-write the archive file. if (accessListFlag || modifierListFlag) { // Need to update the string copy of the access list maintained // in the header. // Need to re-write the archive to have the modified header // information. RandomAccessFile newArchiveStream = null; RandomAccessFile oldArchiveStream = null; try { // Need to re-write the archive to have the modified header // information. getLogFileHeaderInfo().setAccessList(getAccessList().getAccessListAsCommaSeparatedString()); getLogFileHeaderInfo().setModifierList(getModifierList().getAccessListAsCommaSeparatedString()); newArchiveStream = new RandomAccessFile(getTempFile(), "rw"); oldArchiveStream = new RandomAccessFile(getFile(), "r"); // Figure out where we need to copy from in the existing archive // file... (It's the beginning of all revision info). RevisionHeader revInfo = getRevisionHeader(0); long revStartPosition = revInfo.getRevisionStartPosition(); oldArchiveStream.seek(revStartPosition); // Write the new header out to the new archive file. getLogFileHeaderInfo().write(newArchiveStream); // And copy the rest of the existing archive to the new one. long bytesToCopy = oldArchiveStream.length() - revStartPosition; AbstractLogFileOperation.copyFromOneOpenFileToAnotherOpenFile(oldArchiveStream, newArchiveStream, bytesToCopy); success = true; } catch (IOException e) { throw new QVCSException("Exception in makeSureIsOnAccessList(): " + e.getLocalizedMessage()); } finally { try { if (newArchiveStream != null) { newArchiveStream.close(); } if (oldArchiveStream != null) { oldArchiveStream.close(); } } catch (IOException e) { LOGGER.log(Level.WARNING, "Caught exception when closing archive files: " + e.getClass().toString() + " " + e.getLocalizedMessage()); LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); success = false; } if (success) { replaceExistingArchiveWithNewTempArchive(); reReadInformation(); } else { getTempFile().delete(); } } } } void replaceExistingArchiveWithNewTempArchive() throws QVCSException { getOldFile().delete(); if (getFile().renameTo(getOldFile())) { if (getTempFile().renameTo(getFile())) { getOldFile().delete(); } else { getOldFile().renameTo(getFile()); throw new QVCSException("Unable to rename temporary copy of archive for [" + getShortWorkfileName() + "]"); } } else { throw new QVCSException("Unable to rename archive file for [" + getShortWorkfileName() + "]"); } } boolean isRevisionLocked(AtomicReference<String> revisionString, AtomicInteger revisionIndex) throws QVCSException { boolean retVal = true; // Figure out the default revision string if we need to. if (0 == revisionString.get().compareTo(QVCSConstants.QVCS_DEFAULT_REVISION)) { revisionString.set(getDefaultRevisionHeader().getRevisionString()); } if (findRevision(revisionString.get(), revisionIndex)) { RevisionHeader revInfo = getRevisionHeader(revisionIndex.get()); retVal = revInfo.isLocked(); } else { throw new QVCSException("Revision [" + revisionString.get() + "] not found in [" + getShortWorkfileName() + "]"); } return retVal; } String getLockedRevisionString(String userName) { String retVal = null; RevisionInformation revisionInformation = getRevisionInformation(); AccessList localModifierList = revisionInformation.getModifierList(); int revisionCount = getLogFileHeaderInfo().getRevisionCount(); for (int i = 0; i < revisionCount; i++) { RevisionHeader revHeader = revisionInformation.getRevisionHeader(i); if (revHeader.isLocked()) { String lockerName = localModifierList.indexToUser(revHeader.getLockerIndex()); if (0 == lockerName.compareTo(userName)) { retVal = revHeader.getRevisionString(); break; } } } return retVal; } /** * Determine whether the given user holds any locks for this archive. */ boolean isLockedByUser(String userName) { boolean retVal = false; if (!isArchiveInformationRead()) { readInformation(); } if (isArchiveInformationRead()) { if (headerInfo.getLogFileHeader().lockCount() > 0) { int revisionCount = headerInfo.getRevisionCount(); int lockCount = headerInfo.getLogFileHeader().lockCount(); for (int i = 0, j = 0; (i < revisionCount) && (j < lockCount); i++) { RevisionHeader revHeader = revisionInfo.getRevisionHeader(i); if (revHeader.isLocked()) { j++; if (0 == userName.compareTo(indexToUsername(revHeader.getLockerIndex()))) { retVal = true; break; } } } } } return retVal; } boolean createCopyOfArchive() { boolean retVal = false; try { // Make sure the temp file is gone. tempFile.delete(); // Copy the archive to the tempfile. retVal = copyFile(archiveFile, tempFile); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } return retVal; } boolean copyFile(java.io.File fromFile, java.io.File toFile) throws IOException { boolean retVal = true; FileChannel toChannel; try (FileChannel fromChannel = new FileInputStream(fromFile).getChannel()) { toChannel = new FileOutputStream(toFile).getChannel(); toChannel.transferFrom(fromChannel, 0L, fromChannel.size()); } toChannel.close(); return retVal; } ArchiveAttributes getAttributes() { if (mustReadArchiveFileFlag && !isArchiveInformationRead()) { readInformation(); } return headerInfo.getLogFileHeader().attributes(); } boolean setAttributes(String userName, ArchiveAttributes attributes) throws QVCSException { boolean retVal = false; LogFileOperationSetAttributes setAttributesOperation = new LogFileOperationSetAttributes(this, userName, attributes); if (setAttributesOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } String getCommentPrefix() { if (mustReadArchiveFileFlag && !isArchiveInformationRead()) { readInformation(); } return headerInfo.getCommentPrefix(); } boolean setCommentPrefix(final String userName, final String newCommentPrefix) throws QVCSException { boolean retVal = false; LogFileOperationSetCommentPrefix setCommentPrefixOperation = new LogFileOperationSetCommentPrefix(this, userName, newCommentPrefix); if (setCommentPrefixOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } String getModuleDescription() { if (mustReadArchiveFileFlag && !isArchiveInformationRead()) { readInformation(); } return headerInfo.getModuleDescription(); } boolean setModuleDescription(final String userName, final String newDescription) throws QVCSException { boolean retVal = false; LogFileOperationSetModuleDescription setModuleDescriptionOperation = new LogFileOperationSetModuleDescription(this, userName, newDescription); if (setModuleDescriptionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } boolean setRevisionDescription(SetRevisionDescriptionCommandArgs commandLineArgs) throws QVCSException { boolean retVal = false; Object[] args = new Object[2]; args[0] = this; args[1] = commandLineArgs; LogFileOperationSetRevisionDescription setRevisionDescriptionOperation = new LogFileOperationSetRevisionDescription(args); if (setRevisionDescriptionOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } String getRevisionDescription(final String revisionString) { String retVal = null; AtomicInteger revisionIndex = new AtomicInteger(); if (findRevision(revisionString, revisionIndex)) { RevisionHeader revInfo = getRevisionHeader(revisionIndex.get()); retVal = revInfo.getRevisionDescription(); } return retVal; } boolean setIsObsolete(String userName, boolean flag) throws QVCSException { boolean retVal = false; LogFileOperationSetIsObsolete setIsObsoleteOperation = new LogFileOperationSetIsObsolete(this, userName, flag); if (setIsObsoleteOperation.execute()) { retVal = reReadInformation(); } else { reReadInformation(); } return retVal; } byte[] getRevisionAsByteArray(String revisionString) { byte[] workfileBuffer = null; try { if (open()) { AtomicInteger revisionIndex = new AtomicInteger(); if (findRevision(revisionString, revisionIndex)) { RevisionHeader revInfo = getRevisionHeader(revisionIndex.get()); MutableByteArray processedBuffer = new MutableByteArray(); if (fetchRevision(revInfo, null, false, processedBuffer)) { workfileBuffer = processedBuffer.getValue(); } } } } finally { close(); } return workfileBuffer; } boolean hasLabel(final String label) { boolean retVal = false; LogFileHeaderInfo logfileHeaderInfo = getLogFileHeaderInfo(); LabelInfo[] labelInfo = logfileHeaderInfo.getLabelInfo(); if (labelInfo != null) { for (LabelInfo labelInfo1 : labelInfo) { if (0 == label.compareTo(labelInfo1.getLabelString())) { retVal = true; break; } } } return retVal; } private void updateHeaderOnDisk() throws QVCSException { // Need to re-write the archive to have the modified header // information. RandomAccessFile newArchiveStream = null; RandomAccessFile oldArchiveStream = null; boolean success = false; try { // Need to re-write the archive to have the modified header // information. newArchiveStream = new RandomAccessFile(getTempFile(), "rw"); oldArchiveStream = new RandomAccessFile(getFile(), "r"); // Figure out where we need to copy from in the existing archive // file... (It's the beginning of all revision info). RevisionHeader revInfo = getRevisionHeader(0); long revStartPosition = revInfo.getRevisionStartPosition(); oldArchiveStream.seek(revStartPosition); // Write the new header out to the new archive file. getLogFileHeaderInfo().write(newArchiveStream); // And copy the rest of the existing archive to the new one. long bytesToCopy = oldArchiveStream.length() - revStartPosition; AbstractLogFileOperation.copyFromOneOpenFileToAnotherOpenFile(oldArchiveStream, newArchiveStream, bytesToCopy); success = true; } catch (IOException e) { throw new QVCSException("Exception in updateHeaderOnDisk(): " + e.getLocalizedMessage()); } finally { try { if (newArchiveStream != null) { newArchiveStream.close(); } if (oldArchiveStream != null) { oldArchiveStream.close(); } } catch (IOException e) { LOGGER.log(Level.WARNING, "Caught exception when closing archive files: " + e.getClass().toString() + " " + e.getLocalizedMessage()); LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); success = false; } if (success) { replaceExistingArchiveWithNewTempArchive(); reReadInformation(); } else { getTempFile().delete(); } } } @SuppressWarnings("LoggerStringConcat") private void readRevisionData(byte[] revisionData) throws QVCSException { try { int offset = 0; while (true) { int bytesLeft = revisionData.length - offset; if (bytesLeft > QVCSConstants.BYTES_TO_XFER) { int bytesToRead = QVCSConstants.BYTES_TO_XFER; inStream.readFully(revisionData, offset, bytesToRead); offset += bytesToRead; continue; } else { int bytesToRead = bytesLeft; inStream.readFully(revisionData, offset, bytesToRead); break; } } } catch (java.lang.OutOfMemoryError e) { // If they are trying to create an archive for a really big file, // we might have problems. LOGGER.log(Level.WARNING, "Out of memory trying to read revision data."); throw new QVCSException("Out of memory trying to read revision data."); } catch (IOException e) { LOGGER.log(Level.WARNING, "Caught exception: " + e.getClass().toString() + " " + e.getLocalizedMessage()); throw new QVCSException("Exception in readRevisionData.\n" + Utility.expandStackTraceToString(e)); } } /** * Remove any view and branch labels. This method should only be called when performing a file ID reset operation, i.e. when all * views and branches are thrown away. */ private void removeViewAndBranchLabels() { LabelInfo[] labelInfoCollection = getLogFileHeaderInfo().getLabelInfo(); if (getLogFileHeaderInfo().getLabelInfo() != null) { List<LabelInfo> labelInfoArray = new ArrayList<>(); for (LabelInfo labelInfo : labelInfoCollection) { String labelString = labelInfo.getLabelString(); if (labelString.startsWith(QVCSConstants.QVCS_VIEW_LABEL) || labelString.startsWith(QVCSConstants.QVCS_TRANSLUCENT_BRANCH_LABEL) || labelString.startsWith(QVCSConstants.QVCS_OPAQUE_BRANCH_LABEL)) { continue; } else { labelInfoArray.add(labelInfo); } } LabelInfo[] newLabelInfoCollection = new LabelInfo[labelInfoArray.size()]; int i = 0; for (LabelInfo labelInfo : labelInfoArray) { newLabelInfoCollection[i++] = labelInfo; } getLogFileHeaderInfo().setLabelInfo(newLabelInfoCollection); } } }