/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.filter.binding;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jaxen.Context;
import org.jaxen.ContextSupport;
import org.jaxen.FunctionContext;
import org.jaxen.JaxenException;
import org.jaxen.JaxenHandler;
import org.jaxen.NamespaceContext;
import org.jaxen.Navigator;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.SimpleVariableContext;
import org.jaxen.VariableContext;
import org.jaxen.XPath;
import org.jaxen.XPathFunctionContext;
import org.jaxen.expr.XPathExpr;
import org.jaxen.function.BooleanFunction;
import org.jaxen.function.NumberFunction;
import org.jaxen.function.StringFunction;
import org.jaxen.saxpath.SAXPathException;
import org.jaxen.saxpath.XPathReader;
import org.jaxen.saxpath.helpers.XPathReaderFactory;
import org.jaxen.util.SingletonList;
/**
*
* @author Johann Sorel (Geomatys)
* @module
*/
final class JaxenFeatureXPath implements XPath {
private static final JaxenFeatureNavigator NAVIGATOR = new JaxenFeatureNavigator();
public static JaxenFeatureXPath create(String path) throws JaxenException{
final Map<String,String> prefixes = new HashMap<>();
path = replaceNamespaces(path, prefixes);
final NamespaceContext nsc = new SimpleNamespaceContext(prefixes);
final JaxenFeatureXPath xpath = new JaxenFeatureXPath(path);
xpath.setNamespaceContext(nsc);
return xpath;
}
/** the parsed form of the XPath expression */
private final XPathExpr xpath;
/** the support information and function, namespace and variable contexts */
private ContextSupport support;
private JaxenFeatureXPath(final String xpathExpr) throws JaxenException {
try {
final XPathReader reader = XPathReaderFactory.createReader();
final JaxenHandler handler = new JaxenHandler();
handler.setXPathFactory(FeatureXPathFactory.INSTANCE);
reader.setXPathHandler(handler);
reader.parse(xpathExpr);
this.xpath = handler.getXPathExpr();
} catch (org.jaxen.saxpath.XPathSyntaxException e) {
throw new org.jaxen.XPathSyntaxException(e);
} catch (SAXPathException e) {
throw new JaxenException(e);
}
}
@Override
public Object evaluate(final Object context) throws JaxenException{
final List answer = selectNodes(context);
if (answer != null && answer.size() == 1){
return answer.get(0);
}
return answer;
}
/**
* Replaces all {namespace} by prefixes and fill the xpath namespace context.
*/
private static String replaceNamespaces(final String candidate, final Map<String,String> prefixes) throws JaxenException{
int start = candidate.indexOf('{');
if(start >= 0){
//we have some namespaces in this expression
final StringBuilder sb = new StringBuilder();
int nsNum = 0;
int end = 0;
do{
sb.append(candidate.substring((end==0)?0:end+1,start));
end = candidate.indexOf('}', start);
final String namespace = candidate.substring(start+1, end);
String prefix = prefixes.get(namespace);
if(prefix == null){
//add a new prefix
prefix = "ns"+Integer.toString(++nsNum);
prefixes.put(namespace, prefix);
//the namespace context expect the prefix to be the key
//prefix<->namespace are a 1 to1 relation, no danger to do this
prefixes.put(prefix, namespace);
}
sb.append(prefix).append(':');
start = candidate.indexOf('{',end);
}while( start >= 0 );
//append what remains
sb.append(candidate.substring(end+1));
return sb.toString();
}
return candidate;
}
@Override
public String valueOf(final Object node) throws JaxenException {
return stringValueOf( node );
}
@Override
public String stringValueOf(final Object node) throws JaxenException {
final Context context = getContext( node );
final Object result = selectSingleNodeForContext( context );
if ( result == null ){
return "";
}
return StringFunction.evaluate( result, context.getNavigator() );
}
@Override
public boolean booleanValueOf(final Object node) throws JaxenException {
final Context context = getContext( node );
final List result = selectNodesForContext( context );
if ( result == null ) return false;
return BooleanFunction.evaluate( result, context.getNavigator() ).booleanValue();
}
@Override
public Number numberValueOf(final Object node) throws JaxenException {
final Context context = getContext( node );
final Object result = selectSingleNodeForContext( context );
return NumberFunction.evaluate( result, context.getNavigator() );
}
@Override
public List selectNodes(final Object node) throws JaxenException {
final Context context = getContext( node );
return selectNodesForContext( context );
}
@Override
public Object selectSingleNode(final Object node) throws JaxenException {
final List results = selectNodes( node );
if ( results.isEmpty() ){
return null;
}
return results.get( 0 );
}
@Override
public void addNamespace(final String prefix, final String uri) throws JaxenException {
final NamespaceContext nsContext = getNamespaceContext();
if ( nsContext instanceof SimpleNamespaceContext ){
((SimpleNamespaceContext)nsContext).addNamespace( prefix, uri );
return;
}
throw new JaxenException("Operation not permitted while using a non-simple namespace context.");
}
@Override
public void setNamespaceContext(final NamespaceContext namespaceContext) {
getContextSupport().setNamespaceContext(namespaceContext);
}
@Override
public void setFunctionContext(final FunctionContext functionContext) {
getContextSupport().setFunctionContext(functionContext);
}
@Override
public void setVariableContext(final VariableContext variableContext) {
getContextSupport().setVariableContext(variableContext);
}
@Override
public NamespaceContext getNamespaceContext() {
return getContextSupport().getNamespaceContext();
}
@Override
public FunctionContext getFunctionContext() {
return getContextSupport().getFunctionContext();
}
@Override
public VariableContext getVariableContext() {
return getContextSupport().getVariableContext();
}
@Override
public Navigator getNavigator() {
return NAVIGATOR;
}
/////////////////////////////////////////////////////////////////////////
private Context getContext(final Object node) {
if (node instanceof Context) {
return (Context) node;
}
Context fullContext = new Context(getContextSupport());
if (node instanceof List) {
fullContext.setNodeSet((List) node);
} else {
List list = new SingletonList(node);
fullContext.setNodeSet(list);
}
return fullContext;
}
private ContextSupport getContextSupport() {
if ( support == null ){
support = new ContextSupport(
new SimpleNamespaceContext(),
XPathFunctionContext.getInstance(),
new SimpleVariableContext(),
getNavigator()
);
}
return support;
}
private List selectNodesForContext(final Context context) throws JaxenException {
return this.xpath.asList(context);
}
private Object selectSingleNodeForContext(final Context context) throws JaxenException {
final List results = selectNodesForContext(context);
if (results.isEmpty()) {
return null;
}
return results.get(0);
}
}