package pl.ds.util;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Stream;
import static java.util.Objects.isNull;
public final class StringUtil {
private StringUtil() {
// no instances
}
/**
* <p>Checks if a {@code string} contains a {@code pattern}.</p>
*
* <p>A pattern may contain single or multiple wildcard characters {@code *}.
* Each occurrence of {@code *} in the {@code pattern} means that it can be a match for
* zero or more characters of the {@code string}.</p>
*
* <p>Asterisk (*) is considered as a regular character, if it is preceded by a backslash (\)
* in a pattern. Backslash (\) is considered as a regular character in all cases other
* than preceding the asterisk (*).</p>
*
* <p>Examples:</p>
* <pre>
* StringUtils.contains(null, *) = false
* StringUtils.contains(*, null) = false
* StringUtils.contains("", "") = true
* StringUtils.contains("abc", "") = true
* StringUtils.contains("abc", "a") = true
* StringUtils.contains("abc", "a*") = true
* StringUtils.contains("a*c", "a\*") = true
* StringUtils.contains("a*c", "abc") = false
* StringUtils.contains("abc", "A") = false
* StringUtils.contains("abc", "abcd") = false
* </pre>
*
* @param string string to check
* @param pattern pattern to search in a string
* @return true if the {@code string} contains a {@code pattern}, false otherwise.
*/
public static boolean contains(String string, String pattern) {
if (isNull(string) || isNull(pattern) || pattern.length() > string.length()) {
return false;
}
if (pattern.isEmpty()) {
return true;
}
StringCharacterIterator stringIterator = new StringCharacterIterator(string);
StringCharacterIterator patternIterator = new StringCharacterIterator(pattern);
Collection<PatternComparator> comparators = new ArrayList<>();
do {
char stringChar = stringIterator.current();
if (stringChar == patternIterator.first()) {
comparators.add(new PatternComparator(pattern));
}
if (comparators.stream().map(comparator -> comparator.pushCharacter(stringChar)).anyMatch(PatternComparator::isSuccess)) {
return true;
} else {
comparators.removeIf(PatternComparator::isFailed);
}
} while (stringIterator.next() != CharacterIterator.DONE);
return false;
}
private static class PatternComparator {
private Status status;
private enum Status {IN_PROGRESS, FAILED, SUCCESS;}
private final StringCharacterIterator patternIterator;
public PatternComparator(String pattern) {
patternIterator = new StringCharacterIterator(pattern);
status = Status.IN_PROGRESS;
}
private PatternComparator pushCharacter(char character) {
if (status != Status.IN_PROGRESS) {
return this;
}
if (patternIterator.current() == character) {
if (endOfPattern()) {
status = Status.SUCCESS;
} else {
patternIterator.next();
status = Status.IN_PROGRESS;
}
} else {
status = Status.FAILED;
}
return this;
}
public boolean isSuccess() {
return status == Status.SUCCESS;
}
public boolean isFailed() {
return status == Status.FAILED;
}
private boolean endOfPattern() {
return patternIterator.getIndex() + 1 == patternIterator.getEndIndex();
}
}
}