1 11 package org.eclipse.jdt.core.search; 12 13 import org.eclipse.jdt.core.*; 14 import org.eclipse.jdt.core.compiler.*; 15 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; 16 import org.eclipse.jdt.internal.compiler.parser.Scanner; 17 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; 18 import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; 19 import org.eclipse.jdt.internal.core.LocalVariable; 20 import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants; 21 import org.eclipse.jdt.internal.core.search.matching.*; 22 23 24 46 public abstract class SearchPattern extends InternalSearchPattern { 47 48 53 public static final int R_EXACT_MATCH = 0; 54 55 58 public static final int R_PREFIX_MATCH = 0x0001; 59 60 65 public static final int R_PATTERN_MATCH = 0x0002; 66 67 70 public static final int R_REGEXP_MATCH = 0x0004; 71 72 76 public static final int R_CASE_SENSITIVE = 0x0008; 77 78 98 public static final int R_ERASURE_MATCH = 0x0010; 99 100 133 public static final int R_EQUIVALENT_MATCH = 0x0020; 134 135 140 public static final int R_FULL_MATCH = 0x0040; 141 142 170 public static final int R_CAMELCASE_MATCH = 0x0080; 171 172 private static final int MODE_MASK = R_EXACT_MATCH | R_PREFIX_MATCH | R_PATTERN_MATCH | R_REGEXP_MATCH; 173 174 private int matchRule; 175 176 191 public SearchPattern(int matchRule) { 192 this.matchRule = matchRule; 193 if ((matchRule & (R_EQUIVALENT_MATCH | R_ERASURE_MATCH )) == 0) { 195 this.matchRule |= R_FULL_MATCH; 196 } 197 } 198 199 244 public static final boolean camelCaseMatch(String pattern, String name) { 245 if (pattern == null) 246 return true; if (name == null) 248 return false; 250 return camelCaseMatch(pattern, 0, pattern.length(), name, 0, name.length()); 251 } 252 253 337 public static final boolean camelCaseMatch(String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd) { 338 if (name == null) 339 return false; if (pattern == null) 341 return true; if (patternEnd < 0) patternEnd = pattern.length(); 343 if (nameEnd < 0) nameEnd = name.length(); 344 345 if (patternEnd <= patternStart) return nameEnd <= nameStart; 346 if (nameEnd <= nameStart) return false; 347 if (name.charAt(nameStart) != pattern.charAt(patternStart)) { 349 return false; 351 } 352 353 char patternChar, nameChar; 354 int iPattern = patternStart; 355 int iName = nameStart; 356 357 while (true) { 359 360 iPattern++; 361 iName++; 362 363 if (iPattern == patternEnd) { 364 return true; 366 } 367 368 if (iName == nameEnd){ 369 return false; 371 } 372 373 if ((patternChar = pattern.charAt(iPattern)) == name.charAt(iName)) { 375 continue; 376 } 377 378 if (patternChar < ScannerHelper.MAX_OBVIOUS) { 380 if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & ScannerHelper.C_UPPER_LETTER) == 0) { 381 return false; 382 } 383 } 384 else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar)) { 385 return false; 386 } 387 388 while (true) { 390 if (iName == nameEnd){ 391 return false; 393 } 394 395 nameChar = name.charAt(iName); 396 397 if (nameChar < ScannerHelper.MAX_OBVIOUS) { 398 if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & (ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_SPECIAL | ScannerHelper.C_DIGIT)) != 0) { 399 iName++; 401 } else if (patternChar != nameChar) { 403 return false; 405 } else { 406 break; 408 } 409 } 410 else if (Character.isJavaIdentifierPart(nameChar) && !Character.isUpperCase(nameChar)) { 411 iName++; 413 } else if (patternChar != nameChar) { 415 return false; 417 } else { 418 break; 420 } 421 } 422 } 425 } 426 427 436 public static SearchPattern createAndPattern(SearchPattern leftPattern, SearchPattern rightPattern) { 437 return MatchLocator.createAndPattern(leftPattern, rightPattern); 438 } 439 440 445 private static SearchPattern createFieldPattern(String patternString, int limitTo, int matchRule) { 446 447 Scanner scanner = new Scanner(false , true , false , ClassFileConstants.JDK1_3, null , null, true); 448 scanner.setSource(patternString.toCharArray()); 449 final int InsideDeclaringPart = 1; 450 final int InsideType = 2; 451 int lastToken = -1; 452 453 String declaringType = null, fieldName = null; 454 String type = null; 455 int mode = InsideDeclaringPart; 456 int token; 457 try { 458 token = scanner.getNextToken(); 459 } catch (InvalidInputException e) { 460 return null; 461 } 462 while (token != TerminalTokens.TokenNameEOF) { 463 switch(mode) { 464 case InsideDeclaringPart : 466 switch (token) { 467 case TerminalTokens.TokenNameDOT: 468 if (declaringType == null) { 469 if (fieldName == null) return null; 470 declaringType = fieldName; 471 } else { 472 String tokenSource = scanner.getCurrentTokenString(); 473 declaringType += tokenSource + fieldName; 474 } 475 fieldName = null; 476 break; 477 case TerminalTokens.TokenNameWHITESPACE: 478 if (!(TerminalTokens.TokenNameWHITESPACE == lastToken || TerminalTokens.TokenNameDOT == lastToken)) 479 mode = InsideType; 480 break; 481 default: if (fieldName == null) 483 fieldName = scanner.getCurrentTokenString(); 484 else 485 fieldName += scanner.getCurrentTokenString(); 486 } 487 break; 488 case InsideType: 490 switch (token) { 491 case TerminalTokens.TokenNameWHITESPACE: 492 break; 493 default: if (type == null) 495 type = scanner.getCurrentTokenString(); 496 else 497 type += scanner.getCurrentTokenString(); 498 } 499 break; 500 } 501 lastToken = token; 502 try { 503 token = scanner.getNextToken(); 504 } catch (InvalidInputException e) { 505 return null; 506 } 507 } 508 if (fieldName == null) return null; 509 510 char[] fieldNameChars = fieldName.toCharArray(); 511 if (fieldNameChars.length == 1 && fieldNameChars[0] == '*') fieldNameChars = null; 512 513 char[] declaringTypeQualification = null, declaringTypeSimpleName = null; 514 char[] typeQualification = null, typeSimpleName = null; 515 516 if (declaringType != null) { 518 char[] declaringTypePart = declaringType.toCharArray(); 519 int lastDotPosition = CharOperation.lastIndexOf('.', declaringTypePart); 520 if (lastDotPosition >= 0) { 521 declaringTypeQualification = CharOperation.subarray(declaringTypePart, 0, lastDotPosition); 522 if (declaringTypeQualification.length == 1 && declaringTypeQualification[0] == '*') 523 declaringTypeQualification = null; 524 declaringTypeSimpleName = CharOperation.subarray(declaringTypePart, lastDotPosition+1, declaringTypePart.length); 525 } else { 526 declaringTypeSimpleName = declaringTypePart; 527 } 528 if (declaringTypeSimpleName.length == 1 && declaringTypeSimpleName[0] == '*') 529 declaringTypeSimpleName = null; 530 } 531 if (type != null) { 533 char[] typePart = type.toCharArray(); 534 int lastDotPosition = CharOperation.lastIndexOf('.', typePart); 535 if (lastDotPosition >= 0) { 536 typeQualification = CharOperation.subarray(typePart, 0, lastDotPosition); 537 if (typeQualification.length == 1 && typeQualification[0] == '*') { 538 typeQualification = null; 539 } else { 540 typeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, typeQualification); 542 } 543 typeSimpleName = CharOperation.subarray(typePart, lastDotPosition+1, typePart.length); 544 } else { 545 typeSimpleName = typePart; 546 } 547 if (typeSimpleName.length == 1 && typeSimpleName[0] == '*') 548 typeSimpleName = null; 549 } 550 boolean findDeclarations = false; 552 boolean readAccess = false; 553 boolean writeAccess = false; 554 switch (limitTo) { 555 case IJavaSearchConstants.DECLARATIONS : 556 findDeclarations = true; 557 break; 558 case IJavaSearchConstants.REFERENCES : 559 readAccess = true; 560 writeAccess = true; 561 break; 562 case IJavaSearchConstants.READ_ACCESSES : 563 readAccess = true; 564 break; 565 case IJavaSearchConstants.WRITE_ACCESSES : 566 writeAccess = true; 567 break; 568 case IJavaSearchConstants.ALL_OCCURRENCES : 569 findDeclarations = true; 570 readAccess = true; 571 writeAccess = true; 572 break; 573 } 574 return new FieldPattern( 575 findDeclarations, 576 readAccess, 577 writeAccess, 578 fieldNameChars, 579 declaringTypeQualification, 580 declaringTypeSimpleName, 581 typeQualification, 582 typeSimpleName, 583 matchRule); 584 } 585 586 604 private static SearchPattern createMethodOrConstructorPattern(String patternString, int limitTo, int matchRule, boolean isConstructor) { 605 606 Scanner scanner = new Scanner(false , true , false , ClassFileConstants.JDK1_3, null , null, true); 607 scanner.setSource(patternString.toCharArray()); 608 final int InsideSelector = 1; 609 final int InsideTypeArguments = 2; 610 final int InsideParameter = 3; 611 final int InsideReturnType = 4; 612 int lastToken = -1; 613 614 String declaringType = null, selector = null, parameterType = null; 615 String [] parameterTypes = null; 616 char[][] typeArguments = null; 617 String typeArgumentsString = null; 618 int parameterCount = -1; 619 String returnType = null; 620 boolean foundClosingParenthesis = false; 621 int mode = InsideSelector; 622 int token, argCount = 0; 623 try { 624 token = scanner.getNextToken(); 625 } catch (InvalidInputException e) { 626 return null; 627 } 628 while (token != TerminalTokens.TokenNameEOF) { 629 switch(mode) { 630 case InsideSelector : 632 if (argCount == 0) { 633 switch (token) { 634 case TerminalTokens.TokenNameLESS: 635 argCount++; 636 if (selector == null || lastToken == TerminalTokens.TokenNameDOT) { 637 if (typeArgumentsString != null) return null; typeArgumentsString = scanner.getCurrentTokenString(); 639 mode = InsideTypeArguments; 640 break; 641 } 642 if (declaringType == null) { 643 declaringType = selector; 644 } else { 645 declaringType += '.' + selector; 646 } 647 declaringType += scanner.getCurrentTokenString(); 648 selector = null; 649 break; 650 case TerminalTokens.TokenNameDOT: 651 if (typeArgumentsString != null) return null; if (declaringType == null) { 653 if (selector == null) return null; declaringType = selector; 655 } else if (selector != null) { 656 declaringType += scanner.getCurrentTokenString() + selector; 657 } 658 selector = null; 659 break; 660 case TerminalTokens.TokenNameLPAREN: 661 parameterTypes = new String [5]; 662 parameterCount = 0; 663 mode = InsideParameter; 664 break; 665 case TerminalTokens.TokenNameWHITESPACE: 666 switch (lastToken) { 667 case TerminalTokens.TokenNameWHITESPACE: 668 case TerminalTokens.TokenNameDOT: 669 case TerminalTokens.TokenNameGREATER: 670 case TerminalTokens.TokenNameRIGHT_SHIFT: 671 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 672 break; 673 default: 674 mode = InsideReturnType; 675 break; 676 } 677 break; 678 default: if (selector == null) 680 selector = scanner.getCurrentTokenString(); 681 else 682 selector += scanner.getCurrentTokenString(); 683 break; 684 } 685 } else { 686 if (declaringType == null) return null; switch (token) { 688 case TerminalTokens.TokenNameGREATER: 689 case TerminalTokens.TokenNameRIGHT_SHIFT: 690 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 691 argCount--; 692 break; 693 case TerminalTokens.TokenNameLESS: 694 argCount++; 695 break; 696 } 697 declaringType += scanner.getCurrentTokenString(); 698 } 699 break; 700 case InsideTypeArguments: 702 if (typeArgumentsString == null) return null; typeArgumentsString += scanner.getCurrentTokenString(); 704 switch (token) { 705 case TerminalTokens.TokenNameGREATER: 706 case TerminalTokens.TokenNameRIGHT_SHIFT: 707 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 708 argCount--; 709 if (argCount == 0) { 710 String pseudoType = "Type"+typeArgumentsString; typeArguments = Signature.getTypeArguments(Signature.createTypeSignature(pseudoType, false).toCharArray()); 712 mode = InsideSelector; 713 } 714 break; 715 case TerminalTokens.TokenNameLESS: 716 argCount++; 717 break; 718 } 719 break; 720 case InsideParameter : 722 if (argCount == 0) { 723 switch (token) { 724 case TerminalTokens.TokenNameWHITESPACE: 725 break; 726 case TerminalTokens.TokenNameCOMMA: 727 if (parameterType == null) return null; 728 if (parameterTypes != null) { 729 if (parameterTypes.length == parameterCount) 730 System.arraycopy(parameterTypes, 0, parameterTypes = new String [parameterCount*2], 0, parameterCount); 731 parameterTypes[parameterCount++] = parameterType; 732 } 733 parameterType = null; 734 break; 735 case TerminalTokens.TokenNameRPAREN: 736 foundClosingParenthesis = true; 737 if (parameterType != null && parameterTypes != null) { 738 if (parameterTypes.length == parameterCount) 739 System.arraycopy(parameterTypes, 0, parameterTypes = new String [parameterCount*2], 0, parameterCount); 740 parameterTypes[parameterCount++] = parameterType; 741 } 742 mode = isConstructor ? InsideTypeArguments : InsideReturnType; 743 break; 744 case TerminalTokens.TokenNameLESS: 745 argCount++; 746 if (parameterType == null) return null; default: if (parameterType == null) 750 parameterType = scanner.getCurrentTokenString(); 751 else 752 parameterType += scanner.getCurrentTokenString(); 753 } 754 } else { 755 if (parameterType == null) return null; switch (token) { 757 case TerminalTokens.TokenNameGREATER: 758 case TerminalTokens.TokenNameRIGHT_SHIFT: 759 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 760 argCount--; 761 break; 762 case TerminalTokens.TokenNameLESS: 763 argCount++; 764 break; 765 } 766 parameterType += scanner.getCurrentTokenString(); 767 } 768 break; 769 case InsideReturnType: 771 if (argCount == 0) { 772 switch (token) { 773 case TerminalTokens.TokenNameWHITESPACE: 774 break; 775 case TerminalTokens.TokenNameLPAREN: 776 parameterTypes = new String [5]; 777 parameterCount = 0; 778 mode = InsideParameter; 779 break; 780 case TerminalTokens.TokenNameLESS: 781 argCount++; 782 if (returnType == null) return null; default: if (returnType == null) 786 returnType = scanner.getCurrentTokenString(); 787 else 788 returnType += scanner.getCurrentTokenString(); 789 } 790 } else { 791 if (returnType == null) return null; switch (token) { 793 case TerminalTokens.TokenNameGREATER: 794 case TerminalTokens.TokenNameRIGHT_SHIFT: 795 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 796 argCount--; 797 break; 798 case TerminalTokens.TokenNameLESS: 799 argCount++; 800 break; 801 } 802 returnType += scanner.getCurrentTokenString(); 803 } 804 break; 805 } 806 lastToken = token; 807 try { 808 token = scanner.getNextToken(); 809 } catch (InvalidInputException e) { 810 return null; 811 } 812 } 813 if (parameterCount>0 && !foundClosingParenthesis) return null; 815 if (argCount > 0) return null; 817 818 char[] selectorChars = null; 819 if (isConstructor) { 820 if (declaringType == null) 822 declaringType = selector; 823 else if (selector != null) 824 declaringType += '.' + selector; 825 } else { 826 if (selector == null) return null; 828 selectorChars = selector.toCharArray(); 829 if (selectorChars.length == 1 && selectorChars[0] == '*') 830 selectorChars = null; 831 } 832 833 char[] declaringTypeQualification = null, declaringTypeSimpleName = null; 834 char[] returnTypeQualification = null, returnTypeSimpleName = null; 835 char[][] parameterTypeQualifications = null, parameterTypeSimpleNames = null; 836 String declaringTypeSignature = null; 838 String returnTypeSignature = null; 839 String [] parameterTypeSignatures = null; 840 841 if (declaringType != null) { 843 char[] declaringTypePart = null; 845 try { 846 declaringTypeSignature = Signature.createTypeSignature(declaringType, false); 847 if (declaringTypeSignature.indexOf(Signature.C_GENERIC_START) < 0) { 848 declaringTypePart = declaringType.toCharArray(); 849 } else { 850 declaringTypePart = Signature.toCharArray(Signature.getTypeErasure(declaringTypeSignature.toCharArray())); 851 } 852 } 853 catch (IllegalArgumentException iae) { 854 return null; 856 } 857 int lastDotPosition = CharOperation.lastIndexOf('.', declaringTypePart); 858 if (lastDotPosition >= 0) { 859 declaringTypeQualification = CharOperation.subarray(declaringTypePart, 0, lastDotPosition); 860 if (declaringTypeQualification.length == 1 && declaringTypeQualification[0] == '*') 861 declaringTypeQualification = null; 862 declaringTypeSimpleName = CharOperation.subarray(declaringTypePart, lastDotPosition+1, declaringTypePart.length); 863 } else { 864 declaringTypeSimpleName = declaringTypePart; 865 } 866 if (declaringTypeSimpleName.length == 1 && declaringTypeSimpleName[0] == '*') 867 declaringTypeSimpleName = null; 868 } 869 if (parameterCount >= 0) { 871 parameterTypeQualifications = new char[parameterCount][]; 872 parameterTypeSimpleNames = new char[parameterCount][]; 873 parameterTypeSignatures = new String [parameterCount]; 874 for (int i = 0; i < parameterCount; i++) { 875 char[] parameterTypePart = null; 877 try { 878 if (parameterTypes != null) { 879 parameterTypeSignatures[i] = Signature.createTypeSignature(parameterTypes[i], false); 880 if (parameterTypeSignatures[i].indexOf(Signature.C_GENERIC_START) < 0) { 881 parameterTypePart = parameterTypes[i].toCharArray(); 882 } else { 883 parameterTypePart = Signature.toCharArray(Signature.getTypeErasure(parameterTypeSignatures[i].toCharArray())); 884 } 885 } 886 } 887 catch (IllegalArgumentException iae) { 888 return null; 890 } 891 int lastDotPosition = parameterTypePart==null ? -1 : CharOperation.lastIndexOf('.', parameterTypePart); 892 if (parameterTypePart != null && lastDotPosition >= 0) { 893 parameterTypeQualifications[i] = CharOperation.subarray(parameterTypePart, 0, lastDotPosition); 894 if (parameterTypeQualifications[i].length == 1 && parameterTypeQualifications[i][0] == '*') { 895 parameterTypeQualifications[i] = null; 896 } else { 897 parameterTypeQualifications[i] = CharOperation.concat(IIndexConstants.ONE_STAR, parameterTypeQualifications[i]); 899 } 900 parameterTypeSimpleNames[i] = CharOperation.subarray(parameterTypePart, lastDotPosition+1, parameterTypePart.length); 901 } else { 902 parameterTypeQualifications[i] = null; 903 parameterTypeSimpleNames[i] = parameterTypePart; 904 } 905 if (parameterTypeSimpleNames[i].length == 1 && parameterTypeSimpleNames[i][0] == '*') 906 parameterTypeSimpleNames[i] = null; 907 } 908 } 909 if (returnType != null) { 911 char[] returnTypePart = null; 913 try { 914 returnTypeSignature = Signature.createTypeSignature(returnType, false); 915 if (returnTypeSignature.indexOf(Signature.C_GENERIC_START) < 0) { 916 returnTypePart = returnType.toCharArray(); 917 } else { 918 returnTypePart = Signature.toCharArray(Signature.getTypeErasure(returnTypeSignature.toCharArray())); 919 } 920 } 921 catch (IllegalArgumentException iae) { 922 return null; 924 } 925 int lastDotPosition = CharOperation.lastIndexOf('.', returnTypePart); 926 if (lastDotPosition >= 0) { 927 returnTypeQualification = CharOperation.subarray(returnTypePart, 0, lastDotPosition); 928 if (returnTypeQualification.length == 1 && returnTypeQualification[0] == '*') { 929 returnTypeQualification = null; 930 } else { 931 returnTypeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, returnTypeQualification); 933 } 934 returnTypeSimpleName = CharOperation.subarray(returnTypePart, lastDotPosition+1, returnTypePart.length); 935 } else { 936 returnTypeSimpleName = returnTypePart; 937 } 938 if (returnTypeSimpleName.length == 1 && returnTypeSimpleName[0] == '*') 939 returnTypeSimpleName = null; 940 } 941 boolean findDeclarations = true; 943 boolean findReferences = true; 944 switch (limitTo) { 945 case IJavaSearchConstants.DECLARATIONS : 946 findReferences = false; 947 break; 948 case IJavaSearchConstants.REFERENCES : 949 findDeclarations = false; 950 break; 951 case IJavaSearchConstants.ALL_OCCURRENCES : 952 break; 953 } 954 if (isConstructor) { 955 return new ConstructorPattern( 956 findDeclarations, 957 findReferences, 958 declaringTypeSimpleName, 959 declaringTypeQualification, 960 declaringTypeSignature, 961 parameterTypeQualifications, 962 parameterTypeSimpleNames, 963 parameterTypeSignatures, 964 typeArguments, 965 matchRule); 966 } else { 967 return new MethodPattern( 968 findDeclarations, 969 findReferences, 970 selectorChars, 971 declaringTypeQualification, 972 declaringTypeSimpleName, 973 declaringTypeSignature, 974 returnTypeQualification, 975 returnTypeSimpleName, 976 returnTypeSignature, 977 parameterTypeQualifications, 978 parameterTypeSimpleNames, 979 parameterTypeSignatures, 980 typeArguments, 981 matchRule); 982 } 983 } 984 985 994 public static SearchPattern createOrPattern(SearchPattern leftPattern, SearchPattern rightPattern) { 995 return new OrPattern(leftPattern, rightPattern); 996 } 997 998 private static SearchPattern createPackagePattern(String patternString, int limitTo, int matchRule) { 999 switch (limitTo) { 1000 case IJavaSearchConstants.DECLARATIONS : 1001 return new PackageDeclarationPattern(patternString.toCharArray(), matchRule); 1002 case IJavaSearchConstants.REFERENCES : 1003 return new PackageReferencePattern(patternString.toCharArray(), matchRule); 1004 case IJavaSearchConstants.ALL_OCCURRENCES : 1005 return new OrPattern( 1006 new PackageDeclarationPattern(patternString.toCharArray(), matchRule), 1007 new PackageReferencePattern(patternString.toCharArray(), matchRule) 1008 ); 1009 } 1010 return null; 1011} 1012 1013 1072public static SearchPattern createPattern(String stringPattern, int searchFor, int limitTo, int matchRule) { 1073 if (stringPattern == null || stringPattern.length() == 0) return null; 1074 1075 if ((matchRule = validateMatchRule(stringPattern, matchRule)) == -1) { 1076 return null; 1077 } 1078 1079 limitTo &= ~(IJavaSearchConstants.IGNORE_DECLARING_TYPE+IJavaSearchConstants.IGNORE_RETURN_TYPE); 1081 1082 switch (searchFor) { 1083 case IJavaSearchConstants.CLASS: 1084 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.CLASS_SUFFIX); 1085 case IJavaSearchConstants.CLASS_AND_INTERFACE: 1086 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.CLASS_AND_INTERFACE_SUFFIX); 1087 case IJavaSearchConstants.CLASS_AND_ENUM: 1088 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.CLASS_AND_ENUM_SUFFIX); 1089 case IJavaSearchConstants.INTERFACE: 1090 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.INTERFACE_SUFFIX); 1091 case IJavaSearchConstants.INTERFACE_AND_ANNOTATION: 1092 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.INTERFACE_AND_ANNOTATION_SUFFIX); 1093 case IJavaSearchConstants.ENUM: 1094 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.ENUM_SUFFIX); 1095 case IJavaSearchConstants.ANNOTATION_TYPE: 1096 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.ANNOTATION_TYPE_SUFFIX); 1097 case IJavaSearchConstants.TYPE: 1098 return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.TYPE_SUFFIX); 1099 case IJavaSearchConstants.METHOD: 1100 return createMethodOrConstructorPattern(stringPattern, limitTo, matchRule, false); 1101 case IJavaSearchConstants.CONSTRUCTOR: 1102 return createMethodOrConstructorPattern(stringPattern, limitTo, matchRule, true); 1103 case IJavaSearchConstants.FIELD: 1104 return createFieldPattern(stringPattern, limitTo, matchRule); 1105 case IJavaSearchConstants.PACKAGE: 1106 return createPackagePattern(stringPattern, limitTo, matchRule); 1107 } 1108 return null; 1109} 1110 1111 1161public static SearchPattern createPattern(IJavaElement element, int limitTo) { 1162 return createPattern(element, limitTo, R_EXACT_MATCH | R_CASE_SENSITIVE | R_ERASURE_MATCH); 1163} 1164 1165 1218public static SearchPattern createPattern(IJavaElement element, int limitTo, int matchRule) { 1219 SearchPattern searchPattern = null; 1220 int lastDot; 1221 boolean ignoreDeclaringType = false; 1222 boolean ignoreReturnType = false; 1223 int maskedLimitTo = limitTo & ~(IJavaSearchConstants.IGNORE_DECLARING_TYPE+IJavaSearchConstants.IGNORE_RETURN_TYPE); 1224 if (maskedLimitTo == IJavaSearchConstants.DECLARATIONS || maskedLimitTo == IJavaSearchConstants.ALL_OCCURRENCES) { 1225 ignoreDeclaringType = (limitTo & IJavaSearchConstants.IGNORE_DECLARING_TYPE) != 0; 1226 ignoreReturnType = (limitTo & IJavaSearchConstants.IGNORE_RETURN_TYPE) != 0; 1227 } 1228 char[] declaringSimpleName = null; 1229 char[] declaringQualification = null; 1230 switch (element.getElementType()) { 1231 case IJavaElement.FIELD : 1232 IField field = (IField) element; 1233 if (!ignoreDeclaringType) { 1234 IType declaringClass = field.getDeclaringType(); 1235 declaringSimpleName = declaringClass.getElementName().toCharArray(); 1236 declaringQualification = declaringClass.getPackageFragment().getElementName().toCharArray(); 1237 char[][] enclosingNames = enclosingTypeNames(declaringClass); 1238 if (enclosingNames.length > 0) { 1239 declaringQualification = CharOperation.concat(declaringQualification, CharOperation.concatWith(enclosingNames, '.'), '.'); 1240 } 1241 } 1242 char[] name = field.getElementName().toCharArray(); 1243 char[] typeSimpleName = null; 1244 char[] typeQualification = null; 1245 String typeSignature = null; 1246 if (!ignoreReturnType) { 1247 try { 1248 typeSignature = field.getTypeSignature(); 1249 char[] signature = typeSignature.toCharArray(); 1250 char[] typeErasure = Signature.toCharArray(Signature.getTypeErasure(signature)); 1251 CharOperation.replace(typeErasure, '$', '.'); 1252 if ((lastDot = CharOperation.lastIndexOf('.', typeErasure)) == -1) { 1253 typeSimpleName = typeErasure; 1254 } else { 1255 typeSimpleName = CharOperation.subarray(typeErasure, lastDot + 1, typeErasure.length); 1256 typeQualification = CharOperation.subarray(typeErasure, 0, lastDot); 1257 if (!field.isBinary()) { 1258 typeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, typeQualification); 1260 } 1261 } 1262 } catch (JavaModelException e) { 1263 return null; 1264 } 1265 } 1266 boolean findDeclarations = false; 1268 boolean readAccess = false; 1269 boolean writeAccess = false; 1270 switch (maskedLimitTo) { 1271 case IJavaSearchConstants.DECLARATIONS : 1272 findDeclarations = true; 1273 break; 1274 case IJavaSearchConstants.REFERENCES : 1275 readAccess = true; 1276 writeAccess = true; 1277 break; 1278 case IJavaSearchConstants.READ_ACCESSES : 1279 readAccess = true; 1280 break; 1281 case IJavaSearchConstants.WRITE_ACCESSES : 1282 writeAccess = true; 1283 break; 1284 case IJavaSearchConstants.ALL_OCCURRENCES : 1285 findDeclarations = true; 1286 readAccess = true; 1287 writeAccess = true; 1288 break; 1289 } 1290 searchPattern = 1291 new FieldPattern( 1292 findDeclarations, 1293 readAccess, 1294 writeAccess, 1295 name, 1296 declaringQualification, 1297 declaringSimpleName, 1298 typeQualification, 1299 typeSimpleName, 1300 typeSignature, 1301 matchRule); 1302 break; 1303 case IJavaElement.IMPORT_DECLARATION : 1304 String elementName = element.getElementName(); 1305 lastDot = elementName.lastIndexOf('.'); 1306 if (lastDot == -1) return null; IImportDeclaration importDecl = (IImportDeclaration)element; 1308 if (importDecl.isOnDemand()) { 1309 searchPattern = createPackagePattern(elementName.substring(0, lastDot), maskedLimitTo, matchRule); 1310 } else { 1311 searchPattern = 1312 createTypePattern( 1313 elementName.substring(lastDot+1).toCharArray(), 1314 elementName.substring(0, lastDot).toCharArray(), 1315 null, 1316 null, 1317 null, 1318 maskedLimitTo, 1319 matchRule); 1320 } 1321 break; 1322 case IJavaElement.LOCAL_VARIABLE : 1323 LocalVariable localVar = (LocalVariable) element; 1324 boolean findVarDeclarations = false; 1325 boolean findVarReadAccess = false; 1326 boolean findVarWriteAccess = false; 1327 switch (maskedLimitTo) { 1328 case IJavaSearchConstants.DECLARATIONS : 1329 findVarDeclarations = true; 1330 break; 1331 case IJavaSearchConstants.REFERENCES : 1332 findVarReadAccess = true; 1333 findVarWriteAccess = true; 1334 break; 1335 case IJavaSearchConstants.READ_ACCESSES : 1336 findVarReadAccess = true; 1337 break; 1338 case IJavaSearchConstants.WRITE_ACCESSES : 1339 findVarWriteAccess = true; 1340 break; 1341 case IJavaSearchConstants.ALL_OCCURRENCES : 1342 findVarDeclarations = true; 1343 findVarReadAccess = true; 1344 findVarWriteAccess = true; 1345 break; 1346 } 1347 searchPattern = 1348 new LocalVariablePattern( 1349 findVarDeclarations, 1350 findVarReadAccess, 1351 findVarWriteAccess, 1352 localVar, 1353 matchRule); 1354 break; 1355 case IJavaElement.TYPE_PARAMETER: 1356 ITypeParameter typeParam = (ITypeParameter) element; 1357 boolean findParamDeclarations = true; 1358 boolean findParamReferences = true; 1359 switch (maskedLimitTo) { 1360 case IJavaSearchConstants.DECLARATIONS : 1361 findParamReferences = false; 1362 break; 1363 case IJavaSearchConstants.REFERENCES : 1364 findParamDeclarations = false; 1365 break; 1366 } 1367 searchPattern = 1368 new TypeParameterPattern( 1369 findParamDeclarations, 1370 findParamReferences, 1371 typeParam, 1372 matchRule); 1373 break; 1374 case IJavaElement.METHOD : 1375 IMethod method = (IMethod) element; 1376 boolean isConstructor; 1377 try { 1378 isConstructor = method.isConstructor(); 1379 } catch (JavaModelException e) { 1380 return null; 1381 } 1382 IType declaringClass = method.getDeclaringType(); 1383 if (ignoreDeclaringType) { 1384 if (isConstructor) declaringSimpleName = declaringClass.getElementName().toCharArray(); 1385 } else { 1386 declaringSimpleName = declaringClass.getElementName().toCharArray(); 1387 declaringQualification = declaringClass.getPackageFragment().getElementName().toCharArray(); 1388 char[][] enclosingNames = enclosingTypeNames(declaringClass); 1389 if (enclosingNames.length > 0) { 1390 declaringQualification = CharOperation.concat(declaringQualification, CharOperation.concatWith(enclosingNames, '.'), '.'); 1391 } 1392 } 1393 char[] selector = method.getElementName().toCharArray(); 1394 char[] returnSimpleName = null; 1395 char[] returnQualification = null; 1396 String returnSignature = null; 1397 if (!ignoreReturnType) { 1398 try { 1399 returnSignature = method.getReturnType(); 1400 char[] signature = returnSignature.toCharArray(); 1401 char[] returnErasure = Signature.toCharArray(Signature.getTypeErasure(signature)); 1402 CharOperation.replace(returnErasure, '$', '.'); 1403 if ((lastDot = CharOperation.lastIndexOf('.', returnErasure)) == -1) { 1404 returnSimpleName = returnErasure; 1405 } else { 1406 returnSimpleName = CharOperation.subarray(returnErasure, lastDot + 1, returnErasure.length); 1407 returnQualification = CharOperation.subarray(returnErasure, 0, lastDot); 1408 if (!method.isBinary()) { 1409 CharOperation.concat(IIndexConstants.ONE_STAR, returnQualification); 1411 } 1412 } 1413 } catch (JavaModelException e) { 1414 return null; 1415 } 1416 } 1417 String [] parameterTypes = method.getParameterTypes(); 1418 int paramCount = parameterTypes.length; 1419 char[][] parameterSimpleNames = new char[paramCount][]; 1420 char[][] parameterQualifications = new char[paramCount][]; 1421 String [] parameterSignatures = new String [paramCount]; 1422 for (int i = 0; i < paramCount; i++) { 1423 parameterSignatures[i] = parameterTypes[i]; 1424 char[] signature = parameterSignatures[i].toCharArray(); 1425 char[] paramErasure = Signature.toCharArray(Signature.getTypeErasure(signature)); 1426 CharOperation.replace(paramErasure, '$', '.'); 1427 if ((lastDot = CharOperation.lastIndexOf('.', paramErasure)) == -1) { 1428 parameterSimpleNames[i] = paramErasure; 1429 parameterQualifications[i] = null; 1430 } else { 1431 parameterSimpleNames[i] = CharOperation.subarray(paramErasure, lastDot + 1, paramErasure.length); 1432 parameterQualifications[i] = CharOperation.subarray(paramErasure, 0, lastDot); 1433 if (!method.isBinary()) { 1434 CharOperation.concat(IIndexConstants.ONE_STAR, parameterQualifications[i]); 1436 } 1437 } 1438 } 1439 1440 boolean findMethodDeclarations = true; 1442 boolean findMethodReferences = true; 1443 switch (maskedLimitTo) { 1444 case IJavaSearchConstants.DECLARATIONS : 1445 findMethodReferences = false; 1446 break; 1447 case IJavaSearchConstants.REFERENCES : 1448 findMethodDeclarations = false; 1449 break; 1450 case IJavaSearchConstants.ALL_OCCURRENCES : 1451 break; 1452 } 1453 if (isConstructor) { 1454 searchPattern = 1455 new ConstructorPattern( 1456 findMethodDeclarations, 1457 findMethodReferences, 1458 declaringSimpleName, 1459 declaringQualification, 1460 parameterQualifications, 1461 parameterSimpleNames, 1462 parameterSignatures, 1463 method, 1464 matchRule); 1465 } else { 1466 searchPattern = 1467 new MethodPattern( 1468 findMethodDeclarations, 1469 findMethodReferences, 1470 selector, 1471 declaringQualification, 1472 declaringSimpleName, 1473 returnQualification, 1474 returnSimpleName, 1475 returnSignature, 1476 parameterQualifications, 1477 parameterSimpleNames, 1478 parameterSignatures, 1479 method, 1480 matchRule); 1481 } 1482 break; 1483 case IJavaElement.TYPE : 1484 IType type = (IType)element; 1485 searchPattern = createTypePattern( 1486 type.getElementName().toCharArray(), 1487 type.getPackageFragment().getElementName().toCharArray(), 1488 ignoreDeclaringType ? null : enclosingTypeNames(type), 1489 null, 1490 type, 1491 maskedLimitTo, 1492 matchRule); 1493 break; 1494 case IJavaElement.PACKAGE_DECLARATION : 1495 case IJavaElement.PACKAGE_FRAGMENT : 1496 searchPattern = createPackagePattern(element.getElementName(), maskedLimitTo, matchRule); 1497 break; 1498 } 1499 if (searchPattern != null) 1500 MatchLocator.setFocus(searchPattern, element); 1501 return searchPattern; 1502} 1503 1504private static SearchPattern createTypePattern(char[] simpleName, char[] packageName, char[][] enclosingTypeNames, String typeSignature, IType type, int limitTo, int matchRule) { 1505 switch (limitTo) { 1506 case IJavaSearchConstants.DECLARATIONS : 1507 return new TypeDeclarationPattern( 1508 packageName, 1509 enclosingTypeNames, 1510 simpleName, 1511 IIndexConstants.TYPE_SUFFIX, 1512 matchRule); 1513 case IJavaSearchConstants.REFERENCES : 1514 if (type != null) { 1515 return new TypeReferencePattern( 1516 CharOperation.concatWith(packageName, enclosingTypeNames, '.'), 1517 simpleName, 1518 type, 1519 matchRule); 1520 } 1521 return new TypeReferencePattern( 1522 CharOperation.concatWith(packageName, enclosingTypeNames, '.'), 1523 simpleName, 1524 typeSignature, 1525 matchRule); 1526 case IJavaSearchConstants.IMPLEMENTORS : 1527 return new SuperTypeReferencePattern( 1528 CharOperation.concatWith(packageName, enclosingTypeNames, '.'), 1529 simpleName, 1530 SuperTypeReferencePattern.ONLY_SUPER_INTERFACES, 1531 matchRule); 1532 case IJavaSearchConstants.ALL_OCCURRENCES : 1533 return new OrPattern( 1534 new TypeDeclarationPattern( 1535 packageName, 1536 enclosingTypeNames, 1537 simpleName, 1538 IIndexConstants.TYPE_SUFFIX, 1539 matchRule), 1540 (type != null) 1541 ? new TypeReferencePattern( 1542 CharOperation.concatWith(packageName, enclosingTypeNames, '.'), 1543 simpleName, 1544 type, 1545 matchRule) 1546 : new TypeReferencePattern( 1547 CharOperation.concatWith(packageName, enclosingTypeNames, '.'), 1548 simpleName, 1549 typeSignature, 1550 matchRule) 1551 ); 1552 } 1553 return null; 1554} 1555 1568private static SearchPattern createTypePattern(String patternString, int limitTo, int matchRule, char indexSuffix) { 1569 1570 Scanner scanner = new Scanner(false , true , false , ClassFileConstants.JDK1_3, null , null, true); 1571 scanner.setSource(patternString.toCharArray()); 1572 String type = null; 1573 int token; 1574 try { 1575 token = scanner.getNextToken(); 1576 } catch (InvalidInputException e) { 1577 return null; 1578 } 1579 int argCount = 0; 1580 while (token != TerminalTokens.TokenNameEOF) { 1581 if (argCount == 0) { 1582 switch (token) { 1583 case TerminalTokens.TokenNameWHITESPACE: 1584 break; 1585 case TerminalTokens.TokenNameLESS: 1586 argCount++; 1587 default: if (type == null) 1590 type = scanner.getCurrentTokenString(); 1591 else 1592 type += scanner.getCurrentTokenString(); 1593 } 1594 } else { 1595 switch (token) { 1596 case TerminalTokens.TokenNameGREATER: 1597 case TerminalTokens.TokenNameRIGHT_SHIFT: 1598 case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: 1599 argCount--; 1600 break; 1601 case TerminalTokens.TokenNameLESS: 1602 argCount++; 1603 break; 1604 } 1605 if (type == null) return null; type += scanner.getCurrentTokenString(); 1607 } 1608 try { 1609 token = scanner.getNextToken(); 1610 } catch (InvalidInputException e) { 1611 return null; 1612 } 1613 } 1614 if (type == null) return null; 1615 String typeSignature = null; 1616 char[] qualificationChars = null, typeChars = null; 1617 1618 char[] typePart = null; 1620 try { 1621 typeSignature = Signature.createTypeSignature(type, false); 1622 if (typeSignature.indexOf(Signature.C_GENERIC_START) < 0) { 1623 typePart = type.toCharArray(); 1624 } else { 1625 typePart = Signature.toCharArray(Signature.getTypeErasure(typeSignature.toCharArray())); 1626 } 1627 } 1628 catch (IllegalArgumentException iae) { 1629 return null; 1631 } 1632 1633 int lastDotPosition = CharOperation.lastIndexOf('.', typePart); 1635 if (lastDotPosition >= 0) { 1636 qualificationChars = CharOperation.subarray(typePart, 0, lastDotPosition); 1637 if (qualificationChars.length == 1 && qualificationChars[0] == '*') 1638 qualificationChars = null; 1639 typeChars = CharOperation.subarray(typePart, lastDotPosition+1, typePart.length); 1640 } else { 1641 typeChars = typePart; 1642 } 1643 if (typeChars.length == 1 && typeChars[0] == '*') { 1644 typeChars = null; 1645 } 1646 switch (limitTo) { 1647 case IJavaSearchConstants.DECLARATIONS : return new QualifiedTypeDeclarationPattern(qualificationChars, typeChars, indexSuffix, matchRule); 1649 case IJavaSearchConstants.REFERENCES : 1650 return new TypeReferencePattern(qualificationChars, typeChars, typeSignature, matchRule); 1651 case IJavaSearchConstants.IMPLEMENTORS : 1652 return new SuperTypeReferencePattern(qualificationChars, typeChars, SuperTypeReferencePattern.ONLY_SUPER_INTERFACES, indexSuffix, matchRule); 1653 case IJavaSearchConstants.ALL_OCCURRENCES : 1654 return new OrPattern( 1655 new QualifiedTypeDeclarationPattern(qualificationChars, typeChars, indexSuffix, matchRule), new TypeReferencePattern(qualificationChars, typeChars, matchRule)); 1657 } 1658 return null; 1659} 1660 1663private static char[][] enclosingTypeNames(IType type) { 1664 IJavaElement parent = type.getParent(); 1665 switch (parent.getElementType()) { 1666 case IJavaElement.CLASS_FILE: 1667 IType declaringType = type.getDeclaringType(); 1670 if (declaringType == null) return CharOperation.NO_CHAR_CHAR; 1671 return CharOperation.arrayConcat( 1672 enclosingTypeNames(declaringType), 1673 declaringType.getElementName().toCharArray()); 1674 case IJavaElement.COMPILATION_UNIT: 1675 return CharOperation.NO_CHAR_CHAR; 1676 case IJavaElement.FIELD: 1677 case IJavaElement.INITIALIZER: 1678 case IJavaElement.METHOD: 1679 IType declaringClass = ((IMember) parent).getDeclaringType(); 1680 return CharOperation.arrayConcat( 1681 enclosingTypeNames(declaringClass), 1682 new char[][] {declaringClass.getElementName().toCharArray(), IIndexConstants.ONE_STAR}); 1683 case IJavaElement.TYPE: 1684 return CharOperation.arrayConcat( 1685 enclosingTypeNames((IType)parent), 1686 parent.getElementName().toCharArray()); 1687 default: 1688 return null; 1689 } 1690} 1691 1692 1702public void decodeIndexKey(char[] key) { 1703 } 1705 1715public abstract SearchPattern getBlankPattern(); 1716 1728public char[] getIndexKey() { 1729 return null; } 1731 1743public char[][] getIndexCategories() { 1744 return CharOperation.NO_CHAR_CHAR; } 1746 1755public final int getMatchRule() { 1756 return this.matchRule; 1757} 1758 1768public boolean matchesDecodedKey(SearchPattern decodedPattern) { 1769 return true; } 1771 1772 1783public boolean matchesName(char[] pattern, char[] name) { 1784 if (pattern == null) return true; if (name != null) { 1786 boolean isCaseSensitive = (this.matchRule & R_CASE_SENSITIVE) != 0; 1787 boolean isCamelCase = (this.matchRule & R_CAMELCASE_MATCH) != 0; 1788 int matchMode = this.matchRule & MODE_MASK; 1789 boolean emptyPattern = pattern.length == 0; 1790 if (matchMode == R_PREFIX_MATCH && emptyPattern) return true; 1791 boolean sameLength = pattern.length == name.length; 1792 boolean canBePrefix = name.length >= pattern.length; 1793 boolean matchFirstChar = !isCaseSensitive || emptyPattern || (name.length > 0 && pattern[0] == name[0]); 1794 if (isCamelCase && matchFirstChar && CharOperation.camelCaseMatch(pattern, name)) { 1795 return true; 1796 } 1797 switch (matchMode) { 1798 case R_EXACT_MATCH : 1799 case R_FULL_MATCH : 1800 if (!isCamelCase) { 1801 if (sameLength && matchFirstChar) { 1802 return CharOperation.equals(pattern, name, isCaseSensitive); 1803 } 1804 break; 1805 } 1806 case R_PREFIX_MATCH : 1808 if (canBePrefix && matchFirstChar) { 1809 return CharOperation.prefixEquals(pattern, name, isCaseSensitive); 1810 } 1811 break; 1812 1813 case R_PATTERN_MATCH : 1814 if (!isCaseSensitive) 1815 pattern = CharOperation.toLowerCase(pattern); 1816 return CharOperation.match(pattern, name, isCaseSensitive); 1817 1818 case R_REGEXP_MATCH : 1819 return true; 1821 } 1822 } 1823 return false; 1824} 1825 1826 1859public static int validateMatchRule(String stringPattern, int matchRule) { 1860 1861 if ((matchRule & R_REGEXP_MATCH) != 0) { 1863 if ((matchRule & R_PATTERN_MATCH) != 0 || (matchRule & R_PREFIX_MATCH) != 0 || (matchRule & R_CAMELCASE_MATCH) != 0) { 1864 return -1; 1865 } 1866 } 1867 1868 int starIndex = stringPattern.indexOf('*'); 1870 int questionIndex = stringPattern.indexOf('?'); 1871 if (starIndex < 0 && questionIndex < 0) { 1872 matchRule &= ~R_PATTERN_MATCH; 1874 } else { 1875 matchRule |= R_PATTERN_MATCH; 1877 } 1878 if ((matchRule & R_PATTERN_MATCH) != 0) { 1879 matchRule &= ~R_CAMELCASE_MATCH; 1881 matchRule &= ~R_PREFIX_MATCH; 1882 } 1883 1884 if ((matchRule & R_CAMELCASE_MATCH) != 0) { 1886 int length = stringPattern.length(); 1888 boolean validCamelCase = true; 1889 boolean uppercase = false; 1890 for (int i=0; i<length && validCamelCase; i++) { 1891 char ch = stringPattern.charAt(i); 1892 validCamelCase = ScannerHelper.isJavaIdentifierStart(ch); 1893 if (!uppercase) uppercase = ScannerHelper.isUpperCase(ch); 1896 } 1897 validCamelCase = validCamelCase && uppercase; 1898 if (validCamelCase) { 1900 if ((matchRule & R_PREFIX_MATCH) != 0) { 1901 if ((matchRule & R_CASE_SENSITIVE) != 0) { 1902 matchRule &= ~R_PREFIX_MATCH; 1904 matchRule &= ~R_CASE_SENSITIVE; 1905 } 1906 } 1907 } else { 1908 matchRule &= ~R_CAMELCASE_MATCH; 1909 if ((matchRule & R_PREFIX_MATCH) == 0) { 1910 matchRule |= R_PREFIX_MATCH; 1911 matchRule |= R_CASE_SENSITIVE; 1912 } 1913 } 1914 } 1915 return matchRule; 1916} 1917 1918 1921public String toString() { 1922 return "SearchPattern"; } 1924} 1925 | Popular Tags |