X-Git-Url: https://git.mdrn.pl/pylucene.git/blobdiff_plain/a2e61f0c04805cfcb8706176758d1283c7e3a55c..aaeed5504b982cf3545252ab528713250aa33eed:/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/SegmentInfos.java?ds=inline
diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/SegmentInfos.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/SegmentInfos.java
new file mode 100644
index 0000000..46cf77a
--- /dev/null
+++ b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/SegmentInfos.java
@@ -0,0 +1,1147 @@
+package org.apache.lucene.index;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.ChecksumIndexOutput;
+import org.apache.lucene.store.ChecksumIndexInput;
+import org.apache.lucene.store.NoSuchDirectoryException;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.ThreadInterruptedException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A collection of segmentInfo objects with methods for operating on
+ * those segments in relation to the file system.
+ *
+ * @lucene.experimental
+ */
+public final class SegmentInfos implements Cloneable, Iterable {
+
+ /** The file format version, a negative number. */
+ /* Works since counter, the old 1st entry, is always >= 0 */
+ public static final int FORMAT = -1;
+
+ /** This format adds details used for lockless commits. It differs
+ * slightly from the previous format in that file names
+ * are never re-used (write once). Instead, each file is
+ * written to the next generation. For example,
+ * segments_1, segments_2, etc. This allows us to not use
+ * a commit lock. See file
+ * formats for details.
+ */
+ public static final int FORMAT_LOCKLESS = -2;
+
+ /** This format adds a "hasSingleNormFile" flag into each segment info.
+ * See LUCENE-756
+ * for details.
+ */
+ public static final int FORMAT_SINGLE_NORM_FILE = -3;
+
+ /** This format allows multiple segments to share a single
+ * vectors and stored fields file. */
+ public static final int FORMAT_SHARED_DOC_STORE = -4;
+
+ /** This format adds a checksum at the end of the file to
+ * ensure all bytes were successfully written. */
+ public static final int FORMAT_CHECKSUM = -5;
+
+ /** This format adds the deletion count for each segment.
+ * This way IndexWriter can efficiently report numDocs(). */
+ public static final int FORMAT_DEL_COUNT = -6;
+
+ /** This format adds the boolean hasProx to record if any
+ * fields in the segment store prox information (ie, have
+ * omitTermFreqAndPositions==false) */
+ public static final int FORMAT_HAS_PROX = -7;
+
+ /** This format adds optional commit userData (String) storage. */
+ public static final int FORMAT_USER_DATA = -8;
+
+ /** This format adds optional per-segment String
+ * diagnostics storage, and switches userData to Map */
+ public static final int FORMAT_DIAGNOSTICS = -9;
+
+ /** Each segment records whether it has term vectors */
+ public static final int FORMAT_HAS_VECTORS = -10;
+
+ /** Each segment records the Lucene version that created it. */
+ public static final int FORMAT_3_1 = -11;
+
+ /* This must always point to the most recent file format. */
+ public static final int CURRENT_FORMAT = FORMAT_3_1;
+
+ public static final int FORMAT_MINIMUM = FORMAT;
+ public static final int FORMAT_MAXIMUM = CURRENT_FORMAT;
+
+ public int counter = 0; // used to name new segments
+ /**
+ * counts how often the index has been changed by adding or deleting docs.
+ * starting with the current time in milliseconds forces to create unique version numbers.
+ */
+ long version = System.currentTimeMillis();
+
+ private long generation = 0; // generation of the "segments_N" for the next commit
+ private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read
+ // or wrote; this is normally the same as generation except if
+ // there was an IOException that had interrupted a commit
+
+ private Map userData = Collections.emptyMap(); // Opaque Map that user can specify during IndexWriter.commit
+
+ private int format;
+
+ private List segments = new ArrayList();
+ private Set segmentSet = new HashSet();
+ private transient List cachedUnmodifiableList;
+ private transient Set cachedUnmodifiableSet;
+
+ /**
+ * If non-null, information about loading segments_N files
+ * will be printed here. @see #setInfoStream.
+ */
+ private static PrintStream infoStream = null;
+
+ public void setFormat(int format) {
+ this.format = format;
+ }
+
+ public int getFormat() {
+ return format;
+ }
+
+ public SegmentInfo info(int i) {
+ return segments.get(i);
+ }
+
+ /**
+ * Get the generation (N) of the current segments_N file
+ * from a list of files.
+ *
+ * @param files -- array of file names to check
+ */
+ public static long getCurrentSegmentGeneration(String[] files) {
+ if (files == null) {
+ return -1;
+ }
+ long max = -1;
+ for (int i = 0; i < files.length; i++) {
+ String file = files[i];
+ if (file.startsWith(IndexFileNames.SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN)) {
+ long gen = generationFromSegmentsFileName(file);
+ if (gen > max) {
+ max = gen;
+ }
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Get the generation (N) of the current segments_N file
+ * in the directory.
+ *
+ * @param directory -- directory to search for the latest segments_N file
+ */
+ public static long getCurrentSegmentGeneration(Directory directory) throws IOException {
+ try {
+ return getCurrentSegmentGeneration(directory.listAll());
+ } catch (NoSuchDirectoryException nsde) {
+ return -1;
+ }
+ }
+
+ /**
+ * Get the filename of the current segments_N file
+ * from a list of files.
+ *
+ * @param files -- array of file names to check
+ */
+
+ public static String getCurrentSegmentFileName(String[] files) throws IOException {
+ return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ getCurrentSegmentGeneration(files));
+ }
+
+ /**
+ * Get the filename of the current segments_N file
+ * in the directory.
+ *
+ * @param directory -- directory to search for the latest segments_N file
+ */
+ public static String getCurrentSegmentFileName(Directory directory) throws IOException {
+ return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ getCurrentSegmentGeneration(directory));
+ }
+
+ /**
+ * Get the segments_N filename in use by this segment infos.
+ */
+ public String getCurrentSegmentFileName() {
+ return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ lastGeneration);
+ }
+
+ /**
+ * Parse the generation off the segments file name and
+ * return it.
+ */
+ public static long generationFromSegmentsFileName(String fileName) {
+ if (fileName.equals(IndexFileNames.SEGMENTS)) {
+ return 0;
+ } else if (fileName.startsWith(IndexFileNames.SEGMENTS)) {
+ return Long.parseLong(fileName.substring(1+IndexFileNames.SEGMENTS.length()),
+ Character.MAX_RADIX);
+ } else {
+ throw new IllegalArgumentException("fileName \"" + fileName + "\" is not a segments file");
+ }
+ }
+
+
+ /**
+ * Get the next segments_N filename that will be written.
+ */
+ public String getNextSegmentFileName() {
+ long nextGeneration;
+
+ if (generation == -1) {
+ nextGeneration = 1;
+ } else {
+ nextGeneration = generation+1;
+ }
+ return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ nextGeneration);
+ }
+
+ /**
+ * Read a particular segmentFileName. Note that this may
+ * throw an IOException if a commit is in process.
+ *
+ * @param directory -- directory containing the segments file
+ * @param segmentFileName -- segment file to load
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public final void read(Directory directory, String segmentFileName) throws CorruptIndexException, IOException {
+ boolean success = false;
+
+ // Clear any previous segments:
+ this.clear();
+
+ ChecksumIndexInput input = new ChecksumIndexInput(directory.openInput(segmentFileName));
+
+ generation = generationFromSegmentsFileName(segmentFileName);
+
+ lastGeneration = generation;
+
+ try {
+ int format = input.readInt();
+ // check that it is a format we can understand
+ if (format > FORMAT_MINIMUM) {
+ throw new IndexFormatTooOldException(input, format,
+ FORMAT_MINIMUM, FORMAT_MAXIMUM);
+ }
+ if (format < FORMAT_MAXIMUM) {
+ throw new IndexFormatTooNewException(input, format,
+ FORMAT_MINIMUM, FORMAT_MAXIMUM);
+ }
+ version = input.readLong(); // read version
+ counter = input.readInt(); // read counter
+
+ for (int i = input.readInt(); i > 0; i--) { // read segmentInfos
+ SegmentInfo si = new SegmentInfo(directory, format, input);
+ if (si.getVersion() == null) {
+ // It's a pre-3.1 segment, upgrade its version to either 3.0 or 2.x
+ Directory dir = directory;
+ if (si.getDocStoreOffset() != -1) {
+ if (si.getDocStoreIsCompoundFile()) {
+ dir = new CompoundFileReader(dir, IndexFileNames.segmentFileName(
+ si.getDocStoreSegment(),
+ IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), 1024);
+ }
+ } else if (si.getUseCompoundFile()) {
+ dir = new CompoundFileReader(dir, IndexFileNames.segmentFileName(
+ si.name, IndexFileNames.COMPOUND_FILE_EXTENSION), 1024);
+ }
+
+ try {
+ String store = si.getDocStoreOffset() != -1 ? si.getDocStoreSegment() : si.name;
+ si.setVersion(FieldsReader.detectCodeVersion(dir, store));
+ } finally {
+ // If we opened the directory, close it
+ if (dir != directory) dir.close();
+ }
+ }
+ add(si);
+ }
+
+ if(format >= 0){ // in old format the version number may be at the end of the file
+ if (input.getFilePointer() >= input.length())
+ version = System.currentTimeMillis(); // old file format without version number
+ else
+ version = input.readLong(); // read version
+ }
+
+ if (format <= FORMAT_USER_DATA) {
+ if (format <= FORMAT_DIAGNOSTICS) {
+ userData = input.readStringStringMap();
+ } else if (0 != input.readByte()) {
+ userData = Collections.singletonMap("userData", input.readString());
+ } else {
+ userData = Collections.emptyMap();
+ }
+ } else {
+ userData = Collections.emptyMap();
+ }
+
+ if (format <= FORMAT_CHECKSUM) {
+ final long checksumNow = input.getChecksum();
+ final long checksumThen = input.readLong();
+ if (checksumNow != checksumThen)
+ throw new CorruptIndexException("checksum mismatch in segments file (resource: " + input + ")");
+ }
+ success = true;
+ }
+ finally {
+ input.close();
+ if (!success) {
+ // Clear any segment infos we had loaded so we
+ // have a clean slate on retry:
+ this.clear();
+ }
+ }
+ }
+
+ /**
+ * This version of read uses the retry logic (for lock-less
+ * commits) to find the right segments file to load.
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public final void read(Directory directory) throws CorruptIndexException, IOException {
+
+ generation = lastGeneration = -1;
+
+ new FindSegmentsFile(directory) {
+
+ @Override
+ protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
+ read(directory, segmentFileName);
+ return null;
+ }
+ }.run();
+ }
+
+ // Only non-null after prepareCommit has been called and
+ // before finishCommit is called
+ ChecksumIndexOutput pendingSegnOutput;
+
+ private final void write(Directory directory) throws IOException {
+
+ String segmentFileName = getNextSegmentFileName();
+
+ // Always advance the generation on write:
+ if (generation == -1) {
+ generation = 1;
+ } else {
+ generation++;
+ }
+
+ ChecksumIndexOutput segnOutput = new ChecksumIndexOutput(directory.createOutput(segmentFileName));
+
+ boolean success = false;
+
+ try {
+ segnOutput.writeInt(CURRENT_FORMAT); // write FORMAT
+ segnOutput.writeLong(version);
+ segnOutput.writeInt(counter); // write counter
+ segnOutput.writeInt(size()); // write infos
+ for (SegmentInfo si : this) {
+ si.write(segnOutput);
+ }
+ segnOutput.writeStringStringMap(userData);
+ segnOutput.prepareCommit();
+ pendingSegnOutput = segnOutput;
+ success = true;
+ } finally {
+ if (!success) {
+ // We hit an exception above; try to close the file
+ // but suppress any exception:
+ IOUtils.closeWhileHandlingException(segnOutput);
+ try {
+ // Try not to leave a truncated segments_N file in
+ // the index:
+ directory.deleteFile(segmentFileName);
+ } catch (Throwable t) {
+ // Suppress so we keep throwing the original exception
+ }
+ }
+ }
+ }
+
+ /** Prunes any segment whose docs are all deleted. */
+ public void pruneDeletedSegments() throws IOException {
+ for(final Iterator it = segments.iterator(); it.hasNext();) {
+ final SegmentInfo info = it.next();
+ if (info.getDelCount() == info.docCount) {
+ it.remove();
+ segmentSet.remove(info);
+ }
+ }
+ assert segmentSet.size() == segments.size();
+ }
+
+ /**
+ * Returns a copy of this instance, also copying each
+ * SegmentInfo.
+ */
+
+ @Override
+ public Object clone() {
+ try {
+ final SegmentInfos sis = (SegmentInfos) super.clone();
+ // deep clone, first recreate all collections:
+ sis.segments = new ArrayList(size());
+ sis.segmentSet = new HashSet(size());
+ sis.cachedUnmodifiableList = null;
+ sis.cachedUnmodifiableSet = null;
+ for(final SegmentInfo info : this) {
+ // dont directly access segments, use add method!!!
+ sis.add((SegmentInfo) info.clone());
+ }
+ sis.userData = new HashMap(userData);
+ return sis;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException("should not happen", e);
+ }
+ }
+
+ /**
+ * version number when this SegmentInfos was generated.
+ */
+ public long getVersion() {
+ return version;
+ }
+ public long getGeneration() {
+ return generation;
+ }
+ public long getLastGeneration() {
+ return lastGeneration;
+ }
+
+ /**
+ * Current version number from segments file.
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public static long readCurrentVersion(Directory directory)
+ throws CorruptIndexException, IOException {
+
+ // Fully read the segments file: this ensures that it's
+ // completely written so that if
+ // IndexWriter.prepareCommit has been called (but not
+ // yet commit), then the reader will still see itself as
+ // current:
+ SegmentInfos sis = new SegmentInfos();
+ sis.read(directory);
+ return sis.version;
+ }
+
+ /**
+ * Returns userData from latest segments file
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public static Map readCurrentUserData(Directory directory)
+ throws CorruptIndexException, IOException {
+ SegmentInfos sis = new SegmentInfos();
+ sis.read(directory);
+ return sis.getUserData();
+ }
+
+ /** If non-null, information about retries when loading
+ * the segments file will be printed to this.
+ */
+ public static void setInfoStream(PrintStream infoStream) {
+ SegmentInfos.infoStream = infoStream;
+ }
+
+ /* Advanced configuration of retry logic in loading
+ segments_N file */
+ private static int defaultGenFileRetryCount = 10;
+ private static int defaultGenFileRetryPauseMsec = 50;
+ private static int defaultGenLookaheadCount = 10;
+
+ /**
+ * Advanced: set how many times to try loading the
+ * segments.gen file contents to determine current segment
+ * generation. This file is only referenced when the
+ * primary method (listing the directory) fails.
+ */
+ public static void setDefaultGenFileRetryCount(int count) {
+ defaultGenFileRetryCount = count;
+ }
+
+ /**
+ * @see #setDefaultGenFileRetryCount
+ */
+ public static int getDefaultGenFileRetryCount() {
+ return defaultGenFileRetryCount;
+ }
+
+ /**
+ * Advanced: set how many milliseconds to pause in between
+ * attempts to load the segments.gen file.
+ */
+ public static void setDefaultGenFileRetryPauseMsec(int msec) {
+ defaultGenFileRetryPauseMsec = msec;
+ }
+
+ /**
+ * @see #setDefaultGenFileRetryPauseMsec
+ */
+ public static int getDefaultGenFileRetryPauseMsec() {
+ return defaultGenFileRetryPauseMsec;
+ }
+
+ /**
+ * Advanced: set how many times to try incrementing the
+ * gen when loading the segments file. This only runs if
+ * the primary (listing directory) and secondary (opening
+ * segments.gen file) methods fail to find the segments
+ * file.
+ */
+ public static void setDefaultGenLookaheadCount(int count) {
+ defaultGenLookaheadCount = count;
+ }
+ /**
+ * @see #setDefaultGenLookaheadCount
+ */
+ public static int getDefaultGenLookahedCount() {
+ return defaultGenLookaheadCount;
+ }
+
+ /**
+ * @see #setInfoStream
+ */
+ public static PrintStream getInfoStream() {
+ return infoStream;
+ }
+
+ /**
+ * Prints the given message to the infoStream. Note, this method does not
+ * check for null infoStream. It assumes this check has been performed by the
+ * caller, which is recommended to avoid the (usually) expensive message
+ * creation.
+ */
+ private static void message(String message) {
+ infoStream.println("SIS [" + Thread.currentThread().getName() + "]: " + message);
+ }
+
+ /**
+ * Utility class for executing code that needs to do
+ * something with the current segments file. This is
+ * necessary with lock-less commits because from the time
+ * you locate the current segments file name, until you
+ * actually open it, read its contents, or check modified
+ * time, etc., it could have been deleted due to a writer
+ * commit finishing.
+ */
+ public abstract static class FindSegmentsFile {
+
+ final Directory directory;
+
+ public FindSegmentsFile(Directory directory) {
+ this.directory = directory;
+ }
+
+ public Object run() throws CorruptIndexException, IOException {
+ return run(null);
+ }
+
+ public Object run(IndexCommit commit) throws CorruptIndexException, IOException {
+ if (commit != null) {
+ if (directory != commit.getDirectory())
+ throw new IOException("the specified commit does not match the specified Directory");
+ return doBody(commit.getSegmentsFileName());
+ }
+
+ String segmentFileName = null;
+ long lastGen = -1;
+ long gen = 0;
+ int genLookaheadCount = 0;
+ IOException exc = null;
+ int retryCount = 0;
+
+ boolean useFirstMethod = true;
+
+ // Loop until we succeed in calling doBody() without
+ // hitting an IOException. An IOException most likely
+ // means a commit was in process and has finished, in
+ // the time it took us to load the now-old infos files
+ // (and segments files). It's also possible it's a
+ // true error (corrupt index). To distinguish these,
+ // on each retry we must see "forward progress" on
+ // which generation we are trying to load. If we
+ // don't, then the original error is real and we throw
+ // it.
+
+ // We have three methods for determining the current
+ // generation. We try the first two in parallel (when
+ // useFirstMethod is true), and fall back to the third
+ // when necessary.
+
+ while(true) {
+
+ if (useFirstMethod) {
+
+ // List the directory and use the highest
+ // segments_N file. This method works well as long
+ // as there is no stale caching on the directory
+ // contents (NOTE: NFS clients often have such stale
+ // caching):
+ String[] files = null;
+
+ long genA = -1;
+
+ files = directory.listAll();
+
+ if (files != null) {
+ genA = getCurrentSegmentGeneration(files);
+ }
+
+ if (infoStream != null) {
+ message("directory listing genA=" + genA);
+ }
+
+ // Also open segments.gen and read its
+ // contents. Then we take the larger of the two
+ // gens. This way, if either approach is hitting
+ // a stale cache (NFS) we have a better chance of
+ // getting the right generation.
+ long genB = -1;
+ for(int i=0;i= 2) {
+ // Give up on first method -- this is 3rd cycle on
+ // listing directory and checking gen file to
+ // attempt to locate the segments file.
+ useFirstMethod = false;
+ }
+
+ // Second method: since both directory cache and
+ // file contents cache seem to be stale, just
+ // advance the generation.
+ if (!useFirstMethod) {
+ if (genLookaheadCount < defaultGenLookaheadCount) {
+ gen++;
+ genLookaheadCount++;
+ if (infoStream != null) {
+ message("look ahead increment gen to " + gen);
+ }
+ } else {
+ // All attempts have failed -- throw first exc:
+ throw exc;
+ }
+ } else if (lastGen == gen) {
+ // This means we're about to try the same
+ // segments_N last tried.
+ retryCount++;
+ } else {
+ // Segment file has advanced since our last loop
+ // (we made "progress"), so reset retryCount:
+ retryCount = 0;
+ }
+
+ lastGen = gen;
+
+ segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ gen);
+
+ try {
+ Object v = doBody(segmentFileName);
+ if (infoStream != null) {
+ message("success on " + segmentFileName);
+ }
+ return v;
+ } catch (IOException err) {
+
+ // Save the original root cause:
+ if (exc == null) {
+ exc = err;
+ }
+
+ if (infoStream != null) {
+ message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retryCount=" + retryCount + "; gen = " + gen);
+ }
+
+ if (gen > 1 && useFirstMethod && retryCount == 1) {
+
+ // This is our second time trying this same segments
+ // file (because retryCount is 1), and, there is
+ // possibly a segments_(N-1) (because gen > 1).
+ // So, check if the segments_(N-1) exists and
+ // try it if so:
+ String prevSegmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ gen-1);
+
+ final boolean prevExists;
+ prevExists = directory.fileExists(prevSegmentFileName);
+
+ if (prevExists) {
+ if (infoStream != null) {
+ message("fallback to prior segment file '" + prevSegmentFileName + "'");
+ }
+ try {
+ Object v = doBody(prevSegmentFileName);
+ if (infoStream != null) {
+ message("success on fallback " + prevSegmentFileName);
+ }
+ return v;
+ } catch (IOException err2) {
+ if (infoStream != null) {
+ message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Subclass must implement this. The assumption is an
+ * IOException will be thrown if something goes wrong
+ * during the processing that could have been caused by
+ * a writer committing.
+ */
+ protected abstract Object doBody(String segmentFileName) throws CorruptIndexException, IOException;
+ }
+
+ /**
+ * Returns a new SegmentInfos containing the SegmentInfo
+ * instances in the specified range first (inclusive) to
+ * last (exclusive), so total number of segments returned
+ * is last-first.
+ * @deprecated use {@code asList().subList(first, last)}
+ * instead.
+ */
+ @Deprecated
+ public SegmentInfos range(int first, int last) {
+ SegmentInfos infos = new SegmentInfos();
+ infos.addAll(segments.subList(first, last));
+ return infos;
+ }
+
+ // Carry over generation numbers from another SegmentInfos
+ void updateGeneration(SegmentInfos other) {
+ lastGeneration = other.lastGeneration;
+ generation = other.generation;
+ }
+
+ final void rollbackCommit(Directory dir) throws IOException {
+ if (pendingSegnOutput != null) {
+ try {
+ pendingSegnOutput.close();
+ } catch (Throwable t) {
+ // Suppress so we keep throwing the original exception
+ // in our caller
+ }
+
+ // Must carefully compute fileName from "generation"
+ // since lastGeneration isn't incremented:
+ try {
+ final String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
+ "",
+ generation);
+ dir.deleteFile(segmentFileName);
+ } catch (Throwable t) {
+ // Suppress so we keep throwing the original exception
+ // in our caller
+ }
+ pendingSegnOutput = null;
+ }
+ }
+
+ /** Call this to start a commit. This writes the new
+ * segments file, but writes an invalid checksum at the
+ * end, so that it is not visible to readers. Once this
+ * is called you must call {@link #finishCommit} to complete
+ * the commit or {@link #rollbackCommit} to abort it.
+ *
+ * Note: {@link #changed()} should be called prior to this
+ * method if changes have been made to this {@link SegmentInfos} instance
+ *
+ **/
+ final void prepareCommit(Directory dir) throws IOException {
+ if (pendingSegnOutput != null)
+ throw new IllegalStateException("prepareCommit was already called");
+ write(dir);
+ }
+
+ /** Returns all file names referenced by SegmentInfo
+ * instances matching the provided Directory (ie files
+ * associated with any "external" segments are skipped).
+ * The returned collection is recomputed on each
+ * invocation. */
+ public Collection files(Directory dir, boolean includeSegmentsFile) throws IOException {
+ HashSet files = new HashSet();
+ if (includeSegmentsFile) {
+ files.add(getCurrentSegmentFileName());
+ }
+ final int size = size();
+ for(int i=0;i
+ * Note: {@link #changed()} should be called prior to this
+ * method if changes have been made to this {@link SegmentInfos} instance
+ *
+ **/
+ final void commit(Directory dir) throws IOException {
+ prepareCommit(dir);
+ finishCommit(dir);
+ }
+
+ public String toString(Directory directory) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(getCurrentSegmentFileName()).append(": ");
+ final int count = size();
+ for(int i = 0; i < count; i++) {
+ if (i > 0) {
+ buffer.append(' ');
+ }
+ final SegmentInfo info = info(i);
+ buffer.append(info.toString(directory, 0));
+ }
+ return buffer.toString();
+ }
+
+ public Map getUserData() {
+ return userData;
+ }
+
+ void setUserData(Map data) {
+ if (data == null) {
+ userData = Collections.emptyMap();
+ } else {
+ userData = data;
+ }
+ }
+
+ /** Replaces all segments in this instance, but keeps
+ * generation, version, counter so that future commits
+ * remain write once.
+ */
+ void replace(SegmentInfos other) {
+ rollbackSegmentInfos(other.asList());
+ lastGeneration = other.lastGeneration;
+ }
+
+ /** Returns sum of all segment's docCounts. Note that
+ * this does not include deletions */
+ public int totalDocCount() {
+ int count = 0;
+ for(SegmentInfo info : this) {
+ count += info.docCount;
+ }
+ return count;
+ }
+
+ /** Call this before committing if changes have been made to the
+ * segments. */
+ public void changed() {
+ version++;
+ }
+
+ /** applies all changes caused by committing a merge to this SegmentInfos */
+ void applyMergeChanges(MergePolicy.OneMerge merge, boolean dropSegment) {
+ final Set mergedAway = new HashSet(merge.segments);
+ boolean inserted = false;
+ int newSegIdx = 0;
+ for (int segIdx = 0, cnt = segments.size(); segIdx < cnt; segIdx++) {
+ assert segIdx >= newSegIdx;
+ final SegmentInfo info = segments.get(segIdx);
+ if (mergedAway.contains(info)) {
+ if (!inserted && !dropSegment) {
+ segments.set(segIdx, merge.info);
+ inserted = true;
+ newSegIdx++;
+ }
+ } else {
+ segments.set(newSegIdx, info);
+ newSegIdx++;
+ }
+ }
+
+ // Either we found place to insert segment, or, we did
+ // not, but only because all segments we merged became
+ // deleted while we are merging, in which case it should
+ // be the case that the new segment is also all deleted,
+ // we insert it at the beginning if it should not be dropped:
+ if (!inserted && !dropSegment) {
+ segments.add(0, merge.info);
+ }
+
+ // the rest of the segments in list are duplicates, so don't remove from map, only list!
+ segments.subList(newSegIdx, segments.size()).clear();
+
+ // update the Set
+ if (!dropSegment) {
+ segmentSet.add(merge.info);
+ }
+ segmentSet.removeAll(mergedAway);
+
+ assert segmentSet.size() == segments.size();
+ }
+
+ List createBackupSegmentInfos(boolean cloneChildren) {
+ if (cloneChildren) {
+ final List list = new ArrayList(size());
+ for(final SegmentInfo info : this) {
+ list.add((SegmentInfo) info.clone());
+ }
+ return list;
+ } else {
+ return new ArrayList(segments);
+ }
+ }
+
+ void rollbackSegmentInfos(List infos) {
+ this.clear();
+ this.addAll(infos);
+ }
+
+ /** Returns an unmodifiable {@link Iterator} of contained segments in order. */
+ // @Override (comment out until Java 6)
+ public Iterator iterator() {
+ return asList().iterator();
+ }
+
+ /** Returns all contained segments as an unmodifiable {@link List} view. */
+ public List asList() {
+ if (cachedUnmodifiableList == null) {
+ cachedUnmodifiableList = Collections.unmodifiableList(segments);
+ }
+ return cachedUnmodifiableList;
+ }
+
+ /** Returns all contained segments as an unmodifiable {@link Set} view.
+ * The iterator is not sorted, use {@link List} view or {@link #iterator} to get all segments in order. */
+ public Set asSet() {
+ if (cachedUnmodifiableSet == null) {
+ cachedUnmodifiableSet = Collections.unmodifiableSet(segmentSet);
+ }
+ return cachedUnmodifiableSet;
+ }
+
+ public int size() {
+ return segments.size();
+ }
+
+ public void add(SegmentInfo si) {
+ if (segmentSet.contains(si)) {
+ throw new IllegalStateException("Cannot add the same segment two times to this SegmentInfos instance");
+ }
+ segments.add(si);
+ segmentSet.add(si);
+ assert segmentSet.size() == segments.size();
+ }
+
+ public void addAll(Iterable sis) {
+ for (final SegmentInfo si : sis) {
+ this.add(si);
+ }
+ }
+
+ public void clear() {
+ segments.clear();
+ segmentSet.clear();
+ }
+
+ public void remove(SegmentInfo si) {
+ final int index = this.indexOf(si);
+ if (index >= 0) {
+ this.remove(index);
+ }
+ }
+
+ public void remove(int index) {
+ segmentSet.remove(segments.remove(index));
+ assert segmentSet.size() == segments.size();
+ }
+
+ public boolean contains(SegmentInfo si) {
+ return segmentSet.contains(si);
+ }
+
+ public int indexOf(SegmentInfo si) {
+ if (segmentSet.contains(si)) {
+ return segments.indexOf(si);
+ } else {
+ return -1;
+ }
+ }
+
+}