/******************************************************************************* * 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.jdt.internal.core.search.matching; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.ImportReference; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Reference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.CaptureBinding; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants; public abstract class PatternLocator implements IIndexConstants { // store pattern info protected int matchMode; protected boolean isCaseSensitive; protected boolean isEquivalentMatch; protected boolean isErasureMatch; protected boolean mustResolve; protected boolean mayBeGeneric; // match to report SearchMatch match= null; /* match levels */ public static final int IMPOSSIBLE_MATCH= 0; public static final int INACCURATE_MATCH= 1; public static final int POSSIBLE_MATCH= 2; public static final int ACCURATE_MATCH= 3; public static final int ERASURE_MATCH= 4; // Possible rule match flavors // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=79866 public static final int EXACT_FLAVOR= 0x0010; public static final int PREFIX_FLAVOR= 0x0020; public static final int PATTERN_FLAVOR= 0x0040; public static final int REGEXP_FLAVOR= 0x0080; public static final int CAMELCASE_FLAVOR= 0x0100; public static final int SUPER_INVOCATION_FLAVOR= 0x0200; public static final int SUB_INVOCATION_FLAVOR= 0x0400; public static final int OVERRIDDEN_METHOD_FLAVOR= 0x0800; public static final int MATCH_LEVEL_MASK= 0x0F; public static final int FLAVORS_MASK= ~MATCH_LEVEL_MASK; /* match container */ public static final int COMPILATION_UNIT_CONTAINER= 1; public static final int CLASS_CONTAINER= 2; public static final int METHOD_CONTAINER= 4; public static final int FIELD_CONTAINER= 8; public static final int ALL_CONTAINER= COMPILATION_UNIT_CONTAINER | CLASS_CONTAINER | METHOD_CONTAINER | FIELD_CONTAINER; /* match rule */ public static final int RAW_MASK= SearchPattern.R_EQUIVALENT_MATCH | SearchPattern.R_ERASURE_MATCH; public static final int RULE_MASK= RAW_MASK; // no other values for the while... public static PatternLocator patternLocator(SearchPattern pattern) { switch (pattern.kind) { case IIndexConstants.PKG_REF_PATTERN: return new PackageReferenceLocator((PackageReferencePattern)pattern); case IIndexConstants.PKG_DECL_PATTERN: return new PackageDeclarationLocator((PackageDeclarationPattern)pattern); case IIndexConstants.TYPE_REF_PATTERN: return new TypeReferenceLocator((TypeReferencePattern)pattern); case IIndexConstants.TYPE_DECL_PATTERN: return new TypeDeclarationLocator((TypeDeclarationPattern)pattern); case IIndexConstants.SUPER_REF_PATTERN: return new SuperTypeReferenceLocator((SuperTypeReferencePattern)pattern); case IIndexConstants.CONSTRUCTOR_PATTERN: return new ConstructorLocator((ConstructorPattern)pattern); case IIndexConstants.FIELD_PATTERN: return new FieldLocator((FieldPattern)pattern); case IIndexConstants.METHOD_PATTERN: return new MethodLocator((MethodPattern)pattern); case IIndexConstants.OR_PATTERN: return new OrLocator((OrPattern)pattern); case IIndexConstants.AND_PATTERN: return new AndLocator((AndPattern)pattern); case IIndexConstants.LOCAL_VAR_PATTERN: return new LocalVariableLocator((LocalVariablePattern)pattern); case IIndexConstants.TYPE_PARAM_PATTERN: return new TypeParameterLocator((TypeParameterPattern)pattern); } return null; } public static char[] qualifiedPattern(char[] simpleNamePattern, char[] qualificationPattern) { // NOTE: if case insensitive search then simpleNamePattern & qualificationPattern are assumed to be lowercase if (simpleNamePattern == null) { if (qualificationPattern == null) return null; return CharOperation.concat(qualificationPattern, ONE_STAR, '.'); } else { return qualificationPattern == null ? CharOperation.concat(ONE_STAR, simpleNamePattern) : CharOperation.concat(qualificationPattern, simpleNamePattern, '.'); } } public static char[] qualifiedSourceName(TypeBinding binding) { if (binding instanceof ReferenceBinding) { ReferenceBinding type= (ReferenceBinding)binding; if (type.isLocalType()) return type.isMemberType() ? CharOperation.concat(qualifiedSourceName(type.enclosingType()), type.sourceName(), '.') : CharOperation.concat(qualifiedSourceName(type.enclosingType()), new char[] { '.', '1', '.' }, type.sourceName()); } return binding != null ? binding.qualifiedSourceName() : null; } public PatternLocator(SearchPattern pattern) { int matchRule= pattern.getMatchRule(); this.isCaseSensitive= (matchRule & SearchPattern.R_CASE_SENSITIVE) != 0; this.isErasureMatch= (matchRule & SearchPattern.R_ERASURE_MATCH) != 0; this.isEquivalentMatch= (matchRule & SearchPattern.R_EQUIVALENT_MATCH) != 0; this.matchMode= matchRule & JavaSearchPattern.MATCH_MODE_MASK; this.mustResolve= pattern.mustResolve; } /* * Clear caches */ protected void clear() { // nothing to clear by default } /* (non-Javadoc) * Modify PatternLocator.qualifiedPattern behavior: * do not add star before simple name pattern when qualification pattern is null. * This avoid to match p.X when pattern is only X... */ protected char[] getQualifiedPattern(char[] simpleNamePattern, char[] qualificationPattern) { // NOTE: if case insensitive search then simpleNamePattern & qualificationPattern are assumed to be lowercase if (simpleNamePattern == null) { if (qualificationPattern == null) return null; return CharOperation.concat(qualificationPattern, ONE_STAR, '.'); } else if (qualificationPattern == null) { return simpleNamePattern; } else { return CharOperation.concat(qualificationPattern, simpleNamePattern, '.'); } } /* (non-Javadoc) * Modify PatternLocator.qualifiedSourceName behavior: * also concatene enclosing type name when type is a only a member type. */ protected char[] getQualifiedSourceName(TypeBinding binding) { TypeBinding type= binding instanceof ArrayBinding ? ((ArrayBinding)binding).leafComponentType : binding; if (type instanceof ReferenceBinding) { if (type.isLocalType()) { return CharOperation.concat(qualifiedSourceName(type.enclosingType()), new char[] { '.', '1', '.' }, binding.sourceName()); } else if (type.isMemberType()) { return CharOperation.concat(qualifiedSourceName(type.enclosingType()), binding.sourceName(), '.'); } } return binding != null ? binding.qualifiedSourceName() : null; } /* * Get binding of type argument from a class unit scope and its index position. * Cache is lazy initialized and if no binding is found, then store a problem binding * to avoid making research twice... */ protected TypeBinding getTypeNameBinding(int index) { return null; } /** * Initializes this search pattern so that polymorphic search can be performed. */ public void initializePolymorphicSearch(MatchLocator locator) { // default is to do nothing } public int match(Annotation node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } /** * Check if the given ast node syntactically matches this pattern. If it does, add it to the * match set. Returns the match level. */ public int match(ASTNode node, MatchingNodeSet nodeSet) { // needed for some generic nodes // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(ConstructorDeclaration node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(Expression node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(FieldDeclaration node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(LocalDeclaration node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(MethodDeclaration node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(MemberValuePair node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(MessageSend node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(Reference node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(TypeDeclaration node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(TypeParameter node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } public int match(TypeReference node, MatchingNodeSet nodeSet) { // each subtype should override if needed return IMPOSSIBLE_MATCH; } /** * Returns the type(s) of container for this pattern. It is a bit combination of types, denoting * compilation unit, class declarations, field declarations or method declarations. */ protected int matchContainer() { // override if the pattern can be more specific return ALL_CONTAINER; } protected int fineGrain() { return 0; } /** * Returns whether the given name matches the given pattern. */ protected boolean matchesName(char[] pattern, char[] name) { if (pattern == null) return true; // null is as if it was "*" if (name == null) return false; // cannot match null name return matchNameValue(pattern, name) != IMPOSSIBLE_MATCH; } /** * Return how the given name matches the given pattern. * * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=79866" * * @param pattern * @param name * @return Possible values are: * <ul> * <li> {@link #ACCURATE_MATCH}</li> * <li> {@link #IMPOSSIBLE_MATCH}</li> * <li> {@link #POSSIBLE_MATCH} which may be flavored with following values: * <ul> * <li>{@link #EXACT_FLAVOR}: Given name is equals to pattern</li> * <li>{@link #PREFIX_FLAVOR}: Given name prefix equals to pattern</li> * <li>{@link #CAMELCASE_FLAVOR}: Given name matches pattern as Camel Case</li> * <li>{@link #PATTERN_FLAVOR}: Given name matches pattern as Pattern (ie. using '*' and * '?' characters)</li> * </ul> * </li> * </ul> */ protected int matchNameValue(char[] pattern, char[] name) { if (pattern == null) return ACCURATE_MATCH; // null is as if it was "*" if (name == null) return IMPOSSIBLE_MATCH; // cannot match null name if (name.length == 0) { // empty name if (pattern.length == 0) { // can only matches empty pattern return ACCURATE_MATCH; } return IMPOSSIBLE_MATCH; } else if (pattern.length == 0) { return IMPOSSIBLE_MATCH; // need to have both name and pattern length==0 to be accurate } boolean matchFirstChar= !this.isCaseSensitive || pattern[0] == name[0]; boolean sameLength= pattern.length == name.length; boolean canBePrefix= name.length >= pattern.length; switch (this.matchMode) { case SearchPattern.R_EXACT_MATCH: if (sameLength && matchFirstChar && CharOperation.equals(pattern, name, this.isCaseSensitive)) { return POSSIBLE_MATCH | EXACT_FLAVOR; } break; case SearchPattern.R_PREFIX_MATCH: if (canBePrefix && matchFirstChar && CharOperation.prefixEquals(pattern, name, this.isCaseSensitive)) { return POSSIBLE_MATCH; } break; case SearchPattern.R_PATTERN_MATCH: // TODO_PERFS (frederic) Not sure this lowercase is necessary if (!this.isCaseSensitive) { pattern= CharOperation.toLowerCase(pattern); } if (CharOperation.match(pattern, name, this.isCaseSensitive)) { return POSSIBLE_MATCH; } break; case SearchPattern.R_REGEXP_MATCH: // TODO (frederic) implement regular expression match break; case SearchPattern.R_CAMELCASE_MATCH: if (CharOperation.camelCaseMatch(pattern, name, false)) { return POSSIBLE_MATCH; } // only test case insensitive as CamelCase same part count already verified prefix case sensitive if (!this.isCaseSensitive && CharOperation.prefixEquals(pattern, name, false)) { return POSSIBLE_MATCH; } break; case SearchPattern.R_CAMELCASE_SAME_PART_COUNT_MATCH: if (CharOperation.camelCaseMatch(pattern, name, true)) { return POSSIBLE_MATCH; } break; } return IMPOSSIBLE_MATCH; } /** * Returns whether the given type reference matches the given pattern. */ protected boolean matchesTypeReference(char[] pattern, TypeReference type) { if (pattern == null) return true; // null is as if it was "*" if (type == null) return true; // treat as an inexact match char[][] compoundName= type.getTypeName(); char[] simpleName= compoundName[compoundName.length - 1]; int dimensions= type.dimensions() * 2; if (dimensions > 0) { int length= simpleName.length; char[] result= new char[length + dimensions]; System.arraycopy(simpleName, 0, result, 0, length); for (int i= length, l= result.length; i < l;) { result[i++]= '['; result[i++]= ']'; } simpleName= result; } return matchesName(pattern, simpleName); } /** * Returns the match level for the given importRef. */ protected int matchLevel(ImportReference importRef) { // override if interested in import references which are caught by the generic version of match(ASTNode, MatchingNodeSet) return IMPOSSIBLE_MATCH; } /** * Reports the match of the given import reference if the resolveLevel is high enough. */ protected void matchLevelAndReportImportRef(ImportReference importRef, Binding binding, MatchLocator locator) throws CoreException { int level= resolveLevel(binding); if (level >= INACCURATE_MATCH) { matchReportImportRef( importRef, binding, locator.createImportHandle(importRef), level == ACCURATE_MATCH ? SearchMatch.A_ACCURATE : SearchMatch.A_INACCURATE, locator); } } /** * Reports the match of the given import reference. */ protected void matchReportImportRef(ImportReference importRef, Binding binding, IJavaElement element, int accuracy, MatchLocator locator) throws CoreException { if (locator.encloses(element)) { // default is to report a match as a regular ref. this.matchReportReference(importRef, element, null/*no binding*/, accuracy, locator); } } /** * Reports the match of the given reference. */ protected void matchReportReference(ASTNode reference, IJavaElement element, Binding elementBinding, int accuracy, MatchLocator locator) throws CoreException { this.match= null; int referenceType= referenceType(); int offset= reference.sourceStart; switch (referenceType) { case IJavaElement.PACKAGE_FRAGMENT: this.match= locator.newPackageReferenceMatch(element, accuracy, offset, reference.sourceEnd - offset + 1, reference); break; case IJavaElement.TYPE: this.match= locator.newTypeReferenceMatch(element, elementBinding, accuracy, offset, reference.sourceEnd - offset + 1, reference); break; case IJavaElement.FIELD: this.match= locator.newFieldReferenceMatch(element, null, elementBinding, accuracy, offset, reference.sourceEnd - offset + 1, reference); break; case IJavaElement.LOCAL_VARIABLE: this.match= locator.newLocalVariableReferenceMatch(element, accuracy, offset, reference.sourceEnd - offset + 1, reference); break; case IJavaElement.TYPE_PARAMETER: this.match= locator.newTypeParameterReferenceMatch(element, accuracy, offset, reference.sourceEnd - offset + 1, reference); break; } if (this.match != null) { locator.report(this.match); } } /** * Reports the match of the given reference. Also provide a local element to eventually report * in match. */ protected void matchReportReference(ASTNode reference, IJavaElement element, IJavaElement localElement, IJavaElement[] otherElements, Binding elementBinding, int accuracy, MatchLocator locator) throws CoreException { matchReportReference(reference, element, elementBinding, accuracy, locator); } public SearchMatch newDeclarationMatch(ASTNode reference, IJavaElement element, Binding elementBinding, int accuracy, int length, MatchLocator locator) { return locator.newDeclarationMatch(element, elementBinding, accuracy, reference.sourceStart, length); } protected int referenceType() { return 0; // defaults to unknown (a generic JavaSearchMatch will be created) } /** * Finds out whether the given ast node matches this search pattern. Returns IMPOSSIBLE_MATCH if * it doesn't. Returns INACCURATE_MATCH if it potentially matches this search pattern (ie. it * has already been resolved but resolving failed.) Returns ACCURATE_MATCH if it matches exactly * this search pattern (ie. it doesn't need to be resolved or it has already been resolved.) */ public int resolveLevel(ASTNode possibleMatchingNode) { // only called with nodes which were possible matches to the call to matchLevel // need to do instance of checks to find out exact type of ASTNode return IMPOSSIBLE_MATCH; } /* * Update pattern locator match for parameterized top level types. * Set match raw flag and recurse to enclosing types if any... */ protected void updateMatch(ParameterizedTypeBinding parameterizedBinding, char[][][] patternTypeArguments, MatchLocator locator) { // Only possible if locator has an unit scope. if (locator.unitScope != null) { updateMatch(parameterizedBinding, patternTypeArguments, false, 0, locator); } } protected void updateMatch(ParameterizedTypeBinding parameterizedBinding, char[][][] patternTypeArguments, boolean patternHasTypeParameters, int depth, MatchLocator locator) { // Only possible if locator has an unit scope. if (locator.unitScope == null) return; // Set match raw flag boolean endPattern= patternTypeArguments == null ? true : depth >= patternTypeArguments.length; TypeBinding[] argumentsBindings= parameterizedBinding.arguments; boolean isRaw= parameterizedBinding.isRawType() || (argumentsBindings == null && parameterizedBinding.genericType().isGenericType()); if (isRaw && !this.match.isRaw()) { this.match.setRaw(isRaw); } // Update match if (!endPattern && patternTypeArguments != null) { // verify if this is a reference to the generic type itself if (!isRaw && patternHasTypeParameters && argumentsBindings != null) { boolean needUpdate= false; TypeVariableBinding[] typeVariables= parameterizedBinding.genericType().typeVariables(); int length= argumentsBindings.length; if (length == typeVariables.length) { for (int i= 0; i < length; i++) { if (argumentsBindings[i] != typeVariables[i]) { needUpdate= true; break; } } } if (needUpdate) { char[][] patternArguments= patternTypeArguments[depth]; updateMatch(argumentsBindings, locator, patternArguments, patternHasTypeParameters); } } else { char[][] patternArguments= patternTypeArguments[depth]; updateMatch(argumentsBindings, locator, patternArguments, patternHasTypeParameters); } } // Recurse TypeBinding enclosingType= parameterizedBinding.enclosingType(); if (enclosingType != null && (enclosingType.isParameterizedType() || enclosingType.isRawType())) { updateMatch((ParameterizedTypeBinding)enclosingType, patternTypeArguments, patternHasTypeParameters, depth + 1, locator); } } /* * Update pattern locator match comparing type arguments with pattern ones. * Try to resolve pattern and look for compatibility with type arguments * to set match rule. */ protected void updateMatch(TypeBinding[] argumentsBinding, MatchLocator locator, char[][] patternArguments, boolean hasTypeParameters) { // Only possible if locator has an unit scope. if (locator.unitScope == null) return; // First compare lengthes int patternTypeArgsLength= patternArguments == null ? 0 : patternArguments.length; int typeArgumentsLength= argumentsBinding == null ? 0 : argumentsBinding.length; // Initialize match rule int matchRule= this.match.getRule(); if (this.match.isRaw()) { if (patternTypeArgsLength != 0) { matchRule&= ~SearchPattern.R_FULL_MATCH; } } if (hasTypeParameters) { matchRule= SearchPattern.R_ERASURE_MATCH; } // Compare arguments lengthes if (patternTypeArgsLength == typeArgumentsLength) { if (!this.match.isRaw() && hasTypeParameters) { // generic patterns are always not compatible match this.match.setRule(SearchPattern.R_ERASURE_MATCH); return; } } else { if (patternTypeArgsLength == 0) { if (!this.match.isRaw() || hasTypeParameters) { this.match.setRule(matchRule & ~SearchPattern.R_FULL_MATCH); } } else if (typeArgumentsLength == 0) { // raw binding is always compatible this.match.setRule(matchRule & ~SearchPattern.R_FULL_MATCH); } else { this.match.setRule(0); // impossible match } return; } if (argumentsBinding == null || patternArguments == null) { this.match.setRule(matchRule); return; } // Compare binding for each type argument only if pattern is not erasure only and at first level if (!hasTypeParameters && !this.match.isRaw() && (this.match.isEquivalent() || this.match.isExact())) { for (int i= 0; i < typeArgumentsLength; i++) { // Get parameterized type argument binding TypeBinding argumentBinding= argumentsBinding[i]; if (argumentBinding instanceof CaptureBinding) { WildcardBinding capturedWildcard= ((CaptureBinding)argumentBinding).wildcard; if (capturedWildcard != null) argumentBinding= capturedWildcard; } // Get binding for pattern argument char[] patternTypeArgument= patternArguments[i]; char patternWildcard= patternTypeArgument[0]; char[] patternTypeName= patternTypeArgument; int patternWildcardKind= -1; switch (patternWildcard) { case Signature.C_STAR: if (argumentBinding.isWildcard()) { WildcardBinding wildcardBinding= (WildcardBinding)argumentBinding; if (wildcardBinding.boundKind == Wildcard.UNBOUND) continue; } matchRule&= ~SearchPattern.R_FULL_MATCH; continue; // unbound parameter always match case Signature.C_EXTENDS: patternWildcardKind= Wildcard.EXTENDS; patternTypeName= CharOperation.subarray(patternTypeArgument, 1, patternTypeArgument.length); break; case Signature.C_SUPER: patternWildcardKind= Wildcard.SUPER; patternTypeName= CharOperation.subarray(patternTypeArgument, 1, patternTypeArgument.length); break; default: break; } patternTypeName= Signature.toCharArray(patternTypeName); TypeBinding patternBinding= locator.getType(patternTypeArgument, patternTypeName); // If have no binding for pattern arg, then we won't be able to refine accuracy if (patternBinding == null) { if (argumentBinding.isWildcard()) { WildcardBinding wildcardBinding= (WildcardBinding)argumentBinding; if (wildcardBinding.boundKind == Wildcard.UNBOUND) { matchRule&= ~SearchPattern.R_FULL_MATCH; } else { this.match.setRule(SearchPattern.R_ERASURE_MATCH); return; } } continue; } // Verify tha pattern binding is compatible with match type argument binding switch (patternWildcard) { case Signature.C_STAR: // UNBOUND pattern // unbound always match => skip to next argument matchRule&= ~SearchPattern.R_FULL_MATCH; continue; case Signature.C_EXTENDS: // EXTENDS pattern if (argumentBinding.isWildcard()) { // argument is a wildcard WildcardBinding wildcardBinding= (WildcardBinding)argumentBinding; // It's ok if wildcards are identical if (wildcardBinding.boundKind == patternWildcardKind && wildcardBinding.bound == patternBinding) { continue; } // Look for wildcard compatibility switch (wildcardBinding.boundKind) { case Wildcard.EXTENDS: if (wildcardBinding.bound == null || wildcardBinding.bound.isCompatibleWith(patternBinding)) { // valid when arg extends a subclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; case Wildcard.SUPER: break; case Wildcard.UNBOUND: matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } } else if (argumentBinding.isCompatibleWith(patternBinding)) { // valid when arg is a subclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; case Signature.C_SUPER: // SUPER pattern if (argumentBinding.isWildcard()) { // argument is a wildcard WildcardBinding wildcardBinding= (WildcardBinding)argumentBinding; // It's ok if wildcards are identical if (wildcardBinding.boundKind == patternWildcardKind && wildcardBinding.bound == patternBinding) { continue; } // Look for wildcard compatibility switch (wildcardBinding.boundKind) { case Wildcard.EXTENDS: break; case Wildcard.SUPER: if (wildcardBinding.bound == null || patternBinding.isCompatibleWith(wildcardBinding.bound)) { // valid only when arg super a superclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; case Wildcard.UNBOUND: matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } } else if (patternBinding.isCompatibleWith(argumentBinding)) { // valid only when arg is a superclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; default: if (argumentBinding.isWildcard()) { WildcardBinding wildcardBinding= (WildcardBinding)argumentBinding; switch (wildcardBinding.boundKind) { case Wildcard.EXTENDS: if (wildcardBinding.bound == null || patternBinding.isCompatibleWith(wildcardBinding.bound)) { // valid only when arg extends a superclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; case Wildcard.SUPER: if (wildcardBinding.bound == null || wildcardBinding.bound.isCompatibleWith(patternBinding)) { // valid only when arg super a subclass of pattern matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } break; case Wildcard.UNBOUND: matchRule&= ~SearchPattern.R_FULL_MATCH; continue; } } else if (argumentBinding == patternBinding) // valid only when arg is equals to pattern continue; break; } // Argument does not match => erasure match will be the only possible one this.match.setRule(SearchPattern.R_ERASURE_MATCH); return; } } // Set match rule this.match.setRule(matchRule); } /** * Finds out whether the given binding matches this search pattern. Returns ACCURATE_MATCH if it * does. Returns INACCURATE_MATCH if resolve failed but match is still possible. Returns * IMPOSSIBLE_MATCH otherwise. Default is to return INACCURATE_MATCH. */ public int resolveLevel(Binding binding) { // override if the pattern can match the binding return INACCURATE_MATCH; } /** * Returns whether the given type binding matches the given simple name pattern and * qualification pattern. Note that from since 3.1, this method resolve to accurate member or * local types even if they are not fully qualified (ie. X.Member instead of p.X.Member). * Returns ACCURATE_MATCH if it does. Returns INACCURATE_MATCH if resolve failed. Returns * IMPOSSIBLE_MATCH if it doesn't. */ protected int resolveLevelForType(char[] simpleNamePattern, char[] qualificationPattern, TypeBinding binding) { // return resolveLevelForType(qualifiedPattern(simpleNamePattern, qualificationPattern), type); char[] qualifiedPattern= getQualifiedPattern(simpleNamePattern, qualificationPattern); int level= resolveLevelForType(qualifiedPattern, binding); if (level == ACCURATE_MATCH || binding == null || !binding.isValidBinding()) return level; TypeBinding type= binding instanceof ArrayBinding ? ((ArrayBinding)binding).leafComponentType : binding; char[] sourceName= null; if (type.isMemberType() || type.isLocalType()) { if (qualificationPattern != null) { sourceName= getQualifiedSourceName(binding); } else { sourceName= binding.sourceName(); } } else if (qualificationPattern == null) { sourceName= getQualifiedSourceName(binding); } if (sourceName == null) return IMPOSSIBLE_MATCH; switch (this.matchMode) { case SearchPattern.R_PREFIX_MATCH: if (CharOperation.prefixEquals(qualifiedPattern, sourceName, this.isCaseSensitive)) { return ACCURATE_MATCH; } break; case SearchPattern.R_CAMELCASE_MATCH: if ((qualifiedPattern.length > 0 && sourceName.length > 0 && qualifiedPattern[0] == sourceName[0])) { if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName, false)) { return ACCURATE_MATCH; } if (!this.isCaseSensitive && CharOperation.prefixEquals(qualifiedPattern, sourceName, false)) { return ACCURATE_MATCH; } } break; case SearchPattern.R_CAMELCASE_SAME_PART_COUNT_MATCH: if ((qualifiedPattern.length > 0 && sourceName.length > 0 && qualifiedPattern[0] == sourceName[0])) { if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName, true)) { return ACCURATE_MATCH; } } break; default: if (CharOperation.match(qualifiedPattern, sourceName, this.isCaseSensitive)) { return ACCURATE_MATCH; } } return IMPOSSIBLE_MATCH; } /** * Returns whether the given type binding matches the given qualified pattern. Returns * ACCURATE_MATCH if it does. Returns INACCURATE_MATCH if resolve failed. Returns * IMPOSSIBLE_MATCH if it doesn't. */ protected int resolveLevelForType(char[] qualifiedPattern, TypeBinding type) { if (qualifiedPattern == null) return ACCURATE_MATCH; if (type == null || !type.isValidBinding()) return INACCURATE_MATCH; // Type variable cannot be specified through pattern => this kind of binding cannot match it (see bug 79803) if (type.isTypeVariable()) return IMPOSSIBLE_MATCH; // NOTE: if case insensitive search then qualifiedPattern is assumed to be lowercase char[] qualifiedPackageName= type.qualifiedPackageName(); char[] qualifiedSourceName= qualifiedSourceName(type); char[] fullyQualifiedTypeName= qualifiedPackageName.length == 0 ? qualifiedSourceName : CharOperation.concat(qualifiedPackageName, qualifiedSourceName, '.'); return CharOperation.match(qualifiedPattern, fullyQualifiedTypeName, this.isCaseSensitive) ? ACCURATE_MATCH : IMPOSSIBLE_MATCH; } /* (non-Javadoc) * Resolve level for type with a given binding with all pattern information. */ protected int resolveLevelForType(char[] simpleNamePattern, char[] qualificationPattern, char[][][] patternTypeArguments, int depth, TypeBinding type) { // standard search with no generic additional information must succeed int level= resolveLevelForType(simpleNamePattern, qualificationPattern, type); if (level == IMPOSSIBLE_MATCH) return IMPOSSIBLE_MATCH; if (type == null || patternTypeArguments == null || patternTypeArguments.length == 0 || depth >= patternTypeArguments.length) { return level; } // if pattern is erasure match (see bug 79790), commute impossible to erasure int impossible= this.isErasureMatch ? ERASURE_MATCH : IMPOSSIBLE_MATCH; // pattern has type parameter(s) or type argument(s) if (type.isGenericType()) { // Binding is generic, get its type variable(s) TypeVariableBinding[] typeVariables= null; if (type instanceof SourceTypeBinding) { SourceTypeBinding sourceTypeBinding= (SourceTypeBinding)type; typeVariables= sourceTypeBinding.typeVariables; } else if (type instanceof BinaryTypeBinding) { BinaryTypeBinding binaryTypeBinding= (BinaryTypeBinding)type; if (this.mustResolve) typeVariables= binaryTypeBinding.typeVariables(); // TODO (frederic) verify performance } if (patternTypeArguments[depth] != null && patternTypeArguments[depth].length > 0 && typeVariables != null && typeVariables.length > 0) { if (typeVariables.length != patternTypeArguments[depth].length) return IMPOSSIBLE_MATCH; } // TODO (frederic) do we need to verify each parameter? return level; // we can't do better } // raw type always match if (type.isRawType()) { return level; } // Standard types (ie. neither generic nor parameterized nor raw types) // cannot match pattern with type parameters or arguments TypeBinding leafType= type.leafComponentType(); if (!leafType.isParameterizedType()) { return (patternTypeArguments[depth] == null || patternTypeArguments[depth].length == 0) ? level : IMPOSSIBLE_MATCH; } // Parameterized type ParameterizedTypeBinding paramTypeBinding= (ParameterizedTypeBinding)leafType; // Compare arguments only if there ones on both sides if (patternTypeArguments[depth] != null && patternTypeArguments[depth].length > 0 && paramTypeBinding.arguments != null && paramTypeBinding.arguments.length > 0) { // type parameters length must match at least specified type names length int length= patternTypeArguments[depth].length; if (paramTypeBinding.arguments.length != length) return IMPOSSIBLE_MATCH; // verify each pattern type parameter nextTypeArgument: for (int i= 0; i < length; i++) { char[] patternTypeArgument= patternTypeArguments[depth][i]; TypeBinding argTypeBinding= paramTypeBinding.arguments[i]; // get corresponding pattern wildcard switch (patternTypeArgument[0]) { case Signature.C_STAR: // unbound parameter always match case Signature.C_SUPER: // needs pattern type parameter binding // skip to next type argument as it will be resolved later continue nextTypeArgument; case Signature.C_EXTENDS: // remove wildcard from patter type argument patternTypeArgument= CharOperation.subarray(patternTypeArgument, 1, patternTypeArgument.length); break; default: // no wildcard break; } // get pattern type argument from its signature patternTypeArgument= Signature.toCharArray(patternTypeArgument); if (!this.isCaseSensitive) patternTypeArgument= CharOperation.toLowerCase(patternTypeArgument); boolean patternTypeArgHasAnyChars= CharOperation.contains(new char[] { '*', '?' }, patternTypeArgument); // Verify that names match... // ...special case for wildcard if (argTypeBinding instanceof CaptureBinding) { WildcardBinding capturedWildcard= ((CaptureBinding)argTypeBinding).wildcard; if (capturedWildcard != null) argTypeBinding= capturedWildcard; } if (argTypeBinding.isWildcard()) { WildcardBinding wildcardBinding= (WildcardBinding)argTypeBinding; switch (wildcardBinding.boundKind) { case Wildcard.EXTENDS: // Invalid if type argument is not exact if (patternTypeArgHasAnyChars) return impossible; continue nextTypeArgument; case Wildcard.UNBOUND: // there's no bound name to match => valid continue nextTypeArgument; } // Look if bound name match pattern type argument ReferenceBinding boundBinding= (ReferenceBinding)wildcardBinding.bound; if (CharOperation.match(patternTypeArgument, boundBinding.shortReadableName(), this.isCaseSensitive) || CharOperation.match(patternTypeArgument, boundBinding.readableName(), this.isCaseSensitive)) { // found name in hierarchy => match continue nextTypeArgument; } // If pattern is not exact then match fails if (patternTypeArgHasAnyChars) return impossible; // Look for bound name in type argument superclasses boundBinding= boundBinding.superclass(); while (boundBinding != null) { if (CharOperation.equals(patternTypeArgument, boundBinding.shortReadableName(), this.isCaseSensitive) || CharOperation.equals(patternTypeArgument, boundBinding.readableName(), this.isCaseSensitive)) { // found name in hierarchy => match continue nextTypeArgument; } else if (boundBinding.isLocalType() || boundBinding.isMemberType()) { // for local or member type, verify also source name (bug 81084) if (CharOperation.match(patternTypeArgument, boundBinding.sourceName(), this.isCaseSensitive)) continue nextTypeArgument; } boundBinding= boundBinding.superclass(); } return impossible; } // See if names match if (CharOperation.match(patternTypeArgument, argTypeBinding.shortReadableName(), this.isCaseSensitive) || CharOperation.match(patternTypeArgument, argTypeBinding.readableName(), this.isCaseSensitive)) { continue nextTypeArgument; } else if (argTypeBinding.isLocalType() || argTypeBinding.isMemberType()) { // for local or member type, verify also source name (bug 81084) if (CharOperation.match(patternTypeArgument, argTypeBinding.sourceName(), this.isCaseSensitive)) continue nextTypeArgument; } // If pattern is not exact then match fails if (patternTypeArgHasAnyChars) return impossible; // Scan hierarchy TypeBinding leafTypeBinding= argTypeBinding.leafComponentType(); if (leafTypeBinding.isBaseType()) return impossible; ReferenceBinding refBinding= ((ReferenceBinding)leafTypeBinding).superclass(); while (refBinding != null) { if (CharOperation.equals(patternTypeArgument, refBinding.shortReadableName(), this.isCaseSensitive) || CharOperation.equals(patternTypeArgument, refBinding.readableName(), this.isCaseSensitive)) { // found name in hierarchy => match continue nextTypeArgument; } else if (refBinding.isLocalType() || refBinding.isMemberType()) { // for local or member type, verify also source name (bug 81084) if (CharOperation.match(patternTypeArgument, refBinding.sourceName(), this.isCaseSensitive)) continue nextTypeArgument; } refBinding= refBinding.superclass(); } return impossible; } } // Recurse on enclosing type TypeBinding enclosingType= paramTypeBinding.enclosingType(); if (enclosingType != null && enclosingType.isParameterizedType() && depth < patternTypeArguments.length && qualificationPattern != null) { int lastDot= CharOperation.lastIndexOf('.', qualificationPattern); char[] enclosingQualificationPattern= lastDot == -1 ? null : CharOperation.subarray(qualificationPattern, 0, lastDot); char[] enclosingSimpleNamePattern= lastDot == -1 ? qualificationPattern : CharOperation.subarray(qualificationPattern, lastDot + 1, qualificationPattern.length); int enclosingLevel= resolveLevelForType(enclosingSimpleNamePattern, enclosingQualificationPattern, patternTypeArguments, depth + 1, enclosingType); if (enclosingLevel == impossible) return impossible; if (enclosingLevel == IMPOSSIBLE_MATCH) return IMPOSSIBLE_MATCH; } return level; } public String toString() { return "SearchPattern"; //$NON-NLS-1$ } }