package querqy.rewrite.commonrules.model;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import querqy.ComparableCharSequence;
import querqy.CompoundCharSequence;
import querqy.LowerCaseCharSequence;
import querqy.SimpleComparableCharSequence;
public class Term implements ComparableCharSequence {
public final String FIELD_CHAR = ":";
protected final char[] value;
protected final int start;
protected final int length;
protected final List<String> fieldNames;
protected final LinkedList<PlaceHolder> placeHolders;
public Term(char[] value, int start, int length, List<String> fieldNames) {
if (start + length > value.length) {
throw new ArrayIndexOutOfBoundsException("start + length > value.length");
}
this.value = value;
this.start = start;
this.length = length;
this.fieldNames = (fieldNames != null && fieldNames.isEmpty()) ? null : fieldNames;
this.placeHolders = parsePlaceHolders();
}
private enum ParseState {None, Started, InRef}
public int getMaxPlaceHolderRef() {
return placeHolders == null ? -1 : placeHolders.getFirst().ref;
}
public boolean hasPlaceHolder() {
return placeHolders != null && !placeHolders.isEmpty();
}
public ComparableCharSequence fillPlaceholders(final TermMatches termMatches) {
if (placeHolders == null || placeHolders.isEmpty()) {
return this;
}
final List<ComparableCharSequence> parts = new LinkedList<>();
int pos = 0;
for (final PlaceHolder placeHolder : placeHolders) {
if (placeHolder.start > pos) {
parts.add(subSequence(pos, placeHolder.start));
}
parts.add(termMatches.getReplacement(placeHolder.ref));
pos = placeHolder.start + placeHolder.length;
}
if (pos < length) {
parts.add(subSequence(pos, start + length));
}
return new CompoundCharSequence(parts);
}
protected LinkedList<PlaceHolder> parsePlaceHolders() {
final LinkedList<PlaceHolder> placeHolders = new LinkedList<>();
ParseState state = ParseState.None;
int begin = -1;
int end = -1;
for (int idx = start, last = start + length; idx < last; idx++) {
final char ch = value[idx];
switch (state) {
case None:
if (ch == '$') {
state = ParseState.Started;
}
break;
case Started:
if (Character.isDigit(ch)) {
begin = idx - 1;
end = idx;
state = ParseState.InRef;
} else if (ch != '$') {
state = ParseState.None;
begin = -1;
}
break;
case InRef:
if (Character.isDigit(ch)) {
end = idx;
} else {
final int ref = Integer.parseInt(new String(value, begin + 1, end - begin));
final PlaceHolder placeHolder = new PlaceHolder(begin, end - begin + 1, ref);
if (placeHolders.isEmpty() || placeHolders.getFirst().ref < ref) {
placeHolders.addFirst(placeHolder);
} else {
placeHolders.add(placeHolder);
}
state = ch == '$' ? ParseState.Started : ParseState.None;
}
break;
}
}
if (state == ParseState.InRef) {
final int ref = Integer.parseInt(new String(value, begin + 1, end - begin));
final PlaceHolder placeHolder = new PlaceHolder(begin, end - begin + 1, ref);
if (placeHolders.isEmpty() || placeHolders.getFirst().ref < ref) {
placeHolders.addFirst(placeHolder);
} else {
placeHolders.add(placeHolder);
}
}
return placeHolders.isEmpty() ? null : placeHolders;
}
@Override
public char charAt(final int idx) {
if (idx >= length) {
throw new ArrayIndexOutOfBoundsException(idx);
}
return value[start + idx];
}
@Override
public int compareTo(final CharSequence other) {
for (int i = 0, pos = start, len = Math.min(length, other.length()); i < len; i++) {
char ch1 = value[pos++];
char ch2 = other.charAt(i);
if (ch1 != ch2) {
return ch1 - ch2;
}
}
return length - other.length();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((fieldNames == null) ? 0 : fieldNames.hashCode());
result = prime * result + length;
for (int i = 0; i < length; i++) {
result = prime * result + value[start + i];
}
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!Term.class.isAssignableFrom(obj.getClass()))
return false;
Term other = (Term) obj;
if (fieldNames == null) {
if (other.fieldNames != null)
return false;
} else if (!fieldNames.equals(other.fieldNames))
return false;
if (length != other.length)
return false;
for (int i = 0; i < length; i++) {
if (value[start + i] != other.value[other.start + i]) {
return false;
}
}
return true;
}
@Override
public String toString() {
return "Term [fieldNames="
+ fieldNames + ", value=" + new String(value, start, length) + "]";
}
public Term findFirstMatch(final Collection<? extends Term> haystack) {
for (final Term h : haystack) {
if (compareTo(h) == 0) {
if (fieldNames == h.fieldNames) {
return h;
} else {
if (h.fieldNames != null && fieldNames != null) {
for (final String name : fieldNames) {
if (h.fieldNames.contains(name)) {
return h;
}
}
}
}
}
}
return null;
}
@Override
public int length() {
return length;
}
@Override
public ComparableCharSequence subSequence(final int start, final int end) {
if (end > length) {
throw new ArrayIndexOutOfBoundsException(end);
}
if (start < 0) {
throw new ArrayIndexOutOfBoundsException(start);
}
return new SimpleComparableCharSequence(value, this.start + start, end - start);
}
public List<ComparableCharSequence> getCharSequences(final boolean lowerCaseValue) {
final SimpleComparableCharSequence seq = new SimpleComparableCharSequence(value, start, length);
final ComparableCharSequence valueSequence = lowerCaseValue ? new LowerCaseCharSequence(seq) : seq;
final List<ComparableCharSequence> seqs = new LinkedList<>();
if (fieldNames == null) {
seqs.add(valueSequence);
} else {
for (final String name : fieldNames) {
seqs.add(new CompoundCharSequence(FIELD_CHAR, name, valueSequence));
}
}
return seqs;
}
public List<String> getFieldNames() {
return fieldNames;
}
public LinkedList<PlaceHolder> getPlaceHolders() {
return placeHolders;
}
}