KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ccvs > core > syncinfo > ResourceSyncInfo


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  * Red Hat Incorporated - is/setExecutable() code
11  *******************************************************************************/

12 package org.eclipse.team.internal.ccvs.core.syncinfo;
13
14  
15 import java.text.ParseException JavaDoc;
16 import java.util.Date JavaDoc;
17
18 import org.eclipse.core.runtime.Assert;
19 import org.eclipse.osgi.util.NLS;
20 import org.eclipse.team.internal.ccvs.core.*;
21 import org.eclipse.team.internal.ccvs.core.client.Command.KSubstOption;
22 import org.eclipse.team.internal.ccvs.core.resources.CVSEntryLineTag;
23 import org.eclipse.team.internal.ccvs.core.util.CVSDateFormatter;
24 import org.eclipse.team.internal.ccvs.core.util.Util;
25
26 /**
27  * Value (immutable) object that represents workspace state information about a resource contained in
28  * a CVS repository. It is a specialized representation of a line in the CVS/Entry file with the addition of
29  * file permissions.
30  * <p>
31  * ResourceSyncInfo instances are created from entry lines from the CVS server and from the CVS/Entries
32  * file. Although both entry lines have slightly different formats (e.g. timestamps) they can safely be passed
33  * to the constructor.</p>
34  * <p>
35  * A class named <code>MutableResourceSyncInfo</code> can be used to modify an existing resource
36  * sync or create sync info without an entry line.</p>
37  *
38  * Example entry line from the CVS/Entry file:
39  *
40  * /new.java/1.2/Fri Dec 7 00:17:52 2001/-kb/
41  * D/src////
42  *
43  * @see MutableResourceSyncInfo
44  * @see ICVSResource#getSyncInfos()
45  */

46 public class ResourceSyncInfo {
47         
48     // [Note: permissions aren't honoured in this current implementation]
49
// safe default permissions. Permissions are saved separately so that the correct permissions
50
// can be sent back to the server on systems that don't save execute bits (e.g. windows).
51
private static final String JavaDoc DEFAULT_PERMISSIONS = "u=rw,g=rw,o=r"; //$NON-NLS-1$
52
private static final String JavaDoc DEFAULT_EXECUTABLE_PERMISSIONS = "u=rwx,g=rwx,o=rx"; //$NON-NLS-1$
53

54     // file sync information can be associated with a local resource that has been deleted. This is
55
// noted by prefixing the revision with this character.
56
private static final String JavaDoc DELETED_PREFIX = "-"; //$NON-NLS-1$
57
private static final byte DELETED_PREFIX_BYTE = '-';
58
59     // revision can be locked in repository using "cvs admin -l<rev>" command
60
// entry looks like [M revision 1.2.2.3 locked by: igorf;]
61
private static final String JavaDoc LOCKEDBY_SUFFIX = "\tlocked by"; //$NON-NLS-1$
62

63     // a sync element with a revision of '0' is considered a new file that has
64
// not been comitted to the repo. Is visible so that clients can create sync infos
65
// for new files.
66
public static final String JavaDoc ADDED_REVISION = "0"; //$NON-NLS-1$
67

68     // Timestamp constants used to identify special cases
69
protected static final int TYPE_REGULAR = 1;
70     protected static final int TYPE_MERGED = 2;
71     protected static final int TYPE_MERGED_WITH_CONFLICTS = 3;
72     
73     protected static final String JavaDoc TIMESTAMP_DUMMY = "dummy timestamp"; //$NON-NLS-1$
74
protected static final String JavaDoc TIMESTAMP_MERGED = "Result of merge"; //$NON-NLS-1$
75
protected static final String JavaDoc TIMESTAMP_MERGED_WITH_CONFLICT = TIMESTAMP_MERGED + "+"; //$NON-NLS-1$
76

77     protected static final String JavaDoc TIMESTAMP_SERVER_MERGED = "+modified"; //$NON-NLS-1$
78
protected static final String JavaDoc TIMESTAMP_SERVER_MERGED_WITH_CONFLICT = "+="; //$NON-NLS-1$
79

80     // a directory sync info will have nothing more than a name
81
protected boolean isDirectory = false;
82     protected boolean isDeleted = false;
83     
84     // utility constants
85
protected static final String JavaDoc DIRECTORY_PREFIX = "D"; //$NON-NLS-1$
86
protected static final String JavaDoc SEPARATOR = "/"; //$NON-NLS-1$
87
protected static final byte SEPARATOR_BYTE = (byte)'/';
88     
89     // fields describing the synchronization of a resource in CVS parlance
90
protected String JavaDoc name;
91     protected String JavaDoc revision;
92     protected Date JavaDoc timeStamp;
93     protected KSubstOption keywordMode;
94     protected CVSEntryLineTag tag;
95     
96     // type of sync
97
protected int syncType = TYPE_REGULAR;
98     protected ResourceSyncInfo() {
99         // Added for use by subclasses
100
}
101     
102     public ResourceSyncInfo(byte[] entryLine) throws CVSException {
103         this(new String JavaDoc(entryLine), null);
104     }
105     
106     /**
107      * Constructor to create a sync object from entry line formats. The entry lines are parsed by this class.
108      * The constructor can handle parsing entry lines from the server or from an entry file.
109      *
110      * @param entryLine the entry line (e.g. /new.java/1.2/Fri Dec 07 00:17:52 2001/-kb/)
111      * @param permissions the file permission (e.g. u=rw,g=rw,o=r). May be <code>null</code>.
112      * @param timestamp if not included in the entry line. May be <code>null</code>.
113      *
114      * @exception CVSException is thrown if the entry cannot be parsed.
115      */

116     public ResourceSyncInfo(String JavaDoc entryLine, Date JavaDoc timestamp) throws CVSException {
117         Assert.isNotNull(entryLine);
118         setEntryLine(entryLine);
119         
120         // override the timestamp that may of been in entryLine. In some cases the timestamp is not in the
121
// entry line (e.g. receiving entry lines from the server versus reading them from the Entry file).
122
if(timestamp!=null) {
123             this.timeStamp = timestamp;
124         }
125     }
126     
127     /**
128      * Constructor to create a resource sync object for a folder.
129      *
130      * @param name of the resource for which this sync state is associatied, cannot be <code>null</code>.
131      */

132     public ResourceSyncInfo(String JavaDoc name) {
133         Assert.isNotNull(name);
134         this.name = name;
135         this.isDirectory = true;
136     }
137     /**
138      * Answers if this sync information is for a folder in which case only a name is
139      * available.
140      *
141      * @return <code>true</code> if the sync information is for a folder and <code>false</code>
142      * if it is for a file.
143      */

144     public boolean isDirectory() {
145         return isDirectory;
146     }
147     
148     /**
149      * Answers if this sync information is for a resource that has been merged by the cvs server with
150      * conflicts and has not been modified yet relative to the given timestamp.
151      *
152      * @param otherTimestamp is the timestamp of the file associated with this resource sync
153      * @return <code>true</code> if the sync information is for a file that has been merged and
154      * <code>false</code> for folders and for files that have not been merged.
155      */

156     public boolean isNeedsMerge(Date JavaDoc otherTimestamp) {
157         return syncType == TYPE_MERGED_WITH_CONFLICTS && timeStamp != null && timeStamp.equals(otherTimestamp);
158     }
159     
160     /**
161      * Answers if this sync information is for a resource that has been merged with conflicts by the
162      * cvs server.
163      *
164      * @return <code>true</code> if the sync information is for a file that has been merged and
165      * <code>false</code> for folders and for files that have not been merged.
166      */

167     public boolean isMergedWithConflicts() {
168         return syncType == TYPE_MERGED_WITH_CONFLICTS;
169     }
170     
171     /**
172      * Answers if this sync information is for a resource that has been merged by the cvs server.
173      *
174      * @return <code>true</code> if the sync information is for a file that has been merged and
175      * <code>false</code> for folders and for files that have not been merged.
176      */

177     public boolean isMerged() {
178         return syncType == TYPE_MERGED || isMergedWithConflicts();
179     }
180     
181     /**
182      * Answers if this sync information is for a file that has been added but not comitted
183      * to the CVS repository yet.
184      *
185      * @return <code>true</code> if the sync information is new or <code>false</code> if
186      * the sync is for an file that exists remotely. For folder sync info this returns
187      * <code>false</code>.
188      */

189     public boolean isAdded() {
190         if(!isDirectory) {
191             return getRevision().equals(ADDED_REVISION);
192         } else {
193             return false;
194         }
195     }
196     
197     /**
198      * Answers if this sync information is for a file that is scheduled to be deleted
199      * from the repository but the deletion has not yet been comitted.
200      *
201      * @return <code>true</code> if the sync information is deleted or <code>false</code> if
202      * the sync is for an file that exists remotely.
203      */

204     public boolean isDeleted() {
205         return isDeleted;
206     }
207     
208     /**
209      * Returns an entry line that can be saved in the CVS/Entries file. For sending entry lines to the
210      * server use <code>getServerEntryLine</code>.
211      *
212      * @return a file or folder entry line reflecting the state of this sync object.
213      */

214     public String JavaDoc getEntryLine() {
215         return getEntryLine(true /*include timestamps*/, null /*no timestamp override*/);
216     }
217         
218     /**
219      * Same as <code>getEntryLine</code> except it considers merged files in entry line timestamp format.
220      * This is only valid for sending the file to the server.
221      *
222      * @param fileTimestamp is timestamp of the resource associated with this sync info.
223      * @return a file or folder entry line reflecting the state of this sync object.
224      */

225     public String JavaDoc getServerEntryLine(Date JavaDoc fileTimestamp) {
226         String JavaDoc serverTimestamp;
227         if(fileTimestamp != null && (isMerged() || isMergedWithConflicts())) {
228             if(isNeedsMerge(fileTimestamp)) {
229                 serverTimestamp = TIMESTAMP_SERVER_MERGED_WITH_CONFLICT;
230             } else {
231                 serverTimestamp = TIMESTAMP_SERVER_MERGED;
232             }
233             return getEntryLine(true, serverTimestamp);
234         } else {
235             return getEntryLine(false, null);
236         }
237     }
238     
239     /**
240      * Gets the tag or <code>null</code> if a tag is not available.
241      *
242      * @return Returns a String
243      */

244     public CVSTag getTag() {
245         return tag;
246     }
247     /**
248      * Gets the timeStamp or <code>null</code> if a timestamp is not available.
249      *
250      * @return a date instance representing the timestamp
251      */

252     public Date JavaDoc getTimeStamp() {
253         return timeStamp;
254     }
255     /**
256      * Gets the version or <code>null</code> if this is a folder sync info. The returned
257      * revision will never include the DELETED_PREFIX. To found out if this sync info is
258      * for a deleted resource call isDeleted().
259      *
260      * @return Returns a String
261      */

262     public String JavaDoc getRevision() {
263         return revision;
264     }
265     
266     /**
267      * Gets the name.
268      *
269      * @return Returns a String
270      */

271     public String JavaDoc getName() {
272         return name;
273     }
274     /**
275      * Gets the keyword mode.
276      * @return the keyword substitution option
277      */

278     public KSubstOption getKeywordMode() {
279         return keywordMode;
280     }
281     
282     /**
283      * Answers the default permissions string.
284      */

285     public static String JavaDoc getDefaultPermissions() {
286         return DEFAULT_PERMISSIONS;
287     }
288     
289     /**
290      * Answers the default permissions string that is executable.
291      */

292     public static String JavaDoc getDefaultExecutablePermissions() {
293         return DEFAULT_EXECUTABLE_PERMISSIONS;
294     }
295     
296     /**
297      * Name equality between resource sync info objects.
298      */

299     public boolean equals(Object JavaDoc other) {
300         if(other instanceof ResourceSyncInfo) {
301             ResourceSyncInfo syncInfo = ((ResourceSyncInfo)other);
302             if(other == this) return true;
303             if(getName() == syncInfo.getName()) return true;
304             return getName().equals(syncInfo.getName());
305         } else {
306             return false;
307         }
308     }
309     
310     public int hashCode() {
311         return getName().hashCode();
312     }
313     
314     /*
315      * @see Object#toString()
316      */

317     public String JavaDoc toString() {
318         return getEntryLine(true, null /*no timestamp override*/);
319     }
320     public MutableResourceSyncInfo cloneMutable() {
321         MutableResourceSyncInfo newSync = new MutableResourceSyncInfo(this);
322         return newSync;
323     }
324     /**
325      * Sets the tag for the resource.
326      */

327     protected void setTag(CVSTag tag) {
328         if(tag!=null) {
329             this.tag = new CVSEntryLineTag(tag);
330         } else {
331             this.tag = null;
332         }
333     }
334     
335         
336     /*
337      * Sets the sync type
338      */

339     protected void setSyncType(int syncType) {
340         this.syncType = syncType;
341     }
342     /**
343      * Sets the version and decides if the revision is for a deleted resource the revision field
344      * will not include the deleted prefix '-'.
345      *
346      * @param version the version to set
347      */

348     protected void setRevision(String JavaDoc revision) {
349         if(revision==null || revision.equals(ADDED_REVISION)) {
350             this.revision = ADDED_REVISION;
351             timeStamp = null;
352             syncType = TYPE_REGULAR;
353             isDeleted = false;
354         } else if(revision.startsWith(DELETED_PREFIX)) {
355             this.revision = revision.substring(DELETED_PREFIX.length());
356             isDeleted = true;
357         } else {
358             this.revision = revision;
359             isDeleted = false;
360         }
361     }
362     
363     /**
364      * Set the entry line
365      *
366      * @throws CVSException if the entryLine is malformed
367      */

368     protected void setEntryLine(String JavaDoc entryLine) throws CVSException {
369
370         String JavaDoc[] strings = Util.parseIntoSubstrings(entryLine, SEPARATOR);
371         if(strings.length < 6) {
372             throw new CVSException(CVSMessages.Malformed_entry_line___11 + entryLine);
373         }
374         
375         isDirectory = (strings[0].equals(DIRECTORY_PREFIX));
376         
377         name = strings[1];
378         
379         if(name.length()==0) {
380             throw new CVSException(CVSMessages.Malformed_entry_line__missing_name___12 + entryLine);
381         }
382         
383         String JavaDoc rev = strings[2];
384         
385         if(rev.length()==0 && !isDirectory()) {
386             throw new CVSException(CVSMessages.Malformed_entry_line__missing_revision___13 + entryLine);
387         } else {
388             setRevision(rev);
389         }
390     
391         String JavaDoc date = strings[3];
392         
393         // possible timestamps are:
394
// from server: "+=" and "+modified"
395
// from entry line: "Result of Merge+Thu May 25 12:33:33 2002"
396
// "Result of Merge"
397
// "Thu May 25 12:33:33 2002"
398
//
399
// The server will send a timestamp of "+=" if
400
// the file was merged with conflicts. The '+' indicates that there are conflicts and the
401
// '=' indicate that the timestamp for the file should be used. If the merge does not
402
// have conflicts, simply add a text only timestamp and the file will be regarded as
403
// having outgoing changes.
404
// The purpose for having the two different timestamp options for merges is to
405
// dissallow commit of files that have conflicts until they have been manually edited.
406
if(date.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED) != -1) {
407             syncType = TYPE_MERGED;
408             date = null;
409         } else if(date.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED_WITH_CONFLICT) != -1) {
410             syncType = TYPE_MERGED_WITH_CONFLICTS;
411             date = null;
412         } else if(date.indexOf(TIMESTAMP_MERGED_WITH_CONFLICT)!=-1) {
413             date = date.substring(date.indexOf("+") + 1); //$NON-NLS-1$
414
syncType = TYPE_MERGED_WITH_CONFLICTS;
415         } else if(date.indexOf(TIMESTAMP_MERGED)!=-1) {
416             syncType = TYPE_MERGED;
417             date = null;
418         }
419         
420         if(date==null || "".equals(date)) { //$NON-NLS-1$
421
timeStamp = null;
422         } else {
423             try {
424                 timeStamp = CVSDateFormatter.entryLineToDate(date);
425             } catch(ParseException JavaDoc e) {
426                 // something we don't understand, just make this sync have no timestamp and
427
// never be in sync with the server.
428
timeStamp = null;
429             }
430         }
431         keywordMode = KSubstOption.fromMode(strings[4]);
432         String JavaDoc tagEntry;
433         if (strings.length == 6) {
434             tagEntry = strings[5];
435         } else {
436             // It turns out that CVS supports slashes (/) in the tag even though this breaks the spec
437
// See http://dev.eclipse.org/bugs/show_bug.cgi?id=26717
438
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
439             for (int i = 5; i < strings.length; i++) {
440                 buffer.append(strings[i]);
441                 if (i < strings.length - 1) {
442                     buffer.append(SEPARATOR);
443                 }
444             }
445             tagEntry = buffer.toString();
446         }
447                         
448         if(tagEntry.length()>0) {
449             tag = new CVSEntryLineTag(tagEntry);
450         } else {
451             tag = null;
452         }
453     }
454     
455     private String JavaDoc getEntryLine(boolean includeTimeStamp, String JavaDoc timestampOverride) {
456         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
457         
458         if(isDirectory) {
459             result.append(DIRECTORY_PREFIX);
460             result.append(SEPARATOR);
461             result.append(name);
462             for (int i = 0; i < 4; i++) {
463                 result.append(SEPARATOR);
464             }
465         } else {
466             result.append(SEPARATOR);
467             result.append(name);
468             result.append(SEPARATOR);
469             
470             if(isDeleted){
471                 result.append(DELETED_PREFIX);
472             }
473             result.append(revision);
474             result.append(SEPARATOR);
475             if(includeTimeStamp) {
476                 String JavaDoc entryLineTimestamp = ""; //$NON-NLS-1$
477
if(timestampOverride!=null) {
478                     entryLineTimestamp = timestampOverride;
479                 } else {
480                     switch(syncType) {
481                         case TYPE_REGULAR:
482                             if(timeStamp==null) {
483                                 entryLineTimestamp = TIMESTAMP_DUMMY;
484                             } else {
485                                 entryLineTimestamp = CVSDateFormatter.dateToEntryLine(timeStamp);
486                             } break;
487                         case TYPE_MERGED:
488                             entryLineTimestamp = TIMESTAMP_MERGED; break;
489                         case TYPE_MERGED_WITH_CONFLICTS:
490                             entryLineTimestamp = TIMESTAMP_MERGED_WITH_CONFLICT + CVSDateFormatter.dateToEntryLine(timeStamp); break;
491                     }
492                 }
493                 result.append(entryLineTimestamp);
494             }
495             result.append(SEPARATOR);
496             if (keywordMode != null) result.append(keywordMode.toEntryLineMode());
497             result.append(SEPARATOR);
498             if (tag != null) {
499                 result.append(tag.toEntryLineFormat(true));
500             }
501         }
502         return result.toString();
503     }
504     
505     /**
506      * Method getBytes.
507      * @return byte[]
508      */

509     public byte[] getBytes() {
510         return getEntryLine().getBytes();
511     }
512     
513     /**
514      * Method getName.
515      * @param syncBytes
516      * @return String
517      */

518     public static String JavaDoc getName(byte[] syncBytes) throws CVSException {
519         String JavaDoc name = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 1, false);
520         if (name == null) {
521             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
522         }
523         return name;
524     }
525     
526     /**
527      * Method getKeywordMode.
528      * @param syncBytes
529      * @return String
530      */

531     public static KSubstOption getKeywordMode(byte[] syncBytes) throws CVSException {
532         String JavaDoc mode = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 4, false);
533         if (mode == null) {
534             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
535         }
536         return KSubstOption.fromMode(mode);
537     }
538     
539     /**
540      * Method getKeywordMode.
541      * @param syncBytes
542      * @return String
543      */

544     public static byte[] setKeywordMode(byte[] syncBytes, KSubstOption mode) throws CVSException {
545         return setKeywordMode(syncBytes, mode.toEntryLineMode().getBytes());
546     }
547     
548     /**
549      * Method getKeywordMode.
550      * @param syncBytes
551      * @return String
552      */

553     public static byte[] setKeywordMode(byte[] syncBytes, byte[] modeBytes) throws CVSException {
554         return setSlot(syncBytes, 4, modeBytes);
555     }
556     
557     /**
558      * Return whether the provided syncBytes represent a binary file.
559      * @param syncBytes
560      * @return boolean
561      * @throws CVSException
562      */

563     public static boolean isBinary(byte[] syncBytes) throws CVSException {
564         if (syncBytes == null) return false;
565         String JavaDoc mode = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 4, false);
566         if (mode == null) {
567             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
568         }
569         return "-kb".equals(mode); //$NON-NLS-1$
570
}
571     
572     /**
573      * Method isFolder.
574      * @param syncBytes
575      * @return boolean
576      */

577     public static boolean isFolder(byte[] syncBytes) {
578         return syncBytes.length > 0 && syncBytes[0] == 'D';
579     }
580
581     /**
582      * Method isAddition.
583      * @param syncBytes
584      * @return boolean
585      */

586     public static boolean isAddition(byte[] syncBytes) throws CVSException {
587         int start = startOfSlot(syncBytes, 2);
588         // There must be a slot and, in the very least, there must be two characters after the slot
589
if (start == -1 || start > syncBytes.length - 3) {
590             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
591         }
592         // If the zero is followed by a dot, then it is a valid revision and not an addition
593
return syncBytes[start + 1] == '0' && syncBytes[start + 2] != '.';
594     }
595     
596     /**
597      * Method isDeleted.
598      * @param syncBytes
599      * @return boolean
600      */

601     public static boolean isDeletion(byte[] syncBytes) throws CVSException {
602         int start = startOfSlot(syncBytes, 2);
603         if (start == -1 || start >= syncBytes.length) {
604             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
605         }
606         return syncBytes[start + 1] == DELETED_PREFIX_BYTE;
607     }
608         
609     /**
610      * Method convertToDeletion.
611      * @param syncBytes
612      * @return byte[]
613      */

614     public static byte[] convertToDeletion(byte[] syncBytes) throws CVSException {
615         int index = startOfSlot(syncBytes, 2);
616         if (index == -1) {
617             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
618         }
619         if (syncBytes.length > index && syncBytes[index+1] != DELETED_PREFIX_BYTE) {
620             byte[] newSyncBytes = new byte[syncBytes.length + 1];
621             System.arraycopy(syncBytes, 0, newSyncBytes, 0, index + 1);
622             newSyncBytes[index + 1] = DELETED_PREFIX_BYTE;
623             System.arraycopy(syncBytes, index + 1, newSyncBytes, index + 2, syncBytes.length - index - 1);
624             return newSyncBytes;
625         }
626         return syncBytes;
627     }
628     
629     /**
630      * Method convertFromDeletion.
631      * @param syncBytes
632      * @return byte[]
633      */

634     public static byte[] convertFromDeletion(byte[] syncBytes) throws CVSException {
635         int index = startOfSlot(syncBytes, 2);
636         if (index == -1) {
637             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
638         }
639         if (syncBytes.length > index && syncBytes[index+1] == DELETED_PREFIX_BYTE) {
640             byte[] newSyncBytes = new byte[syncBytes.length - 1];
641             System.arraycopy(syncBytes, 0, newSyncBytes, 0, index + 1);
642             System.arraycopy(syncBytes, index + 2, newSyncBytes, index + 1, newSyncBytes.length - index - 1);
643             return newSyncBytes;
644         }
645         return syncBytes;
646     }
647     /**
648      * Method startOfSlot returns the index of the slash that occurs before the
649      * given slot index. The provided index should be >= 1 which assumes that
650      * slot zero occurs before the first slash.
651      *
652      * @param syncBytes
653      * @param i
654      * @return int
655      */

656     private static int startOfSlot(byte[] syncBytes, int slot) {
657         int count = 0;
658         for (int j = 0; j < syncBytes.length; j++) {
659             if (syncBytes[j] == SEPARATOR_BYTE) {
660                 count++;
661                 if (count == slot) return j;
662             }
663         }
664         return -1;
665     }
666     
667     /**
668      * Method setSlot.
669      * @param syncBytes
670      * @param i
671      * @param b
672      * @return byte[]
673      */

674     private static byte[] setSlot(byte[] syncBytes, int slot, byte[] newBytes) throws CVSException {
675         int start = startOfSlot(syncBytes, slot);
676         if (start == -1) {
677             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
678         }
679         int end = startOfSlot(syncBytes, slot + 1);
680         int totalLength = start + 1 + newBytes.length;
681         if (end != -1) {
682             totalLength += syncBytes.length - end;
683         }
684         byte[] result = new byte[totalLength];
685         System.arraycopy(syncBytes, 0, result, 0, start + 1);
686         System.arraycopy(newBytes, 0, result, start + 1, newBytes.length);
687         if (end != -1) {
688             System.arraycopy(syncBytes, end, result, start + 1 + newBytes.length, syncBytes.length - end);
689         }
690         return result;
691     }
692
693     /**
694      * Return the timestamp portion of the sync info that is to be sent to the
695      * server.
696      *
697      * @param syncBytes
698      * @param fileTimestamp
699      * @return String
700      */

701     public static String JavaDoc getTimestampToServer(byte[] syncBytes, Date JavaDoc fileTimestamp) throws CVSException {
702         if(fileTimestamp != null) {
703             String JavaDoc syncTimestamp = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 3, false);
704             if (syncTimestamp == null) {
705                 throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
706             }
707             int syncType = getSyncType(syncTimestamp);
708             if (syncType != TYPE_REGULAR) {
709                 if (syncType == TYPE_MERGED_WITH_CONFLICTS && fileTimestamp.equals(getTimestamp(syncTimestamp))) {
710                     return TIMESTAMP_SERVER_MERGED_WITH_CONFLICT;
711                 } else {
712                     return TIMESTAMP_SERVER_MERGED;
713                 }
714             }
715         }
716         return null;
717     }
718     /**
719      * Method getTimestamp.
720      * @param syncTimestamp
721      * @return Object
722      */

723     private static Date JavaDoc getTimestamp(String JavaDoc syncTimestamp) {
724         String JavaDoc dateString= syncTimestamp;
725         if(syncTimestamp.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED) != -1) {
726             dateString = null;
727         } else if(syncTimestamp.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED_WITH_CONFLICT) != -1) {
728             dateString = null;
729         } else if(syncTimestamp.indexOf(TIMESTAMP_MERGED_WITH_CONFLICT)!=-1) {
730             dateString = syncTimestamp.substring(syncTimestamp.indexOf("+") + 1); //$NON-NLS-1$
731
} else if(syncTimestamp.indexOf(TIMESTAMP_MERGED)!=-1) {
732             dateString = null;
733         }
734         
735         if(dateString==null || "".equals(dateString)) { //$NON-NLS-1$
736
return null;
737         } else {
738             try {
739                 return CVSDateFormatter.entryLineToDate(dateString);
740             } catch(ParseException JavaDoc e) {
741                 // something we don't understand, just make this sync have no timestamp and
742
// never be in sync with the server.
743
return null;
744             }
745         }
746     }
747     
748     /**
749      * Method getSyncType.
750      * @param syncTimestamp
751      * @return int
752      */

753     private static int getSyncType(String JavaDoc date) {
754         if(date.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED) != -1) {
755             return TYPE_MERGED;
756         } else if(date.indexOf(ResourceSyncInfo.TIMESTAMP_SERVER_MERGED_WITH_CONFLICT) != -1) {
757             return TYPE_MERGED_WITH_CONFLICTS;
758         } else if(date.indexOf(TIMESTAMP_MERGED_WITH_CONFLICT)!=-1) {
759             return TYPE_MERGED_WITH_CONFLICTS;
760         } else if(date.indexOf(TIMESTAMP_MERGED)!=-1) {
761             return TYPE_MERGED;
762         }
763         return TYPE_REGULAR;
764     }
765     
766     /**
767      * Method getTag.
768      * @param syncBytes
769      * @return String
770      */

771     public static byte[] getTagBytes(byte[] syncBytes) throws CVSException {
772         byte[] tag = Util.getBytesForSlot(syncBytes, SEPARATOR_BYTE, 5, true);
773         if (tag == null) {
774             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
775         }
776         return tag;
777     }
778     
779     /**
780      * Method setTag.
781      * @param syncBytes
782      * @param tagString
783      * @return byte[]
784      */

785     public static byte[] setTag(byte[] syncBytes, byte[] tagBytes) throws CVSException {
786         return setSlot(syncBytes, 5, tagBytes);
787     }
788     
789     /**
790      * Method setTag.
791      * @param syncBytes
792      * @param tag
793      * @return ResourceSyncInfo
794      */

795     public static byte[] setTag(byte[] syncBytes, CVSTag tag) throws CVSException {
796         CVSEntryLineTag entryTag;
797         if (tag instanceof CVSEntryLineTag) {
798             entryTag = (CVSEntryLineTag)tag;
799         } else {
800             entryTag = new CVSEntryLineTag(tag);
801         }
802         return setTag(syncBytes, entryTag.toEntryLineFormat(true).getBytes());
803     }
804     
805     /**
806      * Method getRevision.
807      * @param syncBytes
808      */

809     public static String JavaDoc getRevision(byte[] syncBytes) throws CVSException {
810         String JavaDoc revision = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 2, false);
811         if (revision == null) {
812             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
813         }
814         if(revision.startsWith(DELETED_PREFIX)) {
815             revision = revision.substring(DELETED_PREFIX.length());
816         }
817         int lockedIdx = revision.indexOf(LOCKEDBY_SUFFIX);
818         if (lockedIdx >= 0) {
819             revision = revision.substring(0, lockedIdx);
820         }
821         return revision;
822     }
823
824     /**
825      * Method setRevision.
826      * @param syncBytes
827      * @param revision
828      * @return byte[]
829      */

830     public static byte[] setRevision(byte[] syncBytes, String JavaDoc revision) throws CVSException {
831         return setSlot(syncBytes, 2, revision.getBytes());
832     }
833     
834     /**
835      * Method isMerge.
836      * @param syncBytes1
837      * @return boolean
838      */

839     public static boolean isMerge(byte[] syncBytes) throws CVSException {
840         String JavaDoc timestamp = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 3, false);
841         if (timestamp == null) {
842             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
843         }
844         int syncType = getSyncType(timestamp);
845         return syncType == TYPE_MERGED || syncType == TYPE_MERGED_WITH_CONFLICTS;
846     }
847
848     /**
849      * Method isMerge.
850      * @param syncBytes1
851      * @return boolean
852      */

853     public static boolean isMergedWithConflicts(byte[] syncBytes) throws CVSException {
854         String JavaDoc timestamp = Util.getSubstring(syncBytes, SEPARATOR_BYTE, 3, false);
855         if (timestamp == null) {
856             throw new CVSException(NLS.bind(CVSMessages.ResourceSyncInfo_malformedSyncBytes, new String JavaDoc[] { new String JavaDoc(syncBytes) }));
857         }
858         int syncType = getSyncType(timestamp);
859         return syncType == TYPE_MERGED_WITH_CONFLICTS;
860     }
861     
862     
863     /**
864      * Return <code>true</code> if the remoteBytes represents a later revision on the same
865      * branch as localBytes. Return <code>false</code> if remoteBytes is the same or an earlier
866      * revision or if the bytes are on a separate branch (or tag)
867      * @param remoteBytes
868      * @param localBytes
869      * @return
870      */

871     public static boolean isLaterRevisionOnSameBranch(byte[] remoteBytes, byte[] localBytes) throws CVSException {
872         // If the two byte arrays are the same, then the remote isn't a later revision
873
if (remoteBytes == localBytes) return false;
874         // If the tags differ, then the remote isn't a later revision
875
byte[] remoteTag = ResourceSyncInfo.getTagBytes(remoteBytes);
876         byte[] localTag = ResourceSyncInfo.getTagBytes(localBytes);
877         if (!Util.equals(remoteTag, localTag)) return false;
878         // If the revisions are the same, the remote isn't later
879
String JavaDoc remoteRevision = ResourceSyncInfo.getRevision(remoteBytes);
880         String JavaDoc localRevision = ResourceSyncInfo.getRevision(localBytes);
881         if (remoteRevision.equals(localRevision)) return false;
882         return isLaterRevision(remoteRevision, localRevision);
883     }
884
885     /**
886      * Return true if the remoteRevision represents a later revision than the local revision
887      * on the same branch.
888      * @param remoteRevision
889      * @param localRevision
890      * @return
891      */

892     public static boolean isLaterRevision(String JavaDoc remoteRevision, String JavaDoc localRevision) {
893         int localDigits[] = Util.convertToDigits(localRevision);
894         if (localDigits.length == 0) return false;
895         int remoteDigits[] = Util.convertToDigits(remoteRevision);
896         if (remoteDigits.length == 0) return false;
897         
898         if (localRevision.equals(ADDED_REVISION)) {
899             return (remoteDigits.length >= 2);
900         }
901         if (localDigits.length < remoteDigits.length) {
902             // If there are more digits in the remote revision then all
903
// the leading digits must match
904
for (int i = 0; i < localDigits.length; i++) {
905                 int localDigit = localDigits[i];
906                 int remoteDigit = remoteDigits[i];
907                 if (remoteDigit != localDigit) return false;
908             }
909             return true;
910         }
911         // They are the same length or the local is longer.
912
// The last digit must differ and all others must be the same.
913
// If the local is longer, ignore the addition numbers
914
// (this can occur as the result on an import)
915
for (int i = 0; i < remoteDigits.length - 1; i++) {
916             int localDigit = localDigits[i];
917             int remoteDigit = remoteDigits[i];
918             if (remoteDigit != localDigit) return false;
919         }
920         // All the leading digits are equals so the remote is later if the last digit is greater
921
return localDigits[remoteDigits.length - 1] < remoteDigits[remoteDigits.length - 1] ;
922     }
923 }
924
Popular Tags