### Eclipse Workspace Patch 1.0 #P org.eclipse.jdt.core.tests.compiler Index: src/org/eclipse/jdt/core/tests/compiler/regression/UtilTest.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/UtilTest.java,v retrieving revision 1.33 diff -u -r1.33 UtilTest.java --- src/org/eclipse/jdt/core/tests/compiler/regression/UtilTest.java 18 Apr 2006 16:27:43 -0000 1.33 +++ src/org/eclipse/jdt/core/tests/compiler/regression/UtilTest.java 17 Aug 2007 17:03:21 -0000 @@ -13,6 +13,7 @@ import java.io.File; //import org.apache.tools.ant.types.selectors.SelectorUtils; +import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.compiler.CharOperation; //import junit.framework.AssertionFailedError; @@ -36,7 +37,14 @@ * If result is invalid then store warning in buffer and display it. */ void assertCamelCase(String pattern, String name, boolean match) { - boolean camelCase = CharOperation.camelCaseMatch(pattern==null?null:pattern.toCharArray(), name==null?null:name.toCharArray()); + +} +/** + * Assert that a pattern and a name matches or not. + * If result is invalid then store warning in buffer and display it. + */ +void assertCamelCase(String pattern, String name, boolean exactEnd, boolean match) { + boolean camelCase = CharOperation.camelCaseMatch(pattern==null?null:pattern.toCharArray(), name==null?null:name.toCharArray(), exactEnd); if (match != camelCase) { StringBuffer line = new StringBuffer("'"); line.append(name); @@ -608,6 +616,128 @@ // Verify that there were no unexpected results assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); } + +// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=109695 +public void test70() throws CoreException { + assertCamelCase("IDE3", "IDocumentExtension", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDE3", "IDocumentExtension2", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDE3", "IDocumentExtension3", true /*exact end match*/, true /* should match */); + assertCamelCase("IDE3", "IDocumentExtension135", true /*exact end match*/, true /* should match */); + assertCamelCase("IDE3", "IDocumentExtension315", true /*exact end match*/, true /* should match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension2", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension4", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension3", true /*exact end match*/, true /* should match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension5", true /*exact end match*/, false /* should not match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension54321", true /*exact end match*/, true /* should match */); + assertCamelCase("IDPE3", "IDocumentProviderExtension12345", true /*exact end match*/, true /* should match */); + assertCamelCase("IPL3", "IPerspectiveListener", true /*exact end match*/, false /* should not match */); + assertCamelCase("IPL3", "IPerspectiveListener2", true /*exact end match*/, false /* should not match */); + assertCamelCase("IPL3", "IPerspectiveListener3", true /*exact end match*/, true /* should match */); + assertCamelCase("IPS2", "IPropertySource", true /*exact end match*/, false /* should not match */); + assertCamelCase("IPS2", "IPropertySource2", true /*exact end match*/, true /* should match */); + assertCamelCase("IWWPD2", "IWorkbenchWindowPulldownDelegate", true /*exact end match*/, false /* should not match */); + assertCamelCase("IWWPD2", "IWorkbenchWindowPulldownDelegate2", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF16DSS", "UTF16DocumentScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF16DSS", "UTF1DocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF16DSS", "UTF6DocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF16DSS", "UTFDocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF1DSS", "UTF16DocumentScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF1DSS", "UTF1DocScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF1DSS", "UTF6DocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF1DSS", "UTFDocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF6DSS", "UTF16DocumentScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF6DSS", "UTF1DocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTF6DSS", "UTF6DocScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTF6DSS", "UTFDocScannerSupport", true /*exact end match*/, false /* should not match */); + assertCamelCase("UTFDSS", "UTF16DocumentScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTFDSS", "UTF1DocScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTFDSS", "UTF6DocScannerSupport", true /*exact end match*/, true /* should match */); + assertCamelCase("UTFDSS", "UTFDocScannerSupport", true /*exact end match*/, true /* should match */); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=124624 +public void test71() { + assertCamelCase("HM", "HashMap", true /*exact end match*/, true /*should match*/); + assertCamelCase("HM", "HtmlMapper", true /*exact end match*/, true /*should match*/); + assertCamelCase("HM", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaM", "HashMap", true /*exact end match*/, true /* should match */); + assertCamelCase("HaM", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaM", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashM", "HashMap", true /*exact end match*/, true /* should match */); + assertCamelCase("HashM", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashM", "HashMapEntry", true /*exact end match*/, false /* should not match */); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +public void test71b() { // previous test cases but with 3.3 behavior + assertCamelCase("HM", "HashMap", true /*should match*/); + assertCamelCase("HM", "HtmlMapper", true /*should match*/); + assertCamelCase("HM", "HashMapEntry", true /*should match*/); + assertCamelCase("HaM", "HashMap", true /* should match */); + assertCamelCase("HaM", "HtmlMapper", true /*should match*/); + assertCamelCase("HaM", "HashMapEntry", true /*should match*/); + assertCamelCase("HashM", "HashMap", true /* should match */); + assertCamelCase("HashM", "HtmlMapper", true /*should match*/); + assertCamelCase("HashM", "HashMapEntry", true /*should match*/); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=124624 +public void test72() { + assertCamelCase("HMa", "HashMap", true /*exact end match*/, false /* should not match */); + assertCamelCase("HMa", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HMa", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaMa", "HashMap", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaMa", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaMa", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashMa", "HashMap", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashMa", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashMa", "HashMapEntry", true /*exact end match*/, false /* should not match */); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +public void test72b() { // previous test cases but with 3.3 behavior + assertCamelCase("HMa", "HashMap", true /*should match*/); + assertCamelCase("HMa", "HtmlMapper", true /*should match*/); + assertCamelCase("HMa", "HashMapEntry", true /*should match*/); + assertCamelCase("HaMa", "HashMap", true /* should match */); + assertCamelCase("HaMa", "HtmlMapper", true /*should match*/); + assertCamelCase("HaMa", "HashMapEntry", true /*should match*/); + assertCamelCase("HashMa", "HashMap", true /* should match */); + assertCamelCase("HashMa", "HtmlMapper", true /*should match*/); + assertCamelCase("HashMa", "HashMapEntry", true /*should match*/); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=124624 +public void test73() { + assertCamelCase("HMap", "HashMap", true /*exact end match*/, true /*should match*/); + assertCamelCase("HMap", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HMap", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaMap", "HashMap", true /*exact end match*/, true /* should match */); + assertCamelCase("HaMap", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HaMap", "HashMapEntry", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashMap", "HashMap", true /*exact end match*/, true /* should match */); + assertCamelCase("HashMap", "HtmlMapper", true /*exact end match*/, false /* should not match */); + assertCamelCase("HashMap", "HashMapEntry", true /*exact end match*/, false /* should not match */); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} +public void test73b() { // previous test cases but with 3.3 behavior + assertCamelCase("HMap", "HashMap", true /*should match*/); + assertCamelCase("HMap", "HtmlMapper", true /*should match*/); + assertCamelCase("HMap", "HashMapEntry", true /*should match*/); + assertCamelCase("HaMap", "HashMap", true /* should match */); + assertCamelCase("HaMap", "HtmlMapper", true /*should match*/); + assertCamelCase("HaMap", "HashMapEntry", true /*should match*/); + assertCamelCase("HashMap", "HashMap", true /* should match */); + assertCamelCase("HashMap", "HtmlMapper", true /*should match*/); + assertCamelCase("HashMap", "HashMapEntry", true /*should match*/); + // Verify that there were no unexpected results + assertTrue(this.camelCaseErrors.toString(), this.camelCaseErrors.length()==0); +} public static Class testClass() { return UtilTest.class; } #P org.eclipse.jdt.core Index: search/org/eclipse/jdt/internal/core/search/matching/JavaSearchPattern.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchPattern.java,v retrieving revision 1.23 diff -u -r1.23 JavaSearchPattern.java --- search/org/eclipse/jdt/internal/core/search/matching/JavaSearchPattern.java 7 Sep 2006 12:59:26 -0000 1.23 +++ search/org/eclipse/jdt/internal/core/search/matching/JavaSearchPattern.java 17 Aug 2007 17:03:25 -0000 @@ -75,7 +75,7 @@ // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=81377 int rule = getMatchRule(); this.isCaseSensitive = (rule & R_CASE_SENSITIVE) != 0; - this.isCamelCase = (rule & R_CAMELCASE_MATCH) != 0; + this.isCamelCase = (rule & (R_CAMELCASE_MATCH | R_CAMELCASE_STRICT_MATCH)) != 0; this.matchCompatibility = rule & MATCH_COMPATIBILITY_MASK; this.matchMode = rule & MATCH_MODE_MASK; } Index: search/org/eclipse/jdt/internal/core/search/matching/PackageReferenceLocator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PackageReferenceLocator.java,v retrieving revision 1.38 diff -u -r1.38 PackageReferenceLocator.java --- search/org/eclipse/jdt/internal/core/search/matching/PackageReferenceLocator.java 27 Apr 2007 15:51:38 -0000 1.38 +++ search/org/eclipse/jdt/internal/core/search/matching/PackageReferenceLocator.java 17 Aug 2007 17:03:25 -0000 @@ -96,11 +96,12 @@ if (this.pattern.pkgName == null) return ACCURATE_MATCH; char[] packageName = null; - if (this.isCamelCase) { + if (this.isCamelCase || this.isStrictCamelCase) { packageName = CharOperation.concatWith(tokens, '.'); - if (CharOperation.camelCaseMatch(this.pattern.pkgName, packageName)) { + if (CharOperation.camelCaseMatch(this.pattern.pkgName, packageName, this.isStrictCamelCase)) { return POSSIBLE_MATCH; } + if (this.isStrictCamelCase) return IMPOSSIBLE_MATCH; } switch (this.matchMode) { case SearchPattern.R_EXACT_MATCH: Index: search/org/eclipse/jdt/internal/core/search/matching/PatternLocator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PatternLocator.java,v retrieving revision 1.66 diff -u -r1.66 PatternLocator.java --- search/org/eclipse/jdt/internal/core/search/matching/PatternLocator.java 4 Jul 2007 13:59:58 -0000 1.66 +++ search/org/eclipse/jdt/internal/core/search/matching/PatternLocator.java 17 Aug 2007 17:03:26 -0000 @@ -24,6 +24,7 @@ protected int matchMode; protected boolean isCaseSensitive; protected boolean isCamelCase; +protected boolean isStrictCamelCase; protected boolean isEquivalentMatch; protected boolean isErasureMatch; protected boolean mustResolve; @@ -118,7 +119,8 @@ public PatternLocator(SearchPattern pattern) { int matchRule = pattern.getMatchRule(); this.isCaseSensitive = (matchRule & SearchPattern.R_CASE_SENSITIVE) != 0; - this.isCamelCase = (matchRule & SearchPattern.R_CAMELCASE_MATCH) != 0; + this.isStrictCamelCase = (matchRule & SearchPattern.R_CAMELCASE_STRICT_MATCH) != 0; + this.isCamelCase = this.isStrictCamelCase || (matchRule & SearchPattern.R_CAMELCASE_MATCH) != 0; this.isErasureMatch = (matchRule & SearchPattern.R_ERASURE_MATCH) != 0; this.isEquivalentMatch = (matchRule & SearchPattern.R_EQUIVALENT_MATCH) != 0; this.matchMode = matchRule & JavaSearchPattern.MATCH_MODE_MASK; @@ -282,18 +284,21 @@ boolean matchFirstChar = !this.isCaseSensitive || pattern[0] == name[0]; boolean sameLength = pattern.length == name.length; boolean canBePrefix = name.length >= pattern.length; - if (this.isCamelCase && matchFirstChar && CharOperation.camelCaseMatch(pattern, name)) { - return POSSIBLE_MATCH; + if (this.isCamelCase || this.isStrictCamelCase) { + if (matchFirstChar && CharOperation.camelCaseMatch(pattern, name, this.isStrictCamelCase)) { + return POSSIBLE_MATCH; + } + if (this.isStrictCamelCase) return IMPOSSIBLE_MATCH; } switch (this.matchMode) { case SearchPattern.R_EXACT_MATCH: - if (!this.isCamelCase) { +// if (!this.isCamelCase) { if (sameLength && matchFirstChar && CharOperation.equals(pattern, name, this.isCaseSensitive)) { return POSSIBLE_MATCH | EXACT_FLAVOR; } break; - } - // fall through next case to match as prefix if camel case failed +// } +// // fall through next case to match as prefix if camel case failed case SearchPattern.R_PREFIX_MATCH: if (canBePrefix && matchFirstChar && CharOperation.prefixEquals(pattern, name, this.isCaseSensitive)) { return POSSIBLE_MATCH; @@ -716,9 +721,9 @@ return ACCURATE_MATCH; } } - if (this.isCamelCase) { + if (this.isCamelCase || this.isStrictCamelCase) { if (!this.isCaseSensitive || (qualifiedPattern.length>0 && sourceName.length>0 && qualifiedPattern[0] == sourceName[0])) { - if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName)) { + if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName, this.isStrictCamelCase)) { return ACCURATE_MATCH; } } Index: search/org/eclipse/jdt/internal/core/search/matching/TypeReferenceLocator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/TypeReferenceLocator.java,v retrieving revision 1.56 diff -u -r1.56 TypeReferenceLocator.java --- search/org/eclipse/jdt/internal/core/search/matching/TypeReferenceLocator.java 27 Apr 2007 15:51:38 -0000 1.56 +++ search/org/eclipse/jdt/internal/core/search/matching/TypeReferenceLocator.java 17 Aug 2007 17:03:26 -0000 @@ -112,8 +112,11 @@ return IMPOSSIBLE_MATCH; } boolean matchFirstChar = !this.isCaseSensitive || (qualifiedPattern[0] == qualifiedTypeName[0]); - if (this.isCamelCase && matchFirstChar && CharOperation.camelCaseMatch(qualifiedPattern, qualifiedTypeName)) { - return POSSIBLE_MATCH; + if (this.isCamelCase || this.isStrictCamelCase) { + if (matchFirstChar && CharOperation.camelCaseMatch(qualifiedPattern, qualifiedTypeName, this.isStrictCamelCase)) { + return POSSIBLE_MATCH; + } + if (this.isStrictCamelCase) return IMPOSSIBLE_MATCH; } switch (this.matchMode) { case SearchPattern.R_EXACT_MATCH: Index: buildnotes_jdt-core.html =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/buildnotes_jdt-core.html,v retrieving revision 1.6030 diff -u -r1.6030 buildnotes_jdt-core.html --- buildnotes_jdt-core.html 16 Aug 2007 13:13:13 -0000 1.6030 +++ buildnotes_jdt-core.html 17 Aug 2007 17:03:23 -0000 @@ -50,10 +50,211 @@
  • New fix for bug 186781 required the index version to be incremented. Indexes will be automatically regenerated upon subsequent search queries (accounting for indexing notification in search progress dialogs).
  • +
  • Add strict mode for Camel Case matching (see bug 124624).
    +Currently Camel Case rules are permissive as it accept prefix match for the last part. +For instance, 'HM' pattern will match 'HashMap' but also 'HashMapEntry'. +Or 'HaMap' pattern will match 'HashMap' but also 'HatMapper'.
    +Some users now needs a more strict mode for Camel Case rule which forces a exact match for the last part of the pattern.

    +To implement this functionality, following API methods/constants have been added in JDT/Core: +
    +
  • Problem Reports Fixed

    -109695 +124624 +[search] Camelcase matching routines should support end character +
    109695 [search] Numbers should be treated as upper-case letters in CamelCase matching
    186781 StackOverflowError while computing launch button tooltip Index: compiler/org/eclipse/jdt/core/compiler/CharOperation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java,v retrieving revision 1.78 diff -u -r1.78 CharOperation.java --- compiler/org/eclipse/jdt/core/compiler/CharOperation.java 16 Aug 2007 13:13:13 -0000 1.78 +++ compiler/org/eclipse/jdt/core/compiler/CharOperation.java 17 Aug 2007 17:03:23 -0000 @@ -58,7 +58,7 @@ * * * - * @param array the array that is concanated with the suffix character + * @param array the array that is concatenated with the suffix character * @param suffix the suffix character * @return the new array */ @@ -72,8 +72,8 @@ } /** - * Append the given subarray to the target array starting at the given index in the target array. - * The start of the subarray is inclusive, the end is exclusive. + * Append the given sub-array to the target array starting at the given index in the target array. + * The start of the sub-array is inclusive, the end is exclusive. * Answers a new target array if it needs to grow, otherwise answers the same target array. *
    * For example:
    @@ -180,18 +180,25 @@ /** * Answers true if the pattern matches the given name using CamelCase rules, or false otherwise. * char[] CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. - *
    + *

    * 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. - *
    + *

    * 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'. - *

    + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * Camel Case rules are applied permissively which means that the name may have more parts than the pattern or its + * last part may be a prefix of the pattern's last part (see {@link #camelCaseMatch(char[], char[], boolean)} + * for more explanation on Camel Case strict/permissive mode. + *

    * Examples: *

      *
    1. @@ -224,6 +231,16 @@
        *    result => false
        * 
      *
    2. + *
    3. + *    pattern = { 'I', 'P', 'L', '3' }
      + *    patternStart = 0
      + *    patternEnd = 4
      + *    name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => true
      + * 
      + *
    4. *
    * * @param pattern the given pattern @@ -237,26 +254,122 @@ if (name == null) return false; // null name cannot match - return camelCaseMatch(pattern, 0, pattern.length, name, 0, name.length); + return camelCaseMatch(pattern, name, false/*prefix match for last part*/); } /** - * Answers true if a sub-pattern matches the subpart of the given name using CamelCase rules, or false otherwise. + * Answers true if the pattern matches the given name using CamelCase rules, or false otherwise. + * char[] CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * CamelCase rules can be applied strictly or permissively. When the strict mode is specified, the given pattern and name + * must have exactly the same number of parts, and the last parts must be identical if they contain lowercase characters. + * For instance, 'HMap<' and 'HaMap' patterns will match 'HashMap' but neither 'HashMapEntry' nor 'HatMapper'. + * Note that when the last part does not contain lowercase characters, then the name may end with lowercase characters. + * So, 'HM' pattern will match both 'HashMap' and 'HatMapper' but it will not match 'HashMapEntry'. + * Reversely, if the permissive mode is specified, the names do not need to have the same number of parts and the same last parts. + * Then, both 'HM' and 'HMap' will match 'HashMap', 'HatMapper' and'HashMapEntry'. + *

    + *

    + * Examples:
    + * 
      + *
    1. pattern = { 'N', 'P', 'E' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    2. + *
    3. pattern = { 'N', 'P', 'E' } + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    4. + *
    5. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    6. + *
    7. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => false
    8. + *
    9. pattern = { 'n', p', 'e' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => false
    10. + *
    11. pattern = { 'I', 'P', 'L', '3' } + * name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' } + * result => true
    12. + *
    13. pattern = { 'H', M' } + * name = { 'H', 'a', 's', 'h', 'M', 'a', 'p', 'E', 'n', 't', 'r', 'y' } + * result => (exactMode == false)
    14. + *
    15. pattern = { 'H', M', 'a', 'p' } + * name = { 'H', 'a', 't', 'M', 'a', 'p', 'p', 'e', 'r' } + * result => (exactMode == false)
    16. + *
    + *
    + * + * @param pattern the given pattern + * @param name the given name + * @param exactMode flag telling whether the name must have same + * number of parts and same last part (if it consists of multiple letters) + * as the pattern or not. + * + * @return true if the pattern matches the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(char[] pattern, char[] name, boolean exactMode) { + 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, exactMode); +} + +/** + * Answers true if a sub-pattern matches the sub-part of the given name using CamelCase rules, or false otherwise. * char[] 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. - *
    + * The sub-pattern is defined by the patternStart and patternEnd positions. + *

    * 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. - *
    + *

    * 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'. - *

    + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * Camel Case rules are applied permissively which means that the name may have more parts than the pattern and its + * last part may be a prefix of the pattern's last part (see {@link #camelCaseMatch(char[], int, int, char[], int, int, boolean)} + * for more explanation on Camel Case strict/permissive mode. + *

    * Examples: *

      *
    1. @@ -309,6 +422,16 @@
        *    result => false
        * 
      *
    2. + *
    3. + *    pattern = { 'I', 'P', 'L', '3' }
      + *    patternStart = 0
      + *    patternEnd = 4
      + *    name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => true
      + * 
      + *
    4. *
    * * @param pattern the given pattern @@ -317,10 +440,133 @@ * @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 + * @return true if a sub-pattern matches the sub-part of the given name, false otherwise * @since 3.2 + * @deprecated Use {@link #camelCaseMatch(char[], int, int, char[], int, int, boolean)} instead */ public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd) { + return camelCaseMatch(pattern, patternStart, patternEnd, name, nameStart, nameEnd, false/*prefix match for last hunk*/); +} + +/** + * Answers true if a sub-pattern matches the sub-part of the given name using CamelCase rules, or false otherwise. + * char[] 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 sub-pattern is defined by the patternStart and patternEnd positions. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * CamelCase rules can be applied strictly or permissively. When the strict mode is specified, the given pattern and name + * must have exactly the same number of parts, and the last parts must be identical if they contain lowercase characters. + * For instance, 'HMap<' and 'HaMap' patterns will match 'HashMap' but neither 'HashMapEntry' nor 'HatMapper'. + * Note that when the last part does not contain lowercase characters, then the name may end with lowercase characters. + * So, 'HM' pattern will match both 'HashMap' and 'HatMapper' but it will not match 'HashMapEntry'. + * Reversely, if the permissive mode is specified, names do not need to have the same number of parts and the same last parts. + * Then, both 'HM' and 'HMap' will match 'HashMap', 'HatMapper' and'HashMapEntry'. + *

    + *

    + * Examples:
    + * 
      + *
    1. pattern = { 'N', 'P', 'E' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => true
    2. + *
    3. pattern = { 'N', 'P', 'E' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 21 + * result => true
    4. + *
    5. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * patternStart = 0 + * patternEnd = 6 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => true
    6. + *
    7. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * patternStart = 0 + * patternEnd = 6 + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 21 + * result => false
    8. + *
    9. pattern = { 'n', p', 'e' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => false
    10. + *
    11. pattern = { 'I', 'P', 'L', '3' } + * patternStart = 0 + * patternEnd = 4 + * name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' } + * nameStart = 0 + * nameEnd = 21 + * result => true
    12. + *
    13. pattern = { 'H', M' } + * patternStart = 0 + * patternEnd = 2 + * name = { 'H', 'a', 's', 'h', 'M', 'a', 'p', 'E', 'n', 't', 'r', 'y' } + * nameStart = 0 + * nameEnd = 12 + * result => (exactMode == false)
    14. + *
    15. pattern = { 'H', M', 'a', 'p' } + * patternStart = 0 + * patternEnd = 4 + * name = { 'H', 'a', 't', 'M', 'a', 'p', 'p', 'e', 'r' } + * nameStart = 0 + * nameEnd = 9 + * result => (exactMode == false)
    16. + *
    + *
    + * + * @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 + * @param exactMode flag telling whether the name must have same + * number of parts and same last part (if it consists of multiple letters) + * as the pattern or not. + * + * @return true if a sub-pattern matches the sub-part of the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, boolean exactMode) { if (name == null) return false; // null name cannot match if (pattern == null) @@ -346,9 +592,38 @@ iPattern++; iName++; - if (iPattern == patternEnd) { - // We have exhausted pattern, so it's a match - return true; + if (iPattern == patternEnd) { // we have exhausted pattern... + // it's a match if not exact mode or name is also exhausted + if (!exactMode || iName == nameEnd) return true; + + // it's not a match if last pattern character is a lowercase + if ((patternChar = pattern[iPattern-1]) < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_DIGIT)) == 0) { + return false; + } + } + else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar) && !Character.isDigit(patternChar)) { + return false; + } + + // it's a match only if name has no more uppercase characters (exact mode) + while (true) { + if (iName == nameEnd) { + // we have exhausted name, so it's a match + return true; + } + nameChar = name[iName]; + if (nameChar < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & ScannerHelper.C_UPPER_LETTER) != 0) { + // nameChar is uppercase, so it's not a match + return false; + } + } + else if (!Character.isJavaIdentifierPart(nameChar) || Character.isUpperCase(nameChar)) { + return false; + } + iName++; + } } if (iName == nameEnd){ Index: search/org/eclipse/jdt/internal/core/search/BasicSearchEngine.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/BasicSearchEngine.java,v retrieving revision 1.47 diff -u -r1.47 BasicSearchEngine.java --- search/org/eclipse/jdt/internal/core/search/BasicSearchEngine.java 6 Jul 2007 12:25:50 -0000 1.47 +++ search/org/eclipse/jdt/internal/core/search/BasicSearchEngine.java 17 Aug 2007 17:03:25 -0000 @@ -454,14 +454,18 @@ return false; if (patternTypeName != null) { + boolean isStrictCamelCase = (matchRule & SearchPattern.R_CAMELCASE_STRICT_MATCH) != 0; boolean isCamelCase = (matchRule & SearchPattern.R_CAMELCASE_MATCH) != 0; int matchMode = matchRule & JavaSearchPattern.MATCH_MODE_MASK; if (!isCaseSensitive && !isCamelCase) { patternTypeName = CharOperation.toLowerCase(patternTypeName); } boolean matchFirstChar = !isCaseSensitive || patternTypeName[0] == typeName[0]; - if (isCamelCase && matchFirstChar && CharOperation.camelCaseMatch(patternTypeName, typeName)) { - return true; + if (isCamelCase || isStrictCamelCase) { + if (matchFirstChar && CharOperation.camelCaseMatch(patternTypeName, typeName, isStrictCamelCase)) { + return true; + } + if (isStrictCamelCase) return false; } switch(matchMode) { case SearchPattern.R_EXACT_MATCH : Index: search/org/eclipse/jdt/core/search/SearchPattern.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/SearchPattern.java,v retrieving revision 1.69 diff -u -r1.69 SearchPattern.java --- search/org/eclipse/jdt/core/search/SearchPattern.java 17 Aug 2007 06:14:50 -0000 1.69 +++ search/org/eclipse/jdt/core/search/SearchPattern.java 17 Aug 2007 17:03:25 -0000 @@ -141,7 +141,7 @@ /** * Match rule: The search pattern contains a Camel Case expression. - *
    + *

    * Examples: *

    - * @see CharOperation#camelCaseMatch(char[], char[]) for a detailed explanation - * of Camel Case matching. - *
    - * Can be combined to {@link #R_PREFIX_MATCH} match rule. For example, - * when prefix match rule is combined with Camel Case match rule, - * "nPE" pattern will match nPException. - *
    + * + * Camel Case match rule already behaves both as prefix match and case sensitive + * match rules (eg. HashMap pattern using using Camel Case rule + * will match both 'HashMap' and HashMapEntry'). + * However, this rule still can be combined to prefix match rule to also behaves + * as prefix match and case insensitive match rules. + * For instance, when {@link #R_CAMELCASE_MATCH} | {@link #R_PREFIX_MATCH} + * flags are combined, "NULL" pattern will still match + * NullPointerException. + *

    * 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 @@ -164,11 +167,55 @@ * For example, with "NPE" string pattern, search will only use * Camel Case match rule, but with N*P*E* string pattern, it will * use only Pattern match rule. + *

    + * @see CharOperation#camelCaseMatch(char[], char[]) for a detailed explanation + * of Camel Case matching. * * @since 3.2 */ public static final int R_CAMELCASE_MATCH = 0x0080; + /** + * Match rule: The search pattern contains a Camel Case expression with + * a strict number of parts and preventing automatic prefix matching for the last part + * (if it consists of multiple letters). + *
    + * Examples: + *

    + * + * Camel Case match rule already behaves both as prefix match and case sensitive + * match rules (eg. HashMap pattern using using Camel Case rule + * will match both 'HashMap' and HashMapEntry'). + * However, this rule still can be combined to prefix match rule to also behaves + * as prefix match and case insensitive match rules. + * For instance, when {@link #R_CAMELCASE_MATCH} | {@link #R_PREFIX_MATCH} + * flags are combined, "NULL" pattern will still match + * NullPointerException. + *

    + * 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 "NPE" string pattern, search will only use + * Camel Case match rule, but with N*P*E* string pattern, it will + * use only Pattern match rule. + *

    + * @see CharOperation#camelCaseMatch(char[], char[], boolean) for a detailed explanation + * of Camel Case matching. + *

    + * @since 3.4 + */ + public static final int R_CAMELCASE_STRICT_MATCH = 0x0100; + private static final int MODE_MASK = R_EXACT_MATCH | R_PREFIX_MATCH | R_PATTERN_MATCH | R_REGEXP_MATCH; private int matchRule; @@ -198,9 +245,71 @@ /** * 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. + * char[] CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * Camel Case rules are applied permissively which means that the name may have more parts than the pattern or its + * last part may be a prefix of the pattern's last part (see {@link #camelCaseMatch(String, String, boolean)} + * for more explanation on Camel Case strict/permissive mode. + *

    + * Examples: + *

      + *
    1. + *    pattern = { 'N', 'P', 'E' }
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    result => true
      + * 
      + *
    2. + *
    3. + *    pattern = { 'N', 'P', 'E' }
      + *    name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    result => true
      + * 
      + *
    4. + *
    5. + *    pattern = { 'N', 'u', 'P', 'o', 'E', 'x' }
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    result => true
      + * 
      + *
    6. + *
    7. + *    pattern = { 'N', 'u', 'P', 'o', 'E', 'x' }
      + *    name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    result => false
      + * 
      + *
    8. + *
    9. + *    pattern = { 'n', p', 'e' }
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    result => false
      + * 
      + *
    10. + *
    11. + *    pattern = { 'I', 'P', 'L', '3' }
      + *    patternStart = 0
      + *    patternEnd = 4
      + *    name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => true
      + * 
      + *
    12. + *
    * - * @see CharOperation#camelCaseMatch(char[], char[]) + * @see #camelCaseMatch(String, int, int, String, int, int, boolean) for algorithm implementation * * @param pattern the given pattern * @param name the given name @@ -213,17 +322,187 @@ if (name == null) return false; // null name cannot match - return CharOperation.camelCaseMatch(pattern.toCharArray(), 0, pattern.length(), name.toCharArray(), 0, name.length()); + return camelCaseMatch(pattern, 0, pattern.length(), name, 0, name.length(), false/*prefix match for last part*/); +} + +/** + * Answers true if the pattern matches the given name using CamelCase rules, or false otherwise. + * char[] CamelCase matching does NOT accept explicit wild-cards '*' and '?' and is inherently case sensitive. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * CamelCase rules can be applied strictly or permissively. When the strict mode is specified, the given pattern and name + * must have exactly the same number of parts, and the last parts must be identical if they contain lowercase characters. + * For instance, 'HMap<' and 'HaMap' patterns will match 'HashMap' but neither 'HashMapEntry' nor 'HatMapper'. + * Note that when the last part does not contain lowercase characters, then the name may end with lowercase characters. + * So, 'HM' pattern will match both 'HashMap' and 'HatMapper' but it will not match 'HashMapEntry'. + * Reversely, if the permissive mode is specified, the names do not need to have the same number of parts and the same last parts. + * Then, both 'HM' and 'HMap' will match 'HashMap', 'HatMapper' and'HashMapEntry'. + *

    + *

    + * Examples:
    + * 
      + *
    1. pattern = { 'N', 'P', 'E' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    2. + *
    3. pattern = { 'N', 'P', 'E' } + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    4. + *
    5. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => true
    6. + *
    7. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => false
    8. + *
    9. pattern = { 'n', p', 'e' } + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * result => false
    10. + *
    11. pattern = { 'I', 'P', 'L', '3' } + * name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' } + * result => true
    12. + *
    13. pattern = { 'H', M' } + * name = { 'H', 'a', 's', 'h', 'M', 'a', 'p', 'E', 'n', 't', 'r', 'y' } + * result => (exactMode == false)
    14. + *
    15. pattern = { 'H', M', 'a', 'p' } + * name = { 'H', 'a', 't', 'M', 'a', 'p', 'p', 'e', 'r' } + * result => (exactMode == false)
    16. + *
    + *
    + * + * @see #camelCaseMatch(String, int, int, String, int, int, boolean) for algorithm implementation + * + * @param pattern the given pattern + * @param name the given name + * @param exactMode flag telling whether the name must have same + * number of parts and same last part (if it consists of multiple letters) + * as the pattern or not. + * + * @return true if the pattern matches the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(String pattern, String name, boolean exactMode) { + 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(), exactMode); } /** - * Answers true if a sub-pattern matches the subpart of the given name using CamelCase rules, or false otherwise. + * Answers true if a sub-pattern matches the sub-part of the given name using CamelCase rules, or false otherwise. * char[] 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. - * - * @see CharOperation#camelCaseMatch(char[], int, int, char[], int, int) for specification - * and implementation of this algorithm. + * The sub-pattern is defined by the patternStart and patternEnd positions. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * Camel Case rules are applied permissively which means that the name may have more parts than the pattern and its + * last part may be a prefix of the pattern's last part (see {@link #camelCaseMatch(String, int, int, String, int, int, boolean)} + * for more explanation on Camel Case strict/permissive mode. + *

    + * Examples: + *

      + *
    1. + *    pattern = { 'N', 'P', 'E' }
      + *    patternStart = 0
      + *    patternEnd = 3
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    nameStart = 0
      + *    nameEnd = 20
      + *    result => true
      + * 
      + *
    2. + *
    3. + *    pattern = { 'N', 'P', 'E' }
      + *    patternStart = 0
      + *    patternEnd = 3
      + *    name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => true
      + * 
      + *
    4. + *
    5. + *    pattern = { 'N', 'u', 'P', 'o', 'E', 'x' }
      + *    patternStart = 0
      + *    patternEnd = 6
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    nameStart = 0
      + *    nameEnd = 20
      + *    result => true
      + * 
      + *
    6. + *
    7. + *    pattern = { 'N', 'u', 'P', 'o', 'E', 'x' }
      + *    patternStart = 0
      + *    patternEnd = 6
      + *    name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => false
      + * 
      + *
    8. + *
    9. + *    pattern = { 'n', p', 'e' }
      + *    patternStart = 0
      + *    patternEnd = 3
      + *    name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' }
      + *    nameStart = 0
      + *    nameEnd = 20
      + *    result => false
      + * 
      + *
    10. + *
    11. + *    pattern = { 'I', 'P', 'L', '3' }
      + *    patternStart = 0
      + *    patternEnd = 4
      + *    name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' }
      + *    nameStart = 0
      + *    nameEnd = 21
      + *    result => true
      + * 
      + *
    12. + *
    * * @param pattern the given pattern * @param patternStart the start index of the pattern, inclusive @@ -231,11 +510,260 @@ * @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 + * @return true if a sub-pattern matches the sub-part of the given name, false otherwise * @since 3.2 + * @deprecated Use {@link #camelCaseMatch(String, int, int, String, int, int, boolean)} instead */ public static final boolean camelCaseMatch(String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd) { - return CharOperation.camelCaseMatch(pattern.toCharArray(), patternStart, patternEnd, name.toCharArray(), nameStart, nameEnd); + return camelCaseMatch(pattern, patternStart, patternEnd, name, nameStart, nameEnd, false/*prefix match for last part*/); +} + +/** + * Answers true if a sub-pattern matches the sub-part of the given name using CamelCase rules, or false otherwise. + * char[] 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 sub-pattern is defined by the patternStart and patternEnd positions. + *

    + * 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. + *

    + * 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'. + *

    + * Digit characters are treated in a special way. They can be used in the pattern but are not always considered as leading + * character. For instance, both 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

    + * CamelCase rules can be applied strictly or permissively. When the strict mode is specified, the given pattern and name + * must have exactly the same number of parts, and the last parts must be identical if they contain lowercase characters. + * For instance, 'HMap<' and 'HaMap' patterns will match 'HashMap' but neither 'HashMapEntry' nor 'HatMapper'. + * Note that when the last part does not contain lowercase characters, then the name may end with lowercase characters. + * So, 'HM' pattern will match both 'HashMap' and 'HatMapper' but it will not match 'HashMapEntry'. + * Reversely, if the permissive mode is specified, names do not need to have the same number of parts and the same last parts. + * Then, both 'HM' and 'HMap' will match 'HashMap', 'HatMapper' and'HashMapEntry'. + *

    + *

    + * Examples:
    + * 
      + *
    1. pattern = { 'N', 'P', 'E' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => true
    2. + *
    3. pattern = { 'N', 'P', 'E' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 21 + * result => true
    4. + *
    5. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * patternStart = 0 + * patternEnd = 6 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => true
    6. + *
    7. pattern = { 'N', 'u', 'P', 'o', 'E', 'x' } + * patternStart = 0 + * patternEnd = 6 + * name = { 'N', 'o', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 21 + * result => false
    8. + *
    9. pattern = { 'n', p', 'e' } + * patternStart = 0 + * patternEnd = 3 + * name = { 'N', 'u','l', 'l', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'E', 'x', 'c', 'e', 'p', 't', 'i', 'o', 'n' } + * nameStart = 0 + * nameEnd = 20 + * result => false
    10. + *
    11. pattern = { 'I', 'P', 'L', '3' } + * patternStart = 0 + * patternEnd = 4 + * name = { 'I', 'P', 'e', 'r', 's', 'p', 'e', 'c', 't', 'i', 'v', 'e', 'L', 'i', 's', 't', 'e', 'n', 'e', 'r', '3' } + * nameStart = 0 + * nameEnd = 21 + * result => true
    12. + *
    13. pattern = { 'H', M' } + * patternStart = 0 + * patternEnd = 2 + * name = { 'H', 'a', 's', 'h', 'M', 'a', 'p', 'E', 'n', 't', 'r', 'y' } + * nameStart = 0 + * nameEnd = 12 + * result => (exactMode == false)
    14. + *
    15. pattern = { 'H', M', 'a', 'p' } + * patternStart = 0 + * patternEnd = 4 + * name = { 'H', 'a', 't', 'M', 'a', 'p', 'p', 'e', 'r' } + * nameStart = 0 + * nameEnd = 9 + * result => (exactMode == false)
    16. + *
    + *
    + * + * @see CharOperation#camelCaseMatch(char[], int, int, char[], int, int, boolean) + * from which algorithm implementation has been entirely copied. + * Array lengths have been replaced with call to {@link String#length()} and + * array direct access have been replaced with call to {@link String#charAt(int)}. + * WARNING: Do not change one of these method without changing the other the same way + * otherwise behavior differences may appear while using them... + * + * @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 + * @param exactMode flag telling whether the name must have same + * number of parts and same last part (if it consists of multiple letters) + * as the pattern or not. + * + * @return true if a sub-pattern matches the sub-part of the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd, boolean exactMode) { + 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... + // it's a match if not exact mode or name is also exhausted + if (!exactMode || iName == nameEnd) return true; + + // it's not a match if last pattern character is a lowercase + if ((patternChar = pattern.charAt(iPattern-1)) < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_DIGIT)) == 0) { + return false; + } + } + else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar) && !Character.isDigit(patternChar)) { + return false; + } + + // it's a match only if name has no more uppercase characters (exact mode) + while (true) { + if (iName == nameEnd) { + // we have exhausted name, so it's a match + return true; + } + nameChar = name.charAt(iName); + if (nameChar < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & ScannerHelper.C_UPPER_LETTER) != 0) { + // nameChar is uppercase, so it's not a match + return false; + } + } + else if (!Character.isJavaIdentifierPart(nameChar) || Character.isUpperCase(nameChar)) { + return false; + } + iName++; + } + } + + 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 | ScannerHelper.C_DIGIT)) == 0) { + return false; + } + } + else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar) && !Character.isDigit(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) { + int charNature = ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar]; + if ((charNature & (ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_SPECIAL)) != 0) { + // nameChar is lowercase + iName++; + } else if ((charNature & ScannerHelper.C_DIGIT) != 0) { + // nameChar is digit => break if the digit is current pattern character otherwise consume it + if (patternChar == nameChar) break; + 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; + } + } + // Same tests for non-obvious characters + else if (Character.isJavaIdentifierPart(nameChar) && !Character.isUpperCase(nameChar)) { + iName++; + } else if (Character.isDigit(nameChar)) { + if (patternChar == nameChar) break; + iName++; + } else if (patternChar != nameChar) { + return false; + } else { + break; + } + } + // At this point, either name has been exhausted, or it is at an uppercase letter. + // Since pattern is also at an uppercase letter + } } /** @@ -1601,6 +2129,7 @@ if (pattern == null) return true; // null is as if it was "*" if (name != null) { boolean isCaseSensitive = (this.matchRule & R_CASE_SENSITIVE) != 0; + boolean isStrictCamelCase = (this.matchRule & R_CAMELCASE_STRICT_MATCH) != 0; boolean isCamelCase = (this.matchRule & R_CAMELCASE_MATCH) != 0; int matchMode = this.matchRule & MODE_MASK; boolean emptyPattern = pattern.length == 0; @@ -1608,12 +2137,14 @@ 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; + if (isCamelCase || isStrictCamelCase) { + if (matchFirstChar && CharOperation.camelCaseMatch(pattern, name, isStrictCamelCase)) { + return true; + } + if (isStrictCamelCase) return false; } switch (matchMode) { case R_EXACT_MATCH : - case R_FULL_MATCH : if (!isCamelCase) { if (sameLength && matchFirstChar) { return CharOperation.equals(pattern, name, isCaseSensitive); @@ -1643,7 +2174,7 @@ /** * Validate compatibility between given string pattern and match rule. *
    - * Optimized (ie. returned match rule is modified) combinations are: + * Returned match rule is modified, when following combinations are observed in the given parameters: *