public ThreadAnonymousInnerClassHelper(TestAddTaxonomy outerInstance, int range, AtomicInteger numCats, DirectoryTaxonomyWriter tw) { this.outerInstance = outerInstance; this.range = range; this.numCats = numCats; this.tw = tw; }
private void Dotest(int ncats, int range) { AtomicInteger numCats = new AtomicInteger(ncats); Directory[] dirs = new Directory[2]; for (int i = 0; i < dirs.Length; i++) { dirs[i] = NewDirectory(); var tw = new DirectoryTaxonomyWriter(dirs[i]); ThreadClass[] addThreads = new ThreadClass[4]; for (int j = 0; j < addThreads.Length; j++) { addThreads[j] = new ThreadAnonymousInnerClassHelper(this, range, numCats, tw); } foreach (ThreadClass t in addThreads) { t.Start(); } foreach (ThreadClass t in addThreads) { t.Join(); } tw.Dispose(); } var tw1 = new DirectoryTaxonomyWriter(dirs[0]); IOrdinalMap map = randomOrdinalMap(); tw1.AddTaxonomy(dirs[1], map); tw1.Dispose(); validate(dirs[0], dirs[1], map); IOUtils.Close(dirs); }
/// <summary> /// Called only from <seealso cref="#doOpenIfChanged()"/>. If the taxonomy has been /// recreated, you should pass {@code null} as the caches and parent/children /// arrays. /// </summary> internal DirectoryTaxonomyReader(DirectoryReader indexReader, DirectoryTaxonomyWriter taxoWriter, LRUHashMap<FacetLabel, IntClass> ordinalCache, LRUHashMap<int, FacetLabel> categoryCache, TaxonomyIndexArrays taxoArrays) { this.indexReader = indexReader; this.taxoWriter = taxoWriter; this.taxoEpoch = taxoWriter == null ? -1 : taxoWriter.TaxonomyEpoch; // use the same instance of the cache, note the protective code in getOrdinal and getPath this.ordinalCache = ordinalCache == null ? new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE) : ordinalCache; this.categoryCache = categoryCache == null ? new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE) : categoryCache; this.taxoArrays = taxoArrays != null ? new TaxonomyIndexArrays(indexReader, taxoArrays) : null; }
public virtual void TestCloseAfterIncRef() { Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Dispose(); DirectoryTaxonomyReader ltr = new DirectoryTaxonomyReader(dir); ltr.IncRef(); ltr.Dispose(); // should not fail as we IncRef() before close var tmpSie = ltr.Size; ltr.DecRef(); dir.Dispose(); }
public virtual void TestCloseNoEmptyCommits() { // LUCENE-4972: DTW used to create empty commits even if no changes were made Directory dir = NewDirectory(); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir); taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.Commit(); long gen1 = SegmentInfos.GetLastCommitGeneration(dir); taxoWriter.Dispose(); long gen2 = SegmentInfos.GetLastCommitGeneration(dir); Assert.AreEqual(gen1, gen2, "empty commit should not have changed the index"); taxoWriter.Dispose(); dir.Dispose(); }
public virtual void TestAddToEmpty() { Directory dest = NewDirectory(); Directory src = NewDirectory(); DirectoryTaxonomyWriter srcTW = new DirectoryTaxonomyWriter(src); srcTW.AddCategory(new FacetLabel("Author", "Rob Pike")); srcTW.AddCategory(new FacetLabel("Aardvarks", "Bob")); srcTW.Dispose(); DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest); OrdinalMap map = randomOrdinalMap(); destTW.AddTaxonomy(src, map); destTW.Dispose(); validate(dest, src, map); IOUtils.Close(dest, src); }
public virtual void TestBackwardsCompatibility() { // tests that if the taxonomy index doesn't have the INDEX_EPOCH // property (supports pre-3.6 indexes), all still works. Directory dir = NewDirectory(); // create an empty index first, so that DirTaxoWriter initializes indexEpoch to 1. (new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null))).Dispose(); var taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); taxoWriter.Dispose(); var taxoReader = new DirectoryTaxonomyReader(dir); Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); Assert.Null(TaxonomyReader.OpenIfChanged(taxoReader)); (taxoReader).Dispose(); dir.Dispose(); }
public virtual void TestOpenIfChangedReuseAfterRecreate() { // tests that if the taxonomy is recreated, no data is reused from the previous taxonomy Directory dir = NewDirectory(); DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(dir); FacetLabel cp_a = new FacetLabel("a"); writer.AddCategory(cp_a); writer.Dispose(); DirectoryTaxonomyReader r1 = new DirectoryTaxonomyReader(dir); // fill r1's caches Assert.AreEqual(1, r1.GetOrdinal(cp_a)); Assert.AreEqual(cp_a, r1.GetPath(1)); // now recreate, add a different category writer = new DirectoryTaxonomyWriter(dir, IndexWriterConfig.OpenMode_e.CREATE); FacetLabel cp_b = new FacetLabel("b"); writer.AddCategory(cp_b); writer.Dispose(); DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1); Assert.NotNull(r2); // fill r2's caches Assert.AreEqual(1, r2.GetOrdinal(cp_b)); Assert.AreEqual(cp_b, r2.GetPath(1)); // check that r1 doesn't see cp_b Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b)); Assert.AreEqual(cp_a, r1.GetPath(1)); // check that r2 doesn't see cp_a Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r2.GetOrdinal(cp_a)); Assert.AreEqual(cp_b, r2.GetPath(1)); (r2).Dispose(); (r1).Dispose(); dir.Dispose(); }
public virtual void TestCloseAfterIncRef() { Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Dispose(); DirectoryTaxonomyReader ltr = new DirectoryTaxonomyReader(dir); ltr.IncRef(); ltr.Dispose(); // should not fail as we IncRef() before close var _ = ltr.Count; ltr.DecRef(); dir.Dispose(); }
public virtual void TestRollback() { // Verifies that if rollback is called, DTW is closed. Directory dir = NewDirectory(); var dtw = new DirectoryTaxonomyWriter(dir); dtw.AddCategory(new FacetLabel("a")); dtw.Rollback(); try { dtw.AddCategory(new FacetLabel("a")); Fail("should not have succeeded to add a category following rollback."); } catch (AlreadyClosedException) { // expected } dir.Dispose(); }
public virtual void TestAlreadyClosed() { Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Dispose(); var ltr = new DirectoryTaxonomyReader(dir); ltr.Dispose(); try { var tmpSize = ltr.Size; Fail("An AlreadyClosedException should have been thrown here"); } catch (AlreadyClosedException) { // good! } dir.Dispose(); }
public virtual void TestReplaceTaxonomy() { Directory input = NewDirectory(); var taxoWriter = new DirectoryTaxonomyWriter(input); int ordA = taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.Dispose(); Directory dir = NewDirectory(); taxoWriter = new DirectoryTaxonomyWriter(dir); int ordB = taxoWriter.AddCategory(new FacetLabel("b")); taxoWriter.AddCategory(new FacetLabel("c")); taxoWriter.Commit(); long origEpoch = getEpoch(dir); // replace the taxonomy with the input one taxoWriter.ReplaceTaxonomy(input); // LUCENE-4633: make sure that category "a" is not added again in any case taxoWriter.AddTaxonomy(input, new MemoryOrdinalMap()); Assert.AreEqual(2, taxoWriter.Size, "no categories should have been added"); // root + 'a' Assert.AreEqual(ordA, taxoWriter.AddCategory(new FacetLabel("a")), "category 'a' received new ordinal?"); // add the same category again -- it should not receive the same ordinal ! int newOrdB = taxoWriter.AddCategory(new FacetLabel("b")); Assert.AreNotSame(ordB, newOrdB, "new ordinal cannot be the original ordinal"); Assert.AreEqual(2, newOrdB, "ordinal should have been 2 since only one category was added by replaceTaxonomy"); taxoWriter.Dispose(); long newEpoch = getEpoch(dir); Assert.True(origEpoch < newEpoch, "index epoch should have been updated after replaceTaxonomy"); dir.Dispose(); input.Dispose(); }
public virtual void TestBackwardsCompatibility() { // tests that if the taxonomy index doesn't have the INDEX_EPOCH // property (supports pre-3.6 indexes), all still works. Directory dir = NewDirectory(); // create an empty index first, so that DirTaxoWriter initializes indexEpoch to 1. (new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null))).Dispose(); var taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); taxoWriter.Dispose(); var taxoReader = new DirectoryTaxonomyReader(dir); Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); Assert.Null(TaxonomyReader.OpenIfChanged(taxoReader)); (taxoReader).Dispose(); dir.Dispose(); }
public virtual void TestAddToEmpty() { Directory dest = NewDirectory(); Directory src = NewDirectory(); DirectoryTaxonomyWriter srcTW = new DirectoryTaxonomyWriter(src); srcTW.AddCategory(new FacetLabel("Author", "Rob Pike")); srcTW.AddCategory(new FacetLabel("Aardvarks", "Bob")); srcTW.Dispose(); DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest); IOrdinalMap map = randomOrdinalMap(); destTW.AddTaxonomy(src, map); destTW.Dispose(); validate(dest, src, map); IOUtils.Dispose(dest, src); }
public virtual void TestRecreateAndRefresh() { // DirTaxoWriter lost the INDEX_EPOCH property if it was opened in // CREATE_OR_APPEND (or commit(userData) called twice), which could lead to // DirTaxoReader succeeding to refresh(). Directory dir = NewDirectory(); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("a")); var taxoReader = new DirectoryTaxonomyReader(dir); TouchTaxo(taxoWriter, new FacetLabel("b")); var newtr = TaxonomyReader.OpenIfChanged(taxoReader); taxoReader.Dispose(); taxoReader = newtr; Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); // now recreate the taxonomy, and check that the epoch is preserved after opening DirTW again. taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("c")); taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("d")); taxoWriter.Dispose(); newtr = TaxonomyReader.OpenIfChanged(taxoReader); taxoReader.Dispose(); taxoReader = newtr; Assert.AreEqual(2, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); taxoReader.Dispose(); dir.Dispose(); }
public virtual void TestAlreadyClosed() { Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Dispose(); var ltr = new DirectoryTaxonomyReader(dir); ltr.Dispose(); try { var _ = ltr.Count; fail("An ObjectDisposedException should have been thrown here"); } catch (ObjectDisposedException) { // good! } dir.Dispose(); }
private void Dotest(int ncats, int range) { AtomicInt32 numCats = new AtomicInt32(ncats); Directory[] dirs = new Directory[2]; for (int i = 0; i < dirs.Length; i++) { dirs[i] = NewDirectory(); var tw = new DirectoryTaxonomyWriter(dirs[i]); ThreadJob[] addThreads = new ThreadJob[4]; for (int j = 0; j < addThreads.Length; j++) { addThreads[j] = new ThreadAnonymousInnerClassHelper(this, range, numCats, tw); } foreach (ThreadJob t in addThreads) { t.Start(); } foreach (ThreadJob t in addThreads) { t.Join(); } tw.Dispose(); } var tw1 = new DirectoryTaxonomyWriter(dirs[0]); IOrdinalMap map = randomOrdinalMap(); tw1.AddTaxonomy(dirs[1], map); tw1.Dispose(); validate(dirs[0], dirs[1], map); IOUtils.Dispose(dirs); }
public virtual void TestReplaceTaxoWithLargeTaxonomy() { var srcTaxoDir = NewDirectory(); var targetTaxoDir = NewDirectory(); // build source, large, taxonomy DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(srcTaxoDir); int ord = taxoWriter.AddCategory(new FacetLabel("A", "1", "1", "1", "1", "1", "1")); taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(targetTaxoDir); int ordinal = taxoWriter.AddCategory(new FacetLabel("B", "1")); Assert.AreEqual(1, taxoWriter.GetParent(ordinal)); // call getParent to initialize taxoArrays taxoWriter.Commit(); taxoWriter.ReplaceTaxonomy(srcTaxoDir); Assert.AreEqual(ord - 1, taxoWriter.GetParent(ord)); taxoWriter.Dispose(); srcTaxoDir.Dispose(); targetTaxoDir.Dispose(); }
public virtual void TestOpenIfChangedReuseAfterRecreate() { // tests that if the taxonomy is recreated, no data is reused from the previous taxonomy Directory dir = NewDirectory(); DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(dir); FacetLabel cp_a = new FacetLabel("a"); writer.AddCategory(cp_a); writer.Dispose(); DirectoryTaxonomyReader r1 = new DirectoryTaxonomyReader(dir); // fill r1's caches Assert.AreEqual(1, r1.GetOrdinal(cp_a)); Assert.AreEqual(cp_a, r1.GetPath(1)); // now recreate, add a different category writer = new DirectoryTaxonomyWriter(dir, IndexWriterConfig.OpenMode_e.CREATE); FacetLabel cp_b = new FacetLabel("b"); writer.AddCategory(cp_b); writer.Dispose(); DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1); Assert.NotNull(r2); // fill r2's caches Assert.AreEqual(1, r2.GetOrdinal(cp_b)); Assert.AreEqual(cp_b, r2.GetPath(1)); // check that r1 doesn't see cp_b Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b)); Assert.AreEqual(cp_a, r1.GetPath(1)); // check that r2 doesn't see cp_a Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r2.GetOrdinal(cp_a)); Assert.AreEqual(cp_b, r2.GetPath(1)); (r2).Dispose(); (r1).Dispose(); dir.Dispose(); }
public virtual void TestConcurrency() { // tests that addTaxonomy and addCategory work in parallel int numCategories = AtLeast(10000); // build an input taxonomy index Directory src = NewDirectory(); var tw = new DirectoryTaxonomyWriter(src); for (int i = 0; i < numCategories; i++) { tw.AddCategory(new FacetLabel("a", Convert.ToString(i))); } tw.Dispose(); // now add the taxonomy to an empty taxonomy, while adding the categories // again, in parallel -- in the end, no duplicate categories should exist. Directory dest = NewDirectory(); var destTw = new DirectoryTaxonomyWriter(dest); ThreadClass t = new ThreadAnonymousInnerClassHelper2(this, numCategories, destTw); t.Start(); OrdinalMap map = new MemoryOrdinalMap(); destTw.AddTaxonomy(src, map); t.Join(); destTw.Dispose(); // now validate var dtr = new DirectoryTaxonomyReader(dest); // +2 to account for the root category + "a" Assert.AreEqual(numCategories + 2, dtr.Size); var categories = new HashSet<FacetLabel>(); for (int i = 1; i < dtr.Size; i++) { FacetLabel cat = dtr.GetPath(i); Assert.True(categories.Add(cat), "category " + cat + " already existed"); } dtr.Dispose(); IOUtils.Close(src, dest); }
public virtual void TestSimple() { Directory dest = NewDirectory(); var tw1 = new DirectoryTaxonomyWriter(dest); tw1.AddCategory(new FacetLabel("Author", "Mark Twain")); tw1.AddCategory(new FacetLabel("Animals", "Dog")); tw1.AddCategory(new FacetLabel("Author", "Rob Pike")); Directory src = NewDirectory(); var tw2 = new DirectoryTaxonomyWriter(src); tw2.AddCategory(new FacetLabel("Author", "Rob Pike")); tw2.AddCategory(new FacetLabel("Aardvarks", "Bob")); tw2.Dispose(); OrdinalMap map = randomOrdinalMap(); tw1.AddTaxonomy(src, map); tw1.Dispose(); validate(dest, src, map); IOUtils.Close(dest, src); }
public ThreadAnonymousInnerClassHelper(TestConcurrentFacetedIndexing outerInstance, AtomicInt32 numDocs, ConcurrentDictionary <string, string> values, IndexWriter iw, DirectoryTaxonomyWriter tw, FacetsConfig config) { this.outerInstance = outerInstance; this.numDocs = numDocs; this.values = values; this.iw = iw; this.tw = tw; this.config = config; }
/// <summary> /// Open for reading a taxonomy stored in a given <seealso cref="Directory"/>. /// </summary> /// <param name="directory"> /// The <seealso cref="Directory"/> in which the taxonomy resides. </param> /// <exception cref="CorruptIndexException"> /// if the Taxonomy is corrupt. </exception> /// <exception cref="IOException"> /// if another error occurred. </exception> public DirectoryTaxonomyReader(Directory directory) { indexReader = OpenIndexReader(directory); taxoWriter = null; taxoEpoch = -1; // These are the default cache sizes; they can be configured after // construction with the cache's setMaxSize() method ordinalCache = new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE); categoryCache = new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE); }
/// <summary> /// Opens a <seealso cref="DirectoryTaxonomyReader"/> over the given /// <seealso cref="DirectoryTaxonomyWriter"/> (for NRT). /// </summary> /// <param name="taxoWriter"> /// The <seealso cref="DirectoryTaxonomyWriter"/> from which to obtain newly /// added categories, in real-time. </param> public DirectoryTaxonomyReader(DirectoryTaxonomyWriter taxoWriter) { this.taxoWriter = taxoWriter; taxoEpoch = taxoWriter.TaxonomyEpoch; indexReader = OpenIndexReader(taxoWriter.InternalIndexWriter); // These are the default cache sizes; they can be configured after // construction with the cache's setMaxSize() method ordinalCache = new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE); categoryCache = new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE); }
public virtual void TestOpenIfChangedAndRefCount() { Directory dir = new RAMDirectory(); // no need for random directories here var taxoWriter = new DirectoryTaxonomyWriter(dir); taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.Commit(); var taxoReader = new DirectoryTaxonomyReader(dir); Assert.AreEqual(1, taxoReader.RefCount, "wrong refCount"); taxoReader.IncRef(); Assert.AreEqual(2, taxoReader.RefCount, "wrong refCount"); taxoWriter.AddCategory(new FacetLabel("a", "b")); taxoWriter.Commit(); var newtr = TaxonomyReader.OpenIfChanged(taxoReader); Assert.NotNull(newtr); taxoReader.Dispose(); taxoReader = newtr; Assert.AreEqual(1, taxoReader.RefCount, "wrong refCount"); taxoWriter.Dispose(); taxoReader.Dispose(); dir.Dispose(); }
public virtual void TestCloseTwice() { Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Dispose(); var ltr = new DirectoryTaxonomyReader(dir); (ltr).Dispose(); (ltr).Dispose(); // no exception should be thrown dir.Dispose(); }
public virtual void TestRollback() { // Verifies that if rollback is called, DTW is closed. Directory dir = NewDirectory(); var dtw = new DirectoryTaxonomyWriter(dir); dtw.AddCategory(new FacetLabel("a")); dtw.Rollback(); try { dtw.AddCategory(new FacetLabel("a")); Fail("should not have succeeded to add a category following rollback."); } catch (AlreadyClosedException) { // expected } dir.Dispose(); }
public virtual void TestOpenIfChangedReplaceTaxonomy() { // test openIfChanged when replaceTaxonomy is called, which is equivalent to recreate // only can work with NRT as well Directory src = NewDirectory(); DirectoryTaxonomyWriter w = new DirectoryTaxonomyWriter(src); FacetLabel cp_b = new FacetLabel("b"); w.AddCategory(cp_b); w.Dispose(); foreach (bool nrt in new bool[] { false, true }) { Directory dir = NewDirectory(); var writer = new DirectoryTaxonomyWriter(dir); FacetLabel cp_a = new FacetLabel("a"); writer.AddCategory(cp_a); if (!nrt) { writer.Commit(); } DirectoryTaxonomyReader r1 = nrt ? new DirectoryTaxonomyReader(writer) : new DirectoryTaxonomyReader(dir); // fill r1's caches Assert.AreEqual(1, r1.GetOrdinal(cp_a)); Assert.AreEqual(cp_a, r1.GetPath(1)); // now replace taxonomy writer.ReplaceTaxonomy(src); if (!nrt) { writer.Commit(); } DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1); Assert.NotNull(r2); // fill r2's caches Assert.AreEqual(1, r2.GetOrdinal(cp_b)); Assert.AreEqual(cp_b, r2.GetPath(1)); // check that r1 doesn't see cp_b Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b)); Assert.AreEqual(cp_a, r1.GetPath(1)); // check that r2 doesn't see cp_a Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r2.GetOrdinal(cp_a)); Assert.AreEqual(cp_b, r2.GetPath(1)); (r2).Dispose(); (r1).Dispose(); writer.Dispose(); dir.Dispose(); } src.Dispose(); }
public virtual void TestOpenIfChangedResult() { Directory dir = null; DirectoryTaxonomyWriter ltw = null; DirectoryTaxonomyReader ltr = null; try { dir = NewDirectory(); ltw = new DirectoryTaxonomyWriter(dir); ltw.AddCategory(new FacetLabel("a")); ltw.Commit(); ltr = new DirectoryTaxonomyReader(dir); Assert.Null(TaxonomyReader.OpenIfChanged(ltr), "Nothing has changed"); ltw.AddCategory(new FacetLabel("b")); ltw.Commit(); DirectoryTaxonomyReader newtr = TaxonomyReader.OpenIfChanged(ltr); Assert.NotNull(newtr, "changes were committed"); Assert.Null(TaxonomyReader.OpenIfChanged(newtr), "Nothing has changed"); (newtr).Dispose(); } finally { IOUtils.Close(ltw, ltr, dir); } }
private void doTestReadRecreatedTaxonomy(Random random, bool closeReader) { Directory dir = null; TaxonomyWriter tw = null; TaxonomyReader tr = null; // prepare a few categories int n = 10; FacetLabel[] cp = new FacetLabel[n]; for (int i = 0; i < n; i++) { cp[i] = new FacetLabel("a", Convert.ToString(i)); } try { dir = NewDirectory(); tw = new DirectoryTaxonomyWriter(dir); tw.AddCategory(new FacetLabel("a")); tw.Dispose(); tr = new DirectoryTaxonomyReader(dir); int baseNumCategories = tr.Size; for (int i = 0; i < n; i++) { int k = random.Next(n); tw = new DirectoryTaxonomyWriter(dir, IndexWriterConfig.OpenMode_e.CREATE); for (int j = 0; j <= k; j++) { tw.AddCategory(cp[j]); } tw.Dispose(); if (closeReader) { tr.Dispose(true); tr = new DirectoryTaxonomyReader(dir); } else { var newtr = TaxonomyReader.OpenIfChanged(tr); Assert.NotNull(newtr); tr.Dispose(true); tr = newtr; } Assert.AreEqual(baseNumCategories + 1 + k, tr.Size, "Wrong #categories in taxonomy (i=" + i + ", k=" + k + ")"); } } finally { IOUtils.Close(tr as DirectoryTaxonomyReader, tw, dir); } }
public virtual void TestCommit() { // Verifies that nothing is committed to the underlying Directory, if // commit() wasn't called. Directory dir = NewDirectory(); var ltw = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); Assert.False(DirectoryReader.IndexExists(dir)); ltw.Commit(); // first commit, so that an index will be created ltw.AddCategory(new FacetLabel("a")); IndexReader r = DirectoryReader.Open(dir); Assert.AreEqual(1, r.NumDocs, "No categories should have been committed to the underlying directory"); r.Dispose(); ltw.Dispose(); dir.Dispose(); }
public virtual void TestRecreateAndRefresh() { // DirTaxoWriter lost the INDEX_EPOCH property if it was opened in // CREATE_OR_APPEND (or commit(userData) called twice), which could lead to // DirTaxoReader succeeding to refresh(). Directory dir = NewDirectory(); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("a")); var taxoReader = new DirectoryTaxonomyReader(dir); TouchTaxo(taxoWriter, new FacetLabel("b")); var newtr = TaxonomyReader.OpenIfChanged(taxoReader); taxoReader.Dispose(); taxoReader = newtr; Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); // now recreate the taxonomy, and check that the epoch is preserved after opening DirTW again. taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("c")); taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); TouchTaxo(taxoWriter, new FacetLabel("d")); taxoWriter.Dispose(); newtr = TaxonomyReader.OpenIfChanged(taxoReader); taxoReader.Dispose(); taxoReader = newtr; Assert.AreEqual(2, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH])); taxoReader.Dispose(); dir.Dispose(); }
public virtual void TestCommitUserData() { // Verifies taxonomy commit data Directory dir = NewDirectory(); var taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.AddCategory(new FacetLabel("b")); IDictionary<string, string> userCommitData = new Dictionary<string, string>(); userCommitData["testing"] = "1 2 3"; taxoWriter.CommitData = userCommitData; taxoWriter.Dispose(); var r = DirectoryReader.Open(dir); Assert.AreEqual(3, r.NumDocs, "2 categories plus root should have been committed to the underlying directory"); var readUserCommitData = r.IndexCommit.UserData; Assert.True("1 2 3".Equals(readUserCommitData["testing"]), "wrong value extracted from commit data"); Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in commitData", readUserCommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]); r.Dispose(); // open DirTaxoWriter again and commit, INDEX_EPOCH should still exist // in the commit data, otherwise DirTaxoReader.refresh() might not detect // that the taxonomy index has been recreated. taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE); taxoWriter.AddCategory(new FacetLabel("c")); // add a category so that commit will happen taxoWriter.CommitData = new Dictionary<string, string>() { {"just", "data"} }; taxoWriter.Commit(); // verify taxoWriter.getCommitData() Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in taoxWriter.commitData", taxoWriter.CommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]); taxoWriter.Dispose(); r = DirectoryReader.Open(dir); readUserCommitData = r.IndexCommit.UserData; Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in commitData", readUserCommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]); r.Dispose(); dir.Dispose(); }
public virtual void TestConcurrency() { int ncats = AtLeast(100000); // add many categories int range = ncats * 3; // affects the categories selection AtomicInteger numCats = new AtomicInteger(ncats); Directory dir = NewDirectory(); var values = new ConcurrentDictionary<string, string>(); double d = Random().NextDouble(); ITaxonomyWriterCache cache; if (d < 0.7) { // this is the fastest, yet most memory consuming cache = new Cl2oTaxonomyWriterCache(1024, 0.15f, 3); } else if (TEST_NIGHTLY && d > 0.98) { // this is the slowest, but tests the writer concurrency when no caching is done. // only pick it during NIGHTLY tests, and even then, with very low chances. cache = NO_OP_CACHE; } else { // this is slower than CL2O, but less memory consuming, and exercises finding categories on disk too. cache = new LruTaxonomyWriterCache(ncats / 10); } if (VERBOSE) { Console.WriteLine("TEST: use cache=" + cache); } var tw = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, cache); ThreadClass[] addThreads = new ThreadClass[AtLeast(4)]; for (int z = 0; z < addThreads.Length; z++) { addThreads[z] = new ThreadAnonymousInnerClassHelper(this, range, numCats, values, tw); } foreach (var t in addThreads) { t.Start(); } foreach (var t in addThreads) { t.Join(); } tw.Dispose(); DirectoryTaxonomyReader dtr = new DirectoryTaxonomyReader(dir); // +1 for root category if (values.Count + 1 != dtr.Count) { foreach (string value in values.Keys) { FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value)); if (dtr.GetOrdinal(label) == -1) { Console.WriteLine("FAIL: path=" + label + " not recognized"); } } Fail("mismatch number of categories"); } int[] parents = dtr.ParallelTaxonomyArrays.Parents; foreach (string cat in values.Keys) { FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat)); Assert.True(dtr.GetOrdinal(cp) > 0, "category not found " + cp); int level = cp.Length; int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0) FacetLabel path = new FacetLabel(); for (int i = 0; i < level; i++) { path = cp.Subpath(i + 1); int ord = dtr.GetOrdinal(path); Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path); parentOrd = ord; // next level should have this parent } } IOUtils.Close(dtr, dir); }
public virtual void TestConcurrency() { int ncats = AtLeast(100000); // add many categories int range = ncats * 3; // affects the categories selection AtomicInteger numCats = new AtomicInteger(ncats); Directory dir = NewDirectory(); var values = new ConcurrentDictionary <string, string>(); double d = Random().NextDouble(); TaxonomyWriterCache cache; if (d < 0.7) { // this is the fastest, yet most memory consuming cache = new Cl2oTaxonomyWriterCache(1024, 0.15f, 3); } else if (TEST_NIGHTLY && d > 0.98) { // this is the slowest, but tests the writer concurrency when no caching is done. // only pick it during NIGHTLY tests, and even then, with very low chances. cache = NO_OP_CACHE; } else { // this is slower than CL2O, but less memory consuming, and exercises finding categories on disk too. cache = new LruTaxonomyWriterCache(ncats / 10); } if (VERBOSE) { Console.WriteLine("TEST: use cache=" + cache); } var tw = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, cache); ThreadClass[] addThreads = new ThreadClass[AtLeast(4)]; for (int z = 0; z < addThreads.Length; z++) { addThreads[z] = new ThreadAnonymousInnerClassHelper(this, range, numCats, values, tw); } foreach (var t in addThreads) { t.Start(); } foreach (var t in addThreads) { t.Join(); } tw.Dispose(); DirectoryTaxonomyReader dtr = new DirectoryTaxonomyReader(dir); // +1 for root category if (values.Count + 1 != dtr.Size) { foreach (string value in values.Keys) { FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value)); if (dtr.GetOrdinal(label) == -1) { Console.WriteLine("FAIL: path=" + label + " not recognized"); } } Fail("mismatch number of categories"); } int[] parents = dtr.ParallelTaxonomyArrays.Parents(); foreach (string cat in values.Keys) { FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat)); Assert.True(dtr.GetOrdinal(cp) > 0, "category not found " + cp); int level = cp.Length; int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0) FacetLabel path = new FacetLabel(); for (int i = 0; i < level; i++) { path = cp.Subpath(i + 1); int ord = dtr.GetOrdinal(path); Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path); parentOrd = ord; // next level should have this parent } } IOUtils.Close(dtr, dir); }
public virtual void TestReplaceTaxonomy() { Directory input = NewDirectory(); var taxoWriter = new DirectoryTaxonomyWriter(input); int ordA = taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.Dispose(); Directory dir = NewDirectory(); taxoWriter = new DirectoryTaxonomyWriter(dir); int ordB = taxoWriter.AddCategory(new FacetLabel("b")); taxoWriter.AddCategory(new FacetLabel("c")); taxoWriter.Commit(); long origEpoch = getEpoch(dir); // replace the taxonomy with the input one taxoWriter.ReplaceTaxonomy(input); // LUCENE-4633: make sure that category "a" is not added again in any case taxoWriter.AddTaxonomy(input, new MemoryOrdinalMap()); Assert.AreEqual(2, taxoWriter.Count, "no categories should have been added"); // root + 'a' Assert.AreEqual(ordA, taxoWriter.AddCategory(new FacetLabel("a")), "category 'a' received new ordinal?"); // add the same category again -- it should not receive the same ordinal ! int newOrdB = taxoWriter.AddCategory(new FacetLabel("b")); Assert.AreNotSame(ordB, newOrdB, "new ordinal cannot be the original ordinal"); Assert.AreEqual(2, newOrdB, "ordinal should have been 2 since only one category was added by replaceTaxonomy"); taxoWriter.Dispose(); long newEpoch = getEpoch(dir); Assert.True(origEpoch < newEpoch, "index epoch should have been updated after replaceTaxonomy"); dir.Dispose(); input.Dispose(); }
public virtual void TestConcurrency() { AtomicInt32 numDocs = new AtomicInt32(AtLeast(10000)); Directory indexDir = NewDirectory(); Directory taxoDir = NewDirectory(); ConcurrentDictionary <string, string> values = new ConcurrentDictionary <string, string>(); IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, null)); var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE, NewTaxoWriterCache(numDocs)); ThreadJob[] indexThreads = new ThreadJob[AtLeast(4)]; FacetsConfig config = new FacetsConfig(); for (int i = 0; i < 10; i++) { config.SetHierarchical("l1." + i, true); config.SetMultiValued("l1." + i, true); } for (int i = 0; i < indexThreads.Length; i++) { indexThreads[i] = new ThreadAnonymousInnerClassHelper(this, numDocs, values, iw, tw, config); } foreach (ThreadJob t in indexThreads) { t.Start(); } foreach (ThreadJob t in indexThreads) { t.Join(); } var tr = new DirectoryTaxonomyReader(tw); // +1 for root category if (values.Count + 1 != tr.Count) { foreach (string value in values.Keys) { FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value)); if (tr.GetOrdinal(label) == -1) { Console.WriteLine("FAIL: path=" + label + " not recognized"); } } fail("mismatch number of categories"); } int[] parents = tr.ParallelTaxonomyArrays.Parents; foreach (string cat in values.Keys) { FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat)); Assert.IsTrue(tr.GetOrdinal(cp) > 0, "category not found " + cp); int level = cp.Length; int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0) FacetLabel path = null; for (int i = 0; i < level; i++) { path = cp.Subpath(i + 1); int ord = tr.GetOrdinal(path); Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path); parentOrd = ord; // next level should have this parent } } IOUtils.Dispose(tw, iw, tr, taxoDir, indexDir); }
public virtual void TestReaderFreshness() { // ensures that the internal index reader is always kept fresh. Previously, // this simple scenario failed, if the cache just evicted the category that // is being added. Directory dir = NewDirectory(); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, NO_OP_CACHE); int o1 = taxoWriter.AddCategory(new FacetLabel("a")); int o2 = taxoWriter.AddCategory(new FacetLabel("a")); Assert.True(o1 == o2, "ordinal for same category that is added twice should be the same !"); taxoWriter.Dispose(); dir.Dispose(); }
public ThreadAnonymousClass2(TestAddTaxonomy outerInstance, int numCategories, DirectoryTaxonomyWriter destTW) { this.outerInstance = outerInstance; this.numCategories = numCategories; this.destTW = destTW; }
public virtual void TestPrepareCommitNoEmptyCommits() { // LUCENE-4972: DTW used to create empty commits even if no changes were made Directory dir = NewDirectory(); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir); taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.PrepareCommit(); taxoWriter.Commit(); long gen1 = SegmentInfos.GetLastCommitGeneration(dir); taxoWriter.PrepareCommit(); taxoWriter.Commit(); long gen2 = SegmentInfos.GetLastCommitGeneration(dir); Assert.AreEqual(gen1, gen2, "empty commit should not have changed the index"); taxoWriter.Dispose(); dir.Dispose(); }
public ThreadAnonymousInnerClassHelper(TestAddTaxonomy outerInstance, int range, AtomicInt32 numCats, DirectoryTaxonomyWriter tw) { this.outerInstance = outerInstance; this.range = range; this.numCats = numCats; this.tw = tw; }
public virtual void TestHugeLabel() { Directory indexDir = NewDirectory(), taxoDir = NewDirectory(); IndexWriter indexWriter = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()))); DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE, new Cl2oTaxonomyWriterCache(2, 1f, 1)); FacetsConfig config = new FacetsConfig(); // Add one huge label: string bigs = null; int ordinal = -1; int len = FacetLabel.MAX_CATEGORY_PATH_LENGTH - 4; // for the dimension and separator bigs = TestUtil.RandomSimpleString(Random(), len, len); FacetField ff = new FacetField("dim", bigs); FacetLabel cp = new FacetLabel("dim", bigs); ordinal = taxoWriter.AddCategory(cp); Document doc = new Document(); doc.Add(ff); indexWriter.AddDocument(config.Build(taxoWriter, doc)); // Add tiny ones to cause a re-hash for (int i = 0; i < 3; i++) { string s = TestUtil.RandomSimpleString(Random(), 1, 10); taxoWriter.AddCategory(new FacetLabel("dim", s)); doc = new Document(); doc.Add(new FacetField("dim", s)); indexWriter.AddDocument(config.Build(taxoWriter, doc)); } // when too large components were allowed to be added, this resulted in a new added category Assert.AreEqual(ordinal, taxoWriter.AddCategory(cp)); IOUtils.Close(indexWriter, taxoWriter); DirectoryReader indexReader = DirectoryReader.Open(indexDir); var taxoReader = new DirectoryTaxonomyReader(taxoDir); IndexSearcher searcher = new IndexSearcher(indexReader); DrillDownQuery ddq = new DrillDownQuery(new FacetsConfig()); ddq.Add("dim", bigs); Assert.AreEqual(1, searcher.Search(ddq, 10).TotalHits); IOUtils.Close(indexReader, taxoReader, indexDir, taxoDir); }
public ThreadAnonymousInnerClassHelper(int range, AtomicInt32 numCats, ConcurrentDictionary <string, string> values, DirectoryTaxonomyWriter tw) { this.range = range; this.numCats = numCats; this.values = values; this.tw = tw; }
public virtual void TestReplaceTaxoWithLargeTaxonomy() { var srcTaxoDir = NewDirectory(); var targetTaxoDir = NewDirectory(); // build source, large, taxonomy DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(srcTaxoDir); int ord = taxoWriter.AddCategory(new FacetLabel("A", "1", "1", "1", "1", "1", "1")); taxoWriter.Dispose(); taxoWriter = new DirectoryTaxonomyWriter(targetTaxoDir); int ordinal = taxoWriter.AddCategory(new FacetLabel("B", "1")); Assert.AreEqual(1, taxoWriter.GetParent(ordinal)); // call getParent to initialize taxoArrays taxoWriter.Commit(); taxoWriter.ReplaceTaxonomy(srcTaxoDir); Assert.AreEqual(ord - 1, taxoWriter.GetParent(ord)); taxoWriter.Dispose(); srcTaxoDir.Dispose(); targetTaxoDir.Dispose(); }
public virtual void TestEnsureOpen() { // verifies that an exception is thrown if DTW was closed Directory dir = NewDirectory(); DirectoryTaxonomyWriter dtw = new DirectoryTaxonomyWriter(dir); dtw.Dispose(); try { dtw.AddCategory(new FacetLabel("a")); Fail("should not have succeeded to add a category following close."); } catch (AlreadyClosedException) { // expected } dir.Dispose(); }
public virtual void TestOpenIfChangedReuse() { // test the reuse of data from the old DTR instance foreach (bool nrt in new bool[] { false, true }) { Directory dir = NewDirectory(); DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(dir); FacetLabel cp_a = new FacetLabel("a"); writer.AddCategory(cp_a); if (!nrt) { writer.Commit(); } DirectoryTaxonomyReader r1 = nrt ? new DirectoryTaxonomyReader(writer) : new DirectoryTaxonomyReader(dir); // fill r1's caches Assert.AreEqual(1, r1.GetOrdinal(cp_a)); Assert.AreEqual(cp_a, r1.GetPath(1)); FacetLabel cp_b = new FacetLabel("b"); writer.AddCategory(cp_b); if (!nrt) { writer.Commit(); } DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1); Assert.NotNull(r2); // add r2's categories to the caches Assert.AreEqual(2, r2.GetOrdinal(cp_b)); Assert.AreEqual(cp_b, r2.GetPath(2)); // check that r1 doesn't see cp_b Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b)); Assert.Null(r1.GetPath(2)); (r1).Dispose(); (r2).Dispose(); writer.Dispose(); dir.Dispose(); } }
public virtual void TestGetChildren() { Directory dir = NewDirectory(); var taxoWriter = new DirectoryTaxonomyWriter(dir); int numCategories = AtLeast(10); int numA = 0, numB = 0; Random random = Random; // add the two categories for which we'll also add children (so asserts are simpler) taxoWriter.AddCategory(new FacetLabel("a")); taxoWriter.AddCategory(new FacetLabel("b")); for (int i = 0; i < numCategories; i++) { if (random.NextBoolean()) { taxoWriter.AddCategory(new FacetLabel("a", Convert.ToString(i, CultureInfo.InvariantCulture))); ++numA; } else { taxoWriter.AddCategory(new FacetLabel("b", Convert.ToString(i, CultureInfo.InvariantCulture))); ++numB; } } // add category with no children taxoWriter.AddCategory(new FacetLabel("c")); taxoWriter.Dispose(); var taxoReader = new DirectoryTaxonomyReader(dir); // non existing category TaxonomyReader.ChildrenEnumerator it = taxoReader.GetChildren(taxoReader.GetOrdinal(new FacetLabel("invalid"))); Assert.AreEqual(false, it.MoveNext()); // a category with no children it = taxoReader.GetChildren(taxoReader.GetOrdinal(new FacetLabel("c"))); Assert.AreEqual(false, it.MoveNext()); // arbitrary negative ordinal it = taxoReader.GetChildren(-2); Assert.AreEqual(false, it.MoveNext()); // root's children var roots = new JCG.HashSet <string> { "a", "b", "c" }; it = taxoReader.GetChildren(TaxonomyReader.ROOT_ORDINAL); while (roots.Count > 0) { it.MoveNext(); FacetLabel root = taxoReader.GetPath(it.Current); Assert.AreEqual(1, root.Length); Assert.IsTrue(roots.Remove(root.Components[0])); } Assert.AreEqual(false, it.MoveNext()); for (int i = 0; i < 2; i++) { FacetLabel cp = i == 0 ? new FacetLabel("a") : new FacetLabel("b"); int ordinal = taxoReader.GetOrdinal(cp); it = taxoReader.GetChildren(ordinal); int numChildren = 0; int child; while (it.MoveNext()) { child = it.Current; FacetLabel path = taxoReader.GetPath(child); Assert.AreEqual(2, path.Length); Assert.AreEqual(path.Components[0], i == 0 ? "a" : "b"); ++numChildren; } int expected = i == 0 ? numA : numB; Assert.AreEqual(expected, numChildren, "invalid num children"); } taxoReader.Dispose(); dir.Dispose(); }
private void TouchTaxo(DirectoryTaxonomyWriter taxoWriter, FacetLabel cp) { taxoWriter.AddCategory(cp); taxoWriter.CommitData = new Dictionary<string, string>() { {"just", "data"} }; taxoWriter.Commit(); }