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
17package com.android.inputmethod.latin.makedict;
18
19import com.android.inputmethod.annotations.UsedForTesting;
20import com.android.inputmethod.latin.define.DecoderSpecificConstants;
21
22import java.util.Date;
23import java.util.HashMap;
24
25/**
26 * Dictionary File Format Specification.
27 */
28public final class FormatSpec {
29
30    /*
31     * File header layout is as follows:
32     *
33     * v |
34     * e | MAGIC_NUMBER + version of the file format, 2 bytes.
35     * r |
36     * sion
37     *
38     * o |
39     * p | not used, 2 bytes.
40     * o |
41     * nflags
42     *
43     * h |
44     * e | size of the file header, 4bytes
45     * a |   including the size of the magic number, the option flags and the header size
46     * d |
47     * ersize
48     *
49     * attributes list
50     *
51     * attributes list is:
52     * <key>   = | string of characters at the char format described below, with the terminator used
53     *           | to signal the end of the string.
54     * <value> = | string of characters at the char format described below, with the terminator used
55     *           | to signal the end of the string.
56     * if the size of already read < headersize, goto key.
57     *
58     */
59
60    /*
61     * Node array (FusionDictionary.PtNodeArray) layout is as follows:
62     *
63     * n |
64     * o | the number of PtNodes, 1 or 2 bytes.
65     * d | 1 byte = bbbbbbbb match
66     * e |   case 1xxxxxxx => xxxxxxx << 8 + next byte
67     * c |   otherwise => bbbbbbbb
68     * o |
69     * unt
70     *
71     * n |
72     * o | sequence of PtNodes,
73     * d | the layout of each PtNode is described below.
74     * e |
75     * s
76     *
77     * f |
78     * o | forward link address, 3byte
79     * r | 1 byte = bbbbbbbb match
80     * w |   case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
81     * a |   otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
82     * r |
83     * dlinkaddress
84     */
85
86    /* Node (FusionDictionary.PtNode) layout is as follows:
87     *   | CHILDREN_ADDRESS_TYPE  2 bits, 11          : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
88     *   |                                10          : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
89     * f |                                01          : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
90     * l |                                00          : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
91     * a | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
92     * g | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
93     * s | has shortcut targets ?      1 bit, 1 = yes, 0 = no   : FLAG_HAS_SHORTCUT_TARGETS
94     *   | has bigrams ?               1 bit, 1 = yes, 0 = no   : FLAG_HAS_BIGRAMS
95     *   | is not a word ?             1 bit, 1 = yes, 0 = no   : FLAG_IS_NOT_A_WORD
96     *   | is possibly offensive ?     1 bit, 1 = yes, 0 = no   : FLAG_IS_POSSIBLY_OFFENSIVE
97     *
98     * c | IF FLAG_HAS_MULTIPLE_CHARS
99     * h |   char, char, char, char    n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
100     * a |   end                       1 byte, = 0
101     * r | ELSE
102     * s |   char                      1 or 3 bytes
103     *   | END
104     *
105     * f |
106     * r | IF FLAG_IS_TERMINAL
107     * e |   frequency                 1 byte
108     * q |
109     *
110     * c |
111     * h | children address, CHILDREN_ADDRESS_TYPE bytes
112     * i | This address is relative to the position of this field.
113     * l |
114     * drenaddress
115     *
116     *   | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
117     *   | shortcut string list
118     *   | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS
119     *   | bigrams address list
120     *
121     * Char format is:
122     * 1 byte = bbbbbbbb match
123     * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
124     * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
125     *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
126     *       00011111 would be outside unicode.
127     * else: iso-latin-1 code
128     * This allows for the whole unicode range to be encoded, including chars outside of
129     * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
130     * characters which should never happen anyway (and still work, but take 3 bytes).
131     *
132     * bigram address list is:
133     * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
134     *           | addressSign = 1 bit,                 : FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE
135     *           |                      1 = must take -address, 0 = must take +address
136     *           |                         xx : mask with MASK_BIGRAM_ATTR_ADDRESS_TYPE
137     *           | addressFormat = 2 bits, 00 = unused  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
138     *           |                         01 = 1 byte  : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE
139     *           |                         10 = 2 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES
140     *           |                         11 = 3 bytes : FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES
141     *           | 4 bits : frequency         : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
142     * <address> | IF (01 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE == addressFormat)
143     *           |   read 1 byte, add top 4 bits
144     *           | ELSIF (10 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES == addressFormat)
145     *           |   read 2 bytes, add top 4 bits
146     *           | ELSE // 11 == FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES == addressFormat
147     *           |   read 3 bytes, add top 4 bits
148     *           | END
149     *           | if (FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE) then address = -address
150     * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) goto bigram_and_shortcut_address_list_is
151     *
152     * shortcut string list is:
153     * <byte size> = PTNODE_SHORTCUT_LIST_SIZE_SIZE bytes, big-endian: size of the list, in bytes.
154     * <flags>     = | hasNext = 1 bit, 1 = yes, 0 = no : FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT
155     *               | reserved = 3 bits, must be 0
156     *               | 4 bits : frequency : mask with FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY
157     * <shortcut>  = | string of characters at the char format described above, with the terminator
158     *               | used to signal the end of the string.
159     * if (FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT goto flags
160     */
161
162    public static final int MAGIC_NUMBER = 0x9BC13AFE;
163    static final int NOT_A_VERSION_NUMBER = -1;
164
165    // These MUST have the same values as the relevant constants in format_utils.h.
166    // From version 2.01 on, we use version * 100 + revision as a version number. That allows
167    // us to change the format during development while having testing devices remove
168    // older files with each upgrade, while still having a readable versioning scheme.
169    // When we bump up the dictionary format version, we should update
170    // ExpandableDictionary.needsToMigrateDictionary() and
171    // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType().
172    public static final int VERSION2 = 2;
173    public static final int VERSION201 = 201;
174    public static final int VERSION202 = 202;
175    // format version for Fava Dictionaries.
176    public static final int VERSION_DELIGHT3 = 86736212;
177    public static final int VERSION402 = 402;
178    public static final int VERSION403 = 403;
179    public static final int VERSION4 = VERSION403;
180    public static final int MINIMUM_SUPPORTED_STATIC_VERSION = VERSION202;
181    public static final int MAXIMUM_SUPPORTED_STATIC_VERSION = VERSION_DELIGHT3;
182    static final int MINIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION4;
183    static final int MAXIMUM_SUPPORTED_DYNAMIC_VERSION = VERSION403;
184
185    // TODO: Make this value adaptative to content data, store it in the header, and
186    // use it in the reading code.
187    static final int MAX_WORD_LENGTH = DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH;
188
189    // These flags are used only in the static dictionary.
190    static final int MASK_CHILDREN_ADDRESS_TYPE = 0xC0;
191    static final int FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS = 0x00;
192    static final int FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE = 0x40;
193    static final int FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES = 0x80;
194    static final int FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = 0xC0;
195
196    static final int FLAG_HAS_MULTIPLE_CHARS = 0x20;
197
198    static final int FLAG_IS_TERMINAL = 0x10;
199    static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
200    static final int FLAG_HAS_BIGRAMS = 0x04;
201    static final int FLAG_IS_NOT_A_WORD = 0x02;
202    static final int FLAG_IS_POSSIBLY_OFFENSIVE = 0x01;
203
204    static final int FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT = 0x80;
205    static final int FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE = 0x40;
206    static final int MASK_BIGRAM_ATTR_ADDRESS_TYPE = 0x30;
207    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE = 0x10;
208    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES = 0x20;
209    static final int FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES = 0x30;
210    static final int FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY = 0x0F;
211
212    static final int PTNODE_CHARACTERS_TERMINATOR = 0x1F;
213
214    static final int PTNODE_TERMINATOR_SIZE = 1;
215    static final int PTNODE_FLAGS_SIZE = 1;
216    static final int PTNODE_FREQUENCY_SIZE = 1;
217    static final int PTNODE_MAX_ADDRESS_SIZE = 3;
218    static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
219    static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
220    static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
221
222    static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
223    static final int INVALID_CHARACTER = -1;
224
225    static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
226    // Large PtNode array size field size is 2 bytes.
227    static final int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG = 0x8000;
228    static final int MAX_PTNODES_IN_A_PT_NODE_ARRAY = 0x7FFF; // 32767
229    static final int MAX_BIGRAMS_IN_A_PTNODE = 10000;
230    static final int MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE = 0xFFFF;
231
232    static final int MAX_TERMINAL_FREQUENCY = 255;
233    static final int MAX_BIGRAM_FREQUENCY = 15;
234
235    public static final int SHORTCUT_WHITELIST_FREQUENCY = 15;
236
237    // This option needs to be the same numeric value as the one in binary_format.h.
238    static final int NOT_VALID_WORD = -99;
239
240    static final int UINT8_MAX = 0xFF;
241    static final int UINT16_MAX = 0xFFFF;
242    static final int UINT24_MAX = 0xFFFFFF;
243    static final int MSB8 = 0x80;
244    static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
245    static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
246
247    /**
248     * Options about file format.
249     */
250    public static final class FormatOptions {
251        public final int mVersion;
252        public final boolean mHasTimestamp;
253
254        @UsedForTesting
255        public FormatOptions(final int version) {
256            this(version, false /* hasTimestamp */);
257        }
258
259        public FormatOptions(final int version, final boolean hasTimestamp) {
260            mVersion = version;
261            mHasTimestamp = hasTimestamp;
262        }
263    }
264
265    /**
266     * Options global to the dictionary.
267     */
268    public static final class DictionaryOptions {
269        public final HashMap<String, String> mAttributes;
270        public DictionaryOptions(final HashMap<String, String> attributes) {
271            mAttributes = attributes;
272        }
273        @Override
274        public String toString() { // Convenience method
275            return toString(0, false);
276        }
277        public String toString(final int indentCount, final boolean plumbing) {
278            final StringBuilder indent = new StringBuilder();
279            if (plumbing) {
280                indent.append("H:");
281            } else {
282                for (int i = 0; i < indentCount; ++i) {
283                    indent.append(" ");
284                }
285            }
286            final StringBuilder s = new StringBuilder();
287            for (final String optionKey : mAttributes.keySet()) {
288                s.append(indent);
289                s.append(optionKey);
290                s.append(" = ");
291                if ("date".equals(optionKey) && !plumbing) {
292                    // Date needs a number of milliseconds, but the dictionary contains seconds
293                    s.append(new Date(
294                            1000 * Long.parseLong(mAttributes.get(optionKey))).toString());
295                } else {
296                    s.append(mAttributes.get(optionKey));
297                }
298                s.append("\n");
299            }
300            return s.toString();
301        }
302    }
303
304    private FormatSpec() {
305        // This utility class is not publicly instantiable.
306    }
307}
308