001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.databind.model.info;
007
008import java.lang.reflect.Constructor;
009import java.lang.reflect.InvocationTargetException;
010import java.util.Map;
011import java.util.function.Supplier;
012
013import dev.metaschema.core.model.IBoundObject;
014import dev.metaschema.core.model.IMetaschemaData;
015import dev.metaschema.core.util.ObjectUtils;
016import dev.metaschema.databind.io.BindingException;
017import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
018import dev.metaschema.databind.model.IBoundProperty;
019import edu.umd.cs.findbugs.annotations.NonNull;
020import edu.umd.cs.findbugs.annotations.Nullable;
021
022/**
023 * A feature interface for handling complex item values during binding
024 * operations.
025 * <p>
026 * Complex items are bound to Java classes and can contain flags and other
027 * nested model content.
028 */
029public interface IFeatureComplexItemValueHandler extends IItemValueHandler<IBoundObject> {
030  /**
031   * Get the Metaschema definition representing the bound complex data.
032   *
033   * @return the definition
034   */
035  @NonNull
036  IBoundDefinitionModelComplex getDefinition();
037
038  // /**
039  // * Get the name of the JSON key, if a JSON key is configured.
040  // *
041  // * @return the name of the JSON key flag if configured, or {@code null}
042  // * otherwise
043  // */
044  // @Nullable
045  // String getJsonKeyFlagName();
046
047  /**
048   * Get the mapping of JSON property names to property bindings.
049   *
050   * @return the mapping
051   */
052  // REFACTOR: move JSON-specific methods to a binding cache implementation
053  @NonNull
054  Map<String, IBoundProperty<?>> getJsonProperties();
055
056  // REFACTOR: flatten implementations?
057  @Override
058  @NonNull
059  IBoundObject deepCopyItem(
060      @NonNull IBoundObject item,
061      @Nullable IBoundObject parentInstance) throws BindingException;
062
063  /**
064   * The class this binding is to.
065   *
066   * @return the bound class
067   */
068  @NonNull
069  Class<? extends IBoundObject> getBoundClass();
070
071  /**
072   * Gets a new instance of the bound class.
073   *
074   * @param <CLASS>
075   *          the type of the bound class
076   * @param supplier
077   *          the metaschema data generator used to capture parse information
078   *          (i.e., location)
079   * @return a Java object for the class
080   * @throws RuntimeException
081   *           if the instance cannot be created due to a binding error
082   */
083  @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
084  @NonNull
085  default <CLASS extends IBoundObject> CLASS newInstance(@Nullable Supplier<IMetaschemaData> supplier) {
086    Class<?> clazz = getBoundClass();
087    try {
088      CLASS retval;
089      if (supplier != null) {
090        @SuppressWarnings("unchecked")
091        Constructor<CLASS> constructor
092            = (Constructor<CLASS>) clazz.getDeclaredConstructor(IMetaschemaData.class);
093        retval = constructor.newInstance(supplier.get());
094      } else {
095        @SuppressWarnings("unchecked")
096        Constructor<CLASS> constructor
097            = (Constructor<CLASS>) clazz.getDeclaredConstructor();
098        retval = constructor.newInstance();
099      }
100      return ObjectUtils.notNull(retval);
101    } catch (NoSuchMethodException ex) {
102      String msg = String.format("Class '%s' does not have a required no-arg constructor.", clazz.getName());
103      throw new RuntimeException(msg, ex);
104    } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
105      throw new RuntimeException(ex);
106    }
107  }
108
109  /**
110   * Invoke the before-deserialize lifecycle callback on the target object.
111   *
112   * @param targetObject
113   *          the object being deserialized
114   * @param parentObject
115   *          the parent object containing the target, or {@code null} if there is
116   *          no parent
117   * @throws BindingException
118   *           if an error occurs during the callback
119   */
120  void callBeforeDeserialize(
121      @NonNull IBoundObject targetObject,
122      @Nullable IBoundObject parentObject) throws BindingException;
123
124  /**
125   * Invoke the after-deserialize lifecycle callback on the target object.
126   *
127   * @param targetObject
128   *          the object that was deserialized
129   * @param parentObject
130   *          the parent object containing the target, or {@code null} if there is
131   *          no parent
132   * @throws BindingException
133   *           if an error occurs during the callback
134   */
135  void callAfterDeserialize(
136      @NonNull IBoundObject targetObject,
137      @Nullable IBoundObject parentObject) throws BindingException;
138}