0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.mercurial;
0042:
0043: import org.netbeans.modules.mercurial.util.HgUtils;
0044: import org.netbeans.modules.turbo.Turbo;
0045: import org.netbeans.modules.turbo.CustomProviders;
0046: import org.netbeans.modules.versioning.util.Utils;
0047: import org.netbeans.modules.versioning.spi.VCSContext;
0048: import org.netbeans.modules.versioning.spi.VersioningSupport;
0049: import org.openide.filesystems.FileSystem;
0050: import org.openide.filesystems.FileUtil;
0051: import org.netbeans.modules.mercurial.Mercurial;
0052: import java.io.File;
0053: import java.util.*;
0054: import java.util.logging.Level;
0055: import java.beans.PropertyChangeSupport;
0056: import java.beans.PropertyChangeListener;
0057: import java.io.File;
0058: import org.netbeans.modules.mercurial.util.HgCommand;
0059: import java.util.logging.Level;
0060: import org.netbeans.api.queries.SharabilityQuery;
0061:
0062: /**
0063: * Central part of status management, deduces and caches statuses of files under version control.
0064: *
0065: * @author Maros Sandor
0066: */
0067: public class FileStatusCache {
0068:
0069: /**
0070: * Indicates that status of a file changed and listeners SHOULD check new status
0071: * values if they are interested in this file.
0072: * The New value is a ChangedEvent object (old FileInformation object may be null)
0073: */
0074: public static final String PROP_FILE_STATUS_CHANGED = "status.changed"; // NOI18N
0075:
0076: /**
0077: * A special map saying that no file inside the folder is managed.
0078: */
0079: private static final Map<File, FileInformation> NOT_MANAGED_MAP = new NotManagedMap();
0080:
0081: public static final FileStatus REPOSITORY_STATUS_UNKNOWN = null;
0082:
0083: // Constant FileInformation objects that can be safely reused
0084: // Files that have a revision number cannot share FileInformation objects
0085: private static final FileInformation FILE_INFORMATION_EXCLUDED = new FileInformation(
0086: FileInformation.STATUS_NOTVERSIONED_EXCLUDED, false);
0087: private static final FileInformation FILE_INFORMATION_EXCLUDED_DIRECTORY = new FileInformation(
0088: FileInformation.STATUS_NOTVERSIONED_EXCLUDED, true);
0089: private static final FileInformation FILE_INFORMATION_UPTODATE_DIRECTORY = new FileInformation(
0090: FileInformation.STATUS_VERSIONED_UPTODATE, true);
0091: private static final FileInformation FILE_INFORMATION_NOTMANAGED = new FileInformation(
0092: FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, false);
0093: private static final FileInformation FILE_INFORMATION_NOTMANAGED_DIRECTORY = new FileInformation(
0094: FileInformation.STATUS_NOTVERSIONED_NOTMANAGED, true);
0095: private static final FileInformation FILE_INFORMATION_UNKNOWN = new FileInformation(
0096: FileInformation.STATUS_UNKNOWN, false);
0097: private static final FileInformation FILE_INFORMATION_NEWLOCALLY = new FileInformation(
0098: FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, false);
0099: public static final FileInformation FILE_INFORMATION_CONFLICT = new FileInformation(
0100: FileInformation.STATUS_VERSIONED_CONFLICT, false);
0101: public static final FileInformation FILE_INFORMATION_REMOVEDLOCALLY = new FileInformation(
0102: FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY, false);
0103:
0104: private PropertyChangeSupport listenerSupport = new PropertyChangeSupport(
0105: this );
0106:
0107: /**
0108: * Caches status of files in memory ant on disk
0109: */
0110: private final Turbo turbo;
0111:
0112: private final String FILE_STATUS_MAP = DiskMapTurboProvider.ATTR_STATUS_MAP;
0113:
0114: private DiskMapTurboProvider cacheProvider;
0115:
0116: private Mercurial hg;
0117:
0118: private Set<FileSystem> filesystemsToRefresh;
0119:
0120: FileStatusCache() {
0121: this .hg = Mercurial.getInstance();
0122: cacheProvider = new DiskMapTurboProvider();
0123: turbo = Turbo.createCustom(new CustomProviders() {
0124: private final Set providers = Collections
0125: .singleton(cacheProvider);
0126:
0127: public Iterator providers() {
0128: return providers.iterator();
0129: }
0130: }, 200, 5000);
0131: }
0132:
0133: // --- Public interface -------------------------------------------------
0134:
0135: /**
0136: * Lists <b>modified files</b> and all folders that are known to be inside
0137: * this folder. There are locally modified files present
0138: * plus any files that exist in the folder in the remote repository.
0139: *
0140: * @param dir folder to list
0141: * @return
0142: */
0143: public File[] listFiles(File dir) {
0144: Set<File> files = getScannedFiles(dir, null).keySet();
0145: return files.toArray(new File[files.size()]);
0146: }
0147:
0148: /**
0149: * Check if this context has at least one file with the passed in status
0150: *
0151: * @param context context to examine
0152: * @param includeStatus file status to check for
0153: * @return boolean true if this context contains at least one file with the includeStatus, false otherwise
0154: */
0155: public boolean containsFileOfStatus(VCSContext context,
0156: int includeStatus) {
0157: Map<File, FileInformation> allFiles = cacheProvider
0158: .getAllModifiedValues();
0159: if (allFiles == null) {
0160: Mercurial.LOG.log(Level.FINE,
0161: "containsFileOfStatus(): allFiles == null"); // NOI18N
0162: return false;
0163: }
0164:
0165: Set<File> roots = context.getRootFiles();
0166: Set<File> exclusions = context.getExclusions();
0167: Set<File> setAllFiles = allFiles.keySet();
0168: boolean bExclusions = exclusions != null
0169: && exclusions.size() > 0;
0170: boolean bContainsFile = false;
0171:
0172: for (File file : setAllFiles) {
0173: FileInformation info = (FileInformation) allFiles.get(file);
0174: if ((info.getStatus() & includeStatus) == 0)
0175: continue;
0176: for (File root : roots) {
0177: if (VersioningSupport.isFlat(root)) {
0178: if (file.equals(root)
0179: || file.getParentFile().equals(root)) {
0180: bContainsFile = true;
0181: break;
0182: }
0183: } else {
0184: if (Utils.isAncestorOrEqual(root, file)) {
0185: File fileRoot = hg
0186: .getTopmostManagedParent(file);
0187: File rootRoot = hg
0188: .getTopmostManagedParent(root);
0189: // Make sure that file is in same repository as root
0190: if (rootRoot.equals(fileRoot)) {
0191: bContainsFile = true;
0192: break;
0193: }
0194: }
0195: }
0196: }
0197: // Check it is not an excluded file
0198: if (bContainsFile && bExclusions) {
0199: for (File excluded : exclusions) {
0200: if (!Utils.isAncestorOrEqual(excluded, file)) {
0201: return true;
0202: }
0203: }
0204: } else if (bContainsFile) {
0205: return true;
0206: }
0207: }
0208: return false;
0209: }
0210:
0211: /**
0212: * Lists <b>interesting files</b> that are known to be inside given folders.
0213: * These are locally and remotely modified and ignored files.
0214: *
0215: * <p>This method returns both folders and files.
0216: *
0217: * @param context context to examine
0218: * @param includeStatus limit returned files to those having one of supplied statuses
0219: * @return File [] array of interesting files
0220: */
0221: public File[] listFiles(VCSContext context, int includeStatus) {
0222: Set<File> set = new HashSet<File>();
0223: Map allFiles = cacheProvider.getAllModifiedValues();
0224: if (allFiles == null) {
0225: Mercurial.LOG.log(Level.FINE,
0226: "FileStatusCache: listFiles(): allFiles == null"); // NOI18N
0227: return new File[0];
0228: }
0229:
0230: for (Iterator i = allFiles.keySet().iterator(); i.hasNext();) {
0231: File file = (File) i.next();
0232: FileInformation info = (FileInformation) allFiles.get(file);
0233: if ((info.getStatus() & includeStatus) == 0)
0234: continue;
0235: Set<File> roots = context.getRootFiles();
0236: for (File root : roots) {
0237: if (VersioningSupport.isFlat(root)) {
0238: if (file.equals(root)
0239: || file.getParentFile().equals(root)) {
0240: set.add(file);
0241: break;
0242: }
0243: } else {
0244: if (Utils.isAncestorOrEqual(root, file)) {
0245: File fileRoot = hg
0246: .getTopmostManagedParent(file);
0247: File rootRoot = hg
0248: .getTopmostManagedParent(root);
0249: // Make sure that file is in same repository as root
0250: if (rootRoot.equals(fileRoot)) {
0251: set.add(file);
0252: break;
0253: }
0254: }
0255: }
0256: }
0257: }
0258: if (context.getExclusions().size() > 0) {
0259: for (Iterator i = context.getExclusions().iterator(); i
0260: .hasNext();) {
0261: File excluded = (File) i.next();
0262: for (Iterator j = set.iterator(); j.hasNext();) {
0263: File file = (File) j.next();
0264: if (Utils.isAncestorOrEqual(excluded, file)) {
0265: j.remove();
0266: }
0267: }
0268: }
0269: }
0270: return set.toArray(new File[set.size()]);
0271: }
0272:
0273: /**
0274: * Lists <b>interesting files</b> that are known to be inside given folders.
0275: * These are locally and remotely modified and ignored files.
0276: *
0277: * <p>Comapring to CVS this method returns both folders and files.
0278: *
0279: * @param roots context to examine
0280: * @param includeStatus limit returned files to those having one of supplied statuses
0281: * @return File [] array of interesting files
0282: */
0283: public File[] listFiles(File[] roots, int includeStatus) {
0284: Set<File> set = new HashSet<File>();
0285: Map allFiles = cacheProvider.getAllModifiedValues();
0286: for (Iterator i = allFiles.keySet().iterator(); i.hasNext();) {
0287: File file = (File) i.next();
0288: FileInformation info = (FileInformation) allFiles.get(file);
0289: if ((info.getStatus() & includeStatus) == 0)
0290: continue;
0291: for (int j = 0; j < roots.length; j++) {
0292: File root = roots[j];
0293: if (VersioningSupport.isFlat(root)) {
0294: if (file.getParentFile().equals(root)) {
0295: set.add(file);
0296: break;
0297: }
0298: } else {
0299: if (Utils.isAncestorOrEqual(root, file)) {
0300: set.add(file);
0301: break;
0302: }
0303: }
0304: }
0305: }
0306: return set.toArray(new File[set.size()]);
0307: }
0308:
0309: /**
0310: * Determines the versioning status of a file. This method accesses disk and may block for a long period of time.
0311: *
0312: * @param file file to get status for
0313: * @return FileInformation structure containing the file status
0314: * @see FileInformation
0315: */
0316: public FileInformation getStatus(File file) {
0317: if (file.isDirectory()
0318: && (hg.isAdministrative(file) || HgUtils
0319: .isIgnored(file)))
0320: return FileStatusCache.FILE_INFORMATION_EXCLUDED_DIRECTORY;
0321: File dir = file.getParentFile();
0322: if (dir == null) {
0323: return FileStatusCache.FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
0324: }
0325: Map files = getScannedFiles(dir, null);
0326: if (files == FileStatusCache.NOT_MANAGED_MAP)
0327: return FileStatusCache.FILE_INFORMATION_NOTMANAGED;
0328: FileInformation fi = (FileInformation) files.get(file);
0329: if (fi != null) {
0330: return fi;
0331: }
0332: if (!exists(file))
0333: return FileStatusCache.FILE_INFORMATION_UNKNOWN;
0334: if (file.isDirectory()) {
0335: return refresh(file, REPOSITORY_STATUS_UNKNOWN);
0336: } else {
0337: return new FileInformation(
0338: FileInformation.STATUS_VERSIONED_UPTODATE, false);
0339: }
0340: }
0341:
0342: /**
0343: * Looks up cached file status.
0344: *
0345: * @param file file to check
0346: * @return give file's status or null if the file's status is not in cache
0347: */
0348: @SuppressWarnings("unchecked")
0349: // Need to change turbo module to remove warning at source
0350: FileInformation getCachedStatus(File file, boolean bCheckSharability) {
0351: File parent = file.getParentFile();
0352: if (parent == null)
0353: return FileStatusCache.FILE_INFORMATION_NOTMANAGED_DIRECTORY;
0354:
0355: Map<File, FileInformation> files = (Map<File, FileInformation>) turbo
0356: .readEntry(parent, FILE_STATUS_MAP);
0357: FileInformation fi = files != null ? files.get(file) : null;
0358: if (fi != null)
0359: return fi;
0360:
0361: if (file.isDirectory()) {
0362: if (hg.isAdministrative(file)
0363: || HgUtils.isIgnored(file, bCheckSharability)) {
0364: return FileStatusCache.FILE_INFORMATION_EXCLUDED_DIRECTORY;
0365: } else {
0366: return FileStatusCache.FILE_INFORMATION_UPTODATE_DIRECTORY;
0367: }
0368: }
0369:
0370: return fi;
0371: }
0372:
0373: private FileInformation refresh(File file,
0374: FileStatus repositoryStatus, boolean forceChangeEvent) {
0375: Mercurial.LOG.log(Level.FINE, "refresh(): {0}", file); // NOI18N
0376: File dir = file.getParentFile();
0377: if (dir == null) {
0378: return FileStatusCache.FILE_INFORMATION_NOTMANAGED; //default for filesystem roots
0379: }
0380: Map<File, FileInformation> files = getScannedFiles(dir, null); // Has side effect of updating the cache
0381: if (files == FileStatusCache.NOT_MANAGED_MAP
0382: && repositoryStatus == FileStatusCache.REPOSITORY_STATUS_UNKNOWN)
0383: return FileStatusCache.FILE_INFORMATION_NOTMANAGED;
0384: FileInformation current = files.get(file);
0385:
0386: FileInformation fi = createFileInformation(file);
0387:
0388: if (FileStatusCache.equivalent(fi, current)) {
0389: if (forceChangeEvent)
0390: fireFileStatusChanged(file, current, fi);
0391: return fi;
0392: }
0393:
0394: // do not include uptodate files into cache, missing directories must be included
0395: if (current == null
0396: && !fi.isDirectory()
0397: && fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE) {
0398: if (forceChangeEvent)
0399: fireFileStatusChanged(file, current, fi);
0400: return fi;
0401: }
0402:
0403: file = FileUtil.normalizeFile(file);
0404: dir = FileUtil.normalizeFile(dir);
0405: Map<File, FileInformation> newFiles = new HashMap<File, FileInformation>(
0406: files);
0407: if (fi.getStatus() == FileInformation.STATUS_UNKNOWN) {
0408: newFiles.remove(file);
0409: turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
0410: } else if (fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE
0411: && file.isFile()) {
0412: newFiles.remove(file);
0413: } else {
0414: newFiles.put(file, fi);
0415: }
0416: assert newFiles.containsKey(dir) == false;
0417: turbo.writeEntry(dir, FILE_STATUS_MAP,
0418: newFiles.size() == 0 ? null : newFiles);
0419:
0420: if (file.isDirectory() && needRecursiveRefresh(fi, current)) {
0421: File[] content = listFiles(file); // Has side effect of updating the cache
0422: for (int i = 0; i < content.length; i++) {
0423: refresh(content[i],
0424: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0425: }
0426: }
0427: fireFileStatusChanged(file, current, fi);
0428: return fi;
0429: }
0430:
0431: private FileInformation createFileInformation(File file) {
0432: return createFileInformation(file, true);
0433: }
0434:
0435: private FileInformation createFileInformation(File file,
0436: Boolean callStatus) {
0437: Mercurial.LOG.log(Level.FINE,
0438: "createFileInformation(): {0} {1}", new Object[] {
0439: file, callStatus }); // NOI18N
0440: if (file == null)
0441: return FILE_INFORMATION_UNKNOWN;
0442: if (hg.isAdministrative(file))
0443: return FILE_INFORMATION_EXCLUDED_DIRECTORY; // Excluded
0444:
0445: File rootManagedFolder = hg.getTopmostManagedParent(file);
0446: if (rootManagedFolder == null)
0447: return FILE_INFORMATION_UNKNOWN; // Avoiding returning NOT_MANAGED dir or file
0448:
0449: if (file.isDirectory()) {
0450: if (HgUtils.isIgnored(file)) {
0451: return FILE_INFORMATION_EXCLUDED_DIRECTORY; // Excluded
0452: } else {
0453: return FILE_INFORMATION_UPTODATE_DIRECTORY; // Managed dir
0454: }
0455: }
0456:
0457: if (callStatus == false) {
0458: if (HgUtils.isIgnored(file)) {
0459: return FILE_INFORMATION_EXCLUDED; // Excluded
0460: }
0461: return null;
0462: }
0463:
0464: FileInformation fi;
0465: try {
0466: fi = HgCommand.getSingleStatus(rootManagedFolder, file
0467: .getParent(), file.getName());
0468: } catch (HgException ex) {
0469: Mercurial.LOG.log(Level.FINE,
0470: "createFileInformation() file: {0} {1}",
0471: new Object[] { file.getAbsolutePath(),
0472: ex.toString() }); // NOI18N
0473: return FILE_INFORMATION_UNKNOWN;
0474: }
0475: return fi;
0476:
0477: }
0478:
0479: /**
0480: * Refreshes the status of the file given the repository status. Repository status is filled
0481: * in when this method is called while processing server output.
0482: *
0483: * <p>Note: it's not necessary if you use Subversion.getClient(), it
0484: * updates the cache automatically using onNotify(). It's not
0485: * fully reliable for removed files.
0486: *
0487: * @param file
0488: * @param repositoryStatus
0489: */
0490: public FileInformation refresh(File file,
0491: FileStatus repositoryStatus) {
0492: return refresh(file, repositoryStatus, false);
0493: }
0494:
0495: public FileInformation refreshForce(File file,
0496: FileStatus repositoryStatus) {
0497: return refresh(file, repositoryStatus, true);
0498: }
0499:
0500: @SuppressWarnings("unchecked")
0501: // Need to change turbo module to remove warning at source
0502: public Map<File, FileInformation> getScannedFiles(File dir,
0503: Map<File, FileInformation> interestingFiles) {
0504: Map<File, FileInformation> files;
0505:
0506: files = (Map<File, FileInformation>) turbo.readEntry(dir,
0507: FILE_STATUS_MAP);
0508: if (files != null)
0509: return files;
0510: if (isNotManagedByDefault(dir)) {
0511: if (interestingFiles == null)
0512: return FileStatusCache.NOT_MANAGED_MAP;
0513: }
0514:
0515: dir = FileUtil.normalizeFile(dir);
0516: files = scanFolder(dir, interestingFiles);
0517: assert files.containsKey(dir) == false;
0518: turbo.writeEntry(dir, FILE_STATUS_MAP, files);
0519: if (interestingFiles == null) {
0520: for (Iterator i = files.keySet().iterator(); i.hasNext();) {
0521: File file = (File) i.next();
0522: FileInformation info = files.get(file);
0523: if ((info.getStatus() & (FileInformation.STATUS_LOCAL_CHANGE | FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) != 0) {
0524: fireFileStatusChanged(file, null, info);
0525: }
0526: }
0527: }
0528: return files;
0529: }
0530:
0531: public void refreshFileStatus(File file, FileInformation fi,
0532: Map<File, FileInformation> interestingFiles) {
0533: refreshFileStatus(file, fi, interestingFiles, false);
0534: }
0535:
0536: public void refreshFileStatus(File file, FileInformation fi,
0537: Map<File, FileInformation> interestingFiles,
0538: boolean alwaysFireEvent) {
0539: if (file == null || fi == null)
0540: return;
0541: File dir = file.getParentFile();
0542: if (dir == null)
0543: return;
0544:
0545: Map<File, FileInformation> files = getScannedFiles(dir,
0546: interestingFiles);
0547:
0548: if (files == null || files == FileStatusCache.NOT_MANAGED_MAP)
0549: return;
0550: FileInformation current = files.get(file);
0551: if (FileStatusCache.equivalent(fi, current)) {
0552: if (FileStatusCache.equivalent(FILE_INFORMATION_NEWLOCALLY,
0553: fi)) {
0554: if (HgUtils.isIgnored(file)) {
0555: Mercurial.LOG
0556: .log(
0557: Level.FINE,
0558: "refreshFileStatus() file: {0} was LocallyNew but is NotSharable",
0559: file.getAbsolutePath()); // NOI18N
0560: fi = FILE_INFORMATION_EXCLUDED;
0561: } else {
0562: return;
0563: }
0564: } else if (!FileStatusCache.equivalent(
0565: FILE_INFORMATION_REMOVEDLOCALLY, fi)) {
0566: return;
0567: }
0568: }
0569: if (FileStatusCache.equivalent(FILE_INFORMATION_NEWLOCALLY, fi)) {
0570: if (FileStatusCache.equivalent(FILE_INFORMATION_EXCLUDED,
0571: current)) {
0572: Mercurial.LOG
0573: .log(
0574: Level.FINE,
0575: "refreshFileStatus() file: {0} was LocallyNew but is Excluded",
0576: file.getAbsolutePath()); // NOI18N
0577: return;
0578: } else if (current == null) {
0579: if (HgUtils.isIgnored(file)) {
0580: Mercurial.LOG
0581: .log(
0582: Level.FINE,
0583: "refreshFileStatus() file: {0} was LocallyNew but current is null and is not NotSharable",
0584: file.getAbsolutePath()); // NOI18N
0585: fi = FILE_INFORMATION_EXCLUDED;
0586: }
0587: }
0588: }
0589: file = FileUtil.normalizeFile(file);
0590: dir = FileUtil.normalizeFile(dir);
0591: Map<File, FileInformation> newFiles = new HashMap<File, FileInformation>(
0592: files);
0593: if (fi.getStatus() == FileInformation.STATUS_UNKNOWN) {
0594: newFiles.remove(file);
0595: turbo.writeEntry(file, FILE_STATUS_MAP, null); // remove mapping in case of directories
0596: } else if (fi.getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE
0597: && file.isFile()) {
0598: newFiles.remove(file);
0599: } else {
0600: newFiles.put(file, fi);
0601: }
0602: assert files.containsKey(dir) == false;
0603: turbo.writeEntry(dir, FILE_STATUS_MAP, newFiles);
0604:
0605: if (interestingFiles == null) {
0606: fireFileStatusChanged(file, current, fi);
0607: } else if (alwaysFireEvent) {
0608: fireFileStatusChanged(file, null, fi);
0609: }
0610:
0611: return;
0612: }
0613:
0614: /**
0615: * Two FileInformation objects are equivalent if their status contants are equal AND they both reperesent a file (or
0616: * both represent a directory) AND Entries they cache, if they can be compared, are equal.
0617: *
0618: * @param other object to compare to
0619: * @return true if status constants of both object are equal, false otherwise
0620: */
0621: private static boolean equivalent(FileInformation main,
0622: FileInformation other) {
0623: if (other == null || main.getStatus() != other.getStatus()
0624: || main.isDirectory() != other.isDirectory())
0625: return false;
0626:
0627: FileStatus e1 = main.getStatus(null);
0628: FileStatus e2 = other.getStatus(null);
0629: return e1 == e2 || e1 == null || e2 == null
0630: || FileStatusCache.equal(e1, e2);
0631: }
0632:
0633: /**
0634: * Replacement for missing Entry.equals(). It is implemented as a separate method to maintain compatibility.
0635: *
0636: * @param e1 first entry to compare
0637: * @param e2 second Entry to compare
0638: * @return true if supplied entries contain equivalent information
0639: */
0640: private static boolean equal(FileStatus e1, FileStatus e2) {
0641: // TODO: use your own logic here
0642: return true;
0643: }
0644:
0645: private boolean needRecursiveRefresh(FileInformation fi,
0646: FileInformation current) {
0647: if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED
0648: || current != null
0649: && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED)
0650: return true;
0651: if (fi.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED
0652: || current != null
0653: && current.getStatus() == FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)
0654: return true;
0655: return false;
0656: }
0657:
0658: /**
0659: * Refreshes information about a given file or directory ONLY if its status is already cached. The
0660: * only exception are non-existing files (new-in-repository) whose statuses are cached in all cases.
0661: *
0662: * @param file
0663: * @param repositoryStatus
0664: */
0665: public void refreshCached(File file, FileStatus repositoryStatus) {
0666: refresh(file, repositoryStatus);
0667: }
0668:
0669: /**
0670: * Refreshes status of the specfified file or all files inside the
0671: * specified directory.
0672: *
0673: * @param file
0674: */
0675: public void refreshCached(File root) {
0676: if (root.isDirectory()) {
0677: File repository = Mercurial.getInstance()
0678: .getTopmostManagedParent(root);
0679: if (repository == null) {
0680: return;
0681: }
0682: File roots[] = new File[1];
0683: roots[0] = root;
0684: File[] files = listFiles(roots, ~0);
0685: if (files.length == 0) {
0686: return;
0687: }
0688: Map<File, FileInformation> allFiles;
0689: try {
0690: allFiles = HgCommand.getAllStatus(repository, root);
0691: for (int i = 0; i < files.length; i++) {
0692: File file = files[i];
0693: FileInformation fi = allFiles.get(file);
0694: if (fi == null) {
0695: // We have a file in the cache which seems to have disappeared
0696: refresh(
0697: file,
0698: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0699: } else {
0700: refreshFileStatus(file, fi, null);
0701: }
0702: }
0703: } catch (HgException ex) {
0704: Mercurial.LOG
0705: .log(Level.FINE,
0706: "refreshCached() file: {0} {1} { 2} ",
0707: new Object[] {
0708: repository.getAbsolutePath(),
0709: root.getAbsolutePath(),
0710: ex.toString() }); // NOI18N
0711: }
0712: } else {
0713: refresh(root, FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0714: }
0715: }
0716:
0717: /**
0718: * Refreshes status of all files inside given context.
0719: *
0720: * @param ctx context to refresh
0721: */
0722: public void refreshCached(VCSContext ctx) {
0723:
0724: for (File root : ctx.getRootFiles()) {
0725: refreshCached(root);
0726: }
0727: }
0728:
0729: public void addToCache(Set<File> files) {
0730: FileInformation fi = new FileInformation(
0731: FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY, null,
0732: false);
0733: HashMap<File, Map<File, FileInformation>> dirMap = new HashMap<File, Map<File, FileInformation>>(
0734: files.size());
0735:
0736: for (File file : files) {
0737: File parent = file.getParentFile();
0738: file = FileUtil.normalizeFile(file);
0739: parent = FileUtil.normalizeFile(parent);
0740: Map<File, FileInformation> currentDirMap = dirMap
0741: .get(parent);
0742: if (currentDirMap == null) {
0743: // 20 is a guess at number of files in a directory
0744: currentDirMap = new HashMap<File, FileInformation>(20);
0745: dirMap.put(parent, currentDirMap);
0746: }
0747: currentDirMap.put(file, fi);
0748: }
0749: for (File dir : dirMap.keySet()) {
0750: dir = FileUtil.normalizeFile(dir);
0751: Map<File, FileInformation> currentDirMap = dirMap.get(dir);
0752: turbo.writeEntry(dir, FILE_STATUS_MAP, currentDirMap);
0753: }
0754: }
0755:
0756: // --- Package private contract ------------------------------------------
0757:
0758: Map<File, FileInformation> getAllModifiedFiles() {
0759: return cacheProvider.getAllModifiedValues();
0760: }
0761:
0762: /**
0763: * Refreshes given directory and all subdirectories.
0764: *
0765: * @param dir directory to refresh
0766: */
0767: void directoryContentChanged(File dir) {
0768: Map originalFiles = (Map) turbo.readEntry(dir, FILE_STATUS_MAP);
0769: if (originalFiles != null) {
0770: for (Iterator i = originalFiles.keySet().iterator(); i
0771: .hasNext();) {
0772: File file = (File) i.next();
0773: refresh(file, FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0774: }
0775: }
0776: }
0777:
0778: /**
0779: * Cleans up the cache by removing or correcting entries that are no longer valid or correct.
0780: */
0781: void cleanUp() {
0782: Map files = cacheProvider.getAllModifiedValues();
0783: for (Iterator i = files.keySet().iterator(); i.hasNext();) {
0784: File file = (File) i.next();
0785: FileInformation info = (FileInformation) files.get(file);
0786: if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0) {
0787: refresh(file, FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0788: } else if (info.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
0789: // remove entries that were excluded but no longer exist
0790: // cannot simply call refresh on excluded files because of 'excluded on server' status
0791: if (!exists(file)) {
0792: refresh(file,
0793: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0794: }
0795: }
0796: }
0797: }
0798:
0799: // --- Private methods ---------------------------------------------------
0800:
0801: private boolean isNotManagedByDefault(File dir) {
0802: return !dir.exists();
0803: }
0804:
0805: /**
0806: * Scans all files in the given folder, computes and stores their CVS status.
0807: *
0808: * @param dir directory to scan
0809: * @return Map map to be included in the status cache (File => FileInformation)
0810: */
0811: private Map<File, FileInformation> scanFolder(File dir,
0812: Map<File, FileInformation> interestingFiles) {
0813: File[] files = dir.listFiles();
0814: if (files == null) {
0815: if (interestingFiles == null) {
0816: files = new File[0];
0817: } else {
0818: files = interestingFiles.keySet().toArray(
0819: new File[interestingFiles.keySet().size()]);
0820: }
0821: }
0822: Map<File, FileInformation> folderFiles = new HashMap<File, FileInformation>(
0823: files.length);
0824:
0825: Mercurial.LOG.log(Level.FINE, "scanFolder(): {0}", dir); // NOI18N
0826: if (hg.isAdministrative(dir)) {
0827: folderFiles.put(dir, FILE_INFORMATION_EXCLUDED_DIRECTORY); // Excluded dir
0828: return folderFiles;
0829: }
0830:
0831: File rootManagedFolder = hg.getTopmostManagedParent(dir);
0832: if (rootManagedFolder == null) {
0833: // Only interested in looking for Hg managed dirs
0834: for (File file : files) {
0835: if (file.isDirectory()
0836: && hg.getTopmostManagedParent(file) != null) {
0837: if (hg.isAdministrative(file)
0838: || HgUtils.isIgnored(file)) {
0839: Mercurial.LOG
0840: .log(
0841: Level.FINE,
0842: "scanFolder NotMng Ignored Dir {0}: exclude SubDir: {1}", // NOI18N
0843: new Object[] {
0844: dir.getAbsolutePath(),
0845: file.getName() });
0846: folderFiles.put(file,
0847: FILE_INFORMATION_EXCLUDED_DIRECTORY); // Excluded dir
0848: } else {
0849: Mercurial.LOG
0850: .log(
0851: Level.FINE,
0852: "scanFolder NotMng Dir {0}: up to date Dir: {1}", // NOI18N
0853: new Object[] {
0854: dir.getAbsolutePath(),
0855: file.getName() });
0856: folderFiles.put(file,
0857: FILE_INFORMATION_UPTODATE_DIRECTORY);
0858: }
0859: }
0860: // Do NOT put any unmanaged dir's (FILE_INFORMATION_NOTMANAGED_DIRECTORY) or
0861: // files (FILE_INFORMATION_NOTMANAGED) into the folderFiles
0862: }
0863: return folderFiles;
0864: }
0865:
0866: boolean bInIgnoredDir = HgUtils.isIgnored(dir);
0867: if (bInIgnoredDir) {
0868: for (File file : files) {
0869: if (HgUtils.isPartOfMercurialMetadata(file))
0870: continue;
0871:
0872: if (file.isDirectory()) {
0873: folderFiles.put(file,
0874: FILE_INFORMATION_EXCLUDED_DIRECTORY); // Excluded dir
0875: Mercurial.LOG
0876: .log(
0877: Level.FINE,
0878: "scanFolder Mng Ignored Dir {0}: exclude SubDir: {1}", // NOI18N
0879: new Object[] {
0880: dir.getAbsolutePath(),
0881: file.getName() });
0882: } else {
0883: Mercurial.LOG
0884: .log(
0885: Level.FINE,
0886: "scanFolder Mng Ignored Dir {0}: exclude File: {1}", // NOI18N
0887: new Object[] {
0888: dir.getAbsolutePath(),
0889: file.getName() });
0890: folderFiles.put(file, FILE_INFORMATION_EXCLUDED);
0891: }
0892: }
0893: return folderFiles;
0894: }
0895:
0896: if (!Mercurial.getInstance().isGoodVersion())
0897: return folderFiles;
0898:
0899: if (interestingFiles == null) {
0900: try {
0901: interestingFiles = HgCommand.getInterestingStatus(
0902: rootManagedFolder, dir);
0903: } catch (HgException ex) {
0904: Mercurial.LOG
0905: .log(
0906: Level.FINE,
0907: "scanFolder() getInterestingStatus Exception: dir: {0} {1}",
0908: new Object[] { dir.getAbsolutePath(),
0909: ex.toString() }); // NOI18N
0910: return folderFiles;
0911: }
0912: }
0913:
0914: if (interestingFiles == null)
0915: return folderFiles;
0916:
0917: for (File file : files) {
0918: if (HgUtils.isPartOfMercurialMetadata(file))
0919: continue;
0920:
0921: if (file.isDirectory()) {
0922: if (hg.isAdministrative(file)
0923: || HgUtils.isIgnored(file)) {
0924: Mercurial.LOG.log(Level.FINE,
0925: "scanFolder Mng Dir {0}: exclude Dir: {1}", // NOI18N
0926: new Object[] { dir.getAbsolutePath(),
0927: file.getName() });
0928: folderFiles.put(file,
0929: FILE_INFORMATION_EXCLUDED_DIRECTORY); // Excluded dir
0930: } else {
0931: Mercurial.LOG
0932: .log(
0933: Level.FINE,
0934: "scanFolder Mng Dir {0}: up to date Dir: {1}", // NOI18N
0935: new Object[] {
0936: dir.getAbsolutePath(),
0937: file.getName() });
0938: folderFiles.put(file,
0939: FILE_INFORMATION_UPTODATE_DIRECTORY);
0940: }
0941: } else {
0942: FileInformation fi = interestingFiles.get(file);
0943: if (fi == null) {
0944: // We have removed -i from HgCommand.getInterestingFiles
0945: // so we might have a file we should be ignoring
0946: fi = createFileInformation(file, false);
0947: }
0948: if (fi != null
0949: && fi.getStatus() != FileInformation.STATUS_VERSIONED_UPTODATE)
0950: folderFiles.put(file, fi);
0951: }
0952: }
0953: return folderFiles;
0954: }
0955:
0956: private boolean exists(File file) {
0957: if (!file.exists())
0958: return false;
0959: return file.getAbsolutePath().equals(
0960: FileUtil.normalizeFile(file).getAbsolutePath());
0961: }
0962:
0963: public synchronized void addPropertyChangeListener(
0964: PropertyChangeListener listener) {
0965: listenerSupport.addPropertyChangeListener(listener);
0966: }
0967:
0968: public void removePropertyChangeListener(
0969: PropertyChangeListener listener) {
0970: listenerSupport.removePropertyChangeListener(listener);
0971: }
0972:
0973: private void fireFileStatusChanged(File file,
0974: FileInformation oldInfo, FileInformation newInfo) {
0975: listenerSupport.firePropertyChange(PROP_FILE_STATUS_CHANGED,
0976: null, new ChangedEvent(file, oldInfo, newInfo));
0977: }
0978:
0979: public void refreshDirtyFileSystems() {
0980: Set<FileSystem> filesystems = getFilesystemsToRefresh();
0981: FileSystem[] filesystemsToRefresh = new FileSystem[filesystems
0982: .size()];
0983: synchronized (filesystems) {
0984: filesystemsToRefresh = filesystems
0985: .toArray(new FileSystem[filesystems.size()]);
0986: filesystems.clear();
0987: }
0988: for (int i = 0; i < filesystemsToRefresh.length; i++) {
0989: // don't call refresh() in synchronized (filesystems). It may lead to a deadlock.
0990: filesystemsToRefresh[i].refresh(true);
0991: }
0992: }
0993:
0994: private Set<FileSystem> getFilesystemsToRefresh() {
0995: if (filesystemsToRefresh == null) {
0996: filesystemsToRefresh = new HashSet<FileSystem>();
0997: }
0998: return filesystemsToRefresh;
0999: }
1000:
1001: private static final class NotManagedMap extends
1002: AbstractMap<File, FileInformation> {
1003: public Set<Entry<File, FileInformation>> entrySet() {
1004: return Collections.emptySet();
1005: }
1006: }
1007:
1008: public static class ChangedEvent {
1009:
1010: private File file;
1011: private FileInformation oldInfo;
1012: private FileInformation newInfo;
1013:
1014: public ChangedEvent(File file, FileInformation oldInfo,
1015: FileInformation newInfo) {
1016: this .file = file;
1017: this .oldInfo = oldInfo;
1018: this .newInfo = newInfo;
1019: }
1020:
1021: public File getFile() {
1022: return file;
1023: }
1024:
1025: public FileInformation getOldInfo() {
1026: return oldInfo;
1027: }
1028:
1029: public FileInformation getNewInfo() {
1030: return newInfo;
1031: }
1032: }
1033: }
|