aboutsummaryrefslogtreecommitdiffstats
path: root/tests/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
blob: 65b84d5f763070b6b7e8432b7720137545c68d82 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed 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.
 */

package com.android.inputmethod.latin.makedict;

import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * An implementation of DictDecoder for version 2 binary dictionary.
 */
// TODO: Separate logics that are used only for testing.
@UsedForTesting
public class Ver2DictDecoder extends AbstractDictDecoder {
    /**
     * A utility class for reading a PtNode.
     */
    protected static class PtNodeReader {
        private static ProbabilityInfo readProbabilityInfo(final DictBuffer dictBuffer) {
            // Ver2 dicts don't contain historical information.
            return new ProbabilityInfo(dictBuffer.readUnsignedByte());
        }

        protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) {
            return dictBuffer.readUnsignedByte();
        }

        protected static int readChildrenAddress(final DictBuffer dictBuffer,
                final int ptNodeFlags) {
            switch (ptNodeFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
                case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
                    return dictBuffer.readUnsignedByte();
                case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES:
                    return dictBuffer.readUnsignedShort();
                case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES:
                    return dictBuffer.readUnsignedInt24();
                case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS:
                default:
                    return FormatSpec.NO_CHILDREN_ADDRESS;
            }
        }

        // Reads shortcuts and returns the read length.
        protected static int readShortcut(final DictBuffer dictBuffer,
                final ArrayList<WeightedString> shortcutTargets) {
            final int pointerBefore = dictBuffer.position();
            dictBuffer.readUnsignedShort(); // skip the size
            while (true) {
                final int targetFlags = dictBuffer.readUnsignedByte();
                final String word = CharEncoding.readString(dictBuffer);
                shortcutTargets.add(new WeightedString(word,
                        targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
                if (0 == (targetFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
            }
            return dictBuffer.position() - pointerBefore;
        }

        protected static int readBigramAddresses(final DictBuffer dictBuffer,
                final ArrayList<PendingAttribute> bigrams, final int baseAddress) {
            int readLength = 0;
            int bigramCount = 0;
            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
                final int bigramFlags = dictBuffer.readUnsignedByte();
                ++readLength;
                final int sign = 0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE)
                        ? 1 : -1;
                int bigramAddress = baseAddress + readLength;
                switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
                        bigramAddress += sign * dictBuffer.readUnsignedByte();
                        readLength += 1;
                        break;
                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
                        bigramAddress += sign * dictBuffer.readUnsignedShort();
                        readLength += 2;
                        break;
                    case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
                        bigramAddress += sign * dictBuffer.readUnsignedInt24();
                        readLength += 3;
                        break;
                    default:
                        throw new RuntimeException("Has bigrams with no address");
                }
                bigrams.add(new PendingAttribute(
                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
                        bigramAddress));
                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
            }
            return readLength;
        }
    }

    protected final File mDictionaryBinaryFile;
    protected final long mOffset;
    protected final long mLength;
    // TODO: Remove mBufferFactory and mDictBuffer from this class members because they are now
    // used only for testing.
    private final DictionaryBufferFactory mBufferFactory;
    protected DictBuffer mDictBuffer;

    @UsedForTesting
    /* package */ Ver2DictDecoder(final File file, final long offset, final long length,
            final int factoryFlag) {
        mDictionaryBinaryFile = file;
        mOffset = offset;
        mLength = length;
        mDictBuffer = null;
        if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
        } else if ((factoryFlag  & MASK_DICTBUFFER) == USE_BYTEARRAY) {
            mBufferFactory = new DictionaryBufferFromByteArrayFactory();
        } else if ((factoryFlag & MASK_DICTBUFFER) == USE_WRITABLE_BYTEBUFFER) {
            mBufferFactory = new DictionaryBufferFromWritableByteBufferFactory();
        } else {
            mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
        }
    }

    /* package */ Ver2DictDecoder(final File file, final long offset, final long length,
            final DictionaryBufferFactory factory) {
        mDictionaryBinaryFile = file;
        mOffset = offset;
        mLength = length;
        mBufferFactory = factory;
    }

    @Override
    public void openDictBuffer() throws FileNotFoundException, IOException {
        mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile);
    }

    @Override
    public boolean isDictBufferOpen() {
        return mDictBuffer != null;
    }

    /* package */ DictBuffer getDictBuffer() {
        return mDictBuffer;
    }

    @UsedForTesting
    /* package */ DictBuffer openAndGetDictBuffer() throws FileNotFoundException, IOException {
        openDictBuffer();
        return getDictBuffer();
    }

    @Override
    public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException {
        // dictType is not being used in dicttool. Passing an empty string.
        final BinaryDictionary binaryDictionary = new BinaryDictionary(
                mDictionaryBinaryFile.getAbsolutePath(), mOffset, mLength,
                true /* useFullEditDistance */, null /* locale */, "" /* dictType */,
                false /* isUpdatable */);
        final DictionaryHeader header = binaryDictionary.getHeader();
        binaryDictionary.close();
        if (header == null) {
            throw new IOException("Cannot read the dictionary header.");
        }
        if (header.mFormatOptions.mVersion != FormatSpec.VERSION2) {
            throw new UnsupportedFormatException("File header has a wrong version : "
                    + header.mFormatOptions.mVersion);
        }
        if (!isDictBufferOpen()) {
            openDictBuffer();
        }
        // Advance buffer reading position to the head of dictionary body.
        setPosition(header.mBodyOffset);
        return header;
    }

    // TODO: Make this buffer multi thread safe.
    private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
    @Override
    public PtNodeInfo readPtNode(final int ptNodePos) {
        int addressPointer = ptNodePos;
        final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
        final int characters[];
        if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
            int index = 0;
            int character = CharEncoding.readChar(mDictBuffer);
            addressPointer += CharEncoding.getCharSize(character);
            while (FormatSpec.INVALID_CHARACTER != character) {
                // FusionDictionary is making sure that the length of the word is smaller than
                // MAX_WORD_LENGTH.
                // So we'll never write past the end of mCharacterBuffer.
                mCharacterBuffer[index++] = character;
                character = CharEncoding.readChar(mDictBuffer);
                addressPointer += CharEncoding.getCharSize(character);
            }
            characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
        } else {
            final int character = CharEncoding.readChar(mDictBuffer);
            addressPointer += CharEncoding.getCharSize(character);
            characters = new int[] { character };
        }
        final ProbabilityInfo probabilityInfo;
        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
            probabilityInfo = PtNodeReader.readProbabilityInfo(mDictBuffer);
            addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE;
        } else {
            probabilityInfo = null;
        }
        int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags);
        if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
            childrenAddress += addressPointer;
        }
        addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags);
        final ArrayList<WeightedString> shortcutTargets;
        if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
            // readShortcut will add shortcuts to shortcutTargets.
            shortcutTargets = new ArrayList<>();
            addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets);
        } else {
            shortcutTargets = null;
        }

        final ArrayList<PendingAttribute> bigrams;
        if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
            bigrams = new ArrayList<>();
            addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
                    addressPointer);
            if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
                throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
                        + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
            }
        } else {
            bigrams = null;
        }
        return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, probabilityInfo,
                childrenAddress, shortcutTargets, bigrams);
    }

    @Override
    public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken)
            throws FileNotFoundException, IOException, UnsupportedFormatException {
        // dictType is not being used in dicttool. Passing an empty string.
        final BinaryDictionary binaryDictionary = new BinaryDictionary(
                mDictionaryBinaryFile.getAbsolutePath(), 0 /* offset */,
                mDictionaryBinaryFile.length() /* length */, true /* useFullEditDistance */,
                null /* locale */, "" /* dictType */, false /* isUpdatable */);
        final DictionaryHeader header = readHeader();
        final FusionDictionary fusionDict =
                new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions);
        int token = 0;
        final ArrayList<WordProperty> wordProperties = new ArrayList<>();
        do {
            final BinaryDictionary.GetNextWordPropertyResult result =
                    binaryDictionary.getNextWordProperty(token);
            final WordProperty wordProperty = result.mWordProperty;
            if (wordProperty == null) {
                binaryDictionary.close();
                if (deleteDictIfBroken) {
                    mDictionaryBinaryFile.delete();
                }
                return null;
            }
            wordProperties.add(wordProperty);
            token = result.mNextToken;
        } while (token != 0);

        // Insert unigrams into the fusion dictionary.
        for (final WordProperty wordProperty : wordProperties) {
            if (wordProperty.mIsBlacklistEntry) {
                fusionDict.addBlacklistEntry(wordProperty.mWord, wordProperty.mShortcutTargets,
                        wordProperty.mIsNotAWord);
            } else {
                fusionDict.add(wordProperty.mWord, wordProperty.mProbabilityInfo,
                        wordProperty.mShortcutTargets, wordProperty.mIsNotAWord);
            }
        }
        // Insert bigrams into the fusion dictionary.
        for (final WordProperty wordProperty : wordProperties) {
            if (wordProperty.mBigrams == null) {
                continue;
            }
            final String word0 = wordProperty.mWord;
            for (final WeightedString bigram : wordProperty.mBigrams) {
                fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo);
            }
        }
        binaryDictionary.close();
        return fusionDict;
    }

    @Override
    public void setPosition(int newPos) {
        mDictBuffer.position(newPos);
    }

    @Override
    public int getPosition() {
        return mDictBuffer.position();
    }

    @Override
    public int readPtNodeCount() {
        return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
    }
}