blob: 3a82084b18f8eda789bfac67cc346ba7dfb60ca4 [file] [log] [blame]
// Lucene version compatibility level 4.8.1
using J2N.Threading.Atomic;
using Lucene.Net.Diagnostics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace Lucene.Net.Facet.Taxonomy
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>
/// TaxonomyReader is the read-only interface with which the faceted-search
/// library uses the taxonomy during search time.
/// <para>
/// A TaxonomyReader holds a list of categories. Each category has a serial
/// number which we call an "ordinal", and a hierarchical "path" name:
/// <list type="bullet">
/// <item><description>
/// The ordinal is an integer that starts at 0 for the first category (which is
/// always the root category), and grows contiguously as more categories are
/// added; Note that once a category is added, it can never be deleted.
/// </description></item>
/// <item><description>
/// The path is a CategoryPath object specifying the category's position in the
/// hierarchy.
/// </description></item>
/// </list>
/// </para>
/// <b>Notes about concurrent access to the taxonomy:</b>
/// <para>
/// An implementation must allow multiple readers to be active concurrently
/// with a single writer. Readers follow so-called "point in time" semantics,
/// i.e., a TaxonomyReader object will only see taxonomy entries which were
/// available at the time it was created. What the writer writes is only
/// available to (new) readers after the writer's <see cref="Index.IndexWriter.Commit()"/> is called.
/// </para>
/// <para>
/// In faceted search, two separate indices are used: the main Lucene index,
/// and the taxonomy. Because the main index refers to the categories listed
/// in the taxonomy, it is important to open the taxonomy *after* opening the
/// main index, and it is also necessary to Reopen() the taxonomy after
/// Reopen()ing the main index.
/// </para>
/// <para>
/// This order is important, otherwise it would be possible for the main index
/// to refer to a category which is not yet visible in the old snapshot of
/// the taxonomy. Note that it is indeed fine for the the taxonomy to be opened
/// after the main index - even a long time after. The reason is that once
/// a category is added to the taxonomy, it can never be changed or deleted,
/// so there is no danger that a "too new" taxonomy not being consistent with
/// an older index.
/// </para>
///
/// @lucene.experimental
/// </summary>
public abstract class TaxonomyReader : IDisposable
{
/// <summary>
/// An iterator over a category's children.
/// </summary>
public class ChildrenEnumerator
{
private readonly int[] siblings;
private int child;
private int currentChild = TaxonomyReader.INVALID_ORDINAL;
internal ChildrenEnumerator(int child, int[] siblings)
{
this.siblings = siblings ?? throw new ArgumentNullException(nameof(siblings)); // LUCENENT specific - added guard clause
this.child = child;
}
/// <summary>
/// Gets the current child. Returns <see cref="TaxonomyReader.INVALID_ORDINAL"/> if
/// positioned before the first child or after the last child.
/// </summary>
public virtual int Current => currentChild;
/// <summary>
/// Move to the next child ordinal. Returns <c>false</c> if there are no more children.
/// </summary>
/// <returns></returns>
public virtual bool MoveNext()
{
if (child == TaxonomyReader.INVALID_ORDINAL)
return false;
currentChild = child;
child = siblings[child];
return true;
}
}
/// <summary>
/// Sole constructor.
/// </summary>
protected TaxonomyReader() // LUCENENET specific - marked protected instead of public
{
}
/// <summary>
/// The root category (the category with the empty path) always has the ordinal
/// 0, to which we give a name ROOT_ORDINAL. <see cref="GetOrdinal(FacetLabel)"/>
/// of an empty path will always return <see cref="ROOT_ORDINAL"/>, and
/// <see cref="GetPath(int)"/> with <see cref="ROOT_ORDINAL"/> will return the empty path.
/// </summary>
public const int ROOT_ORDINAL = 0;
/// <summary>
/// Ordinals are always non-negative, so a negative ordinal can be used to
/// signify an error. Methods here return <see cref="INVALID_ORDINAL"/> (-1) in this case.
/// </summary>
public const int INVALID_ORDINAL = -1;
/// <summary>
/// If the taxonomy has changed since the provided reader was opened, open and
/// return a new <see cref="TaxonomyReader"/>; else, return <c>null</c>. The new
/// reader, if not <c>null</c>, will be the same type of reader as the one
/// given to this method.
///
/// <para>
/// This method is typically far less costly than opening a fully new
/// <see cref="TaxonomyReader"/> as it shares resources with the provided
/// <see cref="TaxonomyReader"/>, when possible.
/// </para>
/// </summary>
public static T OpenIfChanged<T>(T oldTaxoReader) where T : TaxonomyReader
{
T newTaxoReader = (T)oldTaxoReader.DoOpenIfChanged();
if (Debugging.AssertsEnabled) Debugging.Assert(newTaxoReader != oldTaxoReader);
return newTaxoReader;
}
private volatile bool closed = false;
// set refCount to 1 at start
private readonly AtomicInt32 refCount = new AtomicInt32(1);
private readonly object syncLock = new object(); // LUCENENET specific - avoid lock (this)
// LUCENENET specific - Removed DoClose() and replaced with Dispose(true)
/// <summary>
/// Implements the actual opening of a new <see cref="TaxonomyReader"/> instance if
/// the taxonomy has changed.
/// </summary>
/// <seealso cref="OpenIfChanged{T}(T)"/>
protected abstract TaxonomyReader DoOpenIfChanged();
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if this <see cref="Index.IndexReader"/> is disposed
/// </summary>
protected void EnsureOpen()
{
if (RefCount <= 0)
{
throw new ObjectDisposedException(this.GetType().FullName, "this TaxonomyReader is closed");
}
}
// LUCENENET specific - implementing proper dispose pattern
#pragma warning disable CA1063 // Implement IDisposable Correctly
public void Dispose()
#pragma warning restore CA1063 // Implement IDisposable Correctly
{
if (closed) return;
lock (syncLock)
{
if (closed) return;
DecRef();
closed = true;
}
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Performs the actual task of closing the resources that are used by the
/// taxonomy reader.
/// </summary>
protected abstract void Dispose(bool disposing); // LUCENENET: Refactored from DoClose()
/// <summary>
/// Expert: decreases the refCount of this TaxonomyReader instance. If the
/// refCount drops to 0 this taxonomy reader is closed.
/// </summary>
public void DecRef()
{
EnsureOpen();
int rc = refCount.DecrementAndGet();
if (rc == 0)
{
bool success = false;
try
{
Dispose(true); // LUCENENET specific - changed from DoClose() to Dispose(bool)
closed = true;
success = true;
}
finally
{
if (!success)
{
// Put reference back on failure
refCount.IncrementAndGet();
}
}
}
else if (rc < 0)
{
throw new ThreadStateException("too many decRef calls: refCount is " + rc + " after decrement");
}
}
/// <summary>
/// Returns a <see cref="ParallelTaxonomyArrays"/> object which can be used to
/// efficiently traverse the taxonomy tree.
/// </summary>
public abstract ParallelTaxonomyArrays ParallelTaxonomyArrays { get; }
/// <summary>
/// Returns an iterator over the children of the given ordinal.
/// </summary>
public virtual ChildrenEnumerator GetChildren(int ordinal)
{
ParallelTaxonomyArrays arrays = ParallelTaxonomyArrays;
int child = ordinal >= 0 ? arrays.Children[ordinal] : INVALID_ORDINAL;
return new ChildrenEnumerator(child, arrays.Siblings);
}
/// <summary>
/// Retrieve user committed data.
/// </summary>
/// <seealso cref="ITaxonomyWriter.CommitData"/>
public abstract IDictionary<string, string> CommitUserData { get; }
/// <summary>
/// Returns the ordinal of the category given as a path. The ordinal is the
/// category's serial number, an integer which starts with 0 and grows as more
/// categories are added (note that once a category is added, it can never be
/// deleted).
/// </summary>
/// <returns> the category's ordinal or <see cref="INVALID_ORDINAL"/> if the category
/// wasn't found. </returns>
public abstract int GetOrdinal(FacetLabel categoryPath);
/// <summary>
/// Returns ordinal for the dim + path.
/// </summary>
public virtual int GetOrdinal(string dim, string[] path)
{
string[] fullPath = new string[path.Length + 1];
fullPath[0] = dim;
Array.Copy(path, 0, fullPath, 1, path.Length);
return GetOrdinal(new FacetLabel(fullPath));
}
/// <summary>
/// Returns the path name of the category with the given ordinal.
/// </summary>
public abstract FacetLabel GetPath(int ordinal);
/// <summary>
/// Returns the current refCount for this taxonomy reader.
/// </summary>
public int RefCount => refCount;
/// <summary>
/// Returns the number of categories in the taxonomy. Note that the number of
/// categories returned is often slightly higher than the number of categories
/// inserted into the taxonomy; This is because when a category is added to the
/// taxonomy, its ancestors are also added automatically (including the root,
/// which always get ordinal 0).
/// </summary>
public abstract int Count { get; }
/// <summary>
/// Expert: increments the refCount of this TaxonomyReader instance. RefCounts
/// can be used to determine when a taxonomy reader can be closed safely, i.e.
/// as soon as there are no more references. Be sure to always call a
/// corresponding <see cref="DecRef"/>, in a finally clause; otherwise the reader may never
/// be disposed.
/// </summary>
public void IncRef()
{
EnsureOpen();
refCount.IncrementAndGet();
}
/// <summary>
/// Expert: increments the refCount of this TaxonomyReader
/// instance only if it has not been closed yet. Returns
/// <c>true</c> on success.
/// </summary>
public bool TryIncRef()
{
int count;
while ((count = refCount) > 0)
{
if (refCount.CompareAndSet(count, count + 1))
{
return true;
}
}
return false;
}
}
}