com.disney.groovity.model.ModelXmlWriter Maven / Gradle / Ivy
/*******************************************************************************
* © 2018 Disney | ABC Television Group
*
* Licensed under the Apache License, Version 2.0 (the "Apache License")
* with the following modification; you may not use this file except in
* compliance with the Apache License and the following modification to it:
* Section 6. Trademarks. is deleted and replaced with:
*
* 6. Trademarks. This License does not grant permission to use the trade
* names, trademarks, service marks, or product names of the Licensor
* and its affiliates, except as required to comply with Section 4(c) of
* the License and to reproduce the content of the NOTICE file.
*
* You may obtain a copy of the Apache License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Apache License with the above modification is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the Apache License for the specific
* language governing permissions and limitations under the Apache License.
*******************************************************************************/
package com.disney.groovity.model;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.logging.Logger;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;
import com.disney.groovity.util.MetaPropertyLookup;
import com.disney.groovity.util.XmlEscapingWriter;
import groovy.lang.Closure;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaProperty;
import groovy.lang.Writable;
import static com.disney.groovity.util.MetaPropertyLookup.getAnnotation;
/**
* A ModelVisitor that produces a serialized XML representation of a Model, supports JAXB annotations
* to control how the XML is formed
*
* @author Alex Vigdor
*
*/
public class ModelXmlWriter extends ModelWalker{
static TransformerFactory transformerFactory = TransformerFactory.newInstance();
final static Map,String> NO_NAMES = Collections.unmodifiableMap(new HashMap<>());
final static Map,String> SKIP_TAG = Collections.unmodifiableMap(new HashMap<>());
final static Logger log = Logger.getLogger(ModelXmlWriter.class.getName());
final static ConcurrentHashMap, String> ELEMENT_NAME_CACHE = new ConcurrentHashMap<>();
final protected Writer writer;
final protected Writer escape;
int indent = 0;
final String indentChars;
ArrayDeque> listElementNames = new ArrayDeque<>();
ArrayDeque,String>>> listTypedElementNames = new ArrayDeque<>();
String rootElementName;
boolean inAttribute=false;
private Transformer transformer = null;
boolean doDelimit = false;
Map namespacePrefixes;
Map declareNamespaces;
Map usedNamespacePrefixs;
boolean root = true;
public ModelXmlWriter(Writer writer) {
this(writer,null);
}
public ModelXmlWriter(Writer out, String indentChars) {
this.writer = out;
this.escape = new XmlEscapingWriter(out);
this.indentChars = indentChars;
if(indentChars==null || indentChars.length()==0) {
this.indent = -1;
}
}
protected String getNamespacePrefix(String uri) {
if(namespacePrefixes==null) {
namespacePrefixes = new HashMap<>();
}
String prefix = namespacePrefixes.get(uri);
if(prefix == null) {
if(usedNamespacePrefixs!=null) {
prefix = usedNamespacePrefixs.get(uri);
}
else {
usedNamespacePrefixs = new HashMap<>();
}
if(prefix==null) {
prefix = "ns".concat(String.valueOf(usedNamespacePrefixs.size()+1));
usedNamespacePrefixs.put(uri, prefix);
}
namespacePrefixes.put(uri, prefix);
if(declareNamespaces==null) {
declareNamespaces = new LinkedHashMap<>();
}
declareNamespaces.put(uri, prefix);
}
return prefix;
}
protected String getTagName(String namespace, String localName) {
if(namespace==null || namespace.isEmpty() || "##default".equals(namespace)) {
return localName;
}
String prefix = getNamespacePrefix(namespace);
return prefix.concat(":").concat(localName);
}
public void visitNull() throws Exception {
}
protected void writeIndent() throws IOException {
if(indent>0) {
for(int i=0;i=0) {
doDelimit=false;
writer.write("\n");
}
if(indent>0) {
writeIndent();
}
}
}
private boolean writeString(Object o) throws Exception {
if(o == null) {
writer.write("");
return true;
}
if(o instanceof CharSequence) {
escape.append((CharSequence) o);
return true;
}
if(o instanceof Number || o instanceof Boolean) {
writer.write(o.toString());
return true;
}
if(o instanceof Date) {
writer.write(String.valueOf(((Date)o).getTime()));
return true;
}
if(o instanceof Writable && ! (o instanceof Model)) {
((Writable)o).writeTo(escape);
return true;
}
if(o instanceof Class) {
escape.write(((Class>)o).getName());
return true;
}
if(o instanceof URI || o instanceof URL || o instanceof Throwable || o.getClass().isEnum()) {
escape.write(o.toString());
return true;
}
if(o instanceof File) {
escape.write(((File)o).getName());
return true;
}
if(o instanceof byte[]) {
escape.write(DatatypeConverter.printBase64Binary((byte[]) o));
return true;
}
return false;
}
public void visitObject(Object o) throws Exception {
if(!writeString(o)) {
if(o instanceof Node) {
if(transformer==null) {
transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
}
transformer.transform(new DOMSource((Node)o), new StreamResult(writer));
doDelimit=true;
}
else if(o instanceof JAXBElement) {
JAXBElement> je = (JAXBElement>)o;
String tn = getTagName(je.getName().getNamespaceURI(), je.getName().getLocalPart());
writeTag(tn, je.getValue(), t -> {
try {
visit(t);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
else if(o instanceof Closure) {
@SuppressWarnings("rawtypes")
Closure c = (Closure) o;
visit(c.call());
}
else if(o instanceof Future) {
@SuppressWarnings("rawtypes")
Future f = (Future) o;
visit(f.get());
}
else{
if(inAttribute) {
escape.write(o.toString());
return;
}
if(root) {
root=false;
//create root element
writeTag(getRootElementName(o), o, r->{
try {
if(r!=o) {
visitObject(r);
}
else {
super.visitObject(r);
}
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new RuntimeException(e);
}
doDelimit=true;
});
}
else {
super.visitObject(o);
}
}
}
}
protected String getRootElementName(Object o) {
if(rootElementName!=null) {
return rootElementName;
}
return getElementName(o);
}
protected String getElementName(Object o) {
Class> c = o.getClass();
String name = ELEMENT_NAME_CACHE.get(c);
if(name!=null) {
return name;
}
XmlRootElement xre = c.getAnnotation(XmlRootElement.class);
String namespace = null;
if(xre!=null) {
Package p = c.getPackage();
if(p!=null) {
XmlSchema schema = p.getAnnotation(XmlSchema.class);
if(schema!=null && schema.xmlns()!=null) {
if(usedNamespacePrefixs==null) {
usedNamespacePrefixs = new HashMap<>();
}
for(XmlNs xns : schema.xmlns()) {
if(!usedNamespacePrefixs.containsKey(xns.namespaceURI())) {
usedNamespacePrefixs.put(xns.namespaceURI(), xns.prefix());
}
}
}
}
namespace = xre.namespace();
if(!"##default".equals(xre.name())) {
name = getTagName(namespace, xre.name());
}
}
else {
XmlElement x = c.getAnnotation(XmlElement.class);
if(x!=null) {
namespace = x.namespace();
if(!"##default".equals(x.name())) {
name = getTagName(namespace, x.name());
}
}
}
if(name==null) {
String oname = o.getClass().getSimpleName();
if(oname.endsWith("[]")) {
oname = oname.substring(0,oname.length()-2);
}
if(Character.isUpperCase(oname.charAt(0))) {
char[] namechars = oname.toCharArray();
int stop = 1;
for(int i=1;i1) {
stop--;
}
break;
}
for(int i=0;i,String> listTypedNames=NO_NAMES;
XmlElementWrapper xew = getAnnotation(mp, XmlElementWrapper.class);
if(xew!=null) {
writeTag = true;
if(!"##default".equals(xew.name())) {
name = xew.name();
}
name = getTagName(xew.namespace(), name);
}
XmlElement xe = getAnnotation(mp, XmlElement.class);
if(xe!=null) {
if(!"##default".equals(xe.name())) {
listElementName = xe.name();
}
listElementName = getTagName(xe.namespace(), listElementName);
}
if(xe==null) {
XmlElements xes = getAnnotation(mp, XmlElements.class);
if(xes!=null) {
listTypedNames = new HashMap<>();
for(int i=0; i{
try {
super.visitObjectField(n, o);
} catch(RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
else {
super.visitObjectField(name, value);
}
listElementNames.pop();
listTypedElementNames.pop();
}
private Object transformField(MetaProperty mp, Object value) {
if(mp==null) {
return value;
}
if(getAnnotation(mp, XmlList.class) != null
|| getAnnotation(mp, XmlAttribute.class) != null
|| getAnnotation(mp, XmlValue.class) != null) {
Iterable> i = toIterableIfPossible(value);
if(i!=null) {
StringBuilder builder = new StringBuilder();
boolean delim = false;
for(Object o: i) {
if(delim) {
builder.append(" ");
}
else {
delim=true;
}
builder.append(String.valueOf(o));
}
value = builder.toString();
}
}
return value;
}
protected void writeTag(String name, Object value, Consumer
© 2015 - 2025 Weber Informatics LLC | Privacy Policy