KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > versioning > system > cvss > 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.versioning.system.cvss;
21
22 import org.netbeans.modules.versioning.util.ListenersSupport;
23 import org.netbeans.modules.versioning.util.VersioningListener;
24 import org.netbeans.modules.versioning.util.FlatFolder;
25 import org.netbeans.modules.versioning.system.cvss.util.Utils;
26 import org.netbeans.modules.versioning.system.cvss.util.Context;
27 import org.netbeans.modules.turbo.Turbo;
28 import org.netbeans.modules.turbo.CustomProviders;
29 import org.netbeans.lib.cvsclient.admin.Entry;
30 import org.openide.filesystems.FileUtil;
31 import org.openide.ErrorManager;
32
33 import java.io.*;
34 import java.util.*;
35
36 /**
37  * Central part of CVS status management, deduces and caches statuses of files under version control.
38  *
39  * @author Maros Sandor
40  */

41 public class FileStatusCache {
42
43     /**
44      * Indicates that status of a file changed and listeners SHOULD check new status
45      * values if they are interested in this file.
46      * First parameter: File whose status changes
47      * Second parameter: old FileInformation object, may be null
48      * Third parameter: new FileInformation object
49      */

50     public static final Object JavaDoc EVENT_FILE_STATUS_CHANGED = new Object JavaDoc();
51
52     /**
53      * A special map saying that no file inside the folder is managed.
54      */

55     private static final Map<File, FileInformation> NOT_MANAGED_MAP = new NotManagedMap();
56         
57     private static final int STATUS_MISSING =
58             FileInformation.STATUS_VERSIONED_NEWINREPOSITORY |
59             FileInformation.STATUS_VERSIONED_DELETEDLOCALLY |
60             FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY;
61     
62     public static final int REPOSITORY_STATUS_UNKNOWN = 0;
63     public static final int REPOSITORY_STATUS_UPDATED = 'U';
64     public static final int REPOSITORY_STATUS_PATCHED = 'P';
65     public static final int REPOSITORY_STATUS_MODIFIED = 'M';
66     public static final int REPOSITORY_STATUS_CONFLICT = 'C';
67     public static final int REPOSITORY_STATUS_MERGEABLE = 'G';
68     public static final int REPOSITORY_STATUS_REMOVED = 'R';
69     public static final int REPOSITORY_STATUS_REMOVED_REMOTELY = 'Y';
70     public static final int REPOSITORY_STATUS_UPTODATE = 65536;
71
72     // Constant FileInformation objects that can be safely reused
73
// Files that have a revision number cannot share FileInformation objects
74
private static final FileInformation FILE_INFORMATION_EXCLUDED = new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, false);
75     private static final FileInformation FILE_INFORMATION_EXCLUDED_DIRECTORY = new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, true);
76     private static final FileInformation FILE_INFORMATION_UPTODATE_DIRECTORY = new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, true);
77     private static final FileInformation FILE_INFORMATION_NOTMANAGED = new FileInformation(FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, false);
78     private static final FileInformation FILE_INFORMATION_NOTMANAGED_DIRECTORY = new FileInformation(FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, true);
79     private static final FileInformation FILE_INFORMATION_UNKNOWN = new FileInformation(FileInformation.STATUS_UNKNOWN, false);
80
81     private final CvsVersioningSystem cvs;
82     private final CvsLiteAdminHandler sah;
83
84     /*
85      * Holds three kinds of information: what folders we have scanned, what files we have found
86      * and what statuses of these files are.
87      * If a directory is not found as a key in the map, we have not scanned it yet.
88      * If it has been scanned, it maps to a Set of files that were found somehow out of sync with the
89      * repository (have any other status then up-to-date). In case all files are up-to-date, it maps
90      * to Collections.EMPTY_MAP. Entries in this map are created as directories are scanne, are never removed and
91      * are updated by the refresh method.
92      */

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

102     private final String JavaDoc FILE_STATUS_MAP = DiskMapTurboProvider.ATTR_STATUS_MAP;
103
104     private DiskMapTurboProvider cacheProvider;
105
106     FileStatusCache(CvsVersioningSystem cvsVersioningSystem) {
107         this.cvs = cvsVersioningSystem;
108         sah = (CvsLiteAdminHandler) cvs.getAdminHandler();
109
110         cacheProvider = new DiskMapTurboProvider();
111         turbo = Turbo.createCustom(new CustomProviders() {
112             private final Set providers = Collections.singleton(cacheProvider);
113             public Iterator providers() {
114                 return providers.iterator();
115             }
116         }, 200, 5000);
117     }
118
119     // --- Public interface -------------------------------------------------
120

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

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

144     public File [] listFiles(Context context, int includeStatus) {
145         Set<File> set = new HashSet<File>();
146         Map allFiles = cacheProvider.getAllModifiedValues();
147         for (Iterator i = allFiles.keySet().iterator(); i.hasNext();) {
148             File file = (File) i.next();
149             FileInformation info = (FileInformation) allFiles.get(file);
150             if (info.isDirectory() || (info.getStatus() & includeStatus) == 0) continue;
151             File [] roots = context.getRootFiles();
152             for (int j = 0; j < roots.length; j++) {
153                 File root = roots[j];
154                 if (root instanceof FlatFolder) {
155                     if (file.getParentFile().equals(root)) {
156                         set.add(file);
157                         break;
158                     }
159                 } else {
160                     if (Utils.isParentOrEqual(root, file)) {
161                         set.add(file);
162                         break;
163                     }
164                 }
165             }
166         }
167         if (context.getExclusions().size() > 0) {
168             for (Iterator i = context.getExclusions().iterator(); i.hasNext();) {
169                 File excluded = (File) i.next();
170                 for (Iterator j = set.iterator(); j.hasNext();) {
171                     File file = (File) j.next();
172                     if (Utils.isParentOrEqual(excluded, file)) {
173                         j.remove();
174                     }
175                 }
176             }
177         }
178         return (File[]) set.toArray(new File[set.size()]);
179     }
180
181     /**
182      * Determines the CVS status of a file. This method accesses disk and may block for a long period of time.
183      *
184      * @param file file to get status for
185      * @return FileInformation structure containing the file status
186      * @see FileInformation
187      */

188     public FileInformation getStatus(File file) {
189         if (file.getName().equals(CvsVersioningSystem.FILENAME_CVS)) return FILE_INFORMATION_NOTMANAGED_DIRECTORY;
190         File dir = file.getParentFile();
191         if (dir == null) {
192             return FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
193
}
194         Map files = getScannedFiles(dir);
195         if (files == NOT_MANAGED_MAP) return FILE_INFORMATION_NOTMANAGED;
196         FileInformation fi = (FileInformation) files.get(file);
197         if (fi != null) {
198             return fi;
199         }
200         if (!exists(file)) return FILE_INFORMATION_UNKNOWN;
201         if (file.isDirectory()) {
202             return refresh(file, REPOSITORY_STATUS_UNKNOWN);
203         } else {
204             return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, false);
205         }
206     }
207     
208     /**
209      * Refreshes the status of the file given the repository status. Repository status is filled
210      * in when this method is called while processing server output.
211      *
212      * @param file
213      * @param repositoryStatus
214      * @param forceChangeEvent true to force the cache to fire the change event even if the status of the file has not changed
215      */

216     public FileInformation refresh(File file, int repositoryStatus, boolean forceChangeEvent) {
217         File dir = file.getParentFile();
218         if (dir == null) {
219             return FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
220
}
221         Map<File, FileInformation> files = getScannedFiles(dir);
222         if (files == NOT_MANAGED_MAP) return FILE_INFORMATION_NOTMANAGED;
223         FileInformation current = (FileInformation) files.get(file);
224         Entry entry = null;
225         try {
226             entry = cvs.getAdminHandler().getEntry(file);
227         } catch (IOException e) {
228             // no entry for this file
229
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
230         }
231         FileInformation fi = createFileInformation(file, entry, repositoryStatus);
232         if (equivalent(fi, current)) {
233             if (forceChangeEvent) fireFileStatusChanged(file, current, fi);
234             return fi;
235         }
236         // do not include uptodate files into cache, missing directories must be included
237
if (current == null && !fi.isDirectory() && fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE) {
238             if (forceChangeEvent) fireFileStatusChanged(file, current, fi);
239             return fi;
240         }
241
242         file = FileUtil.normalizeFile(file);
243         dir = FileUtil.normalizeFile(dir);
244         Map<File, FileInformation> newFiles = new HashMap<File, FileInformation>(files);
245         if (fi.getStatus() == FileInformation.STATUS_UNKNOWN) {
246             newFiles.remove(file);
247             turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
248
}
249         else if (fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE && file.isFile()) {
250             newFiles.remove(file);
251         } else {
252             newFiles.put(file, fi);
253         }
254         turbo.writeEntry(dir, FILE_STATUS_MAP, newFiles.size() == 0 ? null : newFiles);
255
256         if (file.isDirectory() && needRecursiveRefresh(fi, current)) {
257             File [] content = listFiles(file);
258             for (int i = 0; i < content.length; i++) {
259                 refresh(content[i], REPOSITORY_STATUS_UNKNOWN);
260             }
261         }
262         fireFileStatusChanged(file, current, fi);
263         return fi;
264     }
265     
266     /**
267      * Refreshes the status of the file given the repository status. Repository status is filled
268      * in when this method is called while processing server output.
269      *
270      * @param file
271      * @param repositoryStatus
272      */

273     public FileInformation refresh(File file, int repositoryStatus) {
274         return refresh(file, repositoryStatus, false);
275     }
276
277     /**
278      * Creates local file information. This method does not use cache, it always reads status from disk.
279      *
280      * @param file
281      * @return FileInformation
282      */

283     FileInformation createFileInformation(File file) {
284         Entry entry = null;
285         try {
286             entry = cvs.getAdminHandler().getEntry(file);
287         } catch (IOException e) {
288             // no entry for this file
289
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
290         }
291         return createFileInformation(file, entry, REPOSITORY_STATUS_UNKNOWN);
292     }
293
294     /**
295      * Two FileInformation objects are equivalent if their status contants are equal AND they both reperesent a file (or
296      * both represent a directory) AND Entries they cache, if they can be compared, are equal.
297      *
298      * @param other object to compare to
299      * @return true if status constants of both object are equal, false otherwise
300      */

301     private static boolean equivalent(FileInformation main, FileInformation other) {
302         if (other == null || main.getStatus() != other.getStatus() || main.isDirectory() != other.isDirectory()) return false;
303         Entry e1 = main.getEntry(null);
304         Entry e2 = other.getEntry(null);
305         return e1 == e2 || e1 == null || e2 == null || equal(e1, e2);
306     }
307
308     /**
309      * Replacement for missing Entry.equals(). It is implemented as a separate method to maintain compatibility.
310      *
311      * @param e1 first entry to compare
312      * @param e2 second Entry to compare
313      * @return true if supplied entries contain equivalent information
314      */

315     private static boolean equal(Entry e1, Entry e2) {
316         if (!e1.getRevision().equals(e2.getRevision())) return false;
317         return e1.getStickyInformation() == e2.getStickyInformation() ||
318                 e1.getStickyInformation() != null && e1.getStickyInformation().equals(e2.getStickyInformation());
319     }
320     
321     private boolean needRecursiveRefresh(FileInformation fi, FileInformation current) {
322         if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED ||
323                 current != null && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) return true;
324         if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED ||
325                 current != null && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED) return true;
326         return false;
327     }
328
329     /**
330      * Refreshes information about a given file or directory ONLY if its status is already cached. The
331      * only exception are non-existing files (new-in-repository) whose statuses are cached in all cases.
332      *
333      * @param file
334      * @param repositoryStatus
335      */

336     public void refreshCached(File file, int repositoryStatus) {
337         refresh(file, repositoryStatus);
338     }
339
340     /**
341      * Scans given directory and performs two tasks: 1) refreshes all cached file statuses, 2) removes from cache
342      * all files having one of the {@link STATUS_MISSING} status.
343      *
344      * @param dir directory to cleanup
345      */

346     public void clearVirtualDirectoryContents(File dir, boolean recursive, File [] exclusions) {
347         Map<File, FileInformation> files = (Map<File, FileInformation>) turbo.readEntry(dir, FILE_STATUS_MAP);
348         if (files == null) {
349            return;
350         }
351         Set<File> set = new HashSet<File>(files.keySet());
352         Map<File, FileInformation> newMap = null;
353         outter: for (Iterator i = set.iterator(); i.hasNext();) {
354             File file = (File) i.next();
355             if (exclusions != null) {
356                 for (int j = 0; j < exclusions.length; j++) {
357                     if (Utils.isParentOrEqual(exclusions[j], file)) continue outter;
358                 }
359             }
360             if (recursive && file.isDirectory()) {
361                 clearVirtualDirectoryContents(file, true, exclusions);
362             }
363             FileInformation fi = refresh(file, REPOSITORY_STATUS_UNKNOWN);
364             if ((fi.getStatus() & STATUS_MISSING) != 0) {
365                 if (newMap == null) newMap = new HashMap<File, FileInformation>(files);
366                 newMap.remove(file);
367             }
368         }
369         if (newMap != null) {
370             dir = FileUtil.normalizeFile(dir);
371             turbo.writeEntry(dir, FILE_STATUS_MAP, newMap);
372         }
373     }
374
375     // --- Package private contract ------------------------------------------
376

377     Map<File, FileInformation> getAllModifiedFiles() {
378         return cacheProvider.getAllModifiedValues();
379     }
380
381     /**
382      * Refreshes given directory and all subdirectories.
383      *
384      * @param dir directory to refresh
385      */

386     void directoryContentChanged(File dir) {
387         Map originalFiles = (Map) turbo.readEntry(dir, FILE_STATUS_MAP);
388         if (originalFiles != null) {
389             for (Iterator i = originalFiles.keySet().iterator(); i.hasNext();) {
390                 File file = (File) i.next();
391                 refresh(file, REPOSITORY_STATUS_UNKNOWN);
392             }
393         }
394     }
395     
396     /**
397      * Cleans up the cache by removing or correcting entries that are no longer valid or correct.
398      */

399     void cleanUp() {
400         long t0 = System.currentTimeMillis();
401         Map files = cacheProvider.getAllModifiedValues();
402         for (Iterator i = files.keySet().iterator(); i.hasNext();) {
403             File file = (File) i.next();
404             FileInformation info = (FileInformation) files.get(file);
405             if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0) {
406                 refresh(file, REPOSITORY_STATUS_UNKNOWN);
407             } else if (info.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
408                 // remove entries that were excluded but no longer exist
409
// cannot simply call refresh on excluded files because of 'excluded on server' status
410
if (!exists(file)) {
411                     refresh(file, REPOSITORY_STATUS_UNKNOWN);
412                 }
413             }
414             if (System.currentTimeMillis() - t0 > 15000) break; // lots of files in the cache, abort cleanup
415
}
416     }
417         
418     // --- Private methods ---------------------------------------------------
419

420     private Map<File, FileInformation> getScannedFiles(File dir) {
421         Map<File, FileInformation> files;
422         if (dir.getName().equals(CvsVersioningSystem.FILENAME_CVS)) return NOT_MANAGED_MAP;
423         files = (Map<File, FileInformation>) turbo.readEntry(dir, FILE_STATUS_MAP);
424         if (files != null) return files;
425         if (!dir.exists()) {
426             return NOT_MANAGED_MAP;
427         }
428
429         // scan and populate cache with results
430

431         dir = FileUtil.normalizeFile(dir);
432         files = scanFolder(dir); // must not execute while holding the lock, it may take long to execute
433
turbo.writeEntry(dir, FILE_STATUS_MAP, files);
434         for (Iterator i = files.keySet().iterator(); i.hasNext();) {
435             File file = (File) i.next();
436             FileInformation info = (FileInformation) files.get(file);
437             if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0) fireFileStatusChanged(file, null, info);
438         }
439         return files;
440     }
441
442     /**
443      * Scans all files in the given folder, computes and stores their CVS status.
444      *
445      * @param dir directory to scan
446      * @return Map map to be included in the status cache (File => FileInformation)
447      */

448     private Map<File, FileInformation> scanFolder(File dir) {
449         File [] files = dir.listFiles();
450         if (files == null) files = new File[0];
451         Map<File, FileInformation> folderFiles = new HashMap<File, FileInformation>(files.length);
452
453         Entry [] entries = null;
454         try {
455             entries = sah.getEntriesAsArray(dir);
456         } catch (IOException e) {
457             // no or damaged entries
458
}
459
460         for (int i = 0; i < files.length; i++) {
461             File file = files[i];
462             String JavaDoc filename = file.getName();
463             if (filename.equals(CvsVersioningSystem.FILENAME_CVS)) continue;
464             Entry entry = getEntry(dir, entries, file);
465             FileInformation fi = createFileInformation(file, entry, REPOSITORY_STATUS_UNKNOWN);
466             // directories are always in cache for listFiles() to work
467
if (fi.isDirectory() || fi.getStatus() != FileInformation.STATUS_VERSIONED_UPTODATE) {
468                 folderFiles.put(file, fi);
469             }
470         }
471
472         if (entries != null) {
473             outter : for (int i = 0; i < entries.length; i++) {
474                 Entry entry = entries[i];
475                 File entryFile = new File(dir, entry.getName());
476                 for (int j = 0; j < files.length; j++) {
477                     File file = files[j];
478                     if (entryFile.equals(file)) {
479                         continue outter;
480                     }
481                 }
482                 FileInformation fi = createFileInformation(entryFile, entry, REPOSITORY_STATUS_UNKNOWN);
483                 folderFiles.put(entryFile, fi);
484             }
485         }
486         return folderFiles;
487     }
488
489     /**
490      * Searches array of Entries for the given filename.
491      *
492      * @param entries array of Entries, may be null
493      * @param file file to search for
494      * @return corresponding entry or null
495      */

496     private Entry getEntry(File dir, Entry[] entries, File file) {
497         if (entries != null) {
498             for (int i = 0; i < entries.length; i++) {
499                 Entry entry = entries[i];
500                 File entryFile = new File(dir, entry.getName());
501                 if (file.equals(entryFile)) return entry;
502             }
503         }
504         return null;
505     }
506
507     /**
508      * Examines a file or folder and computes its CVS status.
509      *
510      * @param file file/folder to examine
511      * @param entry CVS entry for this file or null if the file does not have a corresponding entry in CVS/Entries
512      * @param repositoryStatus status of the file/folder as reported by the CVS server
513      * @return FileInformation file/folder status bean
514      */

515     private FileInformation createFileInformation(File file, Entry entry, int repositoryStatus) {
516         if (entry == null) {
517             if (!cvs.isManaged(file)) {
518                 if (file.exists()) {
519                     return file.isDirectory() ? FILE_INFORMATION_NOTMANAGED_DIRECTORY : FILE_INFORMATION_NOTMANAGED;
520                 } else {
521                     return FILE_INFORMATION_UNKNOWN;
522                 }
523             }
524             return createMissingEntryFileInformation(file, repositoryStatus);
525         } else {
526             return createVersionedFileInformation(entry, file, repositoryStatus);
527         }
528     }
529
530     /**
531      * Examines a file or folder that has an associated CVS entry.
532      *
533      * @param entry entry of the file/folder
534      * @param file file/folder to examine
535      * @param repositoryStatus status of the file/folder as reported by the CVS server
536      * @return FileInformation file/folder status bean
537      */

538     private FileInformation createVersionedFileInformation(Entry entry, File file, int repositoryStatus) {
539         if (entry.isDirectory()) {
540             if (file.exists()) {
541                 if (new File(file, CvsVersioningSystem.FILENAME_CVS).isDirectory()) {
542                     return FILE_INFORMATION_UPTODATE_DIRECTORY;
543                 } else {
544                     return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, true);
545                 }
546             } else {
547                 return new FileInformation(FileInformation.STATUS_VERSIONED_DELETEDLOCALLY, true);
548             }
549         }
550         if (entry.isNewUserFile()) {
551             return new FileInformation(FileInformation.STATUS_VERSIONED_ADDEDLOCALLY, entry, false);
552         } else if (entry.isUserFileToBeRemoved()) {
553             return new FileInformation(FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY, entry, false);
554         } else {
555             if (!exists(file)) {
556                 return new FileInformation(FileInformation.STATUS_VERSIONED_DELETEDLOCALLY, entry, false);
557             }
558             if (repositoryStatus == REPOSITORY_STATUS_UPTODATE) {
559                 if (!entryTimestampMatches(entry, file)) {
560                     entry.setConflict(Entry.getLastModifiedDateFormatter().format(new Date(file.lastModified())));
561                     try {
562                         sah.setEntry(file, entry);
563                     } catch (IOException e) {
564                         ErrorManager.getDefault().notify(e);
565                     }
566                 }
567                 return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, entry, false);
568             } else if (repositoryStatus == REPOSITORY_STATUS_UPDATED || repositoryStatus == REPOSITORY_STATUS_PATCHED) {
569                 return new FileInformation(FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY, entry, false);
570             } else if (repositoryStatus == REPOSITORY_STATUS_MODIFIED) {
571                 FileInformation fi = new FileInformation(FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY, entry, false);
572                 return fi;
573             } else if (repositoryStatus == REPOSITORY_STATUS_CONFLICT) {
574                 if (isLocalConflict(entry, file)) {
575                     return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT, entry, false);
576                 } else {
577                     return new FileInformation(FileInformation.STATUS_VERSIONED_MERGE, entry, false);
578                 }
579             } else if (repositoryStatus == REPOSITORY_STATUS_MERGEABLE) {
580                 return new FileInformation(FileInformation.STATUS_VERSIONED_MERGE, entry, false);
581             } else if (repositoryStatus == REPOSITORY_STATUS_REMOVED_REMOTELY) {
582                 return new FileInformation(FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY, entry, false);
583             } else if (repositoryStatus == REPOSITORY_STATUS_UNKNOWN || repositoryStatus == '?') {
584                 if (exists(file)) {
585                     if (isLocalConflict(entry, file)) {
586                         return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT, entry, false);
587                     } else if (entryTimestampMatches(entry, file)) {
588                         return new FileInformation(FileInformation.STATUS_VERSIONED_UPTODATE, entry, false);
589                     } else {
590                         FileInformation fi = new FileInformation(FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY, entry, false);
591                         return fi;
592                     }
593                 } else {
594                     return new FileInformation(FileInformation.STATUS_VERSIONED_DELETEDLOCALLY, entry, false);
595                 }
596             }
597         }
598         throw new IllegalArgumentException JavaDoc("Unknown repository status: " + (char)repositoryStatus); // NOI18N
599
}
600
601     private boolean isLocalConflict(Entry entry, File file) {
602         return exists(file) && entry.hadConflicts() && entryTimestampMatches(entry, file);
603     }
604
605     /**
606      * Examines a file or folder that does NOT have an associated CVS entry.
607      *
608      * @param file file/folder to examine
609      * @param repositoryStatus status of the file/folder as reported by the CVS server
610      * @return FileInformation file/folder status bean
611      */

612     private FileInformation createMissingEntryFileInformation(File file, int repositoryStatus) {
613         boolean isDirectory = file.isDirectory();
614         int parentStatus = getStatus(file.getParentFile()).getStatus();
615         if (parentStatus == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
616             return isDirectory ? FILE_INFORMATION_EXCLUDED_DIRECTORY : FILE_INFORMATION_EXCLUDED;
617         }
618         if (parentStatus == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED) {
619             if (isDirectory) {
620                 // Working directory roots (aka managed roots). We already know that cvs.isManaged(file) is true
621
return isInsideCvsMetadata(file) ? FILE_INFORMATION_NOTMANAGED_DIRECTORY : FILE_INFORMATION_UPTODATE_DIRECTORY;
622             } else {
623                 return FILE_INFORMATION_NOTMANAGED;
624             }
625         }
626         if (repositoryStatus == REPOSITORY_STATUS_UNKNOWN || repositoryStatus == '?') {
627             if (exists(file)) {
628                 if (cvs.isIgnored(file)) {
629                     return isDirectory ? FILE_INFORMATION_EXCLUDED_DIRECTORY : FILE_INFORMATION_EXCLUDED;
630                 }
631                 return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, isDirectory);
632             } else {
633                 return new FileInformation(FileInformation.STATUS_UNKNOWN, false);
634             }
635         } else if (repositoryStatus == REPOSITORY_STATUS_UPDATED) {
636             if (file.exists()) {
637                 // the file should be fetched from server but it already exists locally, this will create a conflict
638
return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT, isDirectory);
639             } else {
640                 return new FileInformation(FileInformation.STATUS_VERSIONED_NEWINREPOSITORY, isDirectory);
641             }
642         } else if (repositoryStatus == REPOSITORY_STATUS_UPTODATE) {
643             if (parentStatus == FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY) {
644                 return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, isDirectory);
645             } else {
646                 // server marks this file as uptodate and it does not have an entry, the file is probably listed in CVSROOT/cvsignore
647
return new FileInformation(FileInformation.STATUS_NOTVERSIONED_EXCLUDED, isDirectory);
648             }
649         } else if (repositoryStatus == REPOSITORY_STATUS_REMOVED_REMOTELY) {
650             if (exists(file)) {
651                 return new FileInformation(FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, isDirectory);
652             } else {
653                 return FILE_INFORMATION_UNKNOWN;
654             }
655         } else if (repositoryStatus == REPOSITORY_STATUS_CONFLICT) {
656             // happens for files that exist locally and are also in repository
657
// CVS reports: cvs.exe update: move away THIS_FILE; it is in the way
658
return new FileInformation(FileInformation.STATUS_VERSIONED_CONFLICT, false);
659         }
660         throw new IllegalArgumentException JavaDoc("Unknown repository status: " + (char)repositoryStatus + " for: " + file.getAbsolutePath()); // NOI18N
661
}
662
663     private boolean isInsideCvsMetadata(File file) {
664         return file.getAbsolutePath().indexOf(File.separator + CvsVersioningSystem.FILENAME_CVS + File.separator) != -1;
665     }
666
667     private boolean exists(File file) {
668         if (!file.exists()) return false;
669         return file.getAbsolutePath().equals(FileUtil.normalizeFile(file).getAbsolutePath());
670     }
671
672     private boolean entryTimestampMatches(Entry entry, File file) {
673         Date d = entry.getLastModified();
674         if (d == null) return false;
675         long t0 = d.getTime();
676         long t1 = file.lastModified() / 1000 * 1000;
677         if (TimeZone.getDefault().inDaylightTime(entry.getLastModified())) {
678             t1 -= TimeZone.getDefault().getDSTSavings();
679         }
680         return t0 == t1 || t0 - t1 == 3600000 || t1 - t0 == 3600000;
681     }
682     
683     ListenersSupport listenerSupport = new ListenersSupport(this);
684     public void addVersioningListener(VersioningListener listener) {
685         listenerSupport.addListener(listener);
686     }
687
688     public void removeVersioningListener(VersioningListener listener) {
689         listenerSupport.removeListener(listener);
690     }
691     
692     private void fireFileStatusChanged(File file, FileInformation oldInfo, FileInformation newInfo) {
693         listenerSupport.fireVersioningEvent(EVENT_FILE_STATUS_CHANGED, new Object JavaDoc [] { file, oldInfo, newInfo });
694     }
695
696     public FileInformation getCachedStatus(File file) {
697         file = file.getParentFile();
698         if (file == null) return FILE_INFORMATION_NOTMANAGED_DIRECTORY;
699         Map<File, FileInformation> files = (Map<File, FileInformation>) turbo.readEntry(file, FILE_STATUS_MAP);
700         return files != null ? files.get(file) : null;
701     }
702
703     private static final class NotManagedMap extends AbstractMap<File, FileInformation> {
704         public Set entrySet() {
705             return Collections.EMPTY_SET;
706         }
707     }
708 }
709
Popular Tags