/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.core.search; import org.eclipse.wst.jsdt.core.IField; import org.eclipse.wst.jsdt.core.IFunction; import org.eclipse.wst.jsdt.core.IImportDeclaration; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.core.IMember; import org.eclipse.wst.jsdt.core.IType; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.core.Signature; import org.eclipse.wst.jsdt.core.compiler.CharOperation; import org.eclipse.wst.jsdt.core.compiler.InvalidInputException; import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.wst.jsdt.internal.compiler.parser.Scanner; import org.eclipse.wst.jsdt.internal.compiler.parser.ScannerHelper; import org.eclipse.wst.jsdt.internal.compiler.parser.TerminalTokens; import org.eclipse.wst.jsdt.internal.core.LocalVariable; import org.eclipse.wst.jsdt.internal.core.search.indexing.IIndexConstants; import org.eclipse.wst.jsdt.internal.core.search.matching.ConstructorPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.FieldPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.InternalSearchPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.LocalVariablePattern; import org.eclipse.wst.jsdt.internal.core.search.matching.MatchLocator; import org.eclipse.wst.jsdt.internal.core.search.matching.MethodPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.OrPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.PackageDeclarationPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.PackageReferencePattern; import org.eclipse.wst.jsdt.internal.core.search.matching.QualifiedTypeDeclarationPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.SuperTypeReferencePattern; import org.eclipse.wst.jsdt.internal.core.search.matching.TypeDeclarationPattern; import org.eclipse.wst.jsdt.internal.core.search.matching.TypeReferencePattern; /** * A search pattern defines how search results are found. Use <code>SearchPattern.createPattern</code> * to create a search pattern. * <p> * Search patterns are used during the search phase to decode index entries that were added during the indexing phase * (see {@link SearchDocument#addIndexEntry(char[], char[])}). When an index is queried, the * index categories and keys to consider are retrieved from the search pattern using {@link #getIndexCategories()} and * {@link #getIndexKey()}, as well as the match rule (see {@link #getMatchRule()}). A blank pattern is * then created (see {@link #getBlankPattern()}). This blank pattern is used as a record as follows. * For each index entry in the given index categories and that starts with the given key, the blank pattern is fed using * {@link #decodeIndexKey(char[])}. The original pattern is then asked if it matches the decoded key using * {@link #matchesDecodedKey(SearchPattern)}. If it matches, a search doument is created for this index entry * using {@link SearchParticipant#getDocument(String)}. * * </p><p> * This class is intended to be subclassed by clients. A default behavior is provided for each of the methods above, that * clients can ovveride if they wish. * </p> * @see #createPattern(org.eclipse.wst.jsdt.core.IJavaScriptElement, int) * @see #createPattern(String, int, int, int) * * Provisional API: This class/interface is part of an interim API that is still under development and expected to * change significantly before reaching stability. It is being made available at this early stage to solicit feedback * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken * (repeatedly) as the API evolves. */ public abstract class SearchPattern extends InternalSearchPattern { // Rules for pattern matching: (exact, prefix, pattern) [ | case sensitive] /** * Match rule: The search pattern matches exactly the search result, * that is, the source of the search result equals the search pattern. */ public static final int R_EXACT_MATCH = 0; /** * Match rule: The search pattern is a prefix of the search result. */ public static final int R_PREFIX_MATCH = 0x0001; /** * Match rule: The search pattern contains one or more wild cards ('*' or '?'). * A '*' wild-card can replace 0 or more characters in the search result. * A '?' wild-card replaces exactly 1 character in the search result. */ public static final int R_PATTERN_MATCH = 0x0002; /** * Match rule: The search pattern contains a regular expression. */ public static final int R_REGEXP_MATCH = 0x0004; /** * Match rule: The search pattern matches the search result only if cases are the same. * Can be combined to previous rules, e.g. {@link #R_EXACT_MATCH} | {@link #R_CASE_SENSITIVE} */ public static final int R_CASE_SENSITIVE = 0x0008; /** * Match rule: The search pattern matches search results as raw/parameterized types/methods with same erasure. * This mode has no effect on other javascript elements search.<br> * Type search example: * <ul> * <li>pattern: <code>List<Exception></code></li> * <li>match: <code>List<Object></code></li> * </ul> * Method search example: * <ul> * <li>declaration: <code><T>foo(T t)</code></li> * <li>pattern: <code><Exception>foo(new Exception())</code></li> * <li>match: <code><Object>foo(new Object())</code></li> * </ul> * Can be combined to all other match rules, e.g. {@link #R_CASE_SENSITIVE} | {@link #R_ERASURE_MATCH} * This rule is not activated by default, so raw types or parameterized types with same erasure will not be found * for pattern List<String>, * Note that with this pattern, the match selection will be only on the erasure even for parameterized types. * */ public static final int R_ERASURE_MATCH = 0x0010; /** * Match rule: The search pattern matches search results as raw/parameterized types/methods with equivalent type parameters. * This mode has no effect on other javascript elements search.<br> * Type search example: * <ul> * <li>pattern: <code>List<Exception></code></li> * <li>match: * <ul> * <li><code>List<? extends Throwable></code></li> * <li><code>List<? super RuntimeException></code></li> * <li><code>List<?></code></li> * </ul> * </li> * </ul> * Method search example: * <ul> * <li>declaration: <code><T>foo(T t)</code></li> * <li>pattern: <code><Exception>foo(new Exception())</code></li> * <li>match: * <ul> * <li><code><? extends Throwable>foo(new Exception())</code></li> * <li><code><? super RuntimeException>foo(new Exception())</code></li> * <li><code>foo(new Exception())</code></li> * </ul> * </ul> * Can be combined to all other match rules, e.g. {@link #R_CASE_SENSITIVE} | {@link #R_EQUIVALENT_MATCH} * This rule is not activated by default, so raw types or equivalent parameterized types will not be found * for pattern List<String>, * This mode is overridden by {@link #R_ERASURE_MATCH} as erasure matches obviously include equivalent ones. * That means that pattern with rule set to {@link #R_EQUIVALENT_MATCH} | {@link #R_ERASURE_MATCH} * will return same results than rule only set with {@link #R_ERASURE_MATCH}. * */ public static final int R_EQUIVALENT_MATCH = 0x0020; /** * Match rule: The search pattern matches exactly the search result, * that is, the source of the search result equals the search pattern. * */ public static final int R_FULL_MATCH = 0x0040; /** * Match rule: The search pattern contains a Camel Case expression. * <br> * Examples: * <ul> * <li><code>NPE</code> type string pattern will match * <code>NullPointerException</code> and <code>NpPermissionException</code> types,</li> * <li><code>NuPoEx</code> type string pattern will only match * <code>NullPointerException</code> type.</li> * </ul> * @see CharOperation#camelCaseMatch(char[], char[]) for a detailed explanation * of Camel Case matching. *<br> * Can be combined to {@link #R_PREFIX_MATCH} match rule. For example, * when prefix match rule is combined with Camel Case match rule, * <code>"nPE"</code> pattern will match <code>nPException</code>. *<br> * Match rule {@link #R_PATTERN_MATCH} may also be combined but both rules * will not be used simultaneously as they are mutually exclusive. * Used match rule depends on whether string pattern contains specific pattern * characters (e.g. '*' or '?') or not. If it does, then only Pattern match rule * will be used, otherwise only Camel Case match will be used. * For example, with <code>"NPE"</code> string pattern, search will only use * Camel Case match rule, but with <code>N*P*E*</code> string pattern, it will * use only Pattern match rule. * * */ public static final int R_CAMELCASE_MATCH = 0x0080; private static final int MODE_MASK = R_EXACT_MATCH | R_PREFIX_MATCH | R_PATTERN_MATCH | R_REGEXP_MATCH; private int matchRule; /** * Creates a search pattern with the rule to apply for matching index keys. * It can be exact match, prefix match, pattern match or regexp match. * Rule can also be combined with a case sensitivity flag. * * @param matchRule one of {@link #R_EXACT_MATCH}, {@link #R_PREFIX_MATCH}, {@link #R_PATTERN_MATCH}, * {@link #R_REGEXP_MATCH}, {@link #R_CAMELCASE_MATCH} combined with one of following values: * {@link #R_CASE_SENSITIVE}, {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH}. * e.g. {@link #R_EXACT_MATCH} | {@link #R_CASE_SENSITIVE} if an exact and case sensitive match is requested, * {@link #R_PREFIX_MATCH} if a prefix non case sensitive match is requested or {@link #R_EXACT_MATCH} | {@link #R_ERASURE_MATCH} * if a non case sensitive and erasure match is requested.<br> * Note that {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH} have no effect * on non-generic types/methods search.<br> * Note also that default behavior for generic types/methods search is to find exact matches. */ public SearchPattern(int matchRule) { this.matchRule = matchRule; // Set full match implicit mode if ((matchRule & (R_EQUIVALENT_MATCH | R_ERASURE_MATCH )) == 0) { this.matchRule |= R_FULL_MATCH; } } /** * Answers true if the pattern matches the given name using CamelCase rules, or false otherwise. * CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. * <br> * CamelCase denotes the convention of writing compound names without spaces, and capitalizing every term. * This function recognizes both upper and lower CamelCase, depending whether the leading character is capitalized * or not. The leading part of an upper CamelCase pattern is assumed to contain a sequence of capitals which are appearing * in the matching name; e.g. 'NPE' will match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern * uses a lowercase first character. In Java, type names follow the upper CamelCase convention, whereas method or field * names follow the lower CamelCase convention. * <br> * The pattern may contain lowercase characters, which will be match in a case sensitive way. These characters must * appear in sequence in the name. For instance, 'NPExcep' will match 'NullPointerException', but not 'NullPointerExCEPTION' * or 'NuPoEx' will match 'NullPointerException', but not 'NoPointerException'. * <br><br> * Examples: * <ol> * <li><pre> * pattern = "NPE" * name = NullPointerException / NoPermissionException * result => true * </pre> * </li> * <li><pre> * pattern = "NuPoEx" * name = NullPointerException * result => true * </pre> * </li> * <li><pre> * pattern = "npe" * name = NullPointerException * result => false * </pre> * </li> * </ol> * @see CharOperation#camelCaseMatch(char[], char[]) * Implementation has been entirely copied from this method except for array lengthes * which were obviously replaced with calls to {@link String#length()}. * * @param pattern the given pattern * @param name the given name * @return true if the pattern matches the given name, false otherwise * */ public static final boolean camelCaseMatch(String pattern, String name) { if (pattern == null) return true; // null pattern is equivalent to '*' if (name == null) return false; // null name cannot match return camelCaseMatch(pattern, 0, pattern.length(), name, 0, name.length()); } /** * Answers true if a sub-pattern matches the subpart of the given name using CamelCase rules, or false otherwise. * CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. * Can match only subset of name/pattern, considering end positions as non-inclusive. * The subpattern is defined by the patternStart and patternEnd positions. * <br> * CamelCase denotes the convention of writing compound names without spaces, and capitalizing every term. * This function recognizes both upper and lower CamelCase, depending whether the leading character is capitalized * or not. The leading part of an upper CamelCase pattern is assumed to contain a sequence of capitals which are appearing * in the matching name; e.g. 'NPE' will match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern * uses a lowercase first character. In Java, type names follow the upper CamelCase convention, whereas method or field * names follow the lower CamelCase convention. * <br> * The pattern may contain lowercase characters, which will be match in a case sensitive way. These characters must * appear in sequence in the name. For instance, 'NPExcep' will match 'NullPointerException', but not 'NullPointerExCEPTION' * or 'NuPoEx' will match 'NullPointerException', but not 'NoPointerException'. * <br><br> * Examples: * <ol> * <li><pre> * pattern = "NPE" * patternStart = 0 * patternEnd = 3 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => true * </pre> * </li> * <li><pre> * pattern = "NPE" * patternStart = 0 * patternEnd = 3 * name = NoPermissionException * nameStart = 0 * nameEnd = 21 * result => true * </pre> * </li> * <li><pre> * pattern = "NuPoEx" * patternStart = 0 * patternEnd = 6 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => true * </pre> * </li> * <li><pre> * pattern = "NuPoEx" * patternStart = 0 * patternEnd = 6 * name = NoPermissionException * nameStart = 0 * nameEnd = 21 * result => false * </pre> * </li> * <li><pre> * pattern = "npe" * patternStart = 0 * patternEnd = 3 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => false * </pre> * </li> * </ol> * @see CharOperation#camelCaseMatch(char[], int, int, char[], int, int) * Implementation has been entirely copied from this method except for array lengthes * which were obviously replaced with calls to {@link String#length()} and * for array direct access which were replaced with calls to {@link String#charAt(int)}. * * @param pattern the given pattern * @param patternStart the start index of the pattern, inclusive * @param patternEnd the end index of the pattern, exclusive * @param name the given name * @param nameStart the start index of the name, inclusive * @param nameEnd the end index of the name, exclusive * @return true if a sub-pattern matches the subpart of the given name, false otherwise * */ public static final boolean camelCaseMatch(String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd) { if (name == null) return false; // null name cannot match if (pattern == null) return true; // null pattern is equivalent to '*' if (patternEnd < 0) patternEnd = pattern.length(); if (nameEnd < 0) nameEnd = name.length(); if (patternEnd <= patternStart) return nameEnd <= nameStart; if (nameEnd <= nameStart) return false; // check first pattern char if (name.charAt(nameStart) != pattern.charAt(patternStart)) { // first char must strictly match (upper/lower) return false; } char patternChar, nameChar; int iPattern = patternStart; int iName = nameStart; // Main loop is on pattern characters while (true) { iPattern++; iName++; if (iPattern == patternEnd) { // We have exhausted pattern, so it's a match return true; } if (iName == nameEnd){ // We have exhausted name (and not pattern), so it's not a match return false; } // For as long as we're exactly matching, bring it on (even if it's a lower case character) if ((patternChar = pattern.charAt(iPattern)) == name.charAt(iName)) { continue; } // If characters are not equals, then it's not a match if patternChar is lowercase if (patternChar < ScannerHelper.MAX_OBVIOUS) { if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & ScannerHelper.C_UPPER_LETTER) == 0) { return false; } } else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar)) { return false; } // patternChar is uppercase, so let's find the next uppercase in name while (true) { if (iName == nameEnd){ // We have exhausted name (and not pattern), so it's not a match return false; } nameChar = name.charAt(iName); if (nameChar < ScannerHelper.MAX_OBVIOUS) { if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & (ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_SPECIAL | ScannerHelper.C_DIGIT)) != 0) { // nameChar is lowercase iName++; // nameChar is uppercase... } else if (patternChar != nameChar) { //.. and it does not match patternChar, so it's not a match return false; } else { //.. and it matched patternChar. Back to the big loop break; } } else if (Character.isJavaIdentifierPart(nameChar) && !Character.isUpperCase(nameChar)) { // nameChar is lowercase iName++; // nameChar is uppercase... } else if (patternChar != nameChar) { //.. and it does not match patternChar, so it's not a match return false; } else { //.. and it matched patternChar. Back to the big loop break; } } // At this point, either name has been exhausted, or it is at an uppercase letter. // Since pattern is also at an uppercase letter } } /** * Returns a search pattern that combines the given two patterns into an * "and" pattern. The search result will match both the left pattern and * the right pattern. * * @param leftPattern the left pattern * @param rightPattern the right pattern * @return an "and" pattern */ public static SearchPattern createAndPattern(SearchPattern leftPattern, SearchPattern rightPattern) { return MatchLocator.createAndPattern(leftPattern, rightPattern); } /** * Field pattern are formed by [declaringType.]name[ type] * e.g. java.lang.String.serialVersionUID long * field* */ private static SearchPattern createFieldPattern(String patternString, int limitTo, int matchRule,boolean isVar) { Scanner scanner = new Scanner(false /*comment*/, true /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3/*sourceLevel*/, null /*taskTags*/, null/*taskPriorities*/, true/*taskCaseSensitive*/); scanner.setSource(patternString.toCharArray()); final int InsideDeclaringPart = 1; final int InsideType = 2; int lastToken = -1; String declaringType = null, fieldName = null; String type = null; int mode = InsideDeclaringPart; int token; try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } while (token != TerminalTokens.TokenNameEOF) { switch(mode) { // read declaring type and fieldName case InsideDeclaringPart : switch (token) { case TerminalTokens.TokenNameDOT: if (declaringType == null) { if (fieldName == null) return null; declaringType = fieldName; } else { String tokenSource = scanner.getCurrentTokenString(); declaringType += tokenSource + fieldName; } fieldName = null; break; case TerminalTokens.TokenNameWHITESPACE: if (!(TerminalTokens.TokenNameWHITESPACE == lastToken || TerminalTokens.TokenNameDOT == lastToken)) mode = InsideType; break; default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (fieldName == null) fieldName = scanner.getCurrentTokenString(); else fieldName += scanner.getCurrentTokenString(); } break; // read type case InsideType: switch (token) { case TerminalTokens.TokenNameWHITESPACE: break; default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (type == null) type = scanner.getCurrentTokenString(); else type += scanner.getCurrentTokenString(); } break; } lastToken = token; try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } } if (fieldName == null) return null; char[] fieldNameChars = fieldName.toCharArray(); if (fieldNameChars.length == 1 && fieldNameChars[0] == '*') fieldNameChars = null; char[] declaringTypeQualification = null, declaringTypeSimpleName = null; char[] typeQualification = null, typeSimpleName = null; // extract declaring type infos if (declaringType != null) { char[] declaringTypePart = declaringType.toCharArray(); int lastDotPosition = CharOperation.lastIndexOf('.', declaringTypePart); if (lastDotPosition >= 0) { declaringTypeQualification = CharOperation.subarray(declaringTypePart, 0, lastDotPosition); if (declaringTypeQualification.length == 1 && declaringTypeQualification[0] == '*') declaringTypeQualification = null; declaringTypeSimpleName = CharOperation.subarray(declaringTypePart, lastDotPosition+1, declaringTypePart.length); } else { declaringTypeSimpleName = declaringTypePart; } if (declaringTypeSimpleName.length == 1 && declaringTypeSimpleName[0] == '*') declaringTypeSimpleName = null; } // extract type infos if (type != null) { char[] typePart = type.toCharArray(); int lastDotPosition = CharOperation.lastIndexOf('.', typePart); if (lastDotPosition >= 0) { typeQualification = CharOperation.subarray(typePart, 0, lastDotPosition); if (typeQualification.length == 1 && typeQualification[0] == '*') { typeQualification = null; } else { // prefix with a '*' as the full qualification could be bigger (because of an import) typeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, typeQualification); } typeSimpleName = CharOperation.subarray(typePart, lastDotPosition+1, typePart.length); } else { typeSimpleName = typePart; } if (typeSimpleName.length == 1 && typeSimpleName[0] == '*') typeSimpleName = null; } // Create field pattern boolean findDeclarations = false; boolean readAccess = false; boolean writeAccess = false; switch (limitTo) { case IJavaScriptSearchConstants.DECLARATIONS : findDeclarations = true; break; case IJavaScriptSearchConstants.REFERENCES : readAccess = true; writeAccess = true; break; case IJavaScriptSearchConstants.READ_ACCESSES : readAccess = true; break; case IJavaScriptSearchConstants.WRITE_ACCESSES : writeAccess = true; break; case IJavaScriptSearchConstants.ALL_OCCURRENCES : findDeclarations = true; readAccess = true; writeAccess = true; break; } return new FieldPattern( findDeclarations, readAccess, writeAccess, isVar, fieldNameChars, declaringTypeQualification, declaringTypeSimpleName, typeQualification, typeSimpleName, matchRule,null); } /** * Method pattern are formed by:<br> * [declaringType '.'] ['<' typeArguments '>'] selector ['(' parameterTypes ')'] [returnType] * <br>e.g.<ul> * <li>java.lang.Runnable.run() void</li> * <li>main(*)</li> * <li><String>toArray(String[])</li> * </ul> * Constructor pattern are formed by:<br> * [declaringQualification '.'] ['<' typeArguments '>'] type ['(' parameterTypes ')'] * <br>e.g.<ul> * <li>java.lang.Object()</li> * <li>Main(*)</li> * <li><Exception>Sample(Exception)</li> * </ul> * Type arguments have the same pattern that for type patterns * @see #createTypePattern(String,int,int,char) */ private static SearchPattern createMethodOrConstructorPattern(String patternString, int limitTo, int matchRule, boolean isConstructor, boolean isFunction) { Scanner scanner = new Scanner(false /*comment*/, true /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3/*sourceLevel*/, null /*taskTags*/, null/*taskPriorities*/, true/*taskCaseSensitive*/); scanner.setSource(patternString.toCharArray()); final int InsideSelector = 1; final int InsideTypeArguments = 2; final int InsideParameter = 3; final int InsideReturnType = 4; int lastToken = -1; String declaringType = null, selector = null, parameterType = null; String[] parameterTypes = null; char[][] typeArguments = null; String typeArgumentsString = null; int parameterCount = -1; String returnType = null; boolean foundClosingParenthesis = false; int mode = InsideSelector; int token, argCount = 0; try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } while (token != TerminalTokens.TokenNameEOF) { switch(mode) { // read declaring type and selector case InsideSelector : if (argCount == 0) { switch (token) { case TerminalTokens.TokenNameLESS: argCount++; if (selector == null || lastToken == TerminalTokens.TokenNameDOT) { if (typeArgumentsString != null) return null; // invalid syntax typeArgumentsString = scanner.getCurrentTokenString(); mode = InsideTypeArguments; break; } if (declaringType == null) { declaringType = selector; } else { declaringType += '.' + selector; } declaringType += scanner.getCurrentTokenString(); selector = null; break; case TerminalTokens.TokenNameDOT: if (typeArgumentsString != null) return null; // invalid syntax if (declaringType == null) { if (selector == null) return null; // invalid syntax declaringType = selector; } else if (selector != null) { declaringType += scanner.getCurrentTokenString() + selector; } selector = null; break; case TerminalTokens.TokenNameLPAREN: parameterTypes = new String[5]; parameterCount = 0; mode = InsideParameter; break; case TerminalTokens.TokenNameWHITESPACE: switch (lastToken) { case TerminalTokens.TokenNameWHITESPACE: case TerminalTokens.TokenNameDOT: case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: break; default: mode = InsideReturnType; break; } break; default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (selector == null) selector = scanner.getCurrentTokenString(); else selector += scanner.getCurrentTokenString(); break; } } else { if (declaringType == null) return null; // invalid syntax switch (token) { case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: argCount--; break; case TerminalTokens.TokenNameLESS: argCount++; break; } declaringType += scanner.getCurrentTokenString(); } break; // read type arguments case InsideTypeArguments: if (typeArgumentsString == null) return null; // invalid syntax typeArgumentsString += scanner.getCurrentTokenString(); switch (token) { case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: argCount--; if (argCount == 0) { typeArguments = new char[0][0]; mode = InsideSelector; } break; case TerminalTokens.TokenNameLESS: argCount++; break; } break; // read parameter types case InsideParameter : if (argCount == 0) { switch (token) { case TerminalTokens.TokenNameWHITESPACE: break; case TerminalTokens.TokenNameCOMMA: if (parameterType == null) return null; if (parameterTypes != null) { if (parameterTypes.length == parameterCount) System.arraycopy(parameterTypes, 0, parameterTypes = new String[parameterCount*2], 0, parameterCount); parameterTypes[parameterCount++] = parameterType; } parameterType = null; break; case TerminalTokens.TokenNameRPAREN: foundClosingParenthesis = true; if (parameterType != null && parameterTypes != null) { if (parameterTypes.length == parameterCount) System.arraycopy(parameterTypes, 0, parameterTypes = new String[parameterCount*2], 0, parameterCount); parameterTypes[parameterCount++] = parameterType; } mode = isConstructor ? InsideTypeArguments : InsideReturnType; break; case TerminalTokens.TokenNameLESS: argCount++; if (parameterType == null) return null; // invalid syntax // fall through next case to add token default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (parameterType == null) parameterType = scanner.getCurrentTokenString(); else parameterType += scanner.getCurrentTokenString(); } } else { if (parameterType == null) return null; // invalid syntax switch (token) { case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: argCount--; break; case TerminalTokens.TokenNameLESS: argCount++; break; } parameterType += scanner.getCurrentTokenString(); } break; // read return type case InsideReturnType: if (argCount == 0) { switch (token) { case TerminalTokens.TokenNameWHITESPACE: break; case TerminalTokens.TokenNameLPAREN: parameterTypes = new String[5]; parameterCount = 0; mode = InsideParameter; break; case TerminalTokens.TokenNameLESS: argCount++; if (returnType == null) return null; // invalid syntax // fall through next case to add token default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (returnType == null) returnType = scanner.getCurrentTokenString(); else returnType += scanner.getCurrentTokenString(); } } else { if (returnType == null) return null; // invalid syntax switch (token) { case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: argCount--; break; case TerminalTokens.TokenNameLESS: argCount++; break; } returnType += scanner.getCurrentTokenString(); } break; } lastToken = token; try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } } // parenthesis mismatch if (parameterCount>0 && !foundClosingParenthesis) return null; // type arguments mismatch if (argCount > 0) return null; char[] selectorChars = null; if (isConstructor) { // retrieve type for constructor patterns if (declaringType == null) declaringType = selector; else if (selector != null) declaringType += '.' + selector; } else { // get selector chars if (selector == null) return null; selectorChars = selector.toCharArray(); if (selectorChars.length == 1 && selectorChars[0] == '*') selectorChars = null; } char[] declaringTypeQualification = null, declaringTypeSimpleName = null; char[] returnTypeQualification = null, returnTypeSimpleName = null; char[][] parameterTypeQualifications = null, parameterTypeSimpleNames = null; // Signatures String declaringTypeSignature = null; String returnTypeSignature = null; String[] parameterTypeSignatures = null; // extract declaring type infos if (declaringType != null) { // get declaring type part and signature char[] declaringTypePart = null; try { declaringTypeSignature = Signature.createTypeSignature(declaringType, false); declaringTypePart = declaringType.toCharArray(); } catch (IllegalArgumentException iae) { // declaring type is invalid return null; } int lastDotPosition = CharOperation.lastIndexOf('.', declaringTypePart); if (lastDotPosition >= 0) { declaringTypeQualification = CharOperation.subarray(declaringTypePart, 0, lastDotPosition); if (declaringTypeQualification.length == 1 && declaringTypeQualification[0] == '*') declaringTypeQualification = null; declaringTypeSimpleName = CharOperation.subarray(declaringTypePart, lastDotPosition+1, declaringTypePart.length); } else { declaringTypeSimpleName = declaringTypePart; } if (declaringTypeSimpleName.length == 1 && declaringTypeSimpleName[0] == '*') declaringTypeSimpleName = null; } // extract parameter types infos if (parameterCount >= 0) { parameterTypeQualifications = new char[parameterCount][]; parameterTypeSimpleNames = new char[parameterCount][]; parameterTypeSignatures = new String[parameterCount]; for (int i = 0; i < parameterCount; i++) { // get parameter type part and signature char[] parameterTypePart = null; try { if (parameterTypes != null) { parameterTypeSignatures[i] = Signature.createTypeSignature(parameterTypes[i], false); parameterTypePart = parameterTypes[i].toCharArray(); } } catch (IllegalArgumentException iae) { // string is not a valid type syntax return null; } int lastDotPosition = parameterTypePart==null ? -1 : CharOperation.lastIndexOf('.', parameterTypePart); if (parameterTypePart != null && lastDotPosition >= 0) { parameterTypeQualifications[i] = CharOperation.subarray(parameterTypePart, 0, lastDotPosition); if (parameterTypeQualifications[i].length == 1 && parameterTypeQualifications[i][0] == '*') { parameterTypeQualifications[i] = null; } else { // prefix with a '*' as the full qualification could be bigger (because of an import) parameterTypeQualifications[i] = CharOperation.concat(IIndexConstants.ONE_STAR, parameterTypeQualifications[i]); } parameterTypeSimpleNames[i] = CharOperation.subarray(parameterTypePart, lastDotPosition+1, parameterTypePart.length); } else { parameterTypeQualifications[i] = null; parameterTypeSimpleNames[i] = parameterTypePart; } if (parameterTypeSimpleNames[i].length == 1 && parameterTypeSimpleNames[i][0] == '*') parameterTypeSimpleNames[i] = null; } } // extract return type infos if (returnType != null) { // get return type part and signature char[] returnTypePart = null; try { returnTypeSignature = Signature.createTypeSignature(returnType, false); returnTypePart = returnType.toCharArray(); } catch (IllegalArgumentException iae) { // declaring type is invalid return null; } int lastDotPosition = CharOperation.lastIndexOf('.', returnTypePart); if (lastDotPosition >= 0) { returnTypeQualification = CharOperation.subarray(returnTypePart, 0, lastDotPosition); if (returnTypeQualification.length == 1 && returnTypeQualification[0] == '*') { returnTypeQualification = null; } else { // because of an import returnTypeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, returnTypeQualification); } returnTypeSimpleName = CharOperation.subarray(returnTypePart, lastDotPosition+1, returnTypePart.length); } else { returnTypeSimpleName = returnTypePart; } if (returnTypeSimpleName.length == 1 && returnTypeSimpleName[0] == '*') returnTypeSimpleName = null; } // Create method/constructor pattern boolean findDeclarations = true; boolean findReferences = true; switch (limitTo) { case IJavaScriptSearchConstants.DECLARATIONS : findReferences = false; break; case IJavaScriptSearchConstants.REFERENCES : findDeclarations = false; break; case IJavaScriptSearchConstants.ALL_OCCURRENCES : break; } if (isConstructor) { return new ConstructorPattern( findDeclarations, findReferences, declaringTypeSimpleName, declaringTypeQualification, declaringTypeSignature, parameterTypeQualifications, parameterTypeSimpleNames, parameterTypeSignatures, typeArguments, matchRule); } else { return new MethodPattern( findDeclarations, findReferences, isFunction, selectorChars, declaringTypeQualification, declaringTypeSimpleName, declaringTypeSignature, returnTypeQualification, returnTypeSimpleName, returnTypeSignature, parameterTypeQualifications, parameterTypeSimpleNames, parameterTypeSignatures, typeArguments, matchRule); } } /** * Returns a search pattern that combines the given two patterns into an * "or" pattern. The search result will match either the left pattern or the * right pattern. * * @param leftPattern the left pattern * @param rightPattern the right pattern * @return an "or" pattern */ public static SearchPattern createOrPattern(SearchPattern leftPattern, SearchPattern rightPattern) { return new OrPattern(leftPattern, rightPattern); } private static SearchPattern createPackagePattern(String patternString, int limitTo, int matchRule) { switch (limitTo) { case IJavaScriptSearchConstants.DECLARATIONS : return new PackageDeclarationPattern(patternString.toCharArray(), matchRule); case IJavaScriptSearchConstants.REFERENCES : return new PackageReferencePattern(patternString.toCharArray(), matchRule); case IJavaScriptSearchConstants.ALL_OCCURRENCES : return new OrPattern( new PackageDeclarationPattern(patternString.toCharArray(), matchRule), new PackageReferencePattern(patternString.toCharArray(), matchRule) ); } return null; } /** * Returns a search pattern based on a given string pattern. The string patterns support '*' wild-cards. * The remaining parameters are used to narrow down the type of expected results. * * <br> * Examples: * <ul> * <li>search for case insensitive references to <code>Object</code>: * <code>createSearchPattern("Object", TYPE, REFERENCES, false);</code></li> * <li>search for case sensitive references to exact <code>Object()</code> constructor: * <code>createSearchPattern("java.lang.Object()", CONSTRUCTOR, REFERENCES, true);</code></li> * <li>search for implementers of <code>java.lang.Runnable</code>: * <code>createSearchPattern("java.lang.Runnable", TYPE, IMPLEMENTORS, true);</code></li> * </ul> * @param stringPattern the given pattern * @param searchFor determines the nature of the searched elements * <ul> * <li>{@link IJavaScriptSearchConstants#CLASS}: only look for classes</li> * <li>{@link IJavaScriptSearchConstants#INTERFACE}: only look for interfaces</li> * <li>{@link IJavaScriptSearchConstants#ENUM}: only look for enumeration</li> * <li>{@link IJavaScriptSearchConstants#ANNOTATION_TYPE}: only look for annotation type</li> * <li>{@link IJavaScriptSearchConstants#CLASS_AND_ENUM}: only look for classes and enumerations</li> * <li>{@link IJavaScriptSearchConstants#CLASS_AND_INTERFACE}: only look for classes and interfaces</li> * <li>{@link IJavaScriptSearchConstants#TYPE}: look for all types (ie. classes, interfaces, enum and annotation types)</li> * <li>{@link IJavaScriptSearchConstants#FIELD}: look for fields</li> * <li>{@link IJavaScriptSearchConstants#METHOD}: look for methods</li> * <li>{@link IJavaScriptSearchConstants#CONSTRUCTOR}: look for constructors</li> * <li>{@link IJavaScriptSearchConstants#PACKAGE}: look for packages</li> * </ul> * @param limitTo determines the nature of the expected matches * <ul> * <li>{@link IJavaScriptSearchConstants#DECLARATIONS}: will search declarations matching * with the corresponding element. In case the element is a method, declarations of matching * methods in subtypes will also be found, allowing to find declarations of abstract methods, etc.<br> * Note that additional flags {@link IJavaScriptSearchConstants#IGNORE_DECLARING_TYPE} and * {@link IJavaScriptSearchConstants#IGNORE_RETURN_TYPE} are ignored for string patterns. * This is due to the fact that client may omit to define them in string pattern to have same behavior. * </li> * <li>{@link IJavaScriptSearchConstants#REFERENCES}: will search references to the given element.</li> * <li>{@link IJavaScriptSearchConstants#ALL_OCCURRENCES}: will search for either declarations or * references as specified above. * </li> * <li>{@link IJavaScriptSearchConstants#IMPLEMENTORS}: for types, will find all types * which directly implement/extend a given interface. * Note that types may be only classes or only interfaces if {@link IJavaScriptSearchConstants#CLASS } or * {@link IJavaScriptSearchConstants#INTERFACE} is respectively used instead of {@link IJavaScriptSearchConstants#TYPE}. * </li> * </ul> * @param matchRule one of {@link #R_EXACT_MATCH}, {@link #R_PREFIX_MATCH}, {@link #R_PATTERN_MATCH}, * {@link #R_REGEXP_MATCH}, {@link #R_CAMELCASE_MATCH} combined with one of following values: * {@link #R_CASE_SENSITIVE}, {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH}. * e.g. {@link #R_EXACT_MATCH} | {@link #R_CASE_SENSITIVE} if an exact and case sensitive match is requested, * {@link #R_PREFIX_MATCH} if a prefix non case sensitive match is requested or {@link #R_EXACT_MATCH} | {@link #R_ERASURE_MATCH} * if a non case sensitive and erasure match is requested.<br> * Note that {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH} have no effect * on non-generic types/methods search.<br> * Note also that default behavior for generic types/methods search is to find exact matches. * @return a search pattern on the given string pattern, or <code>null</code> if the string pattern is ill-formed */ public static SearchPattern createPattern(String stringPattern, int searchFor, int limitTo, int matchRule) { if (stringPattern == null || stringPattern.length() == 0) return null; if ((matchRule = validateMatchRule(stringPattern, matchRule)) == -1) { return null; } // Ignore additional nature flags limitTo &= ~(IJavaScriptSearchConstants.IGNORE_DECLARING_TYPE+IJavaScriptSearchConstants.IGNORE_RETURN_TYPE); switch (searchFor) { case IJavaScriptSearchConstants.CLASS: return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.CLASS_SUFFIX); case IJavaScriptSearchConstants.TYPE: return createTypePattern(stringPattern, limitTo, matchRule, IIndexConstants.TYPE_SUFFIX); case IJavaScriptSearchConstants.FUNCTION: return createMethodOrConstructorPattern(stringPattern, limitTo, matchRule, false/*not a constructor*/,true); case IJavaScriptSearchConstants.METHOD: return createMethodOrConstructorPattern(stringPattern, limitTo, matchRule, false/*not a constructor*/,false); case IJavaScriptSearchConstants.CONSTRUCTOR: return createMethodOrConstructorPattern(stringPattern, limitTo, matchRule, true/*constructor*/,false); case IJavaScriptSearchConstants.FIELD: return createFieldPattern(stringPattern, limitTo, matchRule,false); case IJavaScriptSearchConstants.VAR: return createFieldPattern(stringPattern, limitTo, matchRule,true); case IJavaScriptSearchConstants.PACKAGE: return createPackagePattern(stringPattern, limitTo, matchRule); } return null; } /** * Returns a search pattern based on a given JavaScript element. * The pattern is used to trigger the appropriate search. * <br> * Note that for generic searches, the returned pattern consider {@link #R_ERASURE_MATCH} matches. * If other kind of generic matches (ie. {@link #R_EXACT_MATCH} or {@link #R_EQUIVALENT_MATCH}) * are expected, {@link #createPattern(IJavaScriptElement, int, int)} method need to be used instead with * the explicit match rule specified. * <br> * The pattern can be parameterized as follows: * * @param element the JavaScript element the search pattern is based on * @param limitTo determines the nature of the expected matches * <ul> * <li>{@link IJavaScriptSearchConstants#DECLARATIONS}: will search declarations matching * with the corresponding element. In case the element is a method, declarations of matching * methods in subtypes will also be found, allowing to find declarations of abstract methods, etc. * Some additional flags may be specified while searching declaration: * <ul> * <li>{@link IJavaScriptSearchConstants#IGNORE_DECLARING_TYPE}: declaring type will be ignored * during the search.<br> * For example using following test case: * <pre> * class A { A method() { return null; } } * class B extends A { B method() { return null; } } * class C { A method() { return null; } } * </pre> * search for <code>method</code> declaration with this flag * will return 2 matches: in A and in C * </li> * <li>{@link IJavaScriptSearchConstants#IGNORE_RETURN_TYPE}: return type will be ignored * during the search.<br> * Using same example, search for <code>method</code> declaration with this flag * will return 2 matches: in A and in B. * </li> * </ul> * Note that these two flags may be combined and both declaring and return types can be ignored * during the search. Then, using same example, search for <code>method</code> declaration * with these 2 flags will return 3 matches: in A, in B and in C * </li> * <li>{@link IJavaScriptSearchConstants#REFERENCES}: will search references to the given element.</li> * <li>{@link IJavaScriptSearchConstants#ALL_OCCURRENCES}: will search for either declarations or * references as specified above. * </li> * <li>{@link IJavaScriptSearchConstants#IMPLEMENTORS}: for types, will find all types * which directly implement/extend a given interface. * </li> * </ul> * @return a search pattern for a JavaScript element or <code>null</code> if the given element is ill-formed */ public static SearchPattern createPattern(IJavaScriptElement element, int limitTo) { return createPattern(element, limitTo, R_EXACT_MATCH | R_CASE_SENSITIVE | R_ERASURE_MATCH); } /** * Returns a search pattern based on a given JavaScript element. * The pattern is used to trigger the appropriate search, and can be parameterized as follows: * * @param element the JavaScript element the search pattern is based on * @param limitTo determines the nature of the expected matches * <ul> * <li>{@link IJavaScriptSearchConstants#DECLARATIONS}: will search declarations matching * with the corresponding element. In case the element is a method, declarations of matching * methods in subtypes will also be found, allowing to find declarations of abstract methods, etc. * Some additional flags may be specified while searching declaration: * <ul> * <li>{@link IJavaScriptSearchConstants#IGNORE_DECLARING_TYPE}: declaring type will be ignored * during the search.<br> * For example using following test case: * <pre> * class A { A method() { return null; } } * class B extends A { B method() { return null; } } * class C { A method() { return null; } } * </pre> * search for <code>method</code> declaration with this flag * will return 2 matches: in A and in C * </li> * <li>{@link IJavaScriptSearchConstants#IGNORE_RETURN_TYPE}: return type will be ignored * during the search.<br> * Using same example, search for <code>method</code> declaration with this flag * will return 2 matches: in A and in B. * </li> * </ul> * Note that these two flags may be combined and both declaring and return types can be ignored * during the search. Then, using same example, search for <code>method</code> declaration * with these 2 flags will return 3 matches: in A, in B and in C * </li> * <li>{@link IJavaScriptSearchConstants#REFERENCES}: will search references to the given element.</li> * <li>{@link IJavaScriptSearchConstants#ALL_OCCURRENCES}: will search for either declarations or * references as specified above. * </li> * <li>{@link IJavaScriptSearchConstants#IMPLEMENTORS}: for types, will find all types * which directly implement/extend a given interface. * </li> * </ul> * @param matchRule one of {@link #R_EXACT_MATCH}, {@link #R_PREFIX_MATCH}, {@link #R_PATTERN_MATCH}, * {@link #R_REGEXP_MATCH}, {@link #R_CAMELCASE_MATCH} combined with one of following values: * {@link #R_CASE_SENSITIVE}, {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH}. * e.g. {@link #R_EXACT_MATCH} | {@link #R_CASE_SENSITIVE} if an exact and case sensitive match is requested, * {@link #R_PREFIX_MATCH} if a prefix non case sensitive match is requested or {@link #R_EXACT_MATCH} |{@link #R_ERASURE_MATCH} * if a non case sensitive and erasure match is requested.<br> * Note that {@link #R_ERASURE_MATCH} or {@link #R_EQUIVALENT_MATCH} have no effect on non-generic types * or methods search.<br> * Note also that default behavior for generic types or methods is to find exact matches. * @return a search pattern for a JavaScript element or <code>null</code> if the given element is ill-formed * */ public static SearchPattern createPattern(IJavaScriptElement element, int limitTo, int matchRule) { SearchPattern searchPattern = null; int lastDot; boolean ignoreDeclaringType = false; boolean ignoreReturnType = false; int maskedLimitTo = limitTo & ~(IJavaScriptSearchConstants.IGNORE_DECLARING_TYPE+IJavaScriptSearchConstants.IGNORE_RETURN_TYPE); if (maskedLimitTo == IJavaScriptSearchConstants.DECLARATIONS || maskedLimitTo == IJavaScriptSearchConstants.ALL_OCCURRENCES) { ignoreDeclaringType = (limitTo & IJavaScriptSearchConstants.IGNORE_DECLARING_TYPE) != 0; ignoreReturnType = (limitTo & IJavaScriptSearchConstants.IGNORE_RETURN_TYPE) != 0; } char[] declaringSimpleName = null; char[] declaringQualification = null; boolean isVar=false; boolean isFunction=false; switch (element.getElementType()) { case IJavaScriptElement.FIELD : IField field = (IField) element; IType declaringClassForField = field.getDeclaringType(); isVar=(declaringClassForField==null); if (!ignoreDeclaringType) { if (declaringClassForField!=null) { declaringSimpleName = declaringClassForField.getElementName().toCharArray(); declaringQualification = declaringClassForField.getPackageFragment().getElementName().toCharArray(); char[][] enclosingNames = enclosingTypeNames(declaringClassForField); if (enclosingNames.length > 0) { declaringQualification = CharOperation.concat(declaringQualification, CharOperation.concatWith(enclosingNames, '.'), '.'); } } } char[] name = field.getElementName().toCharArray(); char[] typeSimpleName = null; char[] typeQualification = null; String typeSignature = null; if (!ignoreReturnType) { try { typeSignature = field.getTypeSignature(); char[] signature = typeSignature.toCharArray(); char[] typeErasure = Signature.toCharArray(signature); CharOperation.replace(typeErasure, '$', '.'); if ((lastDot = CharOperation.lastIndexOf('.', typeErasure)) == -1) { typeSimpleName = typeErasure; } else { typeSimpleName = CharOperation.subarray(typeErasure, lastDot + 1, typeErasure.length); typeQualification = CharOperation.subarray(typeErasure, 0, lastDot); if (!field.isBinary()) { // prefix with a '*' as the full qualification could be bigger (because of an import) typeQualification = CharOperation.concat(IIndexConstants.ONE_STAR, typeQualification); } } } catch (JavaScriptModelException e) { return null; } } // Create field pattern boolean findDeclarations = false; boolean readAccess = false; boolean writeAccess = false; switch (maskedLimitTo) { case IJavaScriptSearchConstants.DECLARATIONS : findDeclarations = true; break; case IJavaScriptSearchConstants.REFERENCES : readAccess = true; writeAccess = true; break; case IJavaScriptSearchConstants.READ_ACCESSES : readAccess = true; break; case IJavaScriptSearchConstants.WRITE_ACCESSES : writeAccess = true; break; case IJavaScriptSearchConstants.ALL_OCCURRENCES : findDeclarations = true; readAccess = true; writeAccess = true; break; } searchPattern = new FieldPattern( findDeclarations, readAccess, writeAccess, isVar, name, declaringQualification, declaringSimpleName, typeQualification, typeSimpleName, typeSignature, matchRule,field); break; case IJavaScriptElement.IMPORT_DECLARATION : String elementName = element.getElementName(); lastDot = elementName.lastIndexOf('.'); if (lastDot == -1) return null; // invalid import declaration IImportDeclaration importDecl = (IImportDeclaration)element; if (importDecl.isOnDemand()) { searchPattern = createPackagePattern(elementName.substring(0, lastDot), maskedLimitTo, matchRule); } else { searchPattern = createTypePattern( elementName.substring(lastDot+1).toCharArray(), elementName.substring(0, lastDot).toCharArray(), null, null, null, maskedLimitTo, matchRule); } break; case IJavaScriptElement.LOCAL_VARIABLE : LocalVariable localVar = (LocalVariable) element; boolean findVarDeclarations = false; boolean findVarReadAccess = false; boolean findVarWriteAccess = false; switch (maskedLimitTo) { case IJavaScriptSearchConstants.DECLARATIONS : findVarDeclarations = true; break; case IJavaScriptSearchConstants.REFERENCES : findVarReadAccess = true; findVarWriteAccess = true; break; case IJavaScriptSearchConstants.READ_ACCESSES : findVarReadAccess = true; break; case IJavaScriptSearchConstants.WRITE_ACCESSES : findVarWriteAccess = true; break; case IJavaScriptSearchConstants.ALL_OCCURRENCES : findVarDeclarations = true; findVarReadAccess = true; findVarWriteAccess = true; break; } searchPattern = new LocalVariablePattern( findVarDeclarations, findVarReadAccess, findVarWriteAccess, localVar, matchRule); break; case IJavaScriptElement.METHOD : IFunction method = (IFunction) element; boolean isConstructor; try { isConstructor = method.isConstructor(); } catch (JavaScriptModelException e) { return null; } IType declaringClass = method.getDeclaringType(); if (declaringClass!=null) { if (ignoreDeclaringType) { if (isConstructor) declaringSimpleName = declaringClass.getElementName() .toCharArray(); } else { declaringSimpleName = declaringClass.getElementName() .toCharArray(); declaringQualification = declaringClass .getPackageFragment().getElementName() .toCharArray(); char[][] enclosingNames = enclosingTypeNames(declaringClass); if (enclosingNames.length > 0) { declaringQualification = CharOperation.concat( declaringQualification, CharOperation .concatWith(enclosingNames, '.'), '.'); } } } else isFunction=true; char[] selector = method.getElementName().toCharArray(); char[] returnSimpleName = null; char[] returnQualification = null; String returnSignature = null; if (!ignoreReturnType) { try { returnSignature = method.getReturnType(); char[] signature = returnSignature.toCharArray(); char[] returnErasure = Signature.toCharArray(signature); CharOperation.replace(returnErasure, '$', '.'); if ((lastDot = CharOperation.lastIndexOf('.', returnErasure)) == -1) { returnSimpleName = returnErasure; } else { returnSimpleName = CharOperation.subarray(returnErasure, lastDot + 1, returnErasure.length); returnQualification = CharOperation.subarray(returnErasure, 0, lastDot); if (!method.isBinary()) { // prefix with a '*' as the full qualification could be bigger (because of an import) CharOperation.concat(IIndexConstants.ONE_STAR, returnQualification); } } } catch (JavaScriptModelException e) { return null; } } String[] parameterTypes = method.getParameterTypes(); int paramCount = parameterTypes.length; char[][] parameterSimpleNames = new char[paramCount][]; char[][] parameterQualifications = new char[paramCount][]; String[] parameterSignatures = new String[paramCount]; for (int i = 0; i < paramCount; i++) { parameterSignatures[i] = parameterTypes[i]; char[] signature = parameterSignatures[i].toCharArray(); char[] paramErasure = Signature.toCharArray(signature); CharOperation.replace(paramErasure, '$', '.'); if ((lastDot = CharOperation.lastIndexOf('.', paramErasure)) == -1) { parameterSimpleNames[i] = paramErasure; parameterQualifications[i] = null; } else { parameterSimpleNames[i] = CharOperation.subarray(paramErasure, lastDot + 1, paramErasure.length); parameterQualifications[i] = CharOperation.subarray(paramErasure, 0, lastDot); if (!method.isBinary()) { // prefix with a '*' as the full qualification could be bigger (because of an import) CharOperation.concat(IIndexConstants.ONE_STAR, parameterQualifications[i]); } } } // Create method/constructor pattern boolean findMethodDeclarations = true; boolean findMethodReferences = true; switch (maskedLimitTo) { case IJavaScriptSearchConstants.DECLARATIONS : findMethodReferences = false; break; case IJavaScriptSearchConstants.REFERENCES : findMethodDeclarations = false; break; case IJavaScriptSearchConstants.ALL_OCCURRENCES : break; } if (isConstructor) { searchPattern = new ConstructorPattern( findMethodDeclarations, findMethodReferences, declaringSimpleName, declaringQualification, parameterQualifications, parameterSimpleNames, parameterSignatures, method, matchRule); } else { searchPattern = new MethodPattern( findMethodDeclarations, findMethodReferences, isFunction, selector, declaringQualification, declaringSimpleName, returnQualification, returnSimpleName, returnSignature, parameterQualifications, parameterSimpleNames, parameterSignatures, method, matchRule); } break; case IJavaScriptElement.TYPE : IType type = (IType)element; searchPattern = createTypePattern( type.getElementName().toCharArray(), type.getPackageFragment().getElementName().toCharArray(), ignoreDeclaringType ? null : enclosingTypeNames(type), null, type, maskedLimitTo, matchRule); break; case IJavaScriptElement.PACKAGE_FRAGMENT : searchPattern = createPackagePattern(element.getElementName(), maskedLimitTo, matchRule); break; } if (searchPattern != null) MatchLocator.setFocus(searchPattern, element); return searchPattern; } private static SearchPattern createTypePattern(char[] simpleName, char[] packageName, char[][] enclosingTypeNames, String typeSignature, IType type, int limitTo, int matchRule) { switch (limitTo) { case IJavaScriptSearchConstants.DECLARATIONS : return new TypeDeclarationPattern( packageName, enclosingTypeNames, simpleName, IIndexConstants.TYPE_SUFFIX, matchRule); case IJavaScriptSearchConstants.REFERENCES : if (type != null) { return new TypeReferencePattern( CharOperation.concatWith(packageName, enclosingTypeNames, '.'), simpleName, type, matchRule); } return new TypeReferencePattern( CharOperation.concatWith(packageName, enclosingTypeNames, '.'), simpleName, typeSignature, matchRule); case IJavaScriptSearchConstants.IMPLEMENTORS : return new SuperTypeReferencePattern( CharOperation.concatWith(packageName, enclosingTypeNames, '.'), simpleName, matchRule); case IJavaScriptSearchConstants.ALL_OCCURRENCES : return new OrPattern( new TypeDeclarationPattern( packageName, enclosingTypeNames, simpleName, IIndexConstants.TYPE_SUFFIX, matchRule), (type != null) ? new TypeReferencePattern( CharOperation.concatWith(packageName, enclosingTypeNames, '.'), simpleName, type, matchRule) : new TypeReferencePattern( CharOperation.concatWith(packageName, enclosingTypeNames, '.'), simpleName, typeSignature, matchRule) ); } return null; } /** * Type pattern are formed by [qualification '.']type [typeArguments]. * e.g. java.lang.Object * Runnable * List<String> */ private static SearchPattern createTypePattern(String patternString, int limitTo, int matchRule, char indexSuffix) { Scanner scanner = new Scanner(false /*comment*/, true /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3/*sourceLevel*/, null /*taskTags*/, null/*taskPriorities*/, true/*taskCaseSensitive*/); scanner.setSource(patternString.toCharArray()); String type = null; int token; try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } int argCount = 0; while (token != TerminalTokens.TokenNameEOF) { if (argCount == 0) { switch (token) { case TerminalTokens.TokenNameWHITESPACE: break; case TerminalTokens.TokenNameLESS: argCount++; // fall through default case to add token to type default: // all other tokens are considered identifiers (see bug 21763 Problem in JavaScript search [search]) if (type == null) type = scanner.getCurrentTokenString(); else type += scanner.getCurrentTokenString(); } } else { switch (token) { case TerminalTokens.TokenNameGREATER: case TerminalTokens.TokenNameRIGHT_SHIFT: case TerminalTokens.TokenNameUNSIGNED_RIGHT_SHIFT: argCount--; break; case TerminalTokens.TokenNameLESS: argCount++; break; } if (type == null) return null; // invalid syntax type += scanner.getCurrentTokenString(); } try { token = scanner.getNextToken(); } catch (InvalidInputException e) { return null; } } if (type == null) return null; String typeSignature = null; char[] qualificationChars = null, typeChars = null; // get type part and signature char[] typePart = null; try { typeSignature = Signature.createTypeSignature(type, false); typePart = type.toCharArray(); } catch (IllegalArgumentException iae) { // string is not a valid type syntax return null; } // get qualification name int lastDotPosition = CharOperation.lastIndexOf('.', typePart); if (lastDotPosition >= 0) { qualificationChars = CharOperation.subarray(typePart, 0, lastDotPosition); if (qualificationChars.length == 1 && qualificationChars[0] == '*') qualificationChars = null; typeChars = CharOperation.subarray(typePart, lastDotPosition+1, typePart.length); } else { typeChars = typePart; } if (typeChars.length == 1 && typeChars[0] == '*') { typeChars = null; } switch (limitTo) { case IJavaScriptSearchConstants.DECLARATIONS : // cannot search for explicit member types return new QualifiedTypeDeclarationPattern(qualificationChars, typeChars, indexSuffix, matchRule); case IJavaScriptSearchConstants.REFERENCES : return new TypeReferencePattern(qualificationChars, typeChars, typeSignature, matchRule); case IJavaScriptSearchConstants.IMPLEMENTORS : return new SuperTypeReferencePattern(qualificationChars, typeChars, matchRule); case IJavaScriptSearchConstants.ALL_OCCURRENCES : return new OrPattern( new QualifiedTypeDeclarationPattern(qualificationChars, typeChars, indexSuffix, matchRule),// cannot search for explicit member types new TypeReferencePattern(qualificationChars, typeChars, matchRule)); } return null; } /** * Returns the enclosing type names of the given type. */ private static char[][] enclosingTypeNames(IType type) { IJavaScriptElement parent = type.getParent(); switch (parent.getElementType()) { case IJavaScriptElement.CLASS_FILE: // For a binary type, the parent is not the enclosing type, but the declaring type is. // (see bug 20532 Declaration of member binary type not found) IType declaringType = type.getDeclaringType(); if (declaringType == null) return CharOperation.NO_CHAR_CHAR; return CharOperation.arrayConcat( enclosingTypeNames(declaringType), declaringType.getElementName().toCharArray()); case IJavaScriptElement.JAVASCRIPT_UNIT: return CharOperation.NO_CHAR_CHAR; case IJavaScriptElement.FIELD: case IJavaScriptElement.INITIALIZER: case IJavaScriptElement.METHOD: IType declaringClass = ((IMember) parent).getDeclaringType(); if (declaringClass!=null) return CharOperation.arrayConcat( enclosingTypeNames(declaringClass), new char[][] {declaringClass.getElementName().toCharArray(), IIndexConstants.ONE_STAR}); else return CharOperation.NO_CHAR_CHAR; case IJavaScriptElement.TYPE: return CharOperation.arrayConcat( enclosingTypeNames((IType)parent), parent.getElementName().toCharArray()); default: return null; } } /** * Decode the given index key in this pattern. The decoded index key is used by * {@link #matchesDecodedKey(SearchPattern)} to find out if the corresponding index entry * should be considered. * <p> * This method should be re-implemented in subclasses that need to decode an index key. * </p> * * @param key the given index key */ public void decodeIndexKey(char[] key) { // called from findIndexMatches(), override as necessary } /** * Returns a blank pattern that can be used as a record to decode an index key. * <p> * Implementors of this method should return a new search pattern that is going to be used * to decode index keys. * </p> * * @return a new blank pattern * @see #decodeIndexKey(char[]) */ public abstract SearchPattern getBlankPattern(); /** * Returns a key to find in relevant index categories, if null then all index entries are matched. * The key will be matched according to some match rule. These potential matches * will be further narrowed by the match locator, but precise match locating can be expensive, * and index query should be as accurate as possible so as to eliminate obvious false hits. * <p> * This method should be re-implemented in subclasses that need to narrow down the * index query. * </p> * * @return an index key from this pattern, or <code>null</code> if all index entries are matched. */ public char[] getIndexKey() { return null; // called from queryIn(), override as necessary } /** * Returns an array of index categories to consider for this index query. * These potential matches will be further narrowed by the match locator, but precise * match locating can be expensive, and index query should be as accurate as possible * so as to eliminate obvious false hits. * <p> * This method should be re-implemented in subclasses that need to narrow down the * index query. * </p> * * @return an array of index categories */ public char[][] getIndexCategories() { return CharOperation.NO_CHAR_CHAR; // called from queryIn(), override as necessary } /** * Returns the rule to apply for matching index keys. Can be exact match, prefix match, pattern match or regexp match. * Rule can also be combined with a case sensitivity flag. * * @return one of R_EXACT_MATCH, R_PREFIX_MATCH, R_PATTERN_MATCH, R_REGEXP_MATCH combined with R_CASE_SENSITIVE, * e.g. R_EXACT_MATCH | R_CASE_SENSITIVE if an exact and case sensitive match is requested, * or R_PREFIX_MATCH if a prefix non case sensitive match is requested. * [TODO (frederic) I hope R_ERASURE_MATCH doesn't need to be on this list. Because it would be a breaking API change.] */ public final int getMatchRule() { return this.matchRule; } /** * Returns whether this pattern matches the given pattern (representing a decoded index key). * <p> * This method should be re-implemented in subclasses that need to narrow down the * index query. * </p> * * @param decodedPattern a pattern representing a decoded index key * @return whether this pattern matches the given pattern */ public boolean matchesDecodedKey(SearchPattern decodedPattern) { return true; // called from findIndexMatches(), override as necessary if index key is encoded } /** * Returns whether the given name matches the given pattern. * <p> * This method should be re-implemented in subclasses that need to define how * a name matches a pattern. * </p> * * @param pattern the given pattern, or <code>null</code> to represent "*" * @param name the given name * @return whether the given name matches the given pattern */ public boolean matchesName(char[] pattern, char[] name) { if (pattern == null) return true; // null is as if it was "*" if (name != null) { boolean isCaseSensitive = (this.matchRule & R_CASE_SENSITIVE) != 0; boolean isCamelCase = (this.matchRule & R_CAMELCASE_MATCH) != 0; int matchMode = this.matchRule & MODE_MASK; boolean emptyPattern = pattern.length == 0; if (matchMode == R_PREFIX_MATCH && emptyPattern) return true; boolean sameLength = pattern.length == name.length; boolean canBePrefix = name.length >= pattern.length; boolean matchFirstChar = !isCaseSensitive || emptyPattern || (name.length > 0 && pattern[0] == name[0]); if (isCamelCase && matchFirstChar && CharOperation.camelCaseMatch(pattern, name)) { return true; } switch (matchMode) { case R_EXACT_MATCH : case R_FULL_MATCH : if (!isCamelCase) { if (sameLength && matchFirstChar) { return CharOperation.equals(pattern, name, isCaseSensitive); } break; } // fall through next case to match as prefix if camel case failed case R_PREFIX_MATCH : if (canBePrefix && matchFirstChar) { return CharOperation.prefixEquals(pattern, name, isCaseSensitive); } break; case R_PATTERN_MATCH : if (!isCaseSensitive) pattern = CharOperation.toLowerCase(pattern); return CharOperation.match(pattern, name, isCaseSensitive); case R_REGEXP_MATCH : // TODO (frederic) implement regular expression match return true; } } return false; } /** * Validate compatibility between given string pattern and match rule. *<br> * Optimized (ie. returned match rule is modified) combinations are: * <ul> * <li>{@link #R_PATTERN_MATCH} without any '*' or '?' in string pattern: * pattern match bit is unset, * </li> * <li>{@link #R_PATTERN_MATCH} and {@link #R_PREFIX_MATCH} bits simultaneously set: * prefix match bit is unset, * </li> * <li>{@link #R_PATTERN_MATCH} and {@link #R_CAMELCASE_MATCH} bits simultaneously set: * camel case match bit is unset, * </li> * <li>{@link #R_CAMELCASE_MATCH} with invalid combination of uppercase and lowercase characters: * camel case match bit is unset and replaced with prefix match pattern, * </li> * <li>{@link #R_CAMELCASE_MATCH} combined with {@link #R_PREFIX_MATCH} and {@link #R_CASE_SENSITIVE} * bits is reduced to only {@link #R_CAMELCASE_MATCH} as Camel Case search is already prefix and case sensitive, * </li> * </ul> *<br> * Rejected (ie. returned match rule -1) combinations are: * <ul> * <li>{@link #R_REGEXP_MATCH} with any other match mode bit set, * </li> * </ul> * * @param stringPattern The string pattern * @param matchRule The match rule * @return Optimized valid match rule or -1 if an incompatibility was detected. * */ public static int validateMatchRule(String stringPattern, int matchRule) { // Verify Regexp match rule if ((matchRule & R_REGEXP_MATCH) != 0) { if ((matchRule & R_PATTERN_MATCH) != 0 || (matchRule & R_PREFIX_MATCH) != 0 || (matchRule & R_CAMELCASE_MATCH) != 0) { return -1; } } // Verify Pattern match rule int starIndex = stringPattern.indexOf('*'); int questionIndex = stringPattern.indexOf('?'); if (starIndex < 0 && questionIndex < 0) { // reset pattern match bit if any matchRule &= ~R_PATTERN_MATCH; } else { // force Pattern rule matchRule |= R_PATTERN_MATCH; } if ((matchRule & R_PATTERN_MATCH) != 0) { // remove Camel Case and Prefix match bits if any matchRule &= ~R_CAMELCASE_MATCH; matchRule &= ~R_PREFIX_MATCH; } // Verify Camel Case match rule if ((matchRule & R_CAMELCASE_MATCH) != 0) { // Verify sting pattern validity int length = stringPattern.length(); boolean validCamelCase = true; boolean uppercase = false; for (int i=0; i<length && validCamelCase; i++) { char ch = stringPattern.charAt(i); validCamelCase = ScannerHelper.isJavaIdentifierStart(ch); // at least one uppercase character is need in CamelCase pattern // (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=136313) if (!uppercase) uppercase = ScannerHelper.isUpperCase(ch); } validCamelCase = validCamelCase && uppercase; // Verify bits compatibility if (validCamelCase) { if ((matchRule & R_PREFIX_MATCH) != 0) { if ((matchRule & R_CASE_SENSITIVE) != 0) { // This is equivalent to Camel Case match rule matchRule &= ~R_PREFIX_MATCH; matchRule &= ~R_CASE_SENSITIVE; } } } else { matchRule &= ~R_CAMELCASE_MATCH; if ((matchRule & R_PREFIX_MATCH) == 0) { matchRule |= R_PREFIX_MATCH; matchRule |= R_CASE_SENSITIVE; } } } return matchRule; } /** * @see java.lang.Object#toString() */ public String toString() { return "SearchPattern"; //$NON-NLS-1$ } }