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