/**
* $Id$
* $Date$
*
*/
package org.xmlsh.core;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.s9api.ItemType;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmSequenceIterator;
import net.sf.saxon.s9api.XdmValue;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.DecimalValue;
import net.sf.saxon.value.DoubleValue;
import net.sf.saxon.value.FloatValue;
import net.sf.saxon.value.IntegerValue;
import org.apache.logging.log4j.Logger;
import org.xmlsh.json.JSONUtils;
import org.xmlsh.sh.shell.SerializeOpts;
import org.xmlsh.sh.shell.Shell;
import org.xmlsh.types.IMethods;
import org.xmlsh.types.ITypeFamily;
import org.xmlsh.types.TypeFamily;
import org.xmlsh.types.XTypeUtils;
import org.xmlsh.types.xtypes.IXValueList;
import org.xmlsh.types.xtypes.IXValueMap;
import org.xmlsh.types.xtypes.IXValueSequence;
import org.xmlsh.types.xtypes.XValueProperty;
import org.xmlsh.types.xtypes.XValuePropertyList;
import org.xmlsh.types.xtypes.XValueSequence;
import org.xmlsh.util.JavaUtils;
import org.xmlsh.util.S9Util;
import org.xmlsh.util.StringPair;
import org.xmlsh.util.Util;
import org.xmlsh.util.XMLUtils;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
/*
* A XValue is a single value or a sequence of 0 or more values
*/
public class XValue implements Iterable<XValue> {
private static Logger mLogger = org.apache.logging.log4j.LogManager
.getLogger(XValue.class);
private TypeFamily mTypeFamily = null; // default null defers to a runtime
// evaluatiion
private Object mValue; // String , XdmItem , Object ,
// List<XValue> ...
// Cache of ints
private static final XValue _intValues[] = new XValue[] {
new XValue(TypeFamily.JAVA, 0),
new XValue(TypeFamily.JAVA, 1),
new XValue(TypeFamily.JAVA, 2)
};
private static final XValue _true = new XValue(TypeFamily.JAVA, true);
private static final XValue _false = new XValue(TypeFamily.JAVA, false);
private XValue(BigDecimal n) {
this(TypeFamily.XDM, new XdmAtomicValue(n));
}
private XValue(boolean n) {
this(TypeFamily.JAVA, n);
}
private XValue(int n) {
this(TypeFamily.XDM, new XdmAtomicValue(n));
}
private XValue(Item item) {
this(S9Util.wrapItem(item));
}
private XValue(IXValueList list) {
this(TypeFamily.XTYPE, list);
}
private XValue(IXValueMap map) {
this(TypeFamily.XTYPE, map);
}
/*
* Create an atomic string (xs:string)
*/
private XValue(IXValueSequence<?> seq) {
this(TypeFamily.XTYPE, seq);
}
/*
* Create an XValue by combining a list of XValue objects into a single XValue
* as a sequence
* null or empty list becomes an empty sequence
*/
private XValue(List<XValue> args) {
if(args == null || args.isEmpty()) {
_initSequence(null);
}
else if(args.size() == 1) {
mTypeFamily = args.get(0).mTypeFamily;
mValue = args.get(0).mValue;
}
else {
_initSequence(new XValueSequence(args));
}
}
private XValue(long n) {
this(TypeFamily.XDM, new XdmAtomicValue(n));
}
private XValue(String s) {
this(TypeFamily.JAVA, s);
}
private XValue(String value, ItemType type) throws SaxonApiException {
this(TypeFamily.XDM, new XdmAtomicValue(value, type));
}
// Create XValue from an array of strings
private XValue(String[] astring) {
this(new XValueSequence(Util.toXValueList(astring)));
}
private XValue(TypeFamily family, Object obj) {
mTypeFamily = family;
mValue = obj;
_init();
}
/*
* Create an XValue from an XdmValue
*/
protected XValue(XdmValue v) {
this(TypeFamily.XDM, v == null ? null : XMLUtils.simplify(v));
}
private XValue(XValueProperty prop) {
this(TypeFamily.XTYPE, prop);
}
private XValue(XValuePropertyList plist) {
this(TypeFamily.XTYPE, plist);
}
public XValue() {
mTypeFamily = TypeFamily.XTYPE;
mValue = null;
_init();
}
private XValue(XValue xv) {
mTypeFamily = xv.mTypeFamily;
mValue = xv.mValue;
_init();
}
public XValue(TypeFamily family) {
mTypeFamily = family;
mValue = null;
_init();
}
public static List<XValue> emptyList() {
return Collections.emptyList();
}
public static XValue empytSequence() {
return new XValue(TypeFamily.XTYPE, XValueSequence.emptySequence());
}
public XValue newInstance() throws InvalidArgumentException {
return getTypeMethods().getXValue(mValue);
}
public static XValue newXValue(BigDecimal n) {
return new XValue(n);
}
/*
*
* public XValue append(XValue v) {
* return append( v.asXdmValue() );
*
* }
*/
public static XValue newXValue(boolean n) {
return n ? _true : _false;
}
public static XValue newXValue(int n) {
return intValue(n);
}
public static XValue newXValue(Item item) {
return new XValue(item);
}
public static XValue newXValue(ITypeFamily type, Object value,
boolean convert) throws InvalidArgumentException {
if(type == null)
return newXValue(value);
if(convert && !type.isInstanceOfFamily(value))
return newXValue(type.typeFamily(), value);
return new XValue(type.typeFamily(), value);
}
public static XValue newXValue(IXValueList list) {
return new XValue(list);
}
public static XValue newXValue(IXValueMap map) {
return new XValue(map);
}
public static XValue newXValue(IXValueSequence<?> seq) {
return new XValue(seq);
}
public static XValue newXValue(List<XValue> values) {
return new XValue(values);
}
public static XValue newXValue(long n) {
return new XValue(n);
}
public static XValue newXValue(Object obj) throws InvalidArgumentException {
if(obj == null)
return nullValue();
if(obj instanceof XValue)
return (XValue) obj;
TypeFamily tf = XTypeUtils.inferFamily(obj);
return XTypeUtils.getInstance(tf).getXValue(obj);
}
public static XValue newXValue(String s) {
return new XValue(s);
}
public static XValue newXValue(String value, ItemType type)
throws SaxonApiException {
return new XValue(value, type);
}
public static XValue newXValue(String[] astring) {
return new XValue(astring);
}
public static XValue newXValue(TypeFamily type, Object value)
throws InvalidArgumentException {
if(type == null)
return newXValue(value);
return XTypeUtils.getInstance(type).getXValue(value);
}
public static XValue newXValue(XdmValue v) {
return new XValue(v);
}
public static XValue newXValue(XValueProperty prop) {
return new XValue(prop);
}
/*
* Return a new XValue which is an appending of "this" value
* and another XdmValue as a sequence
* If This is null or the empty sequence then return the value
*/
public static XValue newXValue(XValuePropertyList plist) {
return new XValue(plist);
}
public static XValue nullValue() {
return new XValue(TypeFamily.XTYPE, null);
}
public static XValue nullValue(TypeFamily xtype) {
return XTypeUtils.getInstance(xtype).nullXValue();
}
private void _init() {
if(mTypeFamily == null)
mTypeFamily = XTypeUtils.inferFamily(mValue);
if(mValue != null)
assert (!mValue.getClass().isArray());
assert (XTypeUtils.getInstance(mTypeFamily).isInstanceOfFamily(mValue));
}
private void _initSequence(IXValueSequence<?> seq) {
mTypeFamily = TypeFamily.XTYPE;
mValue = seq == null ? XValueSequence.emptySequence() : seq;
_init();
}
public XValue append(XdmValue value) throws InvalidArgumentException {
if(mValue == null)
return XValue.newXValue(value);
if(value == null)
return this;
/*
* List<XdmItem> items = new ArrayList<XdmItem>();
* for (XdmItem item : asXdmValue())
* items.add(item);
*
* for( XdmItem item : xvalue )
* items.add(item);
*
* return XValue.asXValue(new XdmValue(items));
*/
return append(XValue.newXValue(value));
}
public XValue append(XValue v) throws InvalidArgumentException {
if(mValue == null)
return v;
return getTypeMethods().append(mValue, v);
}
public JsonNode asJson() throws InvalidArgumentException {
if(mValue == null || mValue instanceof JsonNode)
return (JsonNode) mValue;
return JSONUtils.toJsonNode(toString());
}
public NodeInfo asNodeInfo() throws InvalidArgumentException {
return asXdmNode().getUnderlyingNode();
}
@JsonValue
public Object asObject() {
return mValue;
}
/*
* Returns true if the class is an Integer like class
*/
public QName asQName(Shell shell) {
if(mValue == null)
return null;
if(mValue instanceof QName)
return (QName) mValue;
String qn = null;
if(mValue instanceof XdmAtomicValue) {
Object v = ((XdmAtomicValue) mValue).getValue();
if(v instanceof net.sf.saxon.s9api.QName)
return ((net.sf.saxon.s9api.QName) v).getStructuredQName()
.toJaxpQName();
qn = v.toString();
}
if(qn == null && !isAtomic())
return null;
if(qn == null)
qn = mValue.toString();
if(qn.startsWith("{") || qn.indexOf(':') <= 0)
return Util.qnameFromClarkName(mValue.toString());
StringPair pair = new StringPair(qn, ':');
String uri = shell.getEnv().getNamespaces().get(pair.getLeft());
return new QName(uri, pair.getRight(), pair.getLeft());
}
public Source asSource() throws InvalidArgumentException {
return asXdmNode().asSource();
}
public List<String> asStringList() {
if(isNull())
return null;
List<String> list = new ArrayList<String>();
for(XValue v : this)
list.add(v.toString());
return list;
}
public <T> T asType(Class<T> cls) {
return cls.cast(mValue);
}
public XdmItem asXdmItem() {
if(mValue instanceof XdmItem)
return (XdmItem) mValue;
return null;
// return XMLUtils.asXdmItem( asXdmValue() );
}
public XdmNode asXdmNode() throws InvalidArgumentException {
XdmItem item = asXdmItem();
if(item instanceof XdmNode)
return (XdmNode) item;
else
throw new InvalidArgumentException("Value is not a Node");
}
public XdmSequenceIterator asXdmSequenceIterator()
throws InvalidArgumentException {
XdmValue value = toXdmValue();
if(value == null)
return null;
return value.iterator();
}
public List<XValue> asXList() {
// TODO: inspect contents
return Collections.singletonList(this);
}
public int canConvert(Class<?> c)
throws InvalidArgumentException, UnexpectedException {
Object value = mValue;
if(value == null)
return -1;
Class<? extends Object> vclass = value.getClass();
if(isSequence()) {
if(c.isAssignableFrom(XdmValue.class)) {
if(isEmptySequence())
return 1;
int max = -1;
for(XValue v : this) {
int ic = v.canConvert(c);
if(ic < 0)
break;
if(ic >= max)
max = ic;
}
if(max >= 0)
return max;
}
}
// This can be very heavy weight
int ret = JavaUtils.canConvertClass(vclass, c);
if(ret >= 0)
return ret;
// Is this a Xdm and want to convert to something else
// SNH : in JavaUtils
if(value instanceof XdmValue) {
value = getJavaNative();
if(value == null)
return -1;
vclass = value.getClass();
ret = JavaUtils.canConvertClass(vclass, c);
}
return ret - 1;
}
public <T> T convert(Class<T> c) throws InvalidArgumentException {
try {
Object value = mValue;
if(value == null)
return null;
if(c.isAssignableFrom(value.getClass()))
return c.cast(value);
if(c.isInstance(value))
return c.cast(value);
boolean bothXdm = false;
if(c.isAssignableFrom(XdmValue.class)) {
bothXdm = (value instanceof XdmValue);
if(isSequence()) {
if(isEmptySequence())
return c.cast(XMLUtils.emptySequence());
else
return c.cast(XMLUtils.toXdmValue(this));
}
}
if(!bothXdm && value instanceof XdmValue)
value = getJavaNative();
if(JavaUtils.canConvertClass(value.getClass(), c) >= 0) {
T obj = JavaUtils.convert(value, c);
if(obj != null)
return obj;
}
} catch (Exception e) {
Util.wrapException(e, InvalidArgumentException.class);
return null; // SNH
}
throw new InvalidArgumentException("Cannot convert class: "
+ mValue.getClass().getSimpleName() + " to " + c.getSimpleName());
}
@Override
public boolean equals(Object that) {
if(this == that)
return true;
return super.equals(that);
}
/*
* Type Family and 2.0.x extensions
*/
public boolean equals(String s) {
return isAtomic() && toString().equals(s);
}
public boolean equals(XValue that) {
if(this == that)
return true;
if(isAtomic() && that.isAtomic())
return toString().equals(that.toString());
if(mValue != null && that.mValue != null)
return mValue.equals(that.mValue);
return false;
}
// somewhat bogus
public Object getJavaNative()
throws InvalidArgumentException, UnexpectedException {
try {
if(mValue == null)
return null;
if(isJson())
return JSONUtils.asJavaNative(asJson());
// Already a java type
if(!(mValue instanceof XdmValue))
return mValue;
XdmValue xv = (XdmValue) mValue;
Sequence value = xv.getUnderlyingValue();
// Special case for text nodes treat as String
if(value instanceof NodeInfo
&& ((NodeInfo) value).getNodeKind() == net.sf.saxon.type.Type.TEXT)
return ((NodeInfo) value).getStringValue();
if(!(value instanceof AtomicValue))
return value;
AtomicValue av = (AtomicValue) value;
Object java = SequenceTool.convertToJava(av);
return java;
} catch (Exception e) {
Util.wrapException("Exception getting java native value", e,
UnexpectedException.class);
}
// SNH
return null;
}
public IMethods getTypeMethods() {
return typeFamilyInstance();
}
@Override
public int hashCode() {
if(mValue == null)
return 0;
return mValue.hashCode();
}
public boolean isAtomic() {
if(mValue == null)
return false;
return typeFamilyInstance().isAtomic(mValue);
}
public boolean isEmpty() {
if(isNull())
return true;
return getTypeMethods().isEmpty(mValue);
}
public boolean isEmptySequence() {
return mValue instanceof IXValueSequence
&& ((IXValueSequence<?>) mValue).isEmpty();
}
public boolean isInstanceOf(Class<?> cls) {
boolean b1 = mValue != null;
boolean b2 = b1
&& cls.isAssignableFrom(mValue.getClass());
return b1 && b2;
}
public <T> T asInstanceOf(Class<T> cls) {
return cls.cast(mValue);
}
public boolean isJson() {
if(mTypeFamily == TypeFamily.JSON)
return true;
return mValue != null
&& XTypeUtils.getInstance(TypeFamily.JSON).isInstanceOfFamily(mValue);
}
public boolean isNull() {
return mValue == null;
}
/*
* Prefered way create an XValue from an object
* Will cast directly to XValue if its a known type
* otherwise will create a wrapper
*/
public boolean isSequence() {
return mValue instanceof IXValueSequence;
}
public boolean isContainer() {
return getTypeMethods().isContainer(mValue);
}
public boolean isString() {
if(mValue == null)
return false;
if(mValue instanceof String)
return true;
if(!(mValue instanceof XdmItem))
return false;
Sequence value = asXdmItem().getUnderlyingValue();
boolean isString = (value instanceof net.sf.saxon.value.StringValue) ||
(value instanceof NodeInfo
&& ((NodeInfo) value).getNodeKind() == net.sf.saxon.type.Type.TEXT);
return isString;
}
public boolean isTypeFamily(TypeFamily family) {
return typeFamily() == family;
}
// Never ask for an Xdm Value
public boolean isXdmItem() {
return mValue != null && typeFamily() == TypeFamily.XDM
&& mValue instanceof XdmItem;
}
public boolean isXdmNode() {
return mValue != null && typeFamily() == TypeFamily.XDM
&& mValue instanceof XdmNode;
}
public boolean isXType() {
return isTypeFamily(TypeFamily.XTYPE);
}
@Override
public Iterator<XValue> iterator() {
if(isNull() || isEmptySequence())
return Collections.emptyIterator();
if(isSequence())
return ((IXValueSequence<?>) mValue).iterator();
return Util.singletonIterator(this);
}
public String javaTypeName() {
if(mValue == null)
return "null";
return mValue.getClass().getName();
}
public void serialize(OutputStream out, SerializeOpts opts)
throws IOException, InvalidArgumentException {
if(mValue == null)
return;
ITypeFamily tf = typeFamilyInstance();
if(tf != null)
tf.serialize(mValue, out, opts);
else
out.write(toByteArray(opts));
out.flush();
}
public XValue shift(int n) throws InvalidArgumentException {
if(mValue == null)
return this;
if(!(mValue instanceof IXValueSequence))
return this;
IXValueSequence<?> seq = (IXValueSequence<?>) mValue;
if(seq.isEmpty())
return this;
if(seq.size() <= n)
return empytSequence();
return XValue.newXValue(seq.subSequence(n));
}
public BigDecimal toBigDecimal() throws XPathException {
if(mValue == null)
return null;
if(!isAtomic())
return null;
if(mValue instanceof AtomicValue) {
AtomicValue av = (AtomicValue) mValue;
if(av instanceof DecimalValue)
return ((DecimalValue) av).getDecimalValue();
if(av instanceof DoubleValue)
return BigDecimal.valueOf(((DoubleValue) av).getDoubleValue());
if(av instanceof FloatValue)
return BigDecimal.valueOf(((FloatValue) av).getDoubleValue());
if(av instanceof IntegerValue)
return BigDecimal.valueOf(((IntegerValue) av).longValue());
}
return BigDecimal.valueOf(Double.valueOf(mValue.toString()));
}
public boolean toBoolean()
throws InvalidArgumentException, UnexpectedException {
/*
* Check for Java boolean and integer values
*/
if(mValue == null)
return false;
if(isSequence())
return !isEmptySequence();
try {
if(isJson())
return asJson().asBoolean();
} catch (Exception e) {
Util.wrapException(e, InvalidArgumentException.class);
return false; // SNH
}
if(!(mValue instanceof XdmValue)) {
if(mValue instanceof String)
return Util.parseBoolean((String) mValue);
if(JavaUtils.canConvertClass(mValue.getClass(), Boolean.class) >= 0)
return ((Boolean) convert(Boolean.class)).booleanValue();
if(JavaUtils.canConvertClass(mValue.getClass(), Long.class) >= 0)
return ((Long) convert(Long.class)).longValue() != 0L;
return false;
}
XdmValue value = (XdmValue) mValue;
if(value == null)
return false;
// Sequence of > 1 length
if(value.size() > 1)
return true;
// Sequence of 0 length
if(value.size() == 0)
return false;
try {
return XMLUtils.evalXPath(value, ".", null).effectiveBooleanValue();
} catch (Exception e) {
throw new UnexpectedException("Exception evaluating boolean xpath");
}
}
public byte[] toByteArray(SerializeOpts opts) throws IOException {
try {
if(mValue != null) {
switch(typeFamily()){
case JAVA:
case XTYPE:
return JavaUtils.toByteArray(mValue, opts);
case JSON:
return JSONUtils.toByteArray(asJson(), opts);
case XDM:
return XMLUtils.toByteArray(toXdmValue(), opts);
}
}
} catch (JsonGenerationException | JsonMappingException
| UnsupportedEncodingException | InvalidArgumentException
| SaxonApiException e) {
Util.wrapIOException(e);
}
return null;
}
public double toDouble() {
if(mValue == null)
return 0.;
if(!isAtomic())
return 0.;
if(mValue instanceof DoubleValue)
return ((DoubleValue) mValue).getDoubleValue();
return Double.parseDouble(toString());
}
public int toInt() throws XPathException {
return (int) toLong();
}
public long toLong() throws XPathException {
if(mValue == null)
return 0;
if(!isAtomic())
return -1;
if(mValue instanceof IntegerValue)
return ((IntegerValue) mValue).longValue();
return Long.parseLong(toString());
}
@Override
public String toString() {
try {
return getTypeMethods().asString(mValue);
} catch (Exception e) {
mLogger.debug("Exception in XValue.toString()", e);
}
return "";
}
public XdmItem toXdmItem() throws InvalidArgumentException {
if(isXdmItem())
return asXdmItem();
return XMLUtils.toXdmItem(mValue);
}
public XdmValue toXdmValue() throws InvalidArgumentException {
if(isSequence())
return XMLUtils.toXdmValue(((XValueSequence) mValue));
else if(isContainer()) {
return XMLUtils.toXdmValue(getXValues());
}
return XMLUtils.toXdmValue(mValue);
}
public TypeFamily typeFamily() {
return mTypeFamily;
}
public ITypeFamily typeFamilyInstance() {
return XTypeUtils.getInstance(typeFamily());
}
public XValue xpath(Shell shell, String expr) throws UnexpectedException {
if(mValue == null || !(mValue instanceof XdmValue))
return null;
try {
XPathSelector eval = XMLUtils.evalXPath((XdmValue) mValue, expr, shell);
return XValue.newXValue(TypeFamily.XDM, eval.evaluate());
} catch (Exception e) {
throw new UnexpectedException("Exception evaluating xpath: " + expr, e);
}
}
/*
* StrReplace based replacement of values
*
*/
public XValue substitute(XStringSubstituter subst) {
if(isAtomic()) {
StringBuilder sb = new StringBuilder(toString());
if(subst.replaceIn(sb))
return XValue.newXValue(sb.toString());
}
return this;
}
public XValue getNamedValue(String key) throws CoreException {
return typeFamilyInstance().getXValue(mValue, key);
}
public List<XValue> getXValues() throws InvalidArgumentException {
if(mValue == null)
return emptyList();
return getTypeMethods().getXValues(mValue);
}
/*
* A reasonable version of toString that wont be a bazillion bytes
*/
public String describe() {
if(isNull())
return "null";
return typeFamilyInstance().simpleTypeName(mValue);
}
public JsonNode toJson() throws InvalidArgumentException {
return JSONUtils.toJsonType(this);
}
public static XValue intValue(int i) {
if(i >= 0 && i < _intValues.length)
return _intValues[i];
return new XValue(TypeFamily.JAVA, i);
}
}
//
//
// Copyright (C) 2008-2014 David A. Lee.
//
// The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is David A. Lee
//
// Portions created by (your name) are Copyright (C) (your legal entity). All
// Rights Reserved.
//
// Contributor(s): none.
//