0001: /*
0002: ** Java cvs client library package.
0003: ** Copyright (c) 1997-2002 by Timothy Gerard Endres
0004: **
0005: ** This program is free software.
0006: **
0007: ** You may redistribute it and/or modify it under the terms of the GNU
0008: ** Library General Public License (LGPL) as published by the Free Software
0009: ** Foundation.
0010: **
0011: ** Version 2 of the license should be included with this distribution in
0012: ** the file LICENSE.txt, as well as License.html. If the license is not
0013: ** included with this distribution, you may find a copy at the FSF web
0014: ** site at 'www.gnu.org' or 'www.fsf.org', or you may write to the Free
0015: ** Software Foundation at 59 Temple Place - Suite 330, Boston, MA 02111 USA.
0016: **
0017: ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
0018: ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
0019: ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
0020: ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
0021: ** REDISTRIBUTION OF THIS SOFTWARE.
0022: **
0023: */
0024:
0025: package com.ice.cvsc;
0026:
0027: import java.io.*;
0028: import java.lang.*;
0029: import java.text.*;
0030: import java.util.*;
0031:
0032: /**
0033: * CVSEntry implements the concept of a CVS Entry. Traditionally,
0034: * a CVS Entry is a line in an 'Entries' file in a 'CVS' admin
0035: * directory. A CVSEntry represents a CVS file that is checked
0036: * in or being checked in.
0037: *
0038: * CVSEntry objects contain all of the relavent information about
0039: * a CVS file, such as its name, check-out time, modification status,
0040: * local pathname, repository, etc.
0041: *
0042: * @version $Revision: 2.11 $
0043: * @author Timothy Gerard Endres, <a href="mailto:time@ice.com">time@ice.com</a>.
0044: * @see CVSClient
0045: * @see CVSProject
0046: * @see CVSEntryVector
0047: */
0048:
0049: public class CVSEntry extends Object implements Cloneable {
0050: static public final String RCS_ID = "$Id: CVSEntry.java,v 2.11 2003/07/27 01:08:32 time Exp $";
0051: static public final String RCS_REV = "$Revision: 2.11 $";
0052:
0053: /**
0054: * True if this entry is valid.
0055: */
0056: private boolean valid;
0057:
0058: /**
0059: * True if this entry is a directory entry.
0060: */
0061: private boolean isDir;
0062:
0063: /**
0064: * If this entry is a directory entry, then this is
0065: * the entry vector for the entries in that directory.
0066: */
0067: private CVSEntryVector entryList;
0068:
0069: /**
0070: * The full path of the repository as it comes from the
0071: * 'Repository' file in the 'CVS' administration directory.
0072: */
0073: private String repository;
0074:
0075: /**
0076: * This is the entry's 'local directory'. This is the
0077: * local directory that is sent as the 'pathname' of many
0078: * responses, and is sent with 'Directory' requests.
0079: */
0080: private String localDirectory;
0081:
0082: private boolean isNoUserFile;
0083: private boolean isNewUserFile;
0084: private boolean isToBeRemoved;
0085: private boolean isDirty;
0086:
0087: /**
0088: * If this is set, then when the getServerEntryLine() is
0089: * called, we will ignore the file's modification status
0090: * and mark the entry as "/modified/". This was added to
0091: * support jcvsweb, but is currently unused.
0092: */
0093: private boolean forceModified;
0094:
0095: /**
0096: * If this is set, then when the getServerEntryLine() is
0097: * called, we will ignore the "exists" flag and pretend
0098: * that the file does NOT exist. This was added to support
0099: * jcvsweb's need for updates of existing files for viewing
0100: * purposes.
0101: */
0102: private boolean forceNoExistence;
0103:
0104: private CVSMode mode;
0105:
0106: private CVSTimestamp tsCache;
0107: private CVSTimestamp cfCache;
0108:
0109: private String name;
0110: private String version;
0111: private String timestamp;
0112: private String conflict;
0113: private String options;
0114: private String tag;
0115: private String date;
0116:
0117: private Vector childListeners;
0118:
0119: public CVSEntry() {
0120: super ();
0121:
0122: this .valid = false;
0123: this .isDir = false;
0124:
0125: this .repository = null;
0126: this .localDirectory = null;
0127: this .entryList = null;
0128:
0129: this .isNoUserFile = false;
0130: this .isNewUserFile = false;
0131: this .isToBeRemoved = false;
0132: this .isDirty = false;
0133: this .forceModified = false;
0134: this .forceNoExistence = false;
0135:
0136: this .mode = null;
0137: this .tsCache = null;
0138: this .cfCache = null;
0139:
0140: this .name = "";
0141: this .version = "";
0142: this .timestamp = "";
0143: this .conflict = null;
0144: this .options = null;
0145: this .tag = null;
0146: this .date = null;
0147:
0148: this .childListeners = new Vector();
0149: }
0150:
0151: public boolean isValid() {
0152: return this .valid;
0153: }
0154:
0155: public void setValid(boolean valid) {
0156: this .valid = valid;
0157: }
0158:
0159: public boolean isDirty() {
0160: return this .isDirty;
0161: }
0162:
0163: public void setDirty(boolean dirty) {
0164: this .isDirty = dirty;
0165: }
0166:
0167: public boolean isForceModified() {
0168: return this .forceModified;
0169: }
0170:
0171: public void setForceModified(boolean forceModified) {
0172: this .forceModified = forceModified;
0173: }
0174:
0175: public boolean isForceNoExistence() {
0176: return this .forceNoExistence;
0177: }
0178:
0179: public void setForceNoExistence(boolean forceNoExistence) {
0180: this .forceNoExistence = forceNoExistence;
0181: }
0182:
0183: public String getName() {
0184: return this .name;
0185: }
0186:
0187: public void setName(String name) {
0188: this .name = name;
0189: }
0190:
0191: public String getRepository() {
0192: return this .repository;
0193: }
0194:
0195: public void setRepository(String repository) {
0196: this .repository = CVSCUtilities.stripFinalSlash(repository);
0197: }
0198:
0199: // 'LocalDirectory' here is in the sense of the
0200: // local-directory returned with cvs server responses!
0201: public String getLocalDirectory() {
0202: return this .localDirectory;
0203: }
0204:
0205: public void setLocalDirectory(String directory) {
0206: this .localDirectory = CVSCUtilities.ensureFinalSlash(directory);
0207: }
0208:
0209: public String getFullName() {
0210: if (this .isDirectory())
0211: return this .getLocalDirectory();
0212: else
0213: return (this .getLocalDirectory() + this .getName());
0214: }
0215:
0216: private String stripDotSlashPrefix(String path) {
0217: if (path.startsWith("./"))
0218: path = path.substring(2);
0219: return path;
0220: }
0221:
0222: /**
0223: * This method was added when we finally decided to bite the
0224: * bullet and change the naming scheme to work "correctly".
0225: * This will return the same string as getFullName(), except
0226: * that the "./" prefix is removed. This is preferable for
0227: * building file path names, hence the name.
0228: */
0229: public String getFullPathName() {
0230: return this .stripDotSlashPrefix(this .getFullName());
0231: }
0232:
0233: /**
0234: * This method was added when we finally decided to bite the
0235: * bullet and change the naming scheme to work "correctly".
0236: * This will return the same string as getLocalDirectory(),
0237: * except that the "./" prefix is removed. This is preferable
0238: * for building file path names, hence the name.
0239: */
0240: public String getLocalPathName() {
0241: return this .stripDotSlashPrefix(this .getLocalDirectory());
0242: }
0243:
0244: public String getRepositoryName() {
0245: return (this .getRepository() + this .getName());
0246: }
0247:
0248: public String getArgumentName() {
0249: return this .getFullName();
0250: }
0251:
0252: /**
0253: * Provides the directory-ness of this entry.
0254: *
0255: * @return True if this entry is a directory, else false.
0256: */
0257: public boolean isDirectory() {
0258: return this .isDir;
0259: }
0260:
0261: public void appendEntry(CVSEntry entry) {
0262: this .entryList.appendEntry(entry);
0263: this .fireChildAddedEvent(this .new ChildEvent(this .entryList
0264: .size() - 1, entry));
0265: }
0266:
0267: public boolean removeEntry(CVSEntry entry) {
0268: boolean result = false;
0269:
0270: int index = this .entryList.indexOf(entry);
0271: if (index != -1) {
0272: result = true;
0273: this .isDirty = true;
0274: CVSEntry child = this .entryList.entryAt(index);
0275: this .entryList.removeElementAt(index);
0276: this
0277: .fireChildRemovedEvent(this .new ChildEvent(index,
0278: child));
0279: }
0280:
0281: return result;
0282: }
0283:
0284: public boolean removeEntry(String entryName) {
0285: boolean result = false;
0286:
0287: for (int i = 0, sz = this .entryList.size(); i < sz; ++i) {
0288: CVSEntry entry = this .entryList.entryAt(i);
0289: if (entryName.equals(entry.getName())) {
0290: result = true;
0291: this .isDirty = true;
0292: this .entryList.removeElementAt(i);
0293: this
0294: .fireChildRemovedEvent(this .new ChildEvent(i,
0295: entry));
0296: break;
0297: }
0298: }
0299:
0300: return result;
0301: }
0302:
0303: public void removeAllEntries() {
0304: if (this .isDirectory()) {
0305: if (this .entryList != null) {
0306: this .entryList.removeAllEntries();
0307: this
0308: .fireChildRemovedEvent(this .new ChildEvent(-1,
0309: null));
0310: }
0311: }
0312: }
0313:
0314: public CVSEntry locateEntry(String name) {
0315: return this .entryList.locateEntry(name);
0316: }
0317:
0318: public CVSEntryVector getEntryList() {
0319: return this .entryList;
0320: }
0321:
0322: /**
0323: * This method will make this entry a directory entry
0324: * and establish its entry list with the list passed
0325: * in the parameter. This is the <strong>only</strong> means of making
0326: * a CVSEntry become a <em>directory entry</em>.
0327: *
0328: * @param entryList The directory's entry list.
0329: */
0330: public void setDirectoryEntryList(CVSEntryVector entryList) {
0331: if (entryList != null) {
0332: this .isDir = true;
0333: this .entryList = entryList;
0334: }
0335: }
0336:
0337: public String getVersion() {
0338: return this .version;
0339: }
0340:
0341: public void setVersion(String version) {
0342: this .isNoUserFile = false;
0343: this .isNewUserFile = false;
0344: this .isToBeRemoved = false;
0345:
0346: if (version == null || version.length() == 0) {
0347: this .isNoUserFile = true;
0348: this .version = "";
0349: } else if (version.startsWith("-")) {
0350: this .isToBeRemoved = true;
0351: this .version = version.substring(1);
0352: } else if (version.startsWith("0")) // that's a zero
0353: {
0354: this .isNewUserFile = true;
0355: this .version = version.substring(1);
0356: } else {
0357: this .version = version;
0358: }
0359: }
0360:
0361: public void markForRemoval(boolean markState) {
0362: this .isToBeRemoved = markState;
0363: }
0364:
0365: private CVSTimestamp parseTimestamp(String stampStr) {
0366: CVSTimestamp result = new CVSTimestamp(0);
0367:
0368: if (stampStr != null) {
0369: CVSTimestampFormat stamper = CVSTimestampFormat
0370: .getInstance();
0371:
0372: try {
0373: result = stamper.parse(stampStr);
0374: } catch (ParseException ex) {
0375: result = new CVSTimestamp(0);
0376: CVSTracer.traceWithStack("CVSEntry.parseTimestamp: "
0377: + "could not parse timestamp: '" + stampStr
0378: + "' - " + ex.getMessage());
0379: }
0380: }
0381:
0382: return result;
0383: }
0384:
0385: /**
0386: * The cached CVSTimestamp (a subclass of Date), or null.
0387: */
0388: public CVSTimestamp getCVSTime() {
0389: return this .tsCache;
0390: }
0391:
0392: public String getTimestamp() {
0393: return this .timestamp;
0394: }
0395:
0396: public String completeTimestamp() {
0397: return this .timestamp
0398: + (this .conflict == null ? "" : ("+" + this .conflict));
0399: }
0400:
0401: public String getTerseTimestamp() {
0402: if (this .tsCache == null) {
0403: this .tsCache = this .parseTimestamp(this .timestamp);
0404: }
0405:
0406: if (this .tsCache == null) {
0407: return this .timestamp; // punt!
0408: } else {
0409: CVSTimestampFormat stamper = CVSTimestampFormat
0410: .getInstance();
0411:
0412: return stamper.formatTerse(this .tsCache);
0413: }
0414: }
0415:
0416: /**
0417: * Set the timestamp of this entry to that of the modification
0418: * time of the file passed to this method.
0419: *
0420: * <b>NOTE</b> There is an issue with timestamps between Java
0421: * and CVS. Specifically, Java time uses millisecond resolution
0422: * and CVS time uses second resolution. The problem arises when
0423: * a file is "sync-ed" with the CVS/Entries timestamp and the
0424: * file's modtime is stored with non-zero milliseconds. When we
0425: * later compare the file's modtime to that of the CVSEntry's
0426: * timestamp, they will differ by the milliseconds quantity.
0427: * To solve this problem, we strip milliseconds from any file
0428: * timestamp coming into jCVS. This forces all of the timestamps
0429: * to have zero millisecond digits, which will compare properly
0430: * with the CVS timestamps.
0431: */
0432:
0433: public void setTimestamp(File entryFile) {
0434: // FIRST strip the millisecond digits and make them zero!
0435: long mTime = entryFile.lastModified();
0436: mTime = (mTime / 1000) * 1000;
0437:
0438: CVSTimestamp stamp = new CVSTimestamp(mTime);
0439:
0440: CVSTimestampFormat stamper = CVSTimestampFormat.getInstance();
0441:
0442: String stampStr = stamper.format(stamp);
0443:
0444: this .setTimestamp(stampStr);
0445: }
0446:
0447: public void setTimestamp(String timeStamp) {
0448: // REVIEW
0449: if (timeStamp == null) {
0450: CVSTracer.traceWithStack("NULL TIMESTAMP!!!");
0451: timeStamp = "";
0452: }
0453:
0454: String tstamp = new String(timeStamp);
0455:
0456: this .cfCache = null;
0457: this .conflict = null;
0458:
0459: if (tstamp.length() < 1) {
0460: this .timestamp = "";
0461: this .tsCache = null;
0462: } else if (tstamp.startsWith("+")) {
0463: // REVIEW - leave the timestamp in place...
0464: // We have received a "+conflict" format, which
0465: // typically only comes from the server.
0466: this .conflict = tstamp.substring(1);
0467: if (this .conflict.equals("=")) {
0468: // In this case, the server is indicating that the
0469: // file is "going to be equal" once the 'Merged' handling
0470: // is completed. To retain the "inConflict" nature of
0471: // the entry, we will simply set the conflict to an
0472: // empty string (not null), as the conflict will be
0473: // set very shortly as a result of the 'Merged' handling.
0474: //
0475: this .conflict = "";
0476: }
0477: } else {
0478: int index = tstamp.indexOf('+');
0479: if (index < 0) {
0480: // Only the timestamp is provided (no '+').
0481: if (tstamp.startsWith("Initial ")) {
0482: // This file was "added" but not committed,
0483: // timestamp is irrelevant
0484: this .timestamp = "";
0485: this .tsCache = null;
0486: } else if (tstamp.equals("Result of merge")) {
0487: // This file was "merged" timestamp must show modified
0488: this .timestamp = "";
0489: this .tsCache = null;
0490: } else if (!tstamp.equals(this .timestamp)) {
0491: // Only update these if it is different
0492: this .tsCache = null; // signal need to parse!
0493: this .timestamp = tstamp;
0494: }
0495: } else {
0496: // The "timestamp+conflict" case.
0497: // This should <em>only</em> comes from an Entries
0498: // file, and should never come from the server.
0499: this .conflict = tstamp.substring(index + 1);
0500: tstamp = tstamp.substring(0, index);
0501: if (!tstamp.equals(this .timestamp)) {
0502: // Only update these if it is different
0503: this .tsCache = null; // signal need to parse!
0504: //
0505: // REVIEW
0506: // UNDONE
0507: // This next check really should be more "generic"
0508: // in the sense of "if ( ! validTimestamp( tstamp ) )".
0509: //
0510: if (tstamp.equals("Result of merge")) {
0511: // REVIEW should we always set to conflict?
0512: // If timestamp is empty, use the conflict...
0513: if ((this .timestamp == null || this .timestamp
0514: .length() == 0)
0515: && this .conflict.length() > 0) {
0516: this .timestamp = this .conflict;
0517: }
0518: } else {
0519: this .timestamp = tstamp;
0520: }
0521: }
0522: }
0523: }
0524:
0525: CVSTimestampFormat stamper = CVSTimestampFormat.getInstance();
0526:
0527: // If tsCache is set to null, we need to update it...
0528: if (this .tsCache == null && this .timestamp.length() > 0) {
0529: try {
0530: this .tsCache = stamper.parse(this .timestamp);
0531: } catch (ParseException ex) {
0532: this .tsCache = null;
0533: if (false) // in normal operations, this is ok
0534: CVSTracer
0535: .traceWithStack("could not parse entries timestamp (cache): '"
0536: + this .timestamp
0537: + "' - "
0538: + ex.getMessage());
0539: }
0540: }
0541:
0542: // If conflict is not null, we need to update cfCache...
0543: if (this .conflict != null && this .conflict.length() > 0) {
0544: try {
0545: this .cfCache = stamper.parse(this .conflict);
0546: } catch (ParseException ex) {
0547: this .cfCache = null;
0548: if (false) // in normal operations, this is ok
0549: CVSTracer
0550: .traceWithStack("could not parse entries conflict (cache): '"
0551: + this .conflict
0552: + "' - "
0553: + ex.getMessage());
0554: }
0555: }
0556: if (false)
0557: CVSTracer.traceIf(true,
0558: "CVSEntry.setTimestamp: '"
0559: + this .getName()
0560: + "' - '"
0561: + timeStamp
0562: + "'\n timestamp '"
0563: + this .timestamp
0564: + "' tsCache '"
0565: + (this .tsCache == null ? "(not set)"
0566: : "(set)")
0567: + "'\n conflict '"
0568: + (this .conflict == null ? "(null)"
0569: : this .conflict)
0570: + "' cfCache '"
0571: + (this .cfCache == null ? "(not set)"
0572: : "(set)") + "'");
0573: }
0574:
0575: /**
0576: * <b>NOTE</b>Refer to note under setTimestamp( File ) pertaining
0577: * to the resolution of file times and CVS timestamps.
0578: */
0579: public void setConflict(File entryFile) {
0580: // FIRST strip the millisecond digits and make them zero!
0581: long mTime = entryFile.lastModified();
0582: mTime = (mTime / 1000) * 1000;
0583:
0584: this .cfCache = new CVSTimestamp(mTime);
0585:
0586: CVSTimestamp stamp = new CVSTimestamp(this .cfCache.getTime());
0587:
0588: CVSTimestampFormat stamper = CVSTimestampFormat.getInstance();
0589:
0590: String stampStr = stamper.format(stamp);
0591:
0592: this .conflict = stampStr;
0593: }
0594:
0595: public String getOptions() {
0596: return this .options;
0597: }
0598:
0599: public void setOptions(String options) {
0600: this .options = options;
0601: }
0602:
0603: public boolean isBinary() {
0604: return (this .options.indexOf("-kb") != -1);
0605: }
0606:
0607: public String getTag() {
0608: return this .tag;
0609: }
0610:
0611: public void setTag(String tag) {
0612: this .tag = tag;
0613: this .date = null;
0614: }
0615:
0616: public String getDate() {
0617: return this .date;
0618: }
0619:
0620: public void setDate(String date) {
0621: this .tag = null;
0622: this .date = date;
0623: }
0624:
0625: public CVSMode getMode() {
0626: return this .mode;
0627: }
0628:
0629: public void setMode(CVSMode mode) {
0630: this .mode = mode;
0631: }
0632:
0633: public String getModeLine() {
0634: return (this .mode == null ? "u=rw,g=r,o=r" // UNDONE - better idea?
0635: : this .mode.getModeLine());
0636: }
0637:
0638: public boolean isNoUserFile() {
0639: return this .isNoUserFile;
0640: }
0641:
0642: public void setNoUserFile(boolean isNo) {
0643: this .isNoUserFile = isNo;
0644: }
0645:
0646: public boolean isInConflict() {
0647: return (this .conflict != null);
0648: }
0649:
0650: private String getConflict() {
0651: return this .conflict;
0652: }
0653:
0654: public boolean isNewUserFile() {
0655: return this .isNewUserFile;
0656: }
0657:
0658: public void setNewUserFile(boolean isNew) {
0659: this .isNewUserFile = isNew;
0660: }
0661:
0662: public boolean isToBeRemoved() {
0663: return this .isToBeRemoved;
0664: }
0665:
0666: public void setToBeRemoved(boolean toBe) {
0667: this .isToBeRemoved = toBe;
0668: }
0669:
0670: public boolean isLocalFileModified(File localFile) {
0671: if (this .forceModified) {
0672: System.err.println("CVSENTRY: force MOD? "
0673: + this .forceModified);
0674: return true;
0675: }
0676:
0677: // REVIEW is this the best return value for this case?
0678: if (this .tsCache == null)
0679: return true;
0680:
0681: return !this .tsCache.equalsTime(localFile.lastModified());
0682: }
0683:
0684: // UNDONE - all of the "ParseException( , offset's" are zero!
0685:
0686: private String parseAToken(StringTokenizer toker) {
0687: String token = null;
0688:
0689: try {
0690: token = toker.nextToken();
0691: } catch (NoSuchElementException ex) {
0692: token = null;
0693: }
0694:
0695: return token;
0696: }
0697:
0698: public boolean parseEntryLine(String parseLine, boolean fromServer)
0699: throws ParseException {
0700: String token = null;
0701: String nameToke = null;
0702: String versionToke = null;
0703: String conflictToke = null;
0704: String optionsToke = null;
0705: String tagToke = null;
0706:
0707: this .valid = false;
0708:
0709: String entryLine = parseLine;
0710:
0711: // Strip the 'D' from 'Directory' entries
0712: if (entryLine.startsWith("D/")) {
0713: this .isDir = true;
0714: entryLine = entryLine.substring(1);
0715: }
0716:
0717: StringTokenizer toker = new StringTokenizer(entryLine, "/",
0718: true);
0719:
0720: int tokeCount = toker.countTokens();
0721:
0722: if (tokeCount < 6) {
0723: throw new ParseException(
0724: "not enough tokens in entries line "
0725: + "(min 6, parsed " + tokeCount + ")", 0);
0726: }
0727:
0728: token = this .parseAToken(toker);
0729: if (token == null || !token.equals("/"))
0730: throw new ParseException(
0731: "could not parse name's starting slash", 0);
0732:
0733: nameToke = this .parseAToken(toker);
0734: if (nameToke == null) {
0735: throw new ParseException("could not parse entry name", 0);
0736: } else if (nameToke.equals("/")) {
0737: throw new ParseException("entry has an empty name", 0);
0738: } else {
0739: token = this .parseAToken(toker);
0740: if (token == null || !token.equals("/"))
0741: throw new ParseException(
0742: "could not parse version's starting slash", 0);
0743: }
0744:
0745: versionToke = this .parseAToken(toker);
0746: if (versionToke == null) {
0747: throw new ParseException(
0748: "out of tokens getting version field", 0);
0749: } else if (versionToke.equals("/")) {
0750: versionToke = "";
0751: } else {
0752: token = this .parseAToken(toker);
0753: if (token == null || !token.equals("/"))
0754: throw new ParseException(
0755: "could not parse conflict's starting slash", 0);
0756: }
0757:
0758: conflictToke = this .parseAToken(toker);
0759: if (conflictToke == null) {
0760: throw new ParseException(
0761: "out of tokens getting conflict field", 0);
0762: } else if (conflictToke.equals("/")) {
0763: conflictToke = "";
0764: } else {
0765: token = this .parseAToken(toker);
0766: if (token == null || !token.equals("/"))
0767: throw new ParseException(
0768: "could not parse options' starting slash", 0);
0769: }
0770:
0771: optionsToke = this .parseAToken(toker);
0772: if (optionsToke == null) {
0773: throw new ParseException(
0774: "out of tokens getting options field", 0);
0775: } else if (optionsToke.equals("/")) {
0776: optionsToke = "";
0777: } else {
0778: token = this .parseAToken(toker);
0779: if (token == null || !token.equals("/"))
0780: throw new ParseException(
0781: "could not parse tag's starting slash", 0);
0782: }
0783:
0784: tagToke = this .parseAToken(toker);
0785: if (tagToke == null || tagToke.equals("/")) {
0786: tagToke = "";
0787: }
0788:
0789: this .valid = true;
0790:
0791: if (fromServer && conflictToke.length() > 0
0792: && !conflictToke.startsWith("+")) {
0793: // We silently ignore conflicts that don't start with '+'
0794: // when they come from the server.
0795: conflictToke = "";
0796: }
0797:
0798: this .setName(nameToke);
0799: this .setVersion(versionToke);
0800: this .setTimestamp(conflictToke);
0801: this .setOptions(optionsToke);
0802:
0803: if (tagToke == null || tagToke.length() < 1) {
0804: this .setTag(null);
0805: } else {
0806: if (tagToke.startsWith("D")) {
0807: this .setDate(tagToke.substring(1));
0808: } else {
0809: this .setTag(tagToke.substring(1));
0810: }
0811: }
0812:
0813: return this .valid;
0814: }
0815:
0816: public String padString(String str, int width) {
0817: int i;
0818: StringBuffer result = new StringBuffer(width);
0819:
0820: result.append(str);
0821: for (i = result.length() - 1; i < width; ++i)
0822: result.append(" ");
0823:
0824: return result.toString();
0825: }
0826:
0827: public String getAdminEntryLine() {
0828: if (this .isDirectory()) {
0829: // REVIEW should we be carrying along options & tags?!
0830: return "D/" + this .name + "////";
0831: }
0832:
0833: StringBuffer result = new StringBuffer("");
0834:
0835: result.append("/" + this .name + "/");
0836:
0837: if (!this .isNoUserFile()) {
0838: if (this .isNewUserFile()) {
0839: result.append("0"); // that's a zero
0840: } else {
0841: if (this .isToBeRemoved())
0842: result.append("-");
0843:
0844: if (this .version != null)
0845: result.append(this .version);
0846: }
0847: }
0848:
0849: result.append("/");
0850:
0851: if (this .isNewUserFile()) {
0852: result.append("Initial " + this .getName());
0853: } else {
0854: result.append(this .timestamp);
0855:
0856: if (this .isInConflict()) {
0857: result.append("+" + this .conflict);
0858: }
0859: }
0860:
0861: result.append("/");
0862:
0863: if (this .options != null)
0864: result.append(this .options);
0865:
0866: result.append("/");
0867:
0868: if (this .tag != null) {
0869: result.append("T" + this .tag);
0870: } else if (this .date != null) {
0871: result.append("D" + this .date);
0872: }
0873:
0874: return result.toString();
0875: }
0876:
0877: public String getServerEntryLine(boolean exists, boolean isModified) {
0878: if (this .isDirectory()) {
0879: // REVIEW should we be carrying along options & tags?!
0880: return "/" + this .name + "////";
0881: }
0882:
0883: StringBuffer result = new StringBuffer("");
0884:
0885: result.append("/" + this .name + "/");
0886:
0887: if (!this .isNoUserFile()) {
0888: if (this .isNewUserFile()) {
0889: result.append("0"); // that's a zero
0890: } else {
0891: if (this .isToBeRemoved())
0892: result.append("-");
0893:
0894: if (this .version != null && !this .forceNoExistence)
0895: result.append(this .version);
0896: }
0897: }
0898:
0899: result.append("/");
0900:
0901: if (this .isNewUserFile()) {
0902: result.append("Initial " + this .getName());
0903: } else if (exists && !this .forceNoExistence) {
0904: if (this .isInConflict()) {
0905: result.append("+");
0906: }
0907:
0908: if (isModified || forceModified)
0909: result.append("modified");
0910: else
0911: result.append("=");
0912: }
0913:
0914: result.append("/");
0915:
0916: if (this .options != null)
0917: result.append(this .options);
0918:
0919: result.append("/");
0920:
0921: if (this .tag != null && !this .forceNoExistence)
0922: result.append("T" + this .tag);
0923: else if (this .date != null && !this .forceNoExistence)
0924: result.append("D" + this .date);
0925:
0926: CVSTracer.traceIf(false, "getServerEntryLine: '"
0927: + result.toString() + "'");
0928:
0929: return result.toString();
0930: }
0931:
0932: public String toString() {
0933: return "[ " + this .getFullName() + ","
0934: + this .getAdminEntryLine() + " ]";
0935: }
0936:
0937: /**
0938: * Adds all of the file entries in this directory entry to the vector supplied.
0939: *
0940: * @param vector The vector to add the file entries to.
0941: */
0942:
0943: public void addFileEntries(CVSEntryVector vector) {
0944: CVSEntryVector entries = this .getEntryList();
0945: for (int idx = 0; idx < entries.size(); ++idx) {
0946: CVSEntry entry = entries.entryAt(idx);
0947: if (!entry.isDirectory())
0948: vector.appendEntry(entry);
0949: }
0950: }
0951:
0952: /**
0953: * Adds all of the file entries in this directory entry, as well as every
0954: * file entry in every subdirectory entry recursively.
0955: *
0956: * @param vector The vector to add the file entries to.
0957: */
0958:
0959: public void addAllSubTreeEntries(CVSEntryVector vector) {
0960: CVSEntryVector dirs = new CVSEntryVector();
0961: CVSEntryVector list = this .getEntryList();
0962:
0963: // First, append all of the files, caching directories...
0964: for (int idx = 0; idx < list.size(); ++idx) {
0965: CVSEntry entry = list.entryAt(idx);
0966: if (entry.isDirectory())
0967: dirs.appendEntry(entry);
0968: else
0969: vector.appendEntry(entry);
0970: }
0971:
0972: // Now, process all of the cached directories...
0973: for (int idx = 0; idx < dirs.size(); ++idx) {
0974: CVSEntry entry = dirs.entryAt(idx);
0975: entry.addAllSubTreeEntries(vector);
0976: }
0977: }
0978:
0979: public class ChildEvent {
0980: CVSEntry entry;
0981: CVSEntry childEntry;
0982: int childIndex;
0983:
0984: /**
0985: * The source is the CVSEntry that parent's the child.
0986: * The index is the index of the affected child.
0987: */
0988:
0989: public ChildEvent(int index, CVSEntry child) {
0990: this .childIndex = index;
0991: this .childEntry = child;
0992: }
0993:
0994: public CVSEntry getCVSEntry() {
0995: return CVSEntry.this ;
0996: }
0997:
0998: public int getChildIndex() {
0999: return this .childIndex;
1000: }
1001:
1002: public CVSEntry getChildEntry() {
1003: return this .childEntry;
1004: }
1005:
1006: }
1007:
1008: public interface ChildEventListener {
1009: public void cvsEntryAddedChild(CVSEntry.ChildEvent event);
1010:
1011: public void cvsEntryRemovedChild(CVSEntry.ChildEvent event);
1012: }
1013:
1014: protected void fireChildAddedEvent(ChildEvent event) {
1015: // Process the listeners last to first, notifying
1016: // those that are interested in this event
1017: for (int i = this .childListeners.size() - 1; i >= 0; --i) {
1018: ((ChildEventListener) this .childListeners.elementAt(i))
1019: .cvsEntryAddedChild(event);
1020: }
1021: }
1022:
1023: protected void fireChildRemovedEvent(ChildEvent event) {
1024: // Process the listeners last to first, notifying
1025: // those that are interested in this event
1026: for (int i = this .childListeners.size() - 1; i >= 0; --i) {
1027: ((ChildEventListener) this .childListeners.elementAt(i))
1028: .cvsEntryRemovedChild(event);
1029: }
1030: }
1031:
1032: public void addChildEventListener(ChildEventListener l) {
1033: this .childListeners.addElement(l);
1034: }
1035:
1036: public void removeChildEventListener(ChildEventListener l) {
1037: this .childListeners.removeElement(l);
1038: }
1039:
1040: public String dumpString() {
1041: return this .dumpString("");
1042: }
1043:
1044: public String dumpString(String prefix) {
1045: return prefix + "CVSEntry: " + super .toString() + "\n" + prefix
1046: + " Name: " + this .getName() + "\n" + prefix
1047: + " FullName: " + this .getFullName() + "\n" + prefix
1048: + " LocalDir: " + this .getLocalDirectory() + "\n"
1049: + prefix + " Repository: " + this .getRepository()
1050: + "\n" + prefix + " Timestamp: "
1051: + this .getTimestamp() + "\n" + prefix + " Conflict: "
1052: + this.getConflict();
1053: }
1054:
1055: }
|