add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / index / SegmentInfos.java
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(segmentFileName, format,
276           FORMAT_MINIMUM, FORMAT_MAXIMUM);
277       }
278       if (format < FORMAT_MAXIMUM) {
279         throw new IndexFormatTooNewException(segmentFileName, 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");
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 }