| /***************************************************************************** |
| * 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(); |
| } |
| } |