001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.databind.model;
007
008import java.lang.reflect.Array;
009import java.util.Arrays;
010import java.util.Collection;
011import java.util.Collections;
012import java.util.LinkedHashMap;
013import java.util.List;
014import java.util.Map;
015import java.util.function.Function;
016import java.util.stream.Collectors;
017
018import dev.metaschema.core.metapath.StaticContext;
019import dev.metaschema.core.model.AbstractModule;
020import dev.metaschema.core.model.IBoundObject;
021import dev.metaschema.core.model.ISource;
022import dev.metaschema.core.qname.IEnhancedQName;
023import dev.metaschema.core.util.ObjectUtils;
024import dev.metaschema.databind.IBindingContext;
025import dev.metaschema.databind.model.annotations.MetaschemaModule;
026import dev.metaschema.databind.model.annotations.NsBinding;
027import edu.umd.cs.findbugs.annotations.NonNull;
028import nl.talsmasoftware.lazy4j.Lazy;
029
030/**
031 * An abstract base class for Metaschema modules bound to Java classes.
032 * <p>
033 * This class provides the common implementation for modules that are defined
034 * through Java annotations on classes, enabling data binding between Metaschema
035 * module definitions and Java objects.
036 */
037public abstract class AbstractBoundModule
038    extends AbstractModule<
039        IBoundModule,
040        IBoundDefinitionModelComplex,
041        IBoundDefinitionFlag,
042        IBoundDefinitionModelField<?>,
043        IBoundDefinitionModelAssembly>
044    implements IBoundModule {
045  @NonNull
046  private final IBindingContext bindingContext;
047  @NonNull
048  private final Lazy<Map<Integer, IBoundDefinitionModelAssembly>> assemblyDefinitions;
049  @NonNull
050  private final Lazy<Map<Integer, IBoundDefinitionModelField<?>>> fieldDefinitions;
051  @NonNull
052  private final Lazy<StaticContext> staticContext;
053  @NonNull
054  private final ISource source;
055
056  /**
057   * Construct a new Module instance.
058   *
059   * @param importedModules
060   *          Module imports associated with the Metaschema module
061   * @param bindingContext
062   *          the Module binding context
063   */
064  protected AbstractBoundModule(
065      @NonNull List<? extends IBoundModule> importedModules,
066      @NonNull IBindingContext bindingContext) {
067    super(importedModules);
068    this.bindingContext = bindingContext;
069    this.assemblyDefinitions = ObjectUtils.notNull(Lazy.of(() -> Arrays.stream(getAssemblyClasses())
070        .map(clazz -> {
071          assert clazz != null;
072          return (IBoundDefinitionModelAssembly) ObjectUtils
073              .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz));
074        })
075        .collect(Collectors.toUnmodifiableMap(
076            def -> def.getDefinitionQName().getIndexPosition(),
077            Function.identity()))));
078    this.fieldDefinitions = ObjectUtils.notNull(Lazy.of(() -> Arrays.stream(getFieldClasses())
079        .map(clazz -> {
080          assert clazz != null;
081          return (IBoundDefinitionModelField<?>) ObjectUtils
082              .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz));
083        })
084        .collect(Collectors.toUnmodifiableMap(
085            def -> def.getDefinitionQName().getIndexPosition(),
086            Function.identity()))));
087    this.staticContext = ObjectUtils.notNull(Lazy.of(() -> {
088      StaticContext.Builder builder = StaticContext.builder()
089          .defaultModelNamespace(getXmlNamespace());
090
091      getNamespaceBindings()
092          .forEach(
093              (prefix, ns) -> builder.namespace(
094                  ObjectUtils.requireNonNull(prefix),
095                  ObjectUtils.requireNonNull(ns)));
096      return builder.build();
097    }));
098    this.source = ISource.moduleSource(this);
099  }
100
101  @Override
102  public ISource getSource() {
103    return source;
104  }
105
106  @Override
107  public String getLocationHint() {
108    return ObjectUtils.notNull(getClass().getName());
109  }
110
111  @Override
112  public StaticContext getModuleStaticContext() {
113    return ObjectUtils.notNull(staticContext.get());
114  }
115
116  @Override
117  @NonNull
118  public IBindingContext getBindingContext() {
119    return bindingContext;
120  }
121
122  @Override
123  public Map<String, String> getNamespaceBindings() {
124    return ObjectUtils.notNull(Arrays.stream(getNsBindings())
125        .collect(Collectors.toMap(
126            NsBinding::prefix,
127            NsBinding::uri,
128            (v1, v2) -> v2,
129            LinkedHashMap::new)));
130  }
131
132  /**
133   * Get the namespace binding annotations associated with this module.
134   *
135   * @return the namespace bindings array, or an empty array if none are defined
136   */
137  @SuppressWarnings({ "null" })
138  @NonNull
139  protected NsBinding[] getNsBindings() {
140    return getClass().isAnnotationPresent(MetaschemaModule.class)
141        ? getClass().getAnnotation(MetaschemaModule.class).nsBindings()
142        : (NsBinding[]) Array.newInstance(NsBinding.class, 0);
143  }
144
145  /**
146   * Get the assembly instance annotations associated with this bound choice
147   * group.
148   *
149   * @return the annotations
150   */
151  @SuppressWarnings({ "null", "unchecked" })
152  @NonNull
153  protected Class<? extends IBoundObject>[] getAssemblyClasses() {
154    return getClass().isAnnotationPresent(MetaschemaModule.class)
155        ? getClass().getAnnotation(MetaschemaModule.class).assemblies()
156        : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0);
157  }
158
159  /**
160   * Get the field instance annotations associated with this bound choice group.
161   *
162   * @return the annotations
163   */
164  @SuppressWarnings({ "null", "unchecked" })
165  @NonNull
166  protected Class<? extends IBoundObject>[] getFieldClasses() {
167    return getClass().isAnnotationPresent(MetaschemaModule.class)
168        ? getClass().getAnnotation(MetaschemaModule.class).fields()
169        : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0);
170  }
171
172  /**
173   * Get the mapping of assembly definition effective name to definition.
174   *
175   * @return the mapping
176   */
177  protected Map<Integer, IBoundDefinitionModelAssembly> getAssemblyDefinitionMap() {
178    return assemblyDefinitions.get();
179  }
180
181  @SuppressWarnings("null")
182  @Override
183  public Collection<IBoundDefinitionModelAssembly> getAssemblyDefinitions() {
184    return getAssemblyDefinitionMap().values();
185  }
186
187  @Override
188  public IBoundDefinitionModelAssembly getAssemblyDefinitionByName(@NonNull Integer name) {
189    return getAssemblyDefinitionMap().get(name);
190  }
191
192  /**
193   * Get the mapping of field definition effective name to definition.
194   *
195   * @return the mapping
196   */
197  protected Map<Integer, IBoundDefinitionModelField<?>> getFieldDefinitionMap() {
198    return fieldDefinitions.get();
199  }
200
201  @SuppressWarnings("null")
202  @Override
203  public Collection<IBoundDefinitionModelField<?>> getFieldDefinitions() {
204    return getFieldDefinitionMap().values();
205  }
206
207  @Override
208  public IBoundDefinitionModelField<?> getFieldDefinitionByName(@NonNull Integer name) {
209    return getFieldDefinitionMap().get(name);
210  }
211
212  @SuppressWarnings("null")
213  @Override
214  public Collection<IBoundDefinitionFlag> getFlagDefinitions() {
215    // Flags are always inline, so they do not have separate definitions
216    return Collections.emptyList();
217  }
218
219  @Override
220  public IBoundDefinitionFlag getFlagDefinitionByName(@NonNull IEnhancedQName name) {
221    // Flags are always inline, so they do not have separate definitions
222    return null;
223  }
224}