# HG changeset patch # User Earwin Burrfoot # 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. + *

Copy all files of this directory to destination directory. All conflicting files at destination are overwritten

+ *

NOTE: this method only copies files that look like index files (ie, have extensions matching the known + * extensions of index files). + *

NOTE: the source directory should not change while this method is running. Otherwise the results are + * undefined and you could easily hit a FileNotFoundException.

* - *

NOTE: the source directory cannot change - * while this method is running. Otherwise the results - * are undefined and you could easily hit a - * FileNotFoundException. - * - *

NOTE: 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 true, 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 filenames = new ArrayList(); IndexFileNameFilter filter = IndexFileNameFilter.getFilter(); + for (String name : listAll()) + if (filter.accept(null, name)) + filenames.add(name); + + copyTo(to, filenames); + } + + /** + *

Copy given files of this directory to destination directory. All conflicting files at destination are overwritten

+ *

NOTE: the source directory should not change while this method is running. Otherwise the results are + * undefined and you could easily hit a FileNotFoundException.

+ *

NOTE: implementations can check if destination directory is of the same type as 'this' and perform optimized copy

+ * + * @param to destination directory + * @param filenames file names to be copied + */ + public void copyTo(Directory to, Collection 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. + *

+ *

NOTE: the source directory cannot change while this method is running. Otherwise the results are + * undefined and you could easily hit a FileNotFoundException. + *

+ *

NOTE: 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 true, 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 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 { + /** + *

Closes all given Closeables, suppressing all thrown exceptions. Some of the Closeables + * may be null, they are ignored. After everything is closed, method either throws priorException, + * if one is supplied, or the first of suppressed exceptions, or completes normally.

+ *

Sample usage:
+ *

+   * 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);
+   * }
+   * 
+ *

+ * @param priorException null or an exception that will be rethrown after method completion + * @param objects objects to call close() on + */ + public static 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; + } +}