From 75bf47f14526897c6a36837785aa26110cc67d3b Mon Sep 17 00:00:00 2001 From: "r.hwang" Date: Wed, 28 Sep 2022 14:51:51 -0400 Subject: [PATCH 1/6] KoreanAnalyzer feature added The current released version of Lucenenet does not implement the Korean Analyzer as it does in Java. This commit serves to port over the logic from the Java repo to C# however it only contains logic for the Analyzer class. --- .../Analysis/Ko/DecompoundToken.cs | 45 + .../Analysis/Ko/Dict/BinaryDictionary.cs | 316 +++++ .../Analysis/Ko/Dict/CharacterDefinition.cs | 110 ++ .../Analysis/Ko/Dict/CharacterDefinition.dat | Bin 0 -> 65564 bytes .../Analysis/Ko/Dict/ConnectionCosts.cs | 72 ++ .../Analysis/Ko/Dict/ConnectionCosts.dat | Bin 0 -> 11178837 bytes .../Analysis/Ko/Dict/Dictionary.cs | 59 + .../Ko/Dict/TokenInfoDictionary$buffer.dat | Bin 0 -> 7245613 bytes .../Ko/Dict/TokenInfoDictionary$fst.dat | Bin 0 -> 6009053 bytes .../Ko/Dict/TokenInfoDictionary$posDict.dat | Bin 0 -> 2712 bytes .../Ko/Dict/TokenInfoDictionary$targetMap.dat | Bin 0 -> 811783 bytes .../Analysis/Ko/Dict/TokenInfoDictionary.cs | 46 + .../Analysis/Ko/Dict/TokenInfoFST.cs | 85 ++ .../Ko/Dict/UnknownDictionary$buffer.dat | Bin 0 -> 101 bytes .../Ko/Dict/UnknownDictionary$posDict.dat | Bin 0 -> 1823 bytes .../Ko/Dict/UnknownDictionary$targetMap.dat | Bin 0 -> 36 bytes .../Analysis/Ko/Dict/UnknownDictionary.cs | 72 ++ .../Analysis/Ko/Dict/UserDictionary.cs | 229 ++++ .../Analysis/Ko/DictionaryToken.cs | 81 ++ .../Analysis/Ko/GraphvizFormatter.cs | 224 ++++ .../Analysis/Ko/KoreanAnalyzer.cs | 41 + .../Ko/KoreanPartOfSpeechStopFilter.cs | 53 + .../Analysis/Ko/KoreanReadingFormFilter.cs | 48 + .../Analysis/Ko/KoreanTokenizer.cs | 1119 +++++++++++++++++ .../Analysis/Ko/POS.cs | 163 +++ .../Analysis/Ko/Token.cs | 107 ++ .../TokenAttributes/PartOfSpeechAttributes.cs | 50 + .../PartsOfSpeechAttributesImpl.cs | 82 ++ .../Ko/TokenAttributes/ReadingAttributes.cs | 34 + .../TokenAttributes/ReadingAttributesImpl.cs | 52 + .../Analysis/Util/StopwordAnalyzerBase.cs | 2 +- .../Lucene.Net.Analysis.Common.csproj | 5 + 32 files changed, 3094 insertions(+), 1 deletion(-) create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/DecompoundToken.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/BinaryDictionary.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/Dictionary.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoDictionary$buffer.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoDictionary$fst.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoDictionary$posDict.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoDictionary$targetMap.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoDictionary.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/TokenInfoFST.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/UnknownDictionary$buffer.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/UnknownDictionary$posDict.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/UnknownDictionary$targetMap.dat create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/UnknownDictionary.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/UserDictionary.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/DictionaryToken.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/GraphvizFormatter.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/KoreanAnalyzer.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/KoreanPartOfSpeechStopFilter.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/KoreanReadingFormFilter.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/KoreanTokenizer.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/POS.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/Token.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/TokenAttributes/PartOfSpeechAttributes.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/TokenAttributes/PartsOfSpeechAttributesImpl.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/TokenAttributes/ReadingAttributes.cs create mode 100644 src/Lucene.Net.Analysis.Common/Analysis/Ko/TokenAttributes/ReadingAttributesImpl.cs diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/DecompoundToken.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ko/DecompoundToken.cs new file mode 100644 index 0000000000..952513292a --- /dev/null +++ b/src/Lucene.Net.Analysis.Common/Analysis/Ko/DecompoundToken.cs @@ -0,0 +1,45 @@ +using Lucene.Net.Analysis.Ko.Dict; + +namespace Lucene.Net.Analysis.Ko +{ + public class DecompoundToken : Token + { + private readonly POS.Tag posTag; + + public DecompoundToken(POS.Tag posTag, char[] surfaceForm, int startOffset, int endOffset) + : base(surfaceForm, 0, surfaceForm.Length, startOffset, endOffset) + { + this.posTag = posTag; + } + + public override string ToString() { + return "DecompoundToken(\"" + GetSurfaceForm + "\" pos=" + GetStartOffset() + " length=" + GetLength + + " startOffset=" + GetStartOffset() + " endOffset=" + GetEndOffset() + ")"; + } + + public override POS.Type GetPOSType() + { + return POS.Type.MORPHEME; + } + + public override POS.Tag GetLeftPOS() + { + return posTag; + } + + public override POS.Tag GetRightPOS() + { + return posTag; + } + + public override string GetReading() + { + return null; + } + + public override IDictionary.Morpheme[] GetMorphemes() + { + return null; + } + } +} \ No newline at end of file diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/BinaryDictionary.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/BinaryDictionary.cs new file mode 100644 index 0000000000..ec8dd735d5 --- /dev/null +++ b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/BinaryDictionary.cs @@ -0,0 +1,316 @@ +using J2N; +using J2N.IO; +using J2N.Numerics; +using Lucene.Net.Codecs; +using Lucene.Net.Store; +using Lucene.Net.Util; +using System; +using System.IO; +using System.Reflection; +using System.Security; + +namespace Lucene.Net.Analysis.Ko.Dict +{ + public abstract class BinaryDictionary : IDictionary + { + public enum ResourceScheme { + CLASSPATH, FILE + } + + public static readonly string DICT_FILENAME_SUFFIX = "$buffer.dat"; + public static readonly string TARGETMAP_FILENAME_SUFFIX = "$targetMap.dat"; + public static readonly string POSDICT_FILENAME_SUFFIX = "$posDict.dat"; + + public static readonly string DICT_HEADER = "ko_dict"; + public static readonly string TARGETMAP_HEADER = "ko_dict_map"; + public static readonly string POSDICT_HEADER = "ko_dict_pos"; + public static readonly int VERSION = 1; + + private readonly ResourceScheme resourceScheme; + private readonly string resourcePath; + private readonly ByteBuffer buffer; + private readonly int[] targetMapOffsets, targetMap; + private readonly POS.Tag[] posDict; + + // LUCENENET specific - variable to hold the name of the data directory (or empty string to load embedded resources) + private static readonly string DATA_DIR = LoadDataDir(); + + // LUCENENET specific - name of the subdirectory inside of the directory where the Kuromoji dictionary files reside. + private const string DATA_SUBDIR = "ko-data"; + + private static string LoadDataDir() + { + // LUCENENET specific - reformatted with :, renamed from "analysis.data.dir" + string currentPath = SystemProperties.GetProperty("ko:data:dir", AppDomain.CurrentDomain.BaseDirectory); + + // If a matching directory path is found, set our DATA_DIR static + // variable. If it is null or empty after this process, we need to + // load the embedded files. + string candidatePath = System.IO.Path.Combine(currentPath, DATA_SUBDIR); + if (System.IO.Directory.Exists(candidatePath)) + { + return candidatePath; + } + + while (new DirectoryInfo(currentPath).Parent != null) + { + try + { + candidatePath = System.IO.Path.Combine(new DirectoryInfo(currentPath).Parent.FullName, DATA_SUBDIR); + if (System.IO.Directory.Exists(candidatePath)) + { + return candidatePath; + } + + currentPath = new DirectoryInfo(currentPath).Parent.FullName; + } + catch (SecurityException) + { + // ignore security errors + } + } + + return null; // This is the signal to load from local resources + } + + protected BinaryDictionary() + { + int[] targetMapOffsets = null, targetMap = null; + POS.Tag[] posDict = null; + ByteBuffer buffer; // LUCENENET: IDE0059: Remove unnecessary value assignment + + using (Stream mapIS = GetResource(TARGETMAP_FILENAME_SUFFIX)) + { + DataInput @in = new InputStreamDataInput(mapIS); + CodecUtil.CheckHeader(@in, TARGETMAP_HEADER, VERSION, VERSION); + targetMap = new int[@in.ReadVInt32()]; + targetMapOffsets = new int[@in.ReadVInt32()]; + int accum = 0, sourceId = 0; + for (int ofs = 0; ofs < targetMap.Length; ofs++) + { + int val = @in.ReadVInt32(); + if ((val & 0x01) != 0) + { + targetMapOffsets[sourceId] = ofs; + sourceId++; + } + + accum += val.TripleShift(1); + targetMap[ofs] = accum; + } + + if (sourceId + 1 != targetMapOffsets.Length) + throw new IOException("targetMap file format broken"); + targetMapOffsets[sourceId] = targetMap.Length; + } + + using (Stream posIS = GetResource(POSDICT_FILENAME_SUFFIX)) + { + DataInput @in = new InputStreamDataInput(posIS); + CodecUtil.CheckHeader(@in, POSDICT_HEADER, VERSION, VERSION); + int posSize = @in.ReadVInt32(); + posDict = new POS.Tag[posSize]; + for (int j = 0; j < posSize; j++) + { + posDict[j] = POS.ResolveTag(@in.ReadByte()); + } + } + + ByteBuffer tmpBuffer; + + using (Stream dictIS = GetResource(DICT_FILENAME_SUFFIX)) + { + // no buffering here, as we load in one large buffer + DataInput @in = new InputStreamDataInput(dictIS); + CodecUtil.CheckHeader(@in, DICT_HEADER, VERSION, VERSION); + int size = @in.ReadVInt32(); + tmpBuffer = ByteBuffer.Allocate(size); // AllocateDirect..? + int read = dictIS.Read(tmpBuffer.Array, 0, size); + if (read != size) + { + throw EOFException.Create("Cannot read whole dictionary"); + } + } + + buffer = tmpBuffer.AsReadOnlyBuffer(); + + this.targetMap = targetMap; + this.targetMapOffsets = targetMapOffsets; + this.posDict = posDict; + this.buffer = buffer; + } + + protected Stream GetResource(string suffix) + { + return GetTypeResource(GetType(), suffix); + } + + // util, reused by ConnectionCosts and CharacterDefinition + public static Stream GetTypeResource(Type clazz, string suffix) + { + string fileName = clazz.Name + suffix; + + // LUCENENET specific: Rather than forcing the end user to recompile if they want to use a custom dictionary, + // we load the data from the kuromoji-data directory (which can be set via the kuromoji.data.dir environment variable). + if (string.IsNullOrEmpty(DATA_DIR)) + { + Stream @is = clazz.FindAndGetManifestResourceStream(fileName); + if (@is is null) + throw new FileNotFoundException("Not in assembly: " + clazz.FullName + suffix); + return @is; + } + + // We have a data directory, so first check if the file exists + string path = System.IO.Path.Combine(DATA_DIR, fileName); + if (!File.Exists(path)) + { + throw new FileNotFoundException( + string.Format( + "Expected file '{0}' not found. " + + "If the '{1}' directory exists, this file is required. " + + "Either remove the '{3}' directory or generate the required dictionary files using the lucene-cli tool.", + fileName, + DATA_DIR, + DATA_SUBDIR)); + } + + // The file exists - open a stream. + return new FileStream(path, FileMode.Open, FileAccess.Read); + } + + public static Stream GetClassResource(Type clazz, string suffix) { + Stream @is = clazz.FindAndGetManifestResourceStream(clazz.FullName + suffix); + if (@is == null) { + throw new FileNotFoundException("Not in classpath: " + clazz.FullName.Replace('.', '/') + suffix); + } + return @is; + } + + private static Stream GetClassResource(string path) + { + Stream @is = Assembly.LoadFrom(typeof(BinaryDictionary).ToString()). + FindAndGetManifestResourceStream(path); + if (@is == null) { + throw new FileNotFoundException("Not in classpath: " + path); + } + return @is; + } + + public virtual void LookupWordIds(int sourceId, Int32sRef @ref) + { + @ref.Int32s = targetMap; + @ref.Offset = targetMapOffsets[sourceId]; + // targetMapOffsets always has one more entry pointing behind last: + @ref.Length = targetMapOffsets[sourceId + 1] - @ref.Offset; + } + + public virtual int GetLeftId(int wordId) + { + return buffer.GetInt16(wordId). + TripleShift(2); + } + + public virtual int GetRightId(int wordId) + { + return buffer.GetInt16(wordId + 2). // Skip left id + TripleShift(2); + } + + public virtual int GetWordCost(int wordId) + { + return buffer.GetInt16(wordId + 4); // Skip left and right id + } + + public virtual POS.Type GetPOSType(int wordId) { + byte value = (byte) (buffer.GetInt16(wordId) & 3); + return POS.ResolveType(value); + } + + public virtual POS.Tag GetLeftPOS(int wordId) { + return posDict[GetLeftId(wordId)]; + } + + public virtual POS.Tag GetRightPOS(int wordId) { + POS.Type type = GetPOSType(wordId); + if (type == POS.Type.MORPHEME || type == POS.Type.COMPOUND || HasSinglePOS(wordId)) { + return GetLeftPOS(wordId); + } else { + byte value = buffer.Get(wordId + 6); + return POS.ResolveTag(value); + } + } + + public virtual string GetReading(int wordId) + { + if (HasReadingData(wordId)) + { + int offset = wordId + 6; + return ReadString(offset); + } + + return null; + } + + public virtual IDictionary.Morpheme[] GetMorphemes(int wordId, char[] surfaceForm, int off, int len) { + POS.Type posType = GetPOSType(wordId); + if (posType == POS.Type.MORPHEME) { + return null; + } + int offset = wordId + 6; + bool hasSinglePos = HasSinglePOS(wordId); + if (hasSinglePos == false) { + offset++; // skip rightPOS + } + int length = buffer.GetInt16(offset++); + if (length == 0) { + return null; + } + IDictionary.Morpheme[] morphemes = new IDictionary.Morpheme[length]; + int surfaceOffset = 0; + POS.Tag leftPOS = GetLeftPOS(wordId); + for (int i = 0; i < length; i++) { + char[] form; + POS.Tag tag = hasSinglePos ? leftPOS : POS.ResolveTag(buffer.Get(offset++)); + if (posType == POS.Type.INFLECT) { + form = ReadString(offset).ToCharArray(); + offset += form.Length * 2 + 1; + } else { + int formLen = buffer.GetInt16(offset++); + form = new string(surfaceForm, off+surfaceOffset, formLen).ToCharArray(); + surfaceOffset += formLen; + } + morphemes[i] = new IDictionary.Morpheme(tag, form); + } + return morphemes; + } + + + private string ReadString(int offset) + { + int strOffset = offset; + int len = buffer.Get(strOffset + 1); + char[] text = new char[len]; + for (int i = 0; i < len; i++) + { + text[i] = buffer.GetChar(offset + (i << 1)); + } + + return new string(text); + } + + private bool HasSinglePOS(int wordId) { + return (buffer.GetInt16(wordId+2) & HAS_SINGLE_POS) != 0; + } + + private bool HasReadingData(int wordId) + { + return (buffer.GetInt16(wordId + 2) & HAS_READING) != 0; + } + + /// flag that the entry has baseform data. otherwise its not inflected (same as surface form) + public static readonly int HAS_SINGLE_POS = 1; + + /// flag that the entry has reading data. otherwise reading is surface form converted to katakana + public static readonly int HAS_READING = 2; + } +} \ No newline at end of file diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.cs new file mode 100644 index 0000000000..c7605b9334 --- /dev/null +++ b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.cs @@ -0,0 +1,110 @@ +using Lucene.Net.Codecs; +using Lucene.Net.Store; +using System; +using System.IO; + +namespace Lucene.Net.Analysis.Ko.Dict +{ + public sealed class CharacterDefinition + { + public static readonly string FILENAME_SUFFIX = ".dat"; + public static readonly string HEADER = "ko_cd"; + public static readonly int VERSION = 1; + + public static readonly int CLASS_COUNT = Enum.GetValues(typeof(CharacterClass)).Length; + + // only used internally for lookup: + private enum CharacterClass : byte + { + NGRAM, DEFAULT, SPACE, SYMBOL, NUMERIC, ALPHA, CYRILLIC, GREEK, HIRAGANA, KATAKANA, KANJI, HANGUL, HANJA, HANJANUMERIC + } + + private readonly byte[] characterCategoryMap = new byte[0x10000]; + + private readonly bool[] invokeMap = new bool[CLASS_COUNT]; + private readonly bool[] groupMap = new bool[CLASS_COUNT]; + + // the classes: + public static readonly byte NGRAM = (byte)CharacterClass.NGRAM; + public static readonly byte DEFAULT = (byte)CharacterClass.DEFAULT; + public static readonly byte SPACE = (byte)CharacterClass.SPACE; + public static readonly byte SYMBOL = (byte)CharacterClass.SYMBOL; + public static readonly byte NUMERIC = (byte)CharacterClass.NUMERIC; + public static readonly byte ALPHA = (byte)CharacterClass.ALPHA; + public static readonly byte CYRILLIC = (byte)CharacterClass.CYRILLIC; + public static readonly byte GREEK = (byte)CharacterClass.GREEK; + public static readonly byte HIRAGANA = (byte)CharacterClass.HIRAGANA; + public static readonly byte KATAKANA = (byte)CharacterClass.KATAKANA; + public static readonly byte KANJI = (byte)CharacterClass.KANJI; + public static readonly byte HANGUL = (byte)CharacterClass.HANGUL; + public static readonly byte HANJA = (byte)CharacterClass.HANJA; + public static readonly byte HANJANUMERIC = (byte)CharacterClass.HANJANUMERIC; + + + private CharacterDefinition() + { + using Stream @is = BinaryDictionary.GetTypeResource(GetType(), FILENAME_SUFFIX); + DataInput @in = new InputStreamDataInput(@is); + CodecUtil.CheckHeader(@in, HEADER, VERSION, VERSION); + @in.ReadBytes(characterCategoryMap, 0, characterCategoryMap.Length); + for (int i = 0; i < CLASS_COUNT; i++) + { + byte b = @in.ReadByte(); + invokeMap[i] = (b & 0x01) != 0; + groupMap[i] = (b & 0x02) != 0; + } + } + + public byte GetCharacterClass(char c) + { + return characterCategoryMap[c]; + } + + public bool IsInvoke(char c) + { + return invokeMap[characterCategoryMap[c]]; + } + + public bool IsGroup(char c) + { + return groupMap[characterCategoryMap[c]]; + } + + public bool IsHanja(char c) + { + byte characterClass = characterCategoryMap[c]; + return characterClass == HANJA || characterClass == HANJANUMERIC; + } + + public bool IsHangul(char c) { + return GetCharacterClass(c) == HANGUL; + } + + public bool HasCoda(char ch){ + return ((ch - 0xAC00) % 0x001C) != 0; + } + + public static byte LookupCharacterClass(string characterClassName) + { + return (byte)Enum.Parse(typeof(CharacterClass), characterClassName, true); + } + + public static CharacterDefinition Instance => SingletonHolder.INSTANCE; + + private class SingletonHolder + { + internal static readonly CharacterDefinition INSTANCE = LoadInstance(); + private static CharacterDefinition LoadInstance() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006) + { + try + { + return new CharacterDefinition(); + } + catch (Exception ioe) when (ioe.IsIOException()) + { + throw RuntimeException.Create("Cannot load CharacterDefinition.", ioe); + } + } + } + } +} \ No newline at end of file diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.dat b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/CharacterDefinition.dat new file mode 100644 index 0000000000000000000000000000000000000000..97b23bb5d0e3c2f42daf3b42c703545a63d89267 GIT binary patch literal 65564 zcmeI*TT;R>6adgZsEXpZ%W)}w=%3EGT(=_?=E@pk*P}JYwZ-xvXHfSo4yXK%GGBGp%mSG1aAGnvA&&EnxeH z7B}93K|NH+6$FCs?5FkK+009C72oNAZfB*pk1PBlyK!5-N0t8M3 z`nx9hyjrcl*5BpNzT?82yY9QkzNaG}fjWWi@3&SN;{1-)ElAyP0(NycYaO#ic? zhg?CLb{MUBGxQ)pVAKM6hZyC;j@GlNV$+Zo!Ps;NHWz!Dm;a8gW_y$PD<0=dvTWNW H$&&mRl&!bL literal 0 HcmV?d00001 diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.cs new file mode 100644 index 0000000000..ca1067767c --- /dev/null +++ b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.cs @@ -0,0 +1,72 @@ +using J2N.IO; +using J2N.Numerics; +using Lucene.Net.Codecs; +using Lucene.Net.Store; +using Lucene.Net.Support; +using System; +using System.IO; + +namespace Lucene.Net.Analysis.Ko.Dict +{ + public sealed class ConnectionCosts + { + public static readonly string FILENAME_SUFFIX = ".dat"; + public static readonly string HEADER = "ko_cc"; + public static readonly int VERSION = 1; + private readonly ByteBuffer buffer; + private readonly int forwardSize; + + private ConnectionCosts() + { + ByteBuffer buffer; + using (Stream @is = BinaryDictionary.GetTypeResource(GetType(), FILENAME_SUFFIX)) + { + DataInput @in = new InputStreamDataInput(@is); + CodecUtil.CheckHeader(@in, HEADER, VERSION, VERSION); + forwardSize = @in.ReadVInt32(); + int backwardSize = @in.ReadVInt32(); + int size = forwardSize * backwardSize; + + ByteBuffer tmpBuffer = ByteBuffer.Allocate(size); + int accum = 0; + for (int j = 0; j < backwardSize; j++) + { + for (int i = 0; i < forwardSize; i++) + { + int raw = @in.ReadVInt32(); + accum += raw.TripleShift(1) ^ -(raw & 1); + tmpBuffer.PutInt16((short)accum); + } + } + + buffer = tmpBuffer.AsReadOnlyBuffer(); + } + + this.buffer = buffer; + } + + public int Get(int forwardId, int backwardId) + { + int offset = (backwardId * forwardSize + forwardId) * 2; + return buffer.GetInt16(offset); + } + + public static ConnectionCosts Instance => SingletonHolder.INSTANCE; + + private class SingletonHolder + { + internal static readonly ConnectionCosts INSTANCE = LoadInstance(); + private static ConnectionCosts LoadInstance() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006) + { + try + { + return new ConnectionCosts(); + } + catch (Exception ioe) when (ioe.IsIOException()) + { + throw RuntimeException.Create("Cannot load ConnectionCosts.", ioe); + } + } + } + } +} \ No newline at end of file diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.dat b/src/Lucene.Net.Analysis.Common/Analysis/Ko/Dict/ConnectionCosts.dat new file mode 100644 index 0000000000000000000000000000000000000000..7fad91e727dd48be68ad1f0e7d8f24c716829c46 GIT binary patch literal 11178837 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%0A{)` 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