Lucene.Net  3.0.3
Lucene.Net is a .NET port of the Java Lucene Indexing Library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties
SegmentInfos.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.IO;
21 using Lucene.Net.Support;
22 using ChecksumIndexInput = Lucene.Net.Store.ChecksumIndexInput;
23 using ChecksumIndexOutput = Lucene.Net.Store.ChecksumIndexOutput;
24 using Directory = Lucene.Net.Store.Directory;
25 using IndexInput = Lucene.Net.Store.IndexInput;
26 using IndexOutput = Lucene.Net.Store.IndexOutput;
27 using NoSuchDirectoryException = Lucene.Net.Store.NoSuchDirectoryException;
28 
29 namespace Lucene.Net.Index
30 {
31 
38  [Serializable]
39  public sealed class SegmentInfos : List<SegmentInfo>, ICloneable
40  {
41  private class AnonymousClassFindSegmentsFile:FindSegmentsFile
42  {
43  private void InitBlock(SegmentInfos enclosingInstance)
44  {
45  this.enclosingInstance = enclosingInstance;
46  }
47  private SegmentInfos enclosingInstance;
48  public SegmentInfos Enclosing_Instance
49  {
50  get
51  {
52  return enclosingInstance;
53  }
54 
55  }
56  internal AnonymousClassFindSegmentsFile(SegmentInfos enclosingInstance, Lucene.Net.Store.Directory Param1):base(Param1)
57  {
58  InitBlock(enclosingInstance);
59  }
60 
61  public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
62  {
63  Enclosing_Instance.Read(directory, segmentFileName);
64  return null;
65  }
66  }
68  /* Works since counter, the old 1st entry, is always >= 0 */
69  public const int FORMAT = - 1;
70 
80  public const int FORMAT_LOCKLESS = - 2;
81 
86  public const int FORMAT_SINGLE_NORM_FILE = - 3;
87 
91  public const int FORMAT_SHARED_DOC_STORE = - 4;
92 
96  public const int FORMAT_CHECKSUM = - 5;
97 
101  public const int FORMAT_DEL_COUNT = - 6;
102 
107  public const int FORMAT_HAS_PROX = - 7;
108 
110  public const int FORMAT_USER_DATA = - 8;
111 
115  public const int FORMAT_DIAGNOSTICS = - 9;
116 
117  /* This must always point to the most recent file format. */
118  internal static readonly int CURRENT_FORMAT = FORMAT_DIAGNOSTICS;
119 
120  public int counter = 0; // used to name new segments
124  private long version = (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
125 
126  private long generation = 0; // generation of the "segments_N" for the next commit
127  private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read
128  // or wrote; this is normally the same as generation except if
129  // there was an IOException that had interrupted a commit
130 
131  private IDictionary<string, string> userData = new HashMap<string, string>(); // Opaque Map<String, String> that user can specify during IndexWriter.commit
132 
136  private static System.IO.StreamWriter infoStream;
137 
138  public SegmentInfo Info(int i)
139  {
140  return (SegmentInfo) this[i];
141  }
142 
149  public static long GetCurrentSegmentGeneration(System.String[] files)
150  {
151  if (files == null)
152  {
153  return - 1;
154  }
155  long max = - 1;
156  for (int i = 0; i < files.Length; i++)
157  {
158  System.String file = files[i];
159  if (file.StartsWith(IndexFileNames.SEGMENTS) && !file.Equals(IndexFileNames.SEGMENTS_GEN))
160  {
161  long gen = GenerationFromSegmentsFileName(file);
162  if (gen > max)
163  {
164  max = gen;
165  }
166  }
167  }
168  return max;
169  }
170 
177  public static long GetCurrentSegmentGeneration(Directory directory)
178  {
179  try
180  {
181  return GetCurrentSegmentGeneration(directory.ListAll());
182  }
184  {
185  return - 1;
186  }
187  }
188 
195 
196  public static System.String GetCurrentSegmentFileName(System.String[] files)
197  {
198  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", GetCurrentSegmentGeneration(files));
199  }
200 
207  public static System.String GetCurrentSegmentFileName(Directory directory)
208  {
209  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", GetCurrentSegmentGeneration(directory));
210  }
211 
213  public System.String GetCurrentSegmentFileName()
214  {
215  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", lastGeneration);
216  }
217 
221  public static long GenerationFromSegmentsFileName(System.String fileName)
222  {
223  if (fileName.Equals(IndexFileNames.SEGMENTS))
224  {
225  return 0;
226  }
227  else if (fileName.StartsWith(IndexFileNames.SEGMENTS))
228  {
229  return Number.ToInt64(fileName.Substring(1 + IndexFileNames.SEGMENTS.Length));
230  }
231  else
232  {
233  throw new System.ArgumentException("fileName \"" + fileName + "\" is not a segments file");
234  }
235  }
236 
237 
239  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
240  public System.String GetNextSegmentFileName()
241  {
242  long nextGeneration;
243 
244  if (generation == - 1)
245  {
246  nextGeneration = 1;
247  }
248  else
249  {
250  nextGeneration = generation + 1;
251  }
252  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", nextGeneration);
253  }
254 
265  public void Read(Directory directory, System.String segmentFileName)
266  {
267  bool success = false;
268 
269  // Clear any previous segments:
270  Clear();
271 
272  var input = new ChecksumIndexInput(directory.OpenInput(segmentFileName));
273 
274  generation = GenerationFromSegmentsFileName(segmentFileName);
275 
276  lastGeneration = generation;
277 
278  try
279  {
280  int format = input.ReadInt();
281  if (format < 0)
282  {
283  // file contains explicit format info
284  // check that it is a format we can understand
285  if (format < CURRENT_FORMAT)
286  throw new CorruptIndexException("Unknown format version: " + format);
287  version = input.ReadLong(); // read version
288  counter = input.ReadInt(); // read counter
289  }
290  else
291  {
292  // file is in old format without explicit format info
293  counter = format;
294  }
295 
296  for (int i = input.ReadInt(); i > 0; i--)
297  {
298  // read segmentInfos
299  Add(new SegmentInfo(directory, format, input));
300  }
301 
302  if (format >= 0)
303  {
304  // in old format the version number may be at the end of the file
305  if (input.FilePointer >= input.Length())
306  version = (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
307  // old file format without version number
308  else
309  version = input.ReadLong(); // read version
310  }
311 
312  if (format <= FORMAT_USER_DATA)
313  {
314  if (format <= FORMAT_DIAGNOSTICS)
315  {
316  userData = input.ReadStringStringMap();
317  }
318  else if (0 != input.ReadByte())
319  {
320  // TODO: Should be read-only map
321  userData = new HashMap<string,string> {{"userData", input.ReadString()}};
322  }
323  else
324  {
325  // TODO: Should be empty read-only map
326  userData = new HashMap<string, string>();
327  }
328  }
329  else
330  {
331  // TODO: Should be empty read-only map
332  userData = new HashMap<string, string>();
333  }
334 
335  if (format <= FORMAT_CHECKSUM)
336  {
337  long checksumNow = input.Checksum;
338  long checksumThen = input.ReadLong();
339  if (checksumNow != checksumThen)
340  throw new CorruptIndexException("checksum mismatch in segments file");
341  }
342  success = true;
343  }
344  finally
345  {
346  input.Close();
347  if (!success)
348  {
349  // Clear any segment infos we had loaded so we
350  // have a clean slate on retry:
351  Clear();
352  }
353  }
354  }
355 
361  public void Read(Directory directory)
362  {
363 
364  generation = lastGeneration = - 1;
365 
366  new AnonymousClassFindSegmentsFile(this, directory).Run();
367  }
368 
369  // Only non-null after prepareCommit has been called and
370  // before finishCommit is called
371  internal ChecksumIndexOutput pendingSegnOutput;
372 
373  private void Write(Directory directory)
374  {
375 
376  System.String segmentFileName = GetNextSegmentFileName();
377 
378  // Always advance the generation on write:
379  if (generation == - 1)
380  {
381  generation = 1;
382  }
383  else
384  {
385  generation++;
386  }
387 
388  var segnOutput = new ChecksumIndexOutput(directory.CreateOutput(segmentFileName));
389 
390  bool success = false;
391 
392  try
393  {
394  segnOutput.WriteInt(CURRENT_FORMAT); // write FORMAT
395  segnOutput.WriteLong(++version); // every write changes
396  // the index
397  segnOutput.WriteInt(counter); // write counter
398  segnOutput.WriteInt(Count); // write infos
399  for (int i = 0; i < Count; i++)
400  {
401  Info(i).Write(segnOutput);
402  }
403  segnOutput.WriteStringStringMap(userData);
404  segnOutput.PrepareCommit();
405  success = true;
406  pendingSegnOutput = segnOutput;
407  }
408  finally
409  {
410  if (!success)
411  {
412  // We hit an exception above; try to close the file
413  // but suppress any exception:
414  try
415  {
416  segnOutput.Close();
417  }
418  catch (System.Exception)
419  {
420  // Suppress so we keep throwing the original exception
421  }
422  try
423  {
424  // Try not to leave a truncated segments_N file in
425  // the index:
426  directory.DeleteFile(segmentFileName);
427  }
428  catch (System.Exception)
429  {
430  // Suppress so we keep throwing the original exception
431  }
432  }
433  }
434  }
435 
439 
440  public System.Object Clone()
441  {
442  SegmentInfos sis = new SegmentInfos();
443  for (int i = 0; i < this.Count; i++)
444  {
445  sis.Add((SegmentInfo)this[i].Clone());
446  }
447  sis.counter = this.counter;
448  sis.generation = this.generation;
449  sis.lastGeneration = this.lastGeneration;
450  // sis.pendingSegnOutput = this.pendingSegnOutput; // {{Aroush-2.9}} needed?
451  sis.userData = new HashMap<string, string>(userData);
452  sis.version = this.version;
453  return sis;
454  }
455 
457  public long Version
458  {
459  get { return version; }
460  }
461 
462  public long Generation
463  {
464  get { return generation; }
465  }
466 
467  public long LastGeneration
468  {
469  get { return lastGeneration; }
470  }
471 
475  public static long ReadCurrentVersion(Directory directory)
476  {
477  // Fully read the segments file: this ensures that it's
478  // completely written so that if
479  // IndexWriter.prepareCommit has been called (but not
480  // yet commit), then the reader will still see itself as
481  // current:
482  var sis = new SegmentInfos();
483  sis.Read(directory);
484  return sis.version;
485  //return (long) ((System.Int64) new AnonymousClassFindSegmentsFile1(directory).Run());
486  //DIGY: AnonymousClassFindSegmentsFile1 can safely be deleted
487  }
488 
492  public static System.Collections.Generic.IDictionary<string, string> ReadCurrentUserData(Directory directory)
493  {
494  var sis = new SegmentInfos();
495  sis.Read(directory);
496  return sis.UserData;
497  }
498 
502  public static void SetInfoStream(System.IO.StreamWriter infoStream)
503  {
504  SegmentInfos.infoStream = infoStream;
505  }
506 
507  /* Advanced configuration of retry logic in loading
508  segments_N file */
509  private static int defaultGenFileRetryCount = 10;
510  private static int defaultGenFileRetryPauseMsec = 50;
511  private static int defaultGenLookaheadCount = 10;
512 
518  public static int DefaultGenFileRetryCount
519  {
520  get { return defaultGenFileRetryCount; }
521  set { defaultGenFileRetryCount = value; }
522  }
523 
524  public static int DefaultGenFileRetryPauseMsec
525  {
526  set { defaultGenFileRetryPauseMsec = value; }
527  get { return defaultGenFileRetryPauseMsec; }
528  }
529 
536  public static int DefaultGenLookaheadCount
537  {
538  set { defaultGenLookaheadCount = value; }
539  get { return defaultGenLookaheadCount; }
540  }
541 
544  public static StreamWriter InfoStream
545  {
546  get { return infoStream; }
547  }
548 
549  private static void Message(System.String message)
550  {
551  if (infoStream != null)
552  {
553  infoStream.WriteLine("SIS [" + ThreadClass.Current().Name + "]: " + message);
554  }
555  }
556 
565  public abstract class FindSegmentsFile
566  {
567 
568  internal Directory directory;
569 
570  protected FindSegmentsFile(Directory directory)
571  {
572  this.directory = directory;
573  }
574 
575  public System.Object Run()
576  {
577  return Run(null);
578  }
579 
580  public System.Object Run(IndexCommit commit)
581  {
582  if (commit != null)
583  {
584  if (directory != commit.Directory)
585  throw new System.IO.IOException("the specified commit does not match the specified Directory");
586  return DoBody(commit.SegmentsFileName);
587  }
588 
589  System.String segmentFileName = null;
590  long lastGen = - 1;
591  long gen = 0;
592  int genLookaheadCount = 0;
593  System.IO.IOException exc = null;
594  bool retry = false;
595 
596  int method = 0;
597 
598  // Loop until we succeed in calling doBody() without
599  // hitting an IOException. An IOException most likely
600  // means a commit was in process and has finished, in
601  // the time it took us to load the now-old infos files
602  // (and segments files). It's also possible it's a
603  // true error (corrupt index). To distinguish these,
604  // on each retry we must see "forward progress" on
605  // which generation we are trying to load. If we
606  // don't, then the original error is real and we throw
607  // it.
608 
609  // We have three methods for determining the current
610  // generation. We try the first two in parallel, and
611  // fall back to the third when necessary.
612 
613  while (true)
614  {
615 
616  if (0 == method)
617  {
618 
619  // Method 1: list the directory and use the highest
620  // segments_N file. This method works well as long
621  // as there is no stale caching on the directory
622  // contents (NOTE: NFS clients often have such stale
623  // caching):
624  System.String[] files = null;
625 
626  long genA = - 1;
627 
628  files = directory.ListAll();
629 
630  if (files != null)
631  genA = Lucene.Net.Index.SegmentInfos.GetCurrentSegmentGeneration(files);
632 
633  Lucene.Net.Index.SegmentInfos.Message("directory listing genA=" + genA);
634 
635  // Method 2: open segments.gen and read its
636  // contents. Then we take the larger of the two
637  // gens. This way, if either approach is hitting
638  // a stale cache (NFS) we have a better chance of
639  // getting the right generation.
640  long genB = - 1;
641  for (int i = 0; i < Lucene.Net.Index.SegmentInfos.defaultGenFileRetryCount; i++)
642  {
643  IndexInput genInput = null;
644  try
645  {
646  genInput = directory.OpenInput(IndexFileNames.SEGMENTS_GEN);
647  }
648  catch (System.IO.FileNotFoundException e)
649  {
650  Lucene.Net.Index.SegmentInfos.Message("segments.gen open: FileNotFoundException " + e);
651  break;
652  }
653  catch (System.IO.IOException e)
654  {
655  Lucene.Net.Index.SegmentInfos.Message("segments.gen open: IOException " + e);
656  }
657 
658  if (genInput != null)
659  {
660  try
661  {
662  int version = genInput.ReadInt();
663  if (version == Lucene.Net.Index.SegmentInfos.FORMAT_LOCKLESS)
664  {
665  long gen0 = genInput.ReadLong();
666  long gen1 = genInput.ReadLong();
667  Lucene.Net.Index.SegmentInfos.Message("fallback check: " + gen0 + "; " + gen1);
668  if (gen0 == gen1)
669  {
670  // The file is consistent.
671  genB = gen0;
672  break;
673  }
674  }
675  }
676  catch (System.IO.IOException)
677  {
678  // will retry
679  }
680  finally
681  {
682  genInput.Close();
683  }
684  }
685 
686  System.Threading.Thread.Sleep(new TimeSpan((System.Int64) 10000 * Lucene.Net.Index.SegmentInfos.defaultGenFileRetryPauseMsec));
687 
688 
689  }
690 
691  Lucene.Net.Index.SegmentInfos.Message(IndexFileNames.SEGMENTS_GEN + " check: genB=" + genB);
692 
693  // Pick the larger of the two gen's:
694  if (genA > genB)
695  gen = genA;
696  else
697  gen = genB;
698 
699  if (gen == - 1)
700  {
701  throw new System.IO.FileNotFoundException("no segments* file found in " + directory + ": files:" + string.Join(" ", files));
702  }
703  }
704 
705  // Third method (fallback if first & second methods
706  // are not reliable): since both directory cache and
707  // file contents cache seem to be stale, just
708  // advance the generation.
709  if (1 == method || (0 == method && lastGen == gen && retry))
710  {
711 
712  method = 1;
713 
714  if (genLookaheadCount < Lucene.Net.Index.SegmentInfos.defaultGenLookaheadCount)
715  {
716  gen++;
717  genLookaheadCount++;
718  Lucene.Net.Index.SegmentInfos.Message("look ahead increment gen to " + gen);
719  }
720  }
721 
722  if (lastGen == gen)
723  {
724 
725  // This means we're about to try the same
726  // segments_N last tried. This is allowed,
727  // exactly once, because writer could have been in
728  // the process of writing segments_N last time.
729 
730  if (retry)
731  {
732  // OK, we've tried the same segments_N file
733  // twice in a row, so this must be a real
734  // error. We throw the original exception we
735  // got.
736  throw exc;
737  }
738 
739  retry = true;
740  }
741  else if (0 == method)
742  {
743  // Segment file has advanced since our last loop, so
744  // reset retry:
745  retry = false;
746  }
747 
748  lastGen = gen;
749 
750  segmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen);
751 
752  try
753  {
754  System.Object v = DoBody(segmentFileName);
755  Lucene.Net.Index.SegmentInfos.Message("success on " + segmentFileName);
756 
757  return v;
758  }
759  catch (System.IO.IOException err)
760  {
761 
762  // Save the original root cause:
763  if (exc == null)
764  {
765  exc = err;
766  }
767 
768  Lucene.Net.Index.SegmentInfos.Message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retry=" + retry + "; gen = " + gen);
769 
770  if (!retry && gen > 1)
771  {
772 
773  // This is our first time trying this segments
774  // file (because retry is false), and, there is
775  // possibly a segments_(N-1) (because gen > 1).
776  // So, check if the segments_(N-1) exists and
777  // try it if so:
778  System.String prevSegmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen - 1);
779 
780  bool prevExists;
781  prevExists = directory.FileExists(prevSegmentFileName);
782 
783  if (prevExists)
784  {
785  Lucene.Net.Index.SegmentInfos.Message("fallback to prior segment file '" + prevSegmentFileName + "'");
786  try
787  {
788  System.Object v = DoBody(prevSegmentFileName);
789  if (exc != null)
790  {
791  Lucene.Net.Index.SegmentInfos.Message("success on fallback " + prevSegmentFileName);
792  }
793  return v;
794  }
795  catch (System.IO.IOException err2)
796  {
797  Lucene.Net.Index.SegmentInfos.Message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
798  }
799  }
800  }
801  }
802  }
803  }
804 
810  public /*internal*/ abstract System.Object DoBody(System.String segmentFileName);
811  }
812 
818  public SegmentInfos Range(int first, int last)
819  {
820  SegmentInfos infos = new SegmentInfos();
821  infos.AddRange(this.GetRange(first, last - first));
822  return infos;
823  }
824 
825  // Carry over generation numbers from another SegmentInfos
826  internal void UpdateGeneration(SegmentInfos other)
827  {
828  lastGeneration = other.lastGeneration;
829  generation = other.generation;
830  version = other.version;
831  }
832 
833  internal void RollbackCommit(Directory dir)
834  {
835  if (pendingSegnOutput != null)
836  {
837  try
838  {
839  pendingSegnOutput.Close();
840  }
841  catch (System.Exception)
842  {
843  // Suppress so we keep throwing the original exception
844  // in our caller
845  }
846 
847  // Must carefully compute fileName from "generation"
848  // since lastGeneration isn't incremented:
849  try
850  {
851  System.String segmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", generation);
852  dir.DeleteFile(segmentFileName);
853  }
854  catch (System.Exception)
855  {
856  // Suppress so we keep throwing the original exception
857  // in our caller
858  }
859  pendingSegnOutput = null;
860  }
861  }
862 
869  internal void PrepareCommit(Directory dir)
870  {
871  if (pendingSegnOutput != null)
872  throw new System.SystemException("prepareCommit was already called");
873  Write(dir);
874  }
875 
882  public System.Collections.Generic.ICollection<string> Files(Directory dir, bool includeSegmentsFile)
883  {
884  System.Collections.Generic.HashSet<string> files = new System.Collections.Generic.HashSet<string>();
885  if (includeSegmentsFile)
886  {
887  files.Add(GetCurrentSegmentFileName());
888  }
889  int size = Count;
890  for (int i = 0; i < size; i++)
891  {
892  SegmentInfo info = Info(i);
893  if (info.dir == dir)
894  {
895  files.UnionWith(Info(i).Files());
896  }
897  }
898  return files;
899  }
900 
901  internal void FinishCommit(Directory dir)
902  {
903  if (pendingSegnOutput == null)
904  throw new System.SystemException("prepareCommit was not called");
905  bool success = false;
906  try
907  {
908  pendingSegnOutput.FinishCommit();
909  pendingSegnOutput.Close();
910  pendingSegnOutput = null;
911  success = true;
912  }
913  finally
914  {
915  if (!success)
916  RollbackCommit(dir);
917  }
918 
919  // NOTE: if we crash here, we have left a segments_N
920  // file in the directory in a possibly corrupt state (if
921  // some bytes made it to stable storage and others
922  // didn't). But, the segments_N file includes checksum
923  // at the end, which should catch this case. So when a
924  // reader tries to read it, it will throw a
925  // CorruptIndexException, which should cause the retry
926  // logic in SegmentInfos to kick in and load the last
927  // good (previous) segments_N-1 file.
928 
929  System.String fileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", generation);
930  success = false;
931  try
932  {
933  dir.Sync(fileName);
934  success = true;
935  }
936  finally
937  {
938  if (!success)
939  {
940  try
941  {
942  dir.DeleteFile(fileName);
943  }
944  catch (System.Exception)
945  {
946  // Suppress so we keep throwing the original exception
947  }
948  }
949  }
950 
951  lastGeneration = generation;
952 
953  try
954  {
955  IndexOutput genOutput = dir.CreateOutput(IndexFileNames.SEGMENTS_GEN);
956  try
957  {
958  genOutput.WriteInt(FORMAT_LOCKLESS);
959  genOutput.WriteLong(generation);
960  genOutput.WriteLong(generation);
961  }
962  finally
963  {
964  genOutput.Close();
965  }
966  }
967  catch (System.Exception)
968  {
969  // It's OK if we fail to write this file since it's
970  // used only as one of the retry fallbacks.
971  }
972  }
973 
977  public /*internal*/ void Commit(Directory dir)
978  {
979  PrepareCommit(dir);
980  FinishCommit(dir);
981  }
982 
983  public System.String SegString(Directory directory)
984  {
985  lock (this)
986  {
987  var buffer = new System.Text.StringBuilder();
988  int count = Count;
989  for (int i = 0; i < count; i++)
990  {
991  if (i > 0)
992  {
993  buffer.Append(' ');
994  }
995  SegmentInfo info = Info(i);
996  buffer.Append(info.SegString(directory));
997  if (info.dir != directory)
998  buffer.Append("**");
999  }
1000  return buffer.ToString();
1001  }
1002  }
1003 
1004  public IDictionary<string, string> UserData
1005  {
1006  get { return userData; }
1007  internal set {
1008  userData = value ?? new HashMap<string, string>();
1009  }
1010  }
1011 
1016  internal void Replace(SegmentInfos other)
1017  {
1018  Clear();
1019  AddRange(other);
1020  lastGeneration = other.lastGeneration;
1021  }
1022 
1023  // Used only for testing
1024  public bool HasExternalSegments(Directory dir)
1025  {
1026  int numSegments = Count;
1027  for (int i = 0; i < numSegments; i++)
1028  if (Info(i).dir != dir)
1029  return true;
1030  return false;
1031  }
1032 
1033  #region Lucene.NET (Equals & GetHashCode )
1034 
1035 
1036 
1037 
1038 
1039 
1040  public override bool Equals(object obj)
1041  {
1042  if (obj == null) return false;
1043 
1044  var objToCompare = obj as SegmentInfos;
1045  if (objToCompare == null) return false;
1046 
1047  if (this.Count != objToCompare.Count) return false;
1048 
1049  for (int idx = 0; idx < this.Count; idx++)
1050  {
1051  if (!this[idx].Equals(objToCompare[idx])) return false;
1052  }
1053 
1054  return true;
1055  }
1056 
1061  public override int GetHashCode()
1062  {
1063  int h = 1;
1064  for (int i = 0; i < this.Count; i++)
1065  {
1066  SegmentInfo si = (this[i] as SegmentInfo);
1067  h = 31 * h + (si == null ? 0 : si.GetHashCode());
1068  }
1069 
1070  return h;
1071  }
1072  #endregion
1073  }
1074 }