KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > subversion > FileStatusCache


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.subversion;
21
22 import java.io.File JavaDoc;
23 import java.util.regex.*;
24 import org.netbeans.modules.subversion.client.ExceptionHandler;
25 import org.netbeans.modules.versioning.util.ListenersSupport;
26 import org.netbeans.modules.versioning.util.VersioningListener;
27 import org.netbeans.modules.versioning.util.FlatFolder;
28 import org.netbeans.modules.subversion.util.Context;
29 import org.netbeans.modules.subversion.util.SvnUtils;
30 import org.netbeans.modules.turbo.Turbo;
31 import org.netbeans.modules.turbo.CustomProviders;
32 import org.openide.filesystems.FileUtil;
33 import org.openide.filesystems.FileObject;
34 import org.openide.filesystems.FileStateInvalidException;
35 import org.openide.ErrorManager;
36 import java.util.*;
37 import org.netbeans.modules.subversion.client.SvnClient;
38 import org.openide.filesystems.FileSystem;
39 import org.openide.util.RequestProcessor;
40 import org.tigris.subversion.svnclientadapter.*;
41 import org.tigris.subversion.svnclientadapter.ISVNStatus;
42
43 /**
44  * Central part of CVS status management, deduces and caches statuses of files under version control.
45  *
46  * @author Maros Sandor
47  */

48 public class FileStatusCache implements ISVNNotifyListener {
49
50     /**
51      * Indicates that status of a file changed and listeners SHOULD check new status
52      * values if they are interested in this file.
53      * First parameter: File whose status changes
54      * Second parameter: old FileInformation object, may be null
55      * Third parameter: new FileInformation object
56      */

57     public static final Object JavaDoc EVENT_FILE_STATUS_CHANGED = new Object JavaDoc();
58
59     /**
60      * A special map saying that no file inside the folder is managed.
61      */

62     private static final Map<File JavaDoc, FileInformation> NOT_MANAGED_MAP = new NotManagedMap();
63        
64     public static final ISVNStatus REPOSITORY_STATUS_UNKNOWN = null;
65
66     // Constant FileInformation objects that can be safely reused
67
// Files that have a revision number cannot share FileInformation objects
68
private static final FileInformation FILE_INFORMATION_EXCLUDED = new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, false);
69     private static final FileInformation FILE_INFORMATION_EXCLUDED_DIRECTORY = new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, true);
70     private static final FileInformation FILE_INFORMATION_UPTODATE_DIRECTORY = new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, true);
71     private static final FileInformation FILE_INFORMATION_NOTMANAGED = new FileInformation(FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, false);
72     private static final FileInformation FILE_INFORMATION_NOTMANAGED_DIRECTORY = new FileInformation(FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, true);
73     private static final FileInformation FILE_INFORMATION_UNKNOWN = new FileInformation(FileInformation.STATUS_UNKNOWN, false);
74
75     /**
76      * Auxiliary conflict file siblings
77      * After update: *.r#, *.mine
78      * After merge: *.working, *.merge-right.r#, *.metge-left.r#
79      */

80     private static final Pattern auxConflictPattern = Pattern.compile("(.*?)\\.((r\\d+)|(mine)|" + // NOI18N
81
"(working)|(merge-right\\.r\\d+)|((merge-left.r\\d+)))$"); // NOI18N
82

83     /*
84      * Holds three kinds of information: what folders we have scanned, what files we have found
85      * and what statuses of these files are.
86      * If a directory is not found as a key in the map, we have not scanned it yet.
87      * If it has been scanned, it maps to a Set of files that were found somehow out of sync with the
88      * repository (have any other status then up-to-date). In case all files are up-to-date, it maps
89      * to Collections.EMPTY_MAP. Entries in this map are created as directories are scanne, are never removed and
90      * are updated by the refresh method.
91      */

92
93     private final Turbo turbo;
94     
95     /**
96      * Identifies attribute that holds information about all non STATUS_VERSIONED_UPTODATE files.
97      *
98      * <p>Key type: File identifying a folder
99      * <p>Value type: Map&lt;File, FileInformation>
100      */

101     private final String JavaDoc FILE_STATUS_MAP = DiskMapTurboProvider.ATTR_STATUS_MAP;
102
103     private DiskMapTurboProvider cacheProvider;
104     
105     private Subversion svn;
106     
107     private Set<FileSystem> filesystemsToRefresh;
108     
109     private RequestProcessor.Task refreshFilesystemsTask;
110     
111     FileStatusCache() {
112         this.svn = Subversion.getInstance();
113         cacheProvider = new DiskMapTurboProvider();
114         turbo = Turbo.createCustom(new CustomProviders() {
115             private final Set providers = Collections.singleton(cacheProvider);
116             public Iterator providers() {
117                 return providers.iterator();
118             }
119         }, 200, 5000);
120     }
121
122     // --- Public interface -------------------------------------------------
123

124     /**
125      * Lists <b>modified files</b> and all folders that are known to be inside
126      * this folder. There are locally modified files present
127      * plus any files that exist in the folder in the remote repository. It
128      * returns all folders, including CVS folders.
129      *
130      * @param dir folder to list
131      * @return
132      */

133     public File JavaDoc [] listFiles(File JavaDoc dir) {
134         Set<File JavaDoc> files = getScannedFiles(dir).keySet();
135         return files.toArray(new File JavaDoc[files.size()]);
136     }
137
138     /**
139      * Lists <b>interesting files</b> that are known to be inside given folders.
140      * These are locally and remotely modified and ignored files.
141      *
142      * <p>Comapring to CVS this method returns both folders and files.
143      *
144      * @param context context to examine
145      * @param includeStatus limit returned files to those having one of supplied statuses
146      * @return File [] array of interesting files
147      */

148     public File JavaDoc [] listFiles(Context context, int includeStatus) {
149         Set<File JavaDoc> set = new HashSet<File JavaDoc>();
150         Map allFiles = cacheProvider.getAllModifiedValues();
151         for (Iterator i = allFiles.keySet().iterator(); i.hasNext();) {
152             File JavaDoc file = (File JavaDoc) i.next();
153             FileInformation info = (FileInformation) allFiles.get(file);
154             if ((info.getStatus() & includeStatus) == 0) continue;
155             File JavaDoc [] roots = context.getRootFiles();
156             for (int j = 0; j < roots.length; j++) {
157                 File JavaDoc root = roots[j];
158                 if (root instanceof FlatFolder) {
159                     if (file.equals(root) || file.getParentFile().equals(root)) {
160                         set.add(file);
161                         break;
162                     }
163                 } else {
164                     if (SvnUtils.isParentOrEqual(root, file)) {
165                         set.add(file);
166                         break;
167                     }
168                 }
169             }
170         }
171         if (context.getExclusions().size() > 0) {
172             for (Iterator i = context.getExclusions().iterator(); i.hasNext();) {
173                 File JavaDoc excluded = (File JavaDoc) i.next();
174                 for (Iterator j = set.iterator(); j.hasNext();) {
175                     File JavaDoc file = (File JavaDoc) j.next();
176                     if (SvnUtils.isParentOrEqual(excluded, file)) {
177                         j.remove();
178                     }
179                 }
180             }
181         }
182         return set.toArray(new File JavaDoc[set.size()]);
183     }
184
185     /**
186      * Lists <b>interesting files</b> that are known to be inside given folders.
187      * These are locally and remotely modified and ignored files.
188      *
189      * <p>Comapring to CVS this method returns both folders and files.
190      *
191      * @param roots context to examine
192      * @param includeStatus limit returned files to those having one of supplied statuses
193      * @return File [] array of interesting files
194      */

195     public File JavaDoc [] listFiles(File JavaDoc[] roots, int includeStatus) {
196         Set<File JavaDoc> set = new HashSet<File JavaDoc>();
197         Map allFiles = cacheProvider.getAllModifiedValues();
198         for (Iterator i = allFiles.keySet().iterator(); i.hasNext();) {
199             File JavaDoc file = (File JavaDoc) i.next();
200             FileInformation info = (FileInformation) allFiles.get(file);
201             if ((info.getStatus() & includeStatus) == 0) continue;
202             for (int j = 0; j < roots.length; j++) {
203                 File JavaDoc root = roots[j];
204                 if (root instanceof FlatFolder) {
205                     if (file.getParentFile().equals(root)) {
206                         set.add(file);
207                         break;
208                     }
209                 } else {
210                     if (SvnUtils.isParentOrEqual(root, file)) {
211                         set.add(file);
212                         break;
213                     }
214                 }
215             }
216         }
217         return set.toArray(new File JavaDoc[set.size()]);
218     }
219
220     /**
221      * Determines the versioning status of a file. This method accesses disk and may block for a long period of time.
222      *
223      * @param file file to get status for
224      * @return FileInformation structure containing the file status
225      * @see FileInformation
226      */

227     public FileInformation getStatus(File JavaDoc file) {
228         if (svn.isAdministrative(file)) return FILE_INFORMATION_NOTMANAGED_DIRECTORY;
229         File JavaDoc dir = file.getParentFile();
230         if (dir == null) {
231             return FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
232
}
233         Map files = getScannedFiles(dir);
234         if (files == NOT_MANAGED_MAP) return FILE_INFORMATION_NOTMANAGED;
235         FileInformation fi = (FileInformation) files.get(file);
236         if (fi != null) {
237             return fi;
238         }
239         if (!exists(file)) return FILE_INFORMATION_UNKNOWN;
240         if (file.isDirectory()) {
241             return refresh(file, REPOSITORY_STATUS_UNKNOWN);
242         } else {
243             return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, false);
244         }
245     }
246
247     /**
248      * Looks up cached file status.
249      *
250      * @param file file to check
251      * @return give file's status or null if the file's status is not in cache
252      */

253     FileInformation getCachedStatus(File JavaDoc file) {
254         file = file.getParentFile();
255         if (file == null) return FILE_INFORMATION_NOTMANAGED_DIRECTORY;
256         Map<File JavaDoc, FileInformation> files = (Map<File JavaDoc, FileInformation>) turbo.readEntry(file, FILE_STATUS_MAP);
257         return files != null ? files.get(file) : null;
258     }
259
260     private FileInformation refresh(File JavaDoc file, ISVNStatus repositoryStatus, boolean forceChangeEvent) {
261         File JavaDoc dir = file.getParentFile();
262         if (dir == null) {
263             return FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
264
}
265         Map<File JavaDoc, FileInformation> files = getScannedFiles(dir);
266         if (files == NOT_MANAGED_MAP && repositoryStatus == REPOSITORY_STATUS_UNKNOWN) return FILE_INFORMATION_NOTMANAGED;
267         FileInformation current = files.get(file);
268         
269         ISVNStatus status = null;
270         try {
271             SvnClient client = Subversion.getInstance().getClient(false);
272             status = client.getSingleStatus(file);
273             if (status != null && SVNStatusKind.UNVERSIONED.equals(status.getTextStatus())) {
274                 status = null;
275             }
276         } catch (SVNClientException e) {
277             // svnClientAdapter does not return SVNStatusKind.UNVERSIONED!!!
278
// unversioned resource is expected getSingleStatus()
279
// does not return SVNStatusKind.UNVERSIONED but throws exception instead
280
// instead of throwing exception
281
if (ExceptionHandler.isUnversionedResource(e.getMessage()) == false) {
282                 // missing or damaged entries
283
// or ignored file
284
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
285             }
286         }
287         FileInformation fi = createFileInformation(file, status, repositoryStatus);
288         if (equivalent(fi, current)) {
289             if (forceChangeEvent) fireFileStatusChanged(file, current, fi);
290             return fi;
291         }
292         // do not include uptodate files into cache, missing directories must be included
293
if (current == null && !fi.isDirectory() && fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE) {
294             if (forceChangeEvent) fireFileStatusChanged(file, current, fi);
295             return fi;
296         }
297
298         file = FileUtil.normalizeFile(file);
299         dir = FileUtil.normalizeFile(dir);
300         Map<File JavaDoc, FileInformation> newFiles = new HashMap<File JavaDoc, FileInformation>(files);
301         if (fi.getStatus() == FileInformation.STATUS_UNKNOWN) {
302             newFiles.remove(file);
303             turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
304
}
305         else if (fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE && file.isFile()) {
306             newFiles.remove(file);
307         } else {
308             newFiles.put(file, fi);
309         }
310         assert newFiles.containsKey(dir) == false;
311         turbo.writeEntry(dir, FILE_STATUS_MAP, newFiles.size() == 0 ? null : newFiles);
312
313         if (file.isDirectory() && needRecursiveRefresh(fi, current)) {
314             File JavaDoc [] content = listFiles(file);
315             for (int i = 0; i < content.length; i++) {
316                 refresh(content[i], REPOSITORY_STATUS_UNKNOWN);
317             }
318         }
319         fireFileStatusChanged(file, current, fi);
320         return fi;
321     }
322
323     /**
324      * Refreshes the status of the file given the repository status. Repository status is filled
325      * in when this method is called while processing server output.
326      *
327      * <p>Note: it's not necessary if you use Subversion.getClient(), it
328      * updates the cache automatically using onNotify(). It's not
329      * fully reliable for removed files.
330      *
331      * @param file
332      * @param repositoryStatus
333      */

334     public FileInformation refresh(File JavaDoc file, ISVNStatus repositoryStatus) {
335         return refresh(file, repositoryStatus, false);
336     }
337     
338     /**
339      * Two FileInformation objects are equivalent if their status contants are equal AND they both reperesent a file (or
340      * both represent a directory) AND Entries they cache, if they can be compared, are equal.
341      *
342      * @param other object to compare to
343      * @return true if status constants of both object are equal, false otherwise
344      */

345     private static boolean equivalent(FileInformation main, FileInformation other) {
346         if (other == null || main.getStatus() != other.getStatus() || main.isDirectory() != other.isDirectory()) return false;
347         
348         ISVNStatus e1 = main.getEntry(null);
349         ISVNStatus e2 = other.getEntry(null);
350         return e1 == e2 || e1 == null || e2 == null || equal(e1, e2);
351     }
352
353     /**
354      * Replacement for missing Entry.equals(). It is implemented as a separate method to maintain compatibility.
355      *
356      * @param e1 first entry to compare
357      * @param e2 second Entry to compare
358      * @return true if supplied entries contain equivalent information
359      */

360     private static boolean equal(ISVNStatus e1, ISVNStatus e2) {
361         long r1 = -1;
362         if (e1 != null) {
363             SVNRevision r = e1.getRevision();
364             r1 = r != null ? e1.getRevision().getNumber() : r1;
365         }
366
367         long r2 = -2;
368         if (e2 != null) {
369             SVNRevision r = e1.getRevision();
370             r2 = r != null ? e2.getRevision().getNumber() : r2;
371         }
372         
373         if ( r1 != r2 ) {
374             return false;
375         }
376         return e1.getUrl() == e2.getUrl() ||
377                 e1.getUrl() != null && e1.getUrl().equals(e2.getUrl());
378     }
379     
380     private boolean needRecursiveRefresh(FileInformation fi, FileInformation current) {
381         if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED ||
382                 current != null && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) return true;
383         if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED ||
384                 current != null && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED) return true;
385         return false;
386     }
387
388     /**
389      * Refreshes information about a given file or directory ONLY if its status is already cached. The
390      * only exception are non-existing files (new-in-repository) whose statuses are cached in all cases.
391      *
392      * @param file
393      * @param repositoryStatus
394      */

395     public void refreshCached(File JavaDoc file, ISVNStatus repositoryStatus) {
396         refresh(file, repositoryStatus);
397     }
398
399     /**
400      * Refreshes status of all files inside given context. Files that have some remote status, eg. REMOTELY_ADDED
401      * are brought back to UPTODATE.
402      *
403      * @param ctx context to refresh
404      */

405     public void refreshCached(Context ctx) {
406         
407         File JavaDoc [] files = listFiles(ctx, ~0);
408         
409         for (int i = 0; i < files.length; i++) {
410             File JavaDoc file = files[i];
411             refreshCached(file, REPOSITORY_STATUS_UNKNOWN);
412         }
413     }
414
415     // --- Package private contract ------------------------------------------
416

417     Map<File JavaDoc, FileInformation> getAllModifiedFiles() {
418         return cacheProvider.getAllModifiedValues();
419     }
420
421     /**
422      * Refreshes given directory and all subdirectories.
423      *
424      * @param dir directory to refresh
425      */

426     void directoryContentChanged(File JavaDoc dir) {
427         Map originalFiles = (Map) turbo.readEntry(dir, FILE_STATUS_MAP);
428         if (originalFiles != null) {
429             for (Iterator i = originalFiles.keySet().iterator(); i.hasNext();) {
430                 File JavaDoc file = (File JavaDoc) i.next();
431                 refresh(file, REPOSITORY_STATUS_UNKNOWN);
432             }
433         }
434     }
435     
436     /**
437      * Cleans up the cache by removing or correcting entries that are no longer valid or correct.
438      */

439     void cleanUp() {
440         Map files = cacheProvider.getAllModifiedValues();
441         for (Iterator i = files.keySet().iterator(); i.hasNext();) {
442             File JavaDoc file = (File JavaDoc) i.next();
443             FileInformation info = (FileInformation) files.get(file);
444             if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0) {
445                 refresh(file, REPOSITORY_STATUS_UNKNOWN);
446             } else if (info.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
447                 // remove entries that were excluded but no longer exist
448
// cannot simply call refresh on excluded files because of 'excluded on server' status
449
if (!exists(file)) {
450                     refresh(file, REPOSITORY_STATUS_UNKNOWN);
451                 }
452             }
453         }
454     }
455         
456     // --- Private methods ---------------------------------------------------
457

458     private Map<File JavaDoc, FileInformation> getScannedFiles(File JavaDoc dir) {
459         Map<File JavaDoc, FileInformation> files;
460
461         // there are 2nd level nested admin dirs (.svn/tmp, .svn/prop-base, ...)
462

463         if (svn.isAdministrative(dir)) {
464             return NOT_MANAGED_MAP;
465         }
466         File JavaDoc parent = dir.getParentFile();
467         if (parent != null && svn.isAdministrative(parent)) {
468             return NOT_MANAGED_MAP;
469         }
470
471         files = (Map<File JavaDoc, FileInformation>) turbo.readEntry(dir, FILE_STATUS_MAP);
472         if (files != null) return files;
473         if (isNotManagedByDefault(dir)) {
474             return NOT_MANAGED_MAP;
475         }
476
477         // scan and populate cache with results
478

479         dir = FileUtil.normalizeFile(dir);
480         files = scanFolder(dir); // must not execute while holding the lock, it may take long to execute
481
assert files.containsKey(dir) == false;
482         turbo.writeEntry(dir, FILE_STATUS_MAP, files);
483         for (Iterator i = files.keySet().iterator(); i.hasNext();) {
484             File JavaDoc file = (File JavaDoc) i.next();
485             FileInformation info = files.get(file);
486             if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0) {
487                 fireFileStatusChanged(file, null, info);
488             }
489         }
490         return files;
491     }
492
493     private boolean isNotManagedByDefault(File JavaDoc dir) {
494         return !dir.exists();
495     }
496
497     /**
498      * Scans all files in the given folder, computes and stores their CVS status.
499      *
500      * @param dir directory to scan
501      * @return Map map to be included in the status cache (File => FileInformation)
502      */

503     private Map<File JavaDoc, FileInformation> scanFolder(File JavaDoc dir) {
504         File JavaDoc [] files = dir.listFiles();
505         if (files == null) files = new File JavaDoc[0];
506         Map<File JavaDoc, FileInformation> folderFiles = new HashMap<File JavaDoc, FileInformation>(files.length);
507
508         ISVNStatus [] entries = null;
509         try {
510             SvnClient client = Subversion.getInstance().getClient(true);
511             if (Subversion.getInstance().isManaged(dir)) {
512                 entries = client.getStatus(dir, false, false);
513             }
514         } catch (SVNClientException e) {
515             // no or damaged entries
516
ErrorManager.getDefault().annotate(e, "Can not status " + dir.getAbsolutePath() + ", guessing it..."); // NOI18N
517
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
518         }
519
520         if (entries == null) {
521             for (int i = 0; i < files.length; i++) {
522                 File JavaDoc file = files[i];
523                 if (svn.isAdministrative(file)) continue;
524                 FileInformation fi = createFileInformation(file, null, REPOSITORY_STATUS_UNKNOWN);
525                 if (fi.isDirectory() || fi.getStatus() != FileInformation.STATUS_VERSIONED_UPTODATE) {
526                     folderFiles.put(file, fi);
527                 }
528             }
529         } else {
530             Set<File JavaDoc> localFiles = new HashSet<File JavaDoc>(Arrays.asList(files));
531             for (int i = 0; i < entries.length; i++) {
532                 ISVNStatus entry = entries[i];
533                 File JavaDoc file = new File JavaDoc(entry.getPath());
534                 if (file.equals(dir)) {
535                     continue;
536                 }
537                 localFiles.remove(file);
538                 if (svn.isAdministrative(file)) {
539                     continue;
540                 }
541                 FileInformation fi = createFileInformation(file, entry, REPOSITORY_STATUS_UNKNOWN);
542                 if (fi.isDirectory() || fi.getStatus() != FileInformation.STATUS_VERSIONED_UPTODATE) {
543                     folderFiles.put(file, fi);
544                 }
545             }
546
547             Iterator it = localFiles.iterator();
548             while (it.hasNext()) {
549                 File JavaDoc localFile = (File JavaDoc) it.next();
550                 FileInformation fi = createFileInformation(localFile, null, REPOSITORY_STATUS_UNKNOWN);
551                 if (fi.isDirectory() || fi.getStatus() != FileInformation.STATUS_VERSIONED_UPTODATE) {
552                     folderFiles.put(localFile, fi);
553                 }
554             }
555         }
556
557         return folderFiles;
558     }
559
560     /**
561      * Examines a file or folder and computes its status.
562      *
563      * @param status entry for this file or null if the file is unknown to subversion
564      * @return FileInformation file/folder status bean
565      */

566     private FileInformation createFileInformation(File JavaDoc file, ISVNStatus status, ISVNStatus repositoryStatus) {
567         if (status == null || status.getTextStatus().equals(SVNStatusKind.UNVERSIONED)) {
568             if (!svn.isManaged(file)) {
569                 return file.isDirectory() ? FILE_INFORMATION_NOTMANAGED_DIRECTORY : FILE_INFORMATION_NOTMANAGED;
570             }
571             return createMissingEntryFileInformation(file, repositoryStatus);
572         } else {
573             return createVersionedFileInformation(file, status, repositoryStatus);
574         }
575     }
576
577     /**
578      * Examines a file or folder that has an associated CVS entry.
579      *
580      * @param file file/folder to examine
581      * @param status status of the file/folder as reported by the CVS server
582      * @return FileInformation file/folder status bean
583      */

584     private FileInformation createVersionedFileInformation(File JavaDoc file, ISVNStatus status, ISVNStatus repositoryStatus) {
585
586         SVNStatusKind kind = status.getTextStatus();
587         SVNStatusKind pkind = status.getPropStatus();
588
589         int remoteStatus = 0;
590         if (repositoryStatus != REPOSITORY_STATUS_UNKNOWN) {
591             if (repositoryStatus.getRepositoryTextStatus() == SVNStatusKind.MODIFIED
592             || repositoryStatus.getRepositoryPropStatus() == SVNStatusKind.MODIFIED) {
593                 remoteStatus = FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY;
594             } else if (repositoryStatus.getRepositoryTextStatus() == SVNStatusKind.DELETED
595             /*|| repositoryStatus.getRepositoryPropStatus() == SVNStatusKind.DELETED*/) {
596                 remoteStatus = FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY;
597             } else if (repositoryStatus.getRepositoryTextStatus() == SVNStatusKind.ADDED
598             /*|| repositoryStatus.getRepositoryPropStatus() == SVNStatusKind.ADDED*/) {
599                 // solved in createMissingfileInformation
600
} else if (repositoryStatus.getRepositoryTextStatus() == null
601             && repositoryStatus.getRepositoryPropStatus() == null) {
602                 // no remote change at all
603
} else {
604                 // TODO systematically handle all repository statuses
605
// so far above were observed....
606
// XXX
607
System.err.println("SVN.FSC: unhandled repository status: " + file.getAbsolutePath()); // NOI18N
608
System.err.println("\ttext: " + repositoryStatus.getRepositoryTextStatus()); // NOI18N
609
System.err.println("\tprop: " + repositoryStatus.getRepositoryPropStatus()); // NOI18N
610
}
611         }
612
613         if (SVNStatusKind.NONE.equals(pkind)) {
614             // no influence
615
} else if (SVNStatusKind.NORMAL.equals(pkind)) {
616             // no influence
617
} else if (SVNStatusKind.MODIFIED.equals(pkind)) {
618             if (SVNStatusKind.NORMAL.equals(kind)) {
619                 return new FileInformation(FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY | remoteStatus, status);
620             }
621         } else if (SVNStatusKind.CONFLICTED.equals(pkind)) {
622             return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT | remoteStatus, status);
623         } else {
624             throw new IllegalArgumentException JavaDoc("Unknown prop status: " + status.getPropStatus()); // NOI18N
625
}
626
627
628         if (SVNStatusKind.NONE.equals(kind)) {
629             return FILE_INFORMATION_UNKNOWN;
630         } else if (SVNStatusKind.NORMAL.equals(kind)) {
631             return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE | remoteStatus, status);
632         } else if (SVNStatusKind.MODIFIED.equals(kind)) {
633             return new FileInformation(FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY | remoteStatus, status);
634         } else if (SVNStatusKind.ADDED.equals(kind)) {
635             return new FileInformation(FileInformation.STATUS_VERSIONED_ADDEDLOCALLY | remoteStatus, status);
636         } else if (SVNStatusKind.DELETED.equals(kind)) {
637             return new FileInformation(FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY | remoteStatus, status);
638         } else if (SVNStatusKind.UNVERSIONED.equals(kind)) {
639             return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY | remoteStatus, status);
640         } else if (SVNStatusKind.MISSING.equals(kind)) {
641             return new FileInformation(FileInformation.STATUS_VERSIONED_DELETEDLOCALLY | remoteStatus, status);
642         } else if (SVNStatusKind.REPLACED.equals(kind)) {
643             // XXX create new status constant? Is it neccesary to visualize
644
// this status or better to use this simplyfication?
645
return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY | remoteStatus, status);
646         } else if (SVNStatusKind.MERGED.equals(kind)) {
647             return new FileInformation(FileInformation.STATUS_VERSIONED_MERGE | remoteStatus, status);
648         } else if (SVNStatusKind.CONFLICTED.equals(kind)) {
649             return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT | remoteStatus, status);
650         } else if (SVNStatusKind.OBSTRUCTED.equals(kind)) {
651             // TODO: create new status constant?
652
return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT | remoteStatus, status);
653         } else if (SVNStatusKind.IGNORED.equals(kind)) {
654             return new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED | remoteStatus, status);
655         } else if (SVNStatusKind.INCOMPLETE.equals(kind)) {
656             // TODO: create new status constant?
657
return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT | remoteStatus, status);
658         } else if (SVNStatusKind.EXTERNAL.equals(kind)) {
659             // TODO: create new status constant?
660
return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE | remoteStatus, status);
661         } else {
662             throw new IllegalArgumentException JavaDoc("Unknown text status: " + status.getTextStatus()); // NOI18N
663
}
664     }
665
666     static String JavaDoc statusText(ISVNStatus status) {
667         return "file: " + status.getTextStatus().toString() + " copied: " + status.isCopied() + " prop: " + status.getPropStatus().toString(); // NOI18N
668
}
669
670     /**
671      * Examines a file or folder that does NOT have an associated Subversion status.
672      *
673      * @param file file/folder to examine
674      * @return FileInformation file/folder status bean
675      */

676     private FileInformation createMissingEntryFileInformation(File JavaDoc file, ISVNStatus repositoryStatus) {
677         
678         // ignored status applies to whole subtrees
679
boolean isDirectory = file.isDirectory();
680         int parentStatus = getStatus(file.getParentFile()).getStatus();
681         if (parentStatus == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
682             return isDirectory ?
683                 FILE_INFORMATION_EXCLUDED_DIRECTORY : FILE_INFORMATION_EXCLUDED;
684         }
685         if (parentStatus == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED) {
686             if (isDirectory) {
687                 // Working directory roots (aka managed roots). We already know that isManaged(file) is true
688
return SvnUtils.isPartOfSubversionMetadata(file) ?
689                     FILE_INFORMATION_NOTMANAGED_DIRECTORY : FILE_INFORMATION_UPTODATE_DIRECTORY;
690             } else {
691                 return FILE_INFORMATION_NOTMANAGED;
692             }
693         }
694
695         // mark auxiliary after-update conflict files as ignored
696
// C source.java
697
// I source.java.mine
698
// I source.java.r45
699
// I source.java.r57
700
//
701
// XXX:svnClientAdapter design: why is not it returned from getSingleStatus() ?
702
//
703
// after-merge conflicts (even svn st does not recognize as ignored)
704
// C source.java
705
// ? source.java.working
706
// ? source.jave.merge-right.r20
707
// ? source.java.merge-left.r0
708
//
709
// XXX:svn-cli design: why is not it returned from getSingleStatus() ?
710

711         String JavaDoc name = file.getName();
712         Matcher m = auxConflictPattern.matcher(name);
713         if (m.matches()) {
714             File JavaDoc dir = file.getParentFile();
715             if (dir != null) {
716                 String JavaDoc masterName = m.group(1);
717                 File JavaDoc master = new File JavaDoc(dir, masterName);
718                 if (master.isFile()) {
719                     return FILE_INFORMATION_EXCLUDED;
720                 }
721             }
722         }
723         
724         if (file.exists()) {
725             if (Subversion.getInstance().isIgnored(file)) {
726                 return new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, file.isDirectory());
727             } else {
728                 return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, file.isDirectory());
729             }
730         } else {
731             if (repositoryStatus != REPOSITORY_STATUS_UNKNOWN) {
732                 if (repositoryStatus.getRepositoryTextStatus() == SVNStatusKind.ADDED) {
733                     boolean folder = repositoryStatus.getNodeKind() == SVNNodeKind.DIR;
734                     return new FileInformation(FileInformation.STATUS_VERSIONED_NEWINREPOSITORY, folder);
735                 }
736             }
737             return FILE_INFORMATION_UNKNOWN;
738         }
739     }
740
741     private boolean exists(File JavaDoc file) {
742         if (!file.exists()) return false;
743         return file.getAbsolutePath().equals(FileUtil.normalizeFile(file).getAbsolutePath());
744     }
745
746     ListenersSupport listenerSupport = new ListenersSupport(this);
747     public void addVersioningListener(VersioningListener listener) {
748         listenerSupport.addListener(listener);
749     }
750
751     public void removeVersioningListener(VersioningListener listener) {
752         listenerSupport.removeListener(listener);
753     }
754     
755     private void fireFileStatusChanged(File JavaDoc file, FileInformation oldInfo, FileInformation newInfo) {
756         listenerSupport.fireVersioningEvent(EVENT_FILE_STATUS_CHANGED, new Object JavaDoc [] { file, oldInfo, newInfo });
757     }
758
759     public void setCommand(int command) {
760         // boring ISVNNotifyListener event
761
}
762
763     public void logCommandLine(String JavaDoc commandLine) {
764         // boring ISVNNotifyListener event
765
}
766
767     public void logMessage(String JavaDoc message) {
768         // boring ISVNNotifyListener event
769
}
770
771     public void logError(String JavaDoc message) {
772         // boring ISVNNotifyListener event
773
}
774
775     public void logRevision(long revision, String JavaDoc path) {
776         // boring ISVNNotifyListener event
777
}
778
779     public void logCompleted(String JavaDoc message) {
780         // boring ISVNNotifyListener event
781
}
782
783     public void onNotify(File JavaDoc path, SVNNodeKind kind) {
784
785         if (path == null) { // on kill
786
return;
787         }
788
789         // I saw "./"
790
path = FileUtil.normalizeFile(path);
791
792         // ISVNNotifyListener event
793
// invalidate cached status
794
// force event: an updated file changes status from uptodate to uptodate but its entry changes
795
refresh(path, REPOSITORY_STATUS_UNKNOWN, true);
796
797         // collect the filesystems to notify them in SvnClientInvocationHandler about the external change
798
for (;;) {
799             FileObject fo = FileUtil.toFileObject(path);
800             if (fo != null) {
801                 try {
802                   Set<FileSystem> filesystems = getFilesystemsToRefresh();
803                   synchronized (filesystems) {
804                     filesystems.add(fo.getFileSystem());
805                   }
806                 } catch (FileStateInvalidException e) {
807                     // ignore invalid filesystems
808
}
809                 break;
810             } else {
811                 path = path.getParentFile();
812                 if (path == null) break;
813             }
814         }
815     }
816         
817     public void refreshDirtyFileSystems() {
818         if(refreshFilesystemsTask == null) {
819            RequestProcessor rp = new RequestProcessor();
820            refreshFilesystemsTask = rp.create(new Runnable JavaDoc() {
821                 public void run() {
822                     Set<FileSystem> filesystems = getFilesystemsToRefresh();
823                     FileSystem[] filesystemsToRefresh = new FileSystem[filesystems.size()];
824                     synchronized (filesystems) {
825                         filesystemsToRefresh = filesystems.toArray(new FileSystem[filesystems.size()]);
826                         filesystems.clear();
827                     }
828                     for (int i = 0; i < filesystemsToRefresh.length; i++) {
829                         // don't call refresh() in synchronized (filesystems). It may lead to a deadlock.
830
filesystemsToRefresh[i].refresh(true);
831                     }
832                 }
833            });
834         }
835         refreshFilesystemsTask.schedule(200);
836     }
837     
838     private Set<FileSystem> getFilesystemsToRefresh() {
839         if(filesystemsToRefresh == null) {
840             filesystemsToRefresh = new HashSet<FileSystem>();
841         }
842         return filesystemsToRefresh;
843     }
844         
845     private static final class NotManagedMap extends AbstractMap<File JavaDoc, FileInformation> {
846         public Set<Entry<File JavaDoc, FileInformation>> entrySet() {
847             return Collections.emptySet();
848         }
849     }
850 }
851
Popular Tags