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