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.xml;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.commons.collections.*;
025import org.apache.juneau.commons.reflect.*;
026import org.apache.juneau.xml.annotation.*;
027
028/**
029 * Metadata on bean properties specific to the XML serializers and parsers pulled from the {@link Xml @Xml} annotation
030 * on the bean property.
031 *
032 * <h5 class='section'>See Also:</h5><ul>
033 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlBasics">XML Basics</a>
034 * </ul>
035 */
036public class XmlBeanPropertyMeta extends ExtendedBeanPropertyMeta {
037
038   /**
039    * Default instance.
040    */
041   public static final XmlBeanPropertyMeta DEFAULT = new XmlBeanPropertyMeta();
042
043   private Namespace namespace;
044   private XmlFormat xmlFormat = XmlFormat.DEFAULT;
045   private String childName;
046   private final XmlMetaProvider xmlMetaProvider;
047
048   /**
049    * Constructor.
050    *
051    * @param bpm The metadata of the bean property of this additional metadata.
052    * @param mp XML metadata provider (for finding information about other artifacts).
053    */
054   public XmlBeanPropertyMeta(BeanPropertyMeta bpm, XmlMetaProvider mp) {
055      super(bpm);
056      this.xmlMetaProvider = mp;
057
058      bpm.getAnnotations(Xml.class).forEach(x -> findXmlInfo(x.inner(), bpm.getClassMeta().getBeanContext().getAnnotationProvider()));
059
060      if (namespace == null)
061         namespace = mp.getXmlClassMeta(bpm.getBeanMeta().getClassMeta()).getNamespace();
062   }
063
064   private XmlBeanPropertyMeta() {
065      super(null);
066      this.xmlMetaProvider = null;
067   }
068
069   /**
070    * Returns the child element of this property from the {@link Xml#childName} annotation on this bean property.
071    *
072    * @return The child element, or <jk>null</jk> if annotation not specified.
073    */
074   public String getChildName() { return childName; }
075
076   /**
077    * Returns the XML namespace associated with this bean property.
078    *
079    * <p>
080    * Namespace is determined in the following order of {@link Xml#prefix() @Xml(prefix)} annotation:
081    * <ol>
082    *    <li>Bean property field.
083    *    <li>Bean getter.
084    *    <li>Bean setter.
085    *    <li>Bean class.
086    *    <li>Bean package.
087    *    <li>Bean superclasses.
088    *    <li>Bean superclass packages.
089    *    <li>Bean interfaces.
090    *    <li>Bean interface packages.
091    * </ol>
092    *
093    * @return The namespace associated with this bean property, or <jk>null</jk> if no namespace is associated with it.
094    */
095   public Namespace getNamespace() { return namespace; }
096
097   /**
098    * Returns the XML format of this property from the {@link Xml#format} annotation on this bean property.
099    *
100    * @return The XML format, or {@link XmlFormat#DEFAULT} if annotation not specified.
101    */
102   public XmlFormat getXmlFormat() { return xmlFormat; }
103
104   private void findXmlInfo(Xml xml, AnnotationProvider mp) {
105      if (xml == null)
106         return;
107      var bpm = getBeanPropertyMeta();
108      var cmProperty = bpm.getClassMeta();
109      var cmBean = bpm.getBeanMeta().getClassMeta();
110      var name = bpm.getName();
111
112      var ap = bpm.getClassMeta().getBeanContext().getAnnotationProvider();
113      var xmls = new MultiList<>(
114         rstream(ap.find(Xml.class, bpm.getBeanMeta().getClassMeta())).map(AnnotationInfo::inner).toList(),
115         reverse(bpm.getAnnotations(Xml.class).map(AnnotationInfo::inner).toList())
116      );
117      var schemas = new MultiList<>(
118         rstream(ap.find(XmlSchema.class, bpm.getBeanMeta().getClassMeta())).map(AnnotationInfo::inner).toList(),
119         reverse(bpm.getAnnotations(XmlSchema.class).map(AnnotationInfo::inner).toList())
120      );
121      namespace = XmlUtils.findNamespace(xmls, schemas);
122
123      if (xmlFormat == XmlFormat.DEFAULT)
124         xmlFormat = xml.format();
125
126      boolean isCollection = cmProperty.isCollectionOrArray();
127
128      String cen = xml.childName();
129      if ((! cen.isEmpty()) && (! isCollection))
130         throw bex(cmProperty.inner(), "Annotation error on property ''{0}''.  @Xml.childName can only be specified on collections and arrays.", name);
131
132      if (xmlFormat == XmlFormat.COLLAPSED) {
133         if (isCollection) {
134            if (cen.isEmpty() && nn(xmlMetaProvider))
135               cen = xmlMetaProvider.getXmlClassMeta(cmProperty).getChildName();
136            if (cen == null || cen.isEmpty())
137               cen = cmProperty.getElementType().getBeanDictionaryName();
138            if (cen == null || cen.isEmpty())
139               cen = name;
140         } else {
141            throw bex(cmBean.inner(), "Annotation error on property ''{0}''.  @Xml.format=COLLAPSED can only be specified on collections and arrays.", name);
142         }
143         if (cen.isEmpty() && isCollection)
144            cen = cmProperty.getBeanDictionaryName();
145      }
146
147      if (! cen.isEmpty())
148         childName = cen;
149   }
150}