Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
DirectoryReader.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.Linq;
21 using Lucene.Net.Support;
22 using Document = Lucene.Net.Documents.Document;
23 using FieldSelector = Lucene.Net.Documents.FieldSelector;
24 using Directory = Lucene.Net.Store.Directory;
25 using Lock = Lucene.Net.Store.Lock;
26 using LockObtainFailedException = Lucene.Net.Store.LockObtainFailedException;
27 using DefaultSimilarity = Lucene.Net.Search.DefaultSimilarity;
28 
29 namespace Lucene.Net.Index
30 {
31 
32  /// <summary> An IndexReader which reads indexes with multiple segments.</summary>
34  {
35  /*new*/ private class AnonymousClassFindSegmentsFile:SegmentInfos.FindSegmentsFile
36  {
37  private void InitBlock(bool readOnly, IndexDeletionPolicy deletionPolicy, int termInfosIndexDivisor)
38  {
39  this.readOnly = readOnly;
40  this.deletionPolicy = deletionPolicy;
41  this.termInfosIndexDivisor = termInfosIndexDivisor;
42  }
43  private bool readOnly;
44  private IndexDeletionPolicy deletionPolicy;
45  private int termInfosIndexDivisor;
46  internal AnonymousClassFindSegmentsFile(bool readOnly, Lucene.Net.Index.IndexDeletionPolicy deletionPolicy, int termInfosIndexDivisor, Lucene.Net.Store.Directory Param1):base(Param1)
47  {
48  InitBlock(readOnly, deletionPolicy, termInfosIndexDivisor);
49  }
50  public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
51  {
52  var infos = new SegmentInfos();
53  infos.Read(directory, segmentFileName);
54  if (readOnly)
55  return new ReadOnlyDirectoryReader(directory, infos, deletionPolicy, termInfosIndexDivisor);
56  else
57  return new DirectoryReader(directory, infos, deletionPolicy, false, termInfosIndexDivisor);
58  }
59  }
60  private class AnonymousClassFindSegmentsFile1:SegmentInfos.FindSegmentsFile
61  {
62  private void InitBlock(bool openReadOnly, DirectoryReader enclosingInstance)
63  {
64  this.openReadOnly = openReadOnly;
65  this.enclosingInstance = enclosingInstance;
66  }
67  private bool openReadOnly;
68  private DirectoryReader enclosingInstance;
69  public DirectoryReader Enclosing_Instance
70  {
71  get
72  {
73  return enclosingInstance;
74  }
75 
76  }
77  internal AnonymousClassFindSegmentsFile1(bool openReadOnly, DirectoryReader enclosingInstance, Lucene.Net.Store.Directory Param1):base(Param1)
78  {
79  InitBlock(openReadOnly, enclosingInstance);
80  }
81  public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
82  {
83  var infos = new SegmentInfos();
84  infos.Read(directory, segmentFileName);
85  return Enclosing_Instance.DoReopen(infos, false, openReadOnly);
86  }
87  }
88  protected internal Directory internalDirectory;
89  protected internal bool readOnly;
90 
91  internal IndexWriter writer;
92 
93  private IndexDeletionPolicy deletionPolicy;
94  private readonly HashSet<string> synced = new HashSet<string>();
95  private Lock writeLock;
96  private readonly SegmentInfos segmentInfos;
97  private readonly SegmentInfos segmentInfosStart;
98  private bool stale;
99  private readonly int termInfosIndexDivisor;
100 
101  private bool rollbackHasChanges;
102 
103  private SegmentReader[] subReaders;
104  private int[] starts; // 1st docno for each segment
105  private System.Collections.Generic.IDictionary<string, byte[]> normsCache = new HashMap<string, byte[]>();
106  private int maxDoc = 0;
107  private int numDocs = - 1;
108  private bool hasDeletions = false;
109 
110  // Max version in index as of when we opened; this can be
111  // > our current segmentInfos version in case we were
112  // opened on a past IndexCommit:
113  private long maxIndexVersion;
114 
115  internal static IndexReader Open(Directory directory, IndexDeletionPolicy deletionPolicy, IndexCommit commit, bool readOnly, int termInfosIndexDivisor)
116  {
117  return (IndexReader) new AnonymousClassFindSegmentsFile(readOnly, deletionPolicy, termInfosIndexDivisor, directory).Run(commit);
118  }
119 
120  /// <summary>Construct reading the named set of readers. </summary>
121  internal DirectoryReader(Directory directory, SegmentInfos sis, IndexDeletionPolicy deletionPolicy, bool readOnly, int termInfosIndexDivisor)
122  {
123  internalDirectory = directory;
124  this.readOnly = readOnly;
125  this.segmentInfos = sis;
126  this.deletionPolicy = deletionPolicy;
127  this.termInfosIndexDivisor = termInfosIndexDivisor;
128 
129  if (!readOnly)
130  {
131  // We assume that this segments_N was previously
132  // properly sync'd:
133  synced.UnionWith(sis.Files(directory, true));
134  }
135 
136  // To reduce the chance of hitting FileNotFound
137  // (and having to retry), we open segments in
138  // reverse because IndexWriter merges & deletes
139  // the newest segments first.
140 
141  var readers = new SegmentReader[sis.Count];
142  for (int i = sis.Count - 1; i >= 0; i--)
143  {
144  bool success = false;
145  try
146  {
147  readers[i] = SegmentReader.Get(readOnly, sis.Info(i), termInfosIndexDivisor);
148  success = true;
149  }
150  finally
151  {
152  if (!success)
153  {
154  // Close all readers we had opened:
155  for (i++; i < sis.Count; i++)
156  {
157  try
158  {
159  readers[i].Close();
160  }
161  catch (System.Exception)
162  {
163  // keep going - we want to clean up as much as possible
164  }
165  }
166  }
167  }
168  }
169 
170  Initialize(readers);
171  }
172 
173  // Used by near real-time search
174  internal DirectoryReader(IndexWriter writer, SegmentInfos infos, int termInfosIndexDivisor)
175  {
176  this.internalDirectory = writer.Directory;
177  this.readOnly = true;
178  segmentInfos = infos;
179  segmentInfosStart = (SegmentInfos) infos.Clone();
180  this.termInfosIndexDivisor = termInfosIndexDivisor;
181  if (!readOnly)
182  {
183  // We assume that this segments_N was previously
184  // properly sync'd:
185  synced.UnionWith(infos.Files(internalDirectory, true));
186  }
187 
188  // IndexWriter synchronizes externally before calling
189  // us, which ensures infos will not change; so there's
190  // no need to process segments in reverse order
191  int numSegments = infos.Count;
192  var readers = new SegmentReader[numSegments];
193  Directory dir = writer.Directory;
194  int upto = 0;
195 
196  for (int i = 0; i < numSegments; i++)
197  {
198  bool success = false;
199  try
200  {
201  SegmentInfo info = infos.Info(i);
202  if (info.dir == dir)
203  {
204  readers[upto++] = writer.readerPool.GetReadOnlyClone(info, true, termInfosIndexDivisor);
205  }
206  success = true;
207  }
208  finally
209  {
210  if (!success)
211  {
212  // Close all readers we had opened:
213  for (upto--; upto >= 0; upto--)
214  {
215  try
216  {
217  readers[upto].Close();
218  }
219  catch (System.Exception)
220  {
221  // keep going - we want to clean up as much as possible
222  }
223  }
224  }
225  }
226  }
227 
228  this.writer = writer;
229 
230  if (upto < readers.Length)
231  {
232  // This means some segments were in a foreign Directory
233  var newReaders = new SegmentReader[upto];
234  Array.Copy(readers, 0, newReaders, 0, upto);
235  readers = newReaders;
236  }
237 
238  Initialize(readers);
239  }
240 
241  /// <summary>This constructor is only used for <see cref="Reopen()" /> </summary>
242  internal DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
243  IEnumerable<KeyValuePair<string, byte[]>> oldNormsCache, bool readOnly, bool doClone, int termInfosIndexDivisor)
244  {
245  this.internalDirectory = directory;
246  this.readOnly = readOnly;
247  this.segmentInfos = infos;
248  this.termInfosIndexDivisor = termInfosIndexDivisor;
249  if (!readOnly)
250  {
251  // We assume that this segments_N was previously
252  // properly sync'd:
253  synced.UnionWith(infos.Files(directory, true));
254  }
255 
256  // we put the old SegmentReaders in a map, that allows us
257  // to lookup a reader using its segment name
258  IDictionary<string, int> segmentReaders = new HashMap<string, int>();
259 
260  if (oldReaders != null)
261  {
262  // create a Map SegmentName->SegmentReader
263  for (int i = 0; i < oldReaders.Length; i++)
264  {
265  segmentReaders[oldReaders[i].SegmentName] = i;
266  }
267  }
268 
269  var newReaders = new SegmentReader[infos.Count];
270 
271  // remember which readers are shared between the old and the re-opened
272  // DirectoryReader - we have to incRef those readers
273  var readerShared = new bool[infos.Count];
274 
275  for (int i = infos.Count - 1; i >= 0; i--)
276  {
277  // find SegmentReader for this segment
278  if (!segmentReaders.ContainsKey(infos.Info(i).name))
279  {
280  // this is a new segment, no old SegmentReader can be reused
281  newReaders[i] = null;
282  }
283  else
284  {
285  // there is an old reader for this segment - we'll try to reopen it
286  newReaders[i] = oldReaders[segmentReaders[infos.Info(i).name]];
287  }
288 
289  bool success = false;
290  try
291  {
292  SegmentReader newReader;
293  if (newReaders[i] == null || infos.Info(i).GetUseCompoundFile() != newReaders[i].SegmentInfo.GetUseCompoundFile())
294  {
295 
296  // We should never see a totally new segment during cloning
297  System.Diagnostics.Debug.Assert(!doClone);
298 
299  // this is a new reader; in case we hit an exception we can close it safely
300  newReader = SegmentReader.Get(readOnly, infos.Info(i), termInfosIndexDivisor);
301  }
302  else
303  {
304  newReader = newReaders[i].ReopenSegment(infos.Info(i), doClone, readOnly);
305  }
306  if (newReader == newReaders[i])
307  {
308  // this reader will be shared between the old and the new one,
309  // so we must incRef it
310  readerShared[i] = true;
311  newReader.IncRef();
312  }
313  else
314  {
315  readerShared[i] = false;
316  newReaders[i] = newReader;
317  }
318  success = true;
319  }
320  finally
321  {
322  if (!success)
323  {
324  for (i++; i < infos.Count; i++)
325  {
326  if (newReaders[i] != null)
327  {
328  try
329  {
330  if (!readerShared[i])
331  {
332  // this is a new subReader that is not used by the old one,
333  // we can close it
334  newReaders[i].Close();
335  }
336  else
337  {
338  // this subReader is also used by the old reader, so instead
339  // closing we must decRef it
340  newReaders[i].DecRef();
341  }
342  }
343  catch (System.IO.IOException)
344  {
345  // keep going - we want to clean up as much as possible
346  }
347  }
348  }
349  }
350  }
351  }
352 
353  // initialize the readers to calculate maxDoc before we try to reuse the old normsCache
354  Initialize(newReaders);
355 
356  // try to copy unchanged norms from the old normsCache to the new one
357  if (oldNormsCache != null)
358  {
359  foreach(var entry in oldNormsCache)
360  {
361  String field = entry.Key;
362  if (!HasNorms(field))
363  {
364  continue;
365  }
366 
367  byte[] oldBytes = entry.Value;
368 
369  var bytes = new byte[MaxDoc];
370 
371  for (int i = 0; i < subReaders.Length; i++)
372  {
373  int oldReaderIndex = segmentReaders[subReaders[i].SegmentName];
374 
375  // this SegmentReader was not re-opened, we can copy all of its norms
376  if (segmentReaders.ContainsKey(subReaders[i].SegmentName) &&
377  (oldReaders[oldReaderIndex] == subReaders[i]
378  || oldReaders[oldReaderIndex].norms[field] == subReaders[i].norms[field]))
379  {
380  // we don't have to synchronize here: either this constructor is called from a SegmentReader,
381  // in which case no old norms cache is present, or it is called from MultiReader.reopen(),
382  // which is synchronized
383  Array.Copy(oldBytes, oldStarts[oldReaderIndex], bytes, starts[i], starts[i + 1] - starts[i]);
384  }
385  else
386  {
387  subReaders[i].Norms(field, bytes, starts[i]);
388  }
389  }
390 
391  normsCache[field] = bytes; // update cache
392  }
393  }
394  }
395 
396  private void Initialize(SegmentReader[] subReaders)
397  {
398  this.subReaders = subReaders;
399  starts = new int[subReaders.Length + 1]; // build starts array
400  for (int i = 0; i < subReaders.Length; i++)
401  {
402  starts[i] = maxDoc;
403  maxDoc += subReaders[i].MaxDoc; // compute maxDocs
404 
405  if (subReaders[i].HasDeletions)
406  hasDeletions = true;
407  }
408  starts[subReaders.Length] = maxDoc;
409 
410  if (!readOnly)
411  {
412  maxIndexVersion = SegmentInfos.ReadCurrentVersion(internalDirectory);
413  }
414  }
415 
416  public override Object Clone()
417  {
418  lock (this)
419  {
420  try
421  {
422  return Clone(readOnly); // Preserve current readOnly
423  }
424  catch (Exception ex)
425  {
426  throw new SystemException(ex.Message, ex); // TODO: why rethrow this way?
427  }
428  }
429  }
430 
431  public override IndexReader Clone(bool openReadOnly)
432  {
433  lock (this)
434  {
435  DirectoryReader newReader = DoReopen((SegmentInfos) segmentInfos.Clone(), true, openReadOnly);
436 
437  if (this != newReader)
438  {
439  newReader.deletionPolicy = deletionPolicy;
440  }
441  newReader.writer = writer;
442  // If we're cloning a non-readOnly reader, move the
443  // writeLock (if there is one) to the new reader:
444  if (!openReadOnly && writeLock != null)
445  {
446  // In near real-time search, reader is always readonly
447  System.Diagnostics.Debug.Assert(writer == null);
448  newReader.writeLock = writeLock;
449  newReader.hasChanges = hasChanges;
450  newReader.hasDeletions = hasDeletions;
451  writeLock = null;
452  hasChanges = false;
453  }
454 
455  return newReader;
456  }
457  }
458 
459  public override IndexReader Reopen()
460  {
461  // Preserve current readOnly
462  return DoReopen(readOnly, null);
463  }
464 
465  public override IndexReader Reopen(bool openReadOnly)
466  {
467  return DoReopen(openReadOnly, null);
468  }
469 
470  public override IndexReader Reopen(IndexCommit commit)
471  {
472  return DoReopen(true, commit);
473  }
474 
475  private IndexReader DoReopenFromWriter(bool openReadOnly, IndexCommit commit)
476  {
477  System.Diagnostics.Debug.Assert(readOnly);
478 
479  if (!openReadOnly)
480  {
481  throw new System.ArgumentException("a reader obtained from IndexWriter.getReader() can only be reopened with openReadOnly=true (got false)");
482  }
483 
484  if (commit != null)
485  {
486  throw new System.ArgumentException("a reader obtained from IndexWriter.getReader() cannot currently accept a commit");
487  }
488 
489  // TODO: right now we *always* make a new reader; in
490  // the future we could have write make some effort to
491  // detect that no changes have occurred
492  return writer.GetReader();
493  }
494 
495  internal virtual IndexReader DoReopen(bool openReadOnly, IndexCommit commit)
496  {
497  EnsureOpen();
498 
499  System.Diagnostics.Debug.Assert(commit == null || openReadOnly);
500 
501  // If we were obtained by writer.getReader(), re-ask the
502  // writer to get a new reader.
503  if (writer != null)
504  {
505  return DoReopenFromWriter(openReadOnly, commit);
506  }
507  else
508  {
509  return DoReopenNoWriter(openReadOnly, commit);
510  }
511  }
512 
513  private IndexReader DoReopenNoWriter(bool openReadOnly, IndexCommit commit)
514  {
515  lock (this)
516  {
517  if (commit == null)
518  {
519  if (hasChanges)
520  {
521  // We have changes, which means we are not readOnly:
522  System.Diagnostics.Debug.Assert(readOnly == false);
523  // and we hold the write lock:
524  System.Diagnostics.Debug.Assert(writeLock != null);
525  // so no other writer holds the write lock, which
526  // means no changes could have been done to the index:
527  System.Diagnostics.Debug.Assert(IsCurrent());
528 
529  if (openReadOnly)
530  {
531  return Clone(openReadOnly);
532  }
533  else
534  {
535  return this;
536  }
537  }
538  else if (IsCurrent())
539  {
540  if (openReadOnly != readOnly)
541  {
542  // Just fallback to clone
543  return Clone(openReadOnly);
544  }
545  else
546  {
547  return this;
548  }
549  }
550  }
551  else
552  {
553  if (internalDirectory != commit.Directory)
554  throw new System.IO.IOException("the specified commit does not match the specified Directory");
555  if (segmentInfos != null && commit.SegmentsFileName.Equals(segmentInfos.GetCurrentSegmentFileName()))
556  {
557  if (readOnly != openReadOnly)
558  {
559  // Just fallback to clone
560  return Clone(openReadOnly);
561  }
562  else
563  {
564  return this;
565  }
566  }
567  }
568 
569  return (IndexReader)new AnonymousFindSegmentsFile(internalDirectory, openReadOnly, this).Run(commit);
570  }
571  }
572 
573  class AnonymousFindSegmentsFile : SegmentInfos.FindSegmentsFile
574  {
575  readonly DirectoryReader enclosingInstance;
576  readonly bool openReadOnly;
577  readonly Directory dir;
578  public AnonymousFindSegmentsFile(Directory directory, bool openReadOnly, DirectoryReader dirReader) : base(directory)
579  {
580  this.dir = directory;
581  this.openReadOnly = openReadOnly;
582  enclosingInstance = dirReader;
583  }
584 
585  public override object DoBody(string segmentFileName)
586  {
587  var infos = new SegmentInfos();
588  infos.Read(dir, segmentFileName);
589  return enclosingInstance.DoReopen(infos, false, openReadOnly);
590  }
591  }
592 
593  private DirectoryReader DoReopen(SegmentInfos infos, bool doClone, bool openReadOnly)
594  {
595  lock (this)
596  {
597  DirectoryReader reader;
598  if (openReadOnly)
599  {
600  reader = new ReadOnlyDirectoryReader(internalDirectory, infos, subReaders, starts, normsCache, doClone, termInfosIndexDivisor);
601  }
602  else
603  {
604  reader = new DirectoryReader(internalDirectory, infos, subReaders, starts, normsCache, false, doClone, termInfosIndexDivisor);
605  }
606  return reader;
607  }
608  }
609 
610 
611  /// <summary>Version number when this IndexReader was opened. </summary>
612  public override long Version
613  {
614  get
615  {
616  EnsureOpen();
617  return segmentInfos.Version;
618  }
619  }
620 
621  public override ITermFreqVector[] GetTermFreqVectors(int n)
622  {
623  EnsureOpen();
624  int i = ReaderIndex(n); // find segment num
625  return subReaders[i].GetTermFreqVectors(n - starts[i]); // dispatch to segment
626  }
627 
628  public override ITermFreqVector GetTermFreqVector(int n, System.String field)
629  {
630  EnsureOpen();
631  int i = ReaderIndex(n); // find segment num
632  return subReaders[i].GetTermFreqVector(n - starts[i], field);
633  }
634 
635 
636  public override void GetTermFreqVector(int docNumber, System.String field, TermVectorMapper mapper)
637  {
638  EnsureOpen();
639  int i = ReaderIndex(docNumber); // find segment num
640  subReaders[i].GetTermFreqVector(docNumber - starts[i], field, mapper);
641  }
642 
643  public override void GetTermFreqVector(int docNumber, TermVectorMapper mapper)
644  {
645  EnsureOpen();
646  int i = ReaderIndex(docNumber); // find segment num
647  subReaders[i].GetTermFreqVector(docNumber - starts[i], mapper);
648  }
649 
650  /// <summary> Checks is the index is optimized (if it has a single segment and no deletions)</summary>
651  /// <returns> &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; if the index is optimized; &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt; otherwise </returns>
652  public override bool IsOptimized()
653  {
654  EnsureOpen();
655  return segmentInfos.Count == 1 && !HasDeletions;
656  }
657 
658  public override int NumDocs()
659  {
660  // Don't call ensureOpen() here (it could affect performance)
661  // NOTE: multiple threads may wind up init'ing
662  // numDocs... but that's harmless
663  if (numDocs == - 1)
664  {
665  // check cache
666  int n = subReaders.Sum(t => t.NumDocs()); // cache miss--recompute
667  numDocs = n;
668  }
669  return numDocs;
670  }
671 
672  public override int MaxDoc
673  {
674  get
675  {
676  // Don't call ensureOpen() here (it could affect performance)
677  return maxDoc;
678  }
679  }
680 
681  // inherit javadoc
682  public override Document Document(int n, FieldSelector fieldSelector)
683  {
684  EnsureOpen();
685  int i = ReaderIndex(n); // find segment num
686  return subReaders[i].Document(n - starts[i], fieldSelector); // dispatch to segment reader
687  }
688 
689  public override bool IsDeleted(int n)
690  {
691  // Don't call ensureOpen() here (it could affect performance)
692  int i = ReaderIndex(n); // find segment num
693  return subReaders[i].IsDeleted(n - starts[i]); // dispatch to segment reader
694  }
695 
696  public override bool HasDeletions
697  {
698  get
699  {
700  // Don't call ensureOpen() here (it could affect performance)
701  return hasDeletions;
702  }
703  }
704 
705  protected internal override void DoDelete(int n)
706  {
707  numDocs = - 1; // invalidate cache
708  int i = ReaderIndex(n); // find segment num
709  subReaders[i].DeleteDocument(n - starts[i]); // dispatch to segment reader
710  hasDeletions = true;
711  }
712 
713  protected internal override void DoUndeleteAll()
714  {
715  foreach (SegmentReader t in subReaders)
716  t.UndeleteAll();
717 
718  hasDeletions = false;
719  numDocs = - 1; // invalidate cache
720  }
721 
722  private int ReaderIndex(int n)
723  {
724  // find reader for doc n:
725  return ReaderIndex(n, this.starts, this.subReaders.Length);
726  }
727 
728  internal static int ReaderIndex(int n, int[] starts, int numSubReaders)
729  {
730  // find reader for doc n:
731  int lo = 0; // search starts array
732  int hi = numSubReaders - 1; // for first element less
733 
734  while (hi >= lo)
735  {
736  int mid = Number.URShift((lo + hi), 1);
737  int midValue = starts[mid];
738  if (n < midValue)
739  hi = mid - 1;
740  else if (n > midValue)
741  lo = mid + 1;
742  else
743  {
744  // found a match
745  while (mid + 1 < numSubReaders && starts[mid + 1] == midValue)
746  {
747  mid++; // scan to last match
748  }
749  return mid;
750  }
751  }
752  return hi;
753  }
754 
755  public override bool HasNorms(System.String field)
756  {
757  EnsureOpen();
758  return subReaders.Any(t => t.HasNorms(field));
759  }
760 
761  public override byte[] Norms(System.String field)
762  {
763  lock (this)
764  {
765  EnsureOpen();
766  byte[] bytes = normsCache[field];
767  if (bytes != null)
768  return bytes; // cache hit
769  if (!HasNorms(field))
770  return null;
771 
772  bytes = new byte[MaxDoc];
773  for (int i = 0; i < subReaders.Length; i++)
774  subReaders[i].Norms(field, bytes, starts[i]);
775  normsCache[field] = bytes; // update cache
776  return bytes;
777  }
778  }
779 
780  public override void Norms(System.String field, byte[] result, int offset)
781  {
782  lock (this)
783  {
784  EnsureOpen();
785  byte[] bytes = normsCache[field];
786  if (bytes == null && !HasNorms(field))
787  {
788  byte val = DefaultSimilarity.EncodeNorm(1.0f);
789  for (int index = offset; index < result.Length; index++)
790  result.SetValue(val, index);
791  }
792  else if (bytes != null)
793  {
794  // cache hit
795  Array.Copy(bytes, 0, result, offset, MaxDoc);
796  }
797  else
798  {
799  for (int i = 0; i < subReaders.Length; i++)
800  {
801  // read from segments
802  subReaders[i].Norms(field, result, offset + starts[i]);
803  }
804  }
805  }
806  }
807 
808  protected internal override void DoSetNorm(int n, System.String field, byte value_Renamed)
809  {
810  lock (normsCache)
811  {
812  normsCache.Remove(field); // clear cache
813  }
814  int i = ReaderIndex(n); // find segment num
815  subReaders[i].SetNorm(n - starts[i], field, value_Renamed); // dispatch
816  }
817 
818  public override TermEnum Terms()
819  {
820  EnsureOpen();
821  return new MultiTermEnum(this, subReaders, starts, null);
822  }
823 
824  public override TermEnum Terms(Term term)
825  {
826  EnsureOpen();
827  return new MultiTermEnum(this, subReaders, starts, term);
828  }
829 
830  public override int DocFreq(Term t)
831  {
832  EnsureOpen();
833  int total = 0; // sum freqs in segments
834  for (int i = 0; i < subReaders.Length; i++)
835  total += subReaders[i].DocFreq(t);
836  return total;
837  }
838 
839  public override TermDocs TermDocs()
840  {
841  EnsureOpen();
842  return new MultiTermDocs(this, subReaders, starts);
843  }
844 
845  public override TermPositions TermPositions()
846  {
847  EnsureOpen();
848  return new MultiTermPositions(this, subReaders, starts);
849  }
850 
851  /// <summary> Tries to acquire the WriteLock on this directory. this method is only valid if this IndexReader is directory
852  /// owner.
853  ///
854  /// </summary>
855  /// <throws> StaleReaderException if the index has changed since this reader was opened </throws>
856  /// <throws> CorruptIndexException if the index is corrupt </throws>
857  /// <throws> Lucene.Net.Store.LockObtainFailedException </throws>
858  /// <summary> if another writer has this index open (<c>write.lock</c> could not be
859  /// obtained)
860  /// </summary>
861  /// <throws> IOException if there is a low-level IO error </throws>
862  protected internal override void AcquireWriteLock()
863  {
864 
865  if (readOnly)
866  {
867  // NOTE: we should not reach this code w/ the core
868  // IndexReader classes; however, an external subclass
869  // of IndexReader could reach this.
870  ReadOnlySegmentReader.NoWrite();
871  }
872 
873  if (segmentInfos != null)
874  {
875  EnsureOpen();
876  if (stale)
877  throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
878 
879  if (this.writeLock == null)
880  {
881  Lock writeLock = internalDirectory.MakeLock(IndexWriter.WRITE_LOCK_NAME);
882  if (!writeLock.Obtain(IndexWriter.WRITE_LOCK_TIMEOUT))
883  // obtain write lock
884  {
885  throw new LockObtainFailedException("Index locked for write: " + writeLock);
886  }
887  this.writeLock = writeLock;
888 
889  // we have to check whether index has changed since this reader was opened.
890  // if so, this reader is no longer valid for
891  // deletion
892  if (SegmentInfos.ReadCurrentVersion(internalDirectory) > maxIndexVersion)
893  {
894  stale = true;
895  this.writeLock.Release();
896  this.writeLock = null;
897  throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
898  }
899  }
900  }
901  }
902 
903  /// <summary> Commit changes resulting from delete, undeleteAll, or setNorm operations
904  /// <p/>
905  /// If an exception is hit, then either no changes or all changes will have been committed to the index (transactional
906  /// semantics).
907  ///
908  /// </summary>
909  /// <throws> IOException if there is a low-level IO error </throws>
910  protected internal override void DoCommit(IDictionary<string, string> commitUserData)
911  {
912  if (hasChanges)
913  {
914  segmentInfos.UserData = commitUserData;
915  // Default deleter (for backwards compatibility) is
916  // KeepOnlyLastCommitDeleter:
917  var deleter = new IndexFileDeleter(internalDirectory, deletionPolicy ?? new KeepOnlyLastCommitDeletionPolicy(), segmentInfos, null, null, synced);
918 
919  segmentInfos.UpdateGeneration(deleter.LastSegmentInfos);
920 
921  // Checkpoint the state we are about to change, in
922  // case we have to roll back:
923  StartCommit();
924 
925  bool success = false;
926  try
927  {
928  foreach (SegmentReader t in subReaders)
929  t.Commit();
930 
931  // Sync all files we just wrote
932  foreach(string fileName in segmentInfos.Files(internalDirectory, false))
933  {
934  if(!synced.Contains(fileName))
935  {
936  System.Diagnostics.Debug.Assert(internalDirectory.FileExists(fileName));
937  internalDirectory.Sync(fileName);
938  synced.Add(fileName);
939  }
940  }
941 
942  segmentInfos.Commit(internalDirectory);
943  success = true;
944  }
945  finally
946  {
947 
948  if (!success)
949  {
950 
951  // Rollback changes that were made to
952  // SegmentInfos but failed to get [fully]
953  // committed. This way this reader instance
954  // remains consistent (matched to what's
955  // actually in the index):
956  RollbackCommit();
957 
958  // Recompute deletable files & remove them (so
959  // partially written .del files, etc, are
960  // removed):
961  deleter.Refresh();
962  }
963  }
964 
965  // Have the deleter remove any now unreferenced
966  // files due to this commit:
967  deleter.Checkpoint(segmentInfos, true);
968  deleter.Dispose();
969 
970  maxIndexVersion = segmentInfos.Version;
971 
972  if (writeLock != null)
973  {
974  writeLock.Release(); // release write lock
975  writeLock = null;
976  }
977  }
978  hasChanges = false;
979  }
980 
981  internal virtual void StartCommit()
982  {
983  rollbackHasChanges = hasChanges;
984  foreach (SegmentReader t in subReaders)
985  {
986  t.StartCommit();
987  }
988  }
989 
990  internal virtual void RollbackCommit()
991  {
992  hasChanges = rollbackHasChanges;
993  foreach (SegmentReader t in subReaders)
994  {
995  t.RollbackCommit();
996  }
997  }
998 
999  public override IDictionary<string, string> CommitUserData
1000  {
1001  get
1002  {
1003  EnsureOpen();
1004  return segmentInfos.UserData;
1005  }
1006  }
1007 
1008  public override bool IsCurrent()
1009  {
1010  EnsureOpen();
1011  if (writer == null || writer.IsClosed())
1012  {
1013  // we loaded SegmentInfos from the directory
1014  return SegmentInfos.ReadCurrentVersion(internalDirectory) == segmentInfos.Version;
1015  }
1016  else
1017  {
1018  return writer.NrtIsCurrent(segmentInfosStart);
1019  }
1020  }
1021 
1022  protected internal override void DoClose()
1023  {
1024  lock (this)
1025  {
1026  System.IO.IOException ioe = null;
1027  normsCache = null;
1028  foreach (SegmentReader t in subReaders)
1029  {
1030  // try to close each reader, even if an exception is thrown
1031  try
1032  {
1033  t.DecRef();
1034  }
1035  catch (System.IO.IOException e)
1036  {
1037  if (ioe == null)
1038  ioe = e;
1039  }
1040  }
1041 
1042  // NOTE: only needed in case someone had asked for
1043  // FieldCache for top-level reader (which is generally
1044  // not a good idea):
1045  Search.FieldCache_Fields.DEFAULT.Purge(this);
1046 
1047  // throw the first exception
1048  if (ioe != null)
1049  throw ioe;
1050  }
1051  }
1052 
1053  public override ICollection<string> GetFieldNames(IndexReader.FieldOption fieldNames)
1054  {
1055  EnsureOpen();
1056  return GetFieldNames(fieldNames, this.subReaders);
1057  }
1058 
1059  internal static ICollection<string> GetFieldNames(IndexReader.FieldOption fieldNames, IndexReader[] subReaders)
1060  {
1061  // maintain a unique set of field names
1062  ISet<string> fieldSet = Support.Compatibility.SetFactory.CreateHashSet<string>();
1063  foreach (IndexReader reader in subReaders)
1064  {
1065  fieldSet.UnionWith(reader.GetFieldNames(fieldNames));
1066  }
1067  return fieldSet;
1068  }
1069 
1070  public override IndexReader[] GetSequentialSubReaders()
1071  {
1072  return subReaders;
1073  }
1074 
1075  /// <summary>Returns the directory this index resides in. </summary>
1076  public override Directory Directory()
1077  {
1078  // Don't ensureOpen here -- in certain cases, when a
1079  // cloned/reopened reader needs to commit, it may call
1080  // this method on the closed original reader
1081  return internalDirectory;
1082  }
1083 
1084  public override int TermInfosIndexDivisor
1085  {
1086  get { return termInfosIndexDivisor; }
1087  }
1088 
1089  /// <summary> Expert: return the IndexCommit that this reader has opened.
1090  /// <p/>
1091  /// <p/><b>WARNING</b>: this API is new and experimental and may suddenly change.<p/>
1092  /// </summary>
1093  public override IndexCommit IndexCommit
1094  {
1095  get { return new ReaderCommit(segmentInfos, internalDirectory); }
1096  }
1097 
1098  /// <seealso cref="Lucene.Net.Index.IndexReader.ListCommits">
1099  /// </seealso>
1100  public static new ICollection<IndexCommit> ListCommits(Directory dir)
1101  {
1102  String[] files = dir.ListAll();
1103 
1104  ICollection<IndexCommit> commits = new List<IndexCommit>();
1105 
1106  var latest = new SegmentInfos();
1107  latest.Read(dir);
1108  long currentGen = latest.Generation;
1109 
1110  commits.Add(new ReaderCommit(latest, dir));
1111 
1112  foreach (string fileName in files)
1113  {
1114  if (fileName.StartsWith(IndexFileNames.SEGMENTS) && !fileName.Equals(IndexFileNames.SEGMENTS_GEN) && SegmentInfos.GenerationFromSegmentsFileName(fileName) < currentGen)
1115  {
1116 
1117  var sis = new SegmentInfos();
1118  try
1119  {
1120  // IOException allowed to throw there, in case
1121  // segments_N is corrupt
1122  sis.Read(dir, fileName);
1123  }
1124  catch (System.IO.FileNotFoundException)
1125  {
1126  // LUCENE-948: on NFS (and maybe others), if
1127  // you have writers switching back and forth
1128  // between machines, it's very likely that the
1129  // dir listing will be stale and will claim a
1130  // file segments_X exists when in fact it
1131  // doesn't. So, we catch this and handle it
1132  // as if the file does not exist
1133  sis = null;
1134  }
1135 
1136  if (sis != null)
1137  commits.Add(new ReaderCommit(sis, dir));
1138  }
1139  }
1140 
1141  return commits;
1142  }
1143 
1144  private sealed class ReaderCommit:IndexCommit
1145  {
1146  private readonly String segmentsFileName;
1147  private readonly ICollection<string> files;
1148  private readonly Directory dir;
1149  private readonly long generation;
1150  private readonly long version;
1151  private readonly bool isOptimized;
1152  private readonly IDictionary<string, string> userData;
1153 
1154  internal ReaderCommit(SegmentInfos infos, Directory dir)
1155  {
1156  segmentsFileName = infos.GetCurrentSegmentFileName();
1157  this.dir = dir;
1158  userData = infos.UserData;
1159  files = infos.Files(dir, true);
1160  version = infos.Version;
1161  generation = infos.Generation;
1162  isOptimized = infos.Count == 1 && !infos.Info(0).HasDeletions();
1163  }
1164  public override string ToString()
1165  {
1166  return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")";
1167  }
1168 
1169  public override bool IsOptimized
1170  {
1171  get { return isOptimized; }
1172  }
1173 
1174  public override string SegmentsFileName
1175  {
1176  get { return segmentsFileName; }
1177  }
1178 
1179  public override ICollection<string> FileNames
1180  {
1181  get { return files; }
1182  }
1183 
1184  public override Directory Directory
1185  {
1186  get { return dir; }
1187  }
1188 
1189  public override long Version
1190  {
1191  get { return version; }
1192  }
1193 
1194  public override long Generation
1195  {
1196  get { return generation; }
1197  }
1198 
1199  public override bool IsDeleted
1200  {
1201  get { return false; }
1202  }
1203 
1204  public override IDictionary<string, string> UserData
1205  {
1206  get { return userData; }
1207  }
1208 
1209  public override void Delete()
1210  {
1211  throw new System.NotSupportedException("This IndexCommit does not support deletions");
1212  }
1213  }
1214 
1215  internal class MultiTermEnum:TermEnum
1216  {
1217  internal IndexReader topReader; // used for matching TermEnum to TermDocs
1218  private readonly SegmentMergeQueue queue;
1219 
1220  private Term term;
1221  private int docFreq;
1222  internal SegmentMergeInfo[] matchingSegments; // null terminated array of matching segments
1223 
1224  public MultiTermEnum(IndexReader topReader, IndexReader[] readers, int[] starts, Term t)
1225  {
1226  this.topReader = topReader;
1227  queue = new SegmentMergeQueue(readers.Length);
1228  matchingSegments = new SegmentMergeInfo[readers.Length + 1];
1229  for (int i = 0; i < readers.Length; i++)
1230  {
1231  IndexReader reader = readers[i];
1232 
1233  TermEnum termEnum = t != null ? reader.Terms(t) : reader.Terms();
1234 
1235  var smi = new SegmentMergeInfo(starts[i], termEnum, reader) {ord = i};
1236  if (t == null?smi.Next():termEnum.Term != null)
1237  queue.Add(smi);
1238  // initialize queue
1239  else
1240  smi.Dispose();
1241  }
1242 
1243  if (t != null && queue.Size() > 0)
1244  {
1245  Next();
1246  }
1247  }
1248 
1249  public override bool Next()
1250  {
1251  foreach (SegmentMergeInfo smi in matchingSegments)
1252  {
1253  if (smi == null)
1254  break;
1255  if (smi.Next())
1256  queue.Add(smi);
1257  else
1258  smi.Dispose(); // done with segment
1259  }
1260 
1261  int numMatchingSegments = 0;
1262  matchingSegments[0] = null;
1263 
1264  SegmentMergeInfo top = queue.Top();
1265 
1266  if (top == null)
1267  {
1268  term = null;
1269  return false;
1270  }
1271 
1272  term = top.term;
1273  docFreq = 0;
1274 
1275  while (top != null && term.CompareTo(top.term) == 0)
1276  {
1277  matchingSegments[numMatchingSegments++] = top;
1278  queue.Pop();
1279  docFreq += top.termEnum.DocFreq(); // increment freq
1280  top = queue.Top();
1281  }
1282 
1283  matchingSegments[numMatchingSegments] = null;
1284  return true;
1285  }
1286 
1287  public override Term Term
1288  {
1289  get { return term; }
1290  }
1291 
1292  public override int DocFreq()
1293  {
1294  return docFreq;
1295  }
1296 
1297  protected override void Dispose(bool disposing)
1298  {
1299  if (disposing)
1300  {
1301  queue.Dispose();
1302  }
1303  }
1304  }
1305 
1306  internal class MultiTermDocs : TermDocs
1307  {
1308  internal IndexReader topReader; // used for matching TermEnum to TermDocs
1309  protected internal IndexReader[] readers;
1310  protected internal int[] starts;
1311  protected internal Term term;
1312 
1313  protected internal int base_Renamed = 0;
1314  protected internal int pointer = 0;
1315 
1316  private readonly TermDocs[] readerTermDocs;
1317  protected internal TermDocs current; // == readerTermDocs[pointer]
1318 
1319  private MultiTermEnum tenum; // the term enum used for seeking... can be null
1320  internal int matchingSegmentPos; // position into the matching segments from tenum
1321  internal SegmentMergeInfo smi; // current segment mere info... can be null
1322 
1323  public MultiTermDocs(IndexReader topReader, IndexReader[] r, int[] s)
1324  {
1325  this.topReader = topReader;
1326  readers = r;
1327  starts = s;
1328 
1329  readerTermDocs = new TermDocs[r.Length];
1330  }
1331 
1332  public virtual int Doc
1333  {
1334  get { return base_Renamed + current.Doc; }
1335  }
1336 
1337  public virtual int Freq
1338  {
1339  get { return current.Freq; }
1340  }
1341 
1342  public virtual void Seek(Term term)
1343  {
1344  this.term = term;
1345  this.base_Renamed = 0;
1346  this.pointer = 0;
1347  this.current = null;
1348  this.tenum = null;
1349  this.smi = null;
1350  this.matchingSegmentPos = 0;
1351  }
1352 
1353  public virtual void Seek(TermEnum termEnum)
1354  {
1355  Seek(termEnum.Term);
1356  var multiTermEnum = termEnum as MultiTermEnum;
1357  if (multiTermEnum != null)
1358  {
1359  tenum = multiTermEnum;
1360  if (topReader != tenum.topReader)
1361  tenum = null;
1362  }
1363  }
1364 
1365  public virtual bool Next()
1366  {
1367  for (; ; )
1368  {
1369  if (current != null && current.Next())
1370  {
1371  return true;
1372  }
1373  else if (pointer < readers.Length)
1374  {
1375  if (tenum != null)
1376  {
1377  smi = tenum.matchingSegments[matchingSegmentPos++];
1378  if (smi == null)
1379  {
1380  pointer = readers.Length;
1381  return false;
1382  }
1383  pointer = smi.ord;
1384  }
1385  base_Renamed = starts[pointer];
1386  current = TermDocs(pointer++);
1387  }
1388  else
1389  {
1390  return false;
1391  }
1392  }
1393  }
1394 
1395  /// <summary>Optimized implementation. </summary>
1396  public virtual int Read(int[] docs, int[] freqs)
1397  {
1398  while (true)
1399  {
1400  while (current == null)
1401  {
1402  if (pointer < readers.Length)
1403  {
1404  // try next segment
1405  if (tenum != null)
1406  {
1407  smi = tenum.matchingSegments[matchingSegmentPos++];
1408  if (smi == null)
1409  {
1410  pointer = readers.Length;
1411  return 0;
1412  }
1413  pointer = smi.ord;
1414  }
1415  base_Renamed = starts[pointer];
1416  current = TermDocs(pointer++);
1417  }
1418  else
1419  {
1420  return 0;
1421  }
1422  }
1423  int end = current.Read(docs, freqs);
1424  if (end == 0)
1425  {
1426  // none left in segment
1427  current = null;
1428  }
1429  else
1430  {
1431  // got some
1432  int b = base_Renamed; // adjust doc numbers
1433  for (int i = 0; i < end; i++)
1434  docs[i] += b;
1435  return end;
1436  }
1437  }
1438  }
1439 
1440  /* A Possible future optimization could skip entire segments */
1441  public virtual bool SkipTo(int target)
1442  {
1443  for (; ; )
1444  {
1445  if (current != null && current.SkipTo(target - base_Renamed))
1446  {
1447  return true;
1448  }
1449  else if (pointer < readers.Length)
1450  {
1451  if (tenum != null)
1452  {
1453  SegmentMergeInfo smi = tenum.matchingSegments[matchingSegmentPos++];
1454  if (smi == null)
1455  {
1456  pointer = readers.Length;
1457  return false;
1458  }
1459  pointer = smi.ord;
1460  }
1461  base_Renamed = starts[pointer];
1462  current = TermDocs(pointer++);
1463  }
1464  else
1465  return false;
1466  }
1467  }
1468 
1469  private TermDocs TermDocs(int i)
1470  {
1471  TermDocs result = readerTermDocs[i] ?? (readerTermDocs[i] = TermDocs(readers[i]));
1472  if (smi != null)
1473  {
1474  System.Diagnostics.Debug.Assert((smi.ord == i));
1475  System.Diagnostics.Debug.Assert((smi.termEnum.Term.Equals(term)));
1476  result.Seek(smi.termEnum);
1477  }
1478  else
1479  {
1480  result.Seek(term);
1481  }
1482  return result;
1483  }
1484 
1485  protected internal virtual TermDocs TermDocs(IndexReader reader)
1486  {
1487  return term == null ? reader.TermDocs(null):reader.TermDocs();
1488  }
1489 
1490  public virtual void Close()
1491  {
1492  Dispose();
1493  }
1494 
1495  public virtual void Dispose()
1496  {
1497  Dispose(true);
1498  }
1499 
1500  protected virtual void Dispose(bool disposing)
1501  {
1502  if (disposing)
1503  {
1504  foreach (TermDocs t in readerTermDocs)
1505  {
1506  if (t != null)
1507  t.Close();
1508  }
1509  }
1510  }
1511  }
1512 
1514  {
1515  public MultiTermPositions(IndexReader topReader, IndexReader[] r, int[] s):base(topReader, r, s)
1516  {
1517  }
1518 
1519  protected internal override TermDocs TermDocs(IndexReader reader)
1520  {
1521  return reader.TermPositions();
1522  }
1523 
1524  public virtual int NextPosition()
1525  {
1526  return ((TermPositions) current).NextPosition();
1527  }
1528 
1529  public virtual int PayloadLength
1530  {
1531  get { return ((TermPositions) current).PayloadLength; }
1532  }
1533 
1534  public virtual byte[] GetPayload(byte[] data, int offset)
1535  {
1536  return ((TermPositions) current).GetPayload(data, offset);
1537  }
1538 
1539 
1540  // TODO: Remove warning after API has been finalized
1541 
1542  public virtual bool IsPayloadAvailable
1543  {
1544  get { return ((TermPositions) current).IsPayloadAvailable; }
1545  }
1546  }
1547  }
1548 }