package com.bagri.support.util;
import static com.bagri.support.util.FileUtils.EOL;
import static com.bagri.support.util.FileUtils.def_encoding;
import static java.util.Calendar.*;
import static javax.xml.datatype.DatatypeConstants.FIELD_UNDEFINED;
import static javax.xml.xquery.XQItemType.*;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.StaxDriver;
/**
* A set of static utility methods working with XML
*
* @author Denis Sukhoroslov
*
*/
public class XMLUtils {
private static final DatatypeFactory dtFactory;
private static final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
private static final TransformerFactory transFactory = TransformerFactory.newInstance();
private static final XMLInputFactory xiFactory = XMLInputFactory.newInstance();
//private static final XStream xStream = new XStream(new DomDriver(def_encoding, new NoNameCoder()));
private static final XStream xStream = new XStream(new StaxDriver(new NoNameCoder()));
static {
dbFactory.setNamespaceAware(true);
xStream.alias("map", java.util.Map.class);
xStream.registerConverter(new MapConverter());
try {
dtFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
throw new IllegalStateException("Can not instantiate datatype factory");
}
}
private static final ThreadLocal<DocumentBuilder> thDB = new ThreadLocal<DocumentBuilder>() {
@Override
protected DocumentBuilder initialValue() {
try {
return dbFactory.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
throw new RuntimeException(ex);
}
}
@Override
public DocumentBuilder get() {
DocumentBuilder result = super.get();
result.reset();
return result;
}
};
private static final ThreadLocal<Transformer> thTR = new ThreadLocal<Transformer>() {
@Override
protected Transformer initialValue() {
try {
return transFactory.newTransformer();
} catch (TransformerConfigurationException ex) {
throw new RuntimeException(ex);
}
}
@Override
public Transformer get() {
Transformer result = super.get();
result.reset();
return result;
}
};
/**
* converts {@link GregorianCalendar} to the corresponding {@link XMLGregorianCalendar} instance
*
* @param gc the initial GregorianCalendar instance
* @param cType one of XQJ base type constants
* @return XML gregorian calendar instance
*/
public static XMLGregorianCalendar getXMLCalendar(GregorianCalendar gc, int cType) {
switch (cType) {
case XQBASETYPE_DATE:
return dtFactory.newXMLGregorianCalendarDate(gc.get(YEAR),
gc.get(MONTH) + 1, gc.get(DAY_OF_MONTH), gc.get(ZONE_OFFSET));
case XQBASETYPE_GDAY:
return dtFactory.newXMLGregorianCalendarDate(FIELD_UNDEFINED,
FIELD_UNDEFINED, gc.get(DAY_OF_MONTH), FIELD_UNDEFINED);
case XQBASETYPE_GMONTH:
return dtFactory.newXMLGregorianCalendarDate(FIELD_UNDEFINED,
gc.get(MONTH) + 1, FIELD_UNDEFINED, FIELD_UNDEFINED);
case XQBASETYPE_GMONTHDAY:
return dtFactory.newXMLGregorianCalendarDate(FIELD_UNDEFINED,
gc.get(MONTH) + 1, gc.get(DAY_OF_MONTH), FIELD_UNDEFINED);
case XQBASETYPE_GYEAR:
return dtFactory.newXMLGregorianCalendarDate(gc.get(YEAR),
FIELD_UNDEFINED, FIELD_UNDEFINED, FIELD_UNDEFINED);
case XQBASETYPE_GYEARMONTH:
return dtFactory.newXMLGregorianCalendarDate(gc.get(YEAR),
gc.get(Calendar.MONTH) + 1, FIELD_UNDEFINED, FIELD_UNDEFINED);
case XQBASETYPE_TIME:
return dtFactory.newXMLGregorianCalendarTime(gc.get(HOUR), gc.get(MINUTE),
gc.get(SECOND), gc.get(MILLISECOND), gc.get(ZONE_OFFSET));
//default: //XQBASETYPE_DATETIME
}
return dtFactory.newXMLGregorianCalendar(gc);
}
public static XMLGregorianCalendar newXMLCalendar(String value) {
return dtFactory.newXMLGregorianCalendar(value);
}
/**
* converts String representation of duration to its XML equivalent.
* Returns null if the type provided does not correspond to any XML duration types.
*
* @param duration the String duration representation
* @param dType one of XQJ base type constants
* @return XML {@link Duration} instance or null
*/
public static Duration getXMLDuration(String duration, int dType) {
switch (dType) {
case XQBASETYPE_DURATION: return dtFactory.newDuration(duration);
case XQBASETYPE_DAYTIMEDURATION: return dtFactory.newDurationDayTime(duration);
case XQBASETYPE_YEARMONTHDURATION: return dtFactory.newDurationYearMonth(duration);
}
return null;
}
/**
* Reads content from Reader and return it as String
*
* @param text the Reader to read from
* @return the String result
* @throws IOException in case of read error
*/
public static String textToString(Reader text) throws IOException {
if (text == null) {
throw new IOException("Provided reader is null");
}
String line;
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(text)) {
while((line = br.readLine()) != null) {
sb.append(line).append(EOL);
}
sb.delete(sb.length() - EOL.length(), sb.length());
}
return sb.toString();
}
/**
* Reads content from InputStream and return it as String
*
* @param text the InputStream to read from
* @return the String result
* @throws IOException in case of read error
*/
public static String textToString(InputStream text) throws IOException {
if (text == null) {
throw new IOException("Provided stream is null");
}
try (Reader r = new InputStreamReader(text)) {
return textToString(r);
}
}
/**
* Produce new XML Document from the content provided as String
*
* @param text the content to put into the Document
* @return the XML Document
* @throws IOException in case of XML processing error
*/
public static Document textToDocument(String text) throws IOException {
DocumentBuilder builder = thDB.get();
try {
return builder.parse(new ByteArrayInputStream(text.getBytes(def_encoding)));
// shouldn't we close IS above?
} catch (SAXException ex) {
throw new IOException(ex);
}
}
/**
* Produce new XML Document from the content provided as InputStream
*
* @param text the content stream to put into the Document
* @return the XML Document
* @throws IOException in case of XML processing error
*/
public static Document textToDocument(InputStream text) throws IOException {
DocumentBuilder builder = thDB.get();
try {
return builder.parse(text);
} catch (SAXException ex) {
throw new IOException(ex);
}
}
/**
* Produce new XML Document from the content provided as Reader
*
* @param text the content reader to put into the Document
* @return the XML Document
* @throws IOException in case of XML processing error
*/
public static Document textToDocument(Reader text) throws IOException {
DocumentBuilder builder = thDB.get();
try {
return builder.parse(new InputSource(text));
} catch (SAXException ex) {
throw new IOException(ex);
}
}
/**
* Creates an XMLStreamReader over the content provided as String
*
* @param content the String content to parse
* @return the streaming reader over the content
* @throws IOException in case of reader creation error
*/
public static XMLStreamReader stringToStream(String content) throws IOException {
//get Reader connected to XML input from somewhere..?
// note: we can not close this reader as it is used further
Reader reader = new StringReader(content);
try {
return xiFactory.createXMLStreamReader(reader);
} catch (XMLStreamException ex) {
reader.close();
throw new IOException(ex);
}
}
/**
* Transforms XML Source to XML String representation
*
* @param source the XML Source
* @param props XML output properties as {@link OutputKeys}
* @return XML String
* @throws IOException in case of XML processing error
*/
public static String sourceToString(Source source, Properties props) throws IOException {
Transformer trans = thTR.get();
try {
if (props != null) {
trans.setOutputProperties(props);
} else {
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
}
Writer writer = new StringWriter();
trans.transform(source, new StreamResult(writer));
writer.close();
return writer.toString();
} catch (TransformerException ex) {
throw new IOException(ex);
}
}
/**
* Transforms a particular XML Node to XML String representation
*
* @param node the XML Node to marshal
* @param props XML output properties as {@link OutputKeys}
* @return XML String
* @throws IOException in case of XML processing error
*/
public static String nodeToString(Node node, Properties props) throws IOException {
return sourceToString(new DOMSource(node), props);
}
/**
* Transforms XML String content to XML Result
*
* @param source the String XML content
* @param result the Result to transform to
* @throws IOException in case of XML processing error
*/
public static void stringToResult(String source, Result result) throws IOException {
Transformer trans = thTR.get();
try {
StringReader reader = new StringReader(source);
trans.transform(new StreamSource(reader), result);
} catch (TransformerException ex) {
throw new IOException(ex);
}
}
/**
* Serialize POJO to XML string
*
* @param bean the POJO to serialize
* @return the serialization result
*/
public static String beanToXML(Object bean) {
return xStream.toXML(bean);
}
/**
* Deserialize POJO from XML string
*
* @param xml the XML to deserialize
* @return the deserialization result
*/
public static Object beanFromXML(String xml) {
return xStream.fromXML(xml);
}
/**
* Serialize Map to XML string
*
* @param map the Map to serialize
* @return the serialization result
*/
public static String mapToXML(Map<String, Object> map) {
return xStream.toXML(map);
}
/**
* Deserialize Map from XML string
*
* @param xml the XML to deserialize
* @return the deserialization result
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> mapFromXML(String xml) {
return (Map<String, Object>) xStream.fromXML(xml);
}
private static class MapConverter implements Converter {
private ConcurrentHashMap<String, Class<?>> types = new ConcurrentHashMap<>();
@SuppressWarnings("rawtypes")
public boolean canConvert(Class clazz) {
return Map.class.isAssignableFrom(clazz);
}
@SuppressWarnings("unchecked")
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Map<String, Object> map = (Map<String, Object>) value;
for (Map.Entry<String, Object> entry : map.entrySet()) {
writer.startNode(entry.getKey());
Object val = entry.getValue();
if (val != null) {
types.putIfAbsent(entry.getKey(), val.getClass());
context.convertAnother(val);
}
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, Object> map = new HashMap<>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName();
Class<?> type = types.get(key);
if (type == null) {
type = String.class;
}
Object value = context.convertAnother(map, type);
map.put(key, value);
reader.moveUp();
}
return map;
}
}
}