Home | History | Annotate | Download | only in makedict
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin.makedict;
     18 
     19 import com.android.inputmethod.annotations.UsedForTesting;
     20 import com.android.inputmethod.latin.Constants;
     21 import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
     22 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
     23 
     24 import java.io.File;
     25 
     26 /**
     27  * Dictionary File Format Specification.
     28  */
     29 public final class FormatSpec {
     30 
     31     /*
     32      * File header layout is as follows:
     33      *
     34      * v |
     35      * e | MAGIC_NUMBER + version of the file format, 2 bytes.
     36      * r |
     37      * sion
     38      *
     39      * o |
     40      * p | not used                                3 bits
     41      * t | each unigram and bigram entry has a time stamp?
     42      * i |                                         1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
     43      * o | has bigrams ?                           1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
     44      * n | FRENCH_LIGATURE_PROCESSING_FLAG
     45      * f | supports dynamic updates ?              1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
     46      * l | GERMAN_UMLAUT_PROCESSING_FLAG
     47      * a |
     48      * gs
     49      *
     50      * h |
     51      * e | size of the file header, 4bytes
     52      * a |   including the size of the magic number, the option flags and the header size
     53      * d |
     54      * ersize
     55      *
     56      *   | attributes list
     57      *
     58      * attributes list is:
     59      * <key>   = | string of characters at the char format described below, with the terminator used
     60      *           | to signal the end of the string.
     61      * <value> = | string of characters at the char format described below, with the terminator used
     62      *           | to signal the end of the string.
     63      * if the size of already read < headersize, goto key.
     64      *
     65      */
     66 
     67     /*
     68      * Node array (FusionDictionary.PtNodeArray) layout is as follows:
     69      *
     70      * n |
     71      * o | the number of PtNodes, 1 or 2 bytes.
     72      * d | 1 byte = bbbbbbbb match
     73      * e |   case 1xxxxxxx => xxxxxxx << 8 + next byte
     74      * c |   otherwise => bbbbbbbb
     75      * o |
     76      * unt
     77      *
     78      * n |
     79      * o | sequence of PtNodes,
     80      * d | the layout of each PtNode is described below.
     81      * e |
     82      * s
     83      *
     84      * f |
     85      * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
     86      * r |     forward link address, 3byte
     87      * w | 1 byte = bbbbbbbb match
     88      * a |   case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
     89      * r |   otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
     90      * d |
     91      * linkaddress
     92      */
     93 
     94     /* Node (FusionDictionary.PtNode) layout is as follows:
     95      *   | IF !SUPPORTS_DYNAMIC_UPDATE
     96      *   |   addressType                    xx               : mask with MASK_CHILDREN_ADDRESS_TYPE
     97      *   |                          2 bits, 00 = no children : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
     98      * f |                                  01 = 1 byte      : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
     99      * l |                                  10 = 2 bytes     : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
    100      * a |                                  11 = 3 bytes     : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
    101      * g | ELSE
    102      * s |   is moved ?             2 bits, 11 = no          : FLAG_IS_NOT_MOVED
    103      *   |                            This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
    104      *   |                                  01 = yes         : FLAG_IS_MOVED
    105      *   |                        the new address is stored in the same place as the parent address
    106      *   |   is deleted?                    10 = yes         : FLAG_IS_DELETED
    107      *   | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
    108      *   | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
    109      *   | has shortcut targets ?      1 bit, 1 = yes, 0 = no   : FLAG_HAS_SHORTCUT_TARGETS
    110      *   | has bigrams ?               1 bit, 1 = yes, 0 = no   : FLAG_HAS_BIGRAMS
    111      *   | is not a word ?             1 bit, 1 = yes, 0 = no   : FLAG_IS_NOT_A_WORD
    112      *   | is blacklisted ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_BLACKLISTED
    113      *
    114      * p |
    115      * a | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
    116      * r |     parent address, 3byte
    117      * e | 1 byte = bbbbbbbb match
    118      * n |   case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
    119      * t |   otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
    120      * a | This address is relative to the head of the PtNode.
    121      * d | If the node doesn't have a parent, this field is set to 0.
    122      * d |
    123      * ress
    124      *
    125      * c | IF FLAG_HAS_MULTIPLE_CHARS
    126      * h |   char, char, char, char    n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
    127      * a |   end                       1 byte, = 0
    128      * r | ELSE
    129      * s |   char                      1 or 3 bytes
    130      *   | END
    131      *
    132      * f |
    133      * r | IF FLAG_IS_TERMINAL
    134      * e |   frequency                 1 byte
    135      * q |
    136      *
    137      * c | IF SUPPORTS_DYNAMIC_UPDATE
    138      * h |   children address, 3 bytes
    139      * i |   1 byte = bbbbbbbb match
    140      * l |     case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
    141      * d |     otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
    142      * r |   if this node doesn't have children, this field is set to 0.
    143      * e |     (see BinaryDictEncoderUtils#writeVariableSignedAddress)
    144      * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
    145      * a |   // nothing
    146      * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
    147      * d |   children address, 1 byte
    148      * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
    149      * e |   children address, 2 bytes
    150      * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
    151      * s |   children address, 3 bytes
    152      *   | END
    153      *   | This address is relative to the position of this field.
    154      *
    155      *   | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
    156      *   | shortcut string list
    157      *   | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS
    158      *   | bigrams address list
    159      *
    160      * Char format is:
    161      * 1 byte = bbbbbbbb match
    162      * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
    163      * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
    164      *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
    165      *       00011111 would be outside unicode.
    166      * else: iso-latin-1 code
    167      * This allows for the whole unicode range to be encoded, including chars outside of
    168      * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
    169      * characters which should never happen anyway (and still work, but take 3 bytes).
    170      *
    171      * bigram address list is:
    172      * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
    173      *           | addressSign = 1 bit,                 : FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE
    174      *           |                      1 = must take -address, 0 = must take +address
    175      *           |                         xx : mask with MASK_BIGRAM_ATTR_ADDRESS_TYPE
    176      *           | addressFormat = 2 bits, 00 = unused  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
    177      *           |                         01 = 1 byte  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
    178      *           |                         10 = 2 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES
    179      *           |                         11 = 3 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES
    180      *           | 4 bits : frequency         : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
    181      * <address> | IF (01 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE == addressFormat)
    182      *           |   read 1 byte, add top 4 bits
    183      *           | ELSIF (10 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES == addressFormat)
    184      *           |   read 2 bytes, add top 4 bits
    185      *           | ELSE // 11 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES == addressFormat
    186      *           |   read 3 bytes, add top 4 bits
    187      *           | END
    188      *           | if (FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE) then address = -address
    189      * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) goto bigram_and_shortcut_address_list_is
    190      *
    191      * shortcut string list is:
    192      * <byte size> = PTNODE_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes.
    193      * <flags>     = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
    194      *               | reserved = 3 bits, must be 0
    195      *               | 4 bits : frequency : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
    196      * <shortcut>  = | string of characters at the char format described above, with the terminator
    197      *               | used to signal the end of the string.
    198      * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT goto flags
    199      */
    200 
    201     public static final int MAGIC_NUMBER = 0x9BC13AFE;
    202     static final int MINIMUM_SUPPORTED_VERSION = 2;
    203     static final int MAXIMUM_SUPPORTED_VERSION = 4;
    204     static final int NOT_A_VERSION_NUMBER = -1;
    205     static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
    206     static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
    207     static final int VERSION3 = 3;
    208     static final int VERSION4 = 4;
    209 
    210     // These options need to be the same numeric values as the one in the native reading code.
    211     static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
    212     // TODO: Make the native reading code read this variable.
    213     static final int SUPPORTS_DYNAMIC_UPDATE = 0x2;
    214     static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
    215     static final int CONTAINS_BIGRAMS_FLAG = 0x8;
    216     static final int CONTAINS_TIMESTAMP_FLAG = 0x10;
    217 
    218     // TODO: Make this value adaptative to content data, store it in the header, and
    219     // use it in the reading code.
    220     static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
    221 
    222     static final int PARENT_ADDRESS_SIZE = 3;
    223     static final int FORWARD_LINK_ADDRESS_SIZE = 3;
    224 
    225     // These flags are used only in the static dictionary.
    226     static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
    227     static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00;
    228     static final int FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE = 0x40;
    229     static final int FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES = 0x80;
    230     static final int FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = 0xC0;
    231 
    232     static final int FLAG_HAS_MULTIPLE_CHARS = 0x20;
    233 
    234     static final int FLAG_IS_TERMINAL = 0x10;
    235     static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
    236     static final int FLAG_HAS_BIGRAMS = 0x04;
    237     static final int FLAG_IS_NOT_A_WORD = 0x02;
    238     static final int FLAG_IS_BLACKLISTED = 0x01;
    239 
    240     // These flags are used only in the dynamic dictionary.
    241     static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
    242     static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
    243     static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
    244     static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
    245     static final int FLAG_IS_DELETED = 0x80;
    246 
    247     static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
    248     static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
    249     static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30;
    250     static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE = 0x10;
    251     static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES = 0x20;
    252     static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES = 0x30;
    253     static final int FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY = 0x0F;
    254 
    255     static final int PTNODE_CHARACTERS_TERMINATOR = 0x1F;
    256 
    257     static final int PTNODE_TERMINATOR_SIZE = 1;
    258     static final int PTNODE_FLAGS_SIZE = 1;
    259     static final int PTNODE_FREQUENCY_SIZE = 1;
    260     static final int PTNODE_TERMINAL_ID_SIZE = 4;
    261     static final int PTNODE_MAX_ADDRESS_SIZE = 3;
    262     static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
    263     static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
    264     static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
    265 
    266     // These values are used only by version 4 or later.
    267     static final String TRIE_FILE_EXTENSION = ".trie";
    268     static final String FREQ_FILE_EXTENSION = ".freq";
    269     static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp";
    270     // tat = Terminal Address Table
    271     static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
    272     static final String BIGRAM_FILE_EXTENSION = ".bigram";
    273     static final String SHORTCUT_FILE_EXTENSION = ".shortcut";
    274     static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup";
    275     static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
    276     static final int FREQUENCY_AND_FLAGS_SIZE = 2;
    277     static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
    278     static final int UNIGRAM_TIMESTAMP_SIZE = 4;
    279 
    280     // With the English main dictionary as of October 2013, the size of bigram address table is
    281     // is 584KB with the block size being 4.
    282     // This is 91% of that of full address table.
    283     static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
    284     static final int BIGRAM_CONTENT_COUNT = 2;
    285     static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
    286     static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1;
    287     static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
    288     static final String BIGRAM_TIMESTAMP_CONTENT_ID = "_timestamp";
    289     static final int BIGRAM_TIMESTAMP_SIZE = 4;
    290     static final int BIGRAM_COUNTER_SIZE = 1;
    291     static final int BIGRAM_LEVEL_SIZE = 1;
    292 
    293     static final int SHORTCUT_CONTENT_COUNT = 1;
    294     static final int SHORTCUT_CONTENT_INDEX = 0;
    295     // With the English main dictionary as of October 2013, the size of shortcut address table is
    296     // 29KB with the block size being 64.
    297     // This is only 4.4% of that of full address table.
    298     static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
    299     static final String SHORTCUT_CONTENT_ID = "_shortcut";
    300 
    301     static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
    302     static final int NO_PARENT_ADDRESS = 0;
    303     static final int NO_FORWARD_LINK_ADDRESS = 0;
    304     static final int INVALID_CHARACTER = -1;
    305 
    306     static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
    307     // Large PtNode array size field size is 2 bytes.
    308     static final int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG = 0x8000;
    309     static final int MAX_PTNODES_IN_A_PT_NODE_ARRAY = 0x7FFF; // 32767
    310     static final int MAX_BIGRAMS_IN_A_PTNODE = 10000;
    311     static final int MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE = 0xFFFF;
    312 
    313     static final int MAX_TERMINAL_FREQUENCY = 255;
    314     static final int MAX_BIGRAM_FREQUENCY = 15;
    315 
    316     public static final int SHORTCUT_WHITELIST_FREQUENCY = 15;
    317 
    318     // This option needs to be the same numeric value as the one in binary_format.h.
    319     static final int NOT_VALID_WORD = -99;
    320     static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
    321 
    322     static final int UINT8_MAX = 0xFF;
    323     static final int UINT16_MAX = 0xFFFF;
    324     static final int UINT24_MAX = 0xFFFFFF;
    325     static final int SINT24_MAX = 0x7FFFFF;
    326     static final int MSB8 = 0x80;
    327     static final int MSB24 = 0x800000;
    328 
    329     /**
    330      * Options about file format.
    331      */
    332     public static final class FormatOptions {
    333         public final int mVersion;
    334         public final boolean mSupportsDynamicUpdate;
    335         public final boolean mHasTerminalId;
    336         public final boolean mHasTimestamp;
    337         @UsedForTesting
    338         public FormatOptions(final int version) {
    339             this(version, false);
    340         }
    341 
    342         @UsedForTesting
    343         public FormatOptions(final int version, final boolean supportsDynamicUpdate) {
    344             this(version, supportsDynamicUpdate, false /* hasTimestamp */);
    345         }
    346 
    347         public FormatOptions(final int version, final boolean supportsDynamicUpdate,
    348                 final boolean hasTimestamp) {
    349             mVersion = version;
    350             if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) {
    351                 throw new RuntimeException("Dynamic updates are only supported with versions "
    352                         + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
    353             }
    354             mSupportsDynamicUpdate = supportsDynamicUpdate;
    355             mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
    356             mHasTimestamp = hasTimestamp;
    357         }
    358     }
    359 
    360     /**
    361      * Class representing file header.
    362      */
    363     public static final class FileHeader {
    364         public final int mHeaderSize;
    365         public final DictionaryOptions mDictionaryOptions;
    366         public final FormatOptions mFormatOptions;
    367         // Note that these are corresponding definitions in native code in latinime::HeaderPolicy
    368         // and latinime::HeaderReadWriteUtils.
    369         public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE";
    370         public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
    371         public static final String ATTRIBUTE_VALUE_TRUE = "1";
    372 
    373         public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
    374         public static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
    375         public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
    376         private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
    377         public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
    378                 final FormatOptions formatOptions) {
    379             mHeaderSize = headerSize;
    380             mDictionaryOptions = dictionaryOptions;
    381             mFormatOptions = formatOptions;
    382         }
    383 
    384         // Helper method to get the locale as a String
    385         public String getLocaleString() {
    386             return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE);
    387         }
    388 
    389         // Helper method to get the version String
    390         public String getVersion() {
    391             return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE);
    392         }
    393 
    394         // Helper method to get the dictionary ID as a String
    395         public String getId() {
    396             return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE);
    397         }
    398 
    399         // Helper method to get the description
    400         public String getDescription() {
    401             // TODO: Right now each dictionary file comes with a description in its own language.
    402             // It will display as is no matter the device's locale. It should be internationalized.
    403             return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE);
    404         }
    405     }
    406 
    407     /**
    408      * Returns new dictionary decoder.
    409      *
    410      * @param dictFile the dictionary file.
    411      * @param bufferType The type of buffer, as one of USE_* in DictDecoder.
    412      * @return new dictionary decoder if the dictionary file exists, otherwise null.
    413      */
    414     public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
    415         if (dictFile.isDirectory()) {
    416             return new Ver4DictDecoder(dictFile, bufferType);
    417         } else if (dictFile.isFile()) {
    418             return new Ver3DictDecoder(dictFile, bufferType);
    419         }
    420         return null;
    421     }
    422 
    423     public static DictDecoder getDictDecoder(final File dictFile,
    424             final DictionaryBufferFactory factory) {
    425         if (dictFile.isDirectory()) {
    426             return new Ver4DictDecoder(dictFile, factory);
    427         } else if (dictFile.isFile()) {
    428             return new Ver3DictDecoder(dictFile, factory);
    429         }
    430         return null;
    431     }
    432 
    433     public static DictDecoder getDictDecoder(final File dictFile) {
    434         return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER);
    435     }
    436 
    437     private FormatSpec() {
    438         // This utility class is not publicly instantiable.
    439     }
    440 }
    441