~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to lucene/src/java/org/apache/lucene/index/SegmentInfos.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package org.apache.lucene.index;
2
 
 
3
 
/**
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
10
 
 *
11
 
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 
 *
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.
18
 
 */
19
 
 
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;
28
 
 
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;
40
 
import java.util.Map;
41
 
import java.util.Set;
42
 
 
43
 
/**
44
 
 * A collection of segmentInfo objects with methods for operating on
45
 
 * those segments in relation to the file system.
46
 
 * 
47
 
 * @lucene.experimental
48
 
 */
49
 
public final class SegmentInfos implements Cloneable, Iterable<SegmentInfo> {
50
 
 
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;
54
 
 
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.
63
 
   */
64
 
  public static final int FORMAT_LOCKLESS = -2;
65
 
 
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>
68
 
   * for details.
69
 
   */
70
 
  public static final int FORMAT_SINGLE_NORM_FILE = -3;
71
 
 
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;
75
 
 
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;
79
 
 
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;
83
 
 
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;
88
 
 
89
 
  /** This format adds optional commit userData (String) storage. */
90
 
  public static final int FORMAT_USER_DATA = -8;
91
 
 
92
 
  /** This format adds optional per-segment String
93
 
   *  diagnostics storage, and switches userData to Map */
94
 
  public static final int FORMAT_DIAGNOSTICS = -9;
95
 
 
96
 
  /** Each segment records whether it has term vectors */
97
 
  public static final int FORMAT_HAS_VECTORS = -10;
98
 
 
99
 
  /** Each segment records the Lucene version that created it. */
100
 
  public static final int FORMAT_3_1 = -11;
101
 
  
102
 
  /* This must always point to the most recent file format. */
103
 
  public static final int CURRENT_FORMAT = FORMAT_3_1;
104
 
 
105
 
  public static final int FORMAT_MINIMUM = FORMAT;
106
 
  public static final int FORMAT_MAXIMUM = CURRENT_FORMAT;
107
 
  
108
 
  public int counter = 0;    // used to name new segments
109
 
  /**
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.
112
 
   */
113
 
  long version = System.currentTimeMillis();
114
 
 
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
119
 
 
120
 
  private Map<String,String> userData = Collections.<String,String>emptyMap();       // Opaque Map<String, String> that user can specify during IndexWriter.commit
121
 
 
122
 
  private int format;
123
 
  
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;  
128
 
  
129
 
  /**
130
 
   * If non-null, information about loading segments_N files
131
 
   * will be printed here.  @see #setInfoStream.
132
 
   */
133
 
  private static PrintStream infoStream = null;
134
 
 
135
 
  public void setFormat(int format) {
136
 
    this.format = format;
137
 
  }
138
 
 
139
 
  public int getFormat() {
140
 
    return format;
141
 
  }
142
 
 
143
 
  public SegmentInfo info(int i) {
144
 
    return segments.get(i);
145
 
  }
146
 
 
147
 
  /**
148
 
   * Get the generation (N) of the current segments_N file
149
 
   * from a list of files.
150
 
   *
151
 
   * @param files -- array of file names to check
152
 
   */
153
 
  public static long getCurrentSegmentGeneration(String[] files) {
154
 
    if (files == null) {
155
 
      return -1;
156
 
    }
157
 
    long max = -1;
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);
162
 
        if (gen > max) {
163
 
          max = gen;
164
 
        }
165
 
      }
166
 
    }
167
 
    return max;
168
 
  }
169
 
 
170
 
  /**
171
 
   * Get the generation (N) of the current segments_N file
172
 
   * in the directory.
173
 
   *
174
 
   * @param directory -- directory to search for the latest segments_N file
175
 
   */
176
 
  public static long getCurrentSegmentGeneration(Directory directory) throws IOException {
177
 
    try {
178
 
      return getCurrentSegmentGeneration(directory.listAll());
179
 
    } catch (NoSuchDirectoryException nsde) {
180
 
      return -1;
181
 
    }
182
 
  }
183
 
 
184
 
  /**
185
 
   * Get the filename of the current segments_N file
186
 
   * from a list of files.
187
 
   *
188
 
   * @param files -- array of file names to check
189
 
   */
190
 
 
191
 
  public static String getCurrentSegmentFileName(String[] files) throws IOException {
192
 
    return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
193
 
                                                 "",
194
 
                                                 getCurrentSegmentGeneration(files));
195
 
  }
196
 
 
197
 
  /**
198
 
   * Get the filename of the current segments_N file
199
 
   * in the directory.
200
 
   *
201
 
   * @param directory -- directory to search for the latest segments_N file
202
 
   */
203
 
  public static String getCurrentSegmentFileName(Directory directory) throws IOException {
204
 
    return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
205
 
                                                 "",
206
 
                                                 getCurrentSegmentGeneration(directory));
207
 
  }
208
 
 
209
 
  /**
210
 
   * Get the segments_N filename in use by this segment infos.
211
 
   */
212
 
  public String getCurrentSegmentFileName() {
213
 
    return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
214
 
                                                 "",
215
 
                                                 lastGeneration);
216
 
  }
217
 
 
218
 
  /**
219
 
   * Parse the generation off the segments file name and
220
 
   * return it.
221
 
   */
222
 
  public static long generationFromSegmentsFileName(String fileName) {
223
 
    if (fileName.equals(IndexFileNames.SEGMENTS)) {
224
 
      return 0;
225
 
    } else if (fileName.startsWith(IndexFileNames.SEGMENTS)) {
226
 
      return Long.parseLong(fileName.substring(1+IndexFileNames.SEGMENTS.length()),
227
 
                            Character.MAX_RADIX);
228
 
    } else {
229
 
      throw new IllegalArgumentException("fileName \"" + fileName + "\" is not a segments file");
230
 
    }
231
 
  }
232
 
 
233
 
 
234
 
  /**
235
 
   * Get the next segments_N filename that will be written.
236
 
   */
237
 
  public String getNextSegmentFileName() {
238
 
    long nextGeneration;
239
 
 
240
 
    if (generation == -1) {
241
 
      nextGeneration = 1;
242
 
    } else {
243
 
      nextGeneration = generation+1;
244
 
    }
245
 
    return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
246
 
                                                 "",
247
 
                                                 nextGeneration);
248
 
  }
249
 
 
250
 
  /**
251
 
   * Read a particular segmentFileName.  Note that this may
252
 
   * throw an IOException if a commit is in process.
253
 
   *
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
258
 
   */
259
 
  public final void read(Directory directory, String segmentFileName) throws CorruptIndexException, IOException {
260
 
    boolean success = false;
261
 
 
262
 
    // Clear any previous segments:
263
 
    this.clear();
264
 
 
265
 
    ChecksumIndexInput input = new ChecksumIndexInput(directory.openInput(segmentFileName));
266
 
 
267
 
    generation = generationFromSegmentsFileName(segmentFileName);
268
 
 
269
 
    lastGeneration = generation;
270
 
 
271
 
    try {
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);
277
 
      }
278
 
      if (format < FORMAT_MAXIMUM) {
279
 
        throw new IndexFormatTooNewException(input, format,
280
 
          FORMAT_MINIMUM, FORMAT_MAXIMUM);
281
 
      }
282
 
      version = input.readLong(); // read version
283
 
      counter = input.readInt(); // read counter
284
 
      
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);
295
 
            }
296
 
          } else if (si.getUseCompoundFile()) {
297
 
            dir = new CompoundFileReader(dir, IndexFileNames.segmentFileName(
298
 
                si.name, IndexFileNames.COMPOUND_FILE_EXTENSION), 1024);
299
 
          }
300
 
 
301
 
          try {
302
 
            String store = si.getDocStoreOffset() != -1 ? si.getDocStoreSegment() : si.name;
303
 
            si.setVersion(FieldsReader.detectCodeVersion(dir, store));
304
 
          } finally {
305
 
            // If we opened the directory, close it
306
 
            if (dir != directory) dir.close();
307
 
          }
308
 
        }
309
 
        add(si);
310
 
      }
311
 
      
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
315
 
        else
316
 
          version = input.readLong(); // read version
317
 
      }
318
 
 
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());
324
 
        } else {
325
 
          userData = Collections.<String,String>emptyMap();
326
 
        }
327
 
      } else {
328
 
        userData = Collections.<String,String>emptyMap();
329
 
      }
330
 
 
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 + ")");
336
 
      }
337
 
      success = true;
338
 
    }
339
 
    finally {
340
 
      input.close();
341
 
      if (!success) {
342
 
        // Clear any segment infos we had loaded so we
343
 
        // have a clean slate on retry:
344
 
        this.clear();
345
 
      }
346
 
    }
347
 
  }
348
 
 
349
 
  /**
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
354
 
   */
355
 
  public final void read(Directory directory) throws CorruptIndexException, IOException {
356
 
 
357
 
    generation = lastGeneration = -1;
358
 
 
359
 
    new FindSegmentsFile(directory) {
360
 
 
361
 
      @Override
362
 
      protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
363
 
        read(directory, segmentFileName);
364
 
        return null;
365
 
      }
366
 
    }.run();
367
 
  }
368
 
 
369
 
  // Only non-null after prepareCommit has been called and
370
 
  // before finishCommit is called
371
 
  ChecksumIndexOutput pendingSegnOutput;
372
 
 
373
 
  private final void write(Directory directory) throws IOException {
374
 
 
375
 
    String segmentFileName = getNextSegmentFileName();
376
 
 
377
 
    // Always advance the generation on write:
378
 
    if (generation == -1) {
379
 
      generation = 1;
380
 
    } else {
381
 
      generation++;
382
 
    }
383
 
 
384
 
    ChecksumIndexOutput segnOutput = new ChecksumIndexOutput(directory.createOutput(segmentFileName));
385
 
 
386
 
    boolean success = false;
387
 
 
388
 
    try {
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);
395
 
      }
396
 
      segnOutput.writeStringStringMap(userData);
397
 
      segnOutput.prepareCommit();
398
 
      pendingSegnOutput = segnOutput;
399
 
      success = true;
400
 
    } finally {
401
 
      if (!success) {
402
 
        // We hit an exception above; try to close the file
403
 
        // but suppress any exception:
404
 
        IOUtils.closeWhileHandlingException(segnOutput);
405
 
        try {
406
 
          // Try not to leave a truncated segments_N file in
407
 
          // the index:
408
 
          directory.deleteFile(segmentFileName);
409
 
        } catch (Throwable t) {
410
 
          // Suppress so we keep throwing the original exception
411
 
        }
412
 
      }
413
 
    }
414
 
  }
415
 
 
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) {
421
 
        it.remove();
422
 
        segmentSet.remove(info);
423
 
      }
424
 
    }
425
 
    assert segmentSet.size() == segments.size();
426
 
  }
427
 
 
428
 
  /**
429
 
   * Returns a copy of this instance, also copying each
430
 
   * SegmentInfo.
431
 
   */
432
 
  
433
 
  @Override
434
 
  public Object clone() {
435
 
    try {
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());
445
 
      }
446
 
      sis.userData = new HashMap<String,String>(userData);
447
 
      return sis;
448
 
    } catch (CloneNotSupportedException e) {
449
 
      throw new RuntimeException("should not happen", e);
450
 
    }
451
 
  }
452
 
 
453
 
  /**
454
 
   * version number when this SegmentInfos was generated.
455
 
   */
456
 
  public long getVersion() {
457
 
    return version;
458
 
  }
459
 
  public long getGeneration() {
460
 
    return generation;
461
 
  }
462
 
  public long getLastGeneration() {
463
 
    return lastGeneration;
464
 
  }
465
 
 
466
 
  /**
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
470
 
   */
471
 
  public static long readCurrentVersion(Directory directory)
472
 
    throws CorruptIndexException, IOException {
473
 
 
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
478
 
    // current:
479
 
    SegmentInfos sis = new SegmentInfos();
480
 
    sis.read(directory);
481
 
    return sis.version;
482
 
  }
483
 
 
484
 
  /**
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
488
 
   */
489
 
  public static Map<String,String> readCurrentUserData(Directory directory)
490
 
    throws CorruptIndexException, IOException {
491
 
    SegmentInfos sis = new SegmentInfos();
492
 
    sis.read(directory);
493
 
    return sis.getUserData();
494
 
  }
495
 
 
496
 
  /** If non-null, information about retries when loading
497
 
   * the segments file will be printed to this.
498
 
   */
499
 
  public static void setInfoStream(PrintStream infoStream) {
500
 
    SegmentInfos.infoStream = infoStream;
501
 
  }
502
 
 
503
 
  /* Advanced configuration of retry logic in loading
504
 
     segments_N file */
505
 
  private static int defaultGenFileRetryCount = 10;
506
 
  private static int defaultGenFileRetryPauseMsec = 50;
507
 
  private static int defaultGenLookaheadCount = 10;
508
 
 
509
 
  /**
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.
514
 
   */
515
 
  public static void setDefaultGenFileRetryCount(int count) {
516
 
    defaultGenFileRetryCount = count;
517
 
  }
518
 
 
519
 
  /**
520
 
   * @see #setDefaultGenFileRetryCount
521
 
   */
522
 
  public static int getDefaultGenFileRetryCount() {
523
 
    return defaultGenFileRetryCount;
524
 
  }
525
 
 
526
 
  /**
527
 
   * Advanced: set how many milliseconds to pause in between
528
 
   * attempts to load the segments.gen file.
529
 
   */
530
 
  public static void setDefaultGenFileRetryPauseMsec(int msec) {
531
 
    defaultGenFileRetryPauseMsec = msec;
532
 
  }
533
 
 
534
 
  /**
535
 
   * @see #setDefaultGenFileRetryPauseMsec
536
 
   */
537
 
  public static int getDefaultGenFileRetryPauseMsec() {
538
 
    return defaultGenFileRetryPauseMsec;
539
 
  }
540
 
 
541
 
  /**
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
546
 
   * file.
547
 
   */
548
 
  public static void setDefaultGenLookaheadCount(int count) {
549
 
    defaultGenLookaheadCount = count;
550
 
  }
551
 
  /**
552
 
   * @see #setDefaultGenLookaheadCount
553
 
   */
554
 
  public static int getDefaultGenLookahedCount() {
555
 
    return defaultGenLookaheadCount;
556
 
  }
557
 
 
558
 
  /**
559
 
   * @see #setInfoStream
560
 
   */
561
 
  public static PrintStream getInfoStream() {
562
 
    return infoStream;
563
 
  }
564
 
 
565
 
  /**
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
569
 
   * creation.
570
 
   */
571
 
  private static void message(String message) {
572
 
    infoStream.println("SIS [" + Thread.currentThread().getName() + "]: " + message);
573
 
  }
574
 
 
575
 
  /**
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
582
 
   * commit finishing.
583
 
   */
584
 
  public abstract static class FindSegmentsFile {
585
 
    
586
 
    final Directory directory;
587
 
 
588
 
    public FindSegmentsFile(Directory directory) {
589
 
      this.directory = directory;
590
 
    }
591
 
 
592
 
    public Object run() throws CorruptIndexException, IOException {
593
 
      return run(null);
594
 
    }
595
 
    
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());
601
 
      }
602
 
 
603
 
      String segmentFileName = null;
604
 
      long lastGen = -1;
605
 
      long gen = 0;
606
 
      int genLookaheadCount = 0;
607
 
      IOException exc = null;
608
 
      int retryCount = 0;
609
 
 
610
 
      boolean useFirstMethod = true;
611
 
 
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
621
 
      // it.
622
 
      
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
626
 
      // when necessary.
627
 
 
628
 
      while(true) {
629
 
 
630
 
        if (useFirstMethod) {
631
 
 
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
636
 
          // caching):
637
 
          String[] files = null;
638
 
 
639
 
          long genA = -1;
640
 
 
641
 
          files = directory.listAll();
642
 
          
643
 
          if (files != null) {
644
 
            genA = getCurrentSegmentGeneration(files);
645
 
          }
646
 
          
647
 
          if (infoStream != null) {
648
 
            message("directory listing genA=" + genA);
649
 
          }
650
 
 
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.
656
 
          long genB = -1;
657
 
          for(int i=0;i<defaultGenFileRetryCount;i++) {
658
 
            IndexInput genInput = null;
659
 
            try {
660
 
              genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);
661
 
            } catch (FileNotFoundException e) {
662
 
              if (infoStream != null) {
663
 
                message("segments.gen open: FileNotFoundException " + e);
664
 
              }
665
 
              break;
666
 
            } catch (IOException e) {
667
 
              if (infoStream != null) {
668
 
                message("segments.gen open: IOException " + e);
669
 
              }
670
 
            }
671
 
  
672
 
            if (genInput != null) {
673
 
              try {
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);
680
 
                  }
681
 
                  if (gen0 == gen1) {
682
 
                    // The file is consistent.
683
 
                    genB = gen0;
684
 
                    break;
685
 
                  }
686
 
                }
687
 
              } catch (IOException err2) {
688
 
                // will retry
689
 
              } finally {
690
 
                genInput.close();
691
 
              }
692
 
            }
693
 
            try {
694
 
              Thread.sleep(defaultGenFileRetryPauseMsec);
695
 
            } catch (InterruptedException ie) {
696
 
              throw new ThreadInterruptedException(ie);
697
 
            }
698
 
          }
699
 
 
700
 
          if (infoStream != null) {
701
 
            message(IndexFileNames.SEGMENTS_GEN + " check: genB=" + genB);
702
 
          }
703
 
 
704
 
          // Pick the larger of the two gen's:
705
 
          if (genA > genB)
706
 
            gen = genA;
707
 
          else
708
 
            gen = genB;
709
 
 
710
 
          if (gen == -1) {
711
 
            // Neither approach found a generation
712
 
            throw new IndexNotFoundException("no segments* file found in " + directory + ": files: " + Arrays.toString(files));
713
 
          }
714
 
        }
715
 
 
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;
721
 
        }
722
 
 
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) {
728
 
            gen++;
729
 
            genLookaheadCount++;
730
 
            if (infoStream != null) {
731
 
              message("look ahead increment gen to " + gen);
732
 
            }
733
 
          } else {
734
 
            // All attempts have failed -- throw first exc:
735
 
            throw exc;
736
 
          }
737
 
        } else if (lastGen == gen) {
738
 
          // This means we're about to try the same
739
 
          // segments_N last tried.
740
 
          retryCount++;
741
 
        } else {
742
 
          // Segment file has advanced since our last loop
743
 
          // (we made "progress"), so reset retryCount:
744
 
          retryCount = 0;
745
 
        }
746
 
 
747
 
        lastGen = gen;
748
 
 
749
 
        segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
750
 
                                                                "",
751
 
                                                                gen);
752
 
 
753
 
        try {
754
 
          Object v = doBody(segmentFileName);
755
 
          if (infoStream != null) {
756
 
            message("success on " + segmentFileName);
757
 
          }
758
 
          return v;
759
 
        } catch (IOException err) {
760
 
 
761
 
          // Save the original root cause:
762
 
          if (exc == null) {
763
 
            exc = err;
764
 
          }
765
 
 
766
 
          if (infoStream != null) {
767
 
            message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retryCount=" + retryCount + "; gen = " + gen);
768
 
          }
769
 
 
770
 
          if (gen > 1 && useFirstMethod && retryCount == 1) {
771
 
 
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
776
 
            // try it if so:
777
 
            String prevSegmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
778
 
                                                                               "",
779
 
                                                                               gen-1);
780
 
 
781
 
            final boolean prevExists;
782
 
            prevExists = directory.fileExists(prevSegmentFileName);
783
 
 
784
 
            if (prevExists) {
785
 
              if (infoStream != null) {
786
 
                message("fallback to prior segment file '" + prevSegmentFileName + "'");
787
 
              }
788
 
              try {
789
 
                Object v = doBody(prevSegmentFileName);
790
 
                if (infoStream != null) {
791
 
                  message("success on fallback " + prevSegmentFileName);
792
 
                }
793
 
                return v;
794
 
              } catch (IOException err2) {
795
 
                if (infoStream != null) {
796
 
                  message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
797
 
                }
798
 
              }
799
 
            }
800
 
          }
801
 
        }
802
 
      }
803
 
    }
804
 
 
805
 
    /**
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.
810
 
     */
811
 
    protected abstract Object doBody(String segmentFileName) throws CorruptIndexException, IOException;
812
 
  }
813
 
 
814
 
  /**
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
818
 
   * is last-first.
819
 
   * @deprecated use {@code asList().subList(first, last)}
820
 
   * instead.
821
 
   */
822
 
  @Deprecated
823
 
  public SegmentInfos range(int first, int last) {
824
 
    SegmentInfos infos = new SegmentInfos();
825
 
    infos.addAll(segments.subList(first, last));
826
 
    return infos;
827
 
  }
828
 
 
829
 
  // Carry over generation numbers from another SegmentInfos
830
 
  void updateGeneration(SegmentInfos other) {
831
 
    lastGeneration = other.lastGeneration;
832
 
    generation = other.generation;
833
 
  }
834
 
 
835
 
  final void rollbackCommit(Directory dir) throws IOException {
836
 
    if (pendingSegnOutput != null) {
837
 
      try {
838
 
        pendingSegnOutput.close();
839
 
      } catch (Throwable t) {
840
 
        // Suppress so we keep throwing the original exception
841
 
        // in our caller
842
 
      }
843
 
 
844
 
      // Must carefully compute fileName from "generation"
845
 
      // since lastGeneration isn't incremented:
846
 
      try {
847
 
        final String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
848
 
                                                                              "",
849
 
                                                                             generation);
850
 
        dir.deleteFile(segmentFileName);
851
 
      } catch (Throwable t) {
852
 
        // Suppress so we keep throwing the original exception
853
 
        // in our caller
854
 
      }
855
 
      pendingSegnOutput = null;
856
 
    }
857
 
  }
858
 
 
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.
864
 
   *  <p>
865
 
   *  Note: {@link #changed()} should be called prior to this
866
 
   *  method if changes have been made to this {@link SegmentInfos} instance
867
 
   *  </p>  
868
 
   **/
869
 
  final void prepareCommit(Directory dir) throws IOException {
870
 
    if (pendingSegnOutput != null)
871
 
      throw new IllegalStateException("prepareCommit was already called");
872
 
    write(dir);
873
 
  }
874
 
 
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
879
 
   *  invocation.  */
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());
884
 
    }
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());
890
 
      }
891
 
    }
892
 
    return files;
893
 
  }
894
 
 
895
 
  final void finishCommit(Directory dir) throws IOException {
896
 
    if (pendingSegnOutput == null)
897
 
      throw new IllegalStateException("prepareCommit was not called");
898
 
    boolean success = false;
899
 
    try {
900
 
      pendingSegnOutput.finishCommit();
901
 
      pendingSegnOutput.close();
902
 
      pendingSegnOutput = null;
903
 
      success = true;
904
 
    } finally {
905
 
      if (!success)
906
 
        rollbackCommit(dir);
907
 
    }
908
 
 
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.
918
 
 
919
 
    final String fileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
920
 
                                                                  "",
921
 
                                                                  generation);
922
 
    success = false;
923
 
    try {
924
 
      dir.sync(Collections.singleton(fileName));
925
 
      success = true;
926
 
    } finally {
927
 
      if (!success) {
928
 
        try {
929
 
          dir.deleteFile(fileName);
930
 
        } catch (Throwable t) {
931
 
          // Suppress so we keep throwing the original exception
932
 
        }
933
 
      }
934
 
    }
935
 
 
936
 
    lastGeneration = generation;
937
 
 
938
 
    try {
939
 
      IndexOutput genOutput = dir.createOutput(IndexFileNames.SEGMENTS_GEN);
940
 
      try {
941
 
        genOutput.writeInt(FORMAT_LOCKLESS);
942
 
        genOutput.writeLong(generation);
943
 
        genOutput.writeLong(generation);
944
 
      } finally {
945
 
        genOutput.close();
946
 
      }
947
 
    } catch (ThreadInterruptedException t) {
948
 
      throw 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.
952
 
    }
953
 
  }
954
 
 
955
 
  /** Writes & syncs to the Directory dir, taking care to
956
 
   *  remove the segments file on exception
957
 
   *  <p>
958
 
   *  Note: {@link #changed()} should be called prior to this
959
 
   *  method if changes have been made to this {@link SegmentInfos} instance
960
 
   *  </p>  
961
 
   **/
962
 
  final void commit(Directory dir) throws IOException {
963
 
    prepareCommit(dir);
964
 
    finishCommit(dir);
965
 
  }
966
 
 
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++) {
972
 
      if (i > 0) {
973
 
        buffer.append(' ');
974
 
      }
975
 
      final SegmentInfo info = info(i);
976
 
      buffer.append(info.toString(directory, 0));
977
 
    }
978
 
    return buffer.toString();
979
 
  }
980
 
 
981
 
  public Map<String,String> getUserData() {
982
 
    return userData;
983
 
  }
984
 
 
985
 
  void setUserData(Map<String,String> data) {
986
 
    if (data == null) {
987
 
      userData = Collections.<String,String>emptyMap();
988
 
    } else {
989
 
      userData = data;
990
 
    }
991
 
  }
992
 
 
993
 
  /** Replaces all segments in this instance, but keeps
994
 
   *  generation, version, counter so that future commits
995
 
   *  remain write once.
996
 
   */
997
 
  void replace(SegmentInfos other) {
998
 
    rollbackSegmentInfos(other.asList());
999
 
    lastGeneration = other.lastGeneration;
1000
 
  }
1001
 
 
1002
 
  /** Returns sum of all segment's docCounts.  Note that
1003
 
   *  this does not include deletions */
1004
 
  public int totalDocCount() {
1005
 
    int count = 0;
1006
 
    for(SegmentInfo info : this) {
1007
 
      count += info.docCount;
1008
 
    }
1009
 
    return count;
1010
 
  }
1011
 
 
1012
 
  /** Call this before committing if changes have been made to the
1013
 
   *  segments. */
1014
 
  public void changed() {
1015
 
    version++;
1016
 
  }
1017
 
  
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;
1022
 
    int newSegIdx = 0;
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);
1029
 
          inserted = true;
1030
 
          newSegIdx++;
1031
 
        }
1032
 
      } else {
1033
 
        segments.set(newSegIdx, info);
1034
 
        newSegIdx++;
1035
 
      }
1036
 
    }
1037
 
 
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);
1045
 
    }
1046
 
 
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();
1049
 
    
1050
 
    // update the Set
1051
 
    if (!dropSegment) {
1052
 
      segmentSet.add(merge.info);
1053
 
    }
1054
 
    segmentSet.removeAll(mergedAway);
1055
 
    
1056
 
    assert segmentSet.size() == segments.size();
1057
 
  }
1058
 
 
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());
1064
 
      }
1065
 
      return list;
1066
 
    } else {
1067
 
      return new ArrayList<SegmentInfo>(segments);
1068
 
    }
1069
 
  }
1070
 
  
1071
 
  void rollbackSegmentInfos(List<SegmentInfo> infos) {
1072
 
    this.clear();
1073
 
    this.addAll(infos);
1074
 
  }
1075
 
  
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();
1080
 
  }
1081
 
  
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);
1086
 
    }
1087
 
    return cachedUnmodifiableList;
1088
 
  }
1089
 
  
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);
1095
 
    }
1096
 
    return cachedUnmodifiableSet;
1097
 
  }
1098
 
  
1099
 
  public int size() {
1100
 
    return segments.size();
1101
 
  }
1102
 
 
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");
1106
 
    }
1107
 
    segments.add(si);
1108
 
    segmentSet.add(si);
1109
 
    assert segmentSet.size() == segments.size();
1110
 
  }
1111
 
  
1112
 
  public void addAll(Iterable<SegmentInfo> sis) {
1113
 
    for (final SegmentInfo si : sis) {
1114
 
      this.add(si);
1115
 
    }
1116
 
  }
1117
 
  
1118
 
  public void clear() {
1119
 
    segments.clear();
1120
 
    segmentSet.clear();
1121
 
  }
1122
 
  
1123
 
  public void remove(SegmentInfo si) {
1124
 
    final int index = this.indexOf(si);
1125
 
    if (index >= 0) {
1126
 
      this.remove(index);
1127
 
    }
1128
 
  }
1129
 
  
1130
 
  public void remove(int index) {
1131
 
    segmentSet.remove(segments.remove(index));
1132
 
    assert segmentSet.size() == segments.size();
1133
 
  }
1134
 
  
1135
 
  public boolean contains(SegmentInfo si) {
1136
 
    return segmentSet.contains(si);
1137
 
  }
1138
 
 
1139
 
  public int indexOf(SegmentInfo si) {
1140
 
    if (segmentSet.contains(si)) {
1141
 
      return segments.indexOf(si);
1142
 
    } else {
1143
 
      return -1;
1144
 
    }
1145
 
  }
1146
 
 
1147
 
}