1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind.model.impl;
7   
8   import java.lang.reflect.Field;
9   import java.util.Collection;
10  import java.util.Collections;
11  import java.util.LinkedHashMap;
12  import java.util.LinkedList;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.function.Consumer;
16  import java.util.function.Function;
17  import java.util.stream.Collectors;
18  import java.util.stream.Stream;
19  
20  import dev.metaschema.core.model.IContainerFlagSupport;
21  import dev.metaschema.core.util.CollectionUtil;
22  import dev.metaschema.core.util.ObjectUtils;
23  import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
24  import dev.metaschema.databind.model.IBoundInstanceFlag;
25  import dev.metaschema.databind.model.annotations.BoundFlag;
26  import dev.metaschema.databind.model.annotations.Ignore;
27  import edu.umd.cs.findbugs.annotations.NonNull;
28  import edu.umd.cs.findbugs.annotations.Nullable;
29  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
30  
31  public class FlagContainerSupport implements IContainerFlagSupport<IBoundInstanceFlag> {
32    @NonNull
33    private final Map<Integer, IBoundInstanceFlag> flagInstances;
34    @Nullable
35    private IBoundInstanceFlag jsonKeyFlag;
36  
37    /**
38     * Constructs a new flag container by introspecting the bound class for flag
39     * instances.
40     * <p>
41     * This constructor scans the definition's bound class and its superclasses for
42     * fields annotated with {@link BoundFlag}, creating flag instances for each.
43     *
44     * @param definition
45     *          the complex model definition whose bound class will be introspected
46     * @param peeker
47     *          an optional consumer to receive each flag instance as it is
48     *          processed, or {@code null} if no additional processing is needed
49     */
50    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
51    public FlagContainerSupport(
52        @NonNull IBoundDefinitionModelComplex definition,
53        @Nullable Consumer<IBoundInstanceFlag> peeker) {
54      Class<?> clazz = definition.getBoundClass();
55  
56      Stream<IBoundInstanceFlag> instances = getFlagInstanceFields(clazz).stream()
57          .flatMap(field -> {
58            Stream<IBoundInstanceFlag> stream;
59            if (field.isAnnotationPresent(BoundFlag.class)) {
60              stream = Stream.of(IBoundInstanceFlag.newInstance(field, definition));
61            } else {
62              stream = Stream.empty();
63            }
64            return stream;
65          });
66  
67      Consumer<IBoundInstanceFlag> intermediate = this::handleFlagInstance;
68  
69      if (peeker != null) {
70        intermediate = intermediate.andThen(peeker);
71      }
72  
73      this.flagInstances = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(instances
74          .peek(intermediate)
75          .collect(Collectors.toMap(
76              flag -> flag.getQName().getIndexPosition(),
77              Function.identity(),
78              (v1, v2) -> v2,
79              LinkedHashMap::new))));
80    }
81  
82    /**
83     * Collect all fields that are flag instances on this class.
84     *
85     * @param clazz
86     *          the class
87     * @return an immutable collection of flag instances
88     */
89    @NonNull
90    protected static Collection<Field> getFlagInstanceFields(Class<?> clazz) {
91      Field[] fields = clazz.getDeclaredFields();
92  
93      List<Field> retval = new LinkedList<>();
94  
95      Class<?> superClass = clazz.getSuperclass();
96      if (superClass != null) {
97        // get flags from superclass
98        retval.addAll(getFlagInstanceFields(superClass));
99      }
100 
101     for (Field field : fields) {
102       if (!field.isAnnotationPresent(BoundFlag.class) || field.isAnnotationPresent(Ignore.class)) {
103         // skip this field, since it is ignored
104         continue;
105       }
106 
107       retval.add(field);
108     }
109     return ObjectUtils.notNull(Collections.unmodifiableCollection(retval));
110   }
111 
112   /**
113    * Used to delegate flag instance initialization to subclasses.
114    *
115    * @param instance
116    *          the flag instance to process
117    */
118   protected void handleFlagInstance(IBoundInstanceFlag instance) {
119     if (instance.isJsonKey()) {
120       this.jsonKeyFlag = instance;
121     }
122   }
123 
124   @Override
125   @NonNull
126   public Map<Integer, IBoundInstanceFlag> getFlagInstanceMap() {
127     return flagInstances;
128   }
129 
130   @Override
131   public IBoundInstanceFlag getJsonKeyFlagInstance() {
132     return jsonKeyFlag;
133   }
134 }