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     */
017    package org.apache.commons.jxpath.ri.compiler;
018    
019    import java.text.DecimalFormat;
020    import java.text.DecimalFormatSymbols;
021    import java.text.NumberFormat;
022    import java.util.Collection;
023    import java.util.Locale;
024    
025    import org.apache.commons.jxpath.BasicNodeSet;
026    import org.apache.commons.jxpath.JXPathContext;
027    import org.apache.commons.jxpath.JXPathException;
028    import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
029    import org.apache.commons.jxpath.NodeSet;
030    import org.apache.commons.jxpath.ri.Compiler;
031    import org.apache.commons.jxpath.ri.EvalContext;
032    import org.apache.commons.jxpath.ri.InfoSetUtil;
033    import org.apache.commons.jxpath.ri.axes.NodeSetContext;
034    import 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     */
043    public 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    }