org.jdom2.xpath.util.AbstractXPathCompiled Maven / Gradle / Ivy
Show all versions of jdom Show documentation
/*--
Copyright (C) 2012 Jason Hunter & Brett McLaughlin.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows
these conditions in the documentation and/or other materials
provided with the distribution.
3. The name "JDOM" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact .
4. Products derived from this software may not be called "JDOM", nor
may "JDOM" appear in their name, without prior written permission
from the JDOM Project Management .
In addition, we request (but do not require) that you include in the
end-user documentation provided with the redistribution and/or in the
software itself an acknowledgement equivalent to the following:
"This product includes software developed by the
JDOM Project (http://www.jdom.org/)."
Alternatively, the acknowledgment may be graphical using the logos
available at http://www.jdom.org/images/logos.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
This software consists of voluntary contributions made by many
individuals on behalf of the JDOM Project and was originally
created by Jason Hunter and
Brett McLaughlin . For more information
on the JDOM Project, please see .
*/
package org.jdom2.xpath.util;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom2.Namespace;
import org.jdom2.Verifier;
import org.jdom2.filter.Filter;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathDiagnostic;
/**
* A mostly-implemented XPathExpression that only needs two methods to be
* implemented in order to satisfy the complete API. Subclasses of this
* MUST correctly override the clone() method which in turn
* should call super.clone();
*
* @param
* The generic type of the returned values.
* @author Rolf Lear
*/
public abstract class AbstractXPathCompiled implements XPathExpression {
private static final class NamespaceComparator implements Comparator {
@Override
public int compare(Namespace ns1, Namespace ns2) {
return ns1.getPrefix().compareTo(ns2.getPrefix());
}
}
private static final NamespaceComparator NSSORT = new NamespaceComparator();
private final Map xnamespaces = new HashMap();
// Not final to support cloning.
private Map> xvariables = new HashMap>();
private final String xquery;
private final Filter xfilter;
/**
* Construct an XPathExpression.
*
* @see XPathExpression for conditions which throw
* {@link NullPointerException} or {@link IllegalArgumentException}.
* @param query
* The XPath query
* @param filter
* The coercion filter.
* @param variables
* A map of variables.
* @param namespaces
* The namespaces referenced from the query.
*/
public AbstractXPathCompiled(final String query, final Filter filter,
final Map variables, final Namespace[] namespaces) {
if (query == null) {
throw new NullPointerException("Null query");
}
if (filter == null) {
throw new NullPointerException("Null filter");
}
xnamespaces.put(Namespace.NO_NAMESPACE.getPrefix(),
Namespace.NO_NAMESPACE);
if (namespaces != null) {
for (Namespace ns : namespaces) {
if (ns == null) {
throw new NullPointerException("Null namespace");
}
final Namespace oldns = xnamespaces.put(ns.getPrefix(), ns);
if (oldns != null && oldns != ns) {
throw new IllegalArgumentException(
"A Namespace with the prefix '" + ns.getPrefix()
+ "' has already been declared.");
}
}
}
if (variables != null) {
for (Map.Entry me : variables.entrySet()) {
final String qname = me.getKey();
if (qname == null) {
throw new NullPointerException("Variable with a null name");
}
final int p = qname.indexOf(':');
final String pfx = p < 0 ? "" : qname.substring(0, p);
final String lname = p < 0 ? qname : qname.substring(p + 1);
final String vpfxmsg = Verifier.checkNamespacePrefix(pfx);
if (vpfxmsg != null) {
throw new IllegalArgumentException("Prefix '" + pfx
+ "' for variable " + qname + " is illegal: "
+ vpfxmsg);
}
final String vnamemsg = Verifier.checkXMLName(lname);
if (vnamemsg != null) {
throw new IllegalArgumentException("Variable name '"
+ lname + "' for variable " + qname
+ " is illegal: " + vnamemsg);
}
final Namespace ns = xnamespaces.get(pfx);
if (ns == null) {
throw new IllegalArgumentException("Prefix '" + pfx
+ "' for variable " + qname
+ " has not been assigned a Namespace.");
}
Map vmap = xvariables.get(ns.getURI());
if (vmap == null) {
vmap = new HashMap();
xvariables.put(ns.getURI(), vmap);
}
if (vmap.put(lname, me.getValue()) != null) {
throw new IllegalArgumentException("Variable with name "
+ me.getKey() + "' has already been defined.");
}
}
}
xquery = query;
xfilter = filter;
}
/**
* Subclasses of this AbstractXPathCompile class must call super.clone() in
* their clone methods!
*
* This would be a sample clone method from a subclass:
*
*
*
* public XPathExpression<T> clone() {
* {@literal @}SuppressWarnings("unchecked")
* final MyXPathCompiled<T> ret = (MyXPathCompiled<T>)super.clone();
* // change any fields that need to be cloned.
* ....
* return ret;
* }
*
*
* Here's the documentation from {@link XPathExpression#clone()}
*
* {@inheritDoc}
*/
@Override
public XPathExpression clone() {
AbstractXPathCompiled ret = null;
try {
@SuppressWarnings("unchecked")
final AbstractXPathCompiled c = (AbstractXPathCompiled) super
.clone();
ret = c;
} catch (CloneNotSupportedException cnse) {
throw new IllegalStateException(
"Should never be getting a CloneNotSupportedException!",
cnse);
}
Map> vmt = new HashMap>();
for (Map.Entry> me : xvariables.entrySet()) {
final Map cmap = new HashMap();
for (Map.Entry ne : me.getValue().entrySet()) {
cmap.put(ne.getKey(), ne.getValue());
}
vmt.put(me.getKey(), cmap);
}
ret.xvariables = vmt;
return ret;
}
@Override
public final String getExpression() {
return xquery;
}
@Override
public final Namespace getNamespace(final String prefix) {
final Namespace ns = xnamespaces.get(prefix);
if (ns == null) {
throw new IllegalArgumentException("Namespace with prefix '"
+ prefix + "' has not been declared.");
}
return ns;
}
@Override
public Namespace[] getNamespaces() {
final Namespace[] nsa = xnamespaces.values().toArray(
new Namespace[xnamespaces.size()]);
Arrays.sort(nsa, NSSORT);
return nsa;
}
@Override
public final Object getVariable(final String name, Namespace uri) {
final Map vmap =
xvariables.get(uri == null ? "" : uri.getURI());
if (vmap == null) {
throw new IllegalArgumentException("Variable with name '" + name
+ "' in namespace '" + uri.getURI() + "' has not been declared.");
}
final Object ret = vmap.get(name);
if (ret == null) {
if (!vmap.containsKey(name)) {
throw new IllegalArgumentException("Variable with name '"
+ name + "' in namespace '" + uri.getURI()
+ "' has not been declared.");
}
// leave translating null variable values to the implementation.
return null;
}
return ret;
}
@Override
public Object getVariable(String qname) {
if (qname == null) {
throw new NullPointerException(
"Cannot get variable value for null qname");
}
final int pos = qname.indexOf(':');
if (pos >= 0) {
return getVariable(qname.substring(pos + 1),
getNamespace(qname.substring(0, pos)));
}
return getVariable(qname, Namespace.NO_NAMESPACE);
}
@Override
public Object setVariable(String name, Namespace uri, Object value) {
final Object ret = getVariable(name, uri);
// if that succeeded then we have it easy....
xvariables.get(uri.getURI()).put(name, value);
return ret;
}
@Override
public Object setVariable(String qname, Object value) {
if (qname == null) {
throw new NullPointerException(
"Cannot get variable value for null qname");
}
final int pos = qname.indexOf(':');
if (pos >= 0) {
return setVariable(qname.substring(pos + 1),
getNamespace(qname.substring(0, pos)), value);
}
return setVariable(qname, Namespace.NO_NAMESPACE, value);
}
@Override
public final Filter getFilter() {
return xfilter;
}
@Override
public List evaluate(Object context) {
return xfilter.filter(evaluateRawAll(context));
}
/**
*
*/
@Override
public T evaluateFirst(Object context) {
Object raw = evaluateRawFirst(context);
if (raw == null) {
return null;
}
return xfilter.filter(raw);
}
@Override
public XPathDiagnostic diagnose(Object context, boolean firstonly) {
final List result = firstonly ? Collections
.singletonList(evaluateRawFirst(context))
: evaluateRawAll(context);
return new XPathDiagnosticImpl(context, this, result, firstonly);
}
@Override
public String toString() {
int nscnt = xnamespaces.size();
int vcnt = 0;
for (Map cmap : xvariables.values()) {
vcnt += cmap.size();
}
return String.format(
"[XPathExpression: %d namespaces and %d variables for query %s]",
nscnt, vcnt, getExpression());
}
/**
* This is the raw expression evaluator to be implemented by the back-end
* XPath library.
*
* @param context
* The context against which to evaluate the query
* @return A list of XPath results.
*/
protected abstract List evaluateRawAll(Object context);
/**
* This is the raw expression evaluator to be implemented by the back-end
* XPath library. When this method is processed the implementing library is
* free to stop processing when the result that would be the first result is
* retrieved.
*
* Only the first value in the result will be processed (if any).
*
* @param context
* The context against which to evaluate the query
* @return The first item in the XPath results, or null if there are no
* results.
*/
protected abstract Object evaluateRawFirst(Object context);
}