2013-08-19 05:49:57 +00:00
|
|
|
/*
|
|
|
|
* 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 java.io.File;
|
|
|
|
import java.io.IOException;
|
2013-10-02 05:45:22 +00:00
|
|
|
import java.io.OutputStream;
|
2013-08-19 05:49:57 +00:00
|
|
|
import java.nio.ByteBuffer;
|
2014-09-10 09:33:24 +00:00
|
|
|
import java.util.HashMap;
|
2013-08-19 05:49:57 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes binary files for a FusionDictionary.
|
|
|
|
*
|
|
|
|
* All the methods in this class are static.
|
|
|
|
*
|
|
|
|
* TODO: Move this file to makedict/internal.
|
2013-08-22 02:07:52 +00:00
|
|
|
* TODO: Rename this class to DictDecoderUtils.
|
2013-08-19 05:49:57 +00:00
|
|
|
*/
|
|
|
|
public final class BinaryDictDecoderUtils {
|
|
|
|
private BinaryDictDecoderUtils() {
|
|
|
|
// This utility class is not publicly instantiable.
|
|
|
|
}
|
|
|
|
|
|
|
|
@UsedForTesting
|
|
|
|
public interface DictBuffer {
|
|
|
|
public int readUnsignedByte();
|
|
|
|
public int readUnsignedShort();
|
|
|
|
public int readUnsignedInt24();
|
|
|
|
public int readInt();
|
|
|
|
public int position();
|
|
|
|
public void position(int newPosition);
|
2013-12-17 05:31:25 +00:00
|
|
|
@UsedForTesting
|
2013-08-19 05:49:57 +00:00
|
|
|
public void put(final byte b);
|
|
|
|
public int limit();
|
|
|
|
@UsedForTesting
|
|
|
|
public int capacity();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static final class ByteBufferDictBuffer implements DictBuffer {
|
|
|
|
private ByteBuffer mBuffer;
|
|
|
|
|
|
|
|
public ByteBufferDictBuffer(final ByteBuffer buffer) {
|
|
|
|
mBuffer = buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int readUnsignedByte() {
|
|
|
|
return mBuffer.get() & 0xFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int readUnsignedShort() {
|
|
|
|
return mBuffer.getShort() & 0xFFFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int readUnsignedInt24() {
|
|
|
|
final int retval = readUnsignedByte();
|
|
|
|
return (retval << 16) + readUnsignedShort();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int readInt() {
|
|
|
|
return mBuffer.getInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int position() {
|
|
|
|
return mBuffer.position();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void position(int newPos) {
|
|
|
|
mBuffer.position(newPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void put(final byte b) {
|
|
|
|
mBuffer.put(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int limit() {
|
|
|
|
return mBuffer.limit();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int capacity() {
|
|
|
|
return mBuffer.capacity();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A class grouping utility function for our specific character encoding.
|
|
|
|
*/
|
|
|
|
static final class CharEncoding {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to find out whether this code fits on one byte
|
|
|
|
*/
|
2014-10-20 05:48:56 +00:00
|
|
|
private static boolean fitsOnOneByte(final int character,
|
2014-09-10 09:33:24 +00:00
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) {
|
2014-10-20 05:48:56 +00:00
|
|
|
int codePoint = character;
|
2014-09-10 09:33:24 +00:00
|
|
|
if (codePointToOneByteCodeMap != null) {
|
|
|
|
if (codePointToOneByteCodeMap.containsKey(character)) {
|
2014-10-20 05:48:56 +00:00
|
|
|
codePoint = codePointToOneByteCodeMap.get(character);
|
2014-09-10 09:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
2014-10-20 05:48:56 +00:00
|
|
|
return codePoint >= FormatSpec.MINIMAL_ONE_BYTE_CHARACTER_VALUE
|
|
|
|
&& codePoint <= FormatSpec.MAXIMAL_ONE_BYTE_CHARACTER_VALUE;
|
2013-08-19 05:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the size of a character given its character code.
|
|
|
|
*
|
|
|
|
* Char format is:
|
|
|
|
* 1 byte = bbbbbbbb match
|
|
|
|
* case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
|
|
|
|
* else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
|
|
|
|
* unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
|
|
|
|
* 00011111 would be outside unicode.
|
|
|
|
* else: iso-latin-1 code
|
|
|
|
* This allows for the whole unicode range to be encoded, including chars outside of
|
|
|
|
* the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
|
|
|
|
* characters which should never happen anyway (and still work, but take 3 bytes).
|
|
|
|
*
|
|
|
|
* @param character the character code.
|
|
|
|
* @return the size in binary encoded-form, either 1 or 3 bytes.
|
|
|
|
*/
|
2014-09-10 09:33:24 +00:00
|
|
|
static int getCharSize(final int character,
|
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) {
|
2013-08-19 05:49:57 +00:00
|
|
|
// See char encoding in FusionDictionary.java
|
2014-09-10 09:33:24 +00:00
|
|
|
if (fitsOnOneByte(character, codePointToOneByteCodeMap)) return 1;
|
2013-08-19 05:49:57 +00:00
|
|
|
if (FormatSpec.INVALID_CHARACTER == character) return 1;
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the byte size of a character array.
|
|
|
|
*/
|
2014-09-10 09:33:24 +00:00
|
|
|
static int getCharArraySize(final int[] chars,
|
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) {
|
2013-08-19 05:49:57 +00:00
|
|
|
int size = 0;
|
2014-09-10 09:33:24 +00:00
|
|
|
for (int character : chars) size += getCharSize(character, codePointToOneByteCodeMap);
|
2013-08-19 05:49:57 +00:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a char array to a byte buffer.
|
|
|
|
*
|
|
|
|
* @param codePoints the code point array to write.
|
|
|
|
* @param buffer the byte buffer to write to.
|
|
|
|
* @param index the index in buffer to write the character array to.
|
2014-09-10 09:33:24 +00:00
|
|
|
* @param codePointToOneByteCodeMap the map to convert the code point.
|
2013-08-19 05:49:57 +00:00
|
|
|
* @return the index after the last character.
|
|
|
|
*/
|
2014-10-20 05:48:56 +00:00
|
|
|
static int writeCharArray(final int[] codePoints, final byte[] buffer, final int fromIndex,
|
2014-09-10 09:33:24 +00:00
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) {
|
2014-10-20 05:48:56 +00:00
|
|
|
int index = fromIndex;
|
2013-08-19 05:49:57 +00:00
|
|
|
for (int codePoint : codePoints) {
|
2014-09-10 09:33:24 +00:00
|
|
|
if (codePointToOneByteCodeMap != null) {
|
|
|
|
if (codePointToOneByteCodeMap.containsKey(codePoint)) {
|
|
|
|
// Convert code points
|
|
|
|
codePoint = codePointToOneByteCodeMap.get(codePoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (1 == getCharSize(codePoint, codePointToOneByteCodeMap)) {
|
2013-08-19 05:49:57 +00:00
|
|
|
buffer[index++] = (byte)codePoint;
|
|
|
|
} else {
|
|
|
|
buffer[index++] = (byte)(0xFF & (codePoint >> 16));
|
|
|
|
buffer[index++] = (byte)(0xFF & (codePoint >> 8));
|
|
|
|
buffer[index++] = (byte)(0xFF & codePoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a string with our character format to a byte buffer.
|
|
|
|
*
|
|
|
|
* This will also write the terminator byte.
|
|
|
|
*
|
|
|
|
* @param buffer the byte buffer to write to.
|
|
|
|
* @param origin the offset to write from.
|
|
|
|
* @param word the string to write.
|
|
|
|
* @return the size written, in bytes.
|
|
|
|
*/
|
2014-09-10 09:33:24 +00:00
|
|
|
static int writeString(final byte[] buffer, final int origin, final String word,
|
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) {
|
2013-08-19 05:49:57 +00:00
|
|
|
final int length = word.length();
|
|
|
|
int index = origin;
|
|
|
|
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
|
2014-09-10 09:33:24 +00:00
|
|
|
int codePoint = word.codePointAt(i);
|
|
|
|
if (codePointToOneByteCodeMap != null) {
|
|
|
|
if (codePointToOneByteCodeMap.containsKey(codePoint)) {
|
|
|
|
// Convert code points
|
|
|
|
codePoint = codePointToOneByteCodeMap.get(codePoint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (1 == getCharSize(codePoint, codePointToOneByteCodeMap)) {
|
2013-08-19 05:49:57 +00:00
|
|
|
buffer[index++] = (byte)codePoint;
|
|
|
|
} else {
|
|
|
|
buffer[index++] = (byte)(0xFF & (codePoint >> 16));
|
|
|
|
buffer[index++] = (byte)(0xFF & (codePoint >> 8));
|
|
|
|
buffer[index++] = (byte)(0xFF & codePoint);
|
|
|
|
}
|
|
|
|
}
|
2013-08-22 02:07:52 +00:00
|
|
|
buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
|
2013-08-19 05:49:57 +00:00
|
|
|
return index - origin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-10-02 05:45:22 +00:00
|
|
|
* Writes a string with our character format to an OutputStream.
|
2013-08-19 05:49:57 +00:00
|
|
|
*
|
|
|
|
* This will also write the terminator byte.
|
|
|
|
*
|
2013-12-13 08:09:16 +00:00
|
|
|
* @param stream the OutputStream to write to.
|
2013-08-19 05:49:57 +00:00
|
|
|
* @param word the string to write.
|
2013-12-13 08:09:16 +00:00
|
|
|
* @return the size written, in bytes.
|
2013-08-19 05:49:57 +00:00
|
|
|
*/
|
2014-09-10 09:33:24 +00:00
|
|
|
static int writeString(final OutputStream stream, final String word,
|
|
|
|
final HashMap<Integer, Integer> codePointToOneByteCodeMap) throws IOException {
|
2013-08-19 05:49:57 +00:00
|
|
|
final int length = word.length();
|
2013-12-13 08:09:16 +00:00
|
|
|
int written = 0;
|
2013-08-19 05:49:57 +00:00
|
|
|
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
|
|
|
|
final int codePoint = word.codePointAt(i);
|
2014-09-10 09:33:24 +00:00
|
|
|
final int charSize = getCharSize(codePoint, codePointToOneByteCodeMap);
|
2013-12-13 08:09:16 +00:00
|
|
|
if (1 == charSize) {
|
|
|
|
stream.write((byte) codePoint);
|
2013-08-19 05:49:57 +00:00
|
|
|
} else {
|
2013-12-13 08:09:16 +00:00
|
|
|
stream.write((byte) (0xFF & (codePoint >> 16)));
|
|
|
|
stream.write((byte) (0xFF & (codePoint >> 8)));
|
|
|
|
stream.write((byte) (0xFF & codePoint));
|
2013-08-19 05:49:57 +00:00
|
|
|
}
|
2013-12-13 08:09:16 +00:00
|
|
|
written += charSize;
|
2013-08-19 05:49:57 +00:00
|
|
|
}
|
2013-12-13 08:09:16 +00:00
|
|
|
stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
|
|
|
|
written += FormatSpec.PTNODE_TERMINATOR_SIZE;
|
|
|
|
return written;
|
|
|
|
}
|
|
|
|
|
2013-08-19 05:49:57 +00:00
|
|
|
/**
|
|
|
|
* Reads a string from a DictBuffer. This is the converse of the above method.
|
|
|
|
*/
|
|
|
|
static String readString(final DictBuffer dictBuffer) {
|
|
|
|
final StringBuilder s = new StringBuilder();
|
|
|
|
int character = readChar(dictBuffer);
|
|
|
|
while (character != FormatSpec.INVALID_CHARACTER) {
|
|
|
|
s.appendCodePoint(character);
|
|
|
|
character = readChar(dictBuffer);
|
|
|
|
}
|
|
|
|
return s.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads a character from the buffer.
|
|
|
|
*
|
|
|
|
* This follows the character format documented earlier in this source file.
|
|
|
|
*
|
|
|
|
* @param dictBuffer the buffer, positioned over an encoded character.
|
|
|
|
* @return the character code.
|
|
|
|
*/
|
|
|
|
static int readChar(final DictBuffer dictBuffer) {
|
|
|
|
int character = dictBuffer.readUnsignedByte();
|
2014-09-10 09:33:24 +00:00
|
|
|
if (!fitsOnOneByte(character, null)) {
|
2013-08-22 02:07:52 +00:00
|
|
|
if (FormatSpec.PTNODE_CHARACTERS_TERMINATOR == character) {
|
2013-08-19 05:49:57 +00:00
|
|
|
return FormatSpec.INVALID_CHARACTER;
|
|
|
|
}
|
|
|
|
character <<= 16;
|
|
|
|
character += dictBuffer.readUnsignedShort();
|
|
|
|
}
|
|
|
|
return character;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-22 02:07:52 +00:00
|
|
|
* Reads and returns the PtNode count out of a buffer and forwards the pointer.
|
2013-08-19 05:49:57 +00:00
|
|
|
*/
|
2013-08-23 14:23:03 +00:00
|
|
|
/* package */ static int readPtNodeCount(final DictBuffer dictBuffer) {
|
2013-08-19 05:49:57 +00:00
|
|
|
final int msb = dictBuffer.readUnsignedByte();
|
2013-08-22 02:07:52 +00:00
|
|
|
if (FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT >= msb) {
|
2013-08-19 05:49:57 +00:00
|
|
|
return msb;
|
|
|
|
}
|
2014-10-20 05:48:56 +00:00
|
|
|
return ((FormatSpec.MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT & msb) << 8)
|
|
|
|
+ dictBuffer.readUnsignedByte();
|
2013-08-19 05:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-08-21 06:27:36 +00:00
|
|
|
* Finds, as a string, the word at the position passed as an argument.
|
2013-08-19 05:49:57 +00:00
|
|
|
*
|
2013-08-20 08:01:47 +00:00
|
|
|
* @param dictDecoder the dict decoder.
|
2013-08-19 05:49:57 +00:00
|
|
|
* @param headerSize the size of the header.
|
2013-08-21 06:27:36 +00:00
|
|
|
* @param pos the position to seek.
|
2013-08-19 05:49:57 +00:00
|
|
|
* @return the word with its frequency, as a weighted string.
|
|
|
|
*/
|
2014-02-15 08:38:11 +00:00
|
|
|
@UsedForTesting
|
2013-09-11 09:20:00 +00:00
|
|
|
/* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder,
|
2014-02-15 08:38:11 +00:00
|
|
|
final int headerSize, final int pos) {
|
2013-08-19 05:49:57 +00:00
|
|
|
final WeightedString result;
|
2013-09-11 09:20:00 +00:00
|
|
|
final int originalPos = dictDecoder.getPosition();
|
|
|
|
dictDecoder.setPosition(pos);
|
2014-02-15 08:38:11 +00:00
|
|
|
result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos);
|
2013-09-11 09:20:00 +00:00
|
|
|
dictDecoder.setPosition(originalPos);
|
2013-08-19 05:49:57 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2013-08-21 06:27:36 +00:00
|
|
|
private static WeightedString getWordAtPositionWithoutParentAddress(
|
2014-02-15 08:38:11 +00:00
|
|
|
final DictDecoder dictDecoder, final int headerSize, final int pos) {
|
2013-09-11 09:20:00 +00:00
|
|
|
dictDecoder.setPosition(headerSize);
|
|
|
|
final int count = dictDecoder.readPtNodeCount();
|
2013-12-16 13:42:01 +00:00
|
|
|
int groupPos = dictDecoder.getPosition();
|
2013-08-19 05:49:57 +00:00
|
|
|
final StringBuilder builder = new StringBuilder();
|
|
|
|
WeightedString result = null;
|
|
|
|
|
2013-08-22 02:07:52 +00:00
|
|
|
PtNodeInfo last = null;
|
2013-08-19 05:49:57 +00:00
|
|
|
for (int i = count - 1; i >= 0; --i) {
|
2014-02-15 08:38:11 +00:00
|
|
|
PtNodeInfo info = dictDecoder.readPtNode(groupPos);
|
2013-08-21 06:27:36 +00:00
|
|
|
groupPos = info.mEndAddress;
|
|
|
|
if (info.mOriginalAddress == pos) {
|
2013-08-19 05:49:57 +00:00
|
|
|
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
|
2014-02-10 06:05:08 +00:00
|
|
|
result = new WeightedString(builder.toString(), info.mProbabilityInfo);
|
2013-08-19 05:49:57 +00:00
|
|
|
break; // and return
|
|
|
|
}
|
|
|
|
if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
|
2013-08-21 06:27:36 +00:00
|
|
|
if (info.mChildrenAddress > pos) {
|
2013-08-19 05:49:57 +00:00
|
|
|
if (null == last) continue;
|
|
|
|
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
2013-09-11 09:20:00 +00:00
|
|
|
dictDecoder.setPosition(last.mChildrenAddress);
|
|
|
|
i = dictDecoder.readPtNodeCount();
|
2013-08-22 02:07:52 +00:00
|
|
|
groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
|
2013-08-19 05:49:57 +00:00
|
|
|
last = null;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
last = info;
|
|
|
|
}
|
|
|
|
if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) {
|
|
|
|
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
2013-09-11 09:20:00 +00:00
|
|
|
dictDecoder.setPosition(last.mChildrenAddress);
|
|
|
|
i = dictDecoder.readPtNodeCount();
|
2013-08-22 02:07:52 +00:00
|
|
|
groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
|
2013-08-19 05:49:57 +00:00
|
|
|
last = null;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to pass a file name instead of a File object to isBinaryDictionary.
|
|
|
|
*/
|
|
|
|
public static boolean isBinaryDictionary(final String filename) {
|
|
|
|
final File file = new File(filename);
|
|
|
|
return isBinaryDictionary(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic test to find out whether the file is a binary dictionary or not.
|
|
|
|
*
|
|
|
|
* @param file The file to test.
|
|
|
|
* @return true if it's a binary dictionary, false otherwise
|
|
|
|
*/
|
|
|
|
public static boolean isBinaryDictionary(final File file) {
|
2014-03-27 06:30:32 +00:00
|
|
|
final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(file, 0, file.length());
|
2014-01-06 08:56:49 +00:00
|
|
|
if (dictDecoder == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return dictDecoder.hasValidRawBinaryDictionary();
|
2013-08-19 05:49:57 +00:00
|
|
|
}
|
|
|
|
}
|