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 javax.xml.namespace.QName;
29  
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  import edu.umd.cs.findbugs.annotations.Nullable;
32  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
33  
34  public class FlagContainerSupport implements IContainerFlagSupport<IBoundInstanceFlag> {
35    @NonNull
36    private final Map<QName, IBoundInstanceFlag> flagInstances;
37    @Nullable
38    private IBoundInstanceFlag jsonKeyFlag;
39  
40    @SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
41    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
42    public FlagContainerSupport(
43        @NonNull IBoundDefinitionModelComplex definition,
44        @Nullable Consumer<IBoundInstanceFlag> peeker) {
45      Class<?> clazz = definition.getBoundClass();
46  
47      Stream<IBoundInstanceFlag> instances = getFlagInstanceFields(clazz).stream()
48          .flatMap(field -> {
49            Stream<IBoundInstanceFlag> stream;
50            if (field.isAnnotationPresent(BoundFlag.class)) {
51              stream = Stream.of(IBoundInstanceFlag.newInstance(field, definition));
52            } else {
53              stream = Stream.empty();
54            }
55            return stream;
56          });
57  
58      Consumer<IBoundInstanceFlag> intermediate = this::handleFlagInstance;
59  
60      if (peeker != null) {
61        intermediate = intermediate.andThen(peeker);
62      }
63  
64      this.flagInstances = CollectionUtil.unmodifiableMap(ObjectUtils.notNull(instances
65          .peek(intermediate)
66          .collect(Collectors.toMap(
67              IBoundInstanceFlag::getXmlQName,
68              Function.identity(),
69              (v1, v2) -> v2,
70              LinkedHashMap::new))));
71    }
72  
73    /**
74     * Collect all fields that are flag instances on this class.
75     *
76     * @param clazz
77     *          the class
78     * @return an immutable collection of flag instances
79     */
80    @SuppressWarnings("PMD.UseArraysAsList")
81    @NonNull
82    protected static Collection<Field> getFlagInstanceFields(Class<?> clazz) {
83      Field[] fields = clazz.getDeclaredFields();
84  
85      List<Field> retval = new LinkedList<>();
86  
87      Class<?> superClass = clazz.getSuperclass();
88      if (superClass != null) {
89        // get flags from superclass
90        retval.addAll(getFlagInstanceFields(superClass));
91      }
92  
93      for (Field field : fields) {
94        if (!field.isAnnotationPresent(BoundFlag.class) || field.isAnnotationPresent(Ignore.class)) {
95          // skip this field, since it is ignored
96          continue;
97        }
98  
99        retval.add(field);
100     }
101     return ObjectUtils.notNull(Collections.unmodifiableCollection(retval));
102   }
103 
104   /**
105    * Used to delegate flag instance initialization to subclasses.
106    *
107    * @param instance
108    *          the flag instance to process
109    */
110   protected void handleFlagInstance(IBoundInstanceFlag instance) {
111     if (instance.isJsonKey()) {
112       this.jsonKeyFlag = instance;
113     }
114   }
115 
116   @Override
117   @NonNull
118   public Map<QName, IBoundInstanceFlag> getFlagInstanceMap() {
119     return flagInstances;
120   }
121 
122   @Override
123   public IBoundInstanceFlag getJsonKeyFlagInstance() {
124     return jsonKeyFlag;
125   }
126 }