1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package gov.nist.secauto.metaschema.core.metapath;
7
8 import java.util.ArrayDeque;
9 import java.util.Collections;
10 import java.util.Deque;
11
12 import edu.umd.cs.findbugs.annotations.NonNull;
13 import edu.umd.cs.findbugs.annotations.Nullable;
14
15 /**
16 * {@code MetapathException} is the superclass of all exceptions that can be
17 * thrown during the compilation and evaluation of a Metapath.
18 */
19 public class MetapathException
20 extends RuntimeException {
21 /**
22 * the serial version UID.
23 */
24 private static final long serialVersionUID = 1L;
25 /**
26 * The error prefix which identifies what kind of error it is.
27 */
28 @NonNull
29 private final IErrorCode errorCode;
30
31 /**
32 * The evaluation stack recording the expressions being evaluated when the
33 * exception occurred.
34 */
35 @Nullable
36 private Deque<IExpression> evaluationStack = null;
37
38 /**
39 * Constructs a new Metapath exception with the provided {@code code} and
40 * {@code message} and no cause.
41 *
42 * @param errorCode
43 * the error code that identifies the type of error
44 * @param message
45 * the exception message
46 */
47 protected MetapathException(
48 @NonNull IErrorCode errorCode,
49 @Nullable String message) {
50 super(message);
51 this.errorCode = errorCode;
52 }
53
54 /**
55 * Constructs a new Metapath exception with a {@code null} message and the
56 * provided {@code code} and {@code cause}.
57 *
58 * @param errorCode
59 * the error code that identifies the type of error
60 * @param cause
61 * the exception cause
62 */
63 protected MetapathException(
64 @NonNull IErrorCode errorCode,
65 @Nullable Throwable cause) {
66 super(cause);
67 this.errorCode = errorCode;
68 }
69
70 /**
71 * Constructs a new Metapath exception with the provided {@code code},
72 * {@code message} and {@code cause}.
73 *
74 * @param errorCode
75 * the error code that identifies the type of error
76 * @param message
77 * the exception message
78 * @param cause
79 * the exception cause
80 */
81 protected MetapathException(
82 @NonNull IErrorCode errorCode,
83 @Nullable String message,
84 @Nullable Throwable cause) {
85 super(message, cause);
86 this.errorCode = errorCode;
87 }
88
89 /**
90 * Registers the evaluation context from the provided dynamic context.
91 * <p>
92 * A snapshot of the execution stack is captured from the dynamic context if not
93 * already set. This ensures the recorded state reflects the stack at the time
94 * of registration, avoiding confusion from post-throw mutations.
95 *
96 * @param dynamicContext
97 * the dynamic context containing the execution stack
98 * @return this exception instance for chaining
99 */
100 public final MetapathException registerEvaluationContext(@NonNull DynamicContext dynamicContext) {
101 if (evaluationStack == null) {
102 // getExecutionStack() returns a defensive copy
103 evaluationStack = dynamicContext.getExecutionStack();
104 }
105 return this;
106 }
107
108 /**
109 * Registers the evaluation context from the provided evaluation stack.
110 * <p>
111 * A snapshot of the stack is captured if not already set.
112 *
113 * @param stack
114 * the evaluation stack recording the expressions being evaluated
115 * @return this exception instance for chaining
116 */
117 public final MetapathException registerEvaluationContext(@NonNull Deque<? extends IExpression> stack) {
118 if (evaluationStack == null) {
119 evaluationStack = new ArrayDeque<>(stack);
120 }
121 return this;
122 }
123
124 /**
125 * Registers the evaluation context from the provided metapath expression.
126 * <p>
127 * The expression is recorded as the evaluation context if not already set.
128 *
129 * @param metapath
130 * the metapath expression being evaluated
131 * @return this exception instance for chaining
132 */
133 public final MetapathException registerEvaluationContext(@NonNull IMetapathExpression metapath) {
134 if (evaluationStack == null) {
135 evaluationStack = new ArrayDeque<>(Collections.singleton(metapath));
136 }
137 return this;
138 }
139
140 /**
141 * Retrieves the evaluation stack recording the expressions being evaluated.
142 *
143 * @return the evaluation stack, or {@code null} if not set
144 */
145 @Nullable
146 protected Deque<IExpression> getEvaluationStack() {
147 return evaluationStack;
148 }
149
150 @Override
151 public final String getMessage() {
152 String message = getMessageText();
153 return String.format(
154 "%s%s",
155 getErrorCode().toString(),
156 message == null ? "" : ": " + message);
157 }
158
159 /**
160 * Get the message text without the error code prefix.
161 *
162 * @return the message text or {@code null}
163 */
164 @Nullable
165 public String getMessageText() {
166 String msg = super.getMessage();
167
168 Deque<IExpression> stack = getEvaluationStack();
169
170 if (stack != null && !stack.isEmpty()) {
171 IExpression head = stack.peekLast();
172 msg = String.format(
173 "An error occurred while evaluating the expression '%s'%s",
174 head.getPath(),
175 msg == null ? "" : ": " + msg);
176 }
177 return msg;
178 }
179
180 /**
181 * Get the error code, which indicates what type of error it is.
182 *
183 * @return the error code
184 */
185 @NonNull
186 public final IErrorCode getErrorCode() {
187 return errorCode;
188 }
189
190 }