1 package org.apache.lucene.index;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.store.Directory;
21 import org.apache.lucene.store.IndexInput;
22 import org.apache.lucene.store.IndexOutput;
23 import org.apache.lucene.store.ChecksumIndexOutput;
24 import org.apache.lucene.store.ChecksumIndexInput;
25 import org.apache.lucene.store.NoSuchDirectoryException;
26 import org.apache.lucene.util.IOUtils;
27 import org.apache.lucene.util.ThreadInterruptedException;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.PrintStream;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
44 * A collection of segmentInfo objects with methods for operating on
45 * those segments in relation to the file system.
47 * @lucene.experimental
49 public final class SegmentInfos implements Cloneable, Iterable<SegmentInfo> {
51 /** The file format version, a negative number. */
52 /* Works since counter, the old 1st entry, is always >= 0 */
53 public static final int FORMAT = -1;
55 /** This format adds details used for lockless commits. It differs
56 * slightly from the previous format in that file names
57 * are never re-used (write once). Instead, each file is
58 * written to the next generation. For example,
59 * segments_1, segments_2, etc. This allows us to not use
60 * a commit lock. See <a
61 * href="http://lucene.apache.org/java/docs/fileformats.html">file
62 * formats</a> for details.
64 public static final int FORMAT_LOCKLESS = -2;
66 /** This format adds a "hasSingleNormFile" flag into each segment info.
67 * See <a href="http://issues.apache.org/jira/browse/LUCENE-756">LUCENE-756</a>
70 public static final int FORMAT_SINGLE_NORM_FILE = -3;
72 /** This format allows multiple segments to share a single
73 * vectors and stored fields file. */
74 public static final int FORMAT_SHARED_DOC_STORE = -4;
76 /** This format adds a checksum at the end of the file to
77 * ensure all bytes were successfully written. */
78 public static final int FORMAT_CHECKSUM = -5;
80 /** This format adds the deletion count for each segment.
81 * This way IndexWriter can efficiently report numDocs(). */
82 public static final int FORMAT_DEL_COUNT = -6;
84 /** This format adds the boolean hasProx to record if any
85 * fields in the segment store prox information (ie, have
86 * omitTermFreqAndPositions==false) */
87 public static final int FORMAT_HAS_PROX = -7;
89 /** This format adds optional commit userData (String) storage. */
90 public static final int FORMAT_USER_DATA = -8;
92 /** This format adds optional per-segment String
93 * diagnostics storage, and switches userData to Map */
94 public static final int FORMAT_DIAGNOSTICS = -9;
96 /** Each segment records whether it has term vectors */
97 public static final int FORMAT_HAS_VECTORS = -10;
99 /** Each segment records the Lucene version that created it. */
100 public static final int FORMAT_3_1 = -11;
102 /* This must always point to the most recent file format. */
103 public static final int CURRENT_FORMAT = FORMAT_3_1;
105 public static final int FORMAT_MINIMUM = FORMAT;
106 public static final int FORMAT_MAXIMUM = CURRENT_FORMAT;
108 public int counter = 0; // used to name new segments
110 * counts how often the index has been changed by adding or deleting docs.
111 * starting with the current time in milliseconds forces to create unique version numbers.
113 long version = System.currentTimeMillis();
115 private long generation = 0; // generation of the "segments_N" for the next commit
116 private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read
117 // or wrote; this is normally the same as generation except if
118 // there was an IOException that had interrupted a commit
120 private Map<String,String> userData = Collections.<String,String>emptyMap(); // Opaque Map<String, String> that user can specify during IndexWriter.commit
124 private List<SegmentInfo> segments = new ArrayList<SegmentInfo>();
125 private Set<SegmentInfo> segmentSet = new HashSet<SegmentInfo>();
126 private transient List<SegmentInfo> cachedUnmodifiableList;
127 private transient Set<SegmentInfo> cachedUnmodifiableSet;
130 * If non-null, information about loading segments_N files
131 * will be printed here. @see #setInfoStream.
133 private static PrintStream infoStream = null;
135 public void setFormat(int format) {
136 this.format = format;
139 public int getFormat() {
143 public SegmentInfo info(int i) {
144 return segments.get(i);
148 * Get the generation (N) of the current segments_N file
149 * from a list of files.
151 * @param files -- array of file names to check
153 public static long getCurrentSegmentGeneration(String[] files) {
158 for (int i = 0; i < files.length; i++) {
159 String file = files[i];
160 if (file.startsWith(IndexFileNames.SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN)) {
161 long gen = generationFromSegmentsFileName(file);
171 * Get the generation (N) of the current segments_N file
174 * @param directory -- directory to search for the latest segments_N file
176 public static long getCurrentSegmentGeneration(Directory directory) throws IOException {
178 return getCurrentSegmentGeneration(directory.listAll());
179 } catch (NoSuchDirectoryException nsde) {
185 * Get the filename of the current segments_N file
186 * from a list of files.
188 * @param files -- array of file names to check
191 public static String getCurrentSegmentFileName(String[] files) throws IOException {
192 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
194 getCurrentSegmentGeneration(files));
198 * Get the filename of the current segments_N file
201 * @param directory -- directory to search for the latest segments_N file
203 public static String getCurrentSegmentFileName(Directory directory) throws IOException {
204 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
206 getCurrentSegmentGeneration(directory));
210 * Get the segments_N filename in use by this segment infos.
212 public String getCurrentSegmentFileName() {
213 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
219 * Parse the generation off the segments file name and
222 public static long generationFromSegmentsFileName(String fileName) {
223 if (fileName.equals(IndexFileNames.SEGMENTS)) {
225 } else if (fileName.startsWith(IndexFileNames.SEGMENTS)) {
226 return Long.parseLong(fileName.substring(1+IndexFileNames.SEGMENTS.length()),
227 Character.MAX_RADIX);
229 throw new IllegalArgumentException("fileName \"" + fileName + "\" is not a segments file");
235 * Get the next segments_N filename that will be written.
237 public String getNextSegmentFileName() {
240 if (generation == -1) {
243 nextGeneration = generation+1;
245 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
251 * Read a particular segmentFileName. Note that this may
252 * throw an IOException if a commit is in process.
254 * @param directory -- directory containing the segments file
255 * @param segmentFileName -- segment file to load
256 * @throws CorruptIndexException if the index is corrupt
257 * @throws IOException if there is a low-level IO error
259 public final void read(Directory directory, String segmentFileName) throws CorruptIndexException, IOException {
260 boolean success = false;
262 // Clear any previous segments:
265 ChecksumIndexInput input = new ChecksumIndexInput(directory.openInput(segmentFileName));
267 generation = generationFromSegmentsFileName(segmentFileName);
269 lastGeneration = generation;
272 int format = input.readInt();
273 // check that it is a format we can understand
274 if (format > FORMAT_MINIMUM) {
275 throw new IndexFormatTooOldException(segmentFileName, format,
276 FORMAT_MINIMUM, FORMAT_MAXIMUM);
278 if (format < FORMAT_MAXIMUM) {
279 throw new IndexFormatTooNewException(segmentFileName, format,
280 FORMAT_MINIMUM, FORMAT_MAXIMUM);
282 version = input.readLong(); // read version
283 counter = input.readInt(); // read counter
285 for (int i = input.readInt(); i > 0; i--) { // read segmentInfos
286 SegmentInfo si = new SegmentInfo(directory, format, input);
287 if (si.getVersion() == null) {
288 // It's a pre-3.1 segment, upgrade its version to either 3.0 or 2.x
289 Directory dir = directory;
290 if (si.getDocStoreOffset() != -1) {
291 if (si.getDocStoreIsCompoundFile()) {
292 dir = new CompoundFileReader(dir, IndexFileNames.segmentFileName(
293 si.getDocStoreSegment(),
294 IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), 1024);
296 } else if (si.getUseCompoundFile()) {
297 dir = new CompoundFileReader(dir, IndexFileNames.segmentFileName(
298 si.name, IndexFileNames.COMPOUND_FILE_EXTENSION), 1024);
302 String store = si.getDocStoreOffset() != -1 ? si.getDocStoreSegment() : si.name;
303 si.setVersion(FieldsReader.detectCodeVersion(dir, store));
305 // If we opened the directory, close it
306 if (dir != directory) dir.close();
312 if(format >= 0){ // in old format the version number may be at the end of the file
313 if (input.getFilePointer() >= input.length())
314 version = System.currentTimeMillis(); // old file format without version number
316 version = input.readLong(); // read version
319 if (format <= FORMAT_USER_DATA) {
320 if (format <= FORMAT_DIAGNOSTICS) {
321 userData = input.readStringStringMap();
322 } else if (0 != input.readByte()) {
323 userData = Collections.singletonMap("userData", input.readString());
325 userData = Collections.<String,String>emptyMap();
328 userData = Collections.<String,String>emptyMap();
331 if (format <= FORMAT_CHECKSUM) {
332 final long checksumNow = input.getChecksum();
333 final long checksumThen = input.readLong();
334 if (checksumNow != checksumThen)
335 throw new CorruptIndexException("checksum mismatch in segments file");
342 // Clear any segment infos we had loaded so we
343 // have a clean slate on retry:
350 * This version of read uses the retry logic (for lock-less
351 * commits) to find the right segments file to load.
352 * @throws CorruptIndexException if the index is corrupt
353 * @throws IOException if there is a low-level IO error
355 public final void read(Directory directory) throws CorruptIndexException, IOException {
357 generation = lastGeneration = -1;
359 new FindSegmentsFile(directory) {
362 protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
363 read(directory, segmentFileName);
369 // Only non-null after prepareCommit has been called and
370 // before finishCommit is called
371 ChecksumIndexOutput pendingSegnOutput;
373 private final void write(Directory directory) throws IOException {
375 String segmentFileName = getNextSegmentFileName();
377 // Always advance the generation on write:
378 if (generation == -1) {
384 ChecksumIndexOutput segnOutput = new ChecksumIndexOutput(directory.createOutput(segmentFileName));
386 boolean success = false;
389 segnOutput.writeInt(CURRENT_FORMAT); // write FORMAT
390 segnOutput.writeLong(version);
391 segnOutput.writeInt(counter); // write counter
392 segnOutput.writeInt(size()); // write infos
393 for (SegmentInfo si : this) {
394 si.write(segnOutput);
396 segnOutput.writeStringStringMap(userData);
397 segnOutput.prepareCommit();
398 pendingSegnOutput = segnOutput;
402 // We hit an exception above; try to close the file
403 // but suppress any exception:
404 IOUtils.closeWhileHandlingException(segnOutput);
406 // Try not to leave a truncated segments_N file in
408 directory.deleteFile(segmentFileName);
409 } catch (Throwable t) {
410 // Suppress so we keep throwing the original exception
416 /** Prunes any segment whose docs are all deleted. */
417 public void pruneDeletedSegments() throws IOException {
418 for(final Iterator<SegmentInfo> it = segments.iterator(); it.hasNext();) {
419 final SegmentInfo info = it.next();
420 if (info.getDelCount() == info.docCount) {
422 segmentSet.remove(info);
425 assert segmentSet.size() == segments.size();
429 * Returns a copy of this instance, also copying each
434 public Object clone() {
436 final SegmentInfos sis = (SegmentInfos) super.clone();
437 // deep clone, first recreate all collections:
438 sis.segments = new ArrayList<SegmentInfo>(size());
439 sis.segmentSet = new HashSet<SegmentInfo>(size());
440 sis.cachedUnmodifiableList = null;
441 sis.cachedUnmodifiableSet = null;
442 for(final SegmentInfo info : this) {
443 // dont directly access segments, use add method!!!
444 sis.add((SegmentInfo) info.clone());
446 sis.userData = new HashMap<String,String>(userData);
448 } catch (CloneNotSupportedException e) {
449 throw new RuntimeException("should not happen", e);
454 * version number when this SegmentInfos was generated.
456 public long getVersion() {
459 public long getGeneration() {
462 public long getLastGeneration() {
463 return lastGeneration;
467 * Current version number from segments file.
468 * @throws CorruptIndexException if the index is corrupt
469 * @throws IOException if there is a low-level IO error
471 public static long readCurrentVersion(Directory directory)
472 throws CorruptIndexException, IOException {
474 // Fully read the segments file: this ensures that it's
475 // completely written so that if
476 // IndexWriter.prepareCommit has been called (but not
477 // yet commit), then the reader will still see itself as
479 SegmentInfos sis = new SegmentInfos();
485 * Returns userData from latest segments file
486 * @throws CorruptIndexException if the index is corrupt
487 * @throws IOException if there is a low-level IO error
489 public static Map<String,String> readCurrentUserData(Directory directory)
490 throws CorruptIndexException, IOException {
491 SegmentInfos sis = new SegmentInfos();
493 return sis.getUserData();
496 /** If non-null, information about retries when loading
497 * the segments file will be printed to this.
499 public static void setInfoStream(PrintStream infoStream) {
500 SegmentInfos.infoStream = infoStream;
503 /* Advanced configuration of retry logic in loading
505 private static int defaultGenFileRetryCount = 10;
506 private static int defaultGenFileRetryPauseMsec = 50;
507 private static int defaultGenLookaheadCount = 10;
510 * Advanced: set how many times to try loading the
511 * segments.gen file contents to determine current segment
512 * generation. This file is only referenced when the
513 * primary method (listing the directory) fails.
515 public static void setDefaultGenFileRetryCount(int count) {
516 defaultGenFileRetryCount = count;
520 * @see #setDefaultGenFileRetryCount
522 public static int getDefaultGenFileRetryCount() {
523 return defaultGenFileRetryCount;
527 * Advanced: set how many milliseconds to pause in between
528 * attempts to load the segments.gen file.
530 public static void setDefaultGenFileRetryPauseMsec(int msec) {
531 defaultGenFileRetryPauseMsec = msec;
535 * @see #setDefaultGenFileRetryPauseMsec
537 public static int getDefaultGenFileRetryPauseMsec() {
538 return defaultGenFileRetryPauseMsec;
542 * Advanced: set how many times to try incrementing the
543 * gen when loading the segments file. This only runs if
544 * the primary (listing directory) and secondary (opening
545 * segments.gen file) methods fail to find the segments
548 public static void setDefaultGenLookaheadCount(int count) {
549 defaultGenLookaheadCount = count;
552 * @see #setDefaultGenLookaheadCount
554 public static int getDefaultGenLookahedCount() {
555 return defaultGenLookaheadCount;
559 * @see #setInfoStream
561 public static PrintStream getInfoStream() {
566 * Prints the given message to the infoStream. Note, this method does not
567 * check for null infoStream. It assumes this check has been performed by the
568 * caller, which is recommended to avoid the (usually) expensive message
571 private static void message(String message) {
572 infoStream.println("SIS [" + Thread.currentThread().getName() + "]: " + message);
576 * Utility class for executing code that needs to do
577 * something with the current segments file. This is
578 * necessary with lock-less commits because from the time
579 * you locate the current segments file name, until you
580 * actually open it, read its contents, or check modified
581 * time, etc., it could have been deleted due to a writer
584 public abstract static class FindSegmentsFile {
586 final Directory directory;
588 public FindSegmentsFile(Directory directory) {
589 this.directory = directory;
592 public Object run() throws CorruptIndexException, IOException {
596 public Object run(IndexCommit commit) throws CorruptIndexException, IOException {
597 if (commit != null) {
598 if (directory != commit.getDirectory())
599 throw new IOException("the specified commit does not match the specified Directory");
600 return doBody(commit.getSegmentsFileName());
603 String segmentFileName = null;
606 int genLookaheadCount = 0;
607 IOException exc = null;
610 boolean useFirstMethod = true;
612 // Loop until we succeed in calling doBody() without
613 // hitting an IOException. An IOException most likely
614 // means a commit was in process and has finished, in
615 // the time it took us to load the now-old infos files
616 // (and segments files). It's also possible it's a
617 // true error (corrupt index). To distinguish these,
618 // on each retry we must see "forward progress" on
619 // which generation we are trying to load. If we
620 // don't, then the original error is real and we throw
623 // We have three methods for determining the current
624 // generation. We try the first two in parallel (when
625 // useFirstMethod is true), and fall back to the third
630 if (useFirstMethod) {
632 // List the directory and use the highest
633 // segments_N file. This method works well as long
634 // as there is no stale caching on the directory
635 // contents (NOTE: NFS clients often have such stale
637 String[] files = null;
641 files = directory.listAll();
644 genA = getCurrentSegmentGeneration(files);
647 if (infoStream != null) {
648 message("directory listing genA=" + genA);
651 // Also open segments.gen and read its
652 // contents. Then we take the larger of the two
653 // gens. This way, if either approach is hitting
654 // a stale cache (NFS) we have a better chance of
655 // getting the right generation.
657 for(int i=0;i<defaultGenFileRetryCount;i++) {
658 IndexInput genInput = null;
660 genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);
661 } catch (FileNotFoundException e) {
662 if (infoStream != null) {
663 message("segments.gen open: FileNotFoundException " + e);
666 } catch (IOException e) {
667 if (infoStream != null) {
668 message("segments.gen open: IOException " + e);
672 if (genInput != null) {
674 int version = genInput.readInt();
675 if (version == FORMAT_LOCKLESS) {
676 long gen0 = genInput.readLong();
677 long gen1 = genInput.readLong();
678 if (infoStream != null) {
679 message("fallback check: " + gen0 + "; " + gen1);
682 // The file is consistent.
687 } catch (IOException err2) {
694 Thread.sleep(defaultGenFileRetryPauseMsec);
695 } catch (InterruptedException ie) {
696 throw new ThreadInterruptedException(ie);
700 if (infoStream != null) {
701 message(IndexFileNames.SEGMENTS_GEN + " check: genB=" + genB);
704 // Pick the larger of the two gen's:
711 // Neither approach found a generation
712 throw new IndexNotFoundException("no segments* file found in " + directory + ": files: " + Arrays.toString(files));
716 if (useFirstMethod && lastGen == gen && retryCount >= 2) {
717 // Give up on first method -- this is 3rd cycle on
718 // listing directory and checking gen file to
719 // attempt to locate the segments file.
720 useFirstMethod = false;
723 // Second method: since both directory cache and
724 // file contents cache seem to be stale, just
725 // advance the generation.
726 if (!useFirstMethod) {
727 if (genLookaheadCount < defaultGenLookaheadCount) {
730 if (infoStream != null) {
731 message("look ahead increment gen to " + gen);
734 // All attempts have failed -- throw first exc:
737 } else if (lastGen == gen) {
738 // This means we're about to try the same
739 // segments_N last tried.
742 // Segment file has advanced since our last loop
743 // (we made "progress"), so reset retryCount:
749 segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
754 Object v = doBody(segmentFileName);
755 if (infoStream != null) {
756 message("success on " + segmentFileName);
759 } catch (IOException err) {
761 // Save the original root cause:
766 if (infoStream != null) {
767 message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retryCount=" + retryCount + "; gen = " + gen);
770 if (gen > 1 && useFirstMethod && retryCount == 1) {
772 // This is our second time trying this same segments
773 // file (because retryCount is 1), and, there is
774 // possibly a segments_(N-1) (because gen > 1).
775 // So, check if the segments_(N-1) exists and
777 String prevSegmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
781 final boolean prevExists;
782 prevExists = directory.fileExists(prevSegmentFileName);
785 if (infoStream != null) {
786 message("fallback to prior segment file '" + prevSegmentFileName + "'");
789 Object v = doBody(prevSegmentFileName);
790 if (infoStream != null) {
791 message("success on fallback " + prevSegmentFileName);
794 } catch (IOException err2) {
795 if (infoStream != null) {
796 message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
806 * Subclass must implement this. The assumption is an
807 * IOException will be thrown if something goes wrong
808 * during the processing that could have been caused by
809 * a writer committing.
811 protected abstract Object doBody(String segmentFileName) throws CorruptIndexException, IOException;
815 * Returns a new SegmentInfos containing the SegmentInfo
816 * instances in the specified range first (inclusive) to
817 * last (exclusive), so total number of segments returned
819 * @deprecated use {@code asList().subList(first, last)}
823 public SegmentInfos range(int first, int last) {
824 SegmentInfos infos = new SegmentInfos();
825 infos.addAll(segments.subList(first, last));
829 // Carry over generation numbers from another SegmentInfos
830 void updateGeneration(SegmentInfos other) {
831 lastGeneration = other.lastGeneration;
832 generation = other.generation;
835 final void rollbackCommit(Directory dir) throws IOException {
836 if (pendingSegnOutput != null) {
838 pendingSegnOutput.close();
839 } catch (Throwable t) {
840 // Suppress so we keep throwing the original exception
844 // Must carefully compute fileName from "generation"
845 // since lastGeneration isn't incremented:
847 final String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
850 dir.deleteFile(segmentFileName);
851 } catch (Throwable t) {
852 // Suppress so we keep throwing the original exception
855 pendingSegnOutput = null;
859 /** Call this to start a commit. This writes the new
860 * segments file, but writes an invalid checksum at the
861 * end, so that it is not visible to readers. Once this
862 * is called you must call {@link #finishCommit} to complete
863 * the commit or {@link #rollbackCommit} to abort it.
865 * Note: {@link #changed()} should be called prior to this
866 * method if changes have been made to this {@link SegmentInfos} instance
869 final void prepareCommit(Directory dir) throws IOException {
870 if (pendingSegnOutput != null)
871 throw new IllegalStateException("prepareCommit was already called");
875 /** Returns all file names referenced by SegmentInfo
876 * instances matching the provided Directory (ie files
877 * associated with any "external" segments are skipped).
878 * The returned collection is recomputed on each
880 public Collection<String> files(Directory dir, boolean includeSegmentsFile) throws IOException {
881 HashSet<String> files = new HashSet<String>();
882 if (includeSegmentsFile) {
883 files.add(getCurrentSegmentFileName());
885 final int size = size();
886 for(int i=0;i<size;i++) {
887 final SegmentInfo info = info(i);
888 if (info.dir == dir) {
889 files.addAll(info(i).files());
895 final void finishCommit(Directory dir) throws IOException {
896 if (pendingSegnOutput == null)
897 throw new IllegalStateException("prepareCommit was not called");
898 boolean success = false;
900 pendingSegnOutput.finishCommit();
901 pendingSegnOutput.close();
902 pendingSegnOutput = null;
909 // NOTE: if we crash here, we have left a segments_N
910 // file in the directory in a possibly corrupt state (if
911 // some bytes made it to stable storage and others
912 // didn't). But, the segments_N file includes checksum
913 // at the end, which should catch this case. So when a
914 // reader tries to read it, it will throw a
915 // CorruptIndexException, which should cause the retry
916 // logic in SegmentInfos to kick in and load the last
917 // good (previous) segments_N-1 file.
919 final String fileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
924 dir.sync(Collections.singleton(fileName));
929 dir.deleteFile(fileName);
930 } catch (Throwable t) {
931 // Suppress so we keep throwing the original exception
936 lastGeneration = generation;
939 IndexOutput genOutput = dir.createOutput(IndexFileNames.SEGMENTS_GEN);
941 genOutput.writeInt(FORMAT_LOCKLESS);
942 genOutput.writeLong(generation);
943 genOutput.writeLong(generation);
947 } catch (ThreadInterruptedException t) {
949 } catch (Throwable t) {
950 // It's OK if we fail to write this file since it's
951 // used only as one of the retry fallbacks.
955 /** Writes & syncs to the Directory dir, taking care to
956 * remove the segments file on exception
958 * Note: {@link #changed()} should be called prior to this
959 * method if changes have been made to this {@link SegmentInfos} instance
962 final void commit(Directory dir) throws IOException {
967 public String toString(Directory directory) {
968 StringBuilder buffer = new StringBuilder();
969 buffer.append(getCurrentSegmentFileName()).append(": ");
970 final int count = size();
971 for(int i = 0; i < count; i++) {
975 final SegmentInfo info = info(i);
976 buffer.append(info.toString(directory, 0));
978 return buffer.toString();
981 public Map<String,String> getUserData() {
985 void setUserData(Map<String,String> data) {
987 userData = Collections.<String,String>emptyMap();
993 /** Replaces all segments in this instance, but keeps
994 * generation, version, counter so that future commits
997 void replace(SegmentInfos other) {
998 rollbackSegmentInfos(other.asList());
999 lastGeneration = other.lastGeneration;
1002 /** Returns sum of all segment's docCounts. Note that
1003 * this does not include deletions */
1004 public int totalDocCount() {
1006 for(SegmentInfo info : this) {
1007 count += info.docCount;
1012 /** Call this before committing if changes have been made to the
1014 public void changed() {
1018 /** applies all changes caused by committing a merge to this SegmentInfos */
1019 void applyMergeChanges(MergePolicy.OneMerge merge, boolean dropSegment) {
1020 final Set<SegmentInfo> mergedAway = new HashSet<SegmentInfo>(merge.segments);
1021 boolean inserted = false;
1023 for (int segIdx = 0, cnt = segments.size(); segIdx < cnt; segIdx++) {
1024 assert segIdx >= newSegIdx;
1025 final SegmentInfo info = segments.get(segIdx);
1026 if (mergedAway.contains(info)) {
1027 if (!inserted && !dropSegment) {
1028 segments.set(segIdx, merge.info);
1033 segments.set(newSegIdx, info);
1038 // Either we found place to insert segment, or, we did
1039 // not, but only because all segments we merged became
1040 // deleted while we are merging, in which case it should
1041 // be the case that the new segment is also all deleted,
1042 // we insert it at the beginning if it should not be dropped:
1043 if (!inserted && !dropSegment) {
1044 segments.add(0, merge.info);
1047 // the rest of the segments in list are duplicates, so don't remove from map, only list!
1048 segments.subList(newSegIdx, segments.size()).clear();
1052 segmentSet.add(merge.info);
1054 segmentSet.removeAll(mergedAway);
1056 assert segmentSet.size() == segments.size();
1059 List<SegmentInfo> createBackupSegmentInfos(boolean cloneChildren) {
1060 if (cloneChildren) {
1061 final List<SegmentInfo> list = new ArrayList<SegmentInfo>(size());
1062 for(final SegmentInfo info : this) {
1063 list.add((SegmentInfo) info.clone());
1067 return new ArrayList<SegmentInfo>(segments);
1071 void rollbackSegmentInfos(List<SegmentInfo> infos) {
1076 /** Returns an <b>unmodifiable</b> {@link Iterator} of contained segments in order. */
1077 // @Override (comment out until Java 6)
1078 public Iterator<SegmentInfo> iterator() {
1079 return asList().iterator();
1082 /** Returns all contained segments as an <b>unmodifiable</b> {@link List} view. */
1083 public List<SegmentInfo> asList() {
1084 if (cachedUnmodifiableList == null) {
1085 cachedUnmodifiableList = Collections.unmodifiableList(segments);
1087 return cachedUnmodifiableList;
1090 /** Returns all contained segments as an <b>unmodifiable</b> {@link Set} view.
1091 * The iterator is not sorted, use {@link List} view or {@link #iterator} to get all segments in order. */
1092 public Set<SegmentInfo> asSet() {
1093 if (cachedUnmodifiableSet == null) {
1094 cachedUnmodifiableSet = Collections.unmodifiableSet(segmentSet);
1096 return cachedUnmodifiableSet;
1100 return segments.size();
1103 public void add(SegmentInfo si) {
1104 if (segmentSet.contains(si)) {
1105 throw new IllegalStateException("Cannot add the same segment two times to this SegmentInfos instance");
1109 assert segmentSet.size() == segments.size();
1112 public void addAll(Iterable<SegmentInfo> sis) {
1113 for (final SegmentInfo si : sis) {
1118 public void clear() {
1123 public void remove(SegmentInfo si) {
1124 final int index = this.indexOf(si);
1130 public void remove(int index) {
1131 segmentSet.remove(segments.remove(index));
1132 assert segmentSet.size() == segments.size();
1135 public boolean contains(SegmentInfo si) {
1136 return segmentSet.contains(si);
1139 public int indexOf(SegmentInfo si) {
1140 if (segmentSet.contains(si)) {
1141 return segments.indexOf(si);