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}