1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.model.constraint;
7   
8   import dev.metaschema.core.datatype.markup.MarkupMultiline;
9   import dev.metaschema.core.metapath.DynamicContext;
10  import dev.metaschema.core.metapath.IMetapathExpression;
11  import dev.metaschema.core.metapath.MetapathException;
12  import dev.metaschema.core.metapath.item.ISequence;
13  import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
14  import dev.metaschema.core.model.IAttributable;
15  import dev.metaschema.core.model.IDescribable;
16  import dev.metaschema.core.model.ISource;
17  import dev.metaschema.core.util.ObjectUtils;
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  /**
22   * Represents a rule constraining the model of a Metaschema assembly, field or
23   * flag. Provides a common interface for all constraint definitions.
24   */
25  public interface IConstraint extends IAttributable, IDescribable {
26    /**
27     * The type of constraint.
28     */
29    enum Type {
30      /** Constraint restricting the set of allowed values. */
31      ALLOWED_VALUES("allowed-values"),
32      /** Constraint specifying occurrence requirements. */
33      CARDINALITY("cardinality"),
34      /** Constraint expressing an expected condition. */
35      EXPECT("expect"),
36      /** Constraint creating an index over items. */
37      INDEX("index"),
38      /** Constraint requiring uniqueness across items. */
39      UNIQUE("unique"),
40      /** Constraint verifying index key references. */
41      INDEX_HAS_KEY("index-has-key"),
42      /** Constraint validating pattern matching. */
43      MATCHES("matches"),
44      /** Constraint reporting a condition. */
45      REPORT("report");
46  
47      @NonNull
48      private final String name;
49  
50      Type(@NonNull String name) {
51        this.name = name;
52      }
53  
54      /**
55       * Get the name identifier for this constraint type.
56       *
57       * @return the name
58       */
59      @NonNull
60      public String getName() {
61        return name;
62      }
63    }
64  
65    /**
66     * The degree to which a constraint violation is significant.
67     * <p>
68     * These values are ordered from least significant to most significant.
69     */
70    enum Level {
71      /**
72       * No violation.
73       */
74      NONE,
75      /**
76       * A violation of the constraint represents a point of interest.
77       */
78      INFORMATIONAL,
79      /**
80       * A violation of the constraint represents a fault in the content that may
81       * warrant review by a developer when performing model or tool development.
82       */
83      DEBUG,
84      /**
85       * A violation of the constraint represents a potential issue with the content.
86       */
87      WARNING,
88      /**
89       * A violation of the constraint represents a fault in the content. This may
90       * include issues around compatibility, integrity, consistency, etc.
91       */
92      ERROR,
93      /**
94       * A violation of the constraint represents a serious fault in the content that
95       * will prevent typical use of the content.
96       */
97      CRITICAL;
98    }
99  
100   /**
101    * Get the default level to use if no level is provided.
102    *
103    * @return the default level
104    */
105   @NonNull
106   static Level defaultLevel() {
107     return Level.ERROR;
108   }
109 
110   /**
111    * Get the Metapath to use if no target is provided.
112    *
113    * @return the expression
114    */
115   @NonNull
116   static IMetapathExpression defaultTarget() {
117     return IMetapathExpression.contextNode();
118   }
119 
120   /**
121    * Get a string that identifies the provided constraint using the most specific
122    * information available.
123    *
124    * @param constraint
125    *          the constraint to identify
126    * @return the constraint identification statement
127    */
128   @NonNull
129   static String getConstraintIdentity(@NonNull IConstraint constraint) {
130     String identity;
131     if (constraint.getId() != null) {
132       identity = String.format("with id '%s'", constraint.getId());
133     } else if (constraint.getFormalName() != null) {
134       identity = String.format("with the formal name '%s'", constraint.getFormalName());
135     } else {
136       identity = String.format("targeting '%s'", constraint.getTarget().getPath());
137     }
138     return ObjectUtils.notNull(identity);
139   }
140 
141   /**
142    * Get a stable identifier for this constraint, suitable for use in SARIF output
143    * and timing correlation.
144    * <p>
145    * Returns the author-defined {@link #getId()} if present. Otherwise, generates
146    * a deterministic fallback identifier based on the constraint type, source
147    * location, and target expression.
148    *
149    * @return a non-null identifier string
150    */
151   @NonNull
152   default String getInternalIdentifier() {
153     String id = getId();
154     if (id != null) {
155       return id;
156     }
157     // Deterministic fallback based on constraint properties
158     String source = getSource().getLocationHint();
159     String target = getTarget().getPath();
160     int hash = (source + "|" + target).hashCode();
161     return ObjectUtils.notNull(getType().getName() + "-" + String.format("%08x", hash));
162   }
163 
164   /**
165    * Get the constraint type.
166    *
167    * @return the constraint type
168    */
169   @NonNull
170   Type getType();
171 
172   /**
173    * Retrieve the unique identifier for the constraint.
174    *
175    * @return the identifier or {@code null} if no identifier is defined
176    */
177   @Nullable
178   String getId();
179 
180   /**
181    * Get information about the source of the constraint.
182    *
183    * @return the source information
184    */
185   @NonNull
186   ISource getSource();
187 
188   /**
189    * The significance of a violation of this constraint.
190    *
191    * @return the level
192    */
193   @NonNull
194   Level getLevel();
195 
196   /**
197    * Retrieve the Metapath expression to use to query the targets of the
198    * constraint.
199    *
200    * @return a Metapath expression
201    */
202   @NonNull
203   IMetapathExpression getTarget();
204 
205   /**
206    * Based on the provided {@code contextNodeItem}, find all nodes matching the
207    * target expression.
208    *
209    * @param item
210    *          the node item to evaluate the target expression against
211    * @param dynamicContext
212    *          the Metapath evaluation context to use
213    * @return the matching nodes as a sequence
214    * @throws MetapathException
215    *           if an error occurred during evaluation
216    * @see #getTarget()
217    */
218   @NonNull
219   ISequence<? extends IDefinitionNodeItem<?, ?>> matchTargets(
220       @NonNull IDefinitionNodeItem<?, ?> item,
221       @NonNull DynamicContext dynamicContext);
222 
223   /**
224    * Retrieve the remarks associated with the constraint.
225    *
226    * @return the remarks or {@code null} if no remarks are defined
227    */
228   MarkupMultiline getRemarks();
229 
230   /**
231    * Used for double dispatch supporting the visitor pattern provided by
232    * implementations of {@link IConstraintVisitor}.
233    *
234    * @param <T>
235    *          the Java type of a state object passed to the visitor
236    * @param <R>
237    *          the Java type of the result returned by the visitor methods
238    * @param visitor
239    *          the visitor implementation
240    * @param state
241    *          the state object passed to the visitor
242    * @return the visitation result
243    * @see IConstraintVisitor
244    */
245   <T, R> R accept(@NonNull IConstraintVisitor<T, R> visitor, T state);
246 }