blob: 271098d6a3348fcfc574c2c37a62f33c9984ab9a [file] [log] [blame]
/*****************************************************************************
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the CVS Client Library.
* The Initial Developer of the Original Code is Robert Greig.
* Portions created by Robert Greig are Copyright (C) 2000.
* All Rights Reserved.
*
* Contributor(s): Robert Greig.
*****************************************************************************/
package org.netbeans.lib.cvsclient.admin;
import com.intellij.util.text.SyncDateFormat;
import org.jetbrains.annotations.NonNls;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* The class abstracts the CVS concept of an <i>entry line</i>. The entry
* line is textually of the form:<p>
* / name / version / conflict / options / tag_or_date
* <p>These are explained in section 5.1 of the CVS protocol 1.10 document.
*
* @author Robert Greig
*/
public final class Entry implements Cloneable {
// Constants ==============================================================
@NonNls public static final String DUMMY_TIMESTAMP = "dummy timestamp";
@NonNls private static final String DUMMY_TIMESTAMP_NEW_ENTRY = "dummy timestamp from new-entry";
@NonNls private static final String MERGE_TIMESTAMP = "Result of merge";
@NonNls private static final String STICKY_TAG_REVISION_PREFIX = "T";
@NonNls private static final String STICKY_DATE_PREFIX = "D";
@NonNls private static final String BINARY_FILE = "-kb";
@NonNls private static final String HAD_CONFLICTS = "+";
@NonNls private static final char TIMESTAMP_MATCHES_FILE = '=';
@NonNls private static final String DIRECTORY_PREFIX = "D/";
// Static =================================================================
@NonNls private static final String DATE_FORMAT_STR = "yyyy.MM.dd.hh.mm.ss";
public static final SyncDateFormat STICKY_DATE_FORMAT = new SyncDateFormat(new SimpleDateFormat(DATE_FORMAT_STR));
private static SyncDateFormat lastModifiedDateFormatter;
private boolean isAddedFile = false;
private boolean isRemoved = false;
private boolean isResultOfMerge = false;
@NonNls private static final String LAST_MODIFIED_DATE_FORMAT_ATR = "EEE MMM dd HH:mm:ss yyyy";
@NonNls private static final String TIME_ZONE_FORMAT_STR = "GMT+0000";
@NonNls private static final String INITIAL_PREFIX = "Initial ";
public static SyncDateFormat getLastModifiedDateFormatter() {
if (lastModifiedDateFormatter == null) {
lastModifiedDateFormatter = new SyncDateFormat(new SimpleDateFormat(LAST_MODIFIED_DATE_FORMAT_ATR, Locale.US));
lastModifiedDateFormatter.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_FORMAT_STR));
}
return lastModifiedDateFormatter;
}
public static String formatLastModifiedDate(Date date) {
return getLastModifiedDateFormatter().format(date);
}
public static Entry createDirectoryEntry(String directoryName) {
final Entry entry = new Entry();
entry.setFileName(directoryName);
entry.setDirectory(true);
return entry;
}
public static Entry createEntryForLine(String entryLine) {
final Entry entry = new Entry();
entry.parseLine(entryLine);
return entry;
}
// Fields =================================================================
private boolean directory;
private String fileName;
private Date lastModified;
private String revision;
private boolean conflict;
private boolean timeStampMatchesFile;
private String conflictString;
private String conflictStringWithoutConflictMarker;
private String options;
private String stickyRevision;
private String stickyTag;
private String stickyDateString;
private Date stickyDate;
// Setup ==================================================================
private Entry() {
}
// Implemented ============================================================
/**
* Create a string representation of the entry line.
* Create the standard CVS 1.10 entry line format.
*/
public String toString() {
final StringBuffer buf = new StringBuffer();
if (directory) {
buf.append(DIRECTORY_PREFIX);
}
else {
buf.append('/');
}
// if name is null, then this is a totally empty entry, so append
// nothing further
if (fileName != null) {
buf.append(fileName);
buf.append('/');
if (revision != null) {
buf.append(revision);
}
buf.append('/');
if (conflictString != null) {
buf.append(conflictString);
}
buf.append('/');
if (options != null) {
buf.append(options);
}
buf.append('/');
buf.append(getStickyData());
}
return buf.toString();
}
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
final String entryFileName = ((Entry)obj).fileName;
return (fileName == entryFileName) || (fileName != null && fileName.equals(entryFileName));
}
public int hashCode() {
return (fileName != null) ? fileName.hashCode() : 0;
}
// Accessing ==============================================================
public String getFileName() {
return fileName;
}
private void setFileName(String fileName) {
this.fileName = fileName;
}
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
isAddedFile = revision != null && revision.startsWith("0");
isRemoved = revision != null && revision.startsWith("-");
}
public Date getLastModified() {
return lastModified;
}
public void setDummyTimestamp() {
parseConflictString(DUMMY_TIMESTAMP);
}
public boolean isResultOfMerge() {
return isResultOfMerge;
}
public void setConflict(String conflictString) {
this.conflictString = conflictString;
isResultOfMerge = conflictString != null && conflictString.startsWith(MERGE_TIMESTAMP);
}
/**
* A typical conflict string looks like "+=".
*/
public void parseConflictString(String conflictString) {
setConflict(conflictString);
this.conflictStringWithoutConflictMarker = conflictString;
this.lastModified = null;
this.conflict = false;
this.timeStampMatchesFile = false;
if (conflictString == null || conflictString.equals(DUMMY_TIMESTAMP) || conflictString.equals(MERGE_TIMESTAMP) ||
conflictString.equals(DUMMY_TIMESTAMP_NEW_ENTRY)) {
return;
}
int parseStartIndex = 0;
// Look for the position of + which indicates a conflict
final int conflictIndex = conflictStringWithoutConflictMarker.indexOf(HAD_CONFLICTS);
if (conflictIndex >= 0) {
conflict = true;
parseStartIndex = conflictIndex + 1;
}
// if the timestamp matches the file, there will be an = following
// the +
final int timeMatchIndex = conflictStringWithoutConflictMarker.indexOf(TIMESTAMP_MATCHES_FILE);
if (timeMatchIndex >= 0) {
timeStampMatchesFile = true;
parseStartIndex = Math.max(parseStartIndex, timeMatchIndex + 1);
}
// At this point the conflict index tells us where the real conflict
// string starts
if (parseStartIndex > 0) {
conflictStringWithoutConflictMarker = conflictStringWithoutConflictMarker.substring(parseStartIndex);
}
// if we have nothing after the = then don't try to parse it
if (conflictStringWithoutConflictMarker.length() == 0) {
conflictStringWithoutConflictMarker = null;
return;
}
if (conflictStringWithoutConflictMarker.startsWith(INITIAL_PREFIX)) {
return;
}
try {
this.lastModified = getLastModifiedDateFormatter().parse(conflictStringWithoutConflictMarker);
}
catch (Exception ex) {
lastModified = null;
//noinspection HardCodedStringLiteral
System.err.println("[Entry] can't parse conflict '" + conflictStringWithoutConflictMarker + "'");
}
}
public String getOptions() {
return options;
}
public String getStickyTag() {
return stickyTag;
}
public void setStickyTag(String stickyTag) {
this.stickyTag = stickyTag;
this.stickyRevision = null;
this.stickyDateString = null;
this.stickyDate = null;
}
public String getStickyRevision() {
return stickyRevision;
}
public void setStickyRevision(String stickyRevision) {
this.stickyTag = null;
this.stickyRevision = stickyRevision;
this.stickyDateString = null;
this.stickyDate = null;
}
public String getStickyDateString() {
return stickyDateString;
}
public void setStickyDateString(String stickyDateString) {
this.stickyTag = null;
this.stickyRevision = null;
this.stickyDateString = stickyDateString;
this.stickyDate = null;
}
public Date getStickyDate() {
// lazy generation
if (stickyDate != null) {
return stickyDate;
}
if (stickyDateString == null) {
return null;
}
try {
return STICKY_DATE_FORMAT.parse(stickyDateString);
}
catch (ParseException ex) {
// ignore silently
return null;
}
}
public void setStickyDate(Date stickyDate) {
if (stickyDate == null) {
this.stickyTag = null;
this.stickyRevision = null;
this.stickyDateString = null;
this.stickyDate = null;
return;
}
this.stickyTag = null;
this.stickyRevision = null;
this.stickyDateString = STICKY_DATE_FORMAT.format(stickyDate);
this.stickyDate = stickyDate;
}
public String getStickyInformation() {
if (stickyTag != null) {
return stickyTag;
}
if (stickyRevision != null) {
return stickyRevision;
}
return stickyDateString;
}
public void setStickyInformation(String stickyInformation) {
if (stickyInformation == null) {
resetStickyInformation();
return;
}
if (stickyInformation.startsWith(STICKY_TAG_REVISION_PREFIX)) {
final String tagOrRevision = stickyInformation.substring(STICKY_TAG_REVISION_PREFIX.length());
if (tagOrRevision.length() == 0) {
resetStickyInformation();
return;
}
final char firstChar = tagOrRevision.charAt(0);
if (firstChar >= '0' && firstChar <= '9') {
setStickyRevision(tagOrRevision);
}
else {
setStickyTag(tagOrRevision);
}
return;
}
if (stickyInformation.startsWith(STICKY_DATE_PREFIX)) {
setStickyDateString(stickyInformation.substring(STICKY_DATE_PREFIX.length()));
}
// Ignore other cases silently
}
public boolean isBinary() {
return options != null && options.equals(BINARY_FILE);
}
public boolean isUnicode() {
if (options == null || !options.startsWith("-k")) return false;
return options.indexOf('u', 2) >= 0;
}
public boolean isAddedFile() {
return isAddedFile;
}
public boolean isRemoved() {
return isRemoved;
}
public boolean isValid() {
return getFileName() != null && getFileName().length() > 0;
}
public boolean isDirectory() {
return directory;
}
private void setDirectory(boolean directory) {
this.directory = directory;
}
public boolean isConflict() {
return conflict;
}
public String getConflictStringWithoutConflict() {
return conflictStringWithoutConflictMarker;
}
public boolean isTimeStampMatchesFile() {
return timeStampMatchesFile;
}
// Utils ==================================================================
private void parseLine(String entryLine) {
// try to parse the entry line, if we get stuck just
// throw an illegal argument exception
if (entryLine.startsWith(DIRECTORY_PREFIX)) {
directory = true;
entryLine = entryLine.substring(1);
}
// first character is a slash, so name is read from position 1
// up to the next slash
final int[] slashPositions = new int[5];
slashPositions[0] = 0;
for (int i = 1; i < 5; i++) {
slashPositions[i] = entryLine.indexOf('/', slashPositions[i - 1] + 1);
}
// Test if this is a D on its own, a special case indicating that
// directories are understood and there are no subdirectories
// in the current folder
if (slashPositions[1] < 1) {
throw new InvalidEntryFormatException();
}
// note that the parameters to substring are treated as follows:
// (inclusive, exclusive)
fileName = entryLine.substring(slashPositions[0] + 1, slashPositions[1]);
setRevision(entryLine.substring(slashPositions[1] + 1, slashPositions[2]));
if ((slashPositions[3] - slashPositions[2]) > 1) {
final String conflict = entryLine.substring(slashPositions[2] + 1, slashPositions[3]);
parseConflictString(conflict);
}
if ((slashPositions[4] - slashPositions[3]) > 1) {
options = entryLine.substring(slashPositions[3] + 1, slashPositions[4]);
}
if (slashPositions[4] != (entryLine.length() - 1)) {
final String tagOrDate = entryLine.substring(slashPositions[4] + 1);
setStickyInformation(tagOrDate);
}
}
private void resetStickyInformation() {
stickyTag = null;
stickyRevision = null;
stickyDateString = null;
stickyDate = null;
}
public String getStickyData() {
StringBuffer buf = new StringBuffer();
if (stickyTag != null) {
buf.append(STICKY_TAG_REVISION_PREFIX);
buf.append(stickyTag);
}
else if (stickyRevision != null) {
buf.append(STICKY_TAG_REVISION_PREFIX);
buf.append(stickyRevision);
}
else if (stickyDateString != null) {
buf.append(STICKY_DATE_PREFIX);
buf.append(getStickyDateString());
}
return buf.toString();
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}