    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}