/* 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.AccessList;
import com.qumasoft.qvcslib.ArchiveAttributes;
import com.qumasoft.qvcslib.ArchiveInfoInterface;
import com.qumasoft.qvcslib.LabelInfo;
import com.qumasoft.qvcslib.LogFileHeader;
import com.qumasoft.qvcslib.LogFileHeaderInfo;
import com.qumasoft.qvcslib.LogFileInterface;
import com.qumasoft.qvcslib.LogfileInfo;
import com.qumasoft.qvcslib.QVCSConstants;
import com.qumasoft.qvcslib.QVCSException;
import com.qumasoft.qvcslib.RemoteViewProperties;
import com.qumasoft.qvcslib.RevisionHeader;
import com.qumasoft.qvcslib.RevisionInformation;
import com.qumasoft.qvcslib.Utility;
import com.qumasoft.qvcslib.commandargs.CheckInCommandArgs;
import com.qumasoft.qvcslib.commandargs.CheckOutCommandArgs;
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.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Archive info for read-only date based view.
* @author Jim Voris
*/
public final class ArchiveInfoForReadOnlyDateBasedView implements ArchiveInfoInterface, LogFileInterface {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private final String shortWorkfileName;
private Date lastCheckInDate;
private String lastEditBy;
private String defaultRevisionString;
private final byte[] defaultRevisionDigest;
private final ArchiveAttributes archiveAttributes;
private final LogfileInfo logfileInfo;
private int logFileFileID = -1;
private final String projectName;
private int revisionCount = -1;
private String fullLogfileName;
private Set<String> validRevisionNumbers;
/**
* Creates a new instance of ArchiveInfoForReadOnlyDateBasedView.
* @param shortName the short workfile name.
* @param logFile the LogFile from which we create this archive info for this read only date-based branch.
* @param remoteViewProperties the project properties.
*/
ArchiveInfoForReadOnlyDateBasedView(String shortName, LogFile logFile, RemoteViewProperties remoteViewProperties) {
shortWorkfileName = shortName;
projectName = remoteViewProperties.getProjectName();
logfileInfo = buildLogfileInfo(logFile, remoteViewProperties);
defaultRevisionDigest = ArchiveDigestManager.getInstance().getArchiveDigest(logFile, getDefaultRevisionString());
archiveAttributes = new ArchiveAttributes(logFile.getAttributes());
logFileFileID = logFile.getFileID();
}
@Override
public String getShortWorkfileName() {
return shortWorkfileName;
}
// These can NEVER be locked, since they are read-only.
@Override
public int getLockCount() {
return 0;
}
@Override
public String getLockedByString() {
return "";
}
@Override
public Date getLastCheckInDate() {
return lastCheckInDate;
}
// This is always an empty string, since a file can never be locked.
@Override
public String getWorkfileInLocation() {
return "";
}
@Override
public String getLastEditBy() {
return lastEditBy;
}
@Override
public byte[] getDefaultRevisionDigest() {
return defaultRevisionDigest;
}
@Override
public ArchiveAttributes getAttributes() {
return archiveAttributes;
}
@Override
public LogfileInfo getLogfileInfo() {
return logfileInfo;
}
@Override
public String getRevisionDescription(final String revisionString) {
String returnValue = null;
try {
if (validateRevisionString(revisionString)) {
int revisionIndex = getLogfileInfo().getRevisionInformation().getRevisionIndex(revisionString);
returnValue = getLogfileInfo().getRevisionInformation().getRevisionHeader(revisionIndex).getRevisionDescription();
} else {
LOGGER.log(Level.WARNING, "Requested revision: " + revisionString + " is not present in this view!");
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
return returnValue;
}
@Override
public byte[] getRevisionAsByteArray(String revisionString) {
byte[] returnValue = null;
try {
if (validateRevisionString(revisionString)) {
returnValue = getCurrentLogFile().getRevisionAsByteArray(revisionString);
} else {
LOGGER.log(Level.WARNING, "Requested revision: " + revisionString + " is not present in this view!");
}
} catch (QVCSException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
return returnValue;
}
@Override
public String getLockedRevisionString(String userName) {
return "";
}
@Override
public String getDefaultRevisionString() {
return defaultRevisionString;
}
@Override
public boolean checkInRevision(CheckInCommandArgs commandArgs, String checkInFilename, boolean ignoreLocksToEnableBranchCheckInFlag) throws QVCSException {
return false;
}
@Override
public boolean checkOutRevision(CheckOutCommandArgs commandLineArgs, String fetchToFileName) throws QVCSException {
return false;
}
@Override
public boolean getRevision(GetRevisionCommandArgs commandLineArgs, String fetchToFileName) throws QVCSException {
boolean returnValue = false;
try {
// We need to fill in the revision string.
if ((commandLineArgs.getRevisionString() != null) && (commandLineArgs.getRevisionString().length() > 0)) {
if (0 == commandLineArgs.getRevisionString().compareTo(QVCSConstants.QVCS_DEFAULT_REVISION)) {
commandLineArgs.setRevisionString(getDefaultRevisionString());
}
}
returnValue = getCurrentLogFile().getRevision(commandLineArgs, fetchToFileName);
} catch (QVCSException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
return returnValue;
}
@Override
public boolean getForVisualCompare(GetRevisionCommandArgs commandLineArgs, String outputFileName) throws QVCSException {
boolean returnValue = false;
try {
// We need to fill in the revision string.
if ((commandLineArgs.getRevisionString() != null) && (commandLineArgs.getRevisionString().length() > 0)) {
if (0 == commandLineArgs.getRevisionString().compareTo(QVCSConstants.QVCS_DEFAULT_REVISION)) {
commandLineArgs.setRevisionString(getDefaultRevisionString());
}
}
returnValue = getCurrentLogFile().getForVisualCompare(commandLineArgs, outputFileName);
} catch (QVCSException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
}
return returnValue;
}
@Override
public boolean lockRevision(LockRevisionCommandArgs commandArgs) throws QVCSException {
return false;
}
@Override
public boolean unlockRevision(UnlockRevisionCommandArgs commandArgs) throws QVCSException {
return false;
}
@Override
public boolean breakLock(UnlockRevisionCommandArgs commandArgs) throws QVCSException {
return false;
}
@Override
public boolean labelRevision(LabelRevisionCommandArgs commandArgs) throws QVCSException {
return false;
}
@Override
public boolean unLabelRevision(UnLabelRevisionCommandArgs commandArgs) throws QVCSException {
return false;
}
@Override
public boolean getIsObsolete() {
return false;
}
@Override
public boolean setIsObsolete(String userName, boolean flag) throws QVCSException {
return false;
}
@Override
public boolean setAttributes(String userName, ArchiveAttributes attributes) throws QVCSException {
return false;
}
@Override
public boolean setCommentPrefix(String userName, String commentPrefix) throws QVCSException {
return false;
}
@Override
public boolean setModuleDescription(String userName, String moduleDescription) throws QVCSException {
return false;
}
@Override
public boolean setRevisionDescription(SetRevisionDescriptionCommandArgs commandArgs) throws QVCSException {
return false;
}
private String getProjectName() {
return projectName;
}
@Override
public String getFullArchiveFilename() {
return fullLogfileName;
}
private LogFile getCurrentLogFile() throws QVCSException {
FileIDInfo fileIDInfo = FileIDDictionary.getInstance().lookupFileIDInfo(getProjectName(), QVCSConstants.QVCS_TRUNK_VIEW, logFileFileID);
int directoryID = fileIDInfo.getDirectoryID();
// Lookup the archiveDirManager for the file's current location...
ArchiveDirManager archiveDirManager = DirectoryIDDictionary.getInstance().lookupArchiveDirManager(getProjectName(), directoryID, null, true);
String keyToFile = fileIDInfo.getShortFilename();
boolean ignoreCaseFlag = archiveDirManager.getProjectProperties().getIgnoreCaseFlag();
if (ignoreCaseFlag) {
keyToFile = keyToFile.toLowerCase();
}
// Get the file's current archiveInfo...
LogFile archiveInfo = (LogFile) archiveDirManager.getArchiveInfo(keyToFile);
return archiveInfo;
}
private LogfileInfo buildLogfileInfo(LogFile logFile, RemoteViewProperties remoteViewProperties) {
LogfileInfo info = null;
fullLogfileName = logFile.getFullArchiveFilename();
RevisionInformation viewRevisionInformation = buildViewRevisionInformation(logFile, remoteViewProperties);
if (viewRevisionInformation != null) {
LogFileHeaderInfo viewLogFileHeaderInfo = buildViewLogFileHeaderInfo(logFile, viewRevisionInformation);
info = new LogfileInfo(viewLogFileHeaderInfo, viewRevisionInformation, getFileID(), fullLogfileName);
// And save away some things for our accessor methods...
RevisionHeader tipRevisionHeader = viewRevisionInformation.getRevisionHeader(0);
defaultRevisionString = tipRevisionHeader.getRevisionString();
AccessList modifierList = new AccessList(info.getLogFileHeaderInfo().getModifierList());
lastEditBy = modifierList.indexToUser(tipRevisionHeader.getCreatorIndex());
lastCheckInDate = tipRevisionHeader.getCheckInDate();
}
return info;
}
private RevisionInformation buildViewRevisionInformation(LogFile logFile, RemoteViewProperties remoteViewProperties) {
RevisionInformation viewRevisionInformation = null;
validRevisionNumbers = new HashSet<>();
long viewDate = remoteViewProperties.getDateBasedDate().getTime();
// Need to create the set of revisions that are visible for this view...
String sortableTipRevisionString = getSortableRevisionStringForChosenBranch(remoteViewProperties.getDateBasedViewBranch(), logFile);
TreeMap<String, RevisionHeader> revisionMap = new TreeMap<>();
TreeMap<String, Integer> revisionIndexMap = new TreeMap<>();
RevisionInformation revisionInformation = logFile.getRevisionInformation();
// Put all the revisions in a sorted map.
for (int i = 0; i < logFile.getRevisionCount(); i++) {
RevisionHeader revisionHeader = revisionInformation.getRevisionHeader(i);
String sortableRevisionString = revisionHeader.getRevisionDescriptor().toSortableString();
revisionMap.put(sortableRevisionString, revisionHeader);
// So we can later put the revisions in revisionIndex order...
revisionIndexMap.put(sortableRevisionString, Integer.valueOf(i));
}
// Now iterate through all the revisions finding those that are on the
// view's 'branch' that are also before the view's date.
TreeMap<Integer, RevisionHeader> branchRevisionMapKeyedByRevisionIndex = new TreeMap<>();
Iterator<Map.Entry<String, RevisionHeader>> entrySetIt = revisionMap.entrySet().iterator();
while (entrySetIt.hasNext()) {
Map.Entry<String, RevisionHeader> mapEntry = entrySetIt.next();
String sortableRevisionString = mapEntry.getKey();
if (sortableRevisionString.compareTo(sortableTipRevisionString) <= 0) {
RevisionHeader revisionHeader = mapEntry.getValue();
String checkInDateString = revisionHeader.getCheckInDate().toString();
String viewDateString = remoteViewProperties.getDateBasedDate().toString();
LOGGER.log(Level.FINE, "Comparing dates. Check in date: " + checkInDateString + ". View date: " + viewDateString);
if (revisionHeader.getCheckInDate().getTime() <= viewDate) {
// Use the revisionIndex as the key so when we pull these out,
// they will be in revisionIndex order, instead the order
// that you would get by using the sortableRevisionString.
branchRevisionMapKeyedByRevisionIndex.put(revisionIndexMap.get(sortableRevisionString), revisionHeader);
}
}
}
if (branchRevisionMapKeyedByRevisionIndex.size() > 0) {
// Ok. The revisions in the branchRevisionMap are the ones we need.
AccessList accessList = new AccessList(logFile.getLogFileHeaderInfo().getAccessList());
viewRevisionInformation = new RevisionInformation(branchRevisionMapKeyedByRevisionIndex.size(), accessList, accessList);
setRevisionCount(branchRevisionMapKeyedByRevisionIndex.size());
// Iterate through the revisions, adding them to the RevisionInformation object.
// The revisions will appear in the new RevisionInformation object
// in revisionIndex order.
Iterator<RevisionHeader> revisionIterator = branchRevisionMapKeyedByRevisionIndex.values().iterator();
int revisionIndex = 0;
while (revisionIterator.hasNext()) {
RevisionHeader revisionHeader = revisionIterator.next();
RevisionHeader viewRevisionHeader = new RevisionHeader(revisionHeader);
// As this is a read-only view, no locks are allowed.
viewRevisionHeader.setIsLocked(false);
viewRevisionInformation.updateRevision(revisionIndex++, viewRevisionHeader);
// Save away the valid revision numbers.
validRevisionNumbers.add(viewRevisionHeader.getRevisionString());
}
}
return viewRevisionInformation;
}
private String getSortableRevisionStringForChosenBranch(final String branchString, final LogFile logFile) {
String revisionStringForBranch = null;
String sortableRevisionStringForBranch = null;
// This is a 'get by label' request. Find the label, if we can.
if ((branchString != null) && (logFile != null)) {
if (0 == branchString.compareTo(QVCSConstants.QVCS_TRUNK_VIEW)) {
// We're on the trunk...
revisionStringForBranch = logFile.getRevisionInformation().getRevisionHeader(0).getRevisionString();
} else {
LabelInfo[] labelInfo = logFile.getLogFileHeaderInfo().getLabelInfo();
if (labelInfo != null) {
for (LabelInfo labelInfo1 : labelInfo) {
if (branchString.equals(labelInfo1.getLabelString())) {
// If it is a floating label, we have to figure out the
// revision string...
if (labelInfo1.isFloatingLabel()) {
RevisionInformation revisionInformation = logFile.getRevisionInformation();
int revCount = logFile.getRevisionCount();
for (int j = 0; j < revCount; j++) {
RevisionHeader revHeader = revisionInformation.getRevisionHeader(j);
if (revHeader.getDepth() == labelInfo1.getDepth()) {
if (revHeader.isTip()) {
String labelRevisionString = labelInfo1.getLabelRevisionString();
String revisionString = revHeader.getRevisionString();
if (revisionString.startsWith(labelRevisionString)) {
revisionStringForBranch = revisionString;
break;
}
}
}
}
} else {
revisionStringForBranch = labelInfo1.getLabelRevisionString();
}
break;
}
}
}
}
if (revisionStringForBranch != null) {
int revisionIndex = logFile.getRevisionInformation().getRevisionIndex(revisionStringForBranch);
RevisionHeader revisionHeader = logFile.getRevisionInformation().getRevisionHeader(revisionIndex);
sortableRevisionStringForBranch = revisionHeader.getRevisionDescriptor().toSortableString();
}
}
return sortableRevisionStringForBranch;
}
private LogFileHeaderInfo buildViewLogFileHeaderInfo(LogFile logFile, RevisionInformation viewRevisionInformation) {
LogFileHeaderInfo logFileHeaderInfo;
LabelInfo[] labelInfo = buildViewLabelInfo(logFile, viewRevisionInformation);
LogFileHeader logFileHeader = buildViewLogFileHeader(logFile, viewRevisionInformation);
logFileHeaderInfo = new LogFileHeaderInfo(logFileHeader);
logFileHeaderInfo.setAccessList(logFile.getLogFileHeaderInfo().getAccessList());
logFileHeaderInfo.setCommentPrefix(logFile.getLogFileHeaderInfo().getCommentPrefix());
logFileHeaderInfo.setModifierList(logFile.getLogFileHeaderInfo().getModifierList());
logFileHeaderInfo.setModuleDescription(logFile.getLogFileHeaderInfo().getModuleDescription());
logFileHeaderInfo.setOwner(logFile.getLogFileHeaderInfo().getOwner());
logFileHeaderInfo.setWorkfileName(logFile.getLogFileHeaderInfo().getWorkfileName());
logFileHeaderInfo.setLabelInfo(labelInfo);
return logFileHeaderInfo;
}
private LogFileHeader buildViewLogFileHeader(LogFile logFile, RevisionInformation viewRevisionInformation) {
LogFileHeader viewLogFileHeader = new LogFileHeader();
LogFileHeader existingLogFileHeader = logFile.getLogFileHeaderInfo().getLogFileHeader();
RevisionHeader tipRevisionHeader = viewRevisionInformation.getRevisionHeader(0);
viewLogFileHeader.getQVCSVersion().setValue((short) QVCSConstants.QVCS_ARCHIVE_VERSION);
viewLogFileHeader.getMajorNumber().setValue((short) tipRevisionHeader.getMajorNumber());
viewLogFileHeader.getMinorNumber().setValue((short) tipRevisionHeader.getMinorNumber());
viewLogFileHeader.getAttributeBits().setValue((short) existingLogFileHeader.attributes().getAttributesAsInt());
viewLogFileHeader.getVersionCount().setValue((short) 0);
viewLogFileHeader.getRevisionCount().setValue((short) getRevisionCount());
viewLogFileHeader.getDefaultDepth().setValue((short) 0);
viewLogFileHeader.getLockCount().setValue((short) 0);
viewLogFileHeader.getAccessSize().setValue((short) 0);
viewLogFileHeader.getModifierSize().setValue((short) 0);
viewLogFileHeader.getCommentSize().setValue((short) 0);
viewLogFileHeader.getOwnerSize().setValue((short) 0);
viewLogFileHeader.getDescSize().setValue((short) 0);
viewLogFileHeader.getSupplementalInfoSize().setValue((short) 0);
viewLogFileHeader.setAttributes(new ArchiveAttributes(viewLogFileHeader.getAttributeBits().getValue()));
viewLogFileHeader.getCheckSum().setValue(viewLogFileHeader.computeCheckSum());
return viewLogFileHeader;
}
@Override
public RevisionInformation getRevisionInformation() {
return logfileInfo.getRevisionInformation();
}
private LabelInfo[] buildViewLabelInfo(LogFile logFile, RevisionInformation viewRevisionInformation) {
LabelInfo[] viewLabelInfo = null;
if (logFile.getLogfileInfo().getLogFileHeaderInfo().getLogFileHeader().getVersionCount().getValue() > 0) {
// Build the set of revision strings visible for this view. Only include
// a label if the label's revision string is in that set.
Set<String> viewRevisionStringSet = new HashSet<>();
for (int i = 0; i < getRevisionCount(); i++) {
viewRevisionStringSet.add(viewRevisionInformation.getRevisionHeader(i).getRevisionString());
}
ArrayList<LabelInfo> viewLabelArray = new ArrayList<>();
LabelInfo[] currentLogFileLabelInfo = logFile.getLogFileHeaderInfo().getLabelInfo();
for (LabelInfo currentLogFileLabelInfo1 : currentLogFileLabelInfo) {
String labelRevisionString = currentLogFileLabelInfo1.getLabelRevisionString();
if (viewRevisionStringSet.contains(labelRevisionString)) {
viewLabelArray.add(currentLogFileLabelInfo1);
}
}
if (viewLabelArray.size() > 0) {
viewLabelInfo = new LabelInfo[viewLabelArray.size()];
for (int i = 0; i < viewLabelArray.size(); i++) {
viewLabelInfo[i] = viewLabelArray.get(i);
}
}
}
return viewLabelInfo;
}
@Override
public int getRevisionCount() {
return this.revisionCount;
}
private void setRevisionCount(int revCount) {
this.revisionCount = revCount;
}
private boolean validateRevisionString(String revisionString) {
return validRevisionNumbers.contains(revisionString);
}
@Override
public int getFileID() {
return this.logFileFileID;
}
/**
* We do not support overlap detection for read-only date based views.
*
* @return false;
*/
@Override
public boolean getIsOverlap() {
return false;
}
}