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(input, format,
276
FORMAT_MINIMUM, FORMAT_MAXIMUM);
278
if (format < FORMAT_MAXIMUM) {
279
throw new IndexFormatTooNewException(input, 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 (resource: " + input + ")");
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);