View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.compiler;
19  
20  import java.text.DecimalFormat;
21  import java.text.DecimalFormatSymbols;
22  import java.text.NumberFormat;
23  import java.util.Collection;
24  import java.util.Locale;
25  
26  import org.apache.commons.jxpath.BasicNodeSet;
27  import org.apache.commons.jxpath.JXPathContext;
28  import org.apache.commons.jxpath.JXPathException;
29  import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
30  import org.apache.commons.jxpath.NodeSet;
31  import org.apache.commons.jxpath.ri.Compiler;
32  import org.apache.commons.jxpath.ri.EvalContext;
33  import org.apache.commons.jxpath.ri.InfoSetUtil;
34  import org.apache.commons.jxpath.ri.axes.NodeSetContext;
35  import org.apache.commons.jxpath.ri.model.NodePointer;
36  
37  /**
38   * An element of the compile tree representing one of built-in functions like "position()" or "number()".
39   */
40  public class CoreFunction extends Operation {
41  
42      private static final Double ZERO = Double.valueOf(0);
43      private final int functionCode;
44  
45      /**
46       * Constructs a new CoreFunction.
47       *
48       * @param functionCode int function code
49       * @param args         argument Expressions
50       */
51      public CoreFunction(final int functionCode, final Expression[] args) {
52          super(args);
53          this.functionCode = functionCode;
54      }
55  
56      /**
57       * Assert {@code count} args.
58       *
59       * @param count int
60       */
61      private void assertArgCount(final int count) {
62          assertArgRange(count, count);
63      }
64  
65      /**
66       * Assert at least {@code min}/at most {@code max} args.
67       *
68       * @param min int
69       * @param max int
70       */
71      private void assertArgRange(final int min, final int max) {
72          final int ct = getArgumentCount();
73          if (ct < min || ct > max) {
74              throw new JXPathInvalidSyntaxException("Incorrect number of arguments: " + this);
75          }
76      }
77  
78      @Override
79      public Object compute(final EvalContext context) {
80          return computeValue(context);
81      }
82  
83      /**
84       * Returns true if any argument is context dependent or if the function is last(), position(), boolean(), local-name(), name(), string(), lang(), number().
85       *
86       * @return boolean
87       */
88      @Override
89      public boolean computeContextDependent() {
90          if (super.computeContextDependent()) {
91              return true;
92          }
93          switch (functionCode) {
94          case Compiler.FUNCTION_LAST:
95          case Compiler.FUNCTION_POSITION:
96              return true;
97          case Compiler.FUNCTION_BOOLEAN:
98          case Compiler.FUNCTION_LOCAL_NAME:
99          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 }