1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.model.constraint;
7   
8   import gov.nist.secauto.metaschema.core.model.constraint.impl.DefaultAllowedValuesConstraint;
9   import gov.nist.secauto.metaschema.core.util.ObjectUtils;
10  
11  import java.util.LinkedHashMap;
12  import java.util.Map;
13  
14  import edu.umd.cs.findbugs.annotations.NonNull;
15  import edu.umd.cs.findbugs.annotations.Nullable;
16  
17  /**
18   * Represents a rule requiring the value of a field or flag to match the name of
19   * one entry in a set of enumerated values.
20   */
21  public interface IAllowedValuesConstraint extends IConstraint {
22    boolean ALLOW_OTHER_DEFAULT = false;
23    @NonNull
24    Extensible EXTENSIBLE_DEFAULT = Extensible.EXTERNAL;
25  
26    /**
27     * Indicates how an allowed values constraint can be extended, or if it can be.
28     */
29    enum Extensible {
30      /**
31       * Can be extended by external constraints. The most permissive level.
32       */
33      EXTERNAL,
34      /**
35       * Can be extended by constraints in the same model.
36       */
37      MODEL,
38      /**
39       * Cannot be extended. The most restrictive level.
40       */
41      NONE;
42    }
43  
44    /**
45     * Get the collection allowed values associated with this constraint.
46     *
47     * @return a mapping of value to the associated {@link IAllowedValue} item
48     */
49    @NonNull
50    Map<String, ? extends IAllowedValue> getAllowedValues();
51  
52    /**
53     * Get a specific allowed value by name, if it is defined for this constraint.
54     *
55     * @param name
56     *          the value name
57     * @return the allowed value or {@code null} if the value is not defined
58     */
59    @Nullable
60    default IAllowedValue getAllowedValue(String name) {
61      return getAllowedValues().get(name);
62    }
63  
64    /**
65     * Determines if this allowed value constraint is open-ended ({@code true}) or
66     * closed. If "open-ended", the constraint allows the target's value to by any
67     * additional unspecified value. If "closed", the constraint requries the
68     * target's value to be one of the specified values.
69     *
70     * @return {@code true} if the constraint is "open-ended", or {@code false}
71     *         otherwise
72     */
73    boolean isAllowedOther();
74  
75    /**
76     * Determines the degree to which this constraint can be extended by other
77     * constraints applied to the same value.
78     *
79     * @return the enumeration value
80     */
81    @NonNull
82    Extensible getExtensible();
83  
84    @Override
85    default <T, R> R accept(IConstraintVisitor<T, R> visitor, T state) {
86      return visitor.visitAllowedValues(this, state);
87    }
88  
89    /**
90     * Create a new constraint builder.
91     *
92     * @return the builder
93     */
94    @NonNull
95    static Builder builder() {
96      return new Builder();
97    }
98  
99    final class Builder
100       extends AbstractConstraintBuilder<Builder, IAllowedValuesConstraint> {
101     @NonNull
102     private final Map<String, IAllowedValue> allowedValues = new LinkedHashMap<>(); // NOPMD not thread safe
103     private boolean allowedOther = ALLOW_OTHER_DEFAULT;
104     @NonNull
105     private Extensible extensible = EXTENSIBLE_DEFAULT;
106 
107     private Builder() {
108       // disable construction
109     }
110 
111     /**
112      * Use the provided allowed value to validate associated values.
113      *
114      * @param allowedValue
115      *          an expected allowed value
116      * @return this builder
117      */
118     @NonNull
119     public Builder allowedValue(@NonNull IAllowedValue allowedValue) {
120       this.allowedValues.put(allowedValue.getValue(), allowedValue);
121       return this;
122     }
123 
124     /**
125      * Use the provided allowed values to validate associated values.
126      *
127      * @param allowedValues
128      *          an expected allowed values
129      * @return this builder
130      */
131     @NonNull
132     public Builder allowedValues(@NonNull Map<String, IAllowedValue> allowedValues) {
133       this.allowedValues.putAll(allowedValues);
134       return this;
135     }
136 
137     /**
138      * Determine if unspecified values are allowed and will result in the constraint
139      * always passing.
140      *
141      * @param bool
142      *          {@code true} if other values are allowed or {@code false} otherwise
143      * @return this builder
144      */
145     @NonNull
146     public Builder allowsOther(boolean bool) {
147       this.allowedOther = bool;
148       return this;
149     }
150 
151     /**
152      * Determine the allowed scope of extension for other constraints matching this
153      * constraint's target.
154      *
155      * @param extensible
156      *          the degree of allowed extension
157      * @return this builder
158      */
159     @NonNull
160     public Builder extensible(@NonNull Extensible extensible) {
161       this.extensible = extensible;
162       return this;
163     }
164 
165     @Override
166     protected Builder getThis() {
167       return this;
168     }
169 
170     @NonNull
171     private Map<String, IAllowedValue> getAllowedValues() {
172       return allowedValues;
173     }
174 
175     private boolean isAllowedOther() {
176       return allowedOther;
177     }
178 
179     @NonNull
180     private Extensible getExtensible() {
181       return extensible;
182     }
183 
184     @Override
185     protected IAllowedValuesConstraint newInstance() {
186       return new DefaultAllowedValuesConstraint(
187           getId(),
188           getFormalName(),
189           getDescription(),
190           ObjectUtils.notNull(getSource()),
191           getLevel(),
192           getTarget(),
193           getProperties(),
194           getAllowedValues(),
195           isAllowedOther(),
196           getExtensible(),
197           getRemarks());
198     }
199   }
200 }