1 /* 2 * Copyright (C) 2013 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.BinaryDictDecoderUtils.DictBuffer; 22 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; 23 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; 24 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; 25 import com.android.inputmethod.latin.utils.CollectionUtils; 26 27 import java.io.IOException; 28 import java.io.OutputStream; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 32 /** 33 * The utility class to help dynamic updates on the binary dictionary. 34 * 35 * All the methods in this class are static. 36 */ 37 @UsedForTesting 38 public final class DynamicBinaryDictIOUtils { 39 private static final boolean DBG = false; 40 private static final int MAX_JUMPS = 10000; 41 DynamicBinaryDictIOUtils()42 private DynamicBinaryDictIOUtils() { 43 // This utility class is not publicly instantiable. 44 } 45 markAsDeleted(final int flags)46 /* package */ static int markAsDeleted(final int flags) { 47 return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED; 48 } 49 50 /** 51 * Update a parent address in a PtNode that is referred to by ptNodeOriginAddress. 52 * 53 * @param dictUpdater the DictUpdater to write. 54 * @param ptNodeOriginAddress the address of the PtNode. 55 * @param newParentAddress the absolute address of the parent. 56 * @param formatOptions file format options. 57 */ updateParentAddress(final Ver3DictUpdater dictUpdater, final int ptNodeOriginAddress, final int newParentAddress, final FormatOptions formatOptions)58 private static void updateParentAddress(final Ver3DictUpdater dictUpdater, 59 final int ptNodeOriginAddress, final int newParentAddress, 60 final FormatOptions formatOptions) { 61 final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); 62 final int originalPosition = dictBuffer.position(); 63 dictBuffer.position(ptNodeOriginAddress); 64 if (!formatOptions.mSupportsDynamicUpdate) { 65 throw new RuntimeException("this file format does not support parent addresses"); 66 } 67 final int flags = dictBuffer.readUnsignedByte(); 68 if (BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) { 69 // If the node is moved, the parent address is stored in the destination node. 70 // We are guaranteed to process the destination node later, so there is no need to 71 // update anything here. 72 dictBuffer.position(originalPosition); 73 return; 74 } 75 if (DBG) { 76 MakedictLog.d("update parent address flags=" + flags + ", " + ptNodeOriginAddress); 77 } 78 final int parentOffset = newParentAddress - ptNodeOriginAddress; 79 BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, parentOffset); 80 dictBuffer.position(originalPosition); 81 } 82 83 /** 84 * Update parent addresses in a node array stored at ptNodeOriginAddress. 85 * 86 * @param dictUpdater the DictUpdater to be modified. 87 * @param ptNodeOriginAddress the address of the node array to update. 88 * @param newParentAddress the address to be written. 89 * @param formatOptions file format options. 90 */ updateParentAddresses(final Ver3DictUpdater dictUpdater, final int ptNodeOriginAddress, final int newParentAddress, final FormatOptions formatOptions)91 private static void updateParentAddresses(final Ver3DictUpdater dictUpdater, 92 final int ptNodeOriginAddress, final int newParentAddress, 93 final FormatOptions formatOptions) { 94 final int originalPosition = dictUpdater.getPosition(); 95 dictUpdater.setPosition(ptNodeOriginAddress); 96 do { 97 final int count = dictUpdater.readPtNodeCount(); 98 for (int i = 0; i < count; ++i) { 99 updateParentAddress(dictUpdater, dictUpdater.getPosition(), newParentAddress, 100 formatOptions); 101 dictUpdater.skipPtNode(formatOptions); 102 } 103 if (!dictUpdater.readAndFollowForwardLink()) break; 104 if (dictUpdater.getPosition() == FormatSpec.NO_FORWARD_LINK_ADDRESS) break; 105 } while (formatOptions.mSupportsDynamicUpdate); 106 dictUpdater.setPosition(originalPosition); 107 } 108 109 /** 110 * Update a children address in a PtNode that is addressed by ptNodeOriginAddress. 111 * 112 * @param dictUpdater the DictUpdater to write. 113 * @param ptNodeOriginAddress the address of the PtNode. 114 * @param newChildrenAddress the absolute address of the child. 115 * @param formatOptions file format options. 116 */ updateChildrenAddress(final Ver3DictUpdater dictUpdater, final int ptNodeOriginAddress, final int newChildrenAddress, final FormatOptions formatOptions)117 private static void updateChildrenAddress(final Ver3DictUpdater dictUpdater, 118 final int ptNodeOriginAddress, final int newChildrenAddress, 119 final FormatOptions formatOptions) { 120 final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); 121 final int originalPosition = dictBuffer.position(); 122 dictBuffer.position(ptNodeOriginAddress); 123 final int flags = dictBuffer.readUnsignedByte(); 124 BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions); 125 BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); 126 if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte(); 127 final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS 128 ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - dictBuffer.position(); 129 BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, childrenOffset); 130 dictBuffer.position(originalPosition); 131 } 132 133 /** 134 * Helper method to move a PtNode to the tail of the file. 135 */ movePtNode(final OutputStream destination, final Ver3DictUpdater dictUpdater, final PtNodeInfo info, final int nodeArrayOriginAddress, final int oldNodeAddress, final FormatOptions formatOptions)136 private static int movePtNode(final OutputStream destination, 137 final Ver3DictUpdater dictUpdater, final PtNodeInfo info, 138 final int nodeArrayOriginAddress, final int oldNodeAddress, 139 final FormatOptions formatOptions) throws IOException { 140 final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); 141 updateParentAddress(dictUpdater, oldNodeAddress, dictBuffer.limit() + 1, formatOptions); 142 dictBuffer.position(oldNodeAddress); 143 final int currentFlags = dictBuffer.readUnsignedByte(); 144 dictBuffer.position(oldNodeAddress); 145 dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags 146 & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG)))); 147 int size = FormatSpec.PTNODE_FLAGS_SIZE; 148 updateForwardLink(dictUpdater, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions); 149 size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info }); 150 return size; 151 } 152 153 @SuppressWarnings("unused") updateForwardLink(final Ver3DictUpdater dictUpdater, final int nodeArrayOriginAddress, final int newNodeArrayAddress, final FormatOptions formatOptions)154 private static void updateForwardLink(final Ver3DictUpdater dictUpdater, 155 final int nodeArrayOriginAddress, final int newNodeArrayAddress, 156 final FormatOptions formatOptions) { 157 final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); 158 dictUpdater.setPosition(nodeArrayOriginAddress); 159 int jumpCount = 0; 160 while (jumpCount++ < MAX_JUMPS) { 161 final int count = dictUpdater.readPtNodeCount(); 162 for (int i = 0; i < count; ++i) { 163 dictUpdater.readPtNode(dictUpdater.getPosition(), formatOptions); 164 } 165 final int forwardLinkAddress = dictBuffer.readUnsignedInt24(); 166 if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) { 167 dictBuffer.position(dictBuffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE); 168 BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeArrayAddress); 169 return; 170 } 171 dictBuffer.position(forwardLinkAddress); 172 } 173 if (DBG && jumpCount >= MAX_JUMPS) { 174 throw new RuntimeException("too many jumps, probably a bug."); 175 } 176 } 177 178 /** 179 * Move a PtNode that is referred to by oldPtNodeOrigin to the tail of the file, and set the 180 * children address to the byte after the PtNode. 181 * 182 * @param fileEndAddress the address of the tail of the file. 183 * @param codePoints the characters to put inside the PtNode. 184 * @param length how many code points to read from codePoints. 185 * @param flags the flags for this PtNode. 186 * @param frequency the frequency of this terminal. 187 * @param parentAddress the address of the parent PtNode of this PtNode. 188 * @param shortcutTargets the shortcut targets for this PtNode. 189 * @param bigrams the bigrams for this PtNode. 190 * @param destination the stream representing the tail of the file. 191 * @param dictUpdater the DictUpdater. 192 * @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of. 193 * @param oldPtNodeOrigin the old origin where this PtNode used to be stored. 194 * @param formatOptions format options for this dictionary. 195 * @return the size written, in bytes. 196 * @throws IOException if the file can't be accessed 197 */ 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)198 private static int movePtNode(final int fileEndAddress, final int[] codePoints, 199 final int length, final int flags, final int frequency, final int parentAddress, 200 final ArrayList<WeightedString> shortcutTargets, 201 final ArrayList<PendingAttribute> bigrams, final OutputStream destination, 202 final Ver3DictUpdater dictUpdater, final int oldPtNodeArrayOrigin, 203 final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException { 204 int size = 0; 205 final int newPtNodeOrigin = fileEndAddress + 1; 206 final int[] writtenCharacters = Arrays.copyOfRange(codePoints, 0, length); 207 final PtNodeInfo tmpInfo = new PtNodeInfo(newPtNodeOrigin, -1 /* endAddress */, 208 flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS, 209 shortcutTargets, bigrams); 210 size = BinaryDictIOUtils.computePtNodeSize(tmpInfo, formatOptions); 211 final PtNodeInfo newInfo = new PtNodeInfo(newPtNodeOrigin, newPtNodeOrigin + size, 212 flags, writtenCharacters, frequency, parentAddress, 213 fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets, 214 bigrams); 215 movePtNode(destination, dictUpdater, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin, 216 formatOptions); 217 return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; 218 } 219 220 /** 221 * Converts a list of WeightedString to a list of PendingAttribute. 222 */ resolveBigramPositions(final DictUpdater dictUpdater, final ArrayList<WeightedString> bigramStrings)223 public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater, 224 final ArrayList<WeightedString> bigramStrings) 225 throws IOException, UnsupportedFormatException { 226 if (bigramStrings == null) return CollectionUtils.newArrayList(); 227 final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList(); 228 for (final WeightedString bigram : bigramStrings) { 229 final int pos = dictUpdater.getTerminalPosition(bigram.mWord); 230 if (pos == FormatSpec.NOT_VALID_WORD) { 231 // TODO: figure out what is the correct thing to do here. 232 } else { 233 bigrams.add(new PendingAttribute(bigram.mFrequency, pos)); 234 } 235 } 236 return bigrams; 237 } 238 239 /** 240 * Insert a word into a binary dictionary. 241 * 242 * @param dictUpdater the dict updater. 243 * @param destination a stream to the underlying file, with the pointer at the end of the file. 244 * @param word the word to insert. 245 * @param frequency the frequency of the new word. 246 * @param bigramStrings bigram list, or null if none. 247 * @param shortcuts shortcut list, or null if none. 248 * @param isBlackListEntry whether this should be a blacklist entry. 249 * @throws IOException if the file can't be accessed. 250 * @throws UnsupportedFormatException if the existing dictionary is in an unexpected format. 251 */ 252 // TODO: Support batch insertion. 253 // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary. 254 @UsedForTesting 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)255 public static void insertWord(final Ver3DictUpdater dictUpdater, 256 final OutputStream destination, final String word, final int frequency, 257 final ArrayList<WeightedString> bigramStrings, 258 final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, 259 final boolean isBlackListEntry) 260 throws IOException, UnsupportedFormatException { 261 final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater, 262 bigramStrings); 263 final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); 264 265 final boolean isTerminal = true; 266 final boolean hasBigrams = !bigrams.isEmpty(); 267 final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty(); 268 269 // find the insert position of the word. 270 if (dictBuffer.position() != 0) dictBuffer.position(0); 271 final FileHeader fileHeader = dictUpdater.readHeader(); 272 273 int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position(); 274 final int[] codePoints = FusionDictionary.getCodePoints(word); 275 final int wordLen = codePoints.length; 276 277 for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) { 278 if (wordPos >= wordLen) break; 279 nodeOriginAddress = dictBuffer.position(); 280 int nodeParentAddress = -1; 281 final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer); 282 boolean foundNextNode = false; 283 284 for (int i = 0; i < ptNodeCount; ++i) { 285 address = dictBuffer.position(); 286 final PtNodeInfo currentInfo = dictUpdater.readPtNode(address, 287 fileHeader.mFormatOptions); 288 final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, 289 fileHeader.mFormatOptions); 290 if (isMovedNode) continue; 291 nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) 292 ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address; 293 boolean matched = true; 294 for (int p = 0; p < currentInfo.mCharacters.length; ++p) { 295 if (wordPos + p >= wordLen) { 296 /* 297 * splitting 298 * before 299 * abcd - ef 300 * 301 * insert "abc" 302 * 303 * after 304 * abc - d - ef 305 */ 306 final int newNodeAddress = dictBuffer.limit(); 307 final int flags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1, 308 isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */, 309 false /* isBlackListEntry */, fileHeader.mFormatOptions); 310 int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags, 311 frequency, nodeParentAddress, shortcuts, bigrams, destination, 312 dictUpdater, nodeOriginAddress, address, fileHeader.mFormatOptions); 313 314 final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p, 315 currentInfo.mCharacters.length); 316 if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { 317 updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress, 318 newNodeAddress + written + 1, fileHeader.mFormatOptions); 319 } 320 final PtNodeInfo newInfo2 = new PtNodeInfo( 321 newNodeAddress + written + 1, -1 /* endAddress */, 322 currentInfo.mFlags, characters2, currentInfo.mFrequency, 323 newNodeAddress + 1, currentInfo.mChildrenAddress, 324 currentInfo.mShortcutTargets, currentInfo.mBigrams); 325 BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo2 }); 326 return; 327 } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) { 328 if (p > 0) { 329 /* 330 * splitting 331 * before 332 * ab - cd 333 * 334 * insert "ac" 335 * 336 * after 337 * a - b - cd 338 * | 339 * - c 340 */ 341 342 final int newNodeAddress = dictBuffer.limit(); 343 final int childrenAddress = currentInfo.mChildrenAddress; 344 345 // move prefix 346 final int prefixFlags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1, 347 false /* isTerminal */, 0 /* childrenAddressSize*/, 348 false /* hasShortcut */, false /* hasBigrams */, 349 false /* isNotAWord */, false /* isBlackListEntry */, 350 fileHeader.mFormatOptions); 351 int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, 352 prefixFlags, -1 /* frequency */, nodeParentAddress, null, null, 353 destination, dictUpdater, nodeOriginAddress, address, 354 fileHeader.mFormatOptions); 355 356 final int[] suffixCharacters = Arrays.copyOfRange( 357 currentInfo.mCharacters, p, currentInfo.mCharacters.length); 358 if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { 359 updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress, 360 newNodeAddress + written + 1, fileHeader.mFormatOptions); 361 } 362 final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags( 363 suffixCharacters.length > 1, 364 (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0, 365 0 /* childrenAddressSize */, 366 (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) 367 != 0, 368 (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0, 369 isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); 370 final PtNodeInfo suffixInfo = new PtNodeInfo( 371 newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags, 372 suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1, 373 currentInfo.mChildrenAddress, currentInfo.mShortcutTargets, 374 currentInfo.mBigrams); 375 written += BinaryDictIOUtils.computePtNodeSize(suffixInfo, 376 fileHeader.mFormatOptions) + 1; 377 378 final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p, 379 codePoints.length); 380 final int flags = BinaryDictEncoderUtils.makePtNodeFlags( 381 newCharacters.length > 1, isTerminal, 382 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, 383 isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); 384 final PtNodeInfo newInfo = new PtNodeInfo( 385 newNodeAddress + written, -1 /* endAddress */, flags, 386 newCharacters, frequency, newNodeAddress + 1, 387 FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); 388 BinaryDictIOUtils.writeNodes(destination, 389 new PtNodeInfo[] { suffixInfo, newInfo }); 390 return; 391 } 392 matched = false; 393 break; 394 } 395 } 396 397 if (matched) { 398 if (wordPos + currentInfo.mCharacters.length == wordLen) { 399 // the word exists in the dictionary. 400 // only update the PtNode. 401 final int newNodeAddress = dictBuffer.limit(); 402 final boolean hasMultipleChars = currentInfo.mCharacters.length > 1; 403 final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars, 404 isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, 405 isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); 406 final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1, 407 -1 /* endAddress */, flags, currentInfo.mCharacters, frequency, 408 nodeParentAddress, currentInfo.mChildrenAddress, shortcuts, 409 bigrams); 410 movePtNode(destination, dictUpdater, newInfo, nodeOriginAddress, address, 411 fileHeader.mFormatOptions); 412 return; 413 } 414 wordPos += currentInfo.mCharacters.length; 415 if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { 416 /* 417 * found the prefix of the word. 418 * make new PtNode and link to the PtNode from this PtNode. 419 * 420 * before 421 * ab - cd 422 * 423 * insert "abcde" 424 * 425 * after 426 * ab - cd - e 427 */ 428 final int newNodeArrayAddress = dictBuffer.limit(); 429 updateChildrenAddress(dictUpdater, address, newNodeArrayAddress, 430 fileHeader.mFormatOptions); 431 final int newNodeAddress = newNodeArrayAddress + 1; 432 final boolean hasMultipleChars = (wordLen - wordPos) > 1; 433 final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars, 434 isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, 435 isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); 436 final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); 437 final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress, -1, flags, 438 characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS, 439 shortcuts, bigrams); 440 BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo }); 441 return; 442 } 443 dictBuffer.position(currentInfo.mChildrenAddress); 444 foundNextNode = true; 445 break; 446 } 447 } 448 449 if (foundNextNode) continue; 450 451 // reached the end of the array. 452 final int linkAddressPosition = dictBuffer.position(); 453 int nextLink = dictBuffer.readUnsignedInt24(); 454 if ((nextLink & FormatSpec.MSB24) != 0) { 455 nextLink = -(nextLink & FormatSpec.SINT24_MAX); 456 } 457 if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) { 458 /* 459 * expand this node. 460 * 461 * before 462 * ab - cd 463 * 464 * insert "abef" 465 * 466 * after 467 * ab - cd 468 * | 469 * - ef 470 */ 471 472 // change the forward link address. 473 final int newNodeAddress = dictBuffer.limit(); 474 dictBuffer.position(linkAddressPosition); 475 BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeAddress); 476 477 final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); 478 final int flags = BinaryDictEncoderUtils.makePtNodeFlags(characters.length > 1, 479 isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, 480 isNotAWord, isBlackListEntry, fileHeader.mFormatOptions); 481 final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1, 482 -1 /* endAddress */, flags, characters, frequency, nodeParentAddress, 483 FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); 484 BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[]{ newInfo }); 485 return; 486 } else { 487 depth--; 488 dictBuffer.position(nextLink); 489 } 490 } 491 } 492 } 493