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 }