// *************************************************************************************************************************** | |
// * 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); | |
} | |
} |