1 19 20 package org.netbeans; 21 22 import java.io.UnsupportedEncodingException ; 23 import java.util.HashMap ; 24 import java.util.Iterator ; 25 import java.util.List ; 26 import org.openide.util.NbCollections; 27 28 57 public final class PatchByteCode { 58 private static final String ATTR_SUPERCLASS = "org.netbeans.superclass"; private static final byte[] BYTE_SUPERCLASS; 60 static { 61 try { 62 BYTE_SUPERCLASS = ATTR_SUPERCLASS.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 64 throw new IllegalStateException (ex.getMessage()); 65 } 66 } 67 private static final String ATTR_INTERFACES = "org.netbeans.interfaces"; private static final byte[] BYTE_INTERFACES; 69 static { 70 try { 71 BYTE_INTERFACES = ATTR_INTERFACES.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 73 throw new IllegalStateException (ex.getMessage()); 74 } 75 } 76 private static final String ATTR_MEMBER = "org.netbeans.member"; private static final byte[] BYTE_MEMBER; 78 static { 79 try { 80 BYTE_MEMBER = ATTR_MEMBER.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 82 throw new IllegalStateException (ex.getMessage()); 83 } 84 } 85 private static final String ATTR_NAME = "org.netbeans.name"; private static final byte[] BYTE_NAME; 87 static { 88 try { 89 BYTE_NAME = ATTR_NAME.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 91 throw new IllegalStateException (ex.getMessage()); 92 } 93 } 94 95 private static final String ATTR_INIT = "<init>"; private static final byte[] BYTE_INIT; 97 static { 98 try { 99 BYTE_INIT = ATTR_INIT.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 101 throw new IllegalStateException (ex.getMessage()); 102 } 103 } 104 105 private static final String ATTR_INIT_TYPE = "()V"; private static final byte[] BYTE_INIT_TYPE; 107 static { 108 try { 109 BYTE_INIT_TYPE = ATTR_INIT_TYPE.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 111 throw new IllegalStateException (ex.getMessage()); 112 } 113 } 114 115 private byte[] arr; 116 117 private int cpCount; 118 private int cpEnd; 119 private int atCount; 120 private int atEnd; 121 122 123 private int superClassNameIndex; 124 125 private int superClassNameAttr; 126 127 private int interfacesNameIndex; 128 129 private int interfacesNameAttr; 130 131 private int memberNameIndex = -1; 132 133 private int memberClassAttr = -1; 134 135 private int initIndex = -1; 136 137 private int initIndexType = -1; 138 139 private int initNameTypeIndex = -1; 140 141 private int initAttr = -1; 142 143 private int renameNameIndex = -1; 144 145 146 private HashMap <String ,int[]> nameIndexes; 147 148 152 private PatchByteCode(byte[] arr, HashMap <String ,int[]> nameIndexes) { 153 this.arr = arr; 154 this.nameIndexes = nameIndexes; 155 156 scan (); 158 scan (); 159 } 160 161 170 public static byte[] enhanceClass(byte[] arr, java.util.Map <String ,Object > args) { 171 if (isPatched (arr)) { 172 return null; 174 } 175 176 String superClass = (String )args.get ("netbeans.superclass"); 177 String interfaces = (String )args.get ("netbeans.interfaces"); 178 List _methods = (List ) args.get("netbeans.public"); 179 List <String > methods = _methods != null ? NbCollections.checkedListByCopy(_methods, String .class, true) : null; 180 List _rename = (List ) args.get ("netbeans.rename"); 181 List <String > rename = _rename != null ? NbCollections.checkedListByCopy(_rename, String .class, true) : null; 182 183 184 HashMap <String ,int[]> m; 185 if (methods != null || rename != null) { 186 m = new HashMap <String ,int[]> (); 187 188 if (methods != null) { 189 for (String s: methods) { 190 m.put(s, new int[1]); 191 } 192 } 193 194 if (rename != null) { 195 for (String s: rename) { 196 m.put(s, new int[1]); 197 } 198 } 199 } else { 200 m = null; 201 } 202 203 204 PatchByteCode pc = new PatchByteCode (arr, m); 205 boolean patched = false; 206 207 if (superClass != null) { 208 int x = pc.addClass (superClass); 209 210 byte[] sup = new byte[2]; 211 writeU2 (sup, 0, x); 212 pc.addAttribute (ATTR_SUPERCLASS, sup); 213 214 patched = true; 215 } 216 217 if (interfaces != null) { 218 java.util.ArrayList <String > tokens = new java.util.ArrayList <String > (); 219 java.util.StringTokenizer tok = new java.util.StringTokenizer (interfaces, ","); 220 while (tok.hasMoreTokens()) { 221 tokens.add (tok.nextToken()); 222 } 223 String [] ifaces = tokens.toArray (new String [0]); 224 byte[] sup = new byte[2 + ifaces.length * 2]; 225 writeU2 (sup, 0, ifaces.length); 226 227 for (int i = 0; i < ifaces.length; i++) { 228 int x = pc.addClass (ifaces[i]); 229 230 writeU2 (sup, 2 + i * 2, x); 231 } 232 pc.addAttribute (ATTR_INTERFACES, sup); 233 234 patched = true; 235 } 236 237 if (!pc.isPublic ()) { 238 pc.markPublic (); 240 patched = true; 241 } 242 243 if (methods != null) { 244 for (String s : methods) { 245 patched |= pc.markMemberPublic(s); 246 } 247 } 248 249 if (rename != null) { 250 Iterator <String > it = rename.iterator(); 251 while (it.hasNext()) { 252 patched |= pc.renameMember(it.next(), it.next()); 253 } 254 } 255 256 if (patched) { 257 byte[] patch = { 258 'n', 'b' }; 260 261 pc.addAttribute ("org.netbeans.enhanced", patch); 262 } else { 263 return null; 264 } 265 266 267 268 return pc.getClassFile (); 270 } 271 272 280 public static byte[] patch (byte[] arr, String name) { 281 if (!isPatched (arr)) return arr; 282 283 289 290 PatchByteCode pc = new PatchByteCode (arr, null); 291 if (pc.superClassNameAttr > 0) { 292 int classindex = pc.readU2 (pc.superClassNameAttr + 6); 294 295 writeU2 (pc.getClassFile(), pc.cpEnd + 4, classindex); 296 297 if (pc.initAttr != -1) { 298 writeU2 (pc.getClassFile (), pc.initAttr + 1, classindex); 300 } 301 } 302 303 if (pc.memberClassAttr > 0) { 304 if (pc.readU4 (pc.memberClassAttr + 2) != 2) { 306 throw new IllegalArgumentException ("Size of a attribute " + ATTR_MEMBER + " should be 2"); } 308 309 int access = pc.readU2 (pc.memberClassAttr + 6); 311 312 pc.readU2(pc.cpEnd); 313 314 writeU2 (pc.getClassFile (), pc.cpEnd, access); 315 316 } 317 318 if (pc.memberNameIndex > 0 || pc.renameNameIndex > 0) { 319 pc.applyMemberAccessAndNameChanges (); 321 } 322 323 byte[] result = pc.getClassFile (); 324 if (pc.interfacesNameAttr > 0) { 325 int numberOfIfaces = pc.readU2 (pc.interfacesNameAttr + 6); 327 int currentIfaces = pc.readU2 (pc.cpEnd + 6); 328 329 byte[] insert = new byte[result.length + numberOfIfaces * 2]; 330 System.arraycopy(result, 0, insert, 0, pc.cpEnd + 6); 331 System.arraycopy(result, pc.interfacesNameAttr + 8, insert, pc.cpEnd + 8, numberOfIfaces * 2); 332 System.arraycopy(result, pc.cpEnd + 8, insert, pc.cpEnd + 8 + numberOfIfaces * 2, result.length - pc.cpEnd - 8); 333 writeU2 (insert, pc.cpEnd + 6, numberOfIfaces + currentIfaces); 334 result = insert; 335 } 336 337 return result; 338 } 339 340 341 345 private static boolean isPatched (byte[] arr) { 346 if (arr == null || arr.length < 2) return false; 347 348 int base = arr.length - 2; 349 if (arr[base + 1] != 'b') return false; 350 if (arr[base + 0] != 'n') return false; 351 352 return true; 356 } 357 358 359 360 361 362 363 364 367 private byte[] getClassFile () { 368 return arr; 369 } 370 371 375 private int addClass (String s) { 376 int x = addConstant (s); 377 378 byte[] t = { 7, 0, 0 }; 379 writeU2 (t, 1, x); 380 381 return addPool (t); 382 } 383 384 388 private int addConstant (String s) { 389 byte[] t; 390 391 try { 392 t = s.getBytes("utf-8"); } catch (java.io.UnsupportedEncodingException ex) { 394 throw new IllegalStateException ("UTF-8 shall be always supported"); } 396 397 byte[] add = new byte[t.length + 3]; 398 System.arraycopy (t, 0, add, 3, t.length); 399 add[0] = 1; writeU2 (add, 1, t.length); 401 402 return addPool (add); 403 } 404 405 406 private int addPool (byte[] add) { 407 byte[] res = new byte[arr.length + add.length]; 408 409 System.arraycopy (arr, 0, res, 0, cpEnd); 410 int index = readU2 (cpCount); 412 writeU2 (res, cpCount, index + 1); 413 414 System.arraycopy (add, 0, res, cpEnd, add.length); 416 417 System.arraycopy (arr, cpEnd, res, cpEnd + add.length, arr.length - cpEnd); 419 420 arr = res; 421 422 cpEnd += add.length; 423 atCount += add.length; 424 atEnd += add.length; 425 426 return index; 428 } 429 430 432 private boolean isPublic () { 433 int x = readU2 (cpEnd); 434 435 if ((x & 0x0001) != 0) { 436 return true; 437 } else { 438 return false; 439 } 440 } 441 442 445 private boolean markPublic () { 446 if (isPublic ()) { 447 return false; 448 } 449 450 if (memberNameIndex == -1) { 452 memberNameIndex = addConstant (ATTR_MEMBER); 453 } 454 455 int x = readU2 (cpEnd) | 0x0001; 457 byte[] sup = new byte[2]; 458 writeU2 (sup, 0, x); 459 addAttribute (ATTR_MEMBER, sup); 460 461 return true; 462 } 463 464 468 private boolean markMemberPublic (String name) { 469 int constantPoolIndex = nameIndexes.get(name)[0]; 470 int patchCount = 0; 471 boolean modified = false; 472 473 if (memberNameIndex == -1) { 475 memberNameIndex = addConstant (ATTR_MEMBER); 476 } 477 478 int pos = cpEnd; 479 480 pos += 6; 481 pos += 2 * readU2 (pos); 483 pos += 2; 485 486 for (int fieldsAndMethods = 0; fieldsAndMethods < 2; fieldsAndMethods++) { 487 int fieldsOrMethods = readU2 (pos); 489 pos += 2; 490 491 while (fieldsOrMethods-- > 0) { 492 int nameIndex = readU2 (pos + 2); 494 if (nameIndex == constantPoolIndex) { 495 int access = readU2 (pos); 497 if ((access & 0x0001) == 0 || (access & 0x0010) != 0) { 498 access = (access | 0x0001) & ~(0x0010 | 0x0002 | 0x0004); 500 501 502 int cnt = readU2 (pos + 6) + 1; 504 505 byte[] res = new byte[arr.length + 2 + 6]; 507 508 System.arraycopy(arr, 0, res, 0, pos + 6); 510 writeU2 (res, pos + 6, cnt); 512 513 writeU2 (res, pos + 8, memberNameIndex); writeU4 (res, pos + 10, 2); writeU2 (res, pos + 14, access); 518 System.arraycopy(arr, pos + 8, res, pos + 8 + 6 + 2, arr.length - pos - 8); 520 521 atEnd += 2 + 6; 522 atCount += 2 + 6; 523 524 525 arr = res; 526 527 modified = true; 528 } 529 530 patchCount++; 531 } 532 533 pos += memberSize (pos, null); 534 } 535 } 536 537 if (patchCount == 0) { 538 throw new IllegalArgumentException ("Member " + name + " not found!"); 539 } 540 541 return modified; 542 } 543 544 549 private boolean renameMember (String name, String rename) { 550 int constantPoolIndex = nameIndexes.get (name)[0]; 551 int newPoolIndex; 552 { 553 int[] arr = nameIndexes.get(rename); 554 if (arr != null && arr[0] > 0) { 555 newPoolIndex = arr[0]; 556 } else { 557 newPoolIndex = addConstant (rename); 558 nameIndexes.put (rename, new int[] { newPoolIndex }); 559 } 560 } 561 int patchCount = 0; 562 boolean modified = false; 563 564 if (renameNameIndex == -1) { 566 renameNameIndex = addConstant (ATTR_NAME); 567 } 568 569 int pos = cpEnd; 570 571 pos += 6; 572 pos += 2 * readU2 (pos); 574 pos += 2; 576 577 for (int fieldsAndMethods = 0; fieldsAndMethods < 2; fieldsAndMethods++) { 578 int fieldsOrMethods = readU2 (pos); 580 pos += 2; 581 582 while (fieldsOrMethods-- > 0) { 583 int nameIndex = readU2 (pos + 2); 585 if (nameIndex == constantPoolIndex) { 586 int[] attributes = { -1, -1 }; 588 589 memberSize (pos, attributes); 590 if (attributes[1] == -1) { 591 593 int cnt = readU2 (pos + 6) + 1; 595 596 byte[] res = new byte[arr.length + 2 + 6]; 598 599 System.arraycopy(arr, 0, res, 0, pos + 6); 601 writeU2 (res, pos + 6, cnt); 603 604 writeU2 (res, pos + 8, renameNameIndex); writeU4 (res, pos + 10, 2); writeU2 (res, pos + 14, newPoolIndex); 609 System.arraycopy(arr, pos + 8, res, pos + 8 + 6 + 2, arr.length - pos - 8); 611 612 atEnd += 2 + 6; 613 atCount += 2 + 6; 614 615 616 arr = res; 617 618 modified = true; 619 } 620 621 patchCount++; 622 } 623 624 pos += memberSize (pos, null); 625 } 626 } 627 628 if (patchCount == 0) { 629 throw new IllegalArgumentException ("Member " + name + " not found!"); 630 } 631 632 return modified; 633 } 634 635 638 private void applyMemberAccessAndNameChanges () { 639 int[] result = new int[2]; 640 641 int pos = cpEnd; 642 643 pos += 6; 644 pos += 2 * readU2 (pos); 646 pos += 2; 648 649 for (int fieldsAndMethods = 0; fieldsAndMethods < 2; fieldsAndMethods++) { 650 int fieldsOrMethods = readU2 (pos); 652 pos += 2; 653 654 while (fieldsOrMethods-- > 0) { 655 result[0] = -1; 656 result[1] = -1; 657 int size = memberSize(pos, result); 658 if (result[0] != -1) { 659 661 if (readU4 (result[0] + 2) != 2) { 662 throw new IllegalArgumentException ("Size of a attribute " + ATTR_MEMBER + " should be 2"); } 664 665 int access = readU2 (result[0] + 6); 667 writeU2 (arr, pos, access); 668 } 669 670 if (result[1] != -1) { 671 673 if (readU4 (result[1] + 2) != 2) { 674 throw new IllegalArgumentException ("Size of a attribute " + ATTR_NAME + " should be 2"); } 676 677 int newName = readU2 (result[1] + 6); 679 writeU2 (arr, pos + 2, newName); 680 } 681 682 pos += size; 683 } 684 } 685 } 686 687 691 private void addAttribute (String name, byte[] b) { 692 int index = -1; 693 if (ATTR_SUPERCLASS.equals (name) && superClassNameIndex > 0) { 694 index = superClassNameIndex; 695 } 696 697 if (ATTR_MEMBER.equals (name) && memberNameIndex > 0) { 698 index = memberNameIndex; 699 } 700 701 if (ATTR_INTERFACES.equals (name) && interfacesNameIndex > 0) { 702 index = interfacesNameIndex; 703 } 704 705 if (index == -1) { 706 index = addConstant (name); 708 } 709 710 byte[] res = new byte[arr.length + b.length + 6]; 711 712 System.arraycopy(arr, 0, res, 0, arr.length); 713 714 writeU2 (res, arr.length, index); 715 writeU4 (res, arr.length + 2, b.length); 716 717 int begin = arr.length + 6; 718 System.arraycopy(b, 0, res, begin, b.length); 719 720 atEnd += b.length + 6; 721 722 writeU2 (res, atCount, readU2 (atCount) + 1); 723 724 arr = res; 725 } 726 727 728 730 private int get (int pos) { 731 if (pos >= arr.length) { 732 throw new ArrayIndexOutOfBoundsException ("Size: " + arr.length + " index: " + pos); 733 } 734 735 int x = arr[pos]; 736 return x >= 0 ? x : 256 + x; 737 } 738 739 742 private void scan () { 743 if (get (0) != 0xCA && get (1) != 0xFE && get (2) != 0xBA && get (3) != 0xBE) { 744 throw new IllegalStateException ("Not a class file!"); } 746 747 int pos = 10; 748 cpCount = 8; 750 751 int cp = readU2 (8); 752 for (int[] i = { 1 }; i[0] < cp; i[0]++) { 753 int len = constantPoolSize (pos, i); 755 pos += len; 756 } 757 758 cpEnd = pos; 760 761 pos += 6; 762 pos += 2 * readU2 (pos); 764 pos += 2; 766 767 int fields = readU2 (pos); 769 pos += 2; 770 while (fields-- > 0) { 771 pos += memberSize (pos, null); 772 } 773 774 int methods = readU2 (pos); 775 pos += 2; 776 while (methods-- > 0) { 777 pos += memberSize (pos, null); 778 } 779 780 782 int[] memberAccess = { -1, -1 }; 783 784 atCount = pos; 785 int attrs = readU2 (pos); 786 pos += 2; 787 while (attrs-- > 0) { 788 pos += attributeSize (pos, memberAccess); 789 } 790 791 if (memberAccess[0] != -1) { 792 memberClassAttr = memberAccess[0]; 794 } 795 796 atEnd = pos; 798 } 799 800 private int readU2 (int pos) { 801 int b1 = get (pos); 802 int b2 = get (pos + 1); 803 804 return b1 * 256 + b2; 805 } 806 807 private int readU4 (int pos) { 808 return readU2 (pos) * 256 * 256 + readU2 (pos + 2); 809 } 810 811 private static void writeU2 (byte[] arr, int pos, int value) { 812 int v1 = (value & 0xff00) >> 8; 813 int v2 = value & 0xff; 814 815 if (v1 < 0) v1 += 256; 816 if (v2 < 0) v2 += 256; 817 818 arr[pos] = (byte)v1; 819 arr[pos + 1] = (byte)v2; 820 } 821 822 private static void writeU4 (byte[] arr, int pos, int value) { 823 writeU2 (arr, pos, (value & 0xff00) >> 16); 824 writeU2 (arr, pos + 2, value & 0x00ff); 825 } 826 827 830 private int constantPoolSize (int pos, int[] cnt) { 831 switch (get (pos)) { 832 case 7: case 8: return 3; 835 836 case 12: int nameIndex = readU2 (pos + 1); 839 if (nameIndex == initIndex) { 840 int descriptorIndex = readU2 (pos + 3); 841 if (descriptorIndex == initIndexType) { 842 if (initNameTypeIndex > 0 && initNameTypeIndex != cnt[0]) { 843 throw new IllegalArgumentException ("Second initialization of name type index"); } 845 initNameTypeIndex = cnt[0]; 846 } 847 } 848 return 5; 849 850 case 10: int classname = readU2 (pos + 1); 853 int nameAndType = readU2 (pos + 3); 854 if (nameAndType == initNameTypeIndex) { 855 int superclass = readU2 (cpEnd + 4); 857 858 if (superclass == classname) { 859 if (initAttr > 0 && initAttr != pos) { 861 throw new IllegalStateException ("Second initialization of position of <init> invocation"); } 863 initAttr = pos; 864 } 865 } 866 return 5; 867 868 case 9: case 11: case 3: case 4: return 5; 873 874 case 5: case 6: cnt[0]++; 878 return 9; 879 case 1: int len = readU2 (pos + 1); 881 882 if (compareUtfEntry (BYTE_INIT, pos)) { 883 if (initIndex > 0 && initIndex != cnt[0]) { 884 throw new IllegalArgumentException ("Second initialization of " + ATTR_INIT); } 886 initIndex = cnt[0]; 887 } 888 889 if (compareUtfEntry (BYTE_INIT_TYPE, pos)) { 890 if (initIndexType > 0 && initIndexType != cnt[0]) { 891 throw new IllegalArgumentException ("Second initialization of " + ATTR_INIT_TYPE); } 893 initIndexType = cnt[0]; 894 } 895 896 if (compareUtfEntry (BYTE_SUPERCLASS, pos)) { 897 if (superClassNameIndex > 0 && superClassNameIndex != cnt[0]) { 899 throw new IllegalStateException (ATTR_SUPERCLASS + " registered for the second time!"); } 901 902 superClassNameIndex = cnt[0]; 903 } 904 905 if (compareUtfEntry (BYTE_INTERFACES, pos)) { 906 if (interfacesNameIndex > 0 && interfacesNameIndex != cnt[0]) { 908 throw new IllegalStateException (ATTR_INTERFACES + " registered for the second time!"); } 910 911 interfacesNameIndex = cnt[0]; 912 } 913 914 if (compareUtfEntry (BYTE_MEMBER, pos)) { 915 if (memberNameIndex > 0 && memberNameIndex != cnt[0]) { 917 throw new IllegalStateException (ATTR_MEMBER + " registered for the second time!"); } 919 920 memberNameIndex = cnt[0]; 921 } 922 if (compareUtfEntry (BYTE_NAME, pos)) { 923 if (renameNameIndex > 0 && renameNameIndex != cnt[0]) { 925 throw new IllegalStateException (ATTR_NAME + " registered for the second time!"); } 927 928 renameNameIndex = cnt[0]; 929 } 930 931 if (nameIndexes != null) { 932 String s; 934 try { 935 s = new String (arr, pos + 3, len, "utf-8"); } catch (UnsupportedEncodingException ex) { 937 throw new IllegalStateException ("utf-8 is always supported"); } 939 940 int[] index = nameIndexes.get(s); 941 if (index != null) { 942 index[0] = cnt[0]; 943 } 944 } 945 946 return len + 3; 948 default: 949 throw new IllegalStateException ("Unknown pool type: " + get (pos)); } 951 } 952 953 private int memberSize (int pos, int[] containsPatchAttributeAndRename) { 954 int s = 8; 955 readU2(pos + 2); 956 957 int cnt = readU2 (pos + 6); 958 959 while (cnt-- > 0) { 960 s += attributeSize (pos + s, containsPatchAttributeAndRename); 961 } 962 return s; 963 } 964 965 968 private int attributeSize (int pos, int[] containsPatchAttributeAndRename) { 969 int name = readU2 (pos); 971 972 if (name == superClassNameIndex) { 973 if (superClassNameAttr > 0 && superClassNameAttr != pos) { 974 throw new IllegalStateException ("Two attributes with name " + ATTR_SUPERCLASS); } 976 977 superClassNameAttr = pos; 979 } 980 981 if (name == interfacesNameIndex) { 982 if (interfacesNameAttr > 0 && interfacesNameAttr != pos) { 983 throw new IllegalStateException ("Two attributes with name " + ATTR_INTERFACES); } 985 986 interfacesNameAttr = pos; 988 } 989 990 if (name == memberNameIndex && containsPatchAttributeAndRename != null) { 991 if (containsPatchAttributeAndRename[0] != -1) { 992 throw new IllegalStateException ("Second attribute " + ATTR_MEMBER); } 994 containsPatchAttributeAndRename[0] = pos; 995 } 996 997 if (name == renameNameIndex && containsPatchAttributeAndRename != null) { 998 if (containsPatchAttributeAndRename[1] != -1) { 999 throw new IllegalStateException ("Second attribute " + ATTR_NAME); } 1001 containsPatchAttributeAndRename[1] = pos; 1002 } 1003 1004 int len = readU4 (pos + 2); 1005 return len + 6; 1006 } 1007 1008 1009 1011 private boolean compareUtfEntry (byte[] pattern, int pos) { 1012 int len = readU2 (pos + 1); 1013 1014 if (pattern.length != len) { 1015 return false; 1016 } 1017 1018 int base = pos + 3; 1019 for (int i = 0; i < len; i++) { 1021 if (pattern[i] != arr[base + i]) { 1022 return false; 1024 } 1025 } 1026 1027 return true; 1028 } 1029} 1030 | Popular Tags |