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
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.io.PrintStream;
30 import java.util.Vector;
31
32 final class SegmentInfos extends Vector {
33
34 /** The file format version, a negative number. */
35 /* Works since counter, the old 1st entry, is always >= 0 */
36 public static final int FORMAT = -1;
37
38 /** This format adds details used for lockless commits. It differs
39 * slightly from the previous format in that file names
40 * are never re-used (write once). Instead, each file is
41 * written to the next generation. For example,
42 * segments_1, segments_2, etc. This allows us to not use
43 * a commit lock. See <a
44 * href="http://lucene.apache.org/java/docs/fileformats.html">file
45 * formats</a> for details.
46 */
47 public static final int FORMAT_LOCKLESS = -2;
48
49 /** This format adds a "hasSingleNormFile" flag into each segment info.
50 * See <a href="http://issues.apache.org/jira/browse/LUCENE-756">LUCENE-756</a>
51 * for details.
52 */
53 public static final int FORMAT_SINGLE_NORM_FILE = -3;
54
55 /** This format allows multiple segments to share a single
56 * vectors and stored fields file. */
57 public static final int FORMAT_SHARED_DOC_STORE = -4;
58
59 /** This format adds a checksum at the end of the file to
60 * ensure all bytes were successfully written. */
61 public static final int FORMAT_CHECKSUM = -5;
62
63 /** This format adds the deletion count for each segment.
64 * This way IndexWriter can efficiently report numDocs(). */
65 public static final int FORMAT_DEL_COUNT = -6;
66
67 /** This format adds the boolean hasProx to record if any
68 * fields in the segment store prox information (ie, have
69 * omitTf==false) */
70 public static final int FORMAT_HAS_PROX = -7;
71
72 /* This must always point to the most recent file format. */
73 static final int CURRENT_FORMAT = FORMAT_HAS_PROX;
74
75 public int counter = 0; // used to name new segments
76 /**
77 * counts how often the index has been changed by adding or deleting docs.
78 * starting with the current time in milliseconds forces to create unique version numbers.
79 */
80 private long version = System.currentTimeMillis();
81
82 private long generation = 0; // generation of the "segments_N" for the next commit
83 private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read
84 // or wrote; this is normally the same as generation except if
85 // there was an IOException that had interrupted a commit
86
87 /**
88 * If non-null, information about loading segments_N files
89 * will be printed here. @see #setInfoStream.
90 */
91 private static PrintStream infoStream;
92
93 public final SegmentInfo info(int i) {
94 return (SegmentInfo) get(i);
95 }
96
97 /**
98 * Get the generation (N) of the current segments_N file
99 * from a list of files.
100 *
101 * @param files -- array of file names to check
102 */
103 public static long getCurrentSegmentGeneration(String[] files) {
104 if (files == null) {
105 return -1;
106 }
107 long max = -1;
108 for (int i = 0; i < files.length; i++) {
109 String file = files[i];
110 if (file.startsWith(IndexFileNames.SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN)) {
111 long gen = generationFromSegmentsFileName(file);
112 if (gen > max) {
113 max = gen;
114 }
115 }
116 }
117 return max;
118 }
119
120 /**
121 * Get the generation (N) of the current segments_N file
122 * in the directory.
123 *
124 * @param directory -- directory to search for the latest segments_N file
125 */
126 public static long getCurrentSegmentGeneration(Directory directory) throws IOException {
127 String[] files = directory.list();
128 if (files == null)
129 throw new IOException("cannot read directory " + directory + ": list() returned null");
130 return getCurrentSegmentGeneration(files);
131 }
132
133 /**
134 * Get the filename of the current segments_N file
135 * from a list of files.
136 *
137 * @param files -- array of file names to check
138 */
139
140 public static String getCurrentSegmentFileName(String[] files) throws IOException {
141 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
142 "",
143 getCurrentSegmentGeneration(files));
144 }
145
146 /**
147 * Get the filename of the current segments_N file
148 * in the directory.
149 *
150 * @param directory -- directory to search for the latest segments_N file
151 */
152 public static String getCurrentSegmentFileName(Directory directory) throws IOException {
153 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
154 "",
155 getCurrentSegmentGeneration(directory));
156 }
157
158 /**
159 * Get the segments_N filename in use by this segment infos.
160 */
161 public String getCurrentSegmentFileName() {
162 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
163 "",
164 lastGeneration);
165 }
166
167 /**
168 * Parse the generation off the segments file name and
169 * return it.
170 */
171 public static long generationFromSegmentsFileName(String fileName) {
172 if (fileName.equals(IndexFileNames.SEGMENTS)) {
173 return 0;
174 } else if (fileName.startsWith(IndexFileNames.SEGMENTS)) {
175 return Long.parseLong(fileName.substring(1+IndexFileNames.SEGMENTS.length()),
176 Character.MAX_RADIX);
177 } else {
178 throw new IllegalArgumentException("fileName \"" + fileName + "\" is not a segments file");
179 }
180 }
181
182
183 /**
184 * Get the next segments_N filename that will be written.
185 */
186 public String getNextSegmentFileName() {
187 long nextGeneration;
188
189 if (generation == -1) {
190 nextGeneration = 1;
191 } else {
192 nextGeneration = generation+1;
193 }
194 return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
195 "",
196 nextGeneration);
197 }
198
199 /**
200 * Read a particular segmentFileName. Note that this may
201 * throw an IOException if a commit is in process.
202 *
203 * @param directory -- directory containing the segments file
204 * @param segmentFileName -- segment file to load
205 * @throws CorruptIndexException if the index is corrupt
206 * @throws IOException if there is a low-level IO error
207 */
208 public final void read(Directory directory, String segmentFileName) throws CorruptIndexException, IOException {
209 boolean success = false;
210
211 // Clear any previous segments:
212 clear();
213
214 ChecksumIndexInput input = new ChecksumIndexInput(directory.openInput(segmentFileName));
215
216 generation = generationFromSegmentsFileName(segmentFileName);
217
218 lastGeneration = generation;
219
220 try {
221 int format = input.readInt();
222 if(format < 0){ // file contains explicit format info
223 // check that it is a format we can understand
224 if (format < CURRENT_FORMAT)
225 throw new CorruptIndexException("Unknown format version: " + format);
226 version = input.readLong(); // read version
227 counter = input.readInt(); // read counter
228 }
229 else{ // file is in old format without explicit format info
230 counter = format;
231 }
232
233 for (int i = input.readInt(); i > 0; i--) { // read segmentInfos
234 add(new SegmentInfo(directory, format, input));
235 }
236
237 if(format >= 0){ // in old format the version number may be at the end of the file
238 if (input.getFilePointer() >= input.length())
239 version = System.currentTimeMillis(); // old file format without version number
240 else
241 version = input.readLong(); // read version
242 }
243
244 if (format <= FORMAT_CHECKSUM) {
245 final long checksumNow = input.getChecksum();
246 final long checksumThen = input.readLong();
247 if (checksumNow != checksumThen)
248 throw new CorruptIndexException("checksum mismatch in segments file");
249 }
250 success = true;
251 }
252 finally {
253 input.close();
254 if (!success) {
255 // Clear any segment infos we had loaded so we
256 // have a clean slate on retry:
257 clear();
258 }
259 }
260 }
261
262 /**
263 * This version of read uses the retry logic (for lock-less
264 * commits) to find the right segments file to load.
265 * @throws CorruptIndexException if the index is corrupt
266 * @throws IOException if there is a low-level IO error
267 */
268 public final void read(Directory directory) throws CorruptIndexException, IOException {
269
270 generation = lastGeneration = -1;
271
272 new FindSegmentsFile(directory) {
273
274 protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
275 read(directory, segmentFileName);
276 return null;
277 }
278 }.run();
279 }
280
281 // Only non-null after prepareCommit has been called and
282 // before finishCommit is called
283 ChecksumIndexOutput pendingOutput;
284
285 private final void write(Directory directory) throws IOException {
286
287 String segmentFileName = getNextSegmentFileName();
288
289 // Always advance the generation on write:
290 if (generation == -1) {
291 generation = 1;
292 } else {
293 generation++;
294 }
295
296 ChecksumIndexOutput output = new ChecksumIndexOutput(directory.createOutput(segmentFileName));
297
298 boolean success = false;
299
300 try {
301 output.writeInt(CURRENT_FORMAT); // write FORMAT
302 output.writeLong(++version); // every write changes
303 // the index
304 output.writeInt(counter); // write counter
305 output.writeInt(size()); // write infos
306 for (int i = 0; i < size(); i++) {
307 info(i).write(output);
308 }
309 output.prepareCommit();
310 success = true;
311 pendingOutput = output;
312 } finally {
313 if (!success) {
314 // We hit an exception above; try to close the file
315 // but suppress any exception:
316 try {
317 output.close();
318 } catch (Throwable t) {
319 // Suppress so we keep throwing the original exception
320 }
321 try {
322 // Try not to leave a truncated segments_N file in
323 // the index:
324 directory.deleteFile(segmentFileName);
325 } catch (Throwable t) {
326 // Suppress so we keep throwing the original exception
327 }
328 }
329 }
330 }
331
332 /**
333 * Returns a copy of this instance, also copying each
334 * SegmentInfo.
335 */
336
337 public Object clone() {
338 SegmentInfos sis = (SegmentInfos) super.clone();
339 for(int i=0;i<sis.size();i++) {
340 sis.set(i, sis.info(i).clone());
341 }
342 return sis;
343 }
344
345 /**
346 * version number when this SegmentInfos was generated.
347 */
348 public long getVersion() {
349 return version;
350 }
351 public long getGeneration() {
352 return generation;
353 }
354 public long getLastGeneration() {
355 return lastGeneration;
356 }
357
358 /**
359 * Current version number from segments file.
360 * @throws CorruptIndexException if the index is corrupt
361 * @throws IOException if there is a low-level IO error
362 */
363 public static long readCurrentVersion(Directory directory)
364 throws CorruptIndexException, IOException {
365
366 return ((Long) new FindSegmentsFile(directory) {
367 protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
368
369 IndexInput input = directory.openInput(segmentFileName);
370
371 int format = 0;
372 long version = 0;
373 try {
374 format = input.readInt();
375 if(format < 0){
376 if (format < CURRENT_FORMAT)
377 throw new CorruptIndexException("Unknown format version: " + format);
378 version = input.readLong(); // read version
379 }
380 }
381 finally {
382 input.close();
383 }
384
385 if(format < 0)
386 return new Long(version);
387
388 // We cannot be sure about the format of the file.
389 // Therefore we have to read the whole file and cannot simply seek to the version entry.
390 SegmentInfos sis = new SegmentInfos();
391 sis.read(directory, segmentFileName);
392 return new Long(sis.getVersion());
393 }
394 }.run()).longValue();
395 }
396
397 /** If non-null, information about retries when loading
398 * the segments file will be printed to this.
399 */
400 public static void setInfoStream(PrintStream infoStream) {
401 SegmentInfos.infoStream = infoStream;
402 }
403
404 /* Advanced configuration of retry logic in loading
405 segments_N file */
406 private static int defaultGenFileRetryCount = 10;
407 private static int defaultGenFileRetryPauseMsec = 50;
408 private static int defaultGenLookaheadCount = 10;
409
410 /**
411 * Advanced: set how many times to try loading the
412 * segments.gen file contents to determine current segment
413 * generation. This file is only referenced when the
414 * primary method (listing the directory) fails.
415 */
416 public static void setDefaultGenFileRetryCount(int count) {
417 defaultGenFileRetryCount = count;
418 }
419
420 /**
421 * @see #setDefaultGenFileRetryCount
422 */
423 public static int getDefaultGenFileRetryCount() {
424 return defaultGenFileRetryCount;
425 }
426
427 /**
428 * Advanced: set how many milliseconds to pause in between
429 * attempts to load the segments.gen file.
430 */
431 public static void setDefaultGenFileRetryPauseMsec(int msec) {
432 defaultGenFileRetryPauseMsec = msec;
433 }
434
435 /**
436 * @see #setDefaultGenFileRetryPauseMsec
437 */
438 public static int getDefaultGenFileRetryPauseMsec() {
439 return defaultGenFileRetryPauseMsec;
440 }
441
442 /**
443 * Advanced: set how many times to try incrementing the
444 * gen when loading the segments file. This only runs if
445 * the primary (listing directory) and secondary (opening
446 * segments.gen file) methods fail to find the segments
447 * file.
448 */
449 public static void setDefaultGenLookaheadCount(int count) {
450 defaultGenLookaheadCount = count;
451 }
452 /**
453 * @see #setDefaultGenLookaheadCount
454 */
455 public static int getDefaultGenLookahedCount() {
456 return defaultGenLookaheadCount;
457 }
458
459 /**
460 * @see #setInfoStream
461 */
462 public static PrintStream getInfoStream() {
463 return infoStream;
464 }
465
466 private static void message(String message) {
467 if (infoStream != null) {
468 infoStream.println("SIS [" + Thread.currentThread().getName() + "]: " + message);
469 }
470 }
471
472 /**
473 * Utility class for executing code that needs to do
474 * something with the current segments file. This is
475 * necessary with lock-less commits because from the time
476 * you locate the current segments file name, until you
477 * actually open it, read its contents, or check modified
478 * time, etc., it could have been deleted due to a writer
479 * commit finishing.
480 */
481 public abstract static class FindSegmentsFile {
482
483 File fileDirectory;
484 Directory directory;
485
486 public FindSegmentsFile(File directory) {
487 this.fileDirectory = directory;
488 }
489
490 public FindSegmentsFile(Directory directory) {
491 this.directory = directory;
492 }
493
494 public Object run() throws CorruptIndexException, IOException {
495 String segmentFileName = null;
496 long lastGen = -1;
497 long gen = 0;
498 int genLookaheadCount = 0;
499 IOException exc = null;
500 boolean retry = false;
501
502 int method = 0;
503
504 // Loop until we succeed in calling doBody() without
505 // hitting an IOException. An IOException most likely
506 // means a commit was in process and has finished, in
507 // the time it took us to load the now-old infos files
508 // (and segments files). It's also possible it's a
509 // true error (corrupt index). To distinguish these,
510 // on each retry we must see "forward progress" on
511 // which generation we are trying to load. If we
512 // don't, then the original error is real and we throw
513 // it.
514
515 // We have three methods for determining the current
516 // generation. We try the first two in parallel, and
517 // fall back to the third when necessary.
518
519 while(true) {
520
521 if (0 == method) {
522
523 // Method 1: list the directory and use the highest
524 // segments_N file. This method works well as long
525 // as there is no stale caching on the directory
526 // contents (NOTE: NFS clients often have such stale
527 // caching):
528 String[] files = null;
529
530 long genA = -1;
531
532 if (directory != null)
533 files = directory.list();
534 else
535 files = fileDirectory.list();
536
537 if (files != null)
538 genA = getCurrentSegmentGeneration(files);
539
540 message("directory listing genA=" + genA);
541
542 // Method 2: open segments.gen and read its
543 // contents. Then we take the larger of the two
544 // gen's. This way, if either approach is hitting
545 // a stale cache (NFS) we have a better chance of
546 // getting the right generation.
547 long genB = -1;
548 if (directory != null) {
549 for(int i=0;i<defaultGenFileRetryCount;i++) {
550 IndexInput genInput = null;
551 try {
552 genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);
553 } catch (FileNotFoundException e) {
554 message("segments.gen open: FileNotFoundException " + e);
555 break;
556 } catch (IOException e) {
557 message("segments.gen open: IOException " + e);
558 }
559
560 if (genInput != null) {
561 try {
562 int version = genInput.readInt();
563 if (version == FORMAT_LOCKLESS) {
564 long gen0 = genInput.readLong();
565 long gen1 = genInput.readLong();
566 message("fallback check: " + gen0 + "; " + gen1);
567 if (gen0 == gen1) {
568 // The file is consistent.
569 genB = gen0;
570 break;
571 }
572 }
573 } catch (IOException err2) {
574 // will retry
575 } finally {
576 genInput.close();
577 }
578 }
579 try {
580 Thread.sleep(defaultGenFileRetryPauseMsec);
581 } catch (InterruptedException e) {
582 // will retry
583 }
584 }
585 }
586
587 message(IndexFileNames.SEGMENTS_GEN + " check: genB=" + genB);
588
589 // Pick the larger of the two gen's:
590 if (genA > genB)
591 gen = genA;
592 else
593 gen = genB;
594
595 if (gen == -1) {
596 // Neither approach found a generation
597 String s;
598 if (files != null) {
599 s = "";
600 for(int i=0;i<files.length;i++)
601 s += " " + files[i];
602 } else
603 s = " null";
604 throw new FileNotFoundException("no segments* file found in " + directory + ": files:" + s);
605 }
606 }
607
608 // Third method (fallback if first & second methods
609 // are not reliable): since both directory cache and
610 // file contents cache seem to be stale, just
611 // advance the generation.
612 if (1 == method || (0 == method && lastGen == gen && retry)) {
613
614 method = 1;
615
616 if (genLookaheadCount < defaultGenLookaheadCount) {
617 gen++;
618 genLookaheadCount++;
619 message("look ahead increment gen to " + gen);
620 }
621 }
622
623 if (lastGen == gen) {
624
625 // This means we're about to try the same
626 // segments_N last tried. This is allowed,
627 // exactly once, because writer could have been in
628 // the process of writing segments_N last time.
629
630 if (retry) {
631 // OK, we've tried the same segments_N file
632 // twice in a row, so this must be a real
633 // error. We throw the original exception we
634 // got.
635 throw exc;
636 } else {
637 retry = true;
638 }
639
640 } else if (0 == method) {
641 // Segment file has advanced since our last loop, so
642 // reset retry:
643 retry = false;
644 }
645
646 lastGen = gen;
647
648 segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
649 "",
650 gen);
651
652 try {
653 Object v = doBody(segmentFileName);
654 if (exc != null) {
655 message("success on " + segmentFileName);
656 }
657 return v;
658 } catch (IOException err) {
659
660 // Save the original root cause:
661 if (exc == null) {
662 exc = err;
663 }
664
665 message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retry=" + retry + "; gen = " + gen);
666
667 if (!retry && gen > 1) {
668
669 // This is our first time trying this segments
670 // file (because retry is false), and, there is
671 // possibly a segments_(N-1) (because gen > 1).
672 // So, check if the segments_(N-1) exists and
673 // try it if so:
674 String prevSegmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
675 "",
676 gen-1);
677
678 final boolean prevExists;
679 if (directory != null)
680 prevExists = directory.fileExists(prevSegmentFileName);
681 else
682 prevExists = new File(fileDirectory, prevSegmentFileName).exists();
683
684 if (prevExists) {
685 message("fallback to prior segment file '" + prevSegmentFileName + "'");
686 try {
687 Object v = doBody(prevSegmentFileName);
688 if (exc != null) {
689 message("success on fallback " + prevSegmentFileName);
690 }
691 return v;
692 } catch (IOException err2) {
693 message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
694 }
695 }
696 }
697 }
698 }
699 }
700
701 /**
702 * Subclass must implement this. The assumption is an
703 * IOException will be thrown if something goes wrong
704 * during the processing that could have been caused by
705 * a writer committing.
706 */
707 protected abstract Object doBody(String segmentFileName) throws CorruptIndexException, IOException;
708 }
709
710 /**
711 * Returns a new SegmentInfos containg the SegmentInfo
712 * instances in the specified range first (inclusive) to
713 * last (exclusive), so total number of segments returned
714 * is last-first.
715 */
716 public SegmentInfos range(int first, int last) {
717 SegmentInfos infos = new SegmentInfos();
718 infos.addAll(super.subList(first, last));
719 return infos;
720 }
721
722 // Carry over generation numbers from another SegmentInfos
723 void updateGeneration(SegmentInfos other) {
724 lastGeneration = other.lastGeneration;
725 generation = other.generation;
726 version = other.version;
727 }
728
729 public final void rollbackCommit(Directory dir) throws IOException {
730 if (pendingOutput != null) {
731 try {
732 pendingOutput.close();
733 } catch (Throwable t) {
734 // Suppress so we keep throwing the original exception
735 // in our caller
736 }
737
738 // Must carefully compute fileName from "generation"
739 // since lastGeneration isn't incremented:
740 try {
741 final String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
742 "",
743 generation);
744 dir.deleteFile(segmentFileName);
745 } catch (Throwable t) {
746 // Suppress so we keep throwing the original exception
747 // in our caller
748 }
749 pendingOutput = null;
750 }
751 }
752
753 /** Call this to start a commit. This writes the new
754 * segments file, but writes an invalid checksum at the
755 * end, so that it is not visible to readers. Once this
756 * is called you must call {@link #finishCommit} to complete
757 * the commit or {@link #rollbackCommit} to abort it. */
758 public final void prepareCommit(Directory dir) throws IOException {
759 if (pendingOutput != null)
760 throw new IllegalStateException("prepareCommit was already called");
761 write(dir);
762 }
763
764 public final void finishCommit(Directory dir) throws IOException {
765 if (pendingOutput == null)
766 throw new IllegalStateException("prepareCommit was not called");
767 boolean success = false;
768 try {
769 pendingOutput.finishCommit();
770 pendingOutput.close();
771 pendingOutput = null;
772 success = true;
773 } finally {
774 if (!success)
775 rollbackCommit(dir);
776 }
777
778 // NOTE: if we crash here, we have left a segments_N
779 // file in the directory in a possibly corrupt state (if
780 // some bytes made it to stable storage and others
781 // didn't). But, the segments_N file includes checksum
782 // at the end, which should catch this case. So when a
783 // reader tries to read it, it will throw a
784 // CorruptIndexException, which should cause the retry
785 // logic in SegmentInfos to kick in and load the last
786 // good (previous) segments_N-1 file.
787
788 final String fileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
789 "",
790 generation);
791 success = false;
792 try {
793 dir.sync(fileName);
794 success = true;
795 } finally {
796 if (!success) {
797 try {
798 dir.deleteFile(fileName);
799 } catch (Throwable t) {
800 // Suppress so we keep throwing the original exception
801 }
802 }
803 }
804
805 lastGeneration = generation;
806
807 try {
808 IndexOutput genOutput = dir.createOutput(IndexFileNames.SEGMENTS_GEN);
809 try {
810 genOutput.writeInt(FORMAT_LOCKLESS);
811 genOutput.writeLong(generation);
812 genOutput.writeLong(generation);
813 } finally {
814 genOutput.close();
815 }
816 } catch (Throwable t) {
817 // It's OK if we fail to write this file since it's
818 // used only as one of the retry fallbacks.
819 }
820 }
821
822 /** Writes & syncs to the Directory dir, taking care to
823 * remove the segments file on exception */
824 public final void commit(Directory dir) throws IOException {
825 prepareCommit(dir);
826 finishCommit(dir);
827 }
828
829 synchronized String segString(Directory directory) {
830 StringBuffer buffer = new StringBuffer();
831 final int count = size();
832 for(int i = 0; i < count; i++) {
833 if (i > 0) {
834 buffer.append(' ');
835 }
836 final SegmentInfo info = info(i);
837 buffer.append(info.segString(directory));
838 if (info.dir != directory)
839 buffer.append("**");
840 }
841 return buffer.toString();
842 }
843 }