Lucene で simpleTextCodec がどう生成されるか見てみる。

Lucene の IndexWriter を最初に調べていた時、codec とか意味不明な言葉が多すぎたので、まとめました。

## Lucene の simpleTextCodec とは？
lucene の Codec とは、インデックスファイルの読み書きをする機能ですが、その中でも一番内容が分かりやすいものになっているのが、simpleTextCodec です。
lucene では addDocument をする度に、インデックスファイルの内容を初期化かするかファイルの内容を追加します。
simpleTextCodec は例えば下のようなコードをすると、`lucene-plaintext` のフォルダの直下にインデックスファイルが生成されます。これを行っているのが、simpleTextCodec です。
※ ちなみに、デフォルトの codec である lucene94 (2022/10/23現在)ではインデックスファイルはバイナリファイルになります。

```diff_java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.io.IOException;
import java.io.File;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.codecs.simpletext.SimpleTextCodec;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class LuceneSimpleTextCodecSample {

	private static File plaintextDir;
	private static String INDEX_ROOT_FOLDER = "/Users/coffeecup/Documents/programming/Java/lucene/lucene/lucene/core/src/java";

    private static File assureDirectoryExists(File dir) {
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir;
    }

	public static void main(String[] args) {
		plaintextDir = assureDirectoryExists(new File(INDEX_ROOT_FOLDER, "lucene-plaintext"));
		// 
		StandardAnalyzer analyzer = new StandardAnalyzer();
		IndexWriterConfig config = new IndexWriterConfig(analyzer);
		config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+		config.setCodec(new SimpleTextCodec());
		try {
			Directory luceneDir = FSDirectory.open(plaintextDir.toPath());
			IndexWriter writer = new IndexWriter(luceneDir, config);
			writer.addDocument(Arrays.asList(
					new TextField("title", "The title of my first document", Store.YES),
					new TextField("content", "The content of the first document", Store.YES)
			));
			writer.addDocument(Arrays.asList(
					new TextField("title", "The title of my second document", Store.YES),
					new TextField("content", "The content of the second document", Store.YES)
			));
			writer.close();
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}
```

このファイルを実行すると、

<details><summary>simpleTextCodecが出力するファイル</summary>

```
cfs entry for: _b.fld
doc 0
  field 0
    name title
    type string
    value The title of my first document
  field 1
    name content
    type string
    value The content of the first document
doc 1
  field 0
    name title
    type string
    value The title of my second document
  field 1
    name content
    type string
    value The content of the second document
END
checksum 00000000003384965024
cfs entry for: _b.inf
number of fields 2
  name title
  number 0
  index options DOCS_AND_FREQS_AND_POSITIONS
  term vectors false
  payloads false
  norms true
  doc values NONE
  doc values gen -1
  attributes 0
  data dimensional count 0
  index dimensional count 0
  dimensional num bytes 0
  vector number of dimensions 0
  vector encoding FLOAT32
  vector similarity EUCLIDEAN
  soft-deletes false
  name content
  number 1
  index options DOCS_AND_FREQS_AND_POSITIONS
  term vectors false
  payloads false
  norms true
  doc values NONE
  doc values gen -1
  attributes 0
  data dimensional count 0
  index dimensional count 0
  dimensional num bytes 0
  vector number of dimensions 0
  vector encoding FLOAT32
  vector similarity EUCLIDEAN
  soft-deletes false
checksum 00000000002738111167
cfs entry for: _b.len
field title
  type NUMERIC
  minvalue 6
  pattern 0
0
T
0
T
field content
  type NUMERIC
  minvalue 6
  pattern 0
0
T
0
T
END
checksum 00000000003922424621
cfs entry for: _b.pst
field content
  term content
    doc 0
      freq 1
      pos 1
    doc 1
      freq 1
      pos 1
  term document
    doc 0
      freq 1
      pos 5
    doc 1
      freq 1
      pos 5
  term first
    doc 0
      freq 1
      pos 4
  term of
    doc 0
      freq 1
      pos 2
    doc 1
      freq 1
      pos 2
  term second
    doc 1
      freq 1
      pos 4
  term the
    doc 0
      freq 2
      pos 0
      pos 3
    doc 1
      freq 2
      pos 0
      pos 3
field title
  term document
    doc 0
      freq 1
      pos 5
    doc 1
      freq 1
      pos 5
  term first
    doc 0
      freq 1
      pos 4
  term my
    doc 0
      freq 1
      pos 3
    doc 1
      freq 1
      pos 3
  term of
    doc 0
      freq 1
      pos 2
    doc 1
      freq 1
      pos 2
  term second
    doc 1
      freq 1
      pos 4
  term the
    doc 0
      freq 1
      pos 0
    doc 1
      freq 1
      pos 0
  term title
    doc 0
      freq 1
      pos 1
    doc 1
      freq 1
      pos 1
END
checksum 00000000001990244897
table of contents, size: 4
  filename: _b.fld
    start: 22
    end: 408
  filename: _b.inf
    start: 430
    end: 1207
  filename: _b.len
    start: 1229
    end: 1385
  filename: _b.pst
    start: 1407
    end: 2427
table of contents begins at offset: 0000000000000002427
```

</details>

のようなファイルが出力されます。

```
field title
  term document
    doc 0
      freq 1
      pos 5
    doc 1
      freq 1
      pos 5
```

のような部分が転置インデックスを示していますが、今回の記事では、このファイルの転置インデックス部分がどう生成されているかを見てみます。

## writer.AddDocument

このインデックスファイルへの書き込みを行っているのが、`writer.AddDocument` になりますが、その中身は [この記事など](https://po3rin.com/blog/indexwriter-internal) を見れば、 [IndexWriter.java](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/core/src/java/org/apache/lucene/index/IndexWriter.java) から始まり [IndexingChain.java](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/core/src/java/org/apache/lucene/index/IndexingChain.java) に実装が書いてあることが分かります。その実体は、[この記事の最後の方](https://blog.foresta.me/posts/read-apache-lucene-index/) を見れば分かりますが、term の文字列のバイトコードを 突っ込んだbyte[] とそれに対応する textStart[termID] の 配列になります。

では、実際に読んでみます。
まずは、[IndexingChain.java](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/core/src/java/org/apache/lucene/index/IndexingChain.java) の processDocument が DocumentsWriterPerThread から引き継いだ関数でした。この processDocument を見ると、最初に startStoredFields をしています。この startStoredFields の中の `storedFieldsConsumer` でファイルへの書き込みをしています。

```diff_java:IndexingChain.java の 500〜507行目
  private void startStoredFields(int docID) throws IOException {
    try {
+     storedFieldsConsumer.startDocument(docID);
    } catch (Throwable th) {
      onAbortingException(th);
      throw th;
    }
  }
```

[storedFieldsConsumer](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/core/src/java/org/apache/lucene/index/StoredFieldsConsumer.java) の startDocument では、codecs の writer を初期化し、` writer.startDocument();` を呼び出しています。この storedFieldsConsumer の初期化を IndexingChain で見ると、

```java
      storedFieldsConsumer =
          new StoredFieldsConsumer(indexWriterConfig.getCodec(), directory, segmentInfo);
```

のように、`config.setCodec(new SimpleTextCodec());` で simpleTextCodec に設定した codec が `indexWriterConfig.getCodec()` で得られる形になるので、ここでは デフォルトの lucene94 ではなくて、simpleTextCodec を使うことになります。

この simpleTextCodec の storedFieldWriter があるのが、[SimpleTextStoredFieldsWriter.java](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextStoredFieldsWriter.java) になって、ここの `startDocument` が ` storedFieldsConsumer.startDocument(docID)` の中の ` writer.startDocument();` になります。

```java
  @Override
  public void startDocument() throws IOException {
    write(DOC);
    write(Integer.toString(numDocsWritten));
    newLine();

    numDocsWritten++;
  }
```

これと同じ具合で、`processField` の中の `storedFieldsConsumer` を追うと、startDocument と writeField と finishDocument があります。

その[実装](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/core/src/java/org/apache/lucene/index/StoredFieldsConsumer.java)の、実体が [ここ](https://github.com/apache/lucene/blob/44b4602776a63d93896d887e5000c9e3ca7c3119/lucene/codecs/src/java/org/apache/lucene/codecs/simpletext/SimpleTextStoredFieldsWriter.java)にあります。

内容は見てみると簡単なので、是非とも見てみてはいかがでしょうか？