org.apache.juneau.xml.XmlBeanMeta Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.xml;
import static org.apache.juneau.xml.annotation.XmlFormat.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.xml.annotation.*;
/**
* Metadata on beans specific to the XML serializers and parsers pulled from the {@link Xml @Xml} annotation on the
* class.
*/
public class XmlBeanMeta extends BeanMetaExtended {
// XML related fields
private final Map attrs; // Map of bean properties that are represented as XML attributes.
private final Map elements; // Map of bean properties that are represented as XML elements.
private final BeanPropertyMeta attrsProperty; // Bean property that contain XML attribute key/value pairs for this bean.
private final Map collapsedProperties; // Properties defined with @Xml.childName annotation.
private final BeanPropertyMeta contentProperty;
private final XmlFormat contentFormat;
/**
* Constructor.
*
* @param beanMeta The metadata on the bean that this metadata applies to.
*/
public XmlBeanMeta(BeanMeta> beanMeta) {
super(beanMeta);
Class> c = beanMeta.getClassMeta().getInnerClass();
XmlBeanMetaBuilder b = new XmlBeanMetaBuilder(beanMeta);
attrs = Collections.unmodifiableMap(b.attrs);
elements = Collections.unmodifiableMap(b.elements);
attrsProperty = b.attrsProperty;
collapsedProperties = Collections.unmodifiableMap(b.collapsedProperties);
contentProperty = b.contentProperty;
contentFormat = b.contentFormat;
// Do some validation.
if (contentProperty != null || contentFormat == XmlFormat.VOID) {
if (! elements.isEmpty())
throw new BeanRuntimeException(c, "{0} and ELEMENT properties found on the same bean. These cannot be mixed.", contentFormat);
if (! collapsedProperties.isEmpty())
throw new BeanRuntimeException(c, "{0} and COLLAPSED properties found on the same bean. These cannot be mixed.", contentFormat);
}
if (! collapsedProperties.isEmpty()) {
if (! Collections.disjoint(elements.keySet(), collapsedProperties.keySet()))
throw new BeanRuntimeException(c, "Child element name conflicts found with another property.");
}
}
private static class XmlBeanMetaBuilder {
Map
attrs = new LinkedHashMap(),
elements = new LinkedHashMap(),
collapsedProperties = new LinkedHashMap();
BeanPropertyMeta
attrsProperty,
contentProperty;
XmlFormat contentFormat = DEFAULT;
XmlBeanMetaBuilder(BeanMeta> beanMeta) {
Class> c = beanMeta.getClassMeta().getInnerClass();
Xml xml = c.getAnnotation(Xml.class);
XmlFormat defaultFormat = null;
if (xml != null) {
XmlFormat xf = xml.format();
if (xf == ATTRS)
defaultFormat = XmlFormat.ATTR;
else if (xf.isOneOf(ELEMENTS, DEFAULT))
defaultFormat = ELEMENT;
else if (xf == VOID) {
contentFormat = VOID;
defaultFormat = VOID;
}
else
throw new BeanRuntimeException(c, "Invalid format specified in @Xml annotation on bean: {0}. Must be one of the following: DEFAULT,ATTRS,ELEMENTS,VOID", xml.format());
}
for (BeanPropertyMeta p : beanMeta.getPropertyMetas()) {
XmlFormat xf = p.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat();
ClassMeta> pcm = p.getClassMeta();
if (xf == ATTR) {
attrs.put(p.getName(), p);
} else if (xf == ELEMENT) {
elements.put(p.getName(), p);
} else if (xf == COLLAPSED) {
collapsedProperties.put(p.getName(), p);
} else if (xf == DEFAULT) {
if (defaultFormat == ATTR)
attrs.put(p.getName(), p);
else
elements.put(p.getName(), p);
} else if (xf == ATTRS) {
if (attrsProperty != null)
throw new BeanRuntimeException(c, "Multiple instances of ATTRS properties defined on class. Only one property can be designated as such.");
if (! pcm.isMapOrBean())
throw new BeanRuntimeException(c, "Invalid type for ATTRS property. Only properties of type Map and bean can be used.");
attrsProperty = p;
} else if (xf.isOneOf(ELEMENTS, MIXED, MIXED_PWS, TEXT, TEXT_PWS, XMLTEXT)) {
if (xf.isOneOf(ELEMENTS, MIXED, MIXED_PWS) && ! pcm.isCollectionOrArray())
throw new BeanRuntimeException(c, "Invalid type for {0} property. Only properties of type Collection and array can be used.", xf);
if (contentProperty != null) {
if (xf == contentFormat)
throw new BeanRuntimeException(c, "Multiple instances of {0} properties defined on class. Only one property can be designated as such.", xf);
throw new BeanRuntimeException(c, "{0} and {1} properties found on the same bean. Only one property can be designated as such.", contentFormat, xf);
}
contentProperty = p;
contentFormat = xf;
}
// Look for any properties that are collections with @Xml.childName specified.
String n = p.getExtendedMeta(XmlBeanPropertyMeta.class).getChildName();
if (n != null) {
if (collapsedProperties.containsKey(n) && collapsedProperties.get(n) != p)
throw new BeanRuntimeException(c, "Multiple properties found with the child name ''{0}''.", n);
collapsedProperties.put(n, p);
}
}
}
}
/**
* The list of properties that should be rendered as XML attributes.
*
* @return Map of property names to property metadata.
*/
protected Map getAttrProperties() {
return attrs;
}
/**
* The list of names of properties that should be rendered as XML attributes.
*
* @return Set of property names.
*/
protected Set getAttrPropertyNames() {
return attrs.keySet();
}
/**
* The list of properties that should be rendered as child elements.
*
* @return Map of property names to property metadata.
*/
protected Map getElementProperties() {
return elements;
}
/**
* The list of names of properties that should be rendered as child elements.
*
* @return Set of property names.
*/
protected Set getElementPropertyNames() {
return elements.keySet();
}
/**
* The list of properties that should be rendered as collapsed child elements.
* See {@link Xml#childName()}
*
* @return Map of property names to property metadata.
*/
protected Map getCollapsedProperties() {
return collapsedProperties;
}
/**
* The list of names of properties that should be rendered as collapsed child elements.
*
* @return Set of property names.
*/
protected Set getCollapsedPropertyNames() {
return collapsedProperties.keySet();
}
/**
* The property that returns a map of XML attributes as key/value pairs.
*
* @return The bean property metadata, or null if there is no such method.
*/
protected BeanPropertyMeta getAttrsProperty() {
return attrsProperty;
}
/**
* The name of the property that returns a map of XML attributes as key/value pairs.
*
* @return The bean property name, or null if there is no such method.
*/
protected String getAttrsPropertyName() {
return attrsProperty == null ? null : attrsProperty.getName();
}
/**
* The property that represents the inner XML content of this bean.
*
* @return The bean property metadata, or null if there is no such method.
*/
protected BeanPropertyMeta getContentProperty() {
return contentProperty;
}
/**
* The name of the property that represents the inner XML content of this bean.
*
* @return The bean property name, or null if there is no such method.
*/
protected String getContentPropertyName() {
return contentProperty == null ? null : contentProperty.getName();
}
/**
* Returns the format of the inner XML content of this bean.
*
*
* Can be one of the following:
*
* - {@link XmlFormat#ELEMENTS}
*
- {@link XmlFormat#MIXED}
*
- {@link XmlFormat#MIXED_PWS}
*
- {@link XmlFormat#TEXT}
*
- {@link XmlFormat#TEXT_PWS}
*
- {@link XmlFormat#XMLTEXT}
*
- {@link XmlFormat#VOID}
*
null
*
*
* @return The format of the inner XML content of this bean.
*/
protected XmlFormat getContentFormat() {
return contentFormat;
}
/**
* Returns bean property meta with the specified name.
*
*
* This is identical to calling {@link BeanMeta#getPropertyMeta(String)} except it first retrieves the bean property
* meta based on the child name (e.g. a property whose name is "people", but whose child name is "person").
*
* @param fieldName The bean property name.
* @return The property metadata.
*/
protected BeanPropertyMeta getPropertyMeta(String fieldName) {
if (collapsedProperties != null) {
BeanPropertyMeta bpm = collapsedProperties.get(fieldName);
if (bpm == null)
bpm = collapsedProperties.get("*");
if (bpm != null)
return bpm;
}
return getBeanMeta().getPropertyMeta(fieldName);
}
}