| /* |
| * 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.createLiteralLang(lexicalForm, langTag) ; |
| else if ( datatype != null) { |
| RDFDatatype dType = TypeMapper.getInstance().getSafeTypeByName(datatype) ; |
| n = NodeFactory.createLiteral(lexicalForm, dType) ; |
| } else |
| n = NodeFactory.createLiteralString(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, XSDfloat.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); |
| } |
| |
| /** |
| * 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)); |
| } |
| |
| /** |
| * 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 NodeValueCmp.notSameValueAs(nv1, nv2); |
| } |
| |
| // ---------------------------------------------------------------- |
| // compare |
| |
| /** Compare by value (and only value) if possible. |
| * Supports <, <=, >, >= 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 ( !lit.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 nv) ) |
| return false ; |
| 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() ; |
| } |
| } |