1 11 package org.eclipse.jface.text; 12 13 import java.util.Arrays ; 14 import java.util.LinkedList ; 15 import java.util.List ; 16 import java.util.ListIterator ; 17 18 import org.eclipse.core.runtime.Assert; 19 20 import org.eclipse.jface.text.AbstractLineTracker.DelimiterInfo; 21 22 46 abstract class TreeLineTracker implements ILineTracker { 47 75 76 80 private static final boolean ASSERT= false; 81 82 86 private static final String NO_DELIM= ""; 88 93 private static final class Node { 94 Node(int length, String delimiter) { 95 this.length= length; 96 this.delimiter= delimiter; 97 } 98 102 int line; 103 107 int offset; 108 109 int length; 110 111 String delimiter; 112 113 Node parent; 114 115 Node left; 116 117 Node right; 118 119 byte balance; 120 121 124 public final String toString() { 125 String bal; 126 switch (balance) { 127 case 0: 128 bal= "="; break; 130 case 1: 131 bal= "+"; break; 133 case 2: 134 bal= "++"; break; 136 case -1: 137 bal= "-"; break; 139 case -2: 140 bal= "--"; break; 142 default: 143 bal= Byte.toString(balance); 144 } 145 return "[" + offset + "+" + pureLength() + "+" + delimiter.length() + "|" + line + "|" + bal + "]"; } 147 148 153 int pureLength() { 154 return length - delimiter.length(); 155 } 156 } 157 158 161 private Node fRoot= new Node(0, NO_DELIM); 162 163 166 protected TreeLineTracker() { 167 } 168 169 174 TreeLineTracker(ListLineTracker tracker) { 175 final List lines= tracker.getLines(); 176 final int n= lines.size(); 177 if (n == 0) 178 return; 179 180 Line line= (Line) lines.get(0); 181 String delim= line.delimiter; 182 if (delim == null) 183 delim= NO_DELIM; 184 int length= line.length; 185 fRoot= new Node(length, delim); 186 Node node= fRoot; 187 188 for (int i= 1; i < n; i++) { 189 line= (Line) lines.get(i); 190 delim= line.delimiter; 191 if (delim == null) 192 delim= NO_DELIM; 193 length= line.length; 194 node= insertAfter(node, length, delim); 195 } 196 197 if (node.delimiter != NO_DELIM) 198 insertAfter(node, 0, NO_DELIM); 199 200 if (ASSERT) checkTree(); 201 } 202 203 223 private Node nodeByOffset(final int offset) throws BadLocationException { 224 227 int remaining= offset; 228 Node node= fRoot; 229 int line= 0; 230 231 while (true) { 232 if (node == null) 233 fail(offset); 234 235 if (remaining < node.offset) { 236 node= node.left; 237 } else { 238 remaining -= node.offset; 239 line+= node.line; 240 if (remaining < node.length 241 || remaining == node.length && node.right == null) { break; 243 } 244 remaining -= node.length; 245 line ++; 246 node= node.right; 247 } 248 } 249 250 return node; 251 } 252 261 private int lineByOffset(final int offset) throws BadLocationException { 262 265 int remaining= offset; 266 Node node= fRoot; 267 int line= 0; 268 269 while (true) { 270 if (node == null) 271 fail(offset); 272 273 if (remaining < node.offset) { 274 node= node.left; 275 } else { 276 remaining -= node.offset; 277 line+= node.line; 278 if (remaining < node.length || remaining == node.length && node.right == null) return line; 280 281 remaining -= node.length; 282 line ++; 283 node= node.right; 284 } 285 } 286 } 287 288 296 private Node nodeByLine(final int line) throws BadLocationException { 297 300 int remaining= line; 301 int offset= 0; 302 Node node= fRoot; 303 304 while (true) { 305 if (node == null) 306 fail(line); 307 308 if (remaining == node.line) 309 break; 310 if (remaining < node.line) { 311 node= node.left; 312 } else { 313 remaining -= node.line + 1; 314 offset += node.offset + node.length; 315 node= node.right; 316 } 317 } 318 319 return node; 320 } 321 322 330 private int offsetByLine(final int line) throws BadLocationException { 331 334 int remaining= line; 335 int offset= 0; 336 Node node= fRoot; 337 338 while (true) { 339 if (node == null) 340 fail(line); 341 342 if (remaining == node.line) 343 return offset + node.offset; 344 345 if (remaining < node.line) { 346 node= node.left; 347 } else { 348 remaining -= node.line + 1; 349 offset += node.offset + node.length; 350 node= node.right; 351 } 352 } 353 } 354 355 361 private void rotateLeft(Node node) { 362 if (ASSERT) Assert.isNotNull(node); 363 Node child= node.right; 364 if (ASSERT) Assert.isNotNull(child); 365 boolean leftChild= node.parent == null || node == node.parent.left; 366 367 setChild(node.parent, child, leftChild); 369 370 setChild(node, child.left, false); 371 setChild(child, node, true); 372 373 child.line += node.line + 1; 377 child.offset += node.offset + node.length; 378 } 379 380 386 private void rotateRight(Node node) { 387 if (ASSERT) Assert.isNotNull(node); 388 Node child= node.left; 389 if (ASSERT) Assert.isNotNull(child); 390 boolean leftChild= node.parent == null || node == node.parent.left; 391 392 setChild(node.parent, child, leftChild); 393 394 setChild(node, child.right, true); 395 setChild(child, node, false); 396 397 node.line -= child.line + 1; 401 node.offset -= child.offset + child.length; 402 } 403 404 414 private void setChild(Node parent, Node child, boolean isLeftChild) { 415 if (parent == null) { 416 if (child == null) 417 fRoot= new Node(0, NO_DELIM); 418 else 419 fRoot= child; 420 } else { 421 if (isLeftChild) 422 parent.left= child; 423 else 424 parent.right= child; 425 } 426 if (child != null) 427 child.parent= parent; 428 } 429 430 437 private void singleLeftRotation(Node node, Node parent) { 438 rotateLeft(parent); 439 node.balance= 0; 440 parent.balance= 0; 441 } 442 443 450 private void singleRightRotation(Node node, Node parent) { 451 rotateRight(parent); 452 node.balance= 0; 453 parent.balance= 0; 454 } 455 456 463 private void rightLeftRotation(Node node, Node parent) { 464 Node child= node.left; 465 rotateRight(node); 466 rotateLeft(parent); 467 if (child.balance == 1) { 468 node.balance= 0; 469 parent.balance= -1; 470 child.balance= 0; 471 } else if (child.balance == 0) { 472 node.balance= 0; 473 parent.balance= 0; 474 } else if (child.balance == -1) { 475 node.balance= 1; 476 parent.balance= 0; 477 child.balance= 0; 478 } 479 } 480 481 488 private void leftRightRotation(Node node, Node parent) { 489 Node child= node.right; 490 rotateLeft(node); 491 rotateRight(parent); 492 if (child.balance == -1) { 493 node.balance= 0; 494 parent.balance= 1; 495 child.balance= 0; 496 } else if (child.balance == 0) { 497 node.balance= 0; 498 parent.balance= 0; 499 } else if (child.balance == 1) { 500 node.balance= -1; 501 parent.balance= 0; 502 child.balance= 0; 503 } 504 } 505 506 514 private Node insertAfter(Node node, int length, String delimiter) { 515 520 Node added= new Node(length, delimiter); 521 522 if (node.right == null) 523 setChild(node, added, false); 524 else 525 setChild(successorDown(node.right), added, true); 526 527 updateParentChain(added, length, 1); 529 updateParentBalanceAfterInsertion(added); 530 531 return added; 532 } 533 534 540 private void updateParentBalanceAfterInsertion(Node node) { 541 Node parent= node.parent; 542 while (parent != null) { 543 if (node == parent.left) 544 parent.balance--; 545 else 546 parent.balance++; 547 548 switch (parent.balance) { 549 case 1: 550 case -1: 551 node= parent; 552 parent= node.parent; 553 continue; 554 case -2: 555 rebalanceAfterInsertionLeft(node); 556 break; 557 case 2: 558 rebalanceAfterInsertionRight(node); 559 break; 560 case 0: 561 break; 562 default: 563 if (ASSERT) 564 Assert.isTrue(false); 565 } 566 return; 567 } 568 } 569 570 575 private void rebalanceAfterInsertionRight(Node node) { 576 Node parent= node.parent; 577 if (node.balance == 1) { 578 singleLeftRotation(node, parent); 579 } else if (node.balance == -1) { 580 rightLeftRotation(node, parent); 581 } else if (ASSERT) { 582 Assert.isTrue(false); 583 } 584 } 585 586 591 private void rebalanceAfterInsertionLeft(Node node) { 592 Node parent= node.parent; 593 if (node.balance == -1) { 594 singleRightRotation(node, parent); 595 } else if (node.balance == 1) { 596 leftRightRotation(node, parent); 597 } else if (ASSERT) { 598 Assert.isTrue(false); 599 } 600 } 601 602 605 public final void replace(int offset, int length, String text) throws BadLocationException { 606 if (ASSERT) checkTree(); 607 608 int remaining= offset; 610 Node first= fRoot; 611 final int firstNodeOffset; 612 613 while (true) { 614 if (first == null) 615 fail(offset); 616 617 if (remaining < first.offset) { 618 first= first.left; 619 } else { 620 remaining -= first.offset; 621 if (remaining < first.length 622 || remaining == first.length && first.right == null) { firstNodeOffset= offset - remaining; 624 break; 625 } 626 remaining -= first.length; 627 first= first.right; 628 } 629 } 630 if (ASSERT) Assert.isTrue(first != null); 632 633 Node last; 634 if (offset + length < firstNodeOffset + first.length) 635 last= first; 636 else 637 last= nodeByOffset(offset + length); 638 if (ASSERT) Assert.isTrue(last != null); 639 640 int firstLineDelta= firstNodeOffset + first.length - offset; 641 if (first == last) 642 replaceInternal(first, text, length, firstLineDelta); 643 else 644 replaceFromTo(first, last, text, length, firstLineDelta); 645 646 if (ASSERT) checkTree(); 647 } 648 649 658 private void replaceInternal(Node node, String text, int length, int firstLineDelta) { 659 661 DelimiterInfo info= text == null ? null : nextDelimiterInfo(text, 0); 662 663 if (info == null || info.delimiter == null) { 664 int added= text == null ? 0 : text.length(); 666 updateLength(node, added - length); 667 } else { 668 int remainder= firstLineDelta - length; 671 String remDelim= node.delimiter; 672 673 int consumed= info.delimiterIndex + info.delimiterLength; 675 int delta= consumed - firstLineDelta; 676 updateLength(node, delta); 677 node.delimiter= info.delimiter; 678 679 info= nextDelimiterInfo(text, consumed); 681 while (info != null) { 682 int lineLen= info.delimiterIndex - consumed + info.delimiterLength; 683 node= insertAfter(node, lineLen, info.delimiter); 684 consumed += lineLen; 685 info= nextDelimiterInfo(text, consumed); 686 } 687 689 insertAfter(node, remainder + text.length() - consumed, remDelim); 691 } 692 } 693 694 704 private void replaceFromTo(Node node, Node last, String text, int length, int firstLineDelta) { 705 707 Node successor= successor(node); 711 while (successor != last) { 712 length -= successor.length; 713 Node toDelete= successor; 714 successor= successor(successor); 715 updateLength(toDelete, -toDelete.length); 716 } 717 718 DelimiterInfo info= text == null ? null : nextDelimiterInfo(text, 0); 719 720 if (info == null || info.delimiter == null) { 721 int added= text == null ? 0 : text.length(); 722 723 join(node, last, added - length); 725 726 } else { 727 728 int consumed= info.delimiterIndex + info.delimiterLength; 730 updateLength(node, consumed - firstLineDelta); 731 node.delimiter= info.delimiter; 732 length -= firstLineDelta; 733 734 info= nextDelimiterInfo(text, consumed); 736 while (info != null) { 737 int lineLen= info.delimiterIndex - consumed + info.delimiterLength; 738 node= insertAfter(node, lineLen, info.delimiter); 739 consumed += lineLen; 740 info= nextDelimiterInfo(text, consumed); 741 } 742 744 updateLength(last, text.length() - consumed - length); 745 } 746 } 747 748 756 private void join(Node one, Node two, int delta) { 757 int oneLength= one.length; 758 updateLength(one, -oneLength); 759 updateLength(two, oneLength + delta); 760 } 761 762 770 private void updateLength(Node node, int delta) { 771 if (ASSERT) Assert.isTrue(node.length + delta >= 0); 772 773 node.length += delta; 775 776 final int lineDelta; 778 boolean delete= node.length == 0 && node.delimiter != NO_DELIM; 779 if (delete) 780 lineDelta= -1; 781 else 782 lineDelta= 0; 783 784 if (delta != 0 || lineDelta != 0) 786 updateParentChain(node, delta, lineDelta); 787 788 if (delete) 789 delete(node); 790 } 791 792 800 private void updateParentChain(Node node, int deltaLength, int deltaLines) { 801 updateParentChain(node, null, deltaLength, deltaLines); 802 } 803 804 813 private void updateParentChain(Node from, Node to, int deltaLength, int deltaLines) { 814 Node parent= from.parent; 815 while (parent != to) { 816 if (from == parent.left) { 818 parent.offset += deltaLength; 819 parent.line += deltaLines; 820 } 821 from= parent; 822 parent= from.parent; 823 } 824 } 825 826 835 private void delete(Node node) { 836 if (ASSERT) Assert.isTrue(node != null); 837 if (ASSERT) Assert.isTrue(node.length == 0); 838 839 Node parent= node.parent; 840 Node toUpdate; boolean lostLeftChild; 842 boolean isLeftChild= parent == null || node == parent.left; 843 844 if (node.left == null || node.right == null) { 845 Node replacement= node.left == null ? node.right : node.left; 848 setChild(parent, replacement, isLeftChild); 849 toUpdate= parent; 850 lostLeftChild= isLeftChild; 851 } else if (node.right.left == null) { 853 Node replacement= node.right; 856 setChild(parent, replacement, isLeftChild); 857 setChild(replacement, node.left, true); 858 replacement.line= node.line; 859 replacement.offset= node.offset; 860 replacement.balance= node.balance; 861 toUpdate= replacement; 862 lostLeftChild= false; 863 } else { 872 Node successor= successor(node); 874 875 if (ASSERT) Assert.isNotNull(successor); 877 if (ASSERT) Assert.isTrue(successor.left == null); 879 if (ASSERT) Assert.isTrue(successor.line == 0); 880 if (ASSERT) Assert.isTrue(successor == successor.parent.left); 883 if (ASSERT) Assert.isTrue(successor.parent != node); 885 886 toUpdate= successor.parent; 887 lostLeftChild= true; 888 889 updateParentChain(successor, node, -successor.length, -1); 891 892 setChild(toUpdate, successor.right, true); 894 895 setChild(successor, node.right, false); 897 setChild(successor, node.left, true); 898 899 setChild(parent, successor, isLeftChild); 901 902 successor.line= node.line; 904 successor.offset= node.offset; 905 successor.balance= node.balance; 906 } 907 908 updateParentBalanceAfterDeletion(toUpdate, lostLeftChild); 909 } 910 911 919 private void updateParentBalanceAfterDeletion(Node node, boolean wasLeftChild) { 920 while (node != null) { 921 if (wasLeftChild) 922 node.balance++; 923 else 924 node.balance--; 925 926 Node parent= node.parent; 927 if (parent != null) 928 wasLeftChild= node == parent.left; 929 930 switch (node.balance) { 931 case 1: 932 case -1: 933 return; case -2: 935 if (rebalanceAfterDeletionRight(node.left)) 936 return; 937 break; case 2: 939 if (rebalanceAfterDeletionLeft(node.right)) 940 return; 941 break; case 0: 943 break; default: 945 if (ASSERT) 946 Assert.isTrue(false); 947 } 948 949 node= parent; 950 } 951 } 952 953 960 private boolean rebalanceAfterDeletionLeft(Node node) { 961 Node parent= node.parent; 962 if (node.balance == 1) { 963 singleLeftRotation(node, parent); 964 return false; 965 } else if (node.balance == -1) { 966 rightLeftRotation(node, parent); 967 return false; 968 } else if (node.balance == 0) { 969 rotateLeft(parent); 970 node.balance= -1; 971 parent.balance= 1; 972 return true; 973 } else { 974 if (ASSERT) Assert.isTrue(false); 975 return true; 976 } 977 } 978 979 986 private boolean rebalanceAfterDeletionRight(Node node) { 987 Node parent= node.parent; 988 if (node.balance == -1) { 989 singleRightRotation(node, parent); 990 return false; 991 } else if (node.balance == 1) { 992 leftRightRotation(node, parent); 993 return false; 994 } else if (node.balance == 0) { 995 rotateRight(parent); 996 node.balance= 1; 997 parent.balance= -1; 998 return true; 999 } else { 1000 if (ASSERT) Assert.isTrue(false); 1001 return true; 1002 } 1003 } 1004 1005 1011 private Node successor(Node node) { 1012 if (node.right != null) 1013 return successorDown(node.right); 1014 1015 return successorUp(node); 1016 } 1017 1018 1025 private Node successorUp(final Node node) { 1026 Node child= node; 1027 Node parent= child.parent; 1028 while (parent != null) { 1029 if (child == parent.left) 1030 return parent; 1031 child= parent; 1032 parent= child.parent; 1033 } 1034 if (ASSERT) Assert.isTrue(node.delimiter == NO_DELIM); 1035 return null; 1036 } 1037 1038 1044 private Node successorDown(Node node) { 1045 Node child= node.left; 1046 while (child != null) { 1047 node= child; 1048 child= node.left; 1049 } 1050 return node; 1051 } 1052 1053 1054 1055 1061 private void fail(int offset) throws BadLocationException { 1062 throw new BadLocationException(); 1063 } 1064 1065 1073 protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset); 1074 1075 1078 public final String getLineDelimiter(int line) throws BadLocationException { 1079 Node node= nodeByLine(line); 1080 return node.delimiter == NO_DELIM ? null : node.delimiter; 1081 } 1082 1083 1086 public final int computeNumberOfLines(String text) { 1087 int count= 0; 1088 int start= 0; 1089 DelimiterInfo delimiterInfo= nextDelimiterInfo(text, start); 1090 while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) { 1091 ++count; 1092 start= delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength; 1093 delimiterInfo= nextDelimiterInfo(text, start); 1094 } 1095 return count; 1096 } 1097 1098 1101 public final int getNumberOfLines() { 1102 Node node= fRoot; 1104 int lines= 0; 1105 while (node != null) { 1106 lines += node.line + 1; 1107 node= node.right; 1108 } 1109 return lines; 1110 } 1111 1112 1115 public final int getNumberOfLines(int offset, int length) throws BadLocationException { 1116 if (length == 0) 1117 return 1; 1118 1119 int startLine= lineByOffset(offset); 1120 int endLine= lineByOffset(offset + length); 1121 1122 return endLine - startLine + 1; 1123 } 1124 1125 1128 public final int getLineOffset(int line) throws BadLocationException { 1129 return offsetByLine(line); 1130 } 1131 1132 1135 public final int getLineLength(int line) throws BadLocationException { 1136 Node node= nodeByLine(line); 1137 return node.length; 1138 } 1139 1140 1143 public final int getLineNumberOfOffset(int offset) throws BadLocationException { 1144 return lineByOffset(offset); 1145 } 1146 1147 1150 public final IRegion getLineInformationOfOffset(final int offset) throws BadLocationException { 1151 int remaining= offset; 1153 Node node= fRoot; 1154 final int lineOffset; 1155 1156 while (true) { 1157 if (node == null) 1158 fail(offset); 1159 1160 if (remaining < node.offset) { 1161 node= node.left; 1162 } else { 1163 remaining -= node.offset; 1164 if (remaining < node.length 1165 || remaining == node.length && node.right == null) { lineOffset= offset - remaining; 1167 break; 1168 } 1169 remaining -= node.length; 1170 node= node.right; 1171 } 1172 } 1173 return new Region(lineOffset, node.pureLength()); 1175 } 1176 1177 1180 public final IRegion getLineInformation(int line) throws BadLocationException { 1181 try { 1182 int remaining= line; 1184 int offset= 0; 1185 Node node= fRoot; 1186 1187 while (true) { 1188 if (node == null) 1189 fail(line); 1190 1191 if (remaining == node.line) { 1192 offset += node.offset; 1193 break; 1194 } 1195 if (remaining < node.line) { 1196 node= node.left; 1197 } else { 1198 remaining -= node.line + 1; 1199 offset += node.offset + node.length; 1200 node= node.right; 1201 } 1202 } 1203 return new Region(offset, node.pureLength()); 1205 } catch (BadLocationException x) { 1206 1211 if (line > 0 && line == getNumberOfLines()) { 1212 line= line - 1; 1213 int remaining= line; 1215 int offset= 0; 1216 Node node= fRoot; 1217 1218 while (true) { 1219 if (node == null) 1220 fail(line); 1221 1222 if (remaining == node.line) { 1223 offset+= node.offset; 1224 break; 1225 } 1226 if (remaining < node.line) { 1227 node= node.left; 1228 } else { 1229 remaining -= node.line + 1; 1230 offset += node.offset + node.length; 1231 node= node.right; 1232 } 1233 } 1234 Node last= node; 1235 if (last.length > 0) 1237 return new Region(offset + last.length, 0); 1238 } 1239 throw x; 1240 } 1241 } 1242 1243 1246 public final void set(String text) { 1247 fRoot= new Node(0, NO_DELIM); 1248 try { 1249 replace(0, 0, text); 1250 } catch (BadLocationException x) { 1251 throw new InternalError (); 1252 } 1253 } 1254 1255 1258 public String toString() { 1259 int depth= computeDepth(fRoot); 1260 int WIDTH= 30; 1261 int leaves= (int) Math.pow(2, depth - 1); 1262 int width= WIDTH * leaves; 1263 String empty= "."; 1265 List roots= new LinkedList (); 1266 roots.add(fRoot); 1267 StringBuffer buf= new StringBuffer ((width + 1) * depth); 1268 int nodes= 1; 1269 int indents= leaves; 1270 char[] space= new char[leaves * WIDTH / 2]; 1271 Arrays.fill(space, ' '); 1272 for(int d= 0; d < depth; d++) { 1273 indents /= 2; 1275 int spaces= Math.max(0, indents * WIDTH - WIDTH / 2); 1276 for (ListIterator it= roots.listIterator(); it.hasNext();) { 1278 buf.append(space, 0, spaces); 1280 1281 Node node= (Node) it.next(); 1282 String box; 1283 if (node == null) { 1285 it.add(null); 1286 box= empty; 1287 } else { 1288 it.set(node.left); 1289 it.add(node.right); 1290 box= node.toString(); 1291 } 1292 1293 int pad_left= (WIDTH - box.length() + 1) / 2; 1295 int pad_right= WIDTH - box.length() - pad_left; 1296 buf.append(space, 0, pad_left); 1297 buf.append(box); 1298 buf.append(space, 0, pad_right); 1299 1300 buf.append(space, 0, spaces); 1302 } 1303 1304 buf.append('\n'); 1305 nodes *= 2; 1306 } 1307 1308 return buf.toString(); 1309 } 1310 1311 1317 private byte computeDepth(Node root) { 1318 if (root == null) 1319 return 0; 1320 1321 return (byte) (Math.max(computeDepth(root.left), computeDepth(root.right)) + 1); 1322 } 1323 1324 1327 private void checkTree() { 1328 checkTreeStructure(fRoot); 1329 1330 try { 1331 checkTreeOffsets(nodeByOffset(0), new int[] {0, 0}, null); 1332 } catch (BadLocationException x) { 1333 throw new AssertionError (); 1334 } 1335 } 1336 1337 1345 private byte checkTreeStructure(Node node) { 1346 if (node == null) 1347 return 0; 1348 1349 byte leftDepth= checkTreeStructure(node.left); 1350 byte rightDepth= checkTreeStructure(node.right); 1351 Assert.isTrue(node.balance == rightDepth - leftDepth); 1352 Assert.isTrue(node.left == null || node.left.parent == node); 1353 Assert.isTrue(node.right == null || node.right.parent == node); 1354 1355 return (byte) (Math.max(rightDepth, leftDepth) + 1); 1356 } 1357 1358 1371 private int[] checkTreeOffsets(Node node, int[] offLen, Node last) { 1372 if (node == last) 1373 return offLen; 1374 1375 Assert.isTrue(node.offset == offLen[0]); 1376 Assert.isTrue(node.line == offLen[1]); 1377 1378 if (node.right != null) { 1379 int[] result= checkTreeOffsets(successorDown(node.right), new int[2], node); 1380 offLen[0] += result[0]; 1381 offLen[1] += result[1]; 1382 } 1383 1384 offLen[0] += node.length; 1385 offLen[1]++; 1386 return checkTreeOffsets(node.parent, offLen, last); 1387 } 1388} 1389 | Popular Tags |