# HG changeset patch # User Artem Tikhomirov # Date 1315380807 -7200 # Node ID ed6b74a58c66c1b16eb420fe1a1c65bcbd083e3d # Parent 954763c82cc30f8a35074dd5d69c8e7bb7cd5604 Use FileInfo abstraction with necessary subset of File functionality instead of File to facilitate other effective file system iterators diff -r 954763c82cc3 -r ed6b74a58c66 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Sat Sep 03 22:06:16 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Wed Sep 07 09:33:27 2011 +0200 @@ -21,10 +21,9 @@ import static org.tmatesoft.hg.repo.HgRepository.*; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.Collections; import java.util.NoSuchElementException; @@ -41,11 +40,13 @@ import org.tmatesoft.hg.internal.PathScope; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.FileInfo; import org.tmatesoft.hg.util.FileIterator; import org.tmatesoft.hg.util.FileWalker; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathPool; import org.tmatesoft.hg.util.PathRewrite; +import org.tmatesoft.hg.util.RegularFileInfo; /** * @@ -162,7 +163,7 @@ while (repoWalker.hasNext()) { repoWalker.next(); final Path fname = getPathPool().path(repoWalker.name()); - File f = repoWalker.file(); + FileInfo f = repoWalker.file(); if (!f.exists()) { // file coming from iterator doesn't exist. if (knownEntries.remove(fname)) { @@ -193,7 +194,6 @@ } continue; } - assert f.isFile(); if (knownEntries.remove(fname)) { // tracked file. // modified, added, removed, clean @@ -248,11 +248,11 @@ //******************************************** - private void checkLocalStatusAgainstFile(Path fname, File f, HgStatusInspector inspector) { + private void checkLocalStatusAgainstFile(Path fname, FileInfo f, HgStatusInspector inspector) { HgDirstate.Record r; if ((r = getDirstate().checkNormal(fname)) != null) { // either clean or modified - final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); + final boolean timestampEqual = f.lastModified() == r.time, sizeEqual = r.size == f.length(); if (timestampEqual && sizeEqual) { inspector.clean(fname); } else if (!sizeEqual && r.size >= 0) { @@ -281,13 +281,8 @@ } } - // return mtime analog, directly comparable to dirstate's mtime. - private static int getFileModificationTime(File f) { - return (int) (f.lastModified() / 1000); - } - // XXX refactor checkLocalStatus methods in more OO way - private void checkLocalStatusAgainstBaseRevision(Set baseRevNames, ManifestRevision collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) { + private void checkLocalStatusAgainstBaseRevision(Set baseRevNames, ManifestRevision collect, int baseRevision, Path fname, FileInfo f, HgStatusInspector inspector) { // fname is in the dirstate, either Normal, Added, Removed or Merged Nodeid nid1 = collect.nodeid(fname); HgManifest.Flags flags = collect.flags(fname); @@ -325,7 +320,7 @@ if ((r = getDirstate().checkNormal(fname)) != null && nid1.equals(nidFromDirstate)) { // regular file, was the same up to WC initialization. Check if was modified since, and, if not, report right away // same code as in #checkLocalStatusAgainstFile - final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); + final boolean timestampEqual = f.lastModified() == r.time, sizeEqual = r.size == f.length(); boolean handled = false; if (timestampEqual && sizeEqual) { inspector.clean(fname); @@ -373,7 +368,7 @@ // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' } - private boolean areTheSame(File f, HgDataFile dataFile, Nodeid revision) { + private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) { // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison ByteArrayChannel bac = new ByteArrayChannel(); boolean ioFailed = false; @@ -390,12 +385,11 @@ return !ioFailed && areTheSame(f, bac.toArray(), dataFile.getPath()); } - private boolean areTheSame(File f, final byte[] data, Path p) { - FileInputStream fis = null; + private boolean areTheSame(FileInfo f, final byte[] data, Path p) { + ReadableByteChannel is = null; try { try { - fis = new FileInputStream(f); - FileChannel fc = fis.getChannel(); + is = f.newInputChannel(); ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192)); class Check implements ByteChannel { final boolean debug = false; // XXX may want to add global variable to allow clients to turn @@ -431,18 +425,18 @@ }; Check check = new Check(); FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p)); - while (fc.read(fb) != -1 && check.sameSoFar()) { + while (is.read(fb) != -1 && check.sameSoFar()) { fb.flip(); filters.write(fb); fb.compact(); } - fis.close(); return check.ultimatelyTheSame(); } catch (IOException ex) { - if (fis != null) { - fis.close(); + ex.printStackTrace(); // log warn + } finally { + if (is != null) { + is.close(); } - ex.printStackTrace(); // log warn } } catch (/*TODO typed*/Exception ex) { ex.printStackTrace(); @@ -450,7 +444,7 @@ return false; } - private static boolean todoCheckFlagsEqual(File f, HgManifest.Flags originalManifestFlags) { + private static boolean todoCheckFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) { // FIXME implement return true; } @@ -513,7 +507,7 @@ private final File dir; private final Path[] paths; private int index; - private File nextFile; // cache file() in case it's called more than once + private RegularFileInfo nextFile; public FileListIterator(File startDir, Path... files) { dir = startDir; @@ -523,7 +517,7 @@ public void reset() { index = -1; - nextFile = null; + nextFile = new RegularFileInfo(); } public boolean hasNext() { @@ -535,14 +529,14 @@ if (index == paths.length) { throw new NoSuchElementException(); } - nextFile = new File(dir, paths[index].toString()); + nextFile.init(new File(dir, paths[index].toString())); } public Path name() { return paths[index]; } - public File file() { + public FileInfo file() { return nextFile; } @@ -597,7 +591,7 @@ return walker.name(); } - public File file() { + public FileInfo file() { return walker.file(); } diff -r 954763c82cc3 -r ed6b74a58c66 src/org/tmatesoft/hg/util/FileInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/FileInfo.java Wed Sep 07 09:33:27 2011 +0200 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.util; + +import java.nio.channels.ReadableByteChannel; + +/** + * Subset of File-related functionality to support other than {@link java.io.File}-based {@link FileIterator} implementations + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface FileInfo { + + /** + * @return true if the filesystem object described by this instance exists, is a regular file and can be read + */ + boolean exists(); + + /** + * File last modification time, in seconds since Jan 1, 1970. E.g. {@link java.io.File#lastModified()} / 1000 + * @return int value representing time, in seconds, when file was last modified. + */ + int lastModified(); + + /** + * @return file size + */ + long length(); + + /** + * Access file contents. Caller is responsible to close the channel. + * @return file reader object, never null + */ + ReadableByteChannel newInputChannel(); +} diff -r 954763c82cc3 -r ed6b74a58c66 src/org/tmatesoft/hg/util/FileIterator.java --- a/src/org/tmatesoft/hg/util/FileIterator.java Sat Sep 03 22:06:16 2011 +0200 +++ b/src/org/tmatesoft/hg/util/FileIterator.java Wed Sep 07 09:33:27 2011 +0200 @@ -16,8 +16,6 @@ */ package org.tmatesoft.hg.util; -import java.io.File; - import org.tmatesoft.hg.internal.Experimental; /** @@ -49,15 +47,15 @@ Path name(); /** - * File object to retrieve actual state from. Not necessarily exist, if {@link FileIterator} is used to query status - * of specific files. - * @return filesystem element. + * {@link FileInfo} object to retrieve actual file information. Caller shall not assume new instance for each {@link #next()} entry, + * implementors of this interface may reuse {@link FileInfo} instance if deemed suitable. + * @return file information holder. */ - File file(); + FileInfo file(); /** * When {@link FileIterator} represents only fraction of a repository, library might need to figure out if - * specific file (path) belongs to that fraction or not. Paths (and {@link File Files} returned by this {@link FileIterator} + * specific file (path) belongs to that fraction or not. Paths and files returned by this {@link FileIterator} * are always considered as representing the fraction, nonetheless, {@link FileIterator} shall return true for such names if * asked. * @return true if this {@link FileIterator} is responsible for (interested in) specified repository-local path diff -r 954763c82cc3 -r ed6b74a58c66 src/org/tmatesoft/hg/util/FileWalker.java --- a/src/org/tmatesoft/hg/util/FileWalker.java Sat Sep 03 22:06:16 2011 +0200 +++ b/src/org/tmatesoft/hg/util/FileWalker.java Wed Sep 07 09:33:27 2011 +0200 @@ -32,7 +32,7 @@ private final LinkedList dirQueue; private final LinkedList fileQueue; private final Path.Matcher scope; - private File nextFile; + private RegularFileInfo nextFile; private Path nextPath; public FileWalker(File dir, Path.Source pathFactory) { @@ -60,7 +60,7 @@ fileQueue.clear(); dirQueue.clear(); dirQueue.add(startDir); - nextFile = null; + nextFile = new RegularFileInfo(); nextPath = null; } @@ -72,15 +72,16 @@ if (!fill()) { throw new NoSuchElementException(); } - nextFile = fileQueue.removeFirst(); - nextPath = pathHelper.path(nextFile.getPath()); + File next = fileQueue.removeFirst(); + nextFile.init(next); + nextPath = pathHelper.path(next.getPath()); } public Path name() { return nextPath; } - public File file() { + public FileInfo file() { return nextFile; } diff -r 954763c82cc3 -r ed6b74a58c66 src/org/tmatesoft/hg/util/RegularFileInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java Wed Sep 07 09:33:27 2011 +0200 @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class RegularFileInfo implements FileInfo { + private File file; + + public RegularFileInfo() { + } + + public void init(File f) { + file = f; + } + + public boolean exists() { + return file.canRead() && file.isFile(); + } + + public int lastModified() { + return (int) (file.lastModified() / 1000); + } + + public long length() { + return file.length(); + } + + public ReadableByteChannel newInputChannel() { + try { + return new FileInputStream(file).getChannel(); + } catch (FileNotFoundException ex) { + ex.printStackTrace(); // FIXME log debug. + // shall not happen, provided this class is used correctly + return new ReadableByteChannel() { + + public boolean isOpen() { + return true; + } + + public void close() throws IOException { + } + + public int read(ByteBuffer dst) throws IOException { + // EOF right away + return -1; + } + }; + } + } + +}