blob: e92da243a28143aed588326188f12e8bcbc1a4ce [file] [log] [blame]
// ***************************************************************************************************************************
// * 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.internal.CollectionUtils.*;
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 ExtendedBeanMeta {
// XML related fields
private final Map<String,BeanPropertyMeta> attrs; // Map of bean properties that are represented as XML attributes.
private final Map<String,BeanPropertyMeta> 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<String,BeanPropertyMeta> 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.
* @param mp XML metadata provider (for finding information about other artifacts).
*/
public XmlBeanMeta(BeanMeta<?> beanMeta, XmlMetaProvider mp) {
super(beanMeta);
Class<?> c = beanMeta.getClassMeta().getInnerClass();
XmlBeanMetaBuilder b = new XmlBeanMetaBuilder(beanMeta, mp);
attrs = unmodifiableMap(b.attrs);
elements = unmodifiableMap(b.elements);
attrsProperty = b.attrsProperty;
collapsedProperties = 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<String,BeanPropertyMeta>
attrs = new LinkedHashMap<>(),
elements = new LinkedHashMap<>(),
collapsedProperties = new LinkedHashMap<>();
BeanPropertyMeta
attrsProperty,
contentProperty;
XmlFormat contentFormat = DEFAULT;
XmlBeanMetaBuilder(BeanMeta<?> beanMeta, XmlMetaProvider mp) {
Class<?> c = beanMeta.getClassMeta().getInnerClass();
Xml xml = mp.getAnnotation(Xml.class, c);
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 = mp.getXmlBeanPropertyMeta(p).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 = mp.getXmlBeanPropertyMeta(p).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.
*/
public Map<String,BeanPropertyMeta> getAttrProperties() {
return attrs;
}
/**
* The list of names of properties that should be rendered as XML attributes.
*
* @return Set of property names.
*/
protected Set<String> 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<String,BeanPropertyMeta> getElementProperties() {
return elements;
}
/**
* The list of names of properties that should be rendered as child elements.
*
* @return Set of property names.
*/
protected Set<String> getElementPropertyNames() {
return elements.keySet();
}
/**
* The list of properties that should be rendered as collapsed child elements.
* <br>See {@link Xml#childName() @Xml(childName)}
*
* @return Map of property names to property metadata.
*/
protected Map<String,BeanPropertyMeta> getCollapsedProperties() {
return collapsedProperties;
}
/**
* The list of names of properties that should be rendered as collapsed child elements.
*
* @return Set of property names.
*/
protected Set<String> getCollapsedPropertyNames() {
return collapsedProperties.keySet();
}
/**
* The property that returns a map of XML attributes as key/value pairs.
*
* @return The bean property metadata, or <jk>null</jk> 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 <jk>null</jk> 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 <jk>null</jk> if there is no such method.
*/
public BeanPropertyMeta getContentProperty() {
return contentProperty;
}
/**
* The name of the property that represents the inner XML content of this bean.
*
* @return The bean property name, or <jk>null</jk> 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.
*
* <p>
* Can be one of the following:
* <ul>
* <li>{@link XmlFormat#ELEMENTS}
* <li>{@link XmlFormat#MIXED}
* <li>{@link XmlFormat#MIXED_PWS}
* <li>{@link XmlFormat#TEXT}
* <li>{@link XmlFormat#TEXT_PWS}
* <li>{@link XmlFormat#XMLTEXT}
* <li>{@link XmlFormat#VOID}
* <li><jk>null</jk>
* </ul>
*
* @return The format of the inner XML content of this bean.
*/
public XmlFormat getContentFormat() {
return contentFormat;
}
/**
* Returns bean property meta with the specified name.
*
* <p>
* 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);
}
}