| # HG changeset patch |
| # User Earwin Burrfoot <earwin@gmail.com> |
| # Date 1269243135 -10800 |
| # Node ID f3fe62541f59574c4438ae431f0134a6e1e7f369 |
| # Parent d70436a7f00d49cc95d9516d466cc451509838aa |
| [mq]: LUCENE-2339 |
| |
| diff --git a/src/java/org/apache/lucene/store/Directory.java b/src/java/org/apache/lucene/store/Directory.java |
| --- a/src/java/org/apache/lucene/store/Directory.java |
| +++ b/src/java/org/apache/lucene/store/Directory.java |
| @@ -22,7 +22,13 @@ |
| import java.util.Collection; |
| import java.util.Collections; |
| |
| +import java.util.ArrayList; |
| +import static java.util.Arrays.asList; |
| +import java.util.Collection; |
| +import java.util.Iterator; |
| +import java.util.List; |
| import org.apache.lucene.index.IndexFileNameFilter; |
| +import org.apache.lucene.util.IOUtils; |
| |
| /** A Directory is a flat list of files. Files may be written once, when they |
| * are created. Once a file is created it may only be opened for read, or |
| @@ -183,64 +189,83 @@ |
| return this.toString(); |
| } |
| |
| + |
| /** |
| - * Copy contents of a directory src to a directory dest. |
| - * If a file in src already exists in dest then the |
| - * one in dest will be blindly overwritten. |
| + * <p>Copy all files of this directory to destination directory. All conflicting files at destination are overwritten</p> |
| + * <p><b>NOTE:</b> this method only copies files that look like index files (ie, have extensions matching the known |
| + * extensions of index files). |
| + * <p><b>NOTE:</b> the source directory should not change while this method is running. Otherwise the results are |
| + * undefined and you could easily hit a FileNotFoundException. </p> |
| * |
| - * <p><b>NOTE:</b> the source directory cannot change |
| - * while this method is running. Otherwise the results |
| - * are undefined and you could easily hit a |
| - * FileNotFoundException. |
| - * |
| - * <p><b>NOTE:</b> this method only copies files that look |
| - * like index files (ie, have extensions matching the |
| - * known extensions of index files). |
| - * |
| - * @param src source directory |
| - * @param dest destination directory |
| - * @param closeDirSrc if <code>true</code>, call {@link #close()} method on source directory |
| - * @throws IOException |
| + * @param to destination directory |
| */ |
| - public static void copy(Directory src, Directory dest, boolean closeDirSrc) throws IOException { |
| - final String[] files = src.listAll(); |
| - |
| + public final void copyTo(Directory to) throws IOException { |
| + List<String> filenames = new ArrayList<String>(); |
| IndexFileNameFilter filter = IndexFileNameFilter.getFilter(); |
| |
| + for (String name : listAll()) |
| + if (filter.accept(null, name)) |
| + filenames.add(name); |
| + |
| + copyTo(to, filenames); |
| + } |
| + |
| + /** |
| + * <p>Copy given files of this directory to destination directory. All conflicting files at destination are overwritten</p> |
| + * <p><b>NOTE:</b> the source directory should not change while this method is running. Otherwise the results are |
| + * undefined and you could easily hit a FileNotFoundException. </p> |
| + * <p><b>NOTE:</b> implementations can check if destination directory is of the same type as 'this' and perform optimized copy</p> |
| + * |
| + * @param to destination directory |
| + * @param filenames file names to be copied |
| + */ |
| + public void copyTo(Directory to, Collection<String> filenames) throws IOException { |
| byte[] buf = new byte[BufferedIndexOutput.BUFFER_SIZE]; |
| - for (int i = 0; i < files.length; i++) { |
| - |
| - if (!filter.accept(null, files[i])) |
| - continue; |
| - |
| + for (String filename : filenames) { |
| IndexOutput os = null; |
| IndexInput is = null; |
| + IOException priorException = null; |
| try { |
| // create file in dest directory |
| - os = dest.createOutput(files[i]); |
| + os = to.createOutput(filename); |
| // read current file |
| - is = src.openInput(files[i]); |
| + is = openInput(filename); |
| // and copy to dest directory |
| long len = is.length(); |
| long readCount = 0; |
| while (readCount < len) { |
| - int toRead = readCount + BufferedIndexOutput.BUFFER_SIZE > len ? (int)(len - readCount) : BufferedIndexOutput.BUFFER_SIZE; |
| + int toRead = readCount + BufferedIndexOutput.BUFFER_SIZE > len ? (int) (len - readCount) : BufferedIndexOutput.BUFFER_SIZE; |
| is.readBytes(buf, 0, toRead); |
| os.writeBytes(buf, toRead); |
| readCount += toRead; |
| } |
| + } catch (IOException ioe) { |
| + priorException = ioe; |
| } finally { |
| - // graceful cleanup |
| - try { |
| - if (os != null) |
| - os.close(); |
| - } finally { |
| - if (is != null) |
| - is.close(); |
| - } |
| + IOUtils.closeSafely(priorException, os, is); |
| } |
| } |
| - if(closeDirSrc) |
| + } |
| + |
| + /** |
| + * Copy contents of a directory src to a directory dest. If a file in src already exists in dest then the one in dest |
| + * will be blindly overwritten. |
| + * <p/> |
| + * <p><b>NOTE:</b> the source directory cannot change while this method is running. Otherwise the results are |
| + * undefined and you could easily hit a FileNotFoundException. |
| + * <p/> |
| + * <p><b>NOTE:</b> this method only copies files that look like index files (ie, have extensions matching the known |
| + * extensions of index files). |
| + * |
| + * @param src source directory |
| + * @param dest destination directory |
| + * @param closeDirSrc if <code>true</code>, call {@link #close()} method on source directory |
| + * @deprecated should be replaced with src.copyTo(dest); [src.close();] |
| + */ |
| + @Deprecated |
| + public static void copy(Directory src, Directory dest, boolean closeDirSrc) throws IOException { |
| + src.copyTo(dest); |
| + if (closeDirSrc) |
| src.close(); |
| } |
| |
| diff --git a/src/java/org/apache/lucene/store/FSDirectory.java b/src/java/org/apache/lucene/store/FSDirectory.java |
| --- a/src/java/org/apache/lucene/store/FSDirectory.java |
| +++ b/src/java/org/apache/lucene/store/FSDirectory.java |
| @@ -18,9 +18,12 @@ |
| */ |
| |
| import java.io.File; |
| +import java.io.FileInputStream; |
| +import java.io.FileOutputStream; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| +import java.nio.channels.FileChannel; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| |
| @@ -29,6 +32,7 @@ |
| import static java.util.Collections.synchronizedSet; |
| import java.util.HashSet; |
| import java.util.Set; |
| +import org.apache.lucene.util.IOUtils; |
| import org.apache.lucene.util.ThreadInterruptedException; |
| import org.apache.lucene.util.Constants; |
| |
| @@ -422,6 +426,30 @@ |
| return chunkSize; |
| } |
| |
| + @Override |
| + public void copyTo(Directory to, Collection<String> filenames) throws IOException { |
| + if (to instanceof FSDirectory) { |
| + FSDirectory target = (FSDirectory) to; |
| + |
| + for (String filename : filenames) { |
| + target.ensureCanWrite(filename); |
| + FileChannel input = null; |
| + FileChannel output = null; |
| + IOException priorException = null; |
| + try { |
| + input = new FileInputStream(new File(directory, filename)).getChannel(); |
| + output = new FileOutputStream(new File(target.directory, filename)).getChannel(); |
| + output.transferFrom(input, 0, input.size()); |
| + } catch (IOException ioe) { |
| + priorException = ioe; |
| + } finally { |
| + IOUtils.closeSafely(priorException, input, output); |
| + } |
| + } |
| + } else |
| + super.copyTo(to, filenames); |
| + } |
| + |
| protected static class FSIndexOutput extends BufferedIndexOutput { |
| private final FSDirectory parent; |
| private final String name; |
| diff --git a/src/java/org/apache/lucene/util/IOUtils.java b/src/java/org/apache/lucene/util/IOUtils.java |
| new file mode 100644 |
| --- /dev/null |
| +++ b/src/java/org/apache/lucene/util/IOUtils.java |
| @@ -0,0 +1,46 @@ |
| +package org.apache.lucene.util; |
| + |
| +import java.io.Closeable; |
| +import java.io.IOException; |
| + |
| +public class IOUtils { |
| + /** |
| + * <p>Closes all given <tt>Closeable</tt>s, suppressing all thrown exceptions. Some of the <tt>Closeable</tt>s |
| + * may be null, they are ignored. After everything is closed, method either throws <tt>priorException</tt>, |
| + * if one is supplied, or the first of suppressed exceptions, or completes normally.</p> |
| + * <p>Sample usage:<br/> |
| + * <pre> |
| + * Closeable resource1 = null, resource2 = null, resource3 = null; |
| + * ExpectedException priorE = null; |
| + * try { |
| + * resource1 = ...; resource2 = ...; resource3 = ...; // Aquisition may throw ExpectedException |
| + * ..do..stuff.. // May throw ExpectedException |
| + * } catch (ExpectedException e) { |
| + * priorE = e; |
| + * } finally { |
| + * closeSafely(priorE, resource1, resource2, resource3); |
| + * } |
| + * </pre> |
| + * </p> |
| + * @param priorException <tt>null</tt> or an exception that will be rethrown after method completion |
| + * @param objects objects to call <tt>close()</tt> on |
| + */ |
| + public static <E extends Exception> void closeSafely(E priorException, Closeable... objects) throws E, IOException { |
| + IOException firstIOE = null; |
| + |
| + for (Closeable object : objects) { |
| + try { |
| + if (object != null) |
| + object.close(); |
| + } catch (IOException ioe) { |
| + if (firstIOE == null) |
| + firstIOE = ioe; |
| + } |
| + } |
| + |
| + if (priorException != null) |
| + throw priorException; |
| + else if (firstIOE != null) |
| + throw firstIOE; |
| + } |
| +} |