diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/makedict')
23 files changed, 1170 insertions, 3306 deletions
diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java index fda97dafc..bc856f113 100644 --- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java @@ -17,53 +17,19 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.TreeMap; /** * A base class of the binary dictionary decoder. */ public abstract class AbstractDictDecoder implements DictDecoder { - protected FileHeader readHeader(final DictBuffer dictBuffer) - throws IOException, UnsupportedFormatException { - if (dictBuffer == null) { - openDictBuffer(); - } - - final int version = HeaderReader.readVersion(dictBuffer); - if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION - || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) { - throw new UnsupportedFormatException("Unsupported version : " + version); - } - // TODO: Remove this field. - final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer); - - final int headerSize = HeaderReader.readHeaderSize(dictBuffer); - - if (headerSize < 0) { - throw new UnsupportedFormatException("header size can't be negative."); - } - - final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer, - headerSize); - - final FileHeader header = new FileHeader(headerSize, - new FusionDictionary.DictionaryOptions(attributes, - 0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG), - 0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)), - new FormatOptions(version, - 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE), - 0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG))); - return header; - } + private static final int SUCCESS = 0; + private static final int ERROR_CANNOT_READ = 1; + private static final int ERROR_WRONG_FORMAT = 2; @Override @UsedForTesting public int getTerminalPosition(final String word) @@ -86,122 +52,53 @@ public abstract class AbstractDictDecoder implements DictDecoder { } /** - * A utility class for reading a file header. + * Check whether the header contains the expected information. This is a no-error method, + * that will return an error code and never throw a checked exception. + * @return an error code, either ERROR_* or SUCCESS. */ - protected static class HeaderReader { - protected static int readVersion(final DictBuffer dictBuffer) - throws IOException, UnsupportedFormatException { - return BinaryDictDecoderUtils.checkFormatVersion(dictBuffer); - } - - protected static int readOptionFlags(final DictBuffer dictBuffer) { - return dictBuffer.readUnsignedShort(); + private int checkHeader() { + try { + readHeader(); + } catch (IOException e) { + return ERROR_CANNOT_READ; + } catch (UnsupportedFormatException e) { + return ERROR_WRONG_FORMAT; } + return SUCCESS; + } - protected static int readHeaderSize(final DictBuffer dictBuffer) { - return dictBuffer.readInt(); - } + @Override + public boolean hasValidRawBinaryDictionary() { + return checkHeader() == SUCCESS; + } - protected static HashMap<String, String> readAttributes(final DictBuffer dictBuffer, - final int headerSize) { - final HashMap<String, String> attributes = new HashMap<String, String>(); - while (dictBuffer.position() < headerSize) { - // We can avoid an infinite loop here since dictBuffer.position() is always - // increased by calling CharEncoding.readString. - final String key = CharEncoding.readString(dictBuffer); - final String value = CharEncoding.readString(dictBuffer); - attributes.put(key, value); - } - dictBuffer.position(headerSize); - return attributes; - } + // Placeholder implementations below. These are actually unused. + @Override + public void openDictBuffer() throws FileNotFoundException, IOException, + UnsupportedFormatException { } - /** - * A utility class for reading a PtNode. - */ - protected static class PtNodeReader { - protected static int readPtNodeOptionFlags(final DictBuffer dictBuffer) { - return dictBuffer.readUnsignedByte(); - } + @Override + public boolean isDictBufferOpen() { + return false; + } - protected static int readParentAddress(final DictBuffer dictBuffer, - final FormatOptions formatOptions) { - if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { - return BinaryDictDecoderUtils.readSInt24(dictBuffer); - } else { - return FormatSpec.NO_PARENT_ADDRESS; - } - } + @Override + public PtNodeInfo readPtNode(final int ptNodePos) { + return null; + } - protected static int readChildrenAddress(final DictBuffer dictBuffer, final int optionFlags, - final FormatOptions formatOptions) { - if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { - final int address = BinaryDictDecoderUtils.readSInt24(dictBuffer); - if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS; - return address; - } else { - switch (optionFlags & 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; - } - } - } + @Override + public void setPosition(int newPos) { + } - // 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; - } + @Override + public int getPosition() { + return 0; + } - 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; - } + @Override + public int readPtNodeCount() { + return 0; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java index 216492b4d..b534ebeff 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java @@ -17,22 +17,12 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Map; -import java.util.TreeMap; /** * Decodes binary files for a FusionDictionary. @@ -51,8 +41,6 @@ public final class BinaryDictDecoderUtils { // This utility class is not publicly instantiable. } - private static final int MAX_JUMPS = 12; - @UsedForTesting public interface DictBuffer { public int readUnsignedByte(); @@ -61,6 +49,7 @@ public final class BinaryDictDecoderUtils { public int readInt(); public int position(); public void position(int newPosition); + @UsedForTesting public void put(final byte b); public int limit(); @UsedForTesting @@ -200,8 +189,7 @@ public final class BinaryDictDecoderUtils { * @param word the string to write. * @return the size written, in bytes. */ - static int writeString(final byte[] buffer, final int origin, - final String word) { + static int writeString(final byte[] buffer, final int origin, final String word) { final int length = word.length(); int index = origin; for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { @@ -223,22 +211,28 @@ public final class BinaryDictDecoderUtils { * * This will also write the terminator byte. * - * @param buffer the OutputStream to write to. + * @param stream the OutputStream to write to. * @param word the string to write. + * @return the size written, in bytes. */ - static void writeString(final OutputStream buffer, final String word) throws IOException { + static int writeString(final OutputStream stream, final String word) throws IOException { final int length = word.length(); + int written = 0; for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { final int codePoint = word.codePointAt(i); - if (1 == getCharSize(codePoint)) { - buffer.write((byte) codePoint); + final int charSize = getCharSize(codePoint); + if (1 == charSize) { + stream.write((byte) codePoint); } else { - buffer.write((byte) (0xFF & (codePoint >> 16))); - buffer.write((byte) (0xFF & (codePoint >> 8))); - buffer.write((byte) (0xFF & codePoint)); + stream.write((byte) (0xFF & (codePoint >> 16))); + stream.write((byte) (0xFF & (codePoint >> 8))); + stream.write((byte) (0xFF & codePoint)); } + written += charSize; } - buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR); + stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR); + written += FormatSpec.PTNODE_TERMINATOR_SIZE; + return written; } /** @@ -275,50 +269,6 @@ public final class BinaryDictDecoderUtils { } } - // Input methods: Read a binary dictionary to memory. - // readDictionaryBinary is the public entry point for them. - - static int readSInt24(final DictBuffer dictBuffer) { - final int retval = dictBuffer.readUnsignedInt24(); - final int sign = ((retval & FormatSpec.MSB24) != 0) ? -1 : 1; - return sign * (retval & FormatSpec.SINT24_MAX); - } - - static int readChildrenAddress(final DictBuffer dictBuffer, - final int optionFlags, final FormatOptions options) { - if (options.mSupportsDynamicUpdate) { - final int address = dictBuffer.readUnsignedInt24(); - if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS; - if ((address & FormatSpec.MSB24) != 0) { - return -(address & FormatSpec.SINT24_MAX); - } else { - return address; - } - } - switch (optionFlags & 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; - } - } - - static int readParentAddress(final DictBuffer dictBuffer, - final FormatOptions formatOptions) { - if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { - final int parentAddress = dictBuffer.readUnsignedInt24(); - final int sign = ((parentAddress & FormatSpec.MSB24) != 0) ? -1 : 1; - return sign * (parentAddress & FormatSpec.SINT24_MAX); - } else { - return FormatSpec.NO_PARENT_ADDRESS; - } - } - /** * Reads and returns the PtNode count out of a buffer and forwards the pointer. */ @@ -338,71 +288,34 @@ public final class BinaryDictDecoderUtils { * @param dictDecoder the dict decoder. * @param headerSize the size of the header. * @param pos the position to seek. - * @param formatOptions file format options. * @return the word with its frequency, as a weighted string. */ + @UsedForTesting /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder, - final int headerSize, final int pos, final FormatOptions formatOptions) { + final int headerSize, final int pos) { final WeightedString result; final int originalPos = dictDecoder.getPosition(); dictDecoder.setPosition(pos); - - if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { - result = getWordAtPositionWithParentAddress(dictDecoder, pos, formatOptions); - } else { - result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos, - formatOptions); - } - + result = getWordAtPositionWithoutParentAddress(dictDecoder, headerSize, pos); dictDecoder.setPosition(originalPos); return result; } - @SuppressWarnings("unused") - private static WeightedString getWordAtPositionWithParentAddress(final DictDecoder dictDecoder, - final int pos, final FormatOptions options) { - int currentPos = pos; - int frequency = Integer.MIN_VALUE; - final StringBuilder builder = new StringBuilder(); - // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH - for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) { - PtNodeInfo currentInfo; - int loopCounter = 0; - do { - dictDecoder.setPosition(currentPos); - currentInfo = dictDecoder.readPtNode(currentPos, options); - if (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)) { - currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress; - } - if (DBG && loopCounter++ > MAX_JUMPS) { - MakedictLog.d("Too many jumps - probably a bug"); - } - } while (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)); - if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency; - builder.insert(0, - new String(currentInfo.mCharacters, 0, currentInfo.mCharacters.length)); - if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break; - currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress; - } - return new WeightedString(builder.toString(), frequency); - } - private static WeightedString getWordAtPositionWithoutParentAddress( - final DictDecoder dictDecoder, final int headerSize, final int pos, - final FormatOptions options) { + final DictDecoder dictDecoder, final int headerSize, final int pos) { dictDecoder.setPosition(headerSize); final int count = dictDecoder.readPtNodeCount(); - int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count); + int groupPos = dictDecoder.getPosition(); final StringBuilder builder = new StringBuilder(); WeightedString result = null; PtNodeInfo last = null; for (int i = count - 1; i >= 0; --i) { - PtNodeInfo info = dictDecoder.readPtNode(groupPos, options); + PtNodeInfo info = dictDecoder.readPtNode(groupPos); groupPos = info.mEndAddress; if (info.mOriginalAddress == pos) { builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); - result = new WeightedString(builder.toString(), info.mFrequency); + result = new WeightedString(builder.toString(), info.mProbabilityInfo); break; // and return } if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) { @@ -430,158 +343,6 @@ public final class BinaryDictDecoderUtils { } /** - * Reads a single node array from a buffer. - * - * This methods reads the file at the current position. A node array is fully expected to start - * at the current position. - * This will recursively read other node arrays into the structure, populating the reverse - * maps on the fly and using them to keep track of already read nodes. - * - * @param dictDecoder the dict decoder, correctly positioned at the start of a node array. - * @param headerSize the size, in bytes, of the file header. - * @param reverseNodeArrayMap a mapping from addresses to already read node arrays. - * @param reversePtNodeMap a mapping from addresses to already read PtNodes. - * @param options file format options. - * @return the read node array with all his children already read. - */ - private static PtNodeArray readNodeArray(final DictDecoder dictDecoder, - final int headerSize, final Map<Integer, PtNodeArray> reverseNodeArrayMap, - final Map<Integer, PtNode> reversePtNodeMap, final FormatOptions options) - throws IOException { - final ArrayList<PtNode> nodeArrayContents = new ArrayList<PtNode>(); - final int nodeArrayOriginPos = dictDecoder.getPosition(); - - do { // Scan the linked-list node. - final int nodeArrayHeadPos = dictDecoder.getPosition(); - final int count = dictDecoder.readPtNodeCount(); - int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count); - for (int i = count; i > 0; --i) { // Scan the array of PtNode. - PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options); - if (BinaryDictIOUtils.isMovedPtNode(info.mFlags, options)) continue; - ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets; - ArrayList<WeightedString> bigrams = null; - if (null != info.mBigrams) { - bigrams = new ArrayList<WeightedString>(); - for (PendingAttribute bigram : info.mBigrams) { - final WeightedString word = getWordAtPosition(dictDecoder, headerSize, - bigram.mAddress, options); - final int reconstructedFrequency = - BinaryDictIOUtils.reconstructBigramFrequency(word.mFrequency, - bigram.mFrequency); - bigrams.add(new WeightedString(word.mWord, reconstructedFrequency)); - } - } - if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) { - PtNodeArray children = reverseNodeArrayMap.get(info.mChildrenAddress); - if (null == children) { - final int currentPosition = dictDecoder.getPosition(); - dictDecoder.setPosition(info.mChildrenAddress); - children = readNodeArray(dictDecoder, headerSize, reverseNodeArrayMap, - reversePtNodeMap, options); - dictDecoder.setPosition(currentPosition); - } - nodeArrayContents.add( - new PtNode(info.mCharacters, shortcutTargets, bigrams, - info.mFrequency, - 0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD), - 0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children)); - } else { - nodeArrayContents.add( - new PtNode(info.mCharacters, shortcutTargets, bigrams, - info.mFrequency, - 0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD), - 0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED))); - } - groupOffsetPos = info.mEndAddress; - } - - // reach the end of the array. - if (options.mSupportsDynamicUpdate) { - final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink(); - if (!hasValidForwardLink) break; - } - } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray()); - - final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents); - nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos; - nodeArray.mCachedAddressAfterUpdate = nodeArrayOriginPos; - reverseNodeArrayMap.put(nodeArray.mCachedAddressAfterUpdate, nodeArray); - return nodeArray; - } - - /** - * Helper function to get the binary format version from the header. - * @throws IOException - */ - private static int getFormatVersion(final DictBuffer dictBuffer) - throws IOException { - final int magic = dictBuffer.readInt(); - if (FormatSpec.MAGIC_NUMBER == magic) return dictBuffer.readUnsignedShort(); - return FormatSpec.NOT_A_VERSION_NUMBER; - } - - /** - * Helper function to get and validate the binary format version. - * @throws UnsupportedFormatException - * @throws IOException - */ - static int checkFormatVersion(final DictBuffer dictBuffer) - throws IOException, UnsupportedFormatException { - final int version = getFormatVersion(dictBuffer); - if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION - || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) { - throw new UnsupportedFormatException("This file has version " + version - + ", but this implementation does not support versions above " - + FormatSpec.MAXIMUM_SUPPORTED_VERSION); - } - return version; - } - - /** - * Reads a buffer and returns the memory representation of the dictionary. - * - * This high-level method takes a buffer and reads its contents, populating a - * FusionDictionary structure. The optional dict argument is an existing dictionary to - * which words from the buffer should be added. If it is null, a new dictionary is created. - * - * @param dictDecoder the dict decoder. - * @param dict an optional dictionary to add words to, or null. - * @return the created (or merged) dictionary. - */ - @UsedForTesting - /* package */ static FusionDictionary readDictionaryBinary(final DictDecoder dictDecoder, - final FusionDictionary dict) throws IOException, UnsupportedFormatException { - // Read header - final FileHeader fileHeader = dictDecoder.readHeader(); - - Map<Integer, PtNodeArray> reverseNodeArrayMapping = new TreeMap<Integer, PtNodeArray>(); - Map<Integer, PtNode> reversePtNodeMapping = new TreeMap<Integer, PtNode>(); - final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mHeaderSize, - reverseNodeArrayMapping, reversePtNodeMapping, fileHeader.mFormatOptions); - - FusionDictionary newDict = new FusionDictionary(root, fileHeader.mDictionaryOptions); - if (null != dict) { - for (final Word w : dict) { - if (w.mIsBlacklistEntry) { - newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord); - } else { - newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord); - } - } - for (final Word w : dict) { - // By construction a binary dictionary may not have bigrams pointing to - // words that are not also registered as unigrams so we don't have to avoid - // them explicitly here. - for (final WeightedString bigram : w.mBigrams) { - newDict.setBigram(w.mWord, bigram.mWord, bigram.mFrequency); - } - } - } - - return newDict; - } - - /** * Helper method to pass a file name instead of a File object to isBinaryDictionary. */ public static boolean isBinaryDictionary(final String filename) { @@ -592,32 +353,14 @@ public final class BinaryDictDecoderUtils { /** * Basic test to find out whether the file is a binary dictionary or not. * - * Concretely this only tests the magic number. - * * @param file The file to test. * @return true if it's a binary dictionary, false otherwise */ public static boolean isBinaryDictionary(final File file) { - FileInputStream inStream = null; - try { - inStream = new FileInputStream(file); - final ByteBuffer buffer = inStream.getChannel().map( - FileChannel.MapMode.READ_ONLY, 0, file.length()); - final int version = getFormatVersion(new ByteBufferDictBuffer(buffer)); - return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION - && version <= FormatSpec.MAXIMUM_SUPPORTED_VERSION); - } catch (FileNotFoundException e) { - return false; - } catch (IOException e) { + final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file); + if (dictDecoder == null) { return false; - } finally { - if (inStream != null) { - try { - inStream.close(); - } catch (IOException e) { - // do nothing - } - } } + return dictDecoder.hasValidRawBinaryDictionary(); } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java index f761829de..1593dea4f 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java @@ -16,10 +16,11 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; @@ -121,18 +122,13 @@ public class BinaryDictEncoderUtils { * Compute the maximum size of a PtNode, assuming 3-byte addresses for everything. * * @param ptNode the PtNode to compute the size of. - * @param options file format options. * @return the maximum size of the PtNode. */ - private static int getPtNodeMaximumSize(final PtNode ptNode, final FormatOptions options) { - int size = getNodeHeaderSize(ptNode, options); + private static int getPtNodeMaximumSize(final PtNode ptNode) { + int size = getNodeHeaderSize(ptNode); if (ptNode.isTerminal()) { - // If terminal, one byte for the frequency or four bytes for the terminal id. - if (options.mHasTerminalId) { - size += FormatSpec.PTNODE_TERMINAL_ID_SIZE; - } else { - size += FormatSpec.PTNODE_FREQUENCY_SIZE; - } + // If terminal, one byte for the frequency. + size += FormatSpec.PTNODE_FREQUENCY_SIZE; } size += FormatSpec.PTNODE_MAX_ADDRESS_SIZE; // For children address size += getShortcutListSize(ptNode.mShortcutTargets); @@ -150,19 +146,14 @@ public class BinaryDictEncoderUtils { * the containing node array, and cache it it its 'mCachedSize' member. * * @param ptNodeArray the node array to compute the maximum size of. - * @param options file format options. */ - private static void calculatePtNodeArrayMaximumSize(final PtNodeArray ptNodeArray, - final FormatOptions options) { + private static void calculatePtNodeArrayMaximumSize(final PtNodeArray ptNodeArray) { int size = getPtNodeCountSize(ptNodeArray); for (PtNode node : ptNodeArray.mData) { - final int nodeSize = getPtNodeMaximumSize(node, options); + final int nodeSize = getPtNodeMaximumSize(node); node.mCachedSize = nodeSize; size += nodeSize; } - if (options.mSupportsDynamicUpdate) { - size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE; - } ptNodeArray.mCachedSize = size; } @@ -170,15 +161,9 @@ public class BinaryDictEncoderUtils { * Compute the size of the header (flag + [parent address] + characters size) of a PtNode. * * @param ptNode the PtNode of which to compute the size of the header - * @param options file format options. */ - private static int getNodeHeaderSize(final PtNode ptNode, final FormatOptions options) { - if (BinaryDictIOUtils.supportsDynamicUpdate(options)) { - return FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE - + getPtNodeCharactersSize(ptNode); - } else { - return FormatSpec.PTNODE_FLAGS_SIZE + getPtNodeCharactersSize(ptNode); - } + private static int getNodeHeaderSize(final PtNode ptNode) { + return FormatSpec.PTNODE_FLAGS_SIZE + getPtNodeCharactersSize(ptNode); } /** @@ -245,6 +230,27 @@ public class BinaryDictEncoderUtils { } } + @UsedForTesting + static void writeUIntToDictBuffer(final DictBuffer dictBuffer, final int value, + final int size) { + switch(size) { + case 4: + dictBuffer.put((byte) ((value >> 24) & 0xFF)); + /* fall through */ + case 3: + dictBuffer.put((byte) ((value >> 16) & 0xFF)); + /* fall through */ + case 2: + dictBuffer.put((byte) ((value >> 8) & 0xFF)); + /* fall through */ + case 1: + dictBuffer.put((byte) (value & 0xFF)); + break; + default: + /* nop */ + } + } + // End utility methods // This method is responsible for finding a nice ordering of the nodes that favors run-time @@ -357,11 +363,10 @@ public class BinaryDictEncoderUtils { * * @param ptNodeArray the node array to compute the size of. * @param dict the dictionary in which the word/attributes are to be found. - * @param formatOptions file format options. * @return false if none of the cached addresses inside the node array changed, true otherwise. */ private static boolean computeActualPtNodeArraySize(final PtNodeArray ptNodeArray, - final FusionDictionary dict, final FormatOptions formatOptions) { + final FusionDictionary dict) { boolean changed = false; int size = getPtNodeCountSize(ptNodeArray); for (PtNode ptNode : ptNodeArray.mData) { @@ -369,37 +374,26 @@ public class BinaryDictEncoderUtils { if (ptNode.mCachedAddressAfterUpdate != ptNode.mCachedAddressBeforeUpdate) { changed = true; } - int nodeSize = getNodeHeaderSize(ptNode, formatOptions); + int nodeSize = getNodeHeaderSize(ptNode); if (ptNode.isTerminal()) { - if (formatOptions.mHasTerminalId) { - nodeSize += FormatSpec.PTNODE_TERMINAL_ID_SIZE; - } else { - nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE; - } + nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE; } - if (formatOptions.mSupportsDynamicUpdate) { - nodeSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; - } else if (null != ptNode.mChildren) { + if (null != ptNode.mChildren) { nodeSize += getByteSize(getOffsetToTargetNodeArrayDuringUpdate(ptNodeArray, nodeSize + size, ptNode.mChildren)); } - if (formatOptions.mVersion < FormatSpec.FIRST_VERSION_WITH_TERMINAL_ID) { - nodeSize += getShortcutListSize(ptNode.mShortcutTargets); - if (null != ptNode.mBigrams) { - for (WeightedString bigram : ptNode.mBigrams) { - final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray, - nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE, - FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord)); - nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE; - } + nodeSize += getShortcutListSize(ptNode.mShortcutTargets); + if (null != ptNode.mBigrams) { + for (WeightedString bigram : ptNode.mBigrams) { + final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray, + nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE, + FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord)); + nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE; } } ptNode.mCachedSize = nodeSize; size += nodeSize; } - if (formatOptions.mSupportsDynamicUpdate) { - size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE; - } if (ptNodeArray.mCachedSize != size) { ptNodeArray.mCachedSize = size; changed = true; @@ -411,11 +405,10 @@ public class BinaryDictEncoderUtils { * Initializes the cached addresses of node arrays and their containing nodes from their size. * * @param flatNodes the list of node arrays. - * @param formatOptions file format options. * @return the byte size of the entire stack. */ - private static int initializePtNodeArraysCachedAddresses(final ArrayList<PtNodeArray> flatNodes, - final FormatOptions formatOptions) { + private static int initializePtNodeArraysCachedAddresses( + final ArrayList<PtNodeArray> flatNodes) { int nodeArrayOffset = 0; for (final PtNodeArray nodeArray : flatNodes) { nodeArray.mCachedAddressBeforeUpdate = nodeArrayOffset; @@ -446,28 +439,6 @@ public class BinaryDictEncoderUtils { } /** - * Compute the cached parent addresses after all has been updated. - * - * The parent addresses are used by some binary formats at write-to-disk time. Not all formats - * need them. In particular, version 2 does not need them, and version 3 does. - * - * @param flatNodes the flat array of node arrays to fill in - */ - private static void computeParentAddresses(final ArrayList<PtNodeArray> flatNodes) { - for (final PtNodeArray nodeArray : flatNodes) { - for (final PtNode ptNode : nodeArray.mData) { - if (null != ptNode.mChildren) { - // Assign my address to children's parent address - // Here BeforeUpdate and AfterUpdate addresses have the same value, so it - // does not matter which we use. - ptNode.mChildren.mCachedParentAddress = ptNode.mCachedAddressAfterUpdate - - ptNode.mChildren.mCachedAddressAfterUpdate; - } - } - } - } - - /** * Compute the addresses and sizes of an ordered list of PtNode arrays. * * This method takes a list of PtNode arrays and will update their cached address and size @@ -479,14 +450,15 @@ public class BinaryDictEncoderUtils { * * @param dict the dictionary * @param flatNodes the ordered list of PtNode arrays - * @param formatOptions file format options. * @return the same array it was passed. The nodes have been updated for address and size. */ /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict, - final ArrayList<PtNodeArray> flatNodes, final FormatOptions formatOptions) { + final ArrayList<PtNodeArray> flatNodes) { // First get the worst possible sizes and offsets - for (final PtNodeArray n : flatNodes) calculatePtNodeArrayMaximumSize(n, formatOptions); - final int offset = initializePtNodeArraysCachedAddresses(flatNodes, formatOptions); + for (final PtNodeArray n : flatNodes) { + calculatePtNodeArrayMaximumSize(n); + } + final int offset = initializePtNodeArraysCachedAddresses(flatNodes); MakedictLog.i("Compressing the array addresses. Original size : " + offset); MakedictLog.i("(Recursively seen size : " + offset + ")"); @@ -499,8 +471,7 @@ public class BinaryDictEncoderUtils { for (final PtNodeArray ptNodeArray : flatNodes) { ptNodeArray.mCachedAddressAfterUpdate = ptNodeArrayStartOffset; final int oldNodeArraySize = ptNodeArray.mCachedSize; - final boolean changed = - computeActualPtNodeArraySize(ptNodeArray, dict, formatOptions); + final boolean changed = computeActualPtNodeArraySize(ptNodeArray, dict); final int newNodeArraySize = ptNodeArray.mCachedSize; if (oldNodeArraySize < newNodeArraySize) { throw new RuntimeException("Increased size ?!"); @@ -513,9 +484,6 @@ public class BinaryDictEncoderUtils { if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug"); } while (changesDone); - if (formatOptions.mSupportsDynamicUpdate) { - computeParentAddresses(flatNodes); - } final PtNodeArray lastPtNodeArray = flatNodes.get(flatNodes.size() - 1); MakedictLog.i("Compression complete in " + passes + " passes."); MakedictLog.i("After address compression : " @@ -612,35 +580,29 @@ public class BinaryDictEncoderUtils { * @param hasBigrams whether the PtNode has bigrams. * @param isNotAWord whether the PtNode is not a word. * @param isBlackListEntry whether the PtNode is a blacklist entry. - * @param formatOptions file format options. * @return the flags */ static int makePtNodeFlags(final boolean hasMultipleChars, final boolean isTerminal, final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams, - final boolean isNotAWord, final boolean isBlackListEntry, - final FormatOptions formatOptions) { + final boolean isNotAWord, final boolean isBlackListEntry) { byte flags = 0; if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL; - if (formatOptions.mSupportsDynamicUpdate) { - flags |= FormatSpec.FLAG_IS_NOT_MOVED; - } else if (true) { - switch (childrenAddressSize) { - case 1: - flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE; - break; - case 2: - flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES; - break; - case 3: - flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES; - break; - case 0: - flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS; - break; - default: - throw new RuntimeException("Node with a strange address"); - } + switch (childrenAddressSize) { + case 1: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES; + break; + case 0: + flags |= FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS; + break; + default: + throw new RuntimeException("Node with a strange address"); } if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS; @@ -649,12 +611,12 @@ public class BinaryDictEncoderUtils { return flags; } - /* package */ static byte makePtNodeFlags(final PtNode node, final int childrenOffset, - final FormatOptions formatOptions) { - return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0, + /* package */ static byte makePtNodeFlags(final PtNode node, final int childrenOffset) { + return (byte) makePtNodeFlags(node.mChars.length > 1, node.isTerminal(), getByteSize(childrenOffset), node.mShortcutTargets != null && !node.mShortcutTargets.isEmpty(), - node.mBigrams != null, node.mIsNotAWord, node.mIsBlacklistEntry, formatOptions); + node.mBigrams != null && !node.mBigrams.isEmpty(), + node.mIsNotAWord, node.mIsBlacklistEntry); } /** @@ -690,6 +652,13 @@ public class BinaryDictEncoderUtils { + word + " is " + unigramFrequency); bigramFrequency = unigramFrequency; } + bigramFlags += getBigramFrequencyDiff(unigramFrequency, bigramFrequency) + & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY; + return bigramFlags; + } + + public static int getBigramFrequencyDiff(final int unigramFrequency, + final int bigramFrequency) { // We compute the difference between 255 (which means probability = 1) and the // unigram score. We split this into a number of discrete steps. // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15 @@ -723,22 +692,7 @@ public class BinaryDictEncoderUtils { // include this bigram in the dictionary. For now, register as 0, and live with the // small over-estimation that we get in this case. TODO: actually remove this bigram // if discretizedFrequency < 0. - final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0; - bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY; - return bigramFlags; - } - - /** - * Makes the 2-byte value for options flags. - */ - private static final int makeOptionsValue(final FusionDictionary dictionary, - final FormatOptions formatOptions) { - final DictionaryOptions options = dictionary.mOptions; - final boolean hasBigrams = dictionary.hasBigrams(); - return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0) - + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0) - + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0) - + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0); + return discretizedFrequency > 0 ? discretizedFrequency : 0; } /** @@ -753,38 +707,14 @@ public class BinaryDictEncoderUtils { + (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY); } - /* package */ static final int writeParentAddress(final byte[] buffer, final int index, - final int address, final FormatOptions formatOptions) { - if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { - if (address == FormatSpec.NO_PARENT_ADDRESS) { - buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; - } else { - final int absAddress = Math.abs(address); - assert(absAddress <= FormatSpec.SINT24_MAX); - buffer[index] = (byte)((address < 0 ? FormatSpec.MSB8 : 0) - | ((absAddress >> 16) & 0xFF)); - buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF); - buffer[index + 2] = (byte)(absAddress & 0xFF); - } - return index + 3; - } else { - return index; - } - } - - /* package */ static final int getChildrenPosition(final PtNode ptNode, - final FormatOptions formatOptions) { + /* package */ static final int getChildrenPosition(final PtNode ptNode) { int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate - + getNodeHeaderSize(ptNode, formatOptions); + + getNodeHeaderSize(ptNode); if (ptNode.isTerminal()) { - // A terminal node has either the terminal id or the frequency. + // A terminal node has the frequency. // If positionOfChildrenPosField is incorrect, we may crash when jumping to the children // position. - if (formatOptions.mHasTerminalId) { - positionOfChildrenPosField += FormatSpec.PTNODE_TERMINAL_ID_SIZE; - } else { - positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE; - } + positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE; } return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField; @@ -796,12 +726,10 @@ public class BinaryDictEncoderUtils { * @param dict the dictionary the node array is a part of (for relative offsets). * @param dictEncoder the dictionary encoder. * @param ptNodeArray the node array to write. - * @param formatOptions file format options. */ @SuppressWarnings("unused") /* package */ static void writePlacedPtNodeArray(final FusionDictionary dict, - final DictEncoder dictEncoder, final PtNodeArray ptNodeArray, - final FormatOptions formatOptions) { + final DictEncoder dictEncoder, final PtNodeArray ptNodeArray) { // TODO: Make the code in common with BinaryDictIOUtils#writePtNode dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate); @@ -819,15 +747,12 @@ public class BinaryDictEncoderUtils { + ptNode.mCachedAddressAfterUpdate); } // Sanity checks. - if (DBG && ptNode.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) { + if (DBG && ptNode.getProbability() > FormatSpec.MAX_TERMINAL_FREQUENCY) { throw new RuntimeException("A node has a frequency > " + FormatSpec.MAX_TERMINAL_FREQUENCY - + " : " + ptNode.mFrequency); + + " : " + ptNode.mProbabilityInfo.toString()); } - dictEncoder.writePtNode(ptNode, parentPosition, formatOptions, dict); - } - if (formatOptions.mSupportsDynamicUpdate) { - dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS); + dictEncoder.writePtNode(ptNode, dict); } if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize) { @@ -857,7 +782,7 @@ public class BinaryDictEncoderUtils { for (final PtNode ptNode : ptNodeArray.mData) { ++ptNodes; if (ptNode.mChars.length > maxRuns) maxRuns = ptNode.mChars.length; - if (ptNode.mFrequency >= 0) { + if (ptNode.isTerminal()) { if (ptNodeArray.mCachedAddressAfterUpdate < firstTerminalAddress) firstTerminalAddress = ptNodeArray.mCachedAddressAfterUpdate; if (ptNodeArray.mCachedAddressAfterUpdate > lastTerminalAddress) @@ -927,7 +852,8 @@ public class BinaryDictEncoderUtils { headerBuffer.write((byte) (0xFF & version)); // Options flags - final int options = makeOptionsValue(dict, formatOptions); + // TODO: Remove this field. + final int options = 0; headerBuffer.write((byte) (0xFF & (options >> 8))); headerBuffer.write((byte) (0xFF & options)); final int headerSizeOffset = headerBuffer.size(); diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index d5516ef46..989ca4b2f 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -18,12 +18,8 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import com.android.inputmethod.latin.utils.ByteArrayDictBuffer; import java.io.File; @@ -32,7 +28,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Iterator; import java.util.Map; import java.util.Stack; @@ -62,16 +57,15 @@ public final class BinaryDictIOUtils { * Retrieves all node arrays without recursive call. */ private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder, - final int headerSize, final Map<Integer, String> words, + final int bodyOffset, final Map<Integer, String> words, final Map<Integer, Integer> frequencies, - final Map<Integer, ArrayList<PendingAttribute>> bigrams, - final FormatOptions formatOptions) { + final Map<Integer, ArrayList<PendingAttribute>> bigrams) { int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1]; Stack<Position> stack = new Stack<Position>(); int index = 0; - Position initPos = new Position(headerSize, 0); + Position initPos = new Position(bodyOffset, 0); stack.push(initPos); while (!stack.empty()) { @@ -87,51 +81,36 @@ public final class BinaryDictIOUtils { if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) { p.mNumOfPtNode = dictDecoder.readPtNodeCount(); - p.mAddress += getPtNodeCountSize(p.mNumOfPtNode); + p.mAddress = dictDecoder.getPosition(); p.mPosition = 0; } if (p.mNumOfPtNode == 0) { stack.pop(); continue; } - PtNodeInfo info = dictDecoder.readPtNode(p.mAddress, formatOptions); - for (int i = 0; i < info.mCharacters.length; ++i) { - pushedChars[index++] = info.mCharacters[i]; + final PtNodeInfo ptNodeInfo = dictDecoder.readPtNode(p.mAddress); + for (int i = 0; i < ptNodeInfo.mCharacters.length; ++i) { + pushedChars[index++] = ptNodeInfo.mCharacters[i]; } p.mPosition++; - - final boolean isMovedPtNode = isMovedPtNode(info.mFlags, - formatOptions); - final boolean isDeletedPtNode = isDeletedPtNode(info.mFlags, - formatOptions); - if (!isMovedPtNode && !isDeletedPtNode - && info.mFrequency != FusionDictionary.PtNode.NOT_A_TERMINAL) {// found word - words.put(info.mOriginalAddress, new String(pushedChars, 0, index)); - frequencies.put(info.mOriginalAddress, info.mFrequency); - if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams); + if (ptNodeInfo.isTerminal()) {// found word + words.put(ptNodeInfo.mOriginalAddress, new String(pushedChars, 0, index)); + frequencies.put( + ptNodeInfo.mOriginalAddress, ptNodeInfo.mProbabilityInfo.mProbability); + if (ptNodeInfo.mBigrams != null) { + bigrams.put(ptNodeInfo.mOriginalAddress, ptNodeInfo.mBigrams); + } } if (p.mPosition == p.mNumOfPtNode) { - if (formatOptions.mSupportsDynamicUpdate) { - final boolean hasValidForwardLinkAddress = - dictDecoder.readAndFollowForwardLink(); - if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) { - // The node array has a forward link. - p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT; - p.mAddress = dictDecoder.getPosition(); - } else { - stack.pop(); - } - } else { - stack.pop(); - } + stack.pop(); } else { - // The Ptnode array has more PtNodes. + // The PtNode array has more PtNodes. p.mAddress = dictDecoder.getPosition(); } - if (!isMovedPtNode && hasChildrenAddress(info.mChildrenAddress)) { - final Position childrenPos = new Position(info.mChildrenAddress, index); + if (hasChildrenAddress(ptNodeInfo.mChildrenAddress)) { + final Position childrenPos = new Position(ptNodeInfo.mChildrenAddress, index); stack.push(childrenPos); } } @@ -153,9 +132,9 @@ public final class BinaryDictIOUtils { final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException, UnsupportedFormatException { // Read header - final FileHeader header = dictDecoder.readHeader(); - readUnigramsAndBigramsBinaryInner(dictDecoder, header.mHeaderSize, words, - frequencies, bigrams, header.mFormatOptions); + final DictionaryHeader header = dictDecoder.readHeader(); + readUnigramsAndBigramsBinaryInner(dictDecoder, header.mBodyOffset, words, + frequencies, bigrams); } /** @@ -173,8 +152,7 @@ public final class BinaryDictIOUtils { final String word) throws IOException, UnsupportedFormatException { if (word == null) return FormatSpec.NOT_VALID_WORD; dictDecoder.setPosition(0); - - final FileHeader header = dictDecoder.readHeader(); + dictDecoder.readHeader(); int wordPos = 0; final int wordLen = word.codePointCount(0, word.length()); for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) { @@ -185,13 +163,7 @@ public final class BinaryDictIOUtils { boolean foundNextPtNode = false; for (int i = 0; i < ptNodeCount; ++i) { final int ptNodePos = dictDecoder.getPosition(); - final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos, - header.mFormatOptions); - final boolean isMovedNode = isMovedPtNode(currentInfo.mFlags, - header.mFormatOptions); - final boolean isDeletedNode = isDeletedPtNode(currentInfo.mFlags, - header.mFormatOptions); - if (isMovedNode) continue; + final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos); boolean same = true; for (int p = 0, j = word.offsetByCodePoints(0, wordPos); p < currentInfo.mCharacters.length; @@ -206,8 +178,7 @@ public final class BinaryDictIOUtils { if (same) { // found the PtNode matches the word. if (wordPos + currentInfo.mCharacters.length == wordLen) { - if (currentInfo.mFrequency == PtNode.NOT_A_TERMINAL - || isDeletedNode) { + if (!currentInfo.isTerminal()) { return FormatSpec.NOT_VALID_WORD; } else { return ptNodePos; @@ -222,255 +193,33 @@ public final class BinaryDictIOUtils { break; } } - - // If we found the next PtNode, it is under the file pointer. - // But if not, we are at the end of this node array so we expect to have - // a forward link address that we need to consult and possibly resume - // search on the next node array in the linked list. if (foundNextPtNode) break; - if (!header.mFormatOptions.mSupportsDynamicUpdate) { - return FormatSpec.NOT_VALID_WORD; - } - - final boolean hasValidForwardLinkAddress = - dictDecoder.readAndFollowForwardLink(); - if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) { - return FormatSpec.NOT_VALID_WORD; - } + return FormatSpec.NOT_VALID_WORD; } while(true); } return FormatSpec.NOT_VALID_WORD; } /** - * @return the size written, in bytes. Always 3 bytes. - */ - static int writeSInt24ToBuffer(final DictBuffer dictBuffer, - final int value) { - final int absValue = Math.abs(value); - dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); - dictBuffer.put((byte)((absValue >> 8) & 0xFF)); - dictBuffer.put((byte)(absValue & 0xFF)); - return 3; - } - - /** - * @return the size written, in bytes. Always 3 bytes. - */ - static int writeSInt24ToStream(final OutputStream destination, final int value) - throws IOException { - final int absValue = Math.abs(value); - destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); - destination.write((byte)((absValue >> 8) & 0xFF)); - destination.write((byte)(absValue & 0xFF)); - return 3; - } - - /** - * @return the size written, in bytes. 1, 2, or 3 bytes. - */ - private static int writeVariableAddress(final OutputStream destination, final int value) - throws IOException { - switch (BinaryDictEncoderUtils.getByteSize(value)) { - case 1: - destination.write((byte)value); - break; - case 2: - destination.write((byte)(0xFF & (value >> 8))); - destination.write((byte)(0xFF & value)); - break; - case 3: - destination.write((byte)(0xFF & (value >> 16))); - destination.write((byte)(0xFF & (value >> 8))); - destination.write((byte)(0xFF & value)); - break; - } - return BinaryDictEncoderUtils.getByteSize(value); - } - - static void skipString(final DictBuffer dictBuffer, - final boolean hasMultipleChars) { - if (hasMultipleChars) { - int character = CharEncoding.readChar(dictBuffer); - while (character != FormatSpec.INVALID_CHARACTER) { - character = CharEncoding.readChar(dictBuffer); - } - } else { - CharEncoding.readChar(dictBuffer); - } - } - - /** - * Write a string to a stream. - * - * @param destination the stream to write. - * @param word the string to be written. - * @return the size written, in bytes. - * @throws IOException - */ - private static int writeString(final OutputStream destination, final String word) - throws IOException { - int size = 0; - final int length = word.length(); - for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { - final int codePoint = word.codePointAt(i); - if (CharEncoding.getCharSize(codePoint) == 1) { - destination.write((byte)codePoint); - size++; - } else { - destination.write((byte)(0xFF & (codePoint >> 16))); - destination.write((byte)(0xFF & (codePoint >> 8))); - destination.write((byte)(0xFF & codePoint)); - size += 3; - } - } - destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR); - size += FormatSpec.PTNODE_TERMINATOR_SIZE; - return size; - } - - /** - * Write a PtNode to an output stream from a PtNodeInfo. - * A PtNode is an in-memory representation of a node in the patricia trie. - * A PtNode info is a container for low-level information about how the - * PtNode is stored in the binary format. - * - * @param destination the stream to write. - * @param info the PtNode info to be written. - * @return the size written, in bytes. - */ - private static int writePtNode(final OutputStream destination, final PtNodeInfo info) - throws IOException { - int size = FormatSpec.PTNODE_FLAGS_SIZE; - destination.write((byte)info.mFlags); - final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ? - FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress; - size += writeSInt24ToStream(destination, parentOffset); - - for (int i = 0; i < info.mCharacters.length; ++i) { - if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) { - destination.write((byte)info.mCharacters[i]); - size++; - } else { - size += writeSInt24ToStream(destination, info.mCharacters[i]); - } - } - if (info.mCharacters.length > 1) { - destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR); - size++; - } - - if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { - destination.write((byte)info.mFrequency); - size++; - } - - if (DBG) { - MakedictLog.d("writePtNode origin=" + info.mOriginalAddress + ", size=" + size - + ", child=" + info.mChildrenAddress + ", characters =" - + new String(info.mCharacters, 0, info.mCharacters.length)); - } - final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ? - 0 : info.mChildrenAddress - (info.mOriginalAddress + size); - writeSInt24ToStream(destination, childrenOffset); - size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; - - if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) { - final int shortcutListSize = - BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets); - destination.write((byte)(shortcutListSize >> 8)); - destination.write((byte)(shortcutListSize & 0xFF)); - size += 2; - final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator(); - while (shortcutIterator.hasNext()) { - final WeightedString target = shortcutIterator.next(); - destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags( - shortcutIterator.hasNext(), target.mFrequency)); - size++; - size += writeString(destination, target.mWord); - } - } - - if (info.mBigrams != null) { - // TODO: Consolidate this code with the code that computes the size of the bigram list - // in BinaryDictEncoderUtils#computeActualNodeArraySize - for (int i = 0; i < info.mBigrams.size(); ++i) { - - final int bigramFrequency = info.mBigrams.get(i).mFrequency; - int bigramFlags = (i < info.mBigrams.size() - 1) - ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0; - size++; - final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress - + size); - bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0; - switch (BinaryDictEncoderUtils.getByteSize(bigramOffset)) { - case 1: - bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE; - break; - case 2: - bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES; - break; - case 3: - bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES; - break; - } - bigramFlags |= bigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY; - destination.write((byte)bigramFlags); - size += writeVariableAddress(destination, Math.abs(bigramOffset)); - } - } - return size; - } - - /** - * Compute the size of the PtNode. - */ - static int computePtNodeSize(final PtNodeInfo info, final FormatOptions formatOptions) { - int size = FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE - + BinaryDictEncoderUtils.getPtNodeCharactersSize(info.mCharacters) - + getChildrenAddressSize(info.mFlags, formatOptions); - if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { - size += FormatSpec.PTNODE_FREQUENCY_SIZE; - } - if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) { - size += BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets); - } - if (info.mBigrams != null) { - for (final PendingAttribute attr : info.mBigrams) { - size += FormatSpec.PTNODE_FLAGS_SIZE; - size += BinaryDictEncoderUtils.getByteSize(attr.mAddress); - } - } - return size; - } - - /** - * Write a node array to the stream. + * Writes a PtNodeCount to the stream. * * @param destination the stream to write. - * @param infos an array of PtNodeInfo to be written. - * @return the size written, in bytes. - * @throws IOException + * @param ptNodeCount the count. + * @return the size written in bytes. */ - static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos) + @UsedForTesting + static int writePtNodeCount(final OutputStream destination, final int ptNodeCount) throws IOException { - int size = getPtNodeCountSize(infos.length); - switch (getPtNodeCountSize(infos.length)) { - case 1: - destination.write((byte)infos.length); - break; - case 2: - final int encodedPtNodeCount = - infos.length | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG; - destination.write((byte)(encodedPtNodeCount >> 8)); - destination.write((byte)(encodedPtNodeCount & 0xFF)); - break; - default: - throw new RuntimeException("Invalid node count size."); + final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount); + // the count must fit on one byte or two bytes. + // Please see comments in FormatSpec. + if (countSize != 1 && countSize != 2) { + throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize); } - for (final PtNodeInfo info : infos) size += writePtNode(destination, info); - writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS); - return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + final int encodedPtNodeCount = (countSize == 2) ? + (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount; + BinaryDictEncoderUtils.writeUIntToStream(destination, encodedPtNodeCount, countSize); + return countSize; } private static final int HEADER_READING_BUFFER_SIZE = 16384; @@ -482,8 +231,9 @@ public final class BinaryDictIOUtils { * @param file The file to read. * @param offset The offset in the file where to start reading the data. * @param length The length of the data file. + * @return the header of the specified dictionary file. */ - private static FileHeader getDictionaryFileHeader( + private static DictionaryHeader getDictionaryFileHeader( final File file, final long offset, final long length) throws FileNotFoundException, IOException, UnsupportedFormatException { final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE]; @@ -503,13 +253,16 @@ public final class BinaryDictIOUtils { } } ); + if (dictDecoder == null) { + return null; + } return dictDecoder.readHeader(); } - public static FileHeader getDictionaryFileHeaderOrNull(final File file, final long offset, + public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file, final long offset, final long length) { try { - final FileHeader header = getDictionaryFileHeader(file, offset, length); + final DictionaryHeader header = getDictionaryFileHeader(file, offset, length); return header; } catch (UnsupportedFormatException e) { return null; @@ -526,30 +279,6 @@ public final class BinaryDictIOUtils { } /** - * Helper method to check whether the node is moved. - */ - public static boolean isMovedPtNode(final int flags, final FormatOptions options) { - return options.mSupportsDynamicUpdate - && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED); - } - - /** - * Helper method to check whether the dictionary can be updated dynamically. - */ - public static boolean supportsDynamicUpdate(final FormatOptions options) { - return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE - && options.mSupportsDynamicUpdate; - } - - /** - * Helper method to check whether the node is deleted. - */ - public static boolean isDeletedPtNode(final int flags, final FormatOptions formatOptions) { - return formatOptions.mSupportsDynamicUpdate - && ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED); - } - - /** * Compute the binary size of the node count * @param count the node count * @return the size of the node count, either 1 or 2 bytes. @@ -566,9 +295,7 @@ public final class BinaryDictIOUtils { } } - static int getChildrenAddressSize(final int optionFlags, - final FormatOptions formatOptions) { - if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + static int getChildrenAddressSize(final int optionFlags) { switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) { case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE: return 1; @@ -589,6 +316,7 @@ public final class BinaryDictIOUtils { * @param bigramFrequency compressed frequency * @return approximate bigram frequency */ + @UsedForTesting public static int reconstructBigramFrequency(final int unigramFrequency, final int bigramFrequency) { final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency) diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java index 3dbeee099..a3b28a702 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java @@ -18,8 +18,6 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.utils.ByteArrayDictBuffer; import java.io.File; @@ -35,37 +33,34 @@ import java.util.TreeMap; /** * An interface of binary dictionary decoders. */ +// TODO: Straighten out responsibility for the buffer's file pointer. public interface DictDecoder { /** * Reads and returns the file header. */ - public FileHeader readHeader() throws IOException, UnsupportedFormatException; + public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException; /** - * Reads PtNode from nodeAddress. + * Reads PtNode from ptNodePos. * @param ptNodePos the position of PtNode. - * @param formatOptions the format options. * @return PtNodeInfo. */ - public PtNodeInfo readPtNode(final int ptNodePos, final FormatOptions formatOptions); + public PtNodeInfo readPtNode(final int ptNodePos); /** * Reads a buffer and returns the memory representation of the dictionary. * * This high-level method takes a buffer and reads its contents, populating a - * FusionDictionary structure. The optional dict argument is an existing dictionary to - * which words from the buffer should be added. If it is null, a new dictionary is created. + * FusionDictionary structure. * - * @param dict an optional dictionary to add words to, or null. * @param deleteDictIfBroken a flag indicating whether this method should remove the broken * dictionary or not. - * @return the created (or merged) dictionary. + * @return the created dictionary. */ @UsedForTesting - public FusionDictionary readDictionaryBinary(final FusionDictionary dict, - final boolean deleteDictIfBroken) - throws FileNotFoundException, IOException, UnsupportedFormatException; + public FusionDictionary readDictionaryBinary(final boolean deleteDictIfBroken) + throws FileNotFoundException, IOException, UnsupportedFormatException; /** * Gets the address of the last PtNode of the exact matching word in the dictionary. @@ -116,18 +111,11 @@ public interface DictDecoder { public int readPtNodeCount(); /** - * Reads the forward link and advances the position. - * - * @return true if this method moves the file pointer, false otherwise. - */ - public boolean readAndFollowForwardLink(); - public boolean hasNextPtNodeArray(); - - /** * Opens the dictionary file and makes DictBuffer. */ @UsedForTesting - public void openDictBuffer() throws FileNotFoundException, IOException; + public void openDictBuffer() throws FileNotFoundException, IOException, + UnsupportedFormatException; @UsedForTesting public boolean isDictBufferOpen(); @@ -227,5 +215,8 @@ public interface DictDecoder { } } - public void skipPtNode(final FormatOptions formatOptions); + /** + * @return whether this decoder has a valid binary dictionary that it can decode. + */ + public boolean hasValidRawBinaryDictionary(); } diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java index ea5d492d8..a5dc45691 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java @@ -32,7 +32,5 @@ public interface DictEncoder { public int getPosition(); public void writePtNodeCount(final int ptNodeCount); public void writeForwardLinkAddress(final int forwardLinkAddress); - - public void writePtNode(final PtNode ptNode, final int parentPosition, - final FormatOptions formatOptions, final FusionDictionary dict); + public void writePtNode(final PtNode ptNode, final FusionDictionary dict); } diff --git a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java deleted file mode 100644 index c4f7ec91f..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.makedict.FusionDictionary.WeightedString; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * An interface of a binary dictionary updater. - */ -@UsedForTesting -public interface DictUpdater extends DictDecoder { - - /** - * Deletes the word from the binary dictionary. - * - * @param word the word to be deleted. - */ - @UsedForTesting - public void deleteWord(final String word) throws IOException, UnsupportedFormatException; - - /** - * Inserts a word into a binary dictionary. - * - * @param word the word to be inserted. - * @param frequency the frequency of the new word. - * @param bigramStrings bigram list, or null if none. - * @param shortcuts shortcut list, or null if none. - * @param isBlackListEntry whether this should be a blacklist entry. - */ - // TODO: Support batch insertion. - @UsedForTesting - public void insertWord(final String word, final int frequency, - final ArrayList<WeightedString> bigramStrings, - final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, - final boolean isBlackListEntry) throws IOException, UnsupportedFormatException; -} diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java new file mode 100644 index 000000000..b99e281da --- /dev/null +++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 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.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; + +/** + * Class representing dictionary header. + */ +public final class DictionaryHeader { + public final int mBodyOffset; + public final DictionaryOptions mDictionaryOptions; + public final FormatOptions mFormatOptions; + + // Note that these are corresponding definitions in native code in latinime::HeaderPolicy + // and latinime::HeaderReadWriteUtils. + // TODO: Standardize the key names and bump up the format version, taking care not to + // break format version 2 dictionaries. + public static final String DICTIONARY_VERSION_KEY = "version"; + public static final String DICTIONARY_LOCALE_KEY = "locale"; + public static final String DICTIONARY_ID_KEY = "dictionary"; + public static final String DICTIONARY_DESCRIPTION_KEY = "description"; + public static final String DICTIONARY_DATE_KEY = "date"; + public static final String HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO"; + public static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE"; + public static final String ATTRIBUTE_VALUE_TRUE = "1"; + + public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions, + final FormatOptions formatOptions) throws UnsupportedFormatException { + mDictionaryOptions = dictionaryOptions; + mFormatOptions = formatOptions; + mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0; + if (null == getLocaleString()) { + throw new UnsupportedFormatException("Cannot create a FileHeader without a locale"); + } + if (null == getVersion()) { + throw new UnsupportedFormatException( + "Cannot create a FileHeader without a version"); + } + if (null == getId()) { + throw new UnsupportedFormatException("Cannot create a FileHeader without an ID"); + } + } + + // Helper method to get the locale as a String + public String getLocaleString() { + return mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY); + } + + // Helper method to get the version String + public String getVersion() { + return mDictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY); + } + + // Helper method to get the dictionary ID as a String + public String getId() { + return mDictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY); + } + + // Helper method to get the description + public String getDescription() { + // TODO: Right now each dictionary file comes with a description in its own language. + // It will display as is no matter the device's locale. It should be internationalized. + return mDictionaryOptions.mAttributes.get(DICTIONARY_DESCRIPTION_KEY); + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java deleted file mode 100644 index 28da9ffdd..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java +++ /dev/null @@ -1,492 +0,0 @@ -/* - * 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.Constants; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; -import com.android.inputmethod.latin.utils.CollectionUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; - -/** - * The utility class to help dynamic updates on the binary dictionary. - * - * All the methods in this class are static. - */ -@UsedForTesting -public final class DynamicBinaryDictIOUtils { - private static final boolean DBG = false; - private static final int MAX_JUMPS = 10000; - - private DynamicBinaryDictIOUtils() { - // This utility class is not publicly instantiable. - } - - /* package */ static int markAsDeleted(final int flags) { - return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED; - } - - /** - * Update a parent address in a PtNode that is referred to by ptNodeOriginAddress. - * - * @param dictUpdater the DictUpdater to write. - * @param ptNodeOriginAddress the address of the PtNode. - * @param newParentAddress the absolute address of the parent. - * @param formatOptions file format options. - */ - private static void updateParentAddress(final Ver3DictUpdater dictUpdater, - final int ptNodeOriginAddress, final int newParentAddress, - final FormatOptions formatOptions) { - final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - final int originalPosition = dictBuffer.position(); - dictBuffer.position(ptNodeOriginAddress); - if (!formatOptions.mSupportsDynamicUpdate) { - throw new RuntimeException("this file format does not support parent addresses"); - } - final int flags = dictBuffer.readUnsignedByte(); - if (BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) { - // If the node is moved, the parent address is stored in the destination node. - // We are guaranteed to process the destination node later, so there is no need to - // update anything here. - dictBuffer.position(originalPosition); - return; - } - if (DBG) { - MakedictLog.d("update parent address flags=" + flags + ", " + ptNodeOriginAddress); - } - final int parentOffset = newParentAddress - ptNodeOriginAddress; - BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, parentOffset); - dictBuffer.position(originalPosition); - } - - /** - * Update parent addresses in a node array stored at ptNodeOriginAddress. - * - * @param dictUpdater the DictUpdater to be modified. - * @param ptNodeOriginAddress the address of the node array to update. - * @param newParentAddress the address to be written. - * @param formatOptions file format options. - */ - private static void updateParentAddresses(final Ver3DictUpdater dictUpdater, - final int ptNodeOriginAddress, final int newParentAddress, - final FormatOptions formatOptions) { - final int originalPosition = dictUpdater.getPosition(); - dictUpdater.setPosition(ptNodeOriginAddress); - do { - final int count = dictUpdater.readPtNodeCount(); - for (int i = 0; i < count; ++i) { - updateParentAddress(dictUpdater, dictUpdater.getPosition(), newParentAddress, - formatOptions); - dictUpdater.skipPtNode(formatOptions); - } - if (!dictUpdater.readAndFollowForwardLink()) break; - if (dictUpdater.getPosition() == FormatSpec.NO_FORWARD_LINK_ADDRESS) break; - } while (formatOptions.mSupportsDynamicUpdate); - dictUpdater.setPosition(originalPosition); - } - - /** - * Update a children address in a PtNode that is addressed by ptNodeOriginAddress. - * - * @param dictUpdater the DictUpdater to write. - * @param ptNodeOriginAddress the address of the PtNode. - * @param newChildrenAddress the absolute address of the child. - * @param formatOptions file format options. - */ - private static void updateChildrenAddress(final Ver3DictUpdater dictUpdater, - final int ptNodeOriginAddress, final int newChildrenAddress, - final FormatOptions formatOptions) { - final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - final int originalPosition = dictBuffer.position(); - dictBuffer.position(ptNodeOriginAddress); - final int flags = dictBuffer.readUnsignedByte(); - BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions); - BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); - if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte(); - final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS - ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - dictBuffer.position(); - BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, childrenOffset); - dictBuffer.position(originalPosition); - } - - /** - * Helper method to move a PtNode to the tail of the file. - */ - private static int movePtNode(final OutputStream destination, - final Ver3DictUpdater dictUpdater, final PtNodeInfo info, - final int nodeArrayOriginAddress, final int oldNodeAddress, - final FormatOptions formatOptions) throws IOException { - final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - updateParentAddress(dictUpdater, oldNodeAddress, dictBuffer.limit() + 1, formatOptions); - dictBuffer.position(oldNodeAddress); - final int currentFlags = dictBuffer.readUnsignedByte(); - dictBuffer.position(oldNodeAddress); - dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags - & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG)))); - int size = FormatSpec.PTNODE_FLAGS_SIZE; - updateForwardLink(dictUpdater, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions); - size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info }); - return size; - } - - @SuppressWarnings("unused") - private static void updateForwardLink(final Ver3DictUpdater dictUpdater, - final int nodeArrayOriginAddress, final int newNodeArrayAddress, - final FormatOptions formatOptions) { - final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - dictUpdater.setPosition(nodeArrayOriginAddress); - int jumpCount = 0; - while (jumpCount++ < MAX_JUMPS) { - final int count = dictUpdater.readPtNodeCount(); - for (int i = 0; i < count; ++i) { - dictUpdater.readPtNode(dictUpdater.getPosition(), formatOptions); - } - final int forwardLinkAddress = dictBuffer.readUnsignedInt24(); - if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) { - dictBuffer.position(dictBuffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE); - BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeArrayAddress); - return; - } - dictBuffer.position(forwardLinkAddress); - } - if (DBG && jumpCount >= MAX_JUMPS) { - throw new RuntimeException("too many jumps, probably a bug."); - } - } - - /** - * Move a PtNode that is referred to by oldPtNodeOrigin to the tail of the file, and set the - * children address to the byte after the PtNode. - * - * @param fileEndAddress the address of the tail of the file. - * @param codePoints the characters to put inside the PtNode. - * @param length how many code points to read from codePoints. - * @param flags the flags for this PtNode. - * @param frequency the frequency of this terminal. - * @param parentAddress the address of the parent PtNode of this PtNode. - * @param shortcutTargets the shortcut targets for this PtNode. - * @param bigrams the bigrams for this PtNode. - * @param destination the stream representing the tail of the file. - * @param dictUpdater the DictUpdater. - * @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of. - * @param oldPtNodeOrigin the old origin where this PtNode used to be stored. - * @param formatOptions format options for this dictionary. - * @return the size written, in bytes. - * @throws IOException if the file can't be accessed - */ - private static int movePtNode(final int fileEndAddress, final int[] codePoints, - final int length, final int flags, final int frequency, final int parentAddress, - final ArrayList<WeightedString> shortcutTargets, - final ArrayList<PendingAttribute> bigrams, final OutputStream destination, - final Ver3DictUpdater dictUpdater, final int oldPtNodeArrayOrigin, - final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException { - int size = 0; - final int newPtNodeOrigin = fileEndAddress + 1; - final int[] writtenCharacters = Arrays.copyOfRange(codePoints, 0, length); - final PtNodeInfo tmpInfo = new PtNodeInfo(newPtNodeOrigin, -1 /* endAddress */, - flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS, - shortcutTargets, bigrams); - size = BinaryDictIOUtils.computePtNodeSize(tmpInfo, formatOptions); - final PtNodeInfo newInfo = new PtNodeInfo(newPtNodeOrigin, newPtNodeOrigin + size, - flags, writtenCharacters, frequency, parentAddress, - fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets, - bigrams); - movePtNode(destination, dictUpdater, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin, - formatOptions); - return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; - } - - /** - * Converts a list of WeightedString to a list of PendingAttribute. - */ - public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater, - final ArrayList<WeightedString> bigramStrings) - throws IOException, UnsupportedFormatException { - if (bigramStrings == null) return CollectionUtils.newArrayList(); - final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList(); - for (final WeightedString bigram : bigramStrings) { - final int pos = dictUpdater.getTerminalPosition(bigram.mWord); - if (pos == FormatSpec.NOT_VALID_WORD) { - // TODO: figure out what is the correct thing to do here. - } else { - bigrams.add(new PendingAttribute(bigram.mFrequency, pos)); - } - } - return bigrams; - } - - /** - * Insert a word into a binary dictionary. - * - * @param dictUpdater the dict updater. - * @param destination a stream to the underlying file, with the pointer at the end of the file. - * @param word the word to insert. - * @param frequency the frequency of the new word. - * @param bigramStrings bigram list, or null if none. - * @param shortcuts shortcut list, or null if none. - * @param isBlackListEntry whether this should be a blacklist entry. - * @throws IOException if the file can't be accessed. - * @throws UnsupportedFormatException if the existing dictionary is in an unexpected format. - */ - // TODO: Support batch insertion. - // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary. - @UsedForTesting - public static void insertWord(final Ver3DictUpdater dictUpdater, - final OutputStream destination, final String word, final int frequency, - final ArrayList<WeightedString> bigramStrings, - final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, - final boolean isBlackListEntry) - throws IOException, UnsupportedFormatException { - final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater, - bigramStrings); - final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - - final boolean isTerminal = true; - final boolean hasBigrams = !bigrams.isEmpty(); - final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty(); - - // find the insert position of the word. - if (dictBuffer.position() != 0) dictBuffer.position(0); - final FileHeader fileHeader = dictUpdater.readHeader(); - - int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position(); - final int[] codePoints = FusionDictionary.getCodePoints(word); - final int wordLen = codePoints.length; - - for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) { - if (wordPos >= wordLen) break; - nodeOriginAddress = dictBuffer.position(); - int nodeParentAddress = -1; - final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer); - boolean foundNextNode = false; - - for (int i = 0; i < ptNodeCount; ++i) { - address = dictBuffer.position(); - final PtNodeInfo currentInfo = dictUpdater.readPtNode(address, - fileHeader.mFormatOptions); - final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, - fileHeader.mFormatOptions); - if (isMovedNode) continue; - nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) - ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address; - boolean matched = true; - for (int p = 0; p < currentInfo.mCharacters.length; ++p) { - if (wordPos + p >= wordLen) { - /* - * splitting - * before - * abcd - ef - * - * insert "abc" - * - * after - * abc - d - ef - */ - final int newNodeAddress = dictBuffer.limit(); - final int flags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1, - isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */, - false /* isBlackListEntry */, fileHeader.mFormatOptions); - int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags, - frequency, nodeParentAddress, shortcuts, bigrams, destination, - dictUpdater, nodeOriginAddress, address, fileHeader.mFormatOptions); - - final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p, - currentInfo.mCharacters.length); - if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { - updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress, - newNodeAddress + written + 1, fileHeader.mFormatOptions); - } - final PtNodeInfo newInfo2 = new PtNodeInfo( - newNodeAddress + written + 1, -1 /* endAddress */, - currentInfo.mFlags, characters2, currentInfo.mFrequency, - newNodeAddress + 1, currentInfo.mChildrenAddress, - currentInfo.mShortcutTargets, currentInfo.mBigrams); - BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo2 }); - return; - } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) { - if (p > 0) { - /* - * splitting - * before - * ab - cd - * - * insert "ac" - * - * after - * a - b - cd - * | - * - c - */ - - final int newNodeAddress = dictBuffer.limit(); - final int childrenAddress = currentInfo.mChildrenAddress; - - // move prefix - final int prefixFlags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1, - false /* isTerminal */, 0 /* childrenAddressSize*/, - false /* hasShortcut */, false /* hasBigrams */, - false /* isNotAWord */, false /* isBlackListEntry */, - fileHeader.mFormatOptions); - int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, - prefixFlags, -1 /* frequency */, nodeParentAddress, null, null, - destination, dictUpdater, nodeOriginAddress, address, - fileHeader.mFormatOptions); - - final int[] suffixCharacters = Arrays.copyOfRange( - currentInfo.mCharacters, p, currentInfo.mCharacters.length); - if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { - updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress, - newNodeAddress + written + 1, fileHeader.mFormatOptions); - } - final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags( - suffixCharacters.length > 1, - (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0, - 0 /* childrenAddressSize */, - (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) - != 0, - (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0, - isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); - final PtNodeInfo suffixInfo = new PtNodeInfo( - newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags, - suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1, - currentInfo.mChildrenAddress, currentInfo.mShortcutTargets, - currentInfo.mBigrams); - written += BinaryDictIOUtils.computePtNodeSize(suffixInfo, - fileHeader.mFormatOptions) + 1; - - final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p, - codePoints.length); - final int flags = BinaryDictEncoderUtils.makePtNodeFlags( - newCharacters.length > 1, isTerminal, - 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, - isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); - final PtNodeInfo newInfo = new PtNodeInfo( - newNodeAddress + written, -1 /* endAddress */, flags, - newCharacters, frequency, newNodeAddress + 1, - FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); - BinaryDictIOUtils.writeNodes(destination, - new PtNodeInfo[] { suffixInfo, newInfo }); - return; - } - matched = false; - break; - } - } - - if (matched) { - if (wordPos + currentInfo.mCharacters.length == wordLen) { - // the word exists in the dictionary. - // only update the PtNode. - final int newNodeAddress = dictBuffer.limit(); - final boolean hasMultipleChars = currentInfo.mCharacters.length > 1; - final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars, - isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, - isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); - final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1, - -1 /* endAddress */, flags, currentInfo.mCharacters, frequency, - nodeParentAddress, currentInfo.mChildrenAddress, shortcuts, - bigrams); - movePtNode(destination, dictUpdater, newInfo, nodeOriginAddress, address, - fileHeader.mFormatOptions); - return; - } - wordPos += currentInfo.mCharacters.length; - if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { - /* - * found the prefix of the word. - * make new PtNode and link to the PtNode from this PtNode. - * - * before - * ab - cd - * - * insert "abcde" - * - * after - * ab - cd - e - */ - final int newNodeArrayAddress = dictBuffer.limit(); - updateChildrenAddress(dictUpdater, address, newNodeArrayAddress, - fileHeader.mFormatOptions); - final int newNodeAddress = newNodeArrayAddress + 1; - final boolean hasMultipleChars = (wordLen - wordPos) > 1; - final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars, - isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, - isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); - final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); - final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress, -1, flags, - characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS, - shortcuts, bigrams); - BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo }); - return; - } - dictBuffer.position(currentInfo.mChildrenAddress); - foundNextNode = true; - break; - } - } - - if (foundNextNode) continue; - - // reached the end of the array. - final int linkAddressPosition = dictBuffer.position(); - int nextLink = dictBuffer.readUnsignedInt24(); - if ((nextLink & FormatSpec.MSB24) != 0) { - nextLink = -(nextLink & FormatSpec.SINT24_MAX); - } - if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) { - /* - * expand this node. - * - * before - * ab - cd - * - * insert "abef" - * - * after - * ab - cd - * | - * - ef - */ - - // change the forward link address. - final int newNodeAddress = dictBuffer.limit(); - dictBuffer.position(linkAddressPosition); - BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeAddress); - - final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); - final int flags = BinaryDictEncoderUtils.makePtNodeFlags(characters.length > 1, - isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, - isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); - final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1, - -1 /* endAddress */, flags, characters, frequency, nodeParentAddress, - FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); - BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[]{ newInfo }); - return; - } else { - depth--; - dictBuffer.position(nextLink); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index b56234f6d..c7635eff9 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory; -import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import java.io.File; @@ -40,12 +39,8 @@ public final class FormatSpec { * p | not used 3 bits * t | each unigram and bigram entry has a time stamp? * i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG - * o | has bigrams ? 1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG - * n | FRENCH_LIGATURE_PROCESSING_FLAG - * f | supports dynamic updates ? 1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE - * l | GERMAN_UMLAUT_PROCESSING_FLAG - * a | - * gs + * o | + * nflags * * h | * e | size of the file header, 4bytes @@ -82,45 +77,36 @@ public final class FormatSpec { * s * * f | - * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header) - * r | forward link address, 3byte - * w | 1 byte = bbbbbbbb match - * a | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte) - * r | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte - * d | - * linkaddress + * o | forward link address, 3byte + * r | 1 byte = bbbbbbbb match + * w | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte) + * a | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte + * r | + * dlinkaddress */ /* Node (FusionDictionary.PtNode) layout is as follows: - * | IF !SUPPORTS_DYNAMIC_UPDATE - * | addressType xx : mask with MASK_CHILDREN_ADDRESS_TYPE - * | 2 bits, 00 = no children : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS - * f | 01 = 1 byte : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE - * l | 10 = 2 bytes : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES - * a | 11 = 3 bytes : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES - * g | ELSE - * s | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED - * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES - * | 01 = yes : FLAG_IS_MOVED - * | the new address is stored in the same place as the parent address - * | is deleted? 10 = yes : FLAG_IS_DELETED - * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS - * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL - * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS + * | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED + * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES + * | 01 = yes : FLAG_IS_MOVED + * f | the new address is stored in the same place as the parent address + * l | is deleted? 10 = yes : FLAG_IS_DELETED + * a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS + * g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL + * s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS * | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS * | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD * | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED * * p | - * a | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header) - * r | parent address, 3byte - * e | 1 byte = bbbbbbbb match - * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) - * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte - * a | This address is relative to the head of the PtNode. - * d | If the node doesn't have a parent, this field is set to 0. + * a | parent address, 3byte + * r | 1 byte = bbbbbbbb match + * e | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) + * n | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte + * t | This address is relative to the head of the PtNode. + * a | If the node doesn't have a parent, this field is set to 0. * d | - * ress + * dress * * c | IF FLAG_HAS_MULTIPLE_CHARS * h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers @@ -134,23 +120,16 @@ public final class FormatSpec { * e | frequency 1 byte * q | * - * c | IF SUPPORTS_DYNAMIC_UPDATE - * h | children address, 3 bytes - * i | 1 byte = bbbbbbbb match - * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) - * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte - * r | if this node doesn't have children, this field is set to 0. - * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress) - * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType - * a | // nothing - * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType - * d | children address, 1 byte - * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType - * e | children address, 2 bytes - * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType - * s | children address, 3 bytes - * | END - * | This address is relative to the position of this field. + * c | + * h | children address, 3 bytes + * i | 1 byte = bbbbbbbb match + * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) + * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte + * r | if this node doesn't have children, this field is set to 0. + * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress) + * n | This address is relative to the position of this field. + * a | + * ddress * * | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS * | shortcut string list @@ -199,21 +178,18 @@ public final class FormatSpec { */ public static final int MAGIC_NUMBER = 0x9BC13AFE; - static final int MINIMUM_SUPPORTED_VERSION = 2; - static final int MAXIMUM_SUPPORTED_VERSION = 4; static final int NOT_A_VERSION_NUMBER = -1; static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3; static final int FIRST_VERSION_WITH_TERMINAL_ID = 4; - static final int VERSION3 = 3; - static final int VERSION4 = 4; - // These options need to be the same numeric values as the one in the native reading code. - static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1; - // TODO: Make the native reading code read this variable. - static final int SUPPORTS_DYNAMIC_UPDATE = 0x2; - static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4; - static final int CONTAINS_BIGRAMS_FLAG = 0x8; - static final int CONTAINS_TIMESTAMP_FLAG = 0x10; + // These MUST have the same values as the relevant constants in format_utils.h. + // From version 4 on, we use version * 100 + revision as a version number. That allows + // us to change the format during development while having testing devices remove + // older files with each upgrade, while still having a readable versioning scheme. + public static final int VERSION2 = 2; + public static final int VERSION4 = 401; + static final int MINIMUM_SUPPORTED_VERSION = VERSION2; + static final int MAXIMUM_SUPPORTED_VERSION = VERSION4; // TODO: Make this value adaptative to content data, store it in the header, and // use it in the reading code. @@ -263,29 +239,31 @@ public final class FormatSpec { static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3; static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2; - // These values are used only by version 4 or later. + // These values are used only by version 4 or later. They MUST match the definitions in + // ver4_dict_constants.cpp. static final String TRIE_FILE_EXTENSION = ".trie"; + public static final String HEADER_FILE_EXTENSION = ".header"; static final String FREQ_FILE_EXTENSION = ".freq"; - static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp"; // tat = Terminal Address Table static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat"; static final String BIGRAM_FILE_EXTENSION = ".bigram"; static final String SHORTCUT_FILE_EXTENSION = ".shortcut"; static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup"; static final String CONTENT_TABLE_FILE_SUFFIX = "_index"; + static final int FLAGS_IN_FREQ_FILE_SIZE = 1; static final int FREQUENCY_AND_FLAGS_SIZE = 2; static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3; static final int UNIGRAM_TIMESTAMP_SIZE = 4; + static final int UNIGRAM_COUNTER_SIZE = 1; + static final int UNIGRAM_LEVEL_SIZE = 1; // With the English main dictionary as of October 2013, the size of bigram address table is - // is 584KB with the block size being 4. - // This is 91% of that of full address table. - static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4; - static final int BIGRAM_CONTENT_COUNT = 2; + // is 345KB with the block size being 16. + // This is 54% of that of full address table. + static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16; + static final int BIGRAM_CONTENT_COUNT = 1; static final int BIGRAM_FREQ_CONTENT_INDEX = 0; - static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1; static final String BIGRAM_FREQ_CONTENT_ID = "_freq"; - static final String BIGRAM_TIMESTAMP_CONTENT_ID = "_timestamp"; static final int BIGRAM_TIMESTAMP_SIZE = 4; static final int BIGRAM_COUNTER_SIZE = 1; static final int BIGRAM_LEVEL_SIZE = 1; @@ -293,7 +271,7 @@ public final class FormatSpec { static final int SHORTCUT_CONTENT_COUNT = 1; static final int SHORTCUT_CONTENT_INDEX = 0; // With the English main dictionary as of October 2013, the size of shortcut address table is - // 29KB with the block size being 64. + // 26KB with the block size being 64. // This is only 4.4% of that of full address table. static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64; static final String SHORTCUT_CONTENT_ID = "_shortcut"; @@ -331,80 +309,20 @@ public final class FormatSpec { */ public static final class FormatOptions { public final int mVersion; - public final boolean mSupportsDynamicUpdate; - public final boolean mHasTerminalId; public final boolean mHasTimestamp; - @UsedForTesting - public FormatOptions(final int version) { - this(version, false); - } @UsedForTesting - public FormatOptions(final int version, final boolean supportsDynamicUpdate) { - this(version, supportsDynamicUpdate, false /* hasTimestamp */); + public FormatOptions(final int version) { + this(version, false /* hasTimestamp */); } - public FormatOptions(final int version, final boolean supportsDynamicUpdate, - final boolean hasTimestamp) { + public FormatOptions(final int version, final boolean hasTimestamp) { mVersion = version; - if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) { - throw new RuntimeException("Dynamic updates are only supported with versions " - + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior."); - } - mSupportsDynamicUpdate = supportsDynamicUpdate; - mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID); mHasTimestamp = hasTimestamp; } } /** - * Class representing file header. - */ - public static final class FileHeader { - public final int mHeaderSize; - public final DictionaryOptions mDictionaryOptions; - public final FormatOptions mFormatOptions; - // Note that these are corresponding definitions in native code in latinime::HeaderPolicy - // and latinime::HeaderReadWriteUtils. - public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE"; - public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE"; - public static final String ATTRIBUTE_VALUE_TRUE = "1"; - - public static final String DICTIONARY_VERSION_ATTRIBUTE = "version"; - public static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale"; - public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary"; - private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description"; - public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions, - final FormatOptions formatOptions) { - mHeaderSize = headerSize; - mDictionaryOptions = dictionaryOptions; - mFormatOptions = formatOptions; - } - - // Helper method to get the locale as a String - public String getLocaleString() { - return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE); - } - - // Helper method to get the version String - public String getVersion() { - return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE); - } - - // Helper method to get the dictionary ID as a String - public String getId() { - return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE); - } - - // Helper method to get the description - public String getDescription() { - // TODO: Right now each dictionary file comes with a description in its own language. - // It will display as is no matter the device's locale. It should be internationalized. - return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE); - } - } - - /** * Returns new dictionary decoder. * * @param dictFile the dictionary file. @@ -415,7 +333,7 @@ public final class FormatSpec { if (dictFile.isDirectory()) { return new Ver4DictDecoder(dictFile, bufferType); } else if (dictFile.isFile()) { - return new Ver3DictDecoder(dictFile, bufferType); + return new Ver2DictDecoder(dictFile, bufferType); } return null; } @@ -425,7 +343,7 @@ public final class FormatSpec { if (dictFile.isDirectory()) { return new Ver4DictDecoder(dictFile, factory); } else if (dictFile.isFile()) { - return new Ver3DictDecoder(dictFile, factory); + return new Ver2DictDecoder(dictFile, factory); } return null; } diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index 3bb218bea..8f73b27b5 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -31,7 +31,7 @@ import java.util.LinkedList; * A dictionary that can fusion heads and tails of words for more compression. */ @UsedForTesting -public final class FusionDictionary implements Iterable<Word> { +public final class FusionDictionary implements Iterable<WordProperty> { private static final boolean DBG = MakedictLog.DBG; private static int CHARACTER_NOT_FOUND_INDEX = -1; @@ -61,56 +61,72 @@ public final class FusionDictionary implements Iterable<Word> { mData = new ArrayList<PtNode>(); } public PtNodeArray(ArrayList<PtNode> data) { + Collections.sort(data, PTNODE_COMPARATOR); mData = data; } } /** - * A string with a frequency. + * A string with a probability. * * This represents an "attribute", that is either a bigram or a shortcut. */ public static final class WeightedString { public final String mWord; - public int mFrequency; - public WeightedString(String word, int frequency) { + public ProbabilityInfo mProbabilityInfo; + + public WeightedString(final String word, final int probability) { + this(word, new ProbabilityInfo(probability)); + } + + public WeightedString(final String word, final ProbabilityInfo probabilityInfo) { mWord = word; - mFrequency = frequency; + mProbabilityInfo = probabilityInfo; + } + + public int getProbability() { + return mProbabilityInfo.mProbability; + } + + public void setProbability(final int probability) { + mProbabilityInfo = new ProbabilityInfo(probability); } @Override public int hashCode() { - return Arrays.hashCode(new Object[] { mWord, mFrequency }); + return Arrays.hashCode(new Object[] { mWord, mProbabilityInfo}); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof WeightedString)) return false; - WeightedString w = (WeightedString)o; - return mWord.equals(w.mWord) && mFrequency == w.mFrequency; + final WeightedString w = (WeightedString)o; + return mWord.equals(w.mWord) && mProbabilityInfo.equals(w.mProbabilityInfo); } } /** - * PtNode is a group of characters, with a frequency, shortcut targets, bigrams, and children - * (Pt means Patricia Trie). + * PtNode is a group of characters, with probability information, shortcut targets, bigrams, + * and children (Pt means Patricia Trie). * * This is the central class of the in-memory representation. A PtNode is what can * be seen as a traditional "trie node", except it can hold several characters at the * same time. A PtNode essentially represents one or several characters in the middle * of the trie tree; as such, it can be a terminal, and it can have children. * In this in-memory representation, whether the PtNode is a terminal or not is represented - * in the frequency, where NOT_A_TERMINAL (= -1) means this is not a terminal and any other - * value is the frequency of this terminal. A terminal may have non-null shortcuts and/or - * bigrams, but a non-terminal may not. Moreover, children, if present, are null. + * by mProbabilityInfo. The PtNode is a terminal when the mProbabilityInfo is not null and the + * PtNode is not a terminal when the mProbabilityInfo is null. A terminal may have non-null + * shortcuts and/or bigrams, but a non-terminal may not. Moreover, children, if present, + * are non-null. */ public static final class PtNode { - public static final int NOT_A_TERMINAL = -1; + private static final int NOT_A_TERMINAL = -1; final int mChars[]; ArrayList<WeightedString> mShortcutTargets; ArrayList<WeightedString> mBigrams; - int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal. + // null == mProbabilityInfo indicates this is not a terminal. + ProbabilityInfo mProbabilityInfo; int mTerminalId; // NOT_A_TERMINAL == mTerminalId indicates this is not a terminal. PtNodeArray mChildren; boolean mIsNotAWord; // Only a shortcut @@ -126,11 +142,11 @@ public final class FusionDictionary implements Iterable<Word> { int mCachedAddressAfterUpdate; // The address of this PtNode (after update) public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams, final int frequency, + final ArrayList<WeightedString> bigrams, final ProbabilityInfo probabilityInfo, final boolean isNotAWord, final boolean isBlacklistEntry) { mChars = chars; - mFrequency = frequency; - mTerminalId = frequency; + mProbabilityInfo = probabilityInfo; + mTerminalId = probabilityInfo == null ? NOT_A_TERMINAL : probabilityInfo.mProbability; mShortcutTargets = shortcutTargets; mBigrams = bigrams; mChildren = null; @@ -139,11 +155,11 @@ public final class FusionDictionary implements Iterable<Word> { } public PtNode(final int[] chars, final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams, final int frequency, + final ArrayList<WeightedString> bigrams, final ProbabilityInfo probabilityInfo, final boolean isNotAWord, final boolean isBlacklistEntry, final PtNodeArray children) { mChars = chars; - mFrequency = frequency; + mProbabilityInfo = probabilityInfo; mShortcutTargets = shortcutTargets; mBigrams = bigrams; mChildren = children; @@ -163,11 +179,15 @@ public final class FusionDictionary implements Iterable<Word> { } public boolean isTerminal() { - return NOT_A_TERMINAL != mFrequency; + return mProbabilityInfo != null; } - public int getFrequency() { - return mFrequency; + public int getProbability() { + if (isTerminal()) { + return mProbabilityInfo.mProbability; + } else { + return NOT_A_TERMINAL; + } } public boolean getIsNotAWord() { @@ -199,18 +219,18 @@ public final class FusionDictionary implements Iterable<Word> { } /** - * Adds a word to the bigram list. Updates the frequency if the word already + * Adds a word to the bigram list. Updates the probability information if the word already * exists. */ - public void addBigram(final String word, final int frequency) { + public void addBigram(final String word, final ProbabilityInfo probabilityInfo) { if (mBigrams == null) { mBigrams = new ArrayList<WeightedString>(); } WeightedString bigram = getBigram(word); if (bigram != null) { - bigram.mFrequency = frequency; + bigram.mProbabilityInfo = probabilityInfo; } else { - bigram = new WeightedString(word, frequency); + bigram = new WeightedString(word, probabilityInfo); mBigrams.add(bigram); } } @@ -256,12 +276,11 @@ public final class FusionDictionary implements Iterable<Word> { * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only * updated if they are higher than the existing ones. */ - public void update(final int frequency, final ArrayList<WeightedString> shortcutTargets, + private void update(final ProbabilityInfo probabilityInfo, + final ArrayList<WeightedString> shortcutTargets, final ArrayList<WeightedString> bigrams, final boolean isNotAWord, final boolean isBlacklistEntry) { - if (frequency > mFrequency) { - mFrequency = frequency; - } + mProbabilityInfo = ProbabilityInfo.max(mProbabilityInfo, probabilityInfo); if (shortcutTargets != null) { if (mShortcutTargets == null) { mShortcutTargets = shortcutTargets; @@ -272,8 +291,9 @@ public final class FusionDictionary implements Iterable<Word> { final WeightedString existingShortcut = getShortcut(shortcut.mWord); if (existingShortcut == null) { mShortcutTargets.add(shortcut); - } else if (existingShortcut.mFrequency < shortcut.mFrequency) { - existingShortcut.mFrequency = shortcut.mFrequency; + } else { + existingShortcut.mProbabilityInfo = ProbabilityInfo.max( + existingShortcut.mProbabilityInfo, shortcut.mProbabilityInfo); } } } @@ -288,8 +308,9 @@ public final class FusionDictionary implements Iterable<Word> { final WeightedString existingBigram = getBigram(bigram.mWord); if (existingBigram == null) { mBigrams.add(bigram); - } else if (existingBigram.mFrequency < bigram.mFrequency) { - existingBigram.mFrequency = bigram.mFrequency; + } else { + existingBigram.mProbabilityInfo = ProbabilityInfo.max( + existingBigram.mProbabilityInfo, bigram.mProbabilityInfo); } } } @@ -303,14 +324,9 @@ public final class FusionDictionary implements Iterable<Word> { * Options global to the dictionary. */ public static final class DictionaryOptions { - public final boolean mGermanUmlautProcessing; - public final boolean mFrenchLigatureProcessing; public final HashMap<String, String> mAttributes; - public DictionaryOptions(final HashMap<String, String> attributes, - final boolean germanUmlautProcessing, final boolean frenchLigatureProcessing) { + public DictionaryOptions(final HashMap<String, String> attributes) { mAttributes = attributes; - mGermanUmlautProcessing = germanUmlautProcessing; - mFrenchLigatureProcessing = frenchLigatureProcessing; } @Override public String toString() { // Convenience method @@ -339,14 +355,6 @@ public final class FusionDictionary implements Iterable<Word> { } s.append("\n"); } - if (mGermanUmlautProcessing) { - s.append(indent); - s.append("Needs German umlaut processing\n"); - } - if (mFrenchLigatureProcessing) { - s.append(indent); - s.append("Needs French ligature processing\n"); - } return s.toString(); } } @@ -392,13 +400,13 @@ public final class FusionDictionary implements Iterable<Word> { * they will be added to the dictionary as necessary. * * @param word the word to add. - * @param frequency the frequency of the word, in the range [0..255]. + * @param probabilityInfo probability information of the word. * @param shortcutTargets a list of shortcut targets for this word, or null. * @param isNotAWord true if this should not be considered a word (e.g. shortcut only) */ - public void add(final String word, final int frequency, + public void add(final String word, final ProbabilityInfo probabilityInfo, final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) { - add(getCodePoints(word), frequency, shortcutTargets, isNotAWord, + add(getCodePoints(word), probabilityInfo, shortcutTargets, isNotAWord, false /* isBlacklistEntry */); } @@ -411,7 +419,8 @@ public final class FusionDictionary implements Iterable<Word> { */ public void addBlacklistEntry(final String word, final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) { - add(getCodePoints(word), 0, shortcutTargets, isNotAWord, true /* isBlacklistEntry */); + add(getCodePoints(word), new ProbabilityInfo(0), shortcutTargets, isNotAWord, + true /* isBlacklistEntry */); } /** @@ -435,25 +444,26 @@ public final class FusionDictionary implements Iterable<Word> { /** * Helper method to add a new bigram to the dictionary. * - * @param word1 the previous word of the context - * @param word2 the next word of the context - * @param frequency the bigram frequency + * @param word0 the previous word of the context + * @param word1 the next word of the context + * @param probabilityInfo the bigram probability info */ - public void setBigram(final String word1, final String word2, final int frequency) { - PtNode ptNode = findWordInTree(mRootNodeArray, word1); - if (ptNode != null) { - final PtNode ptNode2 = findWordInTree(mRootNodeArray, word2); - if (ptNode2 == null) { - add(getCodePoints(word2), 0, null, false /* isNotAWord */, + public void setBigram(final String word0, final String word1, + final ProbabilityInfo probabilityInfo) { + PtNode ptNode0 = findWordInTree(mRootNodeArray, word0); + if (ptNode0 != null) { + final PtNode ptNode1 = findWordInTree(mRootNodeArray, word1); + if (ptNode1 == null) { + add(getCodePoints(word1), new ProbabilityInfo(0), null, false /* isNotAWord */, false /* isBlacklistEntry */); // The PtNode for the first word may have moved by the above insertion, // if word1 and word2 share a common stem that happens not to have been // a cutting point until now. In this case, we need to refresh ptNode. - ptNode = findWordInTree(mRootNodeArray, word1); + ptNode0 = findWordInTree(mRootNodeArray, word0); } - ptNode.addBigram(word2, frequency); + ptNode0.addBigram(word1, probabilityInfo); } else { - throw new RuntimeException("First word of bigram not found"); + throw new RuntimeException("First word of bigram not found " + word0); } } @@ -464,15 +474,15 @@ public final class FusionDictionary implements Iterable<Word> { * an exception is thrown. * * @param word the word, as an int array. - * @param frequency the frequency of the word, in the range [0..255]. + * @param probabilityInfo the probability information of the word. * @param shortcutTargets an optional list of shortcut targets for this word (null if none). * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so) * @param isBlacklistEntry true if this is a blacklisted word, false otherwise */ - private void add(final int[] word, final int frequency, + private void add(final int[] word, final ProbabilityInfo probabilityInfo, final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord, final boolean isBlacklistEntry) { - assert(frequency >= 0 && frequency <= 255); + assert(probabilityInfo.mProbability <= FormatSpec.MAX_TERMINAL_FREQUENCY); if (word.length >= Constants.DICTIONARY_MAX_WORD_LENGTH) { MakedictLog.w("Ignoring a word that is too long: word.length = " + word.length); return; @@ -500,7 +510,8 @@ public final class FusionDictionary implements Iterable<Word> { // No node at this point to accept the word. Create one. final int insertionIndex = findInsertionIndex(currentNodeArray, word[charIndex]); final PtNode newPtNode = new PtNode(Arrays.copyOfRange(word, charIndex, word.length), - shortcutTargets, null /* bigrams */, frequency, isNotAWord, isBlacklistEntry); + shortcutTargets, null /* bigrams */, probabilityInfo, isNotAWord, + isBlacklistEntry); currentNodeArray.mData.add(insertionIndex, newPtNode); if (DBG) checkStack(currentNodeArray); } else { @@ -510,15 +521,15 @@ public final class FusionDictionary implements Iterable<Word> { // The new word is a prefix of an existing word, but the node on which it // should end already exists as is. Since the old PtNode was not a terminal, // make it one by filling in its frequency and other attributes - currentPtNode.update(frequency, shortcutTargets, null, isNotAWord, + currentPtNode.update(probabilityInfo, shortcutTargets, null, isNotAWord, isBlacklistEntry); } else { // The new word matches the full old word and extends past it. // We only have to create a new node and add it to the end of this. final PtNode newNode = new PtNode( Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length), - shortcutTargets, null /* bigrams */, frequency, isNotAWord, - isBlacklistEntry); + shortcutTargets, null /* bigrams */, probabilityInfo, + isNotAWord, isBlacklistEntry); currentPtNode.mChildren = new PtNodeArray(); currentPtNode.mChildren.mData.add(newNode); } @@ -526,7 +537,7 @@ public final class FusionDictionary implements Iterable<Word> { if (0 == differentCharIndex) { // Exact same word. Update the frequency if higher. This will also add the // new shortcuts to the existing shortcut list if it already exists. - currentPtNode.update(frequency, shortcutTargets, null, + currentPtNode.update(probabilityInfo, shortcutTargets, null, currentPtNode.mIsNotAWord && isNotAWord, currentPtNode.mIsBlacklistEntry || isBlacklistEntry); } else { @@ -536,7 +547,7 @@ public final class FusionDictionary implements Iterable<Word> { final PtNode newOldWord = new PtNode( Arrays.copyOfRange(currentPtNode.mChars, differentCharIndex, currentPtNode.mChars.length), currentPtNode.mShortcutTargets, - currentPtNode.mBigrams, currentPtNode.mFrequency, + currentPtNode.mBigrams, currentPtNode.mProbabilityInfo, currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry, currentPtNode.mChildren); newChildren.mData.add(newOldWord); @@ -545,16 +556,17 @@ public final class FusionDictionary implements Iterable<Word> { if (charIndex + differentCharIndex >= word.length) { newParent = new PtNode( Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex), - shortcutTargets, null /* bigrams */, frequency, + shortcutTargets, null /* bigrams */, probabilityInfo, isNotAWord, isBlacklistEntry, newChildren); } else { newParent = new PtNode( Arrays.copyOfRange(currentPtNode.mChars, 0, differentCharIndex), - null /* shortcutTargets */, null /* bigrams */, -1, - false /* isNotAWord */, false /* isBlacklistEntry */, newChildren); + null /* shortcutTargets */, null /* bigrams */, + null /* probabilityInfo */, false /* isNotAWord */, + false /* isBlacklistEntry */, newChildren); final PtNode newWord = new PtNode(Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length), - shortcutTargets, null /* bigrams */, frequency, + shortcutTargets, null /* bigrams */, probabilityInfo, isNotAWord, isBlacklistEntry); final int addIndex = word[charIndex + differentCharIndex] > currentPtNode.mChars[differentCharIndex] ? 1 : 0; @@ -616,8 +628,8 @@ public final class FusionDictionary implements Iterable<Word> { private static int findInsertionIndex(final PtNodeArray nodeArray, int character) { final ArrayList<PtNode> data = nodeArray.mData; final PtNode reference = new PtNode(new int[] { character }, - null /* shortcutTargets */, null /* bigrams */, 0, false /* isNotAWord */, - false /* isBlacklistEntry */); + null /* shortcutTargets */, null /* bigrams */, null /* probabilityInfo */, + false /* isNotAWord */, false /* isBlacklistEntry */); int result = Collections.binarySearch(data, reference, PTNODE_COMPARATOR); return result >= 0 ? result : -result - 1; } @@ -701,143 +713,11 @@ public final class FusionDictionary implements Iterable<Word> { } /** - * Recursively count the number of nodes in a given branch of the trie. - * - * @param nodeArray the node array to count. - * @return the number of nodes in this branch. - */ - public static int countNodeArrays(final PtNodeArray nodeArray) { - int size = 1; - for (int i = nodeArray.mData.size() - 1; i >= 0; --i) { - PtNode ptNode = nodeArray.mData.get(i); - if (null != ptNode.mChildren) - size += countNodeArrays(ptNode.mChildren); - } - return size; - } - - // Recursively find out whether there are any bigrams. - // This can be pretty expensive especially if there aren't any (we return as soon - // as we find one, so it's much cheaper if there are bigrams) - private static boolean hasBigramsInternal(final PtNodeArray nodeArray) { - if (null == nodeArray) return false; - for (int i = nodeArray.mData.size() - 1; i >= 0; --i) { - PtNode ptNode = nodeArray.mData.get(i); - if (null != ptNode.mBigrams) return true; - if (hasBigramsInternal(ptNode.mChildren)) return true; - } - return false; - } - - /** - * Finds out whether there are any bigrams in this dictionary. - * - * @return true if there is any bigram, false otherwise. - */ - // TODO: this is expensive especially for large dictionaries without any bigram. - // The up side is, this is always accurate and correct and uses no memory. We should - // find a more efficient way of doing this, without compromising too much on memory - // and ease of use. - public boolean hasBigrams() { - return hasBigramsInternal(mRootNodeArray); - } - - // Historically, the tails of the words were going to be merged to save space. - // However, that would prevent the code to search for a specific address in log(n) - // time so this was abandoned. - // The code is still of interest as it does add some compression to any dictionary - // that has no need for attributes. Implementations that does not read attributes should be - // able to read a dictionary with merged tails. - // Also, the following code does support frequencies, as in, it will only merges - // tails that share the same frequency. Though it would result in the above loss of - // performance while searching by address, it is still technically possible to merge - // tails that contain attributes, but this code does not take that into account - it does - // not compare attributes and will merge terminals with different attributes regardless. - public void mergeTails() { - MakedictLog.i("Do not merge tails"); - return; - -// MakedictLog.i("Merging PtNodes. Number of PtNodes : " + countPtNodes(root)); -// MakedictLog.i("Number of PtNodes : " + countPtNodes(root)); -// -// final HashMap<String, ArrayList<PtNodeArray>> repository = -// new HashMap<String, ArrayList<PtNodeArray>>(); -// mergeTailsInner(repository, root); -// -// MakedictLog.i("Number of different pseudohashes : " + repository.size()); -// int size = 0; -// for (ArrayList<PtNodeArray> a : repository.values()) { -// size += a.size(); -// } -// MakedictLog.i("Number of nodes after merge : " + (1 + size)); -// MakedictLog.i("Recursively seen nodes : " + countNodes(root)); - } - - // The following methods are used by the deactivated mergeTails() -// private static boolean isEqual(PtNodeArray a, PtNodeArray b) { -// if (null == a && null == b) return true; -// if (null == a || null == b) return false; -// if (a.data.size() != b.data.size()) return false; -// final int size = a.data.size(); -// for (int i = size - 1; i >= 0; --i) { -// PtNode aPtNode = a.data.get(i); -// PtNode bPtNode = b.data.get(i); -// if (aPtNode.frequency != bPtNode.frequency) return false; -// if (aPtNode.alternates == null && bPtNode.alternates != null) return false; -// if (aPtNode.alternates != null && !aPtNode.equals(bPtNode.alternates)) return false; -// if (!Arrays.equals(aPtNode.chars, bPtNode.chars)) return false; -// if (!isEqual(aPtNode.children, bPtNode.children)) return false; -// } -// return true; -// } - -// static private HashMap<String, ArrayList<PtNodeArray>> mergeTailsInner( -// final HashMap<String, ArrayList<PtNodeArray>> map, final PtNodeArray nodeArray) { -// final ArrayList<PtNode> branches = nodeArray.data; -// final int nodeSize = branches.size(); -// for (int i = 0; i < nodeSize; ++i) { -// PtNode ptNode = branches.get(i); -// if (null != ptNode.children) { -// String pseudoHash = getPseudoHash(ptNode.children); -// ArrayList<PtNodeArray> similarList = map.get(pseudoHash); -// if (null == similarList) { -// similarList = new ArrayList<PtNodeArray>(); -// map.put(pseudoHash, similarList); -// } -// boolean merged = false; -// for (PtNodeArray similar : similarList) { -// if (isEqual(ptNode.children, similar)) { -// ptNode.children = similar; -// merged = true; -// break; -// } -// } -// if (!merged) { -// similarList.add(ptNode.children); -// } -// mergeTailsInner(map, ptNode.children); -// } -// } -// return map; -// } - -// private static String getPseudoHash(final PtNodeArray nodeArray) { -// StringBuilder s = new StringBuilder(); -// for (PtNode ptNode : nodeArray.data) { -// s.append(ptNode.frequency); -// for (int ch : ptNode.chars) { -// s.append(Character.toChars(ch)); -// } -// } -// return s.toString(); -// } - - /** * Iterator to walk through a dictionary. * * This is purely for convenience. */ - public static final class DictionaryIterator implements Iterator<Word> { + public static final class DictionaryIterator implements Iterator<WordProperty> { private static final class Position { public Iterator<PtNode> pos; public int length; @@ -867,7 +747,7 @@ public final class FusionDictionary implements Iterable<Word> { } @Override - public Word next() { + public WordProperty next() { Position currentPos = mPositions.getLast(); mCurrentString.setLength(currentPos.length); @@ -883,8 +763,9 @@ public final class FusionDictionary implements Iterable<Word> { currentPos.length = mCurrentString.length(); mPositions.addLast(currentPos); } - if (currentPtNode.mFrequency >= 0) { - return new Word(mCurrentString.toString(), currentPtNode.mFrequency, + if (currentPtNode.isTerminal()) { + return new WordProperty(mCurrentString.toString(), + currentPtNode.mProbabilityInfo, currentPtNode.mShortcutTargets, currentPtNode.mBigrams, currentPtNode.mIsNotAWord, currentPtNode.mIsBlacklistEntry); } @@ -910,7 +791,7 @@ public final class FusionDictionary implements Iterable<Word> { * and say : for (Word w : x) {} */ @Override - public Iterator<Word> iterator() { + public Iterator<WordProperty> iterator() { return new DictionaryIterator(mRootNodeArray.mData); } } diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java new file mode 100644 index 000000000..9dcd63f0c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 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.latin.BinaryDictionary; +import com.android.inputmethod.latin.utils.CombinedFormatUtils; + +import java.util.Arrays; + +public final class ProbabilityInfo { + public final int mProbability; + // mTimestamp, mLevel and mCount are historical info. These values are depend on the + // implementation in native code; thus, we must not use them and have any assumptions about + // them except for tests. + public final int mTimestamp; + public final int mLevel; + public final int mCount; + + public static ProbabilityInfo max(final ProbabilityInfo probabilityInfo1, + final ProbabilityInfo probabilityInfo2) { + if (probabilityInfo1 == null) { + return probabilityInfo2; + } + if (probabilityInfo2 == null) { + return probabilityInfo1; + } + if (probabilityInfo1.mProbability > probabilityInfo2.mProbability) { + return probabilityInfo1; + } else { + return probabilityInfo2; + } + } + + public ProbabilityInfo(final int probability) { + this(probability, BinaryDictionary.NOT_A_VALID_TIMESTAMP, 0, 0); + } + + public ProbabilityInfo(final int probability, final int timestamp, final int level, + final int count) { + mProbability = probability; + mTimestamp = timestamp; + mLevel = level; + mCount = count; + } + + public boolean hasHistoricalInfo() { + return mTimestamp != BinaryDictionary.NOT_A_VALID_TIMESTAMP; + } + + @Override + public int hashCode() { + if (hasHistoricalInfo()) { + return Arrays.hashCode(new Object[] { mProbability, mTimestamp, mLevel, mCount }); + } else { + return Arrays.hashCode(new Object[] { mProbability }); + } + } + + @Override + public String toString() { + return CombinedFormatUtils.formatProbabilityInfo(this); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ProbabilityInfo)) return false; + final ProbabilityInfo p = (ProbabilityInfo)o; + if (!hasHistoricalInfo() && !p.hasHistoricalInfo()) { + return mProbability == p.mProbability; + } + return mProbability == p.mProbability && mTimestamp == p.mTimestamp && mLevel == p.mLevel + && mCount == p.mCount; + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java b/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java index 188de7a0f..f52117c6c 100644 --- a/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java +++ b/java/src/com/android/inputmethod/latin/makedict/PtNodeInfo.java @@ -29,24 +29,26 @@ public final class PtNodeInfo { public final int mEndAddress; public final int mFlags; public final int[] mCharacters; - public final int mFrequency; + public final ProbabilityInfo mProbabilityInfo; public final int mChildrenAddress; - public final int mParentAddress; public final ArrayList<WeightedString> mShortcutTargets; public final ArrayList<PendingAttribute> mBigrams; public PtNodeInfo(final int originalAddress, final int endAddress, final int flags, - final int[] characters, final int frequency, final int parentAddress, + final int[] characters, final ProbabilityInfo probabilityInfo, final int childrenAddress, final ArrayList<WeightedString> shortcutTargets, final ArrayList<PendingAttribute> bigrams) { mOriginalAddress = originalAddress; mEndAddress = endAddress; mFlags = flags; mCharacters = characters; - mFrequency = frequency; - mParentAddress = parentAddress; + mProbabilityInfo = probabilityInfo; mChildrenAddress = childrenAddress; mShortcutTargets = shortcutTargets; mBigrams = bigrams; } + + public boolean isTerminal() { + return mProbabilityInfo != null; + } } diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java deleted file mode 100644 index 7592a0c13..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.utils.CollectionUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; - -/** - * SparseTable is an extensible map from integer to integer. - * This holds one value for every mBlockSize keys, so it uses 1/mBlockSize'th of the full index - * memory. - */ -@UsedForTesting -public class SparseTable { - - /** - * mLookupTable is indexed by terminal ID, containing exactly one entry for every mBlockSize - * terminals. - * It contains at index i = j / mBlockSize the index in each ArrayList in mContentsTables where - * the values for terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized - * integer array. - */ - private final ArrayList<Integer> mLookupTable; - private final ArrayList<ArrayList<Integer>> mContentTables; - - private final int mBlockSize; - private final int mContentTableCount; - public static final int NOT_EXIST = -1; - public static final int SIZE_OF_INT_IN_BYTES = 4; - - @UsedForTesting - public SparseTable(final int initialCapacity, final int blockSize, - final int contentTableCount) { - mBlockSize = blockSize; - final int lookupTableSize = initialCapacity / mBlockSize - + (initialCapacity % mBlockSize > 0 ? 1 : 0); - mLookupTable = new ArrayList<Integer>(Collections.nCopies(lookupTableSize, NOT_EXIST)); - mContentTableCount = contentTableCount; - mContentTables = CollectionUtils.newArrayList(); - for (int i = 0; i < mContentTableCount; ++i) { - mContentTables.add(new ArrayList<Integer>()); - } - } - - @UsedForTesting - public SparseTable(final ArrayList<Integer> lookupTable, - final ArrayList<ArrayList<Integer>> contentTables, final int blockSize) { - mBlockSize = blockSize; - mContentTableCount = contentTables.size(); - mLookupTable = lookupTable; - mContentTables = contentTables; - } - - /** - * Converts an byte array to an int array considering each set of 4 bytes is an int stored in - * big-endian. - * The length of byteArray must be a multiple of four. - * Otherwise, IndexOutOfBoundsException will be raised. - */ - @UsedForTesting - private static ArrayList<Integer> convertByteArrayToIntegerArray(final byte[] byteArray) { - final ArrayList<Integer> integerArray = new ArrayList<Integer>(byteArray.length / 4); - for (int i = 0; i < byteArray.length; i += 4) { - int value = 0; - for (int j = i; j < i + 4; ++j) { - value <<= 8; - value |= byteArray[j] & 0xFF; - } - integerArray.add(value); - } - return integerArray; - } - - @UsedForTesting - public int get(final int contentTableIndex, final int index) { - if (!contains(index)) { - return NOT_EXIST; - } - return mContentTables.get(contentTableIndex).get( - mLookupTable.get(index / mBlockSize) + (index % mBlockSize)); - } - - @UsedForTesting - public ArrayList<Integer> getAll(final int index) { - final ArrayList<Integer> ret = CollectionUtils.newArrayList(); - for (int i = 0; i < mContentTableCount; ++i) { - ret.add(get(i, index)); - } - return ret; - } - - @UsedForTesting - public void set(final int contentTableIndex, final int index, final int value) { - if (mLookupTable.get(index / mBlockSize) == NOT_EXIST) { - mLookupTable.set(index / mBlockSize, mContentTables.get(contentTableIndex).size()); - for (int i = 0; i < mContentTableCount; ++i) { - for (int j = 0; j < mBlockSize; ++j) { - mContentTables.get(i).add(NOT_EXIST); - } - } - } - mContentTables.get(contentTableIndex).set( - mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value); - } - - public void remove(final int indexOfContent, final int index) { - set(indexOfContent, index, NOT_EXIST); - } - - @UsedForTesting - public int size() { - return mLookupTable.size() * mBlockSize; - } - - @UsedForTesting - /* package */ int getContentTableSize() { - // This class always has at least one content table. - return mContentTables.get(0).size(); - } - - @UsedForTesting - /* package */ int getLookupTableSize() { - return mLookupTable.size(); - } - - public boolean contains(final int index) { - if (index < 0 || index / mBlockSize >= mLookupTable.size() - || mLookupTable.get(index / mBlockSize) == NOT_EXIST) { - return false; - } - return true; - } - - @UsedForTesting - public void write(final OutputStream lookupOutStream, final OutputStream[] contentOutStreams) - throws IOException { - if (contentOutStreams.length != mContentTableCount) { - throw new RuntimeException(contentOutStreams.length + " streams are given, but the" - + " table has " + mContentTableCount + " content tables."); - } - for (final int index : mLookupTable) { - BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, SIZE_OF_INT_IN_BYTES); - } - - for (int i = 0; i < contentOutStreams.length; ++i) { - for (final int data : mContentTables.get(i)) { - BinaryDictEncoderUtils.writeUIntToStream(contentOutStreams[i], data, - SIZE_OF_INT_IN_BYTES); - } - } - } - - @UsedForTesting - public void writeToFiles(final File lookupTableFile, final File[] contentFiles) - throws IOException { - FileOutputStream lookupTableOutStream = null; - final FileOutputStream[] contentTableOutStreams = new FileOutputStream[mContentTableCount]; - try { - lookupTableOutStream = new FileOutputStream(lookupTableFile); - for (int i = 0; i < contentFiles.length; ++i) { - contentTableOutStreams[i] = new FileOutputStream(contentFiles[i]); - } - write(lookupTableOutStream, contentTableOutStreams); - } finally { - if (lookupTableOutStream != null) { - lookupTableOutStream.close(); - } - for (int i = 0; i < contentTableOutStreams.length; ++i) { - if (contentTableOutStreams[i] != null) { - contentTableOutStreams[i].close(); - } - } - } - } - - private static byte[] readFileToByteArray(final File file) throws IOException { - final byte[] contents = new byte[(int) file.length()]; - FileInputStream inStream = null; - try { - inStream = new FileInputStream(file); - inStream.read(contents); - } finally { - if (inStream != null) { - inStream.close(); - } - } - return contents; - } - - @UsedForTesting - public static SparseTable readFromFiles(final File lookupTableFile, final File[] contentFiles, - final int blockSize) throws IOException { - final ArrayList<ArrayList<Integer>> contentTables = - new ArrayList<ArrayList<Integer>>(contentFiles.length); - for (int i = 0; i < contentFiles.length; ++i) { - contentTables.add(convertByteArrayToIntegerArray(readFileToByteArray(contentFiles[i]))); - } - return new SparseTable(convertByteArrayToIntegerArray(readFileToByteArray(lookupTableFile)), - contentTables, blockSize); - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java new file mode 100644 index 000000000..bf776cfc5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java @@ -0,0 +1,316 @@ +/* + * 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 com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.utils.CollectionUtils; + +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 { + private static final String TAG = Ver2DictDecoder.class.getSimpleName(); + + /** + * 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; + // TODO: Remove mBufferFactory and mDictBuffer from this class members because they are now + // used only for testing. + private final DictionaryBufferFactory mBufferFactory; + protected DictBuffer mDictBuffer; + + /* package */ Ver2DictDecoder(final File file, final int factoryFlag) { + mDictionaryBinaryFile = file; + 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 DictionaryBufferFactory factory) { + mDictionaryBinaryFile = file; + 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(), 0 /* offset */, + mDictionaryBinaryFile.length() /* length */, 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<WeightedString>(); + addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets); + } else { + shortcutTargets = null; + } + + final ArrayList<PendingAttribute> bigrams; + if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { + bigrams = new ArrayList<PendingAttribute>(); + 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 = CollectionUtils.newArrayList(); + 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); + } +} diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java index 5da34534e..e247f0121 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; @@ -31,16 +32,18 @@ import java.util.ArrayList; import java.util.Iterator; /** - * An implementation of DictEncoder for version 3 binary dictionary. + * An implementation of DictEncoder for version 2 binary dictionary. */ -public class Ver3DictEncoder implements DictEncoder { +@UsedForTesting +public class Ver2DictEncoder implements DictEncoder { private final File mDictFile; private OutputStream mOutStream; private byte[] mBuffer; private int mPosition; - public Ver3DictEncoder(final File dictFile) { + @UsedForTesting + public Ver2DictEncoder(final File dictFile) { mDictFile = dictFile; mOutStream = null; mBuffer = null; @@ -49,7 +52,8 @@ public class Ver3DictEncoder implements DictEncoder { // This constructor is used only by BinaryDictOffdeviceUtilsTests. // If you want to use this in the production code, you should consider keeping consistency of // the interface of Ver3DictDecoder by using factory. - public Ver3DictEncoder(final OutputStream outStream) { + @UsedForTesting + public Ver2DictEncoder(final OutputStream outStream) { mDictFile = null; mOutStream = outStream; } @@ -68,7 +72,7 @@ public class Ver3DictEncoder implements DictEncoder { @Override public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) throws IOException, UnsupportedFormatException { - if (formatOptions.mVersion > FormatSpec.VERSION3) { + if (formatOptions.mVersion > FormatSpec.VERSION2) { throw new UnsupportedFormatException( "The given format options has wrong version number : " + formatOptions.mVersion); @@ -91,7 +95,7 @@ public class Ver3DictEncoder implements DictEncoder { ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray); MakedictLog.i("Computing addresses..."); - BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions); + BinaryDictEncoderUtils.computeAddresses(dict, flatNodes); MakedictLog.i("Checking PtNode array..."); if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes); @@ -103,7 +107,7 @@ public class Ver3DictEncoder implements DictEncoder { MakedictLog.i("Writing file..."); for (PtNodeArray nodeArray : flatNodes) { - BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions); + BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray); } if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes); mOutStream.write(mBuffer, 0, mPosition); @@ -135,24 +139,13 @@ public class Ver3DictEncoder implements DictEncoder { countSize); } - private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) { - final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); + private void writePtNodeFlags(final PtNode ptNode) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode); mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, - BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions), + BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos), FormatSpec.PTNODE_FLAGS_SIZE); } - private void writeParentPosition(final int parentPosition, final PtNode ptNode, - final FormatOptions formatOptions) { - if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) { - mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition, - parentPosition, formatOptions); - } else { - mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition, - parentPosition - ptNode.mCachedAddressAfterUpdate, formatOptions); - } - } - private void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) { mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition); if (hasSeveralChars) { @@ -167,15 +160,10 @@ public class Ver3DictEncoder implements DictEncoder { } } - private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) { - final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); - if (formatOptions.mSupportsDynamicUpdate) { - mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition, - childrenPos); - } else { - mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, - childrenPos); - } + private void writeChildrenPosition(final PtNode ptNode) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode); + mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, + childrenPos); } /** @@ -193,7 +181,7 @@ public class Ver3DictEncoder implements DictEncoder { final WeightedString target = shortcutIterator.next(); final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags( shortcutIterator.hasNext(), - target.mFrequency); + target.getProbability()); mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags, FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord); @@ -223,11 +211,11 @@ public class Ver3DictEncoder implements DictEncoder { final PtNode target = FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord); final int addressOfBigram = target.mCachedAddressAfterUpdate; - final int unigramFrequencyForThisWord = target.mFrequency; + final int unigramFrequencyForThisWord = target.getProbability(); final int offset = addressOfBigram - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(), - offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord); + offset, bigram.getProbability(), unigramFrequencyForThisWord, bigram.mWord); mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags, FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, @@ -242,13 +230,11 @@ public class Ver3DictEncoder implements DictEncoder { } @Override - public void writePtNode(final PtNode ptNode, final int parentPosition, - final FormatOptions formatOptions, final FusionDictionary dict) { - writePtNodeFlags(ptNode, formatOptions); - writeParentPosition(parentPosition, ptNode, formatOptions); + public void writePtNode(final PtNode ptNode, final FusionDictionary dict) { + writePtNodeFlags(ptNode); writeCharacters(ptNode.mChars, ptNode.hasSeveralChars()); - writeFrequency(ptNode.mFrequency); - writeChildrenPosition(ptNode, formatOptions); + writeFrequency(ptNode.getProbability()); + writeChildrenPosition(ptNode); writeShortcuts(ptNode.mShortcutTargets); writeBigrams(ptNode.mBigrams, dict); } diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java deleted file mode 100644 index acab4f8a5..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * 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.makedict.BinaryDictDecoderUtils.CharEncoding; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; -import com.android.inputmethod.latin.utils.JniUtils; - -import android.util.Log; - -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 3 binary dictionary. - */ -@UsedForTesting -public class Ver3DictDecoder extends AbstractDictDecoder { - private static final String TAG = Ver3DictDecoder.class.getSimpleName(); - - static { - JniUtils.loadNativeLibrary(); - } - - // TODO: implement something sensical instead of just a phony method - private static native int doNothing(); - - protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader { - private static int readFrequency(final DictBuffer dictBuffer) { - return dictBuffer.readUnsignedByte(); - } - } - - protected final File mDictionaryBinaryFile; - private final DictionaryBufferFactory mBufferFactory; - protected DictBuffer mDictBuffer; - - /* package */ Ver3DictDecoder(final File file, final int factoryFlag) { - mDictionaryBinaryFile = file; - 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 */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) { - mDictionaryBinaryFile = file; - 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 FileHeader readHeader() throws IOException, UnsupportedFormatException { - if (mDictBuffer == null) { - openDictBuffer(); - } - final FileHeader header = super.readHeader(mDictBuffer); - final int version = header.mFormatOptions.mVersion; - if (!(version >= 2 && version <= 3)) { - throw new UnsupportedFormatException("File header has a wrong version : " + version); - } - 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, final FormatOptions options) { - int addressPointer = ptNodePos; - final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); - addressPointer += FormatSpec.PTNODE_FLAGS_SIZE; - - final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options); - if (BinaryDictIOUtils.supportsDynamicUpdate(options)) { - addressPointer += FormatSpec.PARENT_ADDRESS_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 int frequency; - if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) { - frequency = PtNodeReader.readFrequency(mDictBuffer); - addressPointer += FormatSpec.PTNODE_FREQUENCY_SIZE; - } else { - frequency = PtNode.NOT_A_TERMINAL; - } - int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options); - if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { - childrenAddress += addressPointer; - } - addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options); - final ArrayList<WeightedString> shortcutTargets; - if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) { - // readShortcut will add shortcuts to shortcutTargets. - shortcutTargets = new ArrayList<WeightedString>(); - addressPointer += PtNodeReader.readShortcut(mDictBuffer, shortcutTargets); - } else { - shortcutTargets = null; - } - - final ArrayList<PendingAttribute> bigrams; - if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { - bigrams = new ArrayList<PendingAttribute>(); - 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, frequency, - parentAddress, childrenAddress, shortcutTargets, bigrams); - } - - @Override - public FusionDictionary readDictionaryBinary(final FusionDictionary dict, - final boolean deleteDictIfBroken) - throws FileNotFoundException, IOException, UnsupportedFormatException { - if (mDictBuffer == null) { - openDictBuffer(); - } - try { - return BinaryDictDecoderUtils.readDictionaryBinary(this, dict); - } catch (IOException e) { - Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e); - if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) { - Log.e(TAG, "Failed to delete the broken dictionary."); - } - throw e; - } catch (UnsupportedFormatException e) { - Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e); - if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) { - Log.e(TAG, "Failed to delete the broken dictionary."); - } - throw e; - } - } - - @Override - public void setPosition(int newPos) { - mDictBuffer.position(newPos); - } - - @Override - public int getPosition() { - return mDictBuffer.position(); - } - - @Override - public int readPtNodeCount() { - return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer); - } - - @Override - public boolean readAndFollowForwardLink() { - final int nextAddress = mDictBuffer.readUnsignedInt24(); - if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) { - mDictBuffer.position(nextAddress); - return true; - } - return false; - } - - @Override - public boolean hasNextPtNodeArray() { - return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS; - } - - @Override - public void skipPtNode(final FormatOptions formatOptions) { - final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); - PtNodeReader.readParentAddress(mDictBuffer, formatOptions); - BinaryDictIOUtils.skipString(mDictBuffer, - (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); - PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions); - if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readFrequency(mDictBuffer); - if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) { - final int shortcutsSize = mDictBuffer.readUnsignedShort(); - mDictBuffer.position(mDictBuffer.position() + shortcutsSize - - FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE); - } - if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) { - int bigramCount = 0; - while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) { - final int bigramFlags = mDictBuffer.readUnsignedByte(); - switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) { - case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE: - mDictBuffer.readUnsignedByte(); - break; - case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES: - mDictBuffer.readUnsignedShort(); - break; - case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES: - mDictBuffer.readUnsignedInt24(); - break; - } - if ((bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) == 0) break; - } - if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) { - throw new RuntimeException("Too many bigrams in a PtNode."); - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java deleted file mode 100644 index 07adda625..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.makedict.FusionDictionary.WeightedString; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; - -/** - * An implementation of DictUpdater for version 3 binary dictionary. - */ -@UsedForTesting -public class Ver3DictUpdater extends Ver3DictDecoder implements DictUpdater { - private OutputStream mOutStream; - - @UsedForTesting - public Ver3DictUpdater(final File dictFile, final int factoryType) { - // DictUpdater must have an updatable DictBuffer. - super(dictFile, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY) - ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER); - mOutStream = null; - } - - private void openStreamAndBuffer() throws FileNotFoundException, IOException { - super.openDictBuffer(); - mOutStream = new FileOutputStream(mDictionaryBinaryFile, true /* append */); - } - - private void close() throws IOException { - if (mOutStream != null) { - mOutStream.close(); - mOutStream = null; - } - } - - @Override @UsedForTesting - public void deleteWord(final String word) throws IOException, UnsupportedFormatException { - if (mOutStream == null) openStreamAndBuffer(); - mDictBuffer.position(0); - readHeader(); - final int wordPos = getTerminalPosition(word); - if (wordPos != FormatSpec.NOT_VALID_WORD) { - mDictBuffer.position(wordPos); - final int flags = mDictBuffer.readUnsignedByte(); - mDictBuffer.position(wordPos); - mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags)); - } - close(); - } - - @Override @UsedForTesting - public void insertWord(final String word, final int frequency, - final ArrayList<WeightedString> bigramStrings, - final ArrayList<WeightedString> shortcuts, - final boolean isNotAWord, final boolean isBlackListEntry) - throws IOException, UnsupportedFormatException { - if (mOutStream == null) openStreamAndBuffer(); - DynamicBinaryDictIOUtils.insertWord(this, mOutStream, word, frequency, bigramStrings, - shortcuts, isNotAWord, isBlackListEntry); - close(); - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java index 734223ec2..afe82317e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -17,21 +17,15 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import com.android.inputmethod.latin.utils.CollectionUtils; - -import android.util.Log; +import com.android.inputmethod.latin.utils.FileUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; /** * An implementation of binary dictionary decoder for version 4 binary dictionary. @@ -40,304 +34,83 @@ import java.util.Arrays; public class Ver4DictDecoder extends AbstractDictDecoder { private static final String TAG = Ver4DictDecoder.class.getSimpleName(); - private static final int FILETYPE_TRIE = 1; - private static final int FILETYPE_FREQUENCY = 2; - private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3; - private static final int FILETYPE_BIGRAM_FREQ = 4; - private static final int FILETYPE_SHORTCUT = 5; - - private final File mDictDirectory; - private final DictionaryBufferFactory mBufferFactory; - protected DictBuffer mDictBuffer; - private DictBuffer mFrequencyBuffer; - private DictBuffer mTerminalAddressTableBuffer; - private DictBuffer mBigramBuffer; - private DictBuffer mShortcutBuffer; - private SparseTable mBigramAddressTable; - private SparseTable mShortcutAddressTable; + final File mDictDirectory; @UsedForTesting /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) { - mDictDirectory = dictDirectory; - mDictBuffer = mFrequencyBuffer = 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(); - } + this(dictDirectory, null /* factory */); } @UsedForTesting /* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) { mDictDirectory = dictDirectory; - mBufferFactory = factory; - mDictBuffer = mFrequencyBuffer = null; - } - private File getFile(final int fileType) { - if (fileType == FILETYPE_TRIE) { - return new File(mDictDirectory, - mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION); - } else if (fileType == FILETYPE_FREQUENCY) { - return new File(mDictDirectory, - mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION); - } else if (fileType == FILETYPE_TERMINAL_ADDRESS_TABLE) { - return new File(mDictDirectory, - mDictDirectory.getName() + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION); - } else if (fileType == FILETYPE_BIGRAM_FREQ) { - return new File(mDictDirectory, - mDictDirectory.getName() + FormatSpec.BIGRAM_FILE_EXTENSION - + FormatSpec.BIGRAM_FREQ_CONTENT_ID); - } else if (fileType == FILETYPE_SHORTCUT) { - return new File(mDictDirectory, - mDictDirectory.getName() + FormatSpec.SHORTCUT_FILE_EXTENSION - + FormatSpec.SHORTCUT_CONTENT_ID); - } else { - throw new RuntimeException("Unsupported kind of file : " + fileType); - } } @Override - public void openDictBuffer() throws FileNotFoundException, IOException { - mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE)); - mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY)); - mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer( - getFile(FILETYPE_TERMINAL_ADDRESS_TABLE)); - mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ)); - loadBigramAddressSparseTable(); - mShortcutBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_SHORTCUT)); - loadShortcutAddressSparseTable(); - } - - @Override - public boolean isDictBufferOpen() { - return mDictBuffer != null; - } - - /* package */ DictBuffer getDictBuffer() { - return mDictBuffer; - } - - @Override - public FileHeader readHeader() throws IOException, UnsupportedFormatException { - if (mDictBuffer == null) { - openDictBuffer(); - } - final FileHeader header = super.readHeader(mDictBuffer); - final int version = header.mFormatOptions.mVersion; - if (version != 4) { - throw new UnsupportedFormatException("File header has a wrong version : " + version); + public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException { + // dictType is not being used in dicttool. Passing an empty string. + final BinaryDictionary binaryDictionary= new BinaryDictionary( + mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */, + true /* useFullEditDistance */, null /* locale */, + "" /* dictType */, true /* isUpdatable */); + final DictionaryHeader header = binaryDictionary.getHeader(); + binaryDictionary.close(); + if (header == null) { + throw new IOException("Cannot read the dictionary header."); } return header; } - private void loadBigramAddressSparseTable() throws IOException { - final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName() - + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX); - final File freqsFile = new File(mDictDirectory, mDictDirectory.getName() - + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX - + FormatSpec.BIGRAM_FREQ_CONTENT_ID); - mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile }, - FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE); - } - - // TODO: Let's have something like SparseTableContentsReader in this class. - private void loadShortcutAddressSparseTable() throws IOException { - final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName() - + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX); - final File contentFile = new File(mDictDirectory, mDictDirectory.getName() - + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX - + FormatSpec.SHORTCUT_CONTENT_ID); - final File timestampsFile = new File(mDictDirectory, mDictDirectory.getName() - + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX - + FormatSpec.SHORTCUT_CONTENT_ID); - mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile, - new File[] { contentFile, timestampsFile }, - FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE); - } - - protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader { - protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) { - frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1); - return frequencyBuffer.readUnsignedByte(); - } - - protected static int readTerminalId(final DictBuffer dictBuffer) { - return dictBuffer.readInt(); - } - } - - private ArrayList<WeightedString> readShortcuts(final int terminalId) { - if (mShortcutAddressTable.get(0, terminalId) == SparseTable.NOT_EXIST) return null; - - final ArrayList<WeightedString> ret = CollectionUtils.newArrayList(); - final int posOfShortcuts = mShortcutAddressTable.get(FormatSpec.SHORTCUT_CONTENT_INDEX, - terminalId); - mShortcutBuffer.position(posOfShortcuts); - while (true) { - final int flags = mShortcutBuffer.readUnsignedByte(); - final String word = CharEncoding.readString(mShortcutBuffer); - ret.add(new WeightedString(word, - flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY)); - if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break; - } - return ret; - } - - // TODO: Make this buffer thread safe. - // TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH. - private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH]; @Override - public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) { - int addressPointer = ptNodePos; - final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); - addressPointer += FormatSpec.PTNODE_FLAGS_SIZE; - - final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options); - if (BinaryDictIOUtils.supportsDynamicUpdate(options)) { - addressPointer += FormatSpec.PARENT_ADDRESS_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 - && index < FormatSpec.MAX_WORD_LENGTH) { - 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 int terminalId; - if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) { - terminalId = PtNodeReader.readTerminalId(mDictBuffer); - addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE; - } else { - terminalId = PtNode.NOT_A_TERMINAL; - } - - final int frequency; - if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) { - frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId); - } else { - frequency = PtNode.NOT_A_TERMINAL; - } - int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options); - if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { - childrenAddress += addressPointer; - } - addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options); - final ArrayList<WeightedString> shortcutTargets = readShortcuts(terminalId); - - final ArrayList<PendingAttribute> bigrams; - if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { - bigrams = new ArrayList<PendingAttribute>(); - final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId); - mBigramBuffer.position(posOfBigrams); - while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) { - // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE, - // remaining bigram entries are ignored. - final int bigramFlags = mBigramBuffer.readUnsignedByte(); - final int targetTerminalId = mBigramBuffer.readUnsignedInt24(); - mTerminalAddressTableBuffer.position( - targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE); - final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24(); - bigrams.add(new PendingAttribute( - bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY, - targetAddress)); - if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break; + 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( + mDictDirectory.getAbsolutePath(), 0 /* offset */, 0 /* length */, + true /* useFullEditDistance */, null /* locale */, + "" /* dictType */, true /* isUpdatable */); + final DictionaryHeader header = readHeader(); + final FusionDictionary fusionDict = + new FusionDictionary(new FusionDictionary.PtNodeArray(), header.mDictionaryOptions); + int token = 0; + final ArrayList<WordProperty> wordProperties = CollectionUtils.newArrayList(); + do { + final BinaryDictionary.GetNextWordPropertyResult result = + binaryDictionary.getNextWordProperty(token); + final WordProperty wordProperty = result.mWordProperty; + if (wordProperty == null) { + binaryDictionary.close(); + if (deleteDictIfBroken) { + FileUtils.deleteRecursively(mDictDirectory); + } + return null; } - 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 + ")"); + 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); } - } else { - bigrams = null; - } - return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency, - parentAddress, childrenAddress, shortcutTargets, bigrams); - } - - private void deleteDictFiles() { - final File[] files = mDictDirectory.listFiles(); - for (int i = 0; i < files.length; ++i) { - files[i].delete(); } - } - - @Override - public FusionDictionary readDictionaryBinary(final FusionDictionary dict, - final boolean deleteDictIfBroken) - throws FileNotFoundException, IOException, UnsupportedFormatException { - if (mDictBuffer == null) { - openDictBuffer(); - } - try { - return BinaryDictDecoderUtils.readDictionaryBinary(this, dict); - } catch (IOException e) { - Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e); - if (deleteDictIfBroken) { - deleteDictFiles(); + // Insert bigrams into the fusion dictionary. + for (final WordProperty wordProperty : wordProperties) { + if (wordProperty.mBigrams == null) { + continue; } - throw e; - } catch (UnsupportedFormatException e) { - Log.e(TAG, "The dictionary " + mDictDirectory.getName() + " is broken.", e); - if (deleteDictIfBroken) { - deleteDictFiles(); + final String word0 = wordProperty.mWord; + for (final WeightedString bigram : wordProperty.mBigrams) { + fusionDict.setBigram(word0, bigram.mWord, bigram.mProbabilityInfo); } - throw e; } - } - - @Override - public void setPosition(int newPos) { - mDictBuffer.position(newPos); - } - - @Override - public int getPosition() { - return mDictBuffer.position(); - } - - @Override - public int readPtNodeCount() { - return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer); - } - - @Override - public boolean readAndFollowForwardLink() { - final int nextAddress = mDictBuffer.readUnsignedInt24(); - if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) { - mDictBuffer.position(nextAddress); - return true; - } - return false; - } - - @Override - public boolean hasNextPtNodeArray() { - return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS; - } - - @Override - public void skipPtNode(final FormatOptions formatOptions) { - final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); - PtNodeReader.readParentAddress(mDictBuffer, formatOptions); - BinaryDictIOUtils.skipString(mDictBuffer, - (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); - if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readTerminalId(mDictBuffer); - PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions); + binaryDictionary.close(); + return fusionDict; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java index 8d5b48a9b..1050d1b0e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java @@ -1,5 +1,4 @@ /* -/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,21 +17,15 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Iterator; /** * An implementation of DictEncoder for version 4 binary dictionary. @@ -40,244 +33,19 @@ import java.util.Iterator; @UsedForTesting public class Ver4DictEncoder implements DictEncoder { private final File mDictPlacedDir; - private byte[] mTrieBuf; - private int mTriePos; - private int mHeaderSize; - private OutputStream mTrieOutStream; - private OutputStream mFreqOutStream; - private OutputStream mUnigramTimestampOutStream; - private OutputStream mTerminalAddressTableOutStream; - private File mDictDir; - private String mBaseFilename; - private BigramContentWriter mBigramWriter; - private ShortcutContentWriter mShortcutWriter; @UsedForTesting public Ver4DictEncoder(final File dictPlacedDir) { mDictPlacedDir = dictPlacedDir; } - private interface SparseTableContentWriterInterface { - public void write(final OutputStream outStream) throws IOException; - } - - private static class SparseTableContentWriter { - private final int mContentCount; - private final SparseTable mSparseTable; - private final File mLookupTableFile; - protected final File mBaseDir; - private final File[] mAddressTableFiles; - private final File[] mContentFiles; - protected final OutputStream[] mContentOutStreams; - - public SparseTableContentWriter(final String name, final int initialCapacity, - final int blockSize, final File baseDir, final String[] contentFilenames, - final String[] contentIds) { - if (contentFilenames.length != contentIds.length) { - throw new RuntimeException("The length of contentFilenames and the length of" - + " contentIds are different " + contentFilenames.length + ", " - + contentIds.length); - } - mContentCount = contentFilenames.length; - mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount); - mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX); - mAddressTableFiles = new File[mContentCount]; - mContentFiles = new File[mContentCount]; - mBaseDir = baseDir; - for (int i = 0; i < mContentCount; ++i) { - mAddressTableFiles[i] = new File(mBaseDir, - name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]); - mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]); - } - mContentOutStreams = new OutputStream[mContentCount]; - } - - public void openStreams() throws FileNotFoundException { - for (int i = 0; i < mContentCount; ++i) { - mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]); - } - } - - protected void write(final int contentIndex, final int index, - final SparseTableContentWriterInterface writer) throws IOException { - mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length()); - writer.write(mContentOutStreams[contentIndex]); - mContentOutStreams[contentIndex].flush(); - } - - public void closeStreams() throws IOException { - mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles); - for (int i = 0; i < mContentCount; ++i) { - mContentOutStreams[i].close(); - } - } - } - - private static class BigramContentWriter extends SparseTableContentWriter { - private final boolean mWriteTimestamp; - - public BigramContentWriter(final String name, final int initialCapacity, - final File baseDir, final boolean writeTimestamp) { - super(name + FormatSpec.BIGRAM_FILE_EXTENSION, initialCapacity, - FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir, - getContentFilenames(name, writeTimestamp), getContentIds(writeTimestamp)); - mWriteTimestamp = writeTimestamp; - } - - private static String[] getContentFilenames(final String name, - final boolean writeTimestamp) { - final String[] contentFilenames; - if (writeTimestamp) { - contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION, - name + FormatSpec.BIGRAM_FILE_EXTENSION }; - } else { - contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION }; - } - return contentFilenames; - } - - private static String[] getContentIds(final boolean writeTimestamp) { - final String[] contentIds; - if (writeTimestamp) { - contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID, - FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID }; - } else { - contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID }; - } - return contentIds; - } - - public void writeBigramsForOneWord(final int terminalId, final int bigramCount, - final Iterator<WeightedString> bigramIterator, final FusionDictionary dict) - throws IOException { - write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId, - new SparseTableContentWriterInterface() { - @Override - public void write(final OutputStream outStream) throws IOException { - writeBigramsForOneWordInternal(outStream, bigramIterator, dict); - }}); - if (mWriteTimestamp) { - write(FormatSpec.BIGRAM_TIMESTAMP_CONTENT_INDEX, terminalId, - new SparseTableContentWriterInterface() { - @Override - public void write(final OutputStream outStream) throws IOException { - initBigramTimestampsCountersAndLevelsForOneWordInternal(outStream, - bigramCount); - }}); - } - } - - private void writeBigramsForOneWordInternal(final OutputStream outStream, - final Iterator<WeightedString> bigramIterator, final FusionDictionary dict) - throws IOException { - while (bigramIterator.hasNext()) { - final WeightedString bigram = bigramIterator.next(); - final PtNode target = - FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord); - final int unigramFrequencyForThisWord = target.mFrequency; - final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags( - bigramIterator.hasNext(), 0, bigram.mFrequency, - unigramFrequencyForThisWord, bigram.mWord); - BinaryDictEncoderUtils.writeUIntToStream(outStream, bigramFlags, - FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); - BinaryDictEncoderUtils.writeUIntToStream(outStream, target.mTerminalId, - FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE); - } - } - - private void initBigramTimestampsCountersAndLevelsForOneWordInternal( - final OutputStream outStream, final int bigramCount) throws IOException { - for (int i = 0; i < bigramCount; ++i) { - // TODO: Figure out what initial values should be. - BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, - FormatSpec.BIGRAM_TIMESTAMP_SIZE); - BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, - FormatSpec.BIGRAM_COUNTER_SIZE); - BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, - FormatSpec.BIGRAM_LEVEL_SIZE); - } - } - } - - private static class ShortcutContentWriter extends SparseTableContentWriter { - public ShortcutContentWriter(final String name, final int initialCapacity, - final File baseDir) { - super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, initialCapacity, - FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir, - new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION }, - new String[] { FormatSpec.SHORTCUT_CONTENT_ID }); - } - - public void writeShortcutForOneWord(final int terminalId, - final Iterator<WeightedString> shortcutIterator) throws IOException { - write(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId, - new SparseTableContentWriterInterface() { - @Override - public void write(final OutputStream outStream) throws IOException { - writeShortcutForOneWordInternal(outStream, shortcutIterator); - } - }); - } - - private void writeShortcutForOneWordInternal(final OutputStream outStream, - final Iterator<WeightedString> shortcutIterator) throws IOException { - while (shortcutIterator.hasNext()) { - final WeightedString target = shortcutIterator.next(); - final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags( - shortcutIterator.hasNext(), target.mFrequency); - BinaryDictEncoderUtils.writeUIntToStream(outStream, shortcutFlags, - FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); - CharEncoding.writeString(outStream, target.mWord); - } - } - } - - private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions) - throws FileNotFoundException, IOException { - final FileHeader header = new FileHeader(0, dictOptions, formatOptions); - mBaseFilename = header.getId() + "." + header.getVersion(); - mDictDir = new File(mDictPlacedDir, mBaseFilename); - final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION); - final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION); - final File timestampFile = new File(mDictDir, - mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION); - final File terminalAddressTableFile = new File(mDictDir, - mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION); - if (!mDictDir.isDirectory()) { - if (mDictDir.exists()) mDictDir.delete(); - mDictDir.mkdirs(); - } - mTrieOutStream = new FileOutputStream(trieFile); - mFreqOutStream = new FileOutputStream(freqFile); - mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile); - if (formatOptions.mHasTimestamp) { - mUnigramTimestampOutStream = new FileOutputStream(timestampFile); - } - } - - private void close() throws IOException { - try { - if (mTrieOutStream != null) { - mTrieOutStream.close(); - } - if (mFreqOutStream != null) { - mFreqOutStream.close(); - } - if (mTerminalAddressTableOutStream != null) { - mTerminalAddressTableOutStream.close(); - } - if (mUnigramTimestampOutStream != null) { - mUnigramTimestampOutStream.close(); - } - } finally { - mTrieOutStream = null; - mFreqOutStream = null; - mTerminalAddressTableOutStream = null; - } - } - + // TODO: This builds a FusionDictionary first and iterates it to add words to the binary + // dictionary. However, it is possible to just add words directly to the binary dictionary + // instead. + // In the long run, when we stop supporting version 2, FusionDictionary will become deprecated + // and we can remove it. Then we'll be able to just call BinaryDictionary directly. @Override - public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) + public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions) throws IOException, UnsupportedFormatException { if (formatOptions.mVersion != FormatSpec.VERSION4) { throw new UnsupportedFormatException("File header has a wrong version number : " @@ -286,190 +54,74 @@ public class Ver4DictEncoder implements DictEncoder { if (!mDictPlacedDir.isDirectory()) { throw new UnsupportedFormatException("Given path is not a directory."); } - - if (mTrieOutStream == null) { - openStreams(formatOptions, dict.mOptions); - } - - mHeaderSize = BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict, - formatOptions); - - MakedictLog.i("Flattening the tree..."); - ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray); - int terminalCount = 0; - for (final PtNodeArray array : flatNodes) { - for (final PtNode node : array.mData) { - if (node.isTerminal()) node.mTerminalId = terminalCount++; + if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(), + FormatSpec.VERSION4, LocaleUtils.constructLocaleFromString( + dict.mOptions.mAttributes.get(DictionaryHeader.DICTIONARY_LOCALE_KEY)), + dict.mOptions.mAttributes)) { + throw new IOException("Cannot create dictionary file : " + + mDictPlacedDir.getAbsolutePath()); + } + final BinaryDictionary binaryDict = new BinaryDictionary(mDictPlacedDir.getAbsolutePath(), + 0l, mDictPlacedDir.length(), true /* useFullEditDistance */, + LocaleUtils.constructLocaleFromString(dict.mOptions.mAttributes.get( + DictionaryHeader.DICTIONARY_LOCALE_KEY)), + Dictionary.TYPE_USER /* Dictionary type. Does not matter for us */, + true /* isUpdatable */); + if (!binaryDict.isValidDictionary()) { + // Somehow createEmptyDictFile returned true, but the file was not created correctly + throw new IOException("Cannot create dictionary file"); + } + for (final WordProperty wordProperty : dict) { + // TODO: switch to addMultipleDictionaryEntries when they support shortcuts + if (null == wordProperty.mShortcutTargets || wordProperty.mShortcutTargets.isEmpty()) { + binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(), + null /* shortcutTarget */, 0 /* shortcutProbability */, + wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry, + 0 /* timestamp */); + } else { + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { + binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(), + shortcutTarget.mWord, shortcutTarget.getProbability(), + wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry, + 0 /* timestamp */); + } + } + if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDict.flushWithGC(); } } - - MakedictLog.i("Computing addresses..."); - BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions); - if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes); - - writeTerminalData(flatNodes, terminalCount); - if (formatOptions.mHasTimestamp) { - initUnigramTimestamps(terminalCount); - } - mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir, - formatOptions.mHasTimestamp); - writeBigrams(flatNodes, dict); - mShortcutWriter = new ShortcutContentWriter(mBaseFilename, terminalCount, mDictDir); - writeShortcuts(flatNodes); - - final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1); - final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize; - mTrieBuf = new byte[bufferSize]; - - MakedictLog.i("Writing file..."); - for (PtNodeArray nodeArray : flatNodes) { - BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions); - } - if (MakedictLog.DBG) { - BinaryDictEncoderUtils.showStatistics(flatNodes); - MakedictLog.i("has " + terminalCount + " terminals."); + for (final WordProperty word0Property : dict) { + if (null == word0Property.mBigrams) continue; + for (final WeightedString word1 : word0Property.mBigrams) { + binaryDict.addBigramWords(word0Property.mWord, word1.mWord, word1.getProbability(), + 0 /* timestamp */); + if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) { + binaryDict.flushWithGC(); + } + } } - mTrieOutStream.write(mTrieBuf); - - MakedictLog.i("Done"); - close(); + binaryDict.flushWithGC(); + binaryDict.close(); } @Override public void setPosition(int position) { - if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return; - mTriePos = position; } @Override public int getPosition() { - return mTriePos; + return 0; } @Override public void writePtNodeCount(int ptNodeCount) { - final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount); - // ptNodeCount must fit on one byte or two bytes. - // Please see comments in FormatSpec - if (countSize != 1 && countSize != 2) { - throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize); - } - final int encodedPtNodeCount = (countSize == 2) ? - (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount; - mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, encodedPtNodeCount, - countSize); - } - - private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) { - final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); - mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, - BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions), - FormatSpec.PTNODE_FLAGS_SIZE); - } - - private void writeParentPosition(int parentPos, final PtNode ptNode, - final FormatOptions formatOptions) { - if (parentPos != FormatSpec.NO_PARENT_ADDRESS) { - parentPos -= ptNode.mCachedAddressAfterUpdate; - } - mTriePos = BinaryDictEncoderUtils.writeParentAddress(mTrieBuf, mTriePos, parentPos, - formatOptions); - } - - private void writeCharacters(final int[] characters, final boolean hasSeveralChars) { - mTriePos = CharEncoding.writeCharArray(characters, mTrieBuf, mTriePos); - if (hasSeveralChars) { - mTrieBuf[mTriePos++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR; - } - } - - private void writeTerminalId(final int terminalId) { - mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, terminalId, - FormatSpec.PTNODE_TERMINAL_ID_SIZE); - } - - private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) { - final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); - if (formatOptions.mSupportsDynamicUpdate) { - mTriePos += BinaryDictEncoderUtils.writeSignedChildrenPosition(mTrieBuf, - mTriePos, childrenPos); - } else { - mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf, - mTriePos, childrenPos); - } - } - - private void writeBigrams(final ArrayList<PtNodeArray> flatNodes, final FusionDictionary dict) - throws IOException { - mBigramWriter.openStreams(); - for (final PtNodeArray nodeArray : flatNodes) { - for (final PtNode ptNode : nodeArray.mData) { - if (ptNode.mBigrams != null) { - mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, ptNode.mBigrams.size(), - ptNode.mBigrams.iterator(), dict); - } - } - } - mBigramWriter.closeStreams(); - } - - private void writeShortcuts(final ArrayList<PtNodeArray> flatNodes) throws IOException { - mShortcutWriter.openStreams(); - for (final PtNodeArray nodeArray : flatNodes) { - for (final PtNode ptNode : nodeArray.mData) { - if (ptNode.mShortcutTargets != null && !ptNode.mShortcutTargets.isEmpty()) { - mShortcutWriter.writeShortcutForOneWord(ptNode.mTerminalId, - ptNode.mShortcutTargets.iterator()); - } - } - } - mShortcutWriter.closeStreams(); } @Override public void writeForwardLinkAddress(int forwardLinkAddress) { - mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, - forwardLinkAddress, FormatSpec.FORWARD_LINK_ADDRESS_SIZE); } @Override - public void writePtNode(final PtNode ptNode, final int parentPosition, - final FormatOptions formatOptions, final FusionDictionary dict) { - writePtNodeFlags(ptNode, formatOptions); - writeParentPosition(parentPosition, ptNode, formatOptions); - writeCharacters(ptNode.mChars, ptNode.hasSeveralChars()); - if (ptNode.isTerminal()) { - writeTerminalId(ptNode.mTerminalId); - } - writeChildrenPosition(ptNode, formatOptions); - } - - private void writeTerminalData(final ArrayList<PtNodeArray> flatNodes, - final int terminalCount) throws IOException { - final byte[] freqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE]; - final byte[] terminalAddressTableBuf = - new byte[terminalCount * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE]; - for (final PtNodeArray nodeArray : flatNodes) { - for (final PtNode ptNode : nodeArray.mData) { - if (ptNode.isTerminal()) { - BinaryDictEncoderUtils.writeUIntToBuffer(freqBuf, - ptNode.mTerminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE, - ptNode.mFrequency, FormatSpec.FREQUENCY_AND_FLAGS_SIZE); - BinaryDictEncoderUtils.writeUIntToBuffer(terminalAddressTableBuf, - ptNode.mTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE, - ptNode.mCachedAddressAfterUpdate + mHeaderSize, - FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE); - } - } - } - mFreqOutStream.write(freqBuf); - mTerminalAddressTableOutStream.write(terminalAddressTableBuf); - } - - private void initUnigramTimestamps(final int terminalCount) throws IOException { - // Initial value of time stamps for each word is 0. - final byte[] unigramTimestampBuf = - new byte[terminalCount * FormatSpec.UNIGRAM_TIMESTAMP_SIZE]; - mUnigramTimestampOutStream.write(unigramTimestampBuf); + public void writePtNode(PtNode ptNode, FusionDictionary dict) { } } diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java deleted file mode 100644 index 3d8f186ba..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.makedict.FusionDictionary.WeightedString; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; - -/** - * An implementation of DictUpdater for version 4 binary dictionary. - */ -@UsedForTesting -public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater { - - @UsedForTesting - public Ver4DictUpdater(final File dictDirectory, final int factoryType) { - // DictUpdater must have an updatable DictBuffer. - super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY) - ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER); - } - - @Override - public void deleteWord(final String word) throws IOException, UnsupportedFormatException { - if (mDictBuffer == null) openDictBuffer(); - readHeader(); - final int wordPos = getTerminalPosition(word); - if (wordPos != FormatSpec.NOT_VALID_WORD) { - mDictBuffer.position(wordPos); - final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); - mDictBuffer.position(wordPos); - mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags)); - } - } - - @Override - public void insertWord(final String word, final int frequency, - final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts, - final boolean isNotAWord, final boolean isBlackListEntry) - throws IOException, UnsupportedFormatException { - // TODO: Implement this method. - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java deleted file mode 100644 index 0eabb7bf3..000000000 --- a/java/src/com/android/inputmethod/latin/makedict/Word.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2011 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.latin.makedict.FusionDictionary.WeightedString; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Utility class for a word with a frequency. - * - * This is chiefly used to iterate a dictionary. - */ -public final class Word implements Comparable<Word> { - public final String mWord; - public final int mFrequency; - public final ArrayList<WeightedString> mShortcutTargets; - public final ArrayList<WeightedString> mBigrams; - public final boolean mIsNotAWord; - public final boolean mIsBlacklistEntry; - - private int mHashCode = 0; - - public Word(final String word, final int frequency, - final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams, - final boolean isNotAWord, final boolean isBlacklistEntry) { - mWord = word; - mFrequency = frequency; - mShortcutTargets = shortcutTargets; - mBigrams = bigrams; - mIsNotAWord = isNotAWord; - mIsBlacklistEntry = isBlacklistEntry; - } - - private static int computeHashCode(Word word) { - return Arrays.hashCode(new Object[] { - word.mWord, - word.mFrequency, - word.mShortcutTargets.hashCode(), - word.mBigrams.hashCode(), - word.mIsNotAWord, - word.mIsBlacklistEntry - }); - } - - /** - * Three-way comparison. - * - * A Word x is greater than a word y if x has a higher frequency. If they have the same - * frequency, they are sorted in lexicographic order. - */ - @Override - public int compareTo(Word w) { - if (mFrequency < w.mFrequency) return 1; - if (mFrequency > w.mFrequency) return -1; - return mWord.compareTo(w.mWord); - } - - /** - * Equality test. - * - * Words are equal if they have the same frequency, the same spellings, and the same - * attributes. - */ - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof Word)) return false; - Word w = (Word)o; - return mFrequency == w.mFrequency && mWord.equals(w.mWord) - && mShortcutTargets.equals(w.mShortcutTargets) - && mBigrams.equals(w.mBigrams) - && mIsNotAWord == w.mIsNotAWord - && mIsBlacklistEntry == w.mIsBlacklistEntry; - } - - @Override - public int hashCode() { - if (mHashCode == 0) { - mHashCode = computeHashCode(this); - } - return mHashCode; - } -} diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java new file mode 100644 index 000000000..1fc61e10a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 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.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CombinedFormatUtils; +import com.android.inputmethod.latin.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Utility class for a word with a probability. + * + * This is chiefly used to iterate a dictionary. + */ +public final class WordProperty implements Comparable<WordProperty> { + public final String mWord; + public final ProbabilityInfo mProbabilityInfo; + public final ArrayList<WeightedString> mShortcutTargets; + public final ArrayList<WeightedString> mBigrams; + public final boolean mIsNotAWord; + public final boolean mIsBlacklistEntry; + public final boolean mHasShortcuts; + public final boolean mHasBigrams; + + private int mHashCode = 0; + + public WordProperty(final String word, final ProbabilityInfo probabilityInfo, + final ArrayList<WeightedString> shortcutTargets, + final ArrayList<WeightedString> bigrams, + final boolean isNotAWord, final boolean isBlacklistEntry) { + mWord = word; + mProbabilityInfo = probabilityInfo; + mShortcutTargets = shortcutTargets; + mBigrams = bigrams; + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklistEntry; + mHasBigrams = bigrams != null && !bigrams.isEmpty(); + mHasShortcuts = shortcutTargets != null && !shortcutTargets.isEmpty(); + } + + private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) { + return new ProbabilityInfo( + probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX], + probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX], + probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX], + probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]); + } + + // Construct word property using information from native code. + // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY. + public WordProperty(final int[] codePoints, final boolean isNotAWord, + final boolean isBlacklisted, final boolean hasBigram, + final boolean hasShortcuts, final int[] probabilityInfo, + final ArrayList<int[]> bigramTargets, final ArrayList<int[]> bigramProbabilityInfo, + final ArrayList<int[]> shortcutTargets, + final ArrayList<Integer> shortcutProbabilities) { + mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); + mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo); + mShortcutTargets = CollectionUtils.newArrayList(); + mBigrams = CollectionUtils.newArrayList(); + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklisted; + mHasShortcuts = hasShortcuts; + mHasBigrams = hasBigram; + + final int bigramTargetCount = bigramTargets.size(); + for (int i = 0; i < bigramTargetCount; i++) { + final String bigramTargetString = + StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i)); + mBigrams.add(new WeightedString(bigramTargetString, + createProbabilityInfoFromArray(bigramProbabilityInfo.get(i)))); + } + + final int shortcutTargetCount = shortcutTargets.size(); + for (int i = 0; i < shortcutTargetCount; i++) { + final String shortcutTargetString = + StringUtils.getStringFromNullTerminatedCodePointArray(shortcutTargets.get(i)); + mShortcutTargets.add( + new WeightedString(shortcutTargetString, shortcutProbabilities.get(i))); + } + } + + public int getProbability() { + return mProbabilityInfo.mProbability; + } + + private static int computeHashCode(WordProperty word) { + return Arrays.hashCode(new Object[] { + word.mWord, + word.mProbabilityInfo, + word.mShortcutTargets.hashCode(), + word.mBigrams.hashCode(), + word.mIsNotAWord, + word.mIsBlacklistEntry + }); + } + + /** + * Three-way comparison. + * + * A Word x is greater than a word y if x has a higher frequency. If they have the same + * frequency, they are sorted in lexicographic order. + */ + @Override + public int compareTo(final WordProperty w) { + if (getProbability() < w.getProbability()) return 1; + if (getProbability() > w.getProbability()) return -1; + return mWord.compareTo(w.mWord); + } + + /** + * Equality test. + * + * Words are equal if they have the same frequency, the same spellings, and the same + * attributes. + */ + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof WordProperty)) return false; + WordProperty w = (WordProperty)o; + return mProbabilityInfo.equals(w.mProbabilityInfo) && mWord.equals(w.mWord) + && mShortcutTargets.equals(w.mShortcutTargets) && mBigrams.equals(w.mBigrams) + && mIsNotAWord == w.mIsNotAWord && mIsBlacklistEntry == w.mIsBlacklistEntry + && mHasBigrams == w.mHasBigrams && mHasShortcuts && w.mHasBigrams; + } + + @Override + public int hashCode() { + if (mHashCode == 0) { + mHashCode = computeHashCode(this); + } + return mHashCode; + } + + @UsedForTesting + public boolean isValid() { + return getProbability() != BinaryDictionary.NOT_A_PROBABILITY; + } + + @Override + public String toString() { + return CombinedFormatUtils.formatWordProperty(this); + } +} |