package org.openntf.formula.function;
public class TextMatchLotus {
/*----------------------------------------------------------------------------*/
private static final char BACK_SLASH = '\\';
private static final char FLAG_SPECIAL = 'S';
private static final char ARB_STRING = '*';
private static final char ARB_CHAR = '?';
private static final char ARB_REPEAT = '+';
private static final char BEGIN_ALTER = '{';
private static final char BEGIN_NOT_ALTER = '!';
private static final char END_ALTER = '}';
private static final char FLAG_ALTER_AREA = '-';
private static final char FLAG_NONSPEC = 'N';
/*----------------------------------------------------------------------------*/
private char[] iNormalPattern;
private int iNPLength;
private boolean iCaseMatches; // if true, case-insensitive match test
/*----------------------------------------------------------------------------*/
public TextMatchLotus(final String pattern) {
init(pattern, true);
compileMatches(pattern);
}
/*----------------------------------------------------------------------------*/
public TextMatchLotus(final String pattern, final char escape) {
init(pattern, false);
compileLike(pattern, escape);
}
/*----------------------------------------------------------------------------*/
private void init(final String pattern, final boolean caseMatches) {
iNormalPattern = new char[pattern.length() * 2 + 1];
iNPLength = 0;
iCaseMatches = caseMatches;
}
/*----------------------------------------------------------------------------*/
private void compileMatches(final String pattern) {
int lh = pattern.length();
for (int i = 0; i < lh; i++) {
char c = Character.toLowerCase(pattern.charAt(i));
if (c == BACK_SLASH) {
if (++i == lh)
throwPatternException(pattern);
iNormalPattern[iNPLength++] = FLAG_NONSPEC;
iNormalPattern[iNPLength++] = pattern.charAt(i);
continue;
}
if (c == ARB_REPEAT) {
if (i + 1 == lh)
throwPatternException(pattern);
c = pattern.charAt(i + 1);
if (c == ARB_STRING || c == ARB_CHAR) {
i++;
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_STRING;
continue;
}
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_REPEAT;
continue;
}
if (c == ARB_STRING) {
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_STRING;
continue;
}
if (c == ARB_CHAR) {
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_CHAR;
continue;
}
if (c != BEGIN_ALTER) {
iNormalPattern[iNPLength++] = FLAG_NONSPEC;
iNormalPattern[iNPLength++] = c;
continue;
}
boolean notAlter = false;
if (i + 2 >= lh)
throwPatternException(pattern);
if (pattern.charAt(++i) == BEGIN_NOT_ALTER) {
notAlter = true;
i++;
}
if (pattern.charAt(i) == END_ALTER)
throwPatternException(pattern);
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = notAlter ? BEGIN_NOT_ALTER : BEGIN_ALTER;
for (;;) {
c = pattern.charAt(i);
if (c == END_ALTER) {
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = END_ALTER;
break;
}
if (++i == lh)
throwPatternException(pattern);
if (pattern.charAt(i) != '-') {
iNormalPattern[iNPLength++] = FLAG_NONSPEC;
iNormalPattern[iNPLength++] = c;
continue;
}
iNormalPattern[iNPLength++] = FLAG_ALTER_AREA;
iNormalPattern[iNPLength++] = c;
if (++i == lh)
throwPatternException(pattern);
c = pattern.charAt(i);
if (c == END_ALTER) {
iNormalPattern[iNPLength++] = (char) 65500;
continue;
}
iNormalPattern[iNPLength++] = c;
i++;
}
}
}
/*----------------------------------------------------------------------------*/
private static final char LIKE_ARB_STRING = '%';
private static final char LIKE_ARB_CHAR = '_';
/*----------------------------------------------------------------------------*/
private void compileLike(final String pattern, final char escape) {
int lh = pattern.length();
for (int i = 0; i < lh; i++) {
char c = pattern.charAt(i);
if (escape != 0 && c == escape) {
if (++i == lh)
throwPatternException(pattern);
iNormalPattern[iNPLength++] = FLAG_NONSPEC;
iNormalPattern[iNPLength++] = pattern.charAt(i);
continue;
}
if (c == LIKE_ARB_STRING) {
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_STRING;
continue;
}
if (c == LIKE_ARB_CHAR) {
iNormalPattern[iNPLength++] = FLAG_SPECIAL;
iNormalPattern[iNPLength++] = ARB_CHAR;
continue;
}
iNormalPattern[iNPLength++] = FLAG_NONSPEC;
iNormalPattern[iNPLength++] = c;
}
}
/*----------------------------------------------------------------------------*/
private void throwPatternException(final String pattern) {
throw new IllegalArgumentException("Invalid text pattern: " + pattern);
}
/*----------------------------------------------------------------------------*/
public boolean matches(final String what) {
iTestStr = what;
iTestStrLh = iTestStr.length();
return matches(0, 0);
}
/*----------------------------------------------------------------------------*/
private String iTestStr;
private int iTestStrLh;
private int iNextNPPos;
/*----------------------------------------------------------------------------*/
private boolean matches(int testStrPos, int npPos) {
while (npPos < iNPLength) {
if (iNormalPattern[npPos] == FLAG_SPECIAL && iNormalPattern[npPos + 1] == ARB_STRING) {
npPos += 2;
if (npPos == iNPLength)
return true;
for (; testStrPos < iTestStrLh; testStrPos++)
if (matches(testStrPos, npPos))
return true;
return false;
}
if (iNormalPattern[npPos] != FLAG_SPECIAL || iNormalPattern[npPos + 1] != ARB_REPEAT) {
if (testStrPos >= iTestStrLh)
return false;
if (!matchesChar(iTestStr.charAt(testStrPos++), npPos))
return false;
npPos = iNextNPPos;
continue;
}
npPos += 2;
matchesChar('1', npPos);
int newNPPos = iNextNPPos;
if (matches(testStrPos, iNextNPPos))
return true;
while (testStrPos < iTestStrLh) {
if (!matchesChar(iTestStr.charAt(testStrPos), npPos))
break;
if (matches(++testStrPos, iNextNPPos))
return true;
}
npPos = newNPPos;
}
return testStrPos == iTestStrLh;
}
/*----------------------------------------------------------------------------*/
private boolean matchesChar(final char which, int npPos) {
if (iNormalPattern[npPos] == FLAG_NONSPEC) {
iNextNPPos = npPos + 2;
if (iCaseMatches)
return Character.toLowerCase(which) == iNormalPattern[npPos + 1];
else
return (which == iNormalPattern[npPos + 1]);
}
if (iNormalPattern[npPos + 1] == ARB_CHAR) {
iNextNPPos = npPos + 2;
return true;
}
boolean notAlter = (iNormalPattern[npPos + 1] == BEGIN_NOT_ALTER);
boolean treffer = false;
for (npPos += 2;;) {
if (iNormalPattern[npPos] == FLAG_NONSPEC) {
if (!treffer)
treffer = (which == iNormalPattern[npPos + 1]);
npPos += 2;
continue;
}
if (iNormalPattern[npPos] == FLAG_SPECIAL) { // must be END_ALTER
npPos += 2;
break;
}
if (!treffer)
treffer = (which >= iNormalPattern[npPos + 1] && which <= iNormalPattern[npPos + 2]);
npPos += 3;
}
iNextNPPos = npPos;
return notAlter ? !treffer : treffer;
}
/*----------------------------------------------------------------------------*/
}