KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ccvs > core > resources > RemoteFolderTreeBuilder


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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  *******************************************************************************/

11 package org.eclipse.team.internal.ccvs.core.resources;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.List JavaDoc;
18 import java.util.Map JavaDoc;
19
20 import org.eclipse.core.resources.IContainer;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.core.runtime.*;
23 import org.eclipse.osgi.util.NLS;
24 import org.eclipse.team.internal.ccvs.core.*;
25 import org.eclipse.team.internal.ccvs.core.client.*;
26 import org.eclipse.team.internal.ccvs.core.client.Command.*;
27 import org.eclipse.team.internal.ccvs.core.client.listeners.*;
28 import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation;
29 import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
30 import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
31 import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
32 import org.eclipse.team.internal.ccvs.core.util.Util;
33
34 /*
35  * This class is responsible for building a remote tree that shows the repository
36  * state of a locally loaded folder tree.
37  *
38  * It is used as follows
39  *
40  * RemoteFolderTreeBuilder.buildRemoteTree(CVSRepositoryLocation, IManagedFolder, String, IProgressMonitor);
41  *
42  * The provider IManagedFolder can be a local resource or a RemoteFolderTree that
43  * that was previously built.
44  */

45 public class RemoteFolderTreeBuilder {
46
47     private static final int MAX_REVISION_FETCHES_PER_CONNECTION = 1024;
48     
49     private Map JavaDoc fileDeltas;
50     private List JavaDoc changedFiles;
51     private Map JavaDoc remoteFolderTable;
52     
53     private ICVSFolder root;
54     private RemoteFolderTree remoteRoot;
55     private CVSRepositoryLocation repository;
56     
57     private CVSTag tag;
58     
59     private LocalOption[] updateLocalOptions;
60     
61     private boolean rootDoesNotExist = false;
62     
63     private static String JavaDoc UNKNOWN = ""; //$NON-NLS-1$
64
private static String JavaDoc DELETED = "DELETED"; //$NON-NLS-1$
65
private static String JavaDoc ADDED = "ADDED"; //$NON-NLS-1$
66
private static String JavaDoc FOLDER = "FOLDER"; //$NON-NLS-1$
67

68     private static Map JavaDoc EMPTY_MAP = new HashMap JavaDoc();
69     
70     private boolean newFolderExist = false;
71     
72     static class DeltaNode {
73         int syncState = Update.STATE_NONE;
74         String JavaDoc name;
75         String JavaDoc revision;
76         
77         DeltaNode(String JavaDoc name, String JavaDoc revision, int syncState) {
78             this.name = name;
79             this.revision = revision;
80             this.syncState = syncState;
81         }
82         
83         String JavaDoc getName() {
84             return name;
85         }
86         
87         String JavaDoc getRevision() {
88             return revision;
89         }
90         
91         int getSyncState() {
92             return syncState;
93         }
94     }
95         
96     
97     /* package */ RemoteFolderTreeBuilder(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag) {
98         this.repository = repository;
99         this.root = root;
100         this.tag = tag;
101         this.fileDeltas = new HashMap JavaDoc();
102         this.changedFiles = new ArrayList JavaDoc();
103         this.remoteFolderTable = new HashMap JavaDoc();
104         
105         // Build the local options
106
List JavaDoc localOptions = new ArrayList JavaDoc();
107         if (tag != null) {
108             if (tag.getType() == CVSTag.HEAD) {
109                 localOptions.add(Update.CLEAR_STICKY);
110             } else {
111                 localOptions.add(Update.makeTagOption(tag));
112             }
113         }
114         updateLocalOptions = (LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]);
115     }
116     
117     private LocalOption[] getOptionsWithoutTag() {
118         // Build the local options
119
List JavaDoc localOptions = new ArrayList JavaDoc();
120         localOptions.add(Update.RETRIEVE_ABSENT_DIRECTORIES);
121         return (LocalOption[])localOptions.toArray(new LocalOption[localOptions.size()]);
122     }
123     
124     public static RemoteFolder buildBaseTree(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag, IProgressMonitor progress) throws CVSException {
125         try {
126             RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, root, tag);
127             progress.beginTask(null, 100);
128             IProgressMonitor subProgress = Policy.infiniteSubMonitorFor(progress, 100);
129             subProgress.beginTask(null, 512);
130             subProgress.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_buildingBase, new String JavaDoc[] { root.getName() }));
131             return builder.buildBaseTree(null, root, subProgress);
132         } finally {
133             progress.done();
134         }
135     }
136     
137     public static RemoteFolderTree buildRemoteTree(CVSRepositoryLocation repository, IContainer root, CVSTag tag, IProgressMonitor monitor) throws CVSException {
138         return buildRemoteTree(repository, CVSWorkspaceRoot.getCVSFolderFor(root), tag, monitor);
139     }
140     
141     public static RemoteFolderTree buildRemoteTree(CVSRepositoryLocation repository, ICVSFolder root, CVSTag tag, IProgressMonitor monitor) throws CVSException {
142         RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, root, tag);
143         return builder.buildTree(new ICVSResource[] { root }, monitor);
144     }
145     public static RemoteFile buildRemoteTree(CVSRepositoryLocation repository, ICVSFile file, CVSTag tag, IProgressMonitor monitor) throws CVSException {
146         RemoteFolderTreeBuilder builder = new RemoteFolderTreeBuilder(repository, file.getParent(), tag);
147         return builder.buildTree(file, monitor);
148     }
149     
150     /* package */ RemoteFolderTree buildTree(ICVSResource[] resources, IProgressMonitor monitor) throws CVSException {
151         
152         // Make sure that the cvs commands are not quiet during this operations
153
QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
154         try {
155             CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
156             
157             monitor.beginTask(null, 100);
158
159             // 1st Connection: Use local state to determine delta with server
160
if (!fetchDelta(resources, Policy.subMonitorFor(monitor, 75))) {
161                 return null;
162             }
163             
164             // 2nd Connection: Build remote tree from above delta using 2nd connection to fetch unknown directories
165
// NOTE: Multiple commands may be issued over this connection.
166
fetchNewDirectories(Policy.subMonitorFor(monitor, 10));
167
168             // 3rd+ Connection: Used to fetch file status in groups of 1024
169
fetchFileRevisions(Policy.subMonitorFor(monitor, 15));
170             
171             return remoteRoot;
172             
173         } finally {
174             CVSProviderPlugin.getPlugin().setQuietness(quietness);
175             monitor.done();
176         }
177     }
178
179     private boolean fetchDelta(ICVSResource[] resources, IProgressMonitor monitor) throws CVSException {
180         
181         // Get the arguments from the files
182
ArrayList JavaDoc arguments = new ArrayList JavaDoc();
183         for (int i = 0; i < resources.length; i++) {
184             ICVSResource resource = resources[i];
185             arguments.add(resource.getRelativePath(root));
186         }
187         
188         // Use local state to determine delta with server
189
monitor.beginTask(null, 100);
190         Policy.checkCanceled(monitor);
191         Session session = new Session(repository, root, false);
192         session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
193         try {
194             Policy.checkCanceled(monitor);
195             fetchDelta(session, (String JavaDoc[]) arguments.toArray(new String JavaDoc[arguments.size()]), Policy.subMonitorFor(monitor, 90));
196             if (rootDoesNotExist) {
197                 // We cannot handle the case where a project (i.e. the top-most CVS folder)
198
// has been deleted directly on the sever (i.e. deleted using rm -rf)
199
if (root.isCVSFolder() && ! root.isManaged()) {
200                     IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuild_folderDeletedFromServer, new String JavaDoc[] { root.getFolderSyncInfo().getRepository() }),root);
201                     throw new CVSException(status);
202                 } else {
203                     return false;
204                 }
205             }
206         } finally {
207             session.close();
208             monitor.done();
209         }
210         return true;
211     }
212
213     private void fetchNewDirectories(IProgressMonitor monitor) throws CVSException {
214         // Build remote tree from the fetched delta using a new connection to fetch unknown directories
215
// NOTE: Multiple commands may be issued over this connection.
216
monitor.beginTask(null, 100);
217         Session session;
218         FolderSyncInfo folderSyncInfo = root.getFolderSyncInfo();
219         if (folderSyncInfo == null) {
220             // We've lost the mapping in the local workspace.
221
// This could be due to the project being deleted.
222
if (root.exists()) {
223                 IResource resource = root.getIResource();
224                 String JavaDoc path;
225                 if (resource == null) {
226                     path = root.getName();
227                 } else {
228                     path = resource.getFullPath().toString();
229                 }
230                 IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuilder_0, new String JavaDoc[] { path }), root);
231                 throw new CVSException(status);
232             } else {
233                 // Just return. The remote tree will be null
234
return;
235             }
236         }
237         remoteRoot =
238             new RemoteFolderTree(null, root.getName(), repository,
239                 folderSyncInfo.getRepository(),
240                 tagForRemoteFolder(root, tag));
241         if (newFolderExist) {
242             // New folders will require a connection for fetching their members
243
session = new Session(repository, remoteRoot, false);
244             session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
245         } else {
246             session = null;
247         }
248         try {
249             // Set up an infinite progress monitor for the recursive build
250
IProgressMonitor subProgress = Policy.infiniteSubMonitorFor(monitor, 90);
251             subProgress.beginTask(null, 512);
252             // Build the remote tree
253
buildRemoteTree(session, root, remoteRoot, "", subProgress); //$NON-NLS-1$
254
} finally {
255             if (session != null) {
256                 session.close();
257             }
258             monitor.done();
259         }
260     }
261     
262     private void fetchFileRevisions(IProgressMonitor monitor) throws CVSException {
263         // 3rd+ Connection: Used to fetch file status in groups of 1024
264
if (remoteRoot != null && !changedFiles.isEmpty()) {
265             String JavaDoc[] allChangedFiles = (String JavaDoc[])changedFiles.toArray(new String JavaDoc[changedFiles.size()]);
266             int iterations = (allChangedFiles.length / MAX_REVISION_FETCHES_PER_CONNECTION)
267                 + (allChangedFiles.length % MAX_REVISION_FETCHES_PER_CONNECTION == 0 ? 0 : 1);
268             for (int i = 0; i < iterations ; i++) {
269                 int length = Math.min(MAX_REVISION_FETCHES_PER_CONNECTION,
270                     allChangedFiles.length - (MAX_REVISION_FETCHES_PER_CONNECTION * i));
271                 String JavaDoc buffer[] = new String JavaDoc[length];
272                 System.arraycopy(allChangedFiles, i * MAX_REVISION_FETCHES_PER_CONNECTION, buffer, 0, length);
273                 Session session = new Session(repository, remoteRoot, false);
274                 session.open(Policy.subMonitorFor(monitor, 1), false /* read-only */);
275                 try {
276                     fetchFileRevisions(session, buffer, Policy.subMonitorFor(monitor, 2));
277                 } finally {
278                     session.close();
279                 }
280             }
281         }
282     }
283     
284     /* package */ RemoteFile buildTree(ICVSFile file, IProgressMonitor monitor) throws CVSException {
285         QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
286         try {
287             CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
288             
289             monitor.beginTask(null, 100);
290     
291             // Query the server to see if there is a delta available
292
Policy.checkCanceled(monitor);
293             Session session = new Session(repository, root, false);
294             session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
295             try {
296                 Policy.checkCanceled(monitor);
297                 fetchDelta(session, new String JavaDoc[] { file.getName() }, Policy.subMonitorFor(monitor, 50));
298                 if (rootDoesNotExist) {
299                     return null;
300                 }
301             } finally {
302                 session.close();
303             }
304             // Create a parent for the remote resource
305
remoteRoot =
306                 new RemoteFolderTree(null, root.getName(), repository,
307                     root.getFolderSyncInfo().getRepository(),
308                     tagForRemoteFolder(root, tag));
309             // Create the remote resource (using the delta if there is one)
310
RemoteFile remoteFile;
311             Map JavaDoc deltas = (Map JavaDoc)fileDeltas.get(""); //$NON-NLS-1$
312
if (deltas == null || deltas.isEmpty()) {
313                 // If the file is an addition, return null as the remote
314
// Note: If there was a conflicting addition, the delta would not be empty
315
byte[] syncBytes = file.getSyncBytes();
316                 if ( syncBytes == null || ResourceSyncInfo.isAddition(syncBytes)) {
317                     return null;
318                 }
319                 remoteFile = new RemoteFile(remoteRoot, syncBytes);
320             } else {
321                 DeltaNode d = (DeltaNode)deltas.get(file.getName());
322                 if (d.getRevision() == DELETED) {
323                     return null;
324                 }
325                 remoteFile = new RemoteFile(remoteRoot,
326                     d.getSyncState(),
327                     file.getName(),
328                     null, /* the revision will be retrieved from the server */
329                     getKeywordMode(file), /* use the same keyword mode a the local file */
330                     tagForRemoteFolder(remoteRoot, tag));
331             }
332             // Add the resource to its parent
333
remoteRoot.setChildren(new ICVSRemoteResource[] {remoteFile});
334             // If there was a delta, fetch the new revision
335
if (!changedFiles.isEmpty()) {
336                 // Add the remote folder to the remote folder lookup table (used to update file revisions)
337
recordRemoteFolder(remoteRoot);
338                 session = new Session(repository, remoteRoot, false);
339                 session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
340                 try {
341                     fetchFileRevisions(session, (String JavaDoc[])changedFiles.toArray(new String JavaDoc[changedFiles.size()]), Policy.subMonitorFor(monitor, 20));
342                 } finally {
343                     session.close();
344                 }
345             }
346             return remoteFile;
347             
348         } finally {
349             CVSProviderPlugin.getPlugin().setQuietness(quietness);
350             monitor.done();
351         }
352     }
353     
354     private Command.KSubstOption getKeywordMode(ICVSFile file) throws CVSException {
355         if (file == null) return null;
356         byte[] syncBytes = file.getSyncBytes();
357         if (syncBytes == null) return null;
358         return ResourceSyncInfo.getKeywordMode(syncBytes);
359     }
360
361     /*
362      * Build the base remote tree from the local tree.
363      *
364      * The localPath is used to retrieve deltas from the recorded deltas
365      *
366      * Does 1 work for each managed file and folder
367      */

368     RemoteFolder buildBaseTree(RemoteFolder parent, ICVSFolder local, IProgressMonitor monitor) throws CVSException {
369         
370         Policy.checkCanceled(monitor);
371                     
372         // Create a remote folder tree corresponding to the local resource
373
FolderSyncInfo folderSyncInfo = local.getFolderSyncInfo();
374         if (folderSyncInfo == null) return null;
375         RemoteFolder remote = createRemoteFolder(local, parent, folderSyncInfo);
376
377         // Create a List to contain the created children
378
List JavaDoc children = new ArrayList JavaDoc();
379         
380         // Build the child folders corresponding to local folders base
381
ICVSResource[] folders = local.members(ICVSFolder.FOLDER_MEMBERS);
382         for (int i=0;i<folders.length;i++) {
383             ICVSFolder folder = (ICVSFolder)folders[i];
384             if (folder.isManaged() && folder.isCVSFolder()) {
385                 monitor.worked(1);
386                 RemoteFolder tree = buildBaseTree(remote, folder, monitor);
387                 if (tree != null)
388                     children.add(tree);
389             }
390         }
391         
392         // Build the child files corresponding to local files base
393
ICVSResource[] files = local.members(ICVSFolder.FILE_MEMBERS);
394         for (int i=0;i<files.length;i++) {
395             ICVSFile file = (ICVSFile)files[i];
396             byte[] syncBytes = file.getSyncBytes();
397             // if there is no sync info then there is no base
398
if (syncBytes==null)
399                 continue;
400             // There is no remote if the file was added
401
if (ResourceSyncInfo.isAddition(syncBytes))
402                 continue;
403             // If the file was deleted locally, we need to generate a new sync info without the delete flag
404
if (ResourceSyncInfo.isDeletion(syncBytes)) {
405                 syncBytes = ResourceSyncInfo.convertFromDeletion(syncBytes);
406             }
407             children.add(createRemoteFile(remote, syncBytes));
408             monitor.worked(1);
409         }
410         
411         // Remove any folders that are phantoms locally if they have no children
412
if (children.isEmpty() && isPruneEmptyDirectories() && !local.exists())
413             return null;
414
415         // Add the children to the remote folder tree
416
remote.setChildren((ICVSRemoteResource[])children.toArray(new ICVSRemoteResource[children.size()]));
417         
418         return remote;
419     }
420
421     protected RemoteFile createRemoteFile(RemoteFolder remote, byte[] syncBytes) throws CVSException {
422         return new RemoteFile(remote, syncBytes);
423     }
424
425     protected RemoteFolder createRemoteFolder(ICVSFolder local, RemoteFolder parent, FolderSyncInfo folderSyncInfo) {
426         return new RemoteFolderTree(parent, local.getName(), repository, folderSyncInfo.getRepository(), folderSyncInfo.getTag());
427     }
428     
429     /*
430      * Build the remote tree from the local tree and the recorded deltas.
431      *
432      * The localPath is used to retrieve deltas from the recorded deltas
433      *
434      * Does 1 work for each file and folder delta processed
435      */

436     private void buildRemoteTree(Session session, ICVSFolder local, RemoteFolderTree remote, String JavaDoc localPath, IProgressMonitor monitor) throws CVSException {
437         
438         Policy.checkCanceled(monitor);
439         
440         // Add the remote folder to the remote folder lookup table (used to update file revisions)
441
recordRemoteFolder(remote);
442         
443         // Create a map to contain the created children
444
Map JavaDoc children = new HashMap JavaDoc();
445         
446         // If there's no corresponding local resource then we need to fetch its contents in order to populate the deltas
447
if (local == null) {
448             fetchNewDirectory(session, remote, localPath, monitor);
449         }
450         
451         // Fetch the delta's for the folder
452
Map JavaDoc deltas = (Map JavaDoc)fileDeltas.get(localPath);
453         if (deltas == null)
454             deltas = EMPTY_MAP;
455         
456         // If there is a local, use the local children to start building the remote children
457
if (local != null) {
458             // Build the child folders corresponding to local folders
459
ICVSResource[] folders = local.members(ICVSFolder.FOLDER_MEMBERS);
460             for (int i=0;i<folders.length;i++) {
461                 ICVSFolder folder = (ICVSFolder)folders[i];
462                 DeltaNode d = (DeltaNode)deltas.get(folder.getName());
463                 if (folder.isCVSFolder() && ! isOrphanedSubtree(folder) && (d==null || d.getRevision() != DELETED)) {
464                     children.put(folders[i].getName(),
465                         new RemoteFolderTree(remote, folders[i].getName(), repository,
466                             folder.getFolderSyncInfo().getRepository(),
467                             tagForRemoteFolder(folder,tag)));
468                 }
469             }
470             // Build the child files corresponding to local files
471
ICVSResource[] files = local.members(ICVSFolder.FILE_MEMBERS);
472             for (int i=0;i<files.length;i++) {
473                 ICVSFile file = (ICVSFile)files[i];
474
475                 DeltaNode d = (DeltaNode)deltas.get(file.getName());
476                 byte[] syncBytes = file.getSyncBytes();
477                 // if there is no sync info then there isn't a remote file for this local file on the
478
// server.
479
if (syncBytes==null)
480                     continue;
481                 // There is no remote if the file was added and we didn't get a conflict (C) indicator from the server
482
if (ResourceSyncInfo.isAddition(syncBytes) && d==null)
483                     continue;
484                 // There is no remote if the file was deleted and we didn't get a remove (R) indicator from the server
485
if (ResourceSyncInfo.isDeletion(syncBytes) && d==null)
486                     continue;
487                     
488                 int type = d==null ? Update.STATE_NONE : d.getSyncState();
489                 children.put(file.getName(), new RemoteFile(remote, type, syncBytes));
490             }
491         }
492         
493         // Build the children for new or out-of-date resources from the deltas
494
Iterator JavaDoc i = deltas.keySet().iterator();
495         while (i.hasNext()) {
496             String JavaDoc name = (String JavaDoc)i.next();
497             DeltaNode d = (DeltaNode)deltas.get(name);
498             String JavaDoc revision = d.getRevision();
499             if (revision == FOLDER) {
500                 children.put(name, new RemoteFolderTree(remote, repository,
501                     Util.appendPath(remote.getRepositoryRelativePath(), name),
502                     tagForRemoteFolder(remote, tag)));
503             } else if (revision == ADDED) {
504                 children.put(name, new RemoteFile(remote,
505                     d.getSyncState(),
506                     name,
507                     null, /* the revision will be fetched later */
508                     null, /* there's no way to know the remote keyword mode */
509                     tagForRemoteFolder(remote, tag)));
510             } else if (revision == UNKNOWN) {
511                 // The local resource is out of sync with the remote.
512
// Create a RemoteFile associated with the tag so we are assured of getting the proper revision
513
// (Note: this will replace the RemoteFile added from the local base)
514
children.put(name, new RemoteFile(remote,
515                     d.getSyncState(),
516                     name,
517                     null, /* the revision will be fetched later */
518                     getKeywordMode((ICVSFile)children.get(name)), /* get the keyword mode from the local file*/
519                     tagForRemoteFolder(remote, tag)));
520             } else if (revision == DELETED) {
521                 // This should have been deleted while creating from the local resources.
522
// If it wasn't, delete it now.
523
if (children.containsKey(name))
524                     children.remove(name);
525             } else {
526                 // We should never get here
527
}
528             monitor.worked(1);
529         }
530
531         // Add the children to the remote folder tree
532
remote.setChildren((ICVSRemoteResource[])children.values().toArray(new ICVSRemoteResource[children.size()]));
533         
534         // We have to delay building the child folders to support the proper fetching of new directories
535
// due to the fact that the same CVS home directory (i.e. the same root directory) must
536
// be used for all requests sent over the same connection
537
Iterator JavaDoc childIterator = children.entrySet().iterator();
538         List JavaDoc emptyChildren = new ArrayList JavaDoc();
539         while (childIterator.hasNext()) {
540             Map.Entry JavaDoc entry = (Map.Entry JavaDoc)childIterator.next();
541             if (((RemoteResource)entry.getValue()).isFolder()) {
542                 RemoteFolderTree remoteFolder = (RemoteFolderTree)entry.getValue();
543                 String JavaDoc name = (String JavaDoc)entry.getKey();
544                 ICVSFolder localFolder;
545                 DeltaNode d = (DeltaNode)deltas.get(name);
546                 // for directories that are new on the server
547
if (d!=null && d.getRevision() == FOLDER)
548                     localFolder = null;
549                 else
550                     localFolder = local.getFolder(name);
551                 buildRemoteTree(session, localFolder, remoteFolder, Util.appendPath(localPath, name), monitor);
552                 // Record any children that are empty
553
if (isPruneEmptyDirectories() && remoteFolder.getChildren().length == 0) {
554                     // Prune if the local folder is also empty.
555
if (localFolder == null || (localFolder.members(ICVSFolder.ALL_EXISTING_MEMBERS).length == 0))
556                         emptyChildren.add(remoteFolder);
557                     else {
558                         // Also prune if the tag we are fetching is not HEAD and differs from the tag of the local folder
559
FolderSyncInfo info = localFolder.getFolderSyncInfo();
560                         if (tag != null && info != null && ! tag.equals(CVSTag.DEFAULT) && ! tag.equals(info.getTag()))
561                             emptyChildren.add(remoteFolder);
562                     }
563                 }
564             }
565         }
566         
567         // Prune any empty child folders
568
if (isPruneEmptyDirectories() && !emptyChildren.isEmpty()) {
569             List JavaDoc newChildren = new ArrayList JavaDoc();
570             newChildren.addAll(Arrays.asList(remote.getChildren()));
571             newChildren.removeAll(emptyChildren);
572             remote.setChildren((ICVSRemoteResource[])newChildren.toArray(new ICVSRemoteResource[newChildren.size()]));
573
574         }
575     }
576     
577     /*
578      * This method fetches the delta between the local state and the remote state of the resource tree
579      * and records the deltas in the fileDeltas instance variable
580      *
581      * Returns the list of changed files
582      */

583     private List JavaDoc fetchDelta(Session session, String JavaDoc[] arguments, final IProgressMonitor monitor) throws CVSException {
584         
585         // Create an listener that will accumulate new and removed files and folders
586
IUpdateMessageListener listener = new IUpdateMessageListener() {
587             public void directoryInformation(ICVSFolder root, String JavaDoc path, boolean newDirectory) {
588                 if (newDirectory) {
589                     // Record new directory with parent so it can be retrieved when building the parent
590
recordDelta(path, FOLDER, Update.STATE_NONE);
591                     monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(path, 3) }));
592                 }
593             }
594             public void directoryDoesNotExist(ICVSFolder root, String JavaDoc path) {
595                 // Record removed directory with parent so it can be removed when building the parent
596
if (path.length() == 0) {
597                     rootDoesNotExist = true;
598                 } else {
599                     recordDelta(path, DELETED, Update.STATE_NONE);
600                     monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(path, 3) }));
601                 }
602             }
603             public void fileInformation(int type, ICVSFolder root, String JavaDoc filename) {
604                 // Cases that do not require action are:
605
// case 'A' : = A locally added file that does not exists remotely
606
// case '?' : = A local file that has not been added and does not exists remotely
607
// case 'M' : = A locally modified file that has not been modified remotely
608
switch(type) {
609                     case Update.STATE_MERGEABLE_CONFLICT :
610                     case Update.STATE_CONFLICT :
611                                 // We have an remote change to a modified local file
612
// The change could be a local change conflicting with a remote deletion.
613
// If so, the deltas may already have a DELETED for the file.
614
// We shouldn't override this DELETED
615
Map JavaDoc deltas = (Map JavaDoc)fileDeltas.get(Util.removeLastSegment(filename));
616                                 DeltaNode d = deltas != null ? (DeltaNode)deltas.get(Util.getLastSegment(filename)) : null;
617                                 if ((d!=null) && (d.getRevision() == DELETED))
618                                     break;
619                     case Update.STATE_DELETED : // We have a locally removed file that still exists remotely
620
case Update.STATE_REMOTE_CHANGES : // We have an remote change to an unmodified local file
621
changedFiles.add(filename);
622                                 recordDelta(filename, UNKNOWN, type);
623                                 monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(filename, 3) }));
624                                 break;
625                 }
626             }
627             public void fileDoesNotExist(ICVSFolder root, String JavaDoc filename) {
628                 recordDelta(filename, DELETED, Update.STATE_NONE);
629                 monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(filename, 3) }));
630             }
631         };
632         
633         // Perform a "cvs -n update -d [-r tag] ." in order to get the
634
// messages from the server that will indicate what has changed on the
635
// server.
636
IStatus status = Command.SYNCUPDATE.execute(session,
637             new GlobalOption[] { Command.DO_NOT_CHANGE },
638             updateLocalOptions,
639             arguments,
640             new UpdateListener(listener),
641             monitor);
642         if (status.getCode() == CVSStatus.SERVER_ERROR) {
643             CVSServerException e = new CVSServerException(status);
644             if (e.isNoTagException()) {
645                 // This error indicates that the complete subtree
646
// being fetched does not have any files for the tag being queried
647
rootDoesNotExist = true;
648             } else if (e.containsErrors()) {
649                 // Log the error
650
CVSProviderPlugin.log(e);
651             }
652         }
653         return changedFiles;
654     }
655     /*
656      * Fetch the children of a previously unknown directory.
657      *
658      * The fetch may do up to 2 units of work in the provided monitor.
659      */

660     private void fetchNewDirectory(Session session, RemoteFolderTree newFolder, String JavaDoc localPath, final IProgressMonitor monitor) throws CVSException {
661         
662         // Create an listener that will accumulate new files and folders
663
IUpdateMessageListener listener = new IUpdateMessageListener() {
664             public void directoryInformation(ICVSFolder root, String JavaDoc path, boolean newDirectory) {
665                 if (newDirectory) {
666                     // Record new directory with parent so it can be retrieved when building the parent
667
// NOTE: Check path prefix
668
recordDelta(path, FOLDER, Update.STATE_NONE);
669                     monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(path, 3) }));
670                 }
671             }
672             public void directoryDoesNotExist(ICVSFolder root, String JavaDoc path) {
673             }
674             public void fileInformation(int type, ICVSFolder root, String JavaDoc filename) {
675                 // NOTE: Check path prefix
676
changedFiles.add(filename);
677                 recordDelta(filename, ADDED, type);
678                 monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingDelta, new String JavaDoc[] { Util.toTruncatedPath(filename, 3) }));
679             }
680             public void fileDoesNotExist(ICVSFolder root, String JavaDoc filename) {
681             }
682         };
683
684         // NOTE: Should use the path relative to the remoteRoot
685
IStatus status = Command.UPDATE.execute(session,
686             new GlobalOption[] { Command.DO_NOT_CHANGE },
687             updateLocalOptions,
688             new String JavaDoc[] { localPath },
689             new UpdateListener(listener),
690             Policy.subMonitorFor(monitor, 1));
691         if (status.getCode() == CVSStatus.SERVER_ERROR) {
692             CVSServerException e = new CVSServerException(status);
693             if ( ! e.isNoTagException() && e.containsErrors())
694                 throw e;
695             // we now know that this is an exception caused by a cvs bug.
696
// if the folder has no files in it (just subfolders) cvs does not respond with the subfolders...
697
// workaround: retry the request with no tag to get the directory names (if any)
698
Policy.checkCanceled(monitor);
699             status = Command.UPDATE.execute(session,
700                 new GlobalOption[] { Command.DO_NOT_CHANGE },
701                 getOptionsWithoutTag(),
702                 new String JavaDoc[] { localPath },
703                 new UpdateListener(listener),
704                 Policy.subMonitorFor(monitor, 1));
705             if (status.getCode() == CVSStatus.SERVER_ERROR) {
706                 throw new CVSServerException(status);
707             }
708         }
709     }
710     
711     // Get the file revisions for the given filenames
712
private void fetchFileRevisions(Session session, String JavaDoc[] fileNames, final IProgressMonitor monitor) throws CVSException {
713         
714         // Create a listener for receiving the revision info
715
final List JavaDoc exceptions = new ArrayList JavaDoc();
716         IStatusListener listener = new IStatusListener() {
717             public void fileStatus(ICVSFolder root, String JavaDoc path, String JavaDoc remoteRevision) {
718                 try {
719                     updateRevision(path, remoteRevision);
720                     monitor.subTask(NLS.bind(CVSMessages.RemoteFolderTreeBuilder_receivingRevision, new String JavaDoc[] { Util.toTruncatedPath(path, 3) }));
721                 } catch (CVSException e) {
722                     exceptions.add(e);
723                 }
724             }
725         };
726             
727         // Perform a "cvs status..." with a custom message handler
728
IStatus status = Command.STATUS.execute(session,
729             Command.NO_GLOBAL_OPTIONS,
730             Command.NO_LOCAL_OPTIONS,
731             fileNames,
732             new StatusListener(listener),
733             monitor);
734         if (status.getCode() == CVSStatus.SERVER_ERROR) {
735             throw new CVSServerException(status);
736         }
737         
738         // Report any exceptions that occurred fetching the revisions
739
if ( ! exceptions.isEmpty()) {
740             if (exceptions.size() == 1) {
741                 throw (CVSException)exceptions.get(0);
742             } else {
743                 MultiStatus multi = new MultiStatus(CVSProviderPlugin.ID, 0, CVSMessages.RemoteFolder_errorFetchingRevisions, null);
744                 for (int i = 0; i < exceptions.size(); i++) {
745                     multi.merge(((CVSException)exceptions.get(i)).getStatus());
746                 }
747                 throw new CVSException(multi);
748             }
749         }
750     }
751
752     protected boolean isPruneEmptyDirectories() {
753         return false;
754     }
755     /*
756      * Record the deltas in a double map where the outer key is the parent directory
757      * and the inner key is the file name. The value is the revision of the file or
758      * DELETED (file or folder). New folders have a revision of FOLDER.
759      *
760      * A revision of UNKNOWN indicates that the revision has not been fetched
761      * from the repository yet.
762      */

763     private void recordDelta(String JavaDoc path, String JavaDoc revision, int syncState) {
764         if (revision == FOLDER) {
765             newFolderExist = true;
766         }
767         String JavaDoc parent = Util.removeLastSegment(path);
768         Map JavaDoc deltas = (Map JavaDoc)fileDeltas.get(parent);
769         if (deltas == null) {
770             deltas = new HashMap JavaDoc();
771             fileDeltas.put(parent, deltas);
772         }
773         String JavaDoc name = Util.getLastSegment(path);
774         deltas.put(name, new DeltaNode(name, revision, syncState));
775     }
776     
777     private void updateRevision(String JavaDoc path, String JavaDoc revision) throws CVSException {
778         RemoteFolderTree folder = getRecoredRemoteFolder(Util.removeLastSegment(path));
779         if (folder == null) {
780             IStatus status = new CVSStatus(IStatus.ERROR, CVSStatus.ERROR, NLS.bind(CVSMessages.RemoteFolderTreeBuilder_missingParent, new String JavaDoc[] { path.toString(), revision }), root);
781             throw new CVSException(status);
782         }
783         ((RemoteFile)folder.getFile(Util.getLastSegment(path))).setRevision(revision);
784     }
785     
786     /*
787      * Return the tag that should be associated with a remote folder.
788      *
789      * This method is used to ensure that new directories contain the tag
790      * derived from the parent local folder when appropriate. For instance,
791      *
792      * The tag should be the provided tag. However, if tag is null, the
793      * tag for the folder should be derived from the provided reference folder
794      * which could be the local resource corresponding to the remote or the parent
795      * of the remote.
796      */

797     private CVSTag tagForRemoteFolder(ICVSFolder folder, CVSTag tag) throws CVSException {
798         return tag == null ? folder.getFolderSyncInfo().getTag() : tag;
799     }
800     
801     private boolean isOrphanedSubtree(ICVSFolder mFolder) throws CVSException {
802         return mFolder.isCVSFolder() && ! mFolder.isManaged() && ! mFolder.equals(root) && mFolder.getParent().isCVSFolder();
803     }
804     
805     private void recordRemoteFolder(RemoteFolderTree remote) throws CVSException {
806         String JavaDoc path = remote.getFolderSyncInfo().getRemoteLocation();
807         remoteFolderTable.put(Util.asPath(path), remote);
808     }
809     
810     private RemoteFolderTree getRecoredRemoteFolder(String JavaDoc path) {
811         return (RemoteFolderTree)remoteFolderTable.get(Util.asPath(path));
812     }
813
814     /**
815      * This method returns an array of the files that differ between the local and remote trees.
816      * The files are represented as a String that contains the path to the file in the remote or local trees.
817      * @return an array of differing files
818      */

819     public String JavaDoc[] getFileDiffs() {
820         return (String JavaDoc[]) changedFiles.toArray(new String JavaDoc[changedFiles.size()]);
821     }
822 }
823
Popular Tags