package lux.xpath; import java.math.BigDecimal; import javax.xml.bind.DatatypeConverter; import lux.exception.LuxException; import lux.xml.QName; import lux.xml.ValueType; public class LiteralExpression extends AbstractExpression { private final Object value; private final ValueType valueType; public LiteralExpression (Object value, ValueType valueType) { super(Type.LITERAL); this.value = value; this.valueType = valueType; } public LiteralExpression (Object value) { super(Type.LITERAL); this.value = value; if (value != null) { valueType = computeType (value); } else { valueType = ValueType.VALUE; } } public static final LiteralExpression EMPTY = new LiteralExpression ("()", ValueType.EMPTY); public static final LiteralExpression ONE = new LiteralExpression (1L); public static final LiteralExpression TRUE = new LiteralExpression (true); private static ValueType computeType (Object value) { if (value instanceof String) { return ValueType.STRING; } else if (value instanceof Integer || value instanceof Long) { return ValueType.INTEGER; } else if (value instanceof Double) { return ValueType.DOUBLE; } else if (value instanceof Float) { return ValueType.FLOAT; } else if (value instanceof BigDecimal) { return ValueType.DECIMAL; } else if (value instanceof Boolean) { return ValueType.BOOLEAN; } else if (value instanceof QName) { return ValueType.QNAME; } throw new LuxException ("unsupported java object type: " + value.getClass().getSimpleName()); } /** * @return 100 */ @Override public int getPrecedence () { return 100; } public ValueType getValueType () { return valueType; } public Object getValue() { return value; } /** * renders the literal as parseable XQuery. Note that */ @Override public void toString(StringBuilder buf) { if (value == null) { buf.append ("()"); return; } switch (valueType) { case UNTYPED_ATOMIC: buf.append ("xs:untypedAtomic("); quoteString (value.toString(), buf); buf.append (')'); break; case STRING: quoteString (value.toString(), buf); break; case BOOLEAN: buf.append ("fn:").append(value).append("()"); break; case FLOAT: Float f = (Float) value; if (f.isInfinite()) { if (f > 0) buf.append ("xs:float('INF')"); else buf.append ("xs:float('-INF')"); } else if (f.isNaN()) { buf.append ("xs:float('NaN')"); } else { buf.append ("xs:float(").append(f).append(')'); } break; case DOUBLE: Double d = (Double) value; if (d.isInfinite()) { if (d > 0) buf.append ("xs:double('INF')"); else buf.append ("xs:double('-INF')"); } else if (d.isNaN()) { buf.append ("xs:double('NaN')"); } else { buf.append ("xs:double(").append(d).append(')'); } break; case DECIMAL: buf.append("xs:decimal(").append (((BigDecimal)value).toPlainString()).append(")"); break; case HEX_BINARY: buf.append("xs:hexBinary(\""); appendHex(buf, (byte[])value); buf.append("\")"); break; case BASE64_BINARY: buf.append("xs:base64Binary(\""); buf.append(DatatypeConverter.printBase64Binary((byte[])value)); buf.append("\")"); break; case DATE: case DATE_TIME: case TIME: case DAY: case MONTH: case MONTH_DAY: case YEAR: case YEAR_MONTH: case DAY_TIME_DURATION: case YEAR_MONTH_DURATION: buf.append(valueType.name).append("(\"").append(value).append("\")"); break; case QNAME: buf.append("fn:QName("); quoteString(((QName)value).getNamespaceURI(), buf); buf.append (",\""); ((QName)value).toString(buf); buf.append("\")"); break; case INT: buf.append(valueType.name).append("(").append(value).append(")"); break; case ATOMIC: default: // rely on the object's toString method - is it only xs:int and its ilk that do this? buf.append (value); } } private static char hexdigits[] = new char[] { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; private void appendHex(StringBuilder buf, byte[] bytes) { for (byte b : bytes) { int b1 = ((b & 0xF0) >> 4); buf.append (hexdigits[b1]); int b2 = b & 0xF; buf.append (hexdigits[b2]); } } /** * Append the string to the buffer, with characters escaped appropriately for XML (and XQuery) text. * The characters ", &, <, >, {, }, and \r are replaced with character entities or numeric character references. * @param s the appended string * @param buf the buffer appended to */ public static void escapeText (String s, StringBuilder buf) { for (char c : s.toCharArray()) { switch (c) { case '{' : buf.append("{"); break; case '}' : buf.append("}"); break; //case '"': buf.append ("\"\""); break; case '>': buf.append (">"); break; case '<': buf.append ("<"); break; case '"': buf.append ("""); break; case '&': buf.append ("&"); break; case '\r': buf.append(" "); break; // XML line ending normalization removes these unless they come in as character references default: buf.append (c); } } } /** * Append the string to the buffer, escaped as in {@link #escapeText(String, StringBuilder)}, surrounded * by double quotes ("). * @param s the appended string * @param buf the buffer appended to */ public static void quoteString(String s, StringBuilder buf) { buf.append ('"'); escapeText (s, buf); buf.append ('"'); } @Override public AbstractExpression accept(ExpressionVisitor visitor) { return visitor.visit(this); } @Override public boolean propEquals (AbstractExpression other) { return value.equals(((LiteralExpression)other).value) && valueType.equals(((LiteralExpression)other).valueType); } @Override public int equivHash () { return value.hashCode() + valueType.ordinal(); } } /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */