Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contains methods to ByteMatcher #497

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
192 changes: 192 additions & 0 deletions src/main/java/emissary/util/search/ByteMatcher.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have been more clear about the organizing the overloads. From the Google Java Style Guide,
Ordering of class contents:

3.4.2.1 Overloads: never split

Methods of a class that share the same name appear in a single contiguous group with no other members in between. The same applies to multiple constructors (which always have the same name). This rule applies even when modifiers such as static or private differ between the methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e.,

    public boolean contains(String pattern) {
        return contains(pattern.getBytes());
    }

    public boolean contains(byte[] pattern) {
        return indexOf(pattern) >= 0;
    }

    public boolean contains(String pattern, int beginIndex, int endIndex) {
        return contains(pattern.getBytes(), beginIndex, endIndex);
    }

    public boolean contains(byte[] pattern, int beginIndex, int endIndex) {
        return indexOf(pattern, beginIndex, endIndex) >= 0;
    }

    public boolean containsAll(int beginIndex, int endIndex, String... patterns) {
        return Arrays.stream(patterns).allMatch(pattern -> contains(pattern, beginIndex, endIndex));
    }

    public boolean containsAll(int beginIndex, int endIndex, String... patterns) {
        return Arrays.stream(patterns).allMatch(pattern -> contains(pattern, beginIndex, endIndex));
    }

    public boolean containsAny(String... patterns) {
        return Arrays.stream(patterns).anyMatch(this::contains);
    }

    public boolean containsAny(int beginIndex, int endIndex, String... patterns) {
        return Arrays.stream(patterns).anyMatch(pattern -> contains(pattern, beginIndex, endIndex));
    }

  ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other references, such as Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition (an oldie but goodie, available in Safari Books Online), also suggest organizing overloads from simplest to the most complex, and I tend to agree with that guideline.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the Javadocs once we're good with the ordering.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overload ordering looks good now

@@ -1,6 +1,7 @@
package emissary.util.search;

import java.nio.charset.Charset;
import java.util.Arrays;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -66,6 +67,197 @@ public int length() {
return mydata.length;
}

/**
* Checks if data contains the string pattern
*
* @param pattern string to find
*
* @return true if data contains the search pattern, false if not or null string input
*/
public boolean contains(String pattern) {
return contains(pattern.getBytes());
}

/**
* Checks if data contains the byte array pattern
*
* @param pattern bytes to find
*
* @return true if data contains the search pattern, false if not or null byte array input
*/
public boolean contains(byte[] pattern) {
return indexOf(pattern) >= 0;
}

/**
* Checks if data contains the string pattern
*
* @param pattern string to find
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
*
* @return true if data contains the search pattern, false if not or null string input
*/
public boolean contains(String pattern, int beginIndex, int endIndex) {
return contains(pattern.getBytes(), beginIndex, endIndex);
}

/**
* Checks if data contains the byte array pattern
*
* @param pattern bytes to find
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
*
* @return true if data contains the search pattern, false if not or null byte array input
*/
public boolean contains(byte[] pattern, int beginIndex, int endIndex) {
return indexOf(pattern, beginIndex, endIndex) >= 0;
}

/**
* Checks if data contains any of the string patterns
*
* @param patterns strings to find
*
* @return true if data contains the search pattern, false if not or null string input
*/
public boolean containsAny(String... patterns) {
return Arrays.stream(patterns).anyMatch(this::contains);
}

/**
* Checks if data contains any of the string patterns
*
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
*
* @param patterns true if data contains any search pattern, false if not or null string input
*/
public boolean containsAny(int beginIndex, int endIndex, String... patterns) {
return Arrays.stream(patterns).anyMatch(pattern -> contains(pattern, beginIndex, endIndex));
}

/**
* Checks if data contains all string patterns
*
* @param patterns strings to find
*
* @return true if data contains all search patterns, false if not or null string input
*/
public boolean containsAll(String... patterns) {
return Arrays.stream(patterns).allMatch(this::contains);
}

/**
* Checks if data contains all string patterns
*
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
* @param patterns strings to find
*
* @return true if data contains all search patterns, false if not or null string input
*/
public boolean containsAll(int beginIndex, int endIndex, String... patterns) {
return Arrays.stream(patterns).allMatch(pattern -> contains(pattern, beginIndex, endIndex));
}

/**
* Checks if data contains the string pattern ignoring upper/lower case
*
* @param pattern string to find
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
*
* @return true if data contains search pattern, false if not or null string input
*/
public boolean containsIgnoreCase(String pattern, int beginIndex, int endIndex) {
return containsIgnoreCase(pattern.getBytes(), beginIndex, endIndex);
}

/**
* Checks if data contains the byte array pattern ignoring upper/lower case
*
* @param pattern bytes to find
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
*
* @return true if data contains all search patterns, false if not or null byte array input
*/
public boolean containsIgnoreCase(byte[] pattern, int beginIndex, int endIndex) {
return indexIgnoreCase(pattern, beginIndex, endIndex) >= 0;
}

/**
* Checks if data contains the string pattern ignoring upper/lower case
*
* @param pattern string to find
*
* @return true if data contains search pattern, false if not or null string input
*/
public boolean containsIgnoreCase(String pattern) {
return containsIgnoreCase(pattern.getBytes());
}

/**
* Checks if data contains the byte array pattern ignoring upper/lower case
*
* @param pattern bytes to find
*
* @return true if data contains search pattern, false if not or null byte array input
*/
public boolean containsIgnoreCase(byte[] pattern) {
return indexIgnoreCase(pattern) >= 0;
}

/**
* Checks if data contains any of the string patterns ignoring upper/lower case
*
* @param patterns strings to find
*
* @return true if data contains any search pattern, false if not or null string input
*/
public boolean containsAnyIgnoreCase(String... patterns) {
return Arrays.stream(patterns).anyMatch(this::containsIgnoreCase);
}

/**
* Checks if data contains any of the string patterns ignoring upper/lower case
*
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
* @param patterns strings to find
*
* @return true if data contains any search pattern, false if not or null string input
*/
public boolean containsAnyIgnoreCase(int beginIndex, int endIndex, String... patterns) {
return Arrays.stream(patterns).anyMatch(pattern -> containsIgnoreCase(pattern, beginIndex, endIndex));
}

/**
* Checks if data contains all string patterns ignoring upper/lower case
*
* @param patterns strings to find
*
* @return true if data contains search pattern, false if not or null string input
*/
public boolean containsAllIgnoreCase(String... patterns) {
return Arrays.stream(patterns).allMatch(this::containsIgnoreCase);
}

/**
* Checks if data contains all string patterns ignoring upper/lower case
*
* @param beginIndex start index
* @param endIndex the index to stop searching at, exclusive
* @param patterns strings to find
*
* @return true if data contains all search patterns, false if not or null string input
*/
public boolean containsAllIgnoreCase(int beginIndex, int endIndex, String... patterns) {
return Arrays.stream(patterns).allMatch(pattern -> containsIgnoreCase(pattern, beginIndex, endIndex));
}

/**
* This method finds a pattern in the text and returns the offset
*
Expand Down
62 changes: 62 additions & 0 deletions src/test/java/emissary/util/search/ByteMatcherTest.java
Expand Up @@ -283,4 +283,66 @@ void testNullDataBytesIgnoreCase() {
assertEquals(-1, this.b.indexIgnoreCase("Fred".getBytes()), "Match pos not found");
}

@Test
void testContainsAny() {
assertTrue(this.b.containsAny("fox", "test"));
assertFalse(this.b.containsAny("fOX", "TEST"));
assertFalse(this.b.containsAny("no", "tokens", "found"));

assertTrue(this.b.containsAny(16, b.length(), "fox", "test"));
assertFalse(this.b.containsAny(16, 18, "fox", "test"));
}

@Test
void testContainsAnyIgnoreCase() {
assertTrue(this.b.containsAnyIgnoreCase("fox", "test"));
assertTrue(this.b.containsAnyIgnoreCase("fOX", "TEST"));
assertFalse(this.b.containsAnyIgnoreCase("no", "tokens", "found"));

assertTrue(this.b.containsAnyIgnoreCase(16, 19, "fOX", "TEST"));
assertFalse(this.b.containsAnyIgnoreCase(17, b.length(), "fOX", "TEST"));
}


@Test
void testContainsAll() {
assertTrue(this.b.containsAll("fox", "jumped", "dog", "lazy"));
assertFalse(this.b.containsAll("fox", "jumped", "dog", "LAZY"));
assertFalse(this.b.containsAll("no", "tokens", "found"));

assertTrue(this.b.containsAll(0, b.length(), "fox", "jumped", "dog", "lazy"));
assertFalse(this.b.containsAll(0, b.length() - 1, "fox", "jumped", "dog", "lazy"));
}

@Test
void testContainsAllIgnoreCase() {
assertTrue(this.b.containsAllIgnoreCase("fox", "jumped", "dog", "lazy"));
assertTrue(this.b.containsAllIgnoreCase("fox", "jumped", "dog", "LAZY"));
assertFalse(this.b.containsAllIgnoreCase("no", "tokens", "found"));

assertTrue(this.b.containsAllIgnoreCase(16, b.length(), "fox", "jumped", "dog", "LAZY"));
assertFalse(this.b.containsAllIgnoreCase(17, b.length(), "fox", "jumped", "dog", "LAZY"));
}

@Test
void testContains() {
assertTrue(this.b.contains("fox"));
}

@Test
void testContainsIgnoreCase() {
assertTrue(this.b.containsIgnoreCase("FoX"));
}

@Test
void testContainsWithIndices() {
assertTrue(this.b.contains("quick brown fox", 4, 19));
assertFalse(this.b.contains("quick brown fox", 4, 18));
}

@Test
void testContainsWithIndicesIgnoreCase() {
assertTrue(this.b.containsIgnoreCase("Quick BROWN fOX", 4, data.length()));
assertFalse(this.b.containsIgnoreCase("Quick BROWN fOX", 5, data.length()));
}
}