/**
* This file is part of Waarp Project.
*
* Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
* COPYRIGHT.txt in the distribution for a full listing of individual contributors.
*
* All Waarp Project is free software: you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Waarp . If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.waarp.common.xml;
import java.io.InvalidObjectException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.waarp.common.exception.InvalidArgumentException;
/**
* XmlValue base element
*
* @author Frederic Bregier
*
*/
public class XmlValue {
private XmlDecl decl;
private Object value;
private List<?> values;
private XmlValue[] subXml;
public XmlValue(XmlDecl decl) {
this.decl = decl;
if (this.decl.isSubXml()) {
if (this.decl.isMultiple()) {
value = null;
values = new ArrayList<XmlValue>();
this.subXml = null;
return;
}
int len = this.decl.getSubXmlSize();
XmlDecl[] newDecls = this.decl.getSubXml();
this.subXml = new XmlValue[len];
for (int i = 0; i < len; i++) {
this.subXml[i] = new XmlValue(newDecls[i]);
}
value = null;
values = null;
return;
}
if (this.decl.isMultiple()) {
value = null;
switch (this.getType()) {
case BOOLEAN:
values = new ArrayList<Boolean>();
break;
case INTEGER:
values = new ArrayList<Integer>();
break;
case FLOAT:
values = new ArrayList<Float>();
break;
case CHARACTER:
values = new ArrayList<Character>();
break;
case BYTE:
values = new ArrayList<Byte>();
break;
case LONG:
values = new ArrayList<Long>();
break;
case DOUBLE:
values = new ArrayList<Double>();
break;
case SHORT:
values = new ArrayList<Short>();
break;
case SQLDATE:
values = new ArrayList<Date>();
break;
case TIMESTAMP:
values = new ArrayList<Timestamp>();
break;
case STRING:
values = new ArrayList<String>();
break;
case EMPTY:
break;
case XVAL:
break;
default:
break;
}
}
}
@SuppressWarnings("unchecked")
public XmlValue(XmlValue from) {
this(from.decl);
if (this.decl.isSubXml()) {
if (this.decl.isMultiple()) {
List<XmlValue[]> subvalues = (List<XmlValue[]>) from.values;
for (XmlValue[] xmlValues : subvalues) {
XmlValue[] newValues = new XmlValue[xmlValues.length];
for (int i = 0; i < xmlValues.length; i++) {
newValues[i] = new XmlValue(xmlValues[i]);
}
((List<XmlValue[]>) this.values).add(newValues);
}
} else {
for (int i = 0; i < from.subXml.length; i++) {
this.subXml[i] = new XmlValue(from.subXml[i]);
}
}
} else if (this.decl.isMultiple()) {
List<Object> subvalues = (List<Object>) from.values;
for (Object object : subvalues) {
try {
this.addValue(getCloneValue(getType(), object));
} catch (InvalidObjectException e) {
continue;
}
}
} else {
try {
this.setValue(from.getCloneValue());
} catch (InvalidObjectException e) {
// Nothing
}
}
}
/**
* @return the decl
*/
public XmlDecl getDecl() {
return decl;
}
/**
* Get Java field name
*
* @return the field name
*/
public String getName() {
return this.decl.getName();
}
/**
* @return the type
*/
public Class<?> getClassType() {
return this.decl.getClassType();
}
/**
* @return the type
*/
public XmlType getType() {
return this.decl.getType();
}
/**
* @return the xmlPath
*/
public String getXmlPath() {
return this.decl.getXmlPath();
}
/**
*
* @return True if this Value is a subXml
*/
public boolean isSubXml() {
return this.decl.isSubXml();
}
/**
*
* @return the associated SubXML with the XmlValue (might be null if singleton or Multiple)
*/
public XmlValue[] getSubXml() {
return this.subXml;
}
/**
*
* @return True if the Value are list of values
*/
public boolean isMultiple() {
return this.decl.isMultiple();
}
/**
*
* @return the associated list with the XmlValues (might be null if singleton or SubXml)
*/
public List<?> getList() {
return this.values;
}
/**
* Add a value into the Multiple values from the String (not compatible with subXml)
*
* @param value
* @throws InvalidObjectException
* @throws InvalidArgumentException
*/
@SuppressWarnings("unchecked")
public void addFromString(String value) throws InvalidObjectException, InvalidArgumentException {
switch (this.getType()) {
case BOOLEAN:
((List<Boolean>) this.values).add((Boolean) convert(
this.getClassType(), value));
break;
case INTEGER:
((List<Integer>) this.values).add((Integer) convert(
this.getClassType(), value));
break;
case FLOAT:
((List<Float>) this.values).add((Float) convert(
this.getClassType(), value));
break;
case CHARACTER:
((List<Character>) this.values).add((Character) convert(
this.getClassType(), value));
break;
case BYTE:
((List<Byte>) this.values).add((Byte) convert(
this.getClassType(), value));
break;
case LONG:
((List<Long>) this.values).add((Long) convert(
this.getClassType(), value));
break;
case DOUBLE:
((List<Double>) this.values).add((Double) convert(
this.getClassType(), value));
break;
case SHORT:
((List<Short>) this.values).add((Short) convert(
this.getClassType(), value));
break;
case SQLDATE:
((List<java.sql.Date>) this.values)
.add((java.sql.Date) convert(this.getClassType(), value));
break;
case TIMESTAMP:
((List<Timestamp>) this.values).add((Timestamp) convert(
this.getClassType(), value));
break;
case STRING:
((List<String>) this.values).add((String) convert(
this.getClassType(), value));
break;
case XVAL:
throw new InvalidObjectException(
"XVAL cannot be assigned from String directly");
// ((List<XmlValue>) this.values).add((XmlValue) value);
case EMPTY:
throw new InvalidObjectException(
"EMPTY cannot be assigned");
}
}
/**
* Add a value into the Multiple values from the Object
*
* @param value
* @throws InvalidObjectException
*/
@SuppressWarnings("unchecked")
public void addValue(Object value) throws InvalidObjectException {
if (this.getType().isNativelyCompatible(value)) {
switch (this.getType()) {
case BOOLEAN:
((List<Boolean>) this.values).add((Boolean) value);
break;
case INTEGER:
((List<Integer>) this.values).add((Integer) value);
break;
case FLOAT:
((List<Float>) this.values).add((Float) value);
break;
case CHARACTER:
((List<Character>) this.values).add((Character) value);
break;
case BYTE:
((List<Byte>) this.values).add((Byte) value);
break;
case LONG:
((List<Long>) this.values).add((Long) value);
break;
case DOUBLE:
((List<Double>) this.values).add((Double) value);
break;
case SHORT:
((List<Short>) this.values).add((Short) value);
break;
case SQLDATE:
if (java.sql.Date.class.isAssignableFrom(value.getClass())) {
((List<java.sql.Date>) this.values)
.add((java.sql.Date) value);
} else if (java.util.Date.class.isAssignableFrom(value
.getClass())) {
((List<java.sql.Date>) this.values)
.add(new java.sql.Date(((java.util.Date) value)
.getTime()));
}
break;
case TIMESTAMP:
((List<Timestamp>) this.values).add((Timestamp) value);
break;
case STRING:
((List<String>) this.values).add((String) value);
break;
case XVAL:
((List<XmlValue[]>) this.values).add((XmlValue[]) value);
break;
default:
throw new InvalidObjectException(
"Can not convert value from " + value.getClass() +
" to type " + this.getClassType());
}
} else {
throw new InvalidObjectException("Can not convert value from " +
value.getClass() + " to type " + this.getClassType());
}
}
/**
* @return the value as Object (might be null if multiple)
*/
public Object getValue() {
return value;
}
/**
* Utility function to get a clone of a value
*
* @param type
* @param value
* @return the clone Object
* @throws InvalidObjectException
*/
public static Object getCloneValue(XmlType type, Object value) throws InvalidObjectException {
if (value == null) {
throw new InvalidObjectException(
"Can not convert value from null to type " + type.classType);
}
switch (type) {
case BOOLEAN:
return new Boolean((Boolean) value);
case INTEGER:
return new Integer((Integer) value);
case FLOAT:
return new Float((Float) value);
case CHARACTER:
return new Character((Character) value);
case BYTE:
return new Byte((Byte) value);
case LONG:
return new Long((Long) value);
case DOUBLE:
return new Double((Double) value);
case SHORT:
return new Short((Short) value);
case SQLDATE:
return new java.sql.Date(((java.sql.Date) value).getTime());
case TIMESTAMP:
return new Timestamp(((Timestamp) value).getTime());
case STRING:
return new String((String) value);
case XVAL:
return new XmlValue((XmlValue) value);
case EMPTY:
default:
throw new InvalidObjectException(
"Can not convert value from " + value.getClass() +
" to type " + type.classType);
}
}
/**
* @return a clone of the value as Object (might be null if multiple)
* @throws InvalidObjectException
*/
public Object getCloneValue() throws InvalidObjectException {
if (getType() == XmlType.EMPTY) {
return new XmlValue(this.decl);
}
return getCloneValue(getType(), value);
}
/**
*
* @return the value as a string
*/
public String getString() {
if (this.getType().isString()) {
return (String) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type String");
}
/**
*
* @return the value as an integer
*/
public int getInteger() {
if (this.getType().isInteger()) {
return (Integer) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Integer");
}
/**
*
* @return the value as a boolean
*/
public boolean getBoolean() {
if (this.getType().isBoolean()) {
return (Boolean) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Boolean");
}
/**
*
* @return the value as a long
*/
public long getLong() {
if (this.getType().isLong()) {
return (Long) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Long");
}
/**
*
* @return the value as a float
*/
public float getFloat() {
if (this.getType().isFloat()) {
return (Float) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Float");
}
/**
*
* @return the value as a float
*/
public char getCharacter() {
if (this.getType().isCharacter()) {
return (Character) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Character");
}
/**
*
* @return the value as a float
*/
public byte getByte() {
if (this.getType().isByte()) {
return (Byte) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Byte");
}
/**
*
* @return the value as a float
*/
public double getDouble() {
if (this.getType().isDouble()) {
return (Double) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Double");
}
/**
*
* @return the value as a float
*/
public short getShort() {
if (this.getType().isShort()) {
return (Short) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Short");
}
/**
*
* @return the value as a float
*/
public Date getDate() {
if (this.getType().isDate()) {
return (Date) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Date");
}
/**
*
* @return the value as a float
*/
public Timestamp getTimestamp() {
if (this.getType().isTimestamp()) {
return (Timestamp) value;
}
throw new IllegalArgumentException("Can not convert value from " +
this.decl.getClassType() + " to type Timestamp");
}
/**
* Set a value from String
*
* @param value
* @throws InvalidArgumentException
*/
public void setFromString(String value) throws InvalidArgumentException {
this.value = convert(this.getClassType(), value);
}
/**
* Test if the Value is empty. If it is a SubXml or isMultiple, check if subnodes are present
* but not if those nodes are empty.
*
* @return True if the Value is Empty
*/
public boolean isEmpty() {
if (isSubXml()) {
if (isMultiple()) {
return (this.values.isEmpty());
} else {
return (this.subXml.length == 0);
}
}
if (isMultiple()) {
return (this.values.isEmpty());
} else {
return (this.value == null);
}
}
/**
* Get a value into a String
*
* @return the value in String format
*/
public String getIntoString() {
if ((!isMultiple()) && (!isSubXml())) {
if (this.value != null) {
return this.value.toString();
} else {
return "";
}
} else {
throw new IllegalArgumentException(
"Cannot convert Multiple values to single String");
}
}
/**
* @param value
* the value to set
* @throws InvalidObjectException
* @exception NumberFormatException
*/
@SuppressWarnings("unchecked")
public void setValue(Object value) throws InvalidObjectException {
if (this.getType().isNativelyCompatible(value)) {
switch (this.getType()) {
case BOOLEAN:
this.value = (Boolean) value;
break;
case INTEGER:
this.value = (Integer) value;
break;
case FLOAT:
this.value = (Float) value;
break;
case CHARACTER:
this.value = (Character) value;
break;
case BYTE:
this.value = (Byte) value;
break;
case LONG:
this.value = (Long) value;
break;
case DOUBLE:
this.value = (Double) value;
break;
case SHORT:
this.value = (Short) value;
break;
case SQLDATE:
if (java.sql.Date.class.isAssignableFrom(value.getClass())) {
this.value = (java.sql.Date) value;
} else if (java.util.Date.class.isAssignableFrom(value
.getClass())) {
this.value = new java.sql.Date(
((java.util.Date) value).getTime());
}
break;
case TIMESTAMP:
this.value = (Timestamp) value;
break;
case STRING:
this.value = (String) value;
break;
case XVAL:
XmlValue[] newValue = ((XmlValue[]) value);
if (this.isSubXml()) {
// FIXME should check also internal XmlDecl equality but
// can only check size
if (this.decl.getSubXmlSize() != newValue.length) {
throw new InvalidObjectException(
"XmlDecl are not compatible from Array of XmlValue" +
" to type " + this.getClassType());
}
if (this.isMultiple()) {
((List<XmlValue[]>) this.values).add(newValue);
} else {
this.subXml = newValue;
}
} else {
throw new InvalidObjectException(
"Can not convert value from Array of XmlValue" +
" to type " + this.getClassType());
}
break;
default:
throw new InvalidObjectException(
"Can not convert value from " + value.getClass() +
" to type " + this.getClassType());
}
} else {
throw new InvalidObjectException("Can not convert value from " +
value.getClass() + " to type " + this.getClassType());
}
}
/**
* Convert String value to the specified type. Throws InvalidArgumentException if type is
* unrecognized.
*
* @throws InvalidArgumentException
*/
protected static Object convert(Class<?> type, String value)
throws InvalidArgumentException {
try {
// test from specific to general
//
if (String.class.isAssignableFrom(type)) {
return value;
}
// primitives
//
else if (type.equals(Boolean.TYPE)) {
if (value.equals("1")) {
return Boolean.TRUE;
}
return Boolean.valueOf(value);
} else if (type.equals(Integer.TYPE)) {
return Integer.valueOf(value);
} else if (type.equals(Float.TYPE)) {
return Float.valueOf(value);
} else if (type.equals(Character.TYPE)) {
return Character.valueOf(value.charAt(0));
} else if (type.equals(Byte.TYPE)) {
return Byte.valueOf(value);
} else if (type.equals(Long.TYPE)) {
return Long.valueOf(value);
} else if (type.equals(Double.TYPE)) {
return Double.valueOf(value);
} else if (type.equals(Short.TYPE)) {
return Short.valueOf(value);
}
// primitive wrappers
//
else if (Boolean.class.isAssignableFrom(type)) {
if ("true".equalsIgnoreCase(value)) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
} else if (Character.class.isAssignableFrom(type)) {
if (value.length() == 1) {
return Character.valueOf(value.charAt(0));
} else {
throw new IllegalArgumentException(
"Can not convert value " + value + " to type " +
type);
}
} else if (Number.class.isAssignableFrom(type)) {
if (Double.class.isAssignableFrom(type)) {
return new Double(value);
} else if (Float.class.isAssignableFrom(type)) {
return new Float(value);
} else if (Integer.class.isAssignableFrom(type)) {
return new Integer(value);
} else if (Long.class.isAssignableFrom(type)) {
return new Long(value);
} else if (Short.class.isAssignableFrom(type)) {
return new Short(value);
}
// other primitive-like classes
//
else if (BigDecimal.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Can not use type " +
type);
} else if (BigInteger.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Can not use type " +
type);
} else {
throw new IllegalArgumentException(
"Can not convert value " + value + " to type " +
type);
}
}
//
// Time and date. We stick close to the JDBC representations
// for time and date, but add the "GMT" timezone so XML files
// can be transferred across timezones without ambiguity. See
// java.sql.Date.toString() and java.sql.Timestamp.toString().
//
else if (java.sql.Date.class.isAssignableFrom(type)) {
return new java.sql.Date(XmlStaticShared.dateFormat.parse(value).getTime());
} else if (Timestamp.class.isAssignableFrom(type)) {
int dotIndex = value.indexOf('.');
int spaceIndex = value.indexOf(' ', dotIndex);
if (dotIndex < 0 || spaceIndex < 0) {
throw new IllegalArgumentException(
"Can not convert value " + value + " to type " +
type);
}
Timestamp ts = new Timestamp(XmlStaticShared.timestampFormat.parse(
value.substring(0, dotIndex)).getTime());
int nanos = Integer.parseInt(value.substring(dotIndex + 1,
spaceIndex));
ts.setNanos(nanos);
return ts;
} else if (java.util.Date.class.isAssignableFrom(type)) {
// Should not be
return new java.sql.Date(XmlStaticShared.timeFormat.parse(value).getTime());
} else {
throw new IllegalArgumentException("Can not convert value " +
value + " to type " + type);
}
} catch (NumberFormatException e) {
throw new InvalidArgumentException("Can not convert value " +
value + " to type " + type);
} catch (IllegalArgumentException e) {
throw new InvalidArgumentException("Can not convert value " +
value + " to type " + type, e);
} catch (ParseException e) {
throw new InvalidArgumentException("Can not convert value " +
value + " to type " + type);
}
}
public String toString() {
return "Val: " +
(isMultiple() ? (values.size() + " elements")
: (value != null ? value.toString()
: (subXml != null ? "subXml" : "no value"))) +
" " + decl.toString();
}
public String toFullString() {
String detail = "Val: " +
(isMultiple() ? (values.size() + " elements")
: (value != null ? value.toString()
: (subXml != null ? "subXml" : "no value"))) +
" " + decl.toString();
if (this.decl.isSubXml()) {
if (isMultiple()) {
detail += "[";
for (Object obj : values) {
if (obj instanceof XmlValue) {
detail += ((XmlValue) obj).toFullString() + ", ";
} else {
detail += "[";
for (XmlValue obj2 : (XmlValue[]) obj) {
detail += obj2.toFullString() + ", ";
}
detail += "], ";
}
}
detail += "]";
} else {
detail += "[";
for (XmlValue obj : this.subXml) {
detail += obj.toFullString() + ", ";
}
detail += "]";
}
}
return detail;
}
}