001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.model;
007
008import org.apache.logging.log4j.LogManager;
009import org.apache.logging.log4j.Logger;
010
011import java.util.LinkedHashMap;
012import java.util.LinkedList;
013import java.util.List;
014import java.util.Map;
015
016import dev.metaschema.core.model.impl.DefaultContainerFlagSupport;
017import dev.metaschema.core.qname.EQNameFactory;
018import dev.metaschema.core.util.CollectionUtil;
019import dev.metaschema.core.util.CustomCollectors;
020import dev.metaschema.core.util.ObjectUtils;
021import edu.umd.cs.findbugs.annotations.NonNull;
022import edu.umd.cs.findbugs.annotations.Nullable;
023
024/**
025 * Default implementation of {@link IFlagContainerBuilder}.
026 * <p>
027 * This builder collects flag instances and constructs an immutable flag
028 * container. It handles duplicate flags (shadowing) by logging errors and using
029 * the last instance encountered. An optional JSON key flag can be specified.
030 *
031 * @param <T>
032 *          the Java type of flag instances
033 */
034public class FlagContainerBuilder<T extends IFlagInstance> implements IFlagContainerBuilder<T> {
035  private static final Logger LOGGER = LogManager.getLogger(FlagContainerBuilder.class);
036
037  @Nullable
038  private final Integer jsonKeyIndex;
039  @NonNull
040  private final List<T> flags;
041
042  /**
043   * Construct a new flag container using the provided flag qualified name as the
044   * JSON key.
045   *
046   * @param jsonKeyIndex
047   *          the qualified name index of the JSON key or {@code null} if no JSON
048   *          key is configured
049   */
050  public FlagContainerBuilder(@Nullable Integer jsonKeyIndex) {
051    this.jsonKeyIndex = jsonKeyIndex;
052    this.flags = new LinkedList<>();
053  }
054
055  @Override
056  @NonNull
057  public IFlagContainerBuilder<T> flag(@NonNull T instance) {
058    flags.add(instance);
059    return this;
060  }
061
062  @Override
063  public IContainerFlagSupport<T> build() {
064    IContainerFlagSupport<T> retval;
065    if (flags.isEmpty()) {
066      retval = IContainerFlagSupport.empty();
067    } else {
068      Map<Integer, T> flagMap = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(flags.stream()
069          .collect(
070              CustomCollectors.toMap(
071                  flag -> flag.getQName().getIndexPosition(),
072                  CustomCollectors.identity(),
073                  FlagContainerBuilder::handleShadowedInstances,
074                  LinkedHashMap::new))));
075
076      T jsonKey = jsonKeyIndex == null ? null : flagMap.get(jsonKeyIndex);
077      retval = new DefaultContainerFlagSupport<>(flagMap, jsonKey);
078    }
079    return retval;
080  }
081
082  @SuppressWarnings("unused")
083  private static <INSTANCE extends IFlagInstance> INSTANCE handleShadowedInstances(
084      @NonNull Integer keyIndex,
085      @NonNull INSTANCE shadowed,
086      @NonNull INSTANCE shadowing) {
087    if (!shadowed.equals(shadowing) && LOGGER.isErrorEnabled()) {
088      IModelDefinition owningDefinition = shadowing.getContainingDefinition();
089      IModule module = owningDefinition.getContainingModule();
090      LOGGER.error("Unexpected duplicate flag instance name '%s' in definition '%s' in module name '%s' at '%s'",
091          EQNameFactory.instance().get(keyIndex).or(null),
092          owningDefinition.getDefinitionQName(),
093          module.getShortName(),
094          module.getLocation());
095    }
096    return shadowing;
097  }
098}