/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.netbeans.lib.cvsclient.admin;
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 {
/**
* The dummy timestamp set the conflict information for added or removed files.
*/
public static final String DUMMY_TIMESTAMP = "dummy timestamp"; // NOI18N
public static final String DUMMY_TIMESTAMP_NEW_ENTRY = "dummy timestamp from new-entry"; // NOI18N
public static final String MERGE_TIMESTAMP = "Result of merge"; // NOI18N
/**
* Indicates a sticky tag.
*/
private static final String TAG = "T"; // NOI18N
/**
* Indicates a sticky date.
*/
private static final String DATE = "D"; // NOI18N
/**
* The instance of the date formatter for sticky dates.
*/
private static SimpleDateFormat stickyDateFormatter;
/**
* Returns the instance of the date formatter for sticky dates.
*/
private static SimpleDateFormat getStickyDateFormatter() {
if (stickyDateFormatter == null) {
stickyDateFormatter = new SimpleDateFormat("yyyy.MM.dd.hh.mm.ss"); // NOI18N
}
return stickyDateFormatter;
}
/**
* Indicates a binary file.
*/
private static final String BINARY_FILE = "-kb"; // NOI18N
/**
* Indicates that no user file is meant by the version details
*/
private static final String NO_USER_FILE = ""; // NOI18N
/**
* Indicates that a new user file is meant by the version details
*/
private static final String NEW_USER_FILE = "0"; // NOI18N
/**
* Indicates that the file is to be removed, in the version details
*/
private static final String REMOVE_USER_FILE = "-"; // NOI18N
/**
* The date formatter to be used for noting the Last Modified date in a file.
*/
private static SimpleDateFormat lastModifiedDateFormatter;
/**
* Returns the instance of the Last-Modified-Date-Formatter.
*/
public static SimpleDateFormat getLastModifiedDateFormatter() {
if (lastModifiedDateFormatter == null) {
lastModifiedDateFormatter = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US); // NOI18N
lastModifiedDateFormatter.setTimeZone(getTimeZone());
}
return lastModifiedDateFormatter;
}
/**
* All entries times are by defaulf in Zulu/GMT0
*/
public static TimeZone getTimeZone() {
return TimeZone.getTimeZone("GMT"); // NOI18N
}
/**
* Indicates that the file had conflicts.
*/
public static final char HAD_CONFLICTS = '+';
/**
* Indicates that the timestamp matches the file.
*/
public static final char TIMESTAMP_MATCHES_FILE = '=';
/**
* Indicates that the file had conflicts and timestamp matches. It likely means unresolved conflict.
*/
public static final String HAD_CONFLICTS_AND_TIMESTAMP_MATCHES_FILE = "+=";
/**
* Initial letter that indicates a directory entry.
*/
private static final String DIRECTORY_PREFIX = "D/";
/**
* The name of the file.
*/
private String name;
/**
* The revision. There are constants defined for no user file, new user file and user file has to be removed.
*/
private String revision;
/**
* The conflict information. There are constants defined for indicating that conflicts occurred and that the timestamp matches the file
*/
private String conflict;
/**
* The last modified date of the file.
*/
private Date lastModified;
/**
* The options for signifying keyword expansion.
*/
private String options;
/**
* The tag. May be present in place of the date information.
*/
private String tag;
/**
* The date. May be present in place of the tag information.
*/
private Date date;
/**
* Indicates whether the entry is for a directory.
*/
private boolean directory;
/**
* Construct a new Entry from a given entry line.
*/
public Entry(String entryLine) {
init(entryLine);
}
/**
* Construct a new blank Entry.
*/
public Entry() {
}
/**
* Initialise the Entry by parsing an entry line.
*
* @param entryLine
* the entry line in standard CVS format
*/
protected void init(String entryLine) {
// System.err.println("Constructing an entry line from: " + 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];
try {
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] > 0) {
// note that the parameters to substring are treated as follows:
// (inclusive, exclusive)
name = entryLine.substring(slashPositions[0] + 1, slashPositions[1]);
revision = entryLine.substring(slashPositions[1] + 1, slashPositions[2]);
if (slashPositions[3] - slashPositions[2] > 1) {
String conflict = entryLine.substring(slashPositions[2] + 1, slashPositions[3]);
setConflict(conflict);
}
if (slashPositions[4] - slashPositions[3] > 1) {
options = entryLine.substring(slashPositions[3] + 1, slashPositions[4]);
}
if (slashPositions[4] != entryLine.length() - 1) {
String tagOrDate = entryLine.substring(slashPositions[4] + 1);
if (tagOrDate.startsWith(TAG)) {
setTag(tagOrDate.substring(1));
} else if (tagOrDate.startsWith(DATE)) {
// TODO process date into something useful
// MK - I didn't notice any time conversions (according to timezone)
// So I just convert it from String to Date and back.
try {
String dateString = tagOrDate.substring(DATE.length());
Date stickyDate = getStickyDateFormatter().parse(dateString);
setDate(stickyDate);
} catch (ParseException exc) {
System.err.println("We got another inconsistency in the library's date formatting."); // NOI18N
}
}
}
}
} catch (Exception e) {
System.err.println("Error parsing entry line: " + e); // NOI18N
e.printStackTrace();
throw new IllegalArgumentException("Invalid entry line: " + // NOI18N
entryLine);
}
}
/**
* Get the name of the associated file.
*
* @return the file name
*/
public String getName() {
return name;
}
/**
* Set the name.
*
* @param theName
* the filename to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the revision.
*
* @return the revision
*/
public String getRevision() {
return revision;
}
/**
* Set the revision.
*
* @param theVersion
* the revision to set
*/
public void setRevision(String revision) {
this.revision = revision;
}
/**
* Get the last modification time.
*
* @return date.getTime() compatible with File.lastModified()
*/
public Date getLastModified() {
return lastModified;
}
/**
* Get the conflict information.
*
* @return the conflict String
*/
public String getConflict() {
return conflict;
}
/**
* Set the conflict information.
*
* @param theConflict
* the conflict information
*/
public void setConflict(String conflict) {
this.conflict = conflict;
this.lastModified = null;
if (conflict == null || conflict.equals(DUMMY_TIMESTAMP) || conflict.equals(MERGE_TIMESTAMP)
|| conflict.equals(DUMMY_TIMESTAMP_NEW_ENTRY)) {
return;
}
String dateString = conflict;
// Look for the position of + which indicates a conflict
int conflictIndex = dateString.indexOf(HAD_CONFLICTS);
if (conflictIndex >= 0) {
// if the timestamp matches the file, there will be an = following
// the +
int timeMatchIndex = dateString.indexOf(TIMESTAMP_MATCHES_FILE);
conflictIndex = Math.max(conflictIndex, timeMatchIndex);
}
// At this point the conflict index tells us where the real conflict
// string starts
if (conflictIndex >= 0) {
dateString = dateString.substring(conflictIndex + 1);
}
// if we have nothing after the = then don't try to parse it
if (dateString.length() == 0) {
return;
}
try {
this.lastModified = getLastModifiedDateFormatter().parse(dateString);
} catch (Exception ex) {
lastModified = null;
// System.err.println("[Entry] can't parse " + dateString); //NOI18N
}
}
/**
* Get the options information.
*
* @return the options details
*/
public String getOptions() {
return options;
}
/**
* Set the options information.
*
* @param theOptions
* the options
*/
public void setOptions(String options) {
this.options = options;
}
/**
* Get the sticky information. It's either a tag, a date or null.
*/
public String getStickyInformation() {
if (tag != null) {
return tag;
}
return getDateFormatted();
}
/**
* Get the sticky tag information. May return null if no tag information was present. If so, you should check for date information. Note
* that tag and date information cannot both be present.
*
* @return the tag, or null if none is present
*/
public String getTag() {
return tag;
}
/**
* Set the sticky tag information. Setting this will remove any date information that is set.
*
* @param theTag
* the tag information
*/
public void setTag(String tag) {
this.tag = tag;
date = null;
}
/**
* Get sticky date information. May return null if no date information is available. If so, you should check for tag informaton. Note
* that tag and date information cannot both be present.
*
* @return the date, or null if none is present
*/
public Date getDate() {
return date;
}
/**
* Gets the sticky date information as a string in the appropriate format. Returns null if there ain't a sticky date assigned.
*/
public String getDateFormatted() {
if (getDate() == null) {
return null;
}
SimpleDateFormat format = getStickyDateFormatter();
String dateFormatted = format.format(getDate());
return dateFormatted;
}
/**
* Set the sticky date information. Note that setting this will remove any tag information that is currently set.
*
* @param theDate
* the date to use.
*/
public void setDate(Date date) {
this.date = date;
tag = null;
}
/**
* Determines whether the entry has a date (as opposed to a tag).
*
* @return true if the entry has a date, false otherwise
*/
public boolean hasDate() {
return date != null;
}
/**
* Determines whether the entry has a tag (as opposed to a date).
*
* @return true if the entry has a tag, false otherwise
*/
public boolean hasTag() {
return tag != null;
}
/**
* Determines whether the file is a binary file.
*/
public boolean isBinary() {
return options != null && options.equals(BINARY_FILE);
}
/**
* Determine whether there is no user file of that name.
*
* @return true if there is no user file of that name
*/
public boolean isNoUserFile() {
return revision == null || revision.equals(NO_USER_FILE);
}
/**
* Determine whether there is a new user file of that name.
*
* @return true if there is a new user file with that name
*/
public boolean isNewUserFile() {
return revision != null && revision.startsWith(NEW_USER_FILE);
}
/**
* Determine whether the user file of that name is to be removed.
*
* @return true if the user file with this name is to be removed
*/
public boolean isUserFileToBeRemoved() {
return revision != null && revision.startsWith(REMOVE_USER_FILE);
}
/**
* Determines whether the entry is valid. A valid entry has at least a name.
*/
public boolean isValid() {
return getName() != null && getName().length() > 0;
}
/**
* Determine whether the entry refers to a directory.
*/
public boolean isDirectory() {
return directory;
}
/**
* Set whether the entry refers to a directory.
*/
public void setDirectory(boolean directory) {
this.directory = directory;
}
/**
* Determine whether there were any conflicts.
*
* @return true if there were conflicts, false otherwise
*/
public boolean hadConflicts() {
if (conflict != null) {
return conflict.indexOf(HAD_CONFLICTS) >= 0;
} else {
return false;
}
}
/**
* Determine whether the timestamp matches the file.
*
* @return true if the timpestamp does match the file, false otherwise
*/
public boolean timestampMatchesFile() {
return conflict.charAt(1) == TIMESTAMP_MATCHES_FILE;
}
/**
* Create a string representation of the entry line. Create the standard CVS 1.10 entry line format.
* <p>
* Th eline format is suitable for writing into <tt>CVS/Entries</tt> file. Conflict one must be transformed before sending to wire
* {@link org.netbeans.lib.cvsclient.command.BasicCommand#sendEntryAndModifiedRequests}.
*/
@Override
public String toString() {
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 (name != null) {
buf.append(name);
buf.append('/');
if (revision != null) {
buf.append(revision);
}
buf.append('/');
if (conflict != null) {
buf.append(conflict);
}
buf.append('/');
if (options != null) {
buf.append(options);
}
buf.append('/');
// TODO: put in tag_or_date section!!!
// MK - Added. Based on assumption "There can be only one"
if (tag != null && date == null) {
if ("HEAD".equals(tag) == false) {
buf.append(TAG);
buf.append(getTag());
}
} else if (tag == null && date != null) {
String dateString = getDateFormatted();
buf.append(DATE);
buf.append(dateString);
}
}
return buf.toString();
}
}