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}