/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-04, Wolfgang M. Meier (wolfgang@exist-db.org)
* and others (see http://exist-db.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
*/
package org.exist.dom;
import org.exist.numbering.NodeId;
import org.exist.xquery.Constants;
/**
* Used to track fulltext matches throughout the query.
*
* {@link org.exist.storage.TextSearchEngine} will add a
* match object to every {@link org.exist.dom.NodeProxy}
* that triggered a fulltext match for every term matched. The
* Match object contains the nodeId of the text node that triggered the
* match, the string value of the matching term and a frequency count,
* indicating the frequency of the matching term string within the corresponding
* single text node.
*
* All path operations copy existing match objects, i.e. the match objects
* are copied to the selected descendant or child nodes. This means that
* every NodeProxy being the direct or indirect result of a fulltext
* selection will have one or more match objects, indicating which text nodes
* among its descendant nodes contained a fulltext match.
*
* @author wolf
*/
public abstract class Match implements Comparable {
public final static class Offset implements Comparable {
private int offset;
private int length;
public Offset(int offset, int length) {
this.offset = offset;
this.length = length;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getLength() {
return length;
}
public int compareTo(Object other) {
final int otherOffset = ((Offset) other).offset;
return offset == otherOffset ? Constants.EQUAL : (offset < otherOffset ? Constants.INFERIOR : Constants.SUPERIOR);
}
}
private int context;
protected NodeId nodeId;
private String matchTerm;
private int[] offsets;
private int[] lengths;
private int currentOffset = 0;
protected Match nextMatch = null;
protected Match(int contextId, NodeId nodeId, String matchTerm) {
this(contextId, nodeId, matchTerm, 1);
}
protected Match(int contextId, NodeId nodeId, String matchTerm, int frequency) {
this.context = contextId;
this.nodeId = nodeId;
this.matchTerm = matchTerm;
this.offsets = new int[frequency];
this.lengths = new int[frequency];
}
protected Match(Match match) {
this.context = match.context;
this.nodeId = match.nodeId;
this.matchTerm = match.matchTerm;
this.offsets = match.offsets;
this.lengths = match.lengths;
this.currentOffset = match.currentOffset;
}
public NodeId getNodeId() {
return nodeId;
}
public int getFrequency() {
return currentOffset;
}
public String getMatchTerm() {
return matchTerm;
}
public int getContextId() {
return context;
}
public abstract Match createInstance(int contextId, NodeId nodeId, String matchTerm);
public abstract Match newCopy();
public abstract String getIndexId();
public void addOffset(int offset, int length) {
if (currentOffset == offsets.length) {
int noffsets[] = new int[currentOffset + 1];
System.arraycopy(offsets, 0, noffsets, 0, currentOffset);
offsets = noffsets;
int nlengths[] = new int[currentOffset + 1];
System.arraycopy(lengths, 0, nlengths, 0, currentOffset);
lengths = nlengths;
}
offsets[currentOffset] = offset;
lengths[currentOffset++] = length;
}
public Offset getOffset(int pos) {
return new Offset(offsets[pos], lengths[pos]);
}
public Match isAfter(Match other) {
Match m = null;
for (int i = 0; i < currentOffset; i++) {
for (int j = 0; j < other.currentOffset; j++) {
if (other.offsets[j] > offsets[i] && other.offsets[j] <= offsets[i] + lengths[i]) {
if (m == null)
m = createInstance(context, nodeId, matchTerm + other.matchTerm);
m.addOffset(offsets[i], lengths[i] + other.lengths[j]);
}
}
}
return m;
}
/**
* Return true if there's a match starting at the given
* character position.
*
* @param pos the position
* @return true if a match starts at the given position
*/
public boolean hasMatchAt(int pos) {
for (int i = 0; i < currentOffset; i++) {
if (offsets[i] == pos)
return true;
}
return false;
}
/**
* Returns true if the given position is within a match.
*
* @param pos the position
* @return true if the given position is within a match
*/
public boolean hasMatchAround(int pos) {
for (int i = 0; i < currentOffset; i++) {
if (offsets[i] + lengths[i] >= pos)
return true;
}
return false;
}
public void mergeOffsets(Match other) {
for (int i = 0; i < other.currentOffset; i++) {
if (!hasMatchAt(other.offsets[i]))
addOffset(other.offsets[i], other.lengths[i]);
}
}
public Match getNextMatch() {
return nextMatch;
}
public boolean equals(Object other) {
if(!(other instanceof Match))
return false;
Match om = (Match) other;
return om.matchTerm != null && om.matchTerm.equals(matchTerm) &&
om.nodeId.equals(nodeId);
}
public boolean matchEquals(Match other) {
if (this == other)
return true;
return
(nodeId == other.nodeId || nodeId.equals(other.nodeId)) &&
matchTerm.equals(other.matchTerm);
}
/**
* Used to sort matches. Terms are compared by their string
* length to have the longest string first.
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Object o) {
Match other = (Match)o;
return matchTerm.compareTo(other.matchTerm);
}
public String toString() {
StringBuilder buf = new StringBuilder(matchTerm);
for (int i = 0; i < currentOffset; i++) {
buf.append(" [");
buf.append(offsets[i]).append(':').append(lengths[i]);
buf.append("]");
}
if (nextMatch != null)
buf.append(' ').append(nextMatch.toString());
return buf.toString();
}
}