001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.xml; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import org.apache.juneau.commons.bean.*; 023import org.apache.juneau.commons.collections.*; 024import org.apache.juneau.commons.reflect.*; 025import org.apache.juneau.marshall.*; 026 027/** 028 * Metadata on bean properties specific to the XML serializers and parsers pulled from the {@link Xml @Xml} annotation 029 * on the bean property. 030 * 031 * <h5 class='section'>See Also:</h5><ul> 032 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlSupport">XML</a> 033 * </ul> 034 */ 035public class XmlBeanPropertyMeta extends ExtendedBeanPropertyMeta { 036 037 /** 038 * Default instance. 039 */ 040 public static final XmlBeanPropertyMeta DEFAULT = new XmlBeanPropertyMeta(); 041 042 private Namespace namespace; 043 private XmlFormat xmlFormat = XmlFormat.DEFAULT; 044 private String childName; 045 private final XmlMetaProvider xmlMetaProvider; 046 047 /** 048 * Constructor. 049 * 050 * @param bpm The metadata of the bean property of this additional metadata. 051 * <br>Must not be <jk>null</jk>. 052 * @param mp XML metadata provider (for finding information about other artifacts). 053 * <br>Must not be <jk>null</jk>. 054 */ 055 public XmlBeanPropertyMeta(BeanPropertyMeta bpm, XmlMetaProvider mp) { 056 super(bpm); 057 this.xmlMetaProvider = mp; 058 059 bpm.getAnnotations(Xml.class).forEach(x -> findXmlInfo(x.inner(), ((ClassMeta<?>) bpm.getBeanInfo()).getMarshallingContext().getAnnotationProvider())); 060 061 if (namespace == null) 062 namespace = mp.getXmlClassMeta((ClassMeta<?>) bpm.getBeanMeta().getBeanInfo()).getNamespace(); 063 } 064 065 private XmlBeanPropertyMeta() { 066 super(null); 067 this.xmlMetaProvider = null; 068 } 069 070 /** 071 * Returns the child element of this property from the {@link Xml#childName} annotation on this bean property. 072 * 073 * @return The child element, or <jk>null</jk> if annotation not specified. 074 */ 075 public String getChildName() { return childName; } 076 077 /** 078 * Returns the XML namespace associated with this bean property. 079 * 080 * <p> 081 * Namespace is determined in the following order of {@link Xml#prefix() @Xml(prefix)} annotation: 082 * <ol> 083 * <li>Bean property field. 084 * <li>Bean getter. 085 * <li>Bean setter. 086 * <li>Bean class. 087 * <li>Bean package. 088 * <li>Bean superclasses. 089 * <li>Bean superclass packages. 090 * <li>Bean interfaces. 091 * <li>Bean interface packages. 092 * </ol> 093 * 094 * @return The namespace associated with this bean property, or <jk>null</jk> if no namespace is associated with it. 095 */ 096 public Namespace getNamespace() { return namespace; } 097 098 /** 099 * Returns the XML format of this property from the {@link Xml#format} annotation on this bean property. 100 * 101 * @return The XML format, or {@link XmlFormat#DEFAULT} if annotation not specified. 102 */ 103 public XmlFormat getXmlFormat() { return xmlFormat; } 104 105 @SuppressWarnings({ 106 "java:S1172", // mp unused here; accepted to match AnnotationProvider-based caller pattern 107 "java:S3776", // Cognitive complexity acceptable for XML format detection from annotations 108 "unused" // mp unused here; accepted to match AnnotationProvider-based caller pattern 109 }) 110 private void findXmlInfo(Xml xml, AnnotationProvider mp) { 111 if (xml == null) 112 return; 113 var bpm = getBeanPropertyMeta(); 114 var cmProperty = (ClassMeta<?>) bpm.getBeanInfo(); 115 var cmBean = (ClassMeta<?>) bpm.getBeanMeta().getBeanInfo(); 116 var name = bpm.getName(); 117 118 var ap = cmProperty.getMarshallingContext().getAnnotationProvider(); 119 var xmls = new MultiList<>( 120 rstream(ap.find(Xml.class, cmBean)).map(AnnotationInfo::inner).toList(), 121 reverse(bpm.getAnnotations(Xml.class).map(AnnotationInfo::inner).toList()) 122 ); 123 var schemas = new MultiList<>( 124 rstream(ap.find(XmlSchema.class, cmBean)).map(AnnotationInfo::inner).toList(), 125 reverse(bpm.getAnnotations(XmlSchema.class).map(AnnotationInfo::inner).toList()) 126 ); 127 namespace = XmlUtils.findNamespace(xmls, schemas); 128 129 if (xmlFormat == XmlFormat.DEFAULT) 130 xmlFormat = xml.format(); 131 132 boolean isCollection = cmProperty.isCollectionOrArray(); 133 134 String cen = xml.childName(); 135 if ((! cen.isEmpty()) && (! isCollection)) 136 throw brex(cmProperty.inner(), "Annotation error on property '%s'. @Xml.childName can only be specified on collections and arrays.", name); 137 138 if (xmlFormat == XmlFormat.COLLAPSED) { 139 if (isCollection) { 140 if (cen.isEmpty() && nn(xmlMetaProvider)) 141 cen = xmlMetaProvider.getXmlClassMeta(cmProperty).getChildName(); 142 if (ie(cen)) 143 cen = cmProperty.getElementType().getBeanDictionaryName(); 144 if (ie(cen)) 145 cen = name; 146 } else { 147 throw brex(cmBean.inner(), "Annotation error on property '%s'. @Xml.format=COLLAPSED can only be specified on collections and arrays.", name); 148 } 149 if (cen.isEmpty()) 150 cen = cmProperty.getBeanDictionaryName(); 151 } 152 153 if (! cen.isEmpty()) 154 childName = cen; 155 } 156}