/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.sparql.expr;

import static javax.xml.datatype.DatatypeConstants.* ;
import static org.apache.jena.datatypes.xsd.XSDDatatype.* ;
import static org.apache.jena.sparql.expr.ValueSpace.*;

import java.math.BigDecimal ;
import java.math.BigInteger ;
import java.util.Calendar ;

import javax.xml.datatype.DatatypeFactory ;
import javax.xml.datatype.Duration ;
import javax.xml.datatype.XMLGregorianCalendar ;

import org.apache.jena.atlas.lib.DateTimeUtils ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.datatypes.DatatypeFormatException ;
import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.TypeMapper ;
import org.apache.jena.datatypes.xsd.XSDDatatype;
import org.apache.jena.ext.xerces.DatatypeFactoryInst;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.graph.impl.LiteralLabel ;
import org.apache.jena.sparql.ARQInternalErrorException ;
import org.apache.jena.sparql.SystemARQ ;
import org.apache.jena.sparql.engine.ExecutionContext ;
import org.apache.jena.sparql.engine.binding.Binding ;
import org.apache.jena.sparql.expr.nodevalue.* ;
import org.apache.jena.sparql.function.FunctionEnv ;
import org.apache.jena.sparql.graph.NodeConst ;
import org.apache.jena.sparql.graph.NodeTransform ;
import org.apache.jena.sparql.serializer.SerializationContext ;
import org.apache.jena.sparql.util.* ;
import org.apache.jena.sys.JenaSystem ;
import org.apache.jena.vocabulary.RDF ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;

public abstract class NodeValue extends ExprNode
{
    static { JenaSystem.init() ; }

    // Maybe:: NodeValueStringLang - strings with language tag

    /* Naming:
     * getXXX => plain accessor
     * asXXX =>  force to the required thing if necessary.
     *
     * Implementation notes:
     *
     * 1. There is little point delaying turning a node into its value
     *    because it has to be verified anyway (e.g. illegal literals).
     *    Because a NodeValue is being created, it is reasonably likely it
     *    is going to be used for it's value, so processing the datatype
     *    can be done at creation time where it is clearer.
     *
     * 2. Conversely, delaying turning a value into a graph node is
     *    valuable because intermediates, like the result of 2+3, will not
     *    be needed as nodes unless assignment (and there is no assignment
     *    in SPARQL even if there is for ARQ).
     *    Node level operations like str() don't need a full node.
     *
     * 3. nodevalue.NodeFunctions contains the SPARQL builtin implementations.
     *    nodevalue.XSDFuncOp contains the implementation of the XQuery/Xpath
     *    functions and operations.
     *    See also NodeUtils.
     *
     * 4. Note that SPARQL "=" is "known to be sameValueAs". Similarly "!=" is
     *    known to be different.
     *
     * 5. To add a new number type:
     *    Add sub type into nodevalue.NodeValueXXX
     *      Must implement .hashCode() and .equals() based on value.
     *    Add Functions.add/subtract/etc code and compareNumeric
     *    Add to compare code
     *    Fix TestExprNumeric
     *    Write lots of tests.
     *    Library code Maths1 and Maths2 for maths functions
     */

    /*
     * Effective boolean value rules.
     *    boolean: value of the boolean
     *    string: length(string) > 0 is true
     *    numeric: number != Nan && number != 0 is true
     * ref:  http://www.w3.org/TR/xquery/#dt-ebv
     */

    private static Logger log = LoggerFactory.getLogger(NodeValue.class) ;

    // ---- Constants and initializers / public

    public static boolean VerboseWarnings = true ;
    public static boolean VerboseExceptions = false ;

    public static final NodeValue TRUE   = NodeValue.makeNode("true", XSDboolean) ;
    public static final NodeValue FALSE  = NodeValue.makeNode("false", XSDboolean) ;

    public static final NodeValue nvZERO = NodeValue.makeNode(NodeConst.nodeZero) ;
    public static final NodeValue nvNegZERO = NodeValue.makeNode("-0.0e0", XSDdouble);
    public static final NodeValue nvONE  = NodeValue.makeNode(NodeConst.nodeOne) ;
    public static final NodeValue nvTEN  = NodeValue.makeNode(NodeConst.nodeTen) ;

    public static final NodeValue nvDecimalZERO = NodeValue.makeNode("0.0", XSDdecimal);
    public static final NodeValue nvDecimalONE  = NodeValue.makeNode("1.0", XSDdecimal);

    public static final NodeValue nvNaN     = NodeValue.makeNode("NaN", XSDdouble) ;
    public static final NodeValue nvINF     = NodeValue.makeNode("INF", XSDdouble) ;
    public static final NodeValue nvNegINF  = NodeValue.makeNode("-INF",XSDdouble) ;

    public static final NodeValue nvEmptyString  = NodeValue.makeString("") ;

    public static final String xsdNamespace = XSD+"#" ;

    public static DatatypeFactory xmlDatatypeFactory = null;

    static {
        // JDK default regardless.
        //xmlDatatypeFactory = DatatypeFactory.newDefaultInstance();
        // Extracted Xerces.
        xmlDatatypeFactory = DatatypeFactoryInst.newDatatypeFactory();
    }

    private Node node = null ;     // Null used when a value has not been turned into a Node.

    // Don't create direct - the static builders manage the value/node relationship
    protected NodeValue() { super() ; }
    protected NodeValue(Node n) { super() ; node = n ; }

//    protected makeNodeValue(NodeValue nv)
//    {
//        if ( v.isNode() )    { ... }
//        if ( v.isBoolean() ) { ... }
//        if ( v.isInteger() ) { ... }
//        if ( v.isDouble() )  { ... }
//        if ( v.isDecimal() ) { ... }
//        if ( v.isString() )  { ... }
//        if ( v.isDate() )    { ... }
//    }

    // ----------------------------------------------------------------
    // ---- Construct NodeValue without a graph node.

    /** Convenience operation - parse a string to produce a NodeValue - common namespaces like xsd: are built-in */
    public static NodeValue parse(String string)
    { return makeNode(NodeFactoryExtra.parseNode(string)) ; }

    public static NodeValue makeInteger(long i)
    { return new NodeValueInteger(BigInteger.valueOf(i)) ; }

    public static NodeValue makeInteger(BigInteger i)
    { return new NodeValueInteger(i) ; }

    public static NodeValue makeInteger(String lexicalForm)
    { return new NodeValueInteger(new BigInteger(lexicalForm)) ; }

    public static NodeValue makeFloat(float f)
    { return new NodeValueFloat(f) ; }

    public static NodeValue makeDouble(double d)
    { return new NodeValueDouble(d) ; }

    public static NodeValue makeString(String s)
    { return new NodeValueString(s) ; }

    public static NodeValue makeSortKey(String s, String collation)
    { return new NodeValueSortKey(s, collation) ; }

    public static NodeValue makeLangString(String s, String lang)
    { return new NodeValueLang(s, lang) ; }

    public static NodeValue makeDecimal(BigDecimal d)
    { return new NodeValueDecimal(d) ; }

    public static NodeValue makeDecimal(long i)
    { return new NodeValueDecimal(BigDecimal.valueOf(i)) ; }

    public static NodeValue makeDecimal(double d)
    { return new NodeValueDecimal(BigDecimal.valueOf(d)) ; }

    public static NodeValue makeDecimal(String lexicalForm)
    { return NodeValue.makeNode(lexicalForm, XSDdecimal) ; }

    public static NodeValue makeDateTime(String lexicalForm)
    { return NodeValue.makeNode(lexicalForm, XSDdateTime) ; }

    public static NodeValue makeDate(String lexicalForm)
    { return NodeValue.makeNode(lexicalForm, XSDdate) ; }

    public static NodeValue makeDateTime(Calendar cal) {
        String lex = DateTimeUtils.calendarToXSDDateTimeString(cal);
        return NodeValue.makeNode(lex, XSDdateTime);
    }

    public static NodeValue makeDateTime(XMLGregorianCalendar cal) {
        String lex = cal.toXMLFormat();
        Node node = NodeFactory.createLiteral(lex, XSDdateTime);
        return NodeValueDateTime.create(lex, node);
    }

    public static NodeValue makeDate(Calendar cal) {
        String lex = DateTimeUtils.calendarToXSDDateString(cal);
        return NodeValue.makeNode(lex, XSDdate);
    }

    public static NodeValue makeDate(XMLGregorianCalendar cal) {
        String lex = cal.toXMLFormat();
        Node node = NodeFactory.createLiteral(lex, XSDdate);
        return NodeValueDateTime.create(lex, node);
    }

    public static NodeValue makeDuration(String lexicalForm)
    { return NodeValue.makeNode(lexicalForm, XSDduration) ; }

    public static NodeValue makeDuration(Duration duration)
    { return new NodeValueDuration(duration); }

    public static NodeValue makeNodeDuration(Duration duration, Node node)
    { return new NodeValueDuration(duration, node); }

    public static NodeValue makeBoolean(boolean b)
    { return b ? NodeValue.TRUE : NodeValue.FALSE ; }

    public static NodeValue booleanReturn(boolean b)
    { return b ? NodeValue.TRUE : NodeValue.FALSE ; }

    // ----------------------------------------------------------------
    // ---- Construct NodeValue from graph nodes

    public static NodeValue makeNode(Node n)
    {
        NodeValue nv = nodeToNodeValue(n) ;
        return nv ;
    }

    public static NodeValue makeNode(String lexicalForm, RDFDatatype dtype)
    {
        Node n = NodeFactory.createLiteral(lexicalForm, dtype) ;
        NodeValue nv = NodeValue.makeNode(n) ;
        return nv ;
    }

    // Convenience - knows that lang tags aren't allowed with datatypes.
    public static NodeValue makeNode(String lexicalForm, String langTag, Node datatype)
    {
        String uri = (datatype==null) ? null : datatype.getURI() ;
        return makeNode(lexicalForm, langTag,  uri) ;
    }

    public static NodeValue makeNode(String lexicalForm, String langTag, String datatype)
    {
        if ( datatype != null && datatype.equals("") )
            datatype = null ;

        if ( langTag != null && datatype != null )
            // raise??
            Log.warn(NodeValue.class, "Both lang tag and datatype defined (lexcial form '"+lexicalForm+"')") ;

        Node n = null ;
        if ( langTag != null )
            n = NodeFactory.createLiteral(lexicalForm, langTag) ;
        else if ( datatype != null) {
            RDFDatatype dType = TypeMapper.getInstance().getSafeTypeByName(datatype) ;
            n = NodeFactory.createLiteral(lexicalForm, dType) ;
        } else
            n = NodeFactory.createLiteral(lexicalForm) ;

        return NodeValue.makeNode(n) ;
    }

    // ----------------------------------------------------------------
    // ---- Construct NodeValue with graph node and value.

    public static NodeValue makeNodeBoolean(boolean b)
    { return b ? NodeValue.TRUE : NodeValue.FALSE ; }

    public static NodeValue makeNodeBoolean(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, null, XSDboolean.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeInteger(long v)
    {
        NodeValue nv = makeNode(Long.toString(v), null, XSDinteger.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeInteger(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, null, XSDinteger.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeFloat(float f)
    {
        NodeValue nv = makeNode(Utils.stringForm(f), null, XSDfloat.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeFloat(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, null, XSDdouble.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeDouble(double v)
    {
        NodeValue nv = makeNode(Utils.stringForm(v), null, XSDdouble.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeDouble(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, null, XSDdouble.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeDecimal(BigDecimal decimal)
    {
      String lex = XSDFuncOp.canonicalDecimalStr(decimal);
      return NodeValue.makeNode(lex, XSDDatatype.XSDdecimal) ;
//        NodeValue nv = XSDFuncOp.canonicalDecimalNV(decimal) ;
//        return nv ;
    }

    public static NodeValue makeNodeDecimal(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, null, XSDdecimal.getURI()) ;
        return nv ;
    }

    public static NodeValue makeNodeString(String string)
    {
        NodeValue nv = makeNode(string, null, (String)null) ;
        return nv ;
    }

    public static NodeValue makeNodeDateTime(Calendar date)
    {
        String lex = DateTimeUtils.calendarToXSDDateTimeString(date) ;
        NodeValue nv = makeNode(lex, XSDdateTime) ;
        return nv ;
    }

    public static NodeValue makeNodeDateTime(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, XSDdateTime) ;
        return nv ;
    }

    public static NodeValue makeNodeDate(Calendar date)
    {
        String lex = DateTimeUtils.calendarToXSDDateString(date) ;
        NodeValue nv = makeNode(lex, XSDdate) ;
        return nv ;
    }

    public static NodeValue makeNodeDate(String lexicalForm)
    {
        NodeValue nv = makeNode(lexicalForm, XSDdate) ;
        return nv ;
    }

   // ----------------------------------------------------------------
   // ---- Expr interface

    @Override
    public NodeValue eval(Binding binding, FunctionEnv env)
    { return this ; }

    // NodeValues are immutable so no need to duplicate.
    @Override
    public Expr copySubstitute(Binding binding)
    {
        return this ;
    }

    @Override
    public Expr applyNodeTransform(NodeTransform transform)
    {
        Node n = asNode() ;
        n = transform.apply(n) ;
        return makeNode(n) ;
    }

    public Node evalNode(Binding binding, ExecutionContext execCxt)
    {
        return asNode() ;
    }

    @Override
    public boolean isConstant()     { return true ; }

    @Override
    public NodeValue getConstant()  { return this ; }

    public boolean isIRI() {
        forceToNode() ;
        return node.isURI() ;
    }

    public boolean isBlank() {
        forceToNode() ;
        return node.isBlank() ;
    }

    public boolean isTripleTerm() {
        forceToNode() ;
        return node.isNodeTriple() ;
    }

    public ValueSpace getValueSpace() {
        return classifyValueSpace(this);
    }

    public static ValueSpace classifyValueOp(NodeValue nv1, NodeValue nv2) {
        ValueSpace c1 = classifyValueSpace(nv1);
        ValueSpace c2 = classifyValueSpace(nv2);
        if ( c1 == c2 ) return c1 ;
        if ( c1 == VSPACE_UNKNOWN || c2 == VSPACE_UNKNOWN )
            return VSPACE_UNKNOWN ;

        // Known values spaces but incompatible
        return VSPACE_DIFFERENT ;
    }

    /*package*/ static ValueSpace classifyValueSpace(NodeValue nv) {
        return ValueSpace.valueSpace(nv);
    }

    // ----------------------------------------------------------------
    // ---- sameValueAs

    // Disjoint value spaces : dateTime and dates are not comparable
    // Every langtag implies another value space as well.

    /**
     * Return true if the two NodeValues are known to be the same value return false
     * if known to be different values, throw ExprEvalException otherwise
     */
    public static boolean sameValueAs(NodeValue nv1, NodeValue nv2) {
        return NodeValueCmp.sameValueAs(nv1, nv2);
    }

    /** @deprecated Use {@link #sameValueAs(NodeValue, NodeValue)}. */
    @Deprecated
    public static boolean sameAs(NodeValue nv1, NodeValue nv2) {
        return sameValueAs(nv1, nv2);
    }

    /**
     * Return true if the two Nodes are known to be different, return false if the
     * two Nodes are known to be the same, else throw ExprEvalException
     */
    public static boolean notSameValueAs(Node n1, Node n2) {
        return notSameValueAs(NodeValue.makeNode(n1), NodeValue.makeNode(n2));
    }

    /** @deprecated Use {@link #sameValueAs(NodeValue, NodeValue)}. */
    @Deprecated
    public static boolean notSameAs(Node n1, Node n2) {
        return notSameValueAs(n1, n2);
    }

    /**
     * Return true if the two NodeValues are known to be different, return false if
     * the two NodeValues are known to be the same, else throw ExprEvalException
     */
    public static boolean notSameValueAs(NodeValue nv1, NodeValue nv2) {
        return !sameValueAs(nv1, nv2);
    }

    /** @deprecated Use {@link #sameValueAs(NodeValue, NodeValue)}. */
    @Deprecated
    public static boolean notSameAs(NodeValue nv1, NodeValue nv2) {
        return notSameValueAs(nv1, nv2);
    }

    // ----------------------------------------------------------------
    // compare

    /** Compare by value (and only value) if possible.
     *  Supports &lt;, &lt;=, &gt;, &gt;= but not = nor != (which are sameValueAs and notSameValueAs)
     * @param nv1
     * @param nv2
     * @return Expr.CMP_LESS(-1), Expr.CMP_EQUAL(0) or Expr.CMP_GREATER(+1)
     * @throws ExprNotComparableException for Expr.CMP_INDETERMINATE(+2)
     */
    public static int compare(NodeValue nv1, NodeValue nv2) {
        //return NodeValueCompare.compare(nv1, nv2);
        int x = NodeValueCmp.compareByValue(nv1, nv2);
        if ( x == Expr.CMP_INDETERMINATE || x == Expr.CMP_UNEQUAL )
            throw new ExprNotComparableException(null);
        return x;
    }
    /**
     * Compare by value if possible else compare by kind/type/lexical form
     * Only use when you want an ordering regardless of form of NodeValue,
     * for example in ORDER BY
     *
     * @param nv1
     * @param nv2
     * @return negative, 0, or positive for less than, equal, greater than.
     */
    public static int compareAlways(NodeValue nv1, NodeValue nv2) {
        //return NodeValueCompare.compareAlways(nv1, nv2);
        return NodeValueCmp.compareAlways(nv1, nv2);
    }

    // ----------------------------------------------------------------
    // ---- Node operations

    public static Node toNode(NodeValue nv)
    {
        if ( nv == null )
            return null ;
        return nv.asNode() ;
    }

    public final Node asNode()
    {
        if ( node == null )
            node = makeNode() ;
        return node ;
    }
    protected abstract Node makeNode() ;

    /** getNode - return the node form - may be null (use .asNode() to force to a node) */
    public Node getNode() { return node ; }

    public String getDatatypeURI() { return asNode().getLiteralDatatypeURI() ; }

    public boolean hasNode() { return node != null ; }

    // ----------------------------------------------------------------
    // ---- Subclass operations

    public boolean isBoolean()      { return false ; }
    public boolean isString()       { return false ; }
    public boolean isLangString()   { return false ; }
    public boolean isSortKey()      { return false ; }

    public boolean isNumber()       { return false ; }
    public boolean isInteger()      { return false ; }
    public boolean isDecimal()      { return false ; }
    public boolean isFloat()        { return false ; }
    public boolean isDouble()       { return false ; }

    public boolean hasDateTime()    { return isDateTime() || isDate() || isTime() || isGYear() || isGYearMonth() || isGMonth() || isGMonthDay() || isGDay() ; }
    public boolean isDateTime()     { return false ; }
    public boolean isDate()         { return false ; }
    public boolean isLiteral()      { return getNode() == null || getNode().isLiteral() ; }
    public boolean isTime()         { return false ; }
    public boolean isDuration()     { return false ; }

    public boolean isYearMonthDuration()
    {
        if ( ! isDuration() ) return false ;
        Duration dur = getDuration() ;
        return ( dur.isSet(YEARS) || dur.isSet(MONTHS) ) &&
               ! dur.isSet(DAYS) && ! dur.isSet(HOURS) && ! dur.isSet(MINUTES) && ! dur.isSet(SECONDS) ;
    }

    public boolean isDayTimeDuration()
    {
        if ( ! isDuration() ) return false ;
        Duration dur = getDuration() ;
        return !dur.isSet(YEARS) && ! dur.isSet(MONTHS) &&
            ( dur.isSet(DAYS) || dur.isSet(HOURS) || dur.isSet(MINUTES) || dur.isSet(SECONDS) );
    }

    public boolean isGYear()        { return false ; }
    public boolean isGYearMonth()   { return false ; }
    public boolean isGMonth()       { return false ; }
    public boolean isGMonthDay()    { return false ; }
    public boolean isGDay()         { return false ; }

    public boolean     getBoolean()     { raise(new ExprEvalTypeException("Not a boolean: "+this)) ; return false ; }
    public String      getString()      { raise(new ExprEvalTypeException("Not a string: "+this)) ; return null ; }
    public String      getLang()        { raise(new ExprEvalTypeException("Not a string: "+this)) ; return null ; }
    public NodeValueSortKey getSortKey()        { raise(new ExprEvalTypeException("Not a sort key: "+this)) ; return null ; }

    public BigInteger  getInteger()     { raise(new ExprEvalTypeException("Not an integer: "+this)) ; return null ; }
    public BigDecimal  getDecimal()     { raise(new ExprEvalTypeException("Not a decimal: "+this)) ; return null ; }
    public float       getFloat()       { raise(new ExprEvalTypeException("Not a float: "+this)) ; return Float.NaN ; }
    public double      getDouble()      { raise(new ExprEvalTypeException("Not a double: "+this)) ; return Double.NaN ; }
    // Value representation for all date and time values.
    public XMLGregorianCalendar getDateTime()    { raise(new ExprEvalTypeException("No DateTime value: "+this)) ; return null ; }
    public Duration    getDuration() { raise(new ExprEvalTypeException("Not a duration: "+this)) ; return null ; }

    // ----------------------------------------------------------------
    // ---- Setting : used when a node is used to make a NodeValue

    private static NodeValue nodeToNodeValue(Node node)
    {
        if ( node.isVariable() )
            Log.warn(NodeValue.class, "Variable passed to NodeValue.nodeToNodeValue") ;

        if ( ! node.isLiteral() )
            // Not a literal - no value to extract
            return new NodeValueNode(node) ;

        boolean hasLangTag = NodeUtils.isLangString(node) ;
        boolean isPlainLiteral = ( node.getLiteralDatatypeURI() == null && ! hasLangTag ) ;

        if ( isPlainLiteral )
            return new NodeValueString(node.getLiteralLexicalForm(), node) ;

        if ( hasLangTag ) {
            // Works for RDF 1.0 and RDF 1.1
            if ( node.getLiteralDatatype() != null && ! RDF.dtLangString.equals(node.getLiteralDatatype()) ) {
                if ( NodeValue.VerboseWarnings )
                    Log.warn(NodeValue.class, "Lang tag and datatype (datatype ignored)") ;
            }
            return new NodeValueLang(node) ;
        }

        // Typed literal
        LiteralLabel lit = node.getLiteral() ;

        // This includes type testing
        //if ( ! lit.getDatatype().isValidLiteral(lit) )
        // Use this - already calculated when the node is formed.
        if ( !node.getLiteral().isWellFormed() )
        {
            if ( NodeValue.VerboseWarnings )
            {
                String tmp =  FmtUtils.stringForNode(node) ;
                Log.warn(NodeValue.class, "Datatype format exception: "+tmp) ;
            }
            // Invalid lexical form.
            return new NodeValueNode(node) ;
        }

        NodeValue nv = _setByValue(node) ;
        if ( nv != null )
            return nv ;

        return new NodeValueNode(node) ;
        //raise(new ExprException("NodeValue.nodeToNodeValue: Unknown Node type: "+n)) ;

    }

    // Jena code does not have these types (yet)
    private static final String dtXSDprecisionDecimal   = XSD+"#precisionDecimal" ;

    // Returns null for unrecognized literal.
    private static NodeValue _setByValue(Node node) {
        // This should not happen.
        // nodeToNodeValue should have dealt with it.
        if ( NodeUtils.hasLang(node) )
            return new NodeValueLang(node) ;
        LiteralLabel lit = node.getLiteral() ;
        RDFDatatype datatype = lit.getDatatype() ;

        // Quick check.
        // Only XSD supported.
        // And (for testing) roman numerals.
        String datatypeURI = datatype.getURI() ;
        if ( !datatypeURI.startsWith(xsdNamespace) && !SystemARQ.EnableRomanNumerals ) {
            // Not XSD.
            return null ;
        }

        String lex = lit.getLexicalForm() ;

        try { // DatatypeFormatException - should not happen
            if ( XSDstring.isValidLiteral(lit) )
                // String - plain or xsd:string, or derived datatype.
                return new NodeValueString(lex, node) ;

            // Otherwise xsd:string is like any other unknown datatype.
            // Ditto literals with language tags (which are handled by nodeToNodeValue)

            // isValidLiteral is a value test - not a syntactic test.
            // This makes a difference in that "1"^^xsd:decimal" is a
            // valid literal for xsd:integer (all other cases are subtypes of xsd:integer)
            // which we want to become integer anyway).

            // Order here is promotion order integer-decimal-float-double

            // XSD allows whitespace. Java String.trim removes too much
            // so must test for validity on the untrimmed lexical form.
            String lexTrimmed = lex.trim();

            if ( ! datatype.equals(XSDdecimal) ) { // ! decimal is short for integers and all derived types.
                // XSD integer and derived types
                if ( XSDinteger.isValidLiteral(lit) )
                {
                    // BigInteger does not accept such whitespace.
                    String s = lexTrimmed;
                    if ( s.startsWith("+") )
                        // BigInteger does not accept leading "+"
                        s = s.substring(1) ;
                    // Includes subtypes (int, byte, postiveInteger etc).
                    // NB Known to be valid for type by now
                    BigInteger integer = new BigInteger(s) ;
                    return new NodeValueInteger(integer, node) ;
                }
            }

            if ( datatype.equals(XSDdecimal) && XSDdecimal.isValidLiteral(lit) ) {
                BigDecimal decimal = new BigDecimal(lexTrimmed) ;
                return new NodeValueDecimal(decimal, node) ;
            }

            if ( datatype.equals(XSDfloat) && XSDfloat.isValidLiteral(lit) ) {
                // NB If needed, call to floatValue, then assign to double.
                // Gets 1.3f != 1.3d right
                float f = ((Number)lit.getValue()).floatValue() ;
                return new NodeValueFloat(f, node) ;
            }

            if ( datatype.equals(XSDdouble) && XSDdouble.isValidLiteral(lit) ) {
                double d = ((Number)lit.getValue()).doubleValue() ;
                return new NodeValueDouble(d, node) ;
            }

            if ( datatype.equals(XSDboolean) && XSDboolean.isValidLiteral(lit) ) {
                boolean b = (Boolean) lit.getValue();
                return new NodeValueBoolean(b, node) ;
            }

            if ( (datatype.equals(XSDdateTime) || datatype.equals(XSDdateTimeStamp)) && XSDdateTime.isValid(lex) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }

            if ( datatype.equals(XSDdate) && XSDdate.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }

            if ( datatype.equals(XSDtime) && XSDtime.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }

            if ( datatype.equals(XSDgYear) && XSDgYear.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }
            if ( datatype.equals(XSDgYearMonth) && XSDgYearMonth.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }
            if ( datatype.equals(XSDgMonth) && XSDgMonth.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }

            if ( datatype.equals(XSDgMonthDay) && XSDgMonthDay.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }
            if ( datatype.equals(XSDgDay) && XSDgDay.isValidLiteral(lit) ) {
                return NodeValueDateTime.create(lexTrimmed, node) ;
            }

            // -- Duration

            if ( datatype.equals(XSDduration) && XSDduration.isValid(lex) ) {
                Duration duration = xmlDatatypeFactory.newDuration(lexTrimmed) ;
                return new NodeValueDuration(duration, node) ;
            }

            if ( datatype.equals(XSDyearMonthDuration) && XSDyearMonthDuration.isValid(lex) ) {
                Duration duration = xmlDatatypeFactory.newDuration(lexTrimmed) ;
                return new NodeValueDuration(duration, node) ;
            }
            if ( datatype.equals(XSDdayTimeDuration) && XSDdayTimeDuration.isValid(lex) ) {
                Duration duration = xmlDatatypeFactory.newDuration(lexTrimmed) ;
                return new NodeValueDuration(duration, node) ;
            }

            // If wired into the TypeMapper via RomanNumeralDatatype.enableAsFirstClassDatatype
//            if ( RomanNumeralDatatype.get().isValidLiteral(lit) )
//            {
//                int i = ((RomanNumeral)lit.getValue()).intValue() ;
//                return new NodeValueInteger(i) ;
//            }

            // Not wired in
            if ( SystemARQ.EnableRomanNumerals )
            {
                if ( lit.getDatatypeURI().equals(RomanNumeralDatatype.get().getURI()) )
                {
                    Object obj = RomanNumeralDatatype.get().parse(lexTrimmed) ;
                    if ( obj instanceof Integer )
                        return new NodeValueInteger(((Integer)obj).longValue()) ;
                    if ( obj instanceof RomanNumeral )
                        return new NodeValueInteger( ((RomanNumeral)obj).intValue() ) ;
                    throw new ARQInternalErrorException("DatatypeFormatException: Roman numeral is unknown class") ;
                }
            }

        } catch (DatatypeFormatException ex)
        {
            // Should have been caught earlier by special test in nodeToNodeValue
            throw new ARQInternalErrorException("DatatypeFormatException: "+lit, ex) ;
        }
        return null ;
    }

    // ----------------------------------------------------------------

    // Point to catch all exceptions.
    public static void raise(ExprException ex) {
        throw ex;
    }

    @Override
    public void visit(ExprVisitor visitor) { visitor.visit(this) ; }

    private void forceToNode()
    {
        if ( node == null )
            node = asNode() ;

        if ( node == null )
            raise(new ExprEvalException("Not a node: "+this)) ;
    }

    // ---- Formatting (suitable for SPARQL syntax).
    // Usually done by being a Node and formatting that.
    // In desperation, will try toString() (no quoting)

    public final String asUnquotedString()
    { return asString() ; }

    public final String asQuotedString()
    { return asQuotedString(new SerializationContext()) ; }

    public final String asQuotedString(SerializationContext context)
    {
        // If possible, make a node and use that as the formatted output.
        if ( node == null )
            node = asNode() ;
        if ( node != null )
            return FmtUtils.stringForNode(node, context) ;
        return toString() ;
    }

    // Convert to a string  - usually overridden.
    public String asString()
    {
        // Do not call .toString()
        forceToNode() ;
        return NodeFunctions.str(node) ;
    }

    @Override
    public int hashCode()
    {
        return asNode().hashCode() ;
    }

    @Override
    public boolean equals(Expr other, boolean bySyntax) {
        if ( other == null ) return false ;
        if ( this == other ) return true ;
        // This is the equality condition Jena uses - lang tags are different by case.

        if ( ! ( other instanceof NodeValue ) )
            return false ;
        NodeValue nv = (NodeValue)other ;
        return asNode().equals(nv.asNode()) ;
        // Not NodeFunctions.sameTerm (which smooshes language tags by case)
    }

    public abstract void visit(NodeValueVisitor visitor) ;

    public Expr apply(ExprTransform transform)  { return transform.transform(this) ; }

    @Override
    public String toString()
    {
        return asQuotedString() ;
    }
}
