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.bean.openapi3;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022import static org.apache.juneau.internal.ConverterUtils.*;
023
024import java.util.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.commons.collections.*;
028
029/**
030 * Used to aid in serialization, deserialization, and validation.
031 *
032 * <p>
033 * The Discriminator Object is used to aid in serialization, deserialization, and validation. It adds support for
034 * polymorphism by allowing schemas to be discriminated based on the value of a specific property. This is particularly
035 * useful when working with inheritance hierarchies in object-oriented programming.
036 *
037 * <h5 class='section'>OpenAPI Specification:</h5>
038 * <p>
039 * The Discriminator Object is composed of the following fields:
040 * <ul class='spaced-list'>
041 *    <li><c>propertyName</c> (string, REQUIRED) - The name of the property in the payload that will hold the discriminator value
042 *    <li><c>mapping</c> (map of strings) - An object to hold mappings between payload values and schema names or references
043 * </ul>
044 *
045 * <h5 class='section'>Example:</h5>
046 * <p class='bcode'>
047 *    <jc>// Construct using SwaggerBuilder.</jc>
048 *    Discriminator <jv>x</jv> = <jsm>discriminator</jsm>()
049 *       .setPropertyName(<js>"petType"</js>)
050 *       .setMapping(<jsm>map</jsm>(<js>"dog"</js>, <js>"#/components/schemas/Dog"</js>, <js>"cat"</js>, <js>"#/components/schemas/Cat"</js>));
051 *
052 *    <jc>// Serialize using JsonSerializer.</jc>
053 *    String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
054 *
055 *    <jc>// Or just use toString() which does the same as above.</jc>
056 *    <jv>json</jv> = <jv>x</jv>.toString();
057 * </p>
058 * <p class='bcode'>
059 *    <jc>// Output</jc>
060 *    {
061 *       <js>"propertyName"</js>: <js>"petType"</js>,
062 *       <js>"mapping"</js>: {
063 *          <js>"dog"</js>: <js>"#/components/schemas/Dog"</js>,
064 *          <js>"cat"</js>: <js>"#/components/schemas/Cat"</js>
065 *       }
066 *    }
067 * </p>
068 *
069 * <h5 class='section'>See Also:</h5><ul>
070 *    <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#discriminator-object">OpenAPI Specification &gt; Discriminator Object</a>
071 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/">OpenAPI Inheritance and Polymorphism</a>
072 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
073 * </ul>
074 */
075public class Discriminator extends OpenApiElement {
076
077   private String propertyName;
078   private Map<String,String> mapping = map();
079
080   /**
081    * Default constructor.
082    */
083   public Discriminator() {}
084
085   /**
086    * Copy constructor.
087    *
088    * @param copyFrom The object to copy.
089    */
090   public Discriminator(Discriminator copyFrom) {
091      super(copyFrom);
092
093      this.propertyName = copyFrom.propertyName;
094      this.mapping = copyOf(copyFrom.mapping);
095   }
096
097   /**
098    * Adds one or more values to the <property>mapping</property> property.
099    *
100    * @param key The key.  Must not be <jk>null</jk>.
101    * @param value The value.  Must not be <jk>null</jk>.
102    * @return This object
103    */
104   public Discriminator addMapping(String key, String value) {
105      assertArgNotNull("key", key);
106      assertArgNotNull("value", value);
107      mapping.put(key, value);
108      return this;
109   }
110
111   /**
112    * Make a deep copy of this object.
113    *
114    * @return A deep copy of this object.
115    */
116   public Discriminator copy() {
117      return new Discriminator(this);
118   }
119
120   @Override /* Overridden from OpenApiElement */
121   public <T> T get(String property, Class<T> type) {
122      assertArgNotNull("property", property);
123      return switch (property) {
124         case "propertyName" -> toType(getPropertyName(), type);
125         case "mapping" -> toType(getMapping(), type);
126         default -> super.get(property, type);
127      };
128   }
129
130   /**
131    * Bean property getter:  <property>mapping</property>.
132    *
133    * <p>
134    * The URL for the target documentation.
135    *
136    * @return The property value, or <jk>null</jk> if it is not set.
137    */
138   public Map<String,String> getMapping() { return nullIfEmpty(mapping); }
139
140   /**
141    * Bean property getter:  <property>propertyName</property>.
142    *
143    * <p>
144    * A short description of the target documentation.
145    *
146    * @return The property value, or <jk>null</jk> if it is not set.
147    */
148   public String getPropertyName() { return propertyName; }
149
150   @Override /* Overridden from OpenApiElement */
151   public Set<String> keySet() {
152      // @formatter:off
153      var s = setb(String.class)
154         .addIf(ne(mapping), "mapping")
155         .addIf(nn(propertyName), "propertyName")
156         .build();
157      // @formatter:on
158      return new MultiSet<>(s, super.keySet());
159   }
160
161   @Override /* Overridden from OpenApiElement */
162   public Discriminator set(String property, Object value) {
163      assertArgNotNull("property", property);
164      return switch (property) {
165         case "mapping" -> setMapping(toMapBuilder(value, String.class, String.class).sparse().build());
166         case "propertyName" -> setPropertyName(s(value));
167         default -> {
168            super.set(property, value);
169            yield this;
170         }
171      };
172   }
173
174   /**
175    * Bean property setter:  <property>mapping</property>.
176    *
177    * <p>
178    * The URL for the target documentation.
179    *
180    * @param value
181    *    The new value for this property.
182    *    <br>Property value is required.
183    *    <br>URIs defined by {@link UriResolver} can be used for values.
184    *    <br>Can be <jk>null</jk> to unset the property.
185    * @return This object
186    */
187   public Discriminator setMapping(Map<String,String> value) {
188      mapping.clear();
189      if (nn(value))
190         mapping.putAll(value);
191      return this;
192   }
193
194   /**
195    * Bean property setter:  <property>propertyName</property>.
196    *
197    * <p>
198    * A short description of the target documentation.
199    *
200    * @param value
201    *    The new value for this property.
202    *    <br>Can be <jk>null</jk> to unset the property.
203    * @return This object
204    */
205   public Discriminator setPropertyName(String value) {
206      propertyName = value;
207      return this;
208   }
209
210   @Override /* Overridden from OpenApiElement */
211   public Discriminator strict() {
212      super.strict();
213      return this;
214   }
215
216   @Override /* Overridden from OpenApiElement */
217   public Discriminator strict(Object value) {
218      super.strict(value);
219      return this;
220   }
221}