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