/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.jxpath.ri.compiler;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Locale;
import org.apache.commons.jxpath.BasicNodeSet;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
import org.apache.commons.jxpath.NodeSet;
import org.apache.commons.jxpath.ri.Compiler;
import org.apache.commons.jxpath.ri.EvalContext;
import org.apache.commons.jxpath.ri.InfoSetUtil;
import org.apache.commons.jxpath.ri.axes.NodeSetContext;
import org.apache.commons.jxpath.ri.model.NodePointer;
/**
* An element of the compile tree representing one of built-in functions
* like "position()" or "number()".
*
* @author Dmitri Plotnikov
* @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
*/
public class CoreFunction extends Operation {
private static final Double ZERO = new Double(0);
private int functionCode;
/**
* Create a new CoreFunction.
* @param functionCode int function code
* @param args argument Expressions
*/
public CoreFunction(int functionCode, Expression[] args) {
super(args);
this.functionCode = functionCode;
}
/**
* Get the function code.
* @return int function code
*/
public int getFunctionCode() {
return functionCode;
}
/**
* Get the name of this function.
* @return String function name
*/
protected String getFunctionName() {
switch (functionCode) {
case Compiler.FUNCTION_LAST :
return "last";
case Compiler.FUNCTION_POSITION :
return "position";
case Compiler.FUNCTION_COUNT :
return "count";
case Compiler.FUNCTION_ID :
return "id";
case Compiler.FUNCTION_LOCAL_NAME :
return "local-name";
case Compiler.FUNCTION_NAMESPACE_URI :
return "namespace-uri";
case Compiler.FUNCTION_NAME :
return "name";
case Compiler.FUNCTION_STRING :
return "string";
case Compiler.FUNCTION_CONCAT :
return "concat";
case Compiler.FUNCTION_STARTS_WITH :
return "starts-with";
case Compiler.FUNCTION_CONTAINS :
return "contains";
case Compiler.FUNCTION_SUBSTRING_BEFORE :
return "substring-before";
case Compiler.FUNCTION_SUBSTRING_AFTER :
return "substring-after";
case Compiler.FUNCTION_SUBSTRING :
return "substring";
case Compiler.FUNCTION_STRING_LENGTH :
return "string-length";
case Compiler.FUNCTION_NORMALIZE_SPACE :
return "normalize-space";
case Compiler.FUNCTION_TRANSLATE :
return "translate";
case Compiler.FUNCTION_BOOLEAN :
return "boolean";
case Compiler.FUNCTION_NOT :
return "not";
case Compiler.FUNCTION_TRUE :
return "true";
case Compiler.FUNCTION_FALSE :
return "false";
case Compiler.FUNCTION_LANG :
return "lang";
case Compiler.FUNCTION_NUMBER :
return "number";
case Compiler.FUNCTION_SUM :
return "sum";
case Compiler.FUNCTION_FLOOR :
return "floor";
case Compiler.FUNCTION_CEILING :
return "ceiling";
case Compiler.FUNCTION_ROUND :
return "round";
case Compiler.FUNCTION_KEY :
return "key";
case Compiler.FUNCTION_FORMAT_NUMBER:
return "format-number";
default:
return "unknownFunction" + functionCode + "()";
}
}
/**
* Convenience method to return the first argument.
* @return Expression
*/
public Expression getArg1() {
return args[0];
}
/**
* Convenience method to return the second argument.
* @return Expression
*/
public Expression getArg2() {
return args[1];
}
/**
* Convenience method to return the third argument.
* @return Expression
*/
public Expression getArg3() {
return args[2];
}
/**
* Return the number of argument Expressions.
* @return int count
*/
public int getArgumentCount() {
if (args == null) {
return 0;
}
return args.length;
}
/**
* Returns true if any argument is context dependent or if
* the function is last(), position(), boolean(), local-name(),
* name(), string(), lang(), number().
* @return boolean
*/
public boolean computeContextDependent() {
if (super.computeContextDependent()) {
return true;
}
switch (functionCode) {
case Compiler.FUNCTION_LAST:
case Compiler.FUNCTION_POSITION:
return true;
case Compiler.FUNCTION_BOOLEAN:
case Compiler.FUNCTION_LOCAL_NAME:
case Compiler.FUNCTION_NAME:
case Compiler.FUNCTION_NAMESPACE_URI:
case Compiler.FUNCTION_STRING:
case Compiler.FUNCTION_LANG:
case Compiler.FUNCTION_NUMBER:
return args == null || args.length == 0;
case Compiler.FUNCTION_FORMAT_NUMBER:
return args != null && args.length == 2;
case Compiler.FUNCTION_COUNT:
case Compiler.FUNCTION_ID:
case Compiler.FUNCTION_CONCAT:
case Compiler.FUNCTION_STARTS_WITH:
case Compiler.FUNCTION_CONTAINS:
case Compiler.FUNCTION_SUBSTRING_BEFORE:
case Compiler.FUNCTION_SUBSTRING_AFTER:
case Compiler.FUNCTION_SUBSTRING:
case Compiler.FUNCTION_STRING_LENGTH:
case Compiler.FUNCTION_NORMALIZE_SPACE:
case Compiler.FUNCTION_TRANSLATE:
case Compiler.FUNCTION_NOT:
case Compiler.FUNCTION_TRUE:
case Compiler.FUNCTION_FALSE:
case Compiler.FUNCTION_SUM:
case Compiler.FUNCTION_FLOOR:
case Compiler.FUNCTION_CEILING:
case Compiler.FUNCTION_ROUND:
default:
return false;
}
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getFunctionName());
buffer.append('(');
Expression[] args = getArguments();
if (args != null) {
for (int i = 0; i < args.length; i++) {
if (i > 0) {
buffer.append(", ");
}
buffer.append(args[i]);
}
}
buffer.append(')');
return buffer.toString();
}
public Object compute(EvalContext context) {
return computeValue(context);
}
public Object computeValue(EvalContext context) {
switch (functionCode) {
case Compiler.FUNCTION_LAST :
return functionLast(context);
case Compiler.FUNCTION_POSITION :
return functionPosition(context);
case Compiler.FUNCTION_COUNT :
return functionCount(context);
case Compiler.FUNCTION_LANG :
return functionLang(context);
case Compiler.FUNCTION_ID :
return functionID(context);
case Compiler.FUNCTION_LOCAL_NAME :
return functionLocalName(context);
case Compiler.FUNCTION_NAMESPACE_URI :
return functionNamespaceURI(context);
case Compiler.FUNCTION_NAME :
return functionName(context);
case Compiler.FUNCTION_STRING :
return functionString(context);
case Compiler.FUNCTION_CONCAT :
return functionConcat(context);
case Compiler.FUNCTION_STARTS_WITH :
return functionStartsWith(context);
case Compiler.FUNCTION_CONTAINS :
return functionContains(context);
case Compiler.FUNCTION_SUBSTRING_BEFORE :
return functionSubstringBefore(context);
case Compiler.FUNCTION_SUBSTRING_AFTER :
return functionSubstringAfter(context);
case Compiler.FUNCTION_SUBSTRING :
return functionSubstring(context);
case Compiler.FUNCTION_STRING_LENGTH :
return functionStringLength(context);
case Compiler.FUNCTION_NORMALIZE_SPACE :
return functionNormalizeSpace(context);
case Compiler.FUNCTION_TRANSLATE :
return functionTranslate(context);
case Compiler.FUNCTION_BOOLEAN :
return functionBoolean(context);
case Compiler.FUNCTION_NOT :
return functionNot(context);
case Compiler.FUNCTION_TRUE :
return functionTrue(context);
case Compiler.FUNCTION_FALSE :
return functionFalse(context);
case Compiler.FUNCTION_NULL :
return functionNull(context);
case Compiler.FUNCTION_NUMBER :
return functionNumber(context);
case Compiler.FUNCTION_SUM :
return functionSum(context);
case Compiler.FUNCTION_FLOOR :
return functionFloor(context);
case Compiler.FUNCTION_CEILING :
return functionCeiling(context);
case Compiler.FUNCTION_ROUND :
return functionRound(context);
case Compiler.FUNCTION_KEY :
return functionKey(context);
case Compiler.FUNCTION_FORMAT_NUMBER :
return functionFormatNumber(context);
default:
return null;
}
}
/**
* last() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionLast(EvalContext context) {
assertArgCount(0);
// Move the position to the beginning and iterate through
// the context to count nodes.
int old = context.getCurrentPosition();
context.reset();
int count = 0;
while (context.nextNode()) {
count++;
}
// Restore the current position.
if (old != 0) {
context.setPosition(old);
}
return new Double(count);
}
/**
* position() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionPosition(EvalContext context) {
assertArgCount(0);
return new Integer(context.getCurrentPosition());
}
/**
* count() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionCount(EvalContext context) {
assertArgCount(1);
Expression arg1 = getArg1();
int count = 0;
Object value = arg1.compute(context);
if (value instanceof NodePointer) {
value = ((NodePointer) value).getValue();
}
if (value instanceof EvalContext) {
EvalContext ctx = (EvalContext) value;
while (ctx.hasNext()) {
ctx.next();
count++;
}
}
else if (value instanceof Collection) {
count = ((Collection) value).size();
}
else if (value == null) {
count = 0;
}
else {
count = 1;
}
return new Double(count);
}
/**
* lang() implementation.
* @param context evaluation context
* @return Boolean
*/
protected Object functionLang(EvalContext context) {
assertArgCount(1);
String lang = InfoSetUtil.stringValue(getArg1().computeValue(context));
NodePointer pointer = (NodePointer) context.getSingleNodePointer();
if (pointer == null) {
return Boolean.FALSE;
}
return pointer.isLanguage(lang) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* id() implementation.
* @param context evaluation context
* @return Pointer
*/
protected Object functionID(EvalContext context) {
assertArgCount(1);
String id = InfoSetUtil.stringValue(getArg1().computeValue(context));
JXPathContext jxpathContext = context.getJXPathContext();
NodePointer pointer = (NodePointer) jxpathContext.getContextPointer();
return pointer.getPointerByID(jxpathContext, id);
}
/**
* key() implementation.
* @param context evaluation context
* @return various Object
*/
protected Object functionKey(EvalContext context) {
assertArgCount(2);
String key = InfoSetUtil.stringValue(getArg1().computeValue(context));
Object value = getArg2().compute(context);
EvalContext ec = null;
if (value instanceof EvalContext) {
ec = (EvalContext) value;
if (ec.hasNext()) {
value = ((NodePointer) ec.next()).getValue();
}
else { // empty context -> empty results
return new NodeSetContext(context, new BasicNodeSet());
}
}
JXPathContext jxpathContext = context.getJXPathContext();
NodeSet nodeSet = jxpathContext.getNodeSetByKey(key, value);
if (ec != null && ec.hasNext()) {
BasicNodeSet accum = new BasicNodeSet();
accum.add(nodeSet);
while (ec.hasNext()) {
value = ((NodePointer) ec.next()).getValue();
accum.add(jxpathContext.getNodeSetByKey(key, value));
}
nodeSet = accum;
}
return new NodeSetContext(context, nodeSet);
}
/**
* namespace-uri() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionNamespaceURI(EvalContext context) {
if (getArgumentCount() == 0) {
NodePointer ptr = context.getCurrentNodePointer();
String str = ptr.getNamespaceURI();
return str == null ? "" : str;
}
assertArgCount(1);
Object set = getArg1().compute(context);
if (set instanceof EvalContext) {
EvalContext ctx = (EvalContext) set;
if (ctx.hasNext()) {
NodePointer ptr = (NodePointer) ctx.next();
String str = ptr.getNamespaceURI();
return str == null ? "" : str;
}
}
return "";
}
/**
* local-name() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionLocalName(EvalContext context) {
if (getArgumentCount() == 0) {
NodePointer ptr = context.getCurrentNodePointer();
return ptr.getName().getName();
}
assertArgCount(1);
Object set = getArg1().compute(context);
if (set instanceof EvalContext) {
EvalContext ctx = (EvalContext) set;
if (ctx.hasNext()) {
NodePointer ptr = (NodePointer) ctx.next();
return ptr.getName().getName();
}
}
return "";
}
/**
* name() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionName(EvalContext context) {
if (getArgumentCount() == 0) {
NodePointer ptr = context.getCurrentNodePointer();
return ptr.getName().toString();
}
assertArgCount(1);
Object set = getArg1().compute(context);
if (set instanceof EvalContext) {
EvalContext ctx = (EvalContext) set;
if (ctx.hasNext()) {
NodePointer ptr = (NodePointer) ctx.next();
return ptr.getName().toString();
}
}
return "";
}
/**
* string() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionString(EvalContext context) {
if (getArgumentCount() == 0) {
return InfoSetUtil.stringValue(context.getCurrentNodePointer());
}
assertArgCount(1);
return InfoSetUtil.stringValue(getArg1().computeValue(context));
}
/**
* concat() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionConcat(EvalContext context) {
if (getArgumentCount() < 2) {
assertArgCount(2);
}
StringBuffer buffer = new StringBuffer();
Expression[] args = getArguments();
for (int i = 0; i < args.length; i++) {
buffer.append(InfoSetUtil.stringValue(args[i].compute(context)));
}
return buffer.toString();
}
/**
* starts-with() implementation.
* @param context evaluation context
* @return Boolean
*/
protected Object functionStartsWith(EvalContext context) {
assertArgCount(2);
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
return s1.startsWith(s2) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* contains() implementation.
* @param context evaluation context
* @return Boolean
*/
protected Object functionContains(EvalContext context) {
assertArgCount(2);
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
return s1.indexOf(s2) != -1 ? Boolean.TRUE : Boolean.FALSE;
}
/**
* substring-before() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionSubstringBefore(EvalContext context) {
assertArgCount(2);
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
int index = s1.indexOf(s2);
if (index == -1) {
return "";
}
return s1.substring(0, index);
}
/**
* substring-after() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionSubstringAfter(EvalContext context) {
assertArgCount(2);
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
int index = s1.indexOf(s2);
if (index == -1) {
return "";
}
return s1.substring(index + s2.length());
}
/**
* substring() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionSubstring(EvalContext context) {
final int minArgs = 2;
final int maxArgs = 3;
assertArgRange(minArgs, maxArgs);
int ac = getArgumentCount();
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
double from = InfoSetUtil.doubleValue(getArg2().computeValue(context));
if (Double.isNaN(from)) {
return "";
}
from = Math.round(from);
if (from > s1.length() + 1) {
return "";
}
if (ac == 2) {
if (from < 1) {
from = 1;
}
return s1.substring((int) from - 1);
}
double length =
InfoSetUtil.doubleValue(getArg3().computeValue(context));
length = Math.round(length);
if (length < 0) {
return "";
}
double to = from + length;
if (to < 1) {
return "";
}
if (to > s1.length() + 1) {
if (from < 1) {
from = 1;
}
return s1.substring((int) from - 1);
}
if (from < 1) {
from = 1;
}
return s1.substring((int) from - 1, (int) (to - 1));
}
/**
* string-length() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionStringLength(EvalContext context) {
String s;
if (getArgumentCount() == 0) {
s = InfoSetUtil.stringValue(context.getCurrentNodePointer());
}
else {
assertArgCount(1);
s = InfoSetUtil.stringValue(getArg1().computeValue(context));
}
return new Double(s.length());
}
/**
* normalize-space() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionNormalizeSpace(EvalContext context) {
assertArgCount(1);
String s = InfoSetUtil.stringValue(getArg1().computeValue(context));
char[] chars = s.toCharArray();
int out = 0;
int phase = 0;
for (int in = 0; in < chars.length; in++) {
switch (chars[in]) {
case ' ':
case '\t':
case '\r':
case '\n':
if (phase == 1) { // non-space
phase = 2;
chars[out++] = ' ';
}
break;
default:
chars[out++] = chars[in];
phase = 1;
}
}
if (phase == 2) { // trailing-space
out--;
}
return new String(chars, 0, out);
}
/**
* translate() implementation.
* @param context evaluation context
* @return String
*/
protected Object functionTranslate(EvalContext context) {
final int argCount = 3;
assertArgCount(argCount);
String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
String s3 = InfoSetUtil.stringValue(getArg3().computeValue(context));
char[] chars = s1.toCharArray();
int out = 0;
for (int in = 0; in < chars.length; in++) {
char c = chars[in];
int inx = s2.indexOf(c);
if (inx != -1) {
if (inx < s3.length()) {
chars[out++] = s3.charAt(inx);
}
}
else {
chars[out++] = c;
}
}
return new String(chars, 0, out);
}
/**
* boolean() implementation.
* @param context evaluation context
* @return Boolean
*/
protected Object functionBoolean(EvalContext context) {
assertArgCount(1);
return InfoSetUtil.booleanValue(getArg1().computeValue(context))
? Boolean.TRUE
: Boolean.FALSE;
}
/**
* not() implementation.
* @param context evaluation context
* @return Boolean
*/
protected Object functionNot(EvalContext context) {
assertArgCount(1);
return InfoSetUtil.booleanValue(getArg1().computeValue(context))
? Boolean.FALSE
: Boolean.TRUE;
}
/**
* true() implementation.
* @param context evaluation context
* @return Boolean.TRUE
*/
protected Object functionTrue(EvalContext context) {
assertArgCount(0);
return Boolean.TRUE;
}
/**
* false() implementation.
* @param context evaluation context
* @return Boolean.FALSE
*/
protected Object functionFalse(EvalContext context) {
assertArgCount(0);
return Boolean.FALSE;
}
/**
* null() implementation.
* @param context evaluation context
* @return null
*/
protected Object functionNull(EvalContext context) {
assertArgCount(0);
return null;
}
/**
* number() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionNumber(EvalContext context) {
if (getArgumentCount() == 0) {
return InfoSetUtil.number(context.getCurrentNodePointer());
}
assertArgCount(1);
return InfoSetUtil.number(getArg1().computeValue(context));
}
/**
* sum() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionSum(EvalContext context) {
assertArgCount(1);
Object v = getArg1().compute(context);
if (v == null) {
return ZERO;
}
if (v instanceof EvalContext) {
double sum = 0.0;
EvalContext ctx = (EvalContext) v;
while (ctx.hasNext()) {
NodePointer ptr = (NodePointer) ctx.next();
sum += InfoSetUtil.doubleValue(ptr);
}
return new Double(sum);
}
throw new JXPathException(
"Invalid argument type for 'sum': " + v.getClass().getName());
}
/**
* floor() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionFloor(EvalContext context) {
assertArgCount(1);
double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
if (Double.isNaN(v) || Double.isInfinite(v)) {
return new Double(v);
}
return new Double(Math.floor(v));
}
/**
* ceiling() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionCeiling(EvalContext context) {
assertArgCount(1);
double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
if (Double.isNaN(v) || Double.isInfinite(v)) {
return new Double(v);
}
return new Double(Math.ceil(v));
}
/**
* round() implementation.
* @param context evaluation context
* @return Number
*/
protected Object functionRound(EvalContext context) {
assertArgCount(1);
double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
if (Double.isNaN(v) || Double.isInfinite(v)) {
return new Double(v);
}
return new Double(Math.round(v));
}
/**
* format-number() implementation.
* @param context evaluation context
* @return String
*/
private Object functionFormatNumber(EvalContext context) {
final int minArgs = 2;
final int maxArgs = 3;
assertArgRange(minArgs, maxArgs);
double number =
InfoSetUtil.doubleValue(getArg1().computeValue(context));
String pattern =
InfoSetUtil.stringValue(getArg2().computeValue(context));
DecimalFormatSymbols symbols = null;
if (getArgumentCount() == maxArgs) {
String symbolsName =
InfoSetUtil.stringValue(getArg3().computeValue(context));
symbols =
context.getJXPathContext().getDecimalFormatSymbols(symbolsName);
}
else {
NodePointer pointer = context.getCurrentNodePointer();
Locale locale;
if (pointer != null) {
locale = pointer.getLocale();
}
else {
locale = context.getJXPathContext().getLocale();
}
symbols = new DecimalFormatSymbols(locale);
}
DecimalFormat format = (DecimalFormat) NumberFormat.getInstance();
format.setDecimalFormatSymbols(symbols);
format.applyLocalizedPattern(pattern);
return format.format(number);
}
/**
* Assert <code>count</code> args.
* @param count int
*/
private void assertArgCount(int count) {
assertArgRange(count, count);
}
/**
* Assert at least <code>min</code>/at most <code>max</code> args.
* @param min int
* @param max int
*/
private void assertArgRange(int min, int max) {
int ct = getArgumentCount();
if (ct < min || ct > max) {
throw new JXPathInvalidSyntaxException(
"Incorrect number of arguments: " + this);
}
}
}
|