/* * 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); } } }