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