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