001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jxpath.ri.compiler;
018
019import java.text.DecimalFormat;
020import java.text.DecimalFormatSymbols;
021import java.text.NumberFormat;
022import java.util.Collection;
023import java.util.Locale;
024
025import org.apache.commons.jxpath.BasicNodeSet;
026import org.apache.commons.jxpath.JXPathContext;
027import org.apache.commons.jxpath.JXPathException;
028import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
029import org.apache.commons.jxpath.NodeSet;
030import org.apache.commons.jxpath.ri.Compiler;
031import org.apache.commons.jxpath.ri.EvalContext;
032import org.apache.commons.jxpath.ri.InfoSetUtil;
033import org.apache.commons.jxpath.ri.axes.NodeSetContext;
034import org.apache.commons.jxpath.ri.model.NodePointer;
035
036/**
037 * An element of the compile tree representing one of built-in functions
038 * like "position()" or "number()".
039 *
040 * @author Dmitri Plotnikov
041 * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
042 */
043public class CoreFunction extends Operation {
044
045    private static final Double ZERO = new Double(0);
046    private int functionCode;
047
048    /**
049     * Create a new CoreFunction.
050     * @param functionCode int function code
051     * @param args argument Expressions
052     */
053    public CoreFunction(int functionCode, Expression[] args) {
054        super(args);
055        this.functionCode = functionCode;
056    }
057
058    /**
059     * Get the function code.
060     * @return int function code
061     */
062    public int getFunctionCode() {
063        return functionCode;
064    }
065
066    /**
067     * Get the name of this function.
068     * @return String function name
069     */
070    protected String getFunctionName() {
071        switch (functionCode) {
072            case Compiler.FUNCTION_LAST :
073                return "last";
074            case Compiler.FUNCTION_POSITION :
075                return "position";
076            case Compiler.FUNCTION_COUNT :
077                return "count";
078            case Compiler.FUNCTION_ID :
079                return "id";
080            case Compiler.FUNCTION_LOCAL_NAME :
081                return "local-name";
082            case Compiler.FUNCTION_NAMESPACE_URI :
083                return "namespace-uri";
084            case Compiler.FUNCTION_NAME :
085                return "name";
086            case Compiler.FUNCTION_STRING :
087                return "string";
088            case Compiler.FUNCTION_CONCAT :
089                return "concat";
090            case Compiler.FUNCTION_STARTS_WITH :
091                return "starts-with";
092            case Compiler.FUNCTION_CONTAINS :
093                return "contains";
094            case Compiler.FUNCTION_SUBSTRING_BEFORE :
095                return "substring-before";
096            case Compiler.FUNCTION_SUBSTRING_AFTER :
097                return "substring-after";
098            case Compiler.FUNCTION_SUBSTRING :
099                return "substring";
100            case Compiler.FUNCTION_STRING_LENGTH :
101                return "string-length";
102            case Compiler.FUNCTION_NORMALIZE_SPACE :
103                return "normalize-space";
104            case Compiler.FUNCTION_TRANSLATE :
105                return "translate";
106            case Compiler.FUNCTION_BOOLEAN :
107                return "boolean";
108            case Compiler.FUNCTION_NOT :
109                return "not";
110            case Compiler.FUNCTION_TRUE :
111                return "true";
112            case Compiler.FUNCTION_FALSE :
113                return "false";
114            case Compiler.FUNCTION_LANG :
115                return "lang";
116            case Compiler.FUNCTION_NUMBER :
117                return "number";
118            case Compiler.FUNCTION_SUM :
119                return "sum";
120            case Compiler.FUNCTION_FLOOR :
121                return "floor";
122            case Compiler.FUNCTION_CEILING :
123                return "ceiling";
124            case Compiler.FUNCTION_ROUND :
125                return "round";
126            case Compiler.FUNCTION_KEY :
127                return "key";
128            case Compiler.FUNCTION_FORMAT_NUMBER:
129                return "format-number";
130            default:
131                return "unknownFunction" + functionCode + "()";
132        }
133    }
134
135    /**
136     * Convenience method to return the first argument.
137     * @return Expression
138     */
139    public Expression getArg1() {
140        return args[0];
141    }
142
143    /**
144     * Convenience method to return the second argument.
145     * @return Expression
146     */
147    public Expression getArg2() {
148        return args[1];
149    }
150
151    /**
152     * Convenience method to return the third argument.
153     * @return Expression
154     */
155    public Expression getArg3() {
156        return args[2];
157    }
158
159    /**
160     * Return the number of argument Expressions.
161     * @return int count
162     */
163    public int getArgumentCount() {
164        if (args == null) {
165            return 0;
166        }
167        return args.length;
168    }
169
170    /**
171     * Returns true if any argument is context dependent or if
172     * the function is last(), position(), boolean(), local-name(),
173     * name(), string(), lang(), number().
174     * @return boolean
175     */
176    public boolean computeContextDependent() {
177        if (super.computeContextDependent()) {
178            return true;
179        }
180
181        switch (functionCode) {
182            case Compiler.FUNCTION_LAST:
183            case Compiler.FUNCTION_POSITION:
184                return true;
185
186            case Compiler.FUNCTION_BOOLEAN:
187            case Compiler.FUNCTION_LOCAL_NAME:
188            case Compiler.FUNCTION_NAME:
189            case Compiler.FUNCTION_NAMESPACE_URI:
190            case Compiler.FUNCTION_STRING:
191            case Compiler.FUNCTION_LANG:
192            case Compiler.FUNCTION_NUMBER:
193                return args == null || args.length == 0;
194
195            case Compiler.FUNCTION_FORMAT_NUMBER:
196                return args != null && args.length == 2;
197
198            case Compiler.FUNCTION_COUNT:
199            case Compiler.FUNCTION_ID:
200            case Compiler.FUNCTION_CONCAT:
201            case Compiler.FUNCTION_STARTS_WITH:
202            case Compiler.FUNCTION_CONTAINS:
203            case Compiler.FUNCTION_SUBSTRING_BEFORE:
204            case Compiler.FUNCTION_SUBSTRING_AFTER:
205            case Compiler.FUNCTION_SUBSTRING:
206            case Compiler.FUNCTION_STRING_LENGTH:
207            case Compiler.FUNCTION_NORMALIZE_SPACE:
208            case Compiler.FUNCTION_TRANSLATE:
209            case Compiler.FUNCTION_NOT:
210            case Compiler.FUNCTION_TRUE:
211            case Compiler.FUNCTION_FALSE:
212            case Compiler.FUNCTION_SUM:
213            case Compiler.FUNCTION_FLOOR:
214            case Compiler.FUNCTION_CEILING:
215            case Compiler.FUNCTION_ROUND:
216            default:
217                return false;
218        }
219    }
220
221    public String toString() {
222        StringBuffer buffer = new StringBuffer();
223        buffer.append(getFunctionName());
224        buffer.append('(');
225        Expression[] args = getArguments();
226        if (args != null) {
227            for (int i = 0; i < args.length; i++) {
228                if (i > 0) {
229                    buffer.append(", ");
230                }
231                buffer.append(args[i]);
232            }
233        }
234        buffer.append(')');
235        return buffer.toString();
236    }
237
238    public Object compute(EvalContext context) {
239        return computeValue(context);
240    }
241
242    public Object computeValue(EvalContext context) {
243        switch (functionCode) {
244            case Compiler.FUNCTION_LAST :
245                return functionLast(context);
246            case Compiler.FUNCTION_POSITION :
247                return functionPosition(context);
248            case Compiler.FUNCTION_COUNT :
249                return functionCount(context);
250            case Compiler.FUNCTION_LANG :
251                return functionLang(context);
252            case Compiler.FUNCTION_ID :
253                return functionID(context);
254            case Compiler.FUNCTION_LOCAL_NAME :
255                return functionLocalName(context);
256            case Compiler.FUNCTION_NAMESPACE_URI :
257                return functionNamespaceURI(context);
258            case Compiler.FUNCTION_NAME :
259                return functionName(context);
260            case Compiler.FUNCTION_STRING :
261                return functionString(context);
262            case Compiler.FUNCTION_CONCAT :
263                return functionConcat(context);
264            case Compiler.FUNCTION_STARTS_WITH :
265                return functionStartsWith(context);
266            case Compiler.FUNCTION_CONTAINS :
267                return functionContains(context);
268            case Compiler.FUNCTION_SUBSTRING_BEFORE :
269                return functionSubstringBefore(context);
270            case Compiler.FUNCTION_SUBSTRING_AFTER :
271                return functionSubstringAfter(context);
272            case Compiler.FUNCTION_SUBSTRING :
273                return functionSubstring(context);
274            case Compiler.FUNCTION_STRING_LENGTH :
275                return functionStringLength(context);
276            case Compiler.FUNCTION_NORMALIZE_SPACE :
277                return functionNormalizeSpace(context);
278            case Compiler.FUNCTION_TRANSLATE :
279                return functionTranslate(context);
280            case Compiler.FUNCTION_BOOLEAN :
281                return functionBoolean(context);
282            case Compiler.FUNCTION_NOT :
283                return functionNot(context);
284            case Compiler.FUNCTION_TRUE :
285                return functionTrue(context);
286            case Compiler.FUNCTION_FALSE :
287                return functionFalse(context);
288            case Compiler.FUNCTION_NULL :
289                return functionNull(context);
290            case Compiler.FUNCTION_NUMBER :
291                return functionNumber(context);
292            case Compiler.FUNCTION_SUM :
293                return functionSum(context);
294            case Compiler.FUNCTION_FLOOR :
295                return functionFloor(context);
296            case Compiler.FUNCTION_CEILING :
297                return functionCeiling(context);
298            case Compiler.FUNCTION_ROUND :
299                return functionRound(context);
300            case Compiler.FUNCTION_KEY :
301                return functionKey(context);
302            case Compiler.FUNCTION_FORMAT_NUMBER :
303                return functionFormatNumber(context);
304            default:
305                return null;
306        }
307    }
308
309    /**
310     * last() implementation.
311     * @param context evaluation context
312     * @return Number
313     */
314    protected Object functionLast(EvalContext context) {
315        assertArgCount(0);
316        // Move the position to the beginning and iterate through
317        // the context to count nodes.
318        int old = context.getCurrentPosition();
319        context.reset();
320        int count = 0;
321        while (context.nextNode()) {
322            count++;
323        }
324
325        // Restore the current position.
326        if (old != 0) {
327            context.setPosition(old);
328        }
329        return new Double(count);
330    }
331
332    /**
333     * position() implementation.
334     * @param context evaluation context
335     * @return Number
336     */
337    protected Object functionPosition(EvalContext context) {
338        assertArgCount(0);
339        return new Integer(context.getCurrentPosition());
340    }
341
342    /**
343     * count() implementation.
344     * @param context evaluation context
345     * @return Number
346     */
347    protected Object functionCount(EvalContext context) {
348        assertArgCount(1);
349        Expression arg1 = getArg1();
350        int count = 0;
351        Object value = arg1.compute(context);
352        if (value instanceof NodePointer) {
353            value = ((NodePointer) value).getValue();
354        }
355        if (value instanceof EvalContext) {
356            EvalContext ctx = (EvalContext) value;
357            while (ctx.hasNext()) {
358                ctx.next();
359                count++;
360            }
361        }
362        else if (value instanceof Collection) {
363            count = ((Collection) value).size();
364        }
365        else if (value == null) {
366            count = 0;
367        }
368        else {
369            count = 1;
370        }
371        return new Double(count);
372    }
373
374    /**
375     * lang() implementation.
376     * @param context evaluation context
377     * @return Boolean
378     */
379    protected Object functionLang(EvalContext context) {
380        assertArgCount(1);
381        String lang = InfoSetUtil.stringValue(getArg1().computeValue(context));
382        NodePointer pointer = (NodePointer) context.getSingleNodePointer();
383        if (pointer == null) {
384            return Boolean.FALSE;
385        }
386        return pointer.isLanguage(lang) ? Boolean.TRUE : Boolean.FALSE;
387    }
388
389    /**
390     * id() implementation.
391     * @param context evaluation context
392     * @return Pointer
393     */
394    protected Object functionID(EvalContext context) {
395        assertArgCount(1);
396        String id = InfoSetUtil.stringValue(getArg1().computeValue(context));
397        JXPathContext jxpathContext = context.getJXPathContext();
398        NodePointer pointer = (NodePointer) jxpathContext.getContextPointer();
399        return pointer.getPointerByID(jxpathContext, id);
400    }
401
402    /**
403     * key() implementation.
404     * @param context evaluation context
405     * @return various Object
406     */
407    protected Object functionKey(EvalContext context) {
408        assertArgCount(2);
409        String key = InfoSetUtil.stringValue(getArg1().computeValue(context));
410        Object value = getArg2().compute(context);
411        EvalContext ec = null;
412        if (value instanceof EvalContext) {
413            ec = (EvalContext) value;
414            if (ec.hasNext()) {
415                value = ((NodePointer) ec.next()).getValue();
416            }
417            else { // empty context -> empty results
418                return new NodeSetContext(context, new BasicNodeSet());
419            }
420        }
421        JXPathContext jxpathContext = context.getJXPathContext();
422        NodeSet nodeSet = jxpathContext.getNodeSetByKey(key, value);
423        if (ec != null && ec.hasNext()) {
424            BasicNodeSet accum = new BasicNodeSet();
425            accum.add(nodeSet);
426            while (ec.hasNext()) {
427                value = ((NodePointer) ec.next()).getValue();
428                accum.add(jxpathContext.getNodeSetByKey(key, value));
429            }
430            nodeSet = accum;
431        }
432        return new NodeSetContext(context, nodeSet);
433    }
434
435    /**
436     * namespace-uri() implementation.
437     * @param context evaluation context
438     * @return String
439     */
440    protected Object functionNamespaceURI(EvalContext context) {
441        if (getArgumentCount() == 0) {
442            NodePointer ptr = context.getCurrentNodePointer();
443            String str = ptr.getNamespaceURI();
444            return str == null ? "" : str;
445        }
446        assertArgCount(1);
447        Object set = getArg1().compute(context);
448        if (set instanceof EvalContext) {
449            EvalContext ctx = (EvalContext) set;
450            if (ctx.hasNext()) {
451                NodePointer ptr = (NodePointer) ctx.next();
452                String str = ptr.getNamespaceURI();
453                return str == null ? "" : str;
454            }
455        }
456        return "";
457    }
458
459    /**
460     * local-name() implementation.
461     * @param context evaluation context
462     * @return String
463     */
464    protected Object functionLocalName(EvalContext context) {
465        if (getArgumentCount() == 0) {
466            NodePointer ptr = context.getCurrentNodePointer();
467            return ptr.getName().getName();
468        }
469        assertArgCount(1);
470        Object set = getArg1().compute(context);
471        if (set instanceof EvalContext) {
472            EvalContext ctx = (EvalContext) set;
473            if (ctx.hasNext()) {
474                NodePointer ptr = (NodePointer) ctx.next();
475                return ptr.getName().getName();
476            }
477        }
478        return "";
479    }
480
481    /**
482     * name() implementation.
483     * @param context evaluation context
484     * @return String
485     */
486    protected Object functionName(EvalContext context) {
487        if (getArgumentCount() == 0) {
488            NodePointer ptr = context.getCurrentNodePointer();
489            return ptr.getName().toString();
490        }
491        assertArgCount(1);
492        Object set = getArg1().compute(context);
493        if (set instanceof EvalContext) {
494            EvalContext ctx = (EvalContext) set;
495            if (ctx.hasNext()) {
496                NodePointer ptr = (NodePointer) ctx.next();
497                return ptr.getName().toString();
498            }
499        }
500        return "";
501    }
502
503    /**
504     * string() implementation.
505     * @param context evaluation context
506     * @return String
507     */
508    protected Object functionString(EvalContext context) {
509        if (getArgumentCount() == 0) {
510            return InfoSetUtil.stringValue(context.getCurrentNodePointer());
511        }
512        assertArgCount(1);
513        return InfoSetUtil.stringValue(getArg1().computeValue(context));
514    }
515
516    /**
517     * concat() implementation.
518     * @param context evaluation context
519     * @return String
520     */
521    protected Object functionConcat(EvalContext context) {
522        if (getArgumentCount() < 2) {
523            assertArgCount(2);
524        }
525        StringBuffer buffer = new StringBuffer();
526        Expression[] args = getArguments();
527        for (int i = 0; i < args.length; i++) {
528            buffer.append(InfoSetUtil.stringValue(args[i].compute(context)));
529        }
530        return buffer.toString();
531    }
532
533    /**
534     * starts-with() implementation.
535     * @param context evaluation context
536     * @return Boolean
537     */
538    protected Object functionStartsWith(EvalContext context) {
539        assertArgCount(2);
540        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
541        String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
542        return s1.startsWith(s2) ? Boolean.TRUE : Boolean.FALSE;
543    }
544
545    /**
546     * contains() implementation.
547     * @param context evaluation context
548     * @return Boolean
549     */
550    protected Object functionContains(EvalContext context) {
551        assertArgCount(2);
552        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
553        String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
554        return s1.indexOf(s2) != -1 ? Boolean.TRUE : Boolean.FALSE;
555    }
556
557    /**
558     * substring-before() implementation.
559     * @param context evaluation context
560     * @return String
561     */
562    protected Object functionSubstringBefore(EvalContext context) {
563        assertArgCount(2);
564        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
565        String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
566        int index = s1.indexOf(s2);
567        if (index == -1) {
568            return "";
569        }
570        return s1.substring(0, index);
571    }
572
573    /**
574     * substring-after() implementation.
575     * @param context evaluation context
576     * @return String
577     */
578    protected Object functionSubstringAfter(EvalContext context) {
579        assertArgCount(2);
580        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
581        String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
582        int index = s1.indexOf(s2);
583        if (index == -1) {
584            return "";
585        }
586        return s1.substring(index + s2.length());
587    }
588
589    /**
590     * substring() implementation.
591     * @param context evaluation context
592     * @return String
593     */
594    protected Object functionSubstring(EvalContext context) {
595        final int minArgs = 2;
596        final int maxArgs = 3;
597        assertArgRange(minArgs, maxArgs);
598        int ac = getArgumentCount();
599
600        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
601        double from = InfoSetUtil.doubleValue(getArg2().computeValue(context));
602        if (Double.isNaN(from)) {
603            return "";
604        }
605
606        from = Math.round(from);
607        if (from > s1.length() + 1) {
608            return "";
609        }
610        if (ac == 2) {
611            if (from < 1) {
612                from = 1;
613            }
614            return s1.substring((int) from - 1);
615        }
616        double length =
617            InfoSetUtil.doubleValue(getArg3().computeValue(context));
618        length = Math.round(length);
619        if (length < 0) {
620            return "";
621        }
622
623        double to = from + length;
624        if (to < 1) {
625            return "";
626        }
627
628        if (to > s1.length() + 1) {
629            if (from < 1) {
630                from = 1;
631            }
632            return s1.substring((int) from - 1);
633        }
634
635        if (from < 1) {
636            from = 1;
637        }
638        return s1.substring((int) from - 1, (int) (to - 1));
639    }
640
641    /**
642     * string-length() implementation.
643     * @param context evaluation context
644     * @return Number
645     */
646    protected Object functionStringLength(EvalContext context) {
647        String s;
648        if (getArgumentCount() == 0) {
649            s = InfoSetUtil.stringValue(context.getCurrentNodePointer());
650        }
651        else {
652            assertArgCount(1);
653            s = InfoSetUtil.stringValue(getArg1().computeValue(context));
654        }
655        return new Double(s.length());
656    }
657
658    /**
659     * normalize-space() implementation.
660     * @param context evaluation context
661     * @return String
662     */
663    protected Object functionNormalizeSpace(EvalContext context) {
664        assertArgCount(1);
665        String s = InfoSetUtil.stringValue(getArg1().computeValue(context));
666        char[] chars = s.toCharArray();
667        int out = 0;
668        int phase = 0;
669        for (int in = 0; in < chars.length; in++) {
670            switch (chars[in]) {
671                case ' ':
672                case '\t':
673                case '\r':
674                case '\n':
675                    if (phase == 1) { // non-space
676                        phase = 2;
677                        chars[out++] = ' ';
678                    }
679                    break;
680                default:
681                    chars[out++] = chars[in];
682                    phase = 1;
683            }
684        }
685        if (phase == 2) { // trailing-space
686            out--;
687        }
688        return new String(chars, 0, out);
689    }
690
691    /**
692     * translate() implementation.
693     * @param context evaluation context
694     * @return String
695     */
696    protected Object functionTranslate(EvalContext context) {
697        final int argCount = 3;
698        assertArgCount(argCount);
699        String s1 = InfoSetUtil.stringValue(getArg1().computeValue(context));
700        String s2 = InfoSetUtil.stringValue(getArg2().computeValue(context));
701        String s3 = InfoSetUtil.stringValue(getArg3().computeValue(context));
702        char[] chars = s1.toCharArray();
703        int out = 0;
704        for (int in = 0; in < chars.length; in++) {
705            char c = chars[in];
706            int inx = s2.indexOf(c);
707            if (inx != -1) {
708                if (inx < s3.length()) {
709                    chars[out++] = s3.charAt(inx);
710                }
711            }
712            else {
713                chars[out++] = c;
714            }
715        }
716        return new String(chars, 0, out);
717    }
718
719    /**
720     * boolean() implementation.
721     * @param context evaluation context
722     * @return Boolean
723     */
724    protected Object functionBoolean(EvalContext context) {
725        assertArgCount(1);
726        return InfoSetUtil.booleanValue(getArg1().computeValue(context))
727            ? Boolean.TRUE
728            : Boolean.FALSE;
729    }
730
731    /**
732     * not() implementation.
733     * @param context evaluation context
734     * @return Boolean
735     */
736    protected Object functionNot(EvalContext context) {
737        assertArgCount(1);
738        return InfoSetUtil.booleanValue(getArg1().computeValue(context))
739            ? Boolean.FALSE
740            : Boolean.TRUE;
741    }
742
743    /**
744     * true() implementation.
745     * @param context evaluation context
746     * @return Boolean.TRUE
747     */
748    protected Object functionTrue(EvalContext context) {
749        assertArgCount(0);
750        return Boolean.TRUE;
751    }
752
753    /**
754     * false() implementation.
755     * @param context evaluation context
756     * @return Boolean.FALSE
757     */
758    protected Object functionFalse(EvalContext context) {
759        assertArgCount(0);
760        return Boolean.FALSE;
761    }
762
763    /**
764     * null() implementation.
765     * @param context evaluation context
766     * @return null
767     */
768    protected Object functionNull(EvalContext context) {
769        assertArgCount(0);
770        return null;
771    }
772
773    /**
774     * number() implementation.
775     * @param context evaluation context
776     * @return Number
777     */
778    protected Object functionNumber(EvalContext context) {
779        if (getArgumentCount() == 0) {
780            return InfoSetUtil.number(context.getCurrentNodePointer());
781        }
782        assertArgCount(1);
783        return InfoSetUtil.number(getArg1().computeValue(context));
784    }
785
786    /**
787     * sum() implementation.
788     * @param context evaluation context
789     * @return Number
790     */
791    protected Object functionSum(EvalContext context) {
792        assertArgCount(1);
793        Object v = getArg1().compute(context);
794        if (v == null) {
795            return ZERO;
796        }
797        if (v instanceof EvalContext) {
798            double sum = 0.0;
799            EvalContext ctx = (EvalContext) v;
800            while (ctx.hasNext()) {
801                NodePointer ptr = (NodePointer) ctx.next();
802                sum += InfoSetUtil.doubleValue(ptr);
803            }
804            return new Double(sum);
805        }
806        throw new JXPathException(
807            "Invalid argument type for 'sum': " + v.getClass().getName());
808    }
809
810    /**
811     * floor() implementation.
812     * @param context evaluation context
813     * @return Number
814     */
815    protected Object functionFloor(EvalContext context) {
816        assertArgCount(1);
817        double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
818        if (Double.isNaN(v) || Double.isInfinite(v)) {
819            return new Double(v);
820        }
821        return new Double(Math.floor(v));
822    }
823
824    /**
825     * ceiling() implementation.
826     * @param context evaluation context
827     * @return Number
828     */
829    protected Object functionCeiling(EvalContext context) {
830        assertArgCount(1);
831        double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
832        if (Double.isNaN(v) || Double.isInfinite(v)) {
833            return new Double(v);
834        }
835        return new Double(Math.ceil(v));
836    }
837
838    /**
839     * round() implementation.
840     * @param context evaluation context
841     * @return Number
842     */
843    protected Object functionRound(EvalContext context) {
844        assertArgCount(1);
845        double v = InfoSetUtil.doubleValue(getArg1().computeValue(context));
846        if (Double.isNaN(v) || Double.isInfinite(v)) {
847            return new Double(v);
848        }
849        return new Double(Math.round(v));
850    }
851
852    /**
853     * format-number() implementation.
854     * @param context evaluation context
855     * @return String
856     */
857    private Object functionFormatNumber(EvalContext context) {
858        final int minArgs = 2;
859        final int maxArgs = 3;
860        assertArgRange(minArgs, maxArgs);
861
862        double number =
863            InfoSetUtil.doubleValue(getArg1().computeValue(context));
864        String pattern =
865            InfoSetUtil.stringValue(getArg2().computeValue(context));
866
867        DecimalFormatSymbols symbols = null;
868        if (getArgumentCount() == maxArgs) {
869            String symbolsName =
870                InfoSetUtil.stringValue(getArg3().computeValue(context));
871            symbols =
872                context.getJXPathContext().getDecimalFormatSymbols(symbolsName);
873        }
874        else {
875            NodePointer pointer = context.getCurrentNodePointer();
876            Locale locale;
877            if (pointer != null) {
878                locale = pointer.getLocale();
879            }
880            else {
881                locale = context.getJXPathContext().getLocale();
882            }
883            symbols = new DecimalFormatSymbols(locale);
884        }
885
886        DecimalFormat format = (DecimalFormat) NumberFormat.getInstance();
887        format.setDecimalFormatSymbols(symbols);
888        format.applyLocalizedPattern(pattern);
889        return format.format(number);
890    }
891
892    /**
893     * Assert <code>count</code> args.
894     * @param count int
895     */
896    private void assertArgCount(int count) {
897        assertArgRange(count, count);
898    }
899
900    /**
901     * Assert at least <code>min</code>/at most <code>max</code> args.
902     * @param min int
903     * @param max int
904     */
905    private void assertArgRange(int min, int max) {
906        int ct = getArgumentCount();
907        if (ct < min || ct > max) {
908            throw new JXPathInvalidSyntaxException(
909                    "Incorrect number of arguments: " + this);
910        }
911    }
912}