1 /*
2 * Metis: Java Data Framework
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.metis.field;
18
19 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
20 import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21
22 import java.util.ArrayList;
23 import java.util.List;
24
25 /**
26 * Records errors relating to an item.
27 */
28 public class MetisFieldValidation
29 implements MetisFieldItem {
30 /*
31 * FieldSet definitions.
32 */
33 static {
34 MetisFieldSet.newFieldSet(MetisFieldValidation.class);
35 }
36
37 /**
38 * The local fields.
39 */
40 private MetisFieldSet<MetisFieldValidation> theLocalFields;
41
42 /**
43 * The first error in the list.
44 */
45 private final List<MetisFieldError> theErrors;
46
47 /**
48 * Constructor.
49 */
50 public MetisFieldValidation() {
51 /* Store details */
52 theErrors = new ArrayList<>();
53 allocateNewFields();
54 }
55
56 /**
57 * Allocate new DataFields.
58 */
59 private void allocateNewFields() {
60 theLocalFields = MetisFieldSet.newFieldSet(this);
61 }
62
63 @Override
64 public MetisFieldSetDef getDataFieldSet() {
65 return theLocalFields;
66 }
67
68 @Override
69 public String formatObject(final OceanusDataFormatter pFormatter) {
70 final int mySize = theErrors.size();
71 if (mySize != 1) {
72 return mySize
73 + " Errors";
74 }
75 final MetisFieldError myError = theErrors.get(0);
76 return myError.formatError();
77 }
78
79 /**
80 * Do we have any errors?
81 *
82 * @return true/false
83 */
84 public boolean hasErrors() {
85 return !theErrors.isEmpty();
86 }
87
88 /**
89 * Get the first error in the list.
90 *
91 * @return the first error or <code>null</code>
92 */
93 public MetisFieldError getFirst() {
94 return theErrors.isEmpty()
95 ? null
96 : theErrors.get(0);
97 }
98
99 /**
100 * add error to the list.
101 *
102 * @param pText the text for the error
103 * @param pField the field for the error
104 */
105 public void addError(final String pText,
106 final MetisDataFieldId pField) {
107 /* Create a new error element */
108 final MetisFieldError myError = new MetisFieldError(pText, pField);
109
110 /* Declare error field */
111 final int myCount = countFieldErrors(pField);
112 final String myName = pField.getId() + "-" + myCount;
113 theLocalFields.declareLocalField(myName, f -> myError);
114
115 /* Add to the end of the list */
116 theErrors.add(myError);
117 }
118
119 /**
120 * Determine whether there are any errors for a particular field.
121 *
122 * @param pField - the field
123 * @return <code>true</code> if there are any errors <code>false</code> otherwise
124 */
125 public boolean hasErrors(final MetisDataFieldId pField) {
126 /* Loop through the elements */
127 for (MetisFieldError myCurr : theErrors) {
128 /* Access the element and return if related to required field */
129 if (myCurr.getField().equals(pField)) {
130 return true;
131 }
132 }
133 return false;
134 }
135
136 /**
137 * Get the error details for a particular field.
138 *
139 * @param pField - the field
140 * @return the error text
141 */
142 public String getFieldErrors(final MetisDataFieldId pField) {
143 final StringBuilder myErrors = new StringBuilder();
144
145 /* Loop through the elements */
146 for (MetisFieldError myCurr : theErrors) {
147 /* Access the element */
148 /* If the field matches */
149 if (myCurr.getField().equals(pField)) {
150 /* Add the error */
151 addErrorText(myErrors, myCurr.getError());
152 }
153 }
154
155 /* If we have errors */
156 if (!myErrors.isEmpty()) {
157 /* Complete the format and return it */
158 endErrors(myErrors);
159 return myErrors.toString();
160 }
161
162 /* Return null */
163 return null;
164 }
165
166 /**
167 * Count the errors for this field.
168 *
169 * @param pField - the field number to check
170 * @return the error count
171 */
172 private int countFieldErrors(final MetisDataFieldId pField) {
173 int myCount = 0;
174
175 /* Loop through the elements */
176 for (MetisFieldError myCurr : theErrors) {
177 /* Access the element */
178 /* If the field matches */
179 if (myCurr.getField().equals(pField)) {
180 /* Increment the count */
181 myCount++;
182 }
183 }
184
185 /* Return the count */
186 return myCount;
187 }
188
189 /**
190 * Get the error text for fields outside a set of fields.
191 *
192 * @param aFields the set of fields
193 * @return the error text
194 */
195 public String getFieldErrors(final MetisFieldDef[] aFields) {
196 final StringBuilder myErrors = new StringBuilder();
197
198 /* Loop through the elements */
199 for (MetisFieldError myCurr : theErrors) {
200 /* Access the element and field */
201 final MetisDataFieldId myField = myCurr.getField();
202
203 /* Search the field set */
204 boolean bFound = false;
205 for (MetisFieldDef field : aFields) {
206 /* If we have found the field note it and break loop */
207 if (field != null && field.equals(myField)) {
208 bFound = true;
209 break;
210 }
211 }
212
213 /* Skip error if the field was found */
214 if (bFound) {
215 continue;
216 }
217
218 /* Add the error */
219 addErrorText(myErrors, myCurr.getError());
220 }
221
222 /* If we have errors */
223 if (!myErrors.isEmpty()) {
224 /* Complete the format and return it */
225 endErrors(myErrors);
226 return myErrors.toString();
227 }
228
229 /* Return null */
230 return null;
231 }
232
233 /**
234 * Add error text.
235 *
236 * @param pBuilder the string builder
237 * @param pError new error text
238 */
239 private static void addErrorText(final StringBuilder pBuilder,
240 final String pError) {
241 /* Add relevant prefix */
242 pBuilder.append((pBuilder.isEmpty())
243 ? "<html>"
244 : "<br>");
245
246 /* Add error text */
247 pBuilder.append(pError);
248 }
249
250 /**
251 * End error text.
252 *
253 * @param pErrors the error builder
254 */
255 public void endErrors(final StringBuilder pErrors) {
256 pErrors.append("</html>");
257 }
258
259 /**
260 * Clear errors.
261 */
262 public void clearErrors() {
263 /* Remove all errors */
264 theErrors.clear();
265 allocateNewFields();
266 }
267
268 /**
269 * represents an instance of an error for an object.
270 */
271 public static final class MetisFieldError {
272 /**
273 * The text of the error.
274 */
275 private final String theError;
276
277 /**
278 * The field for the error.
279 */
280 private final MetisDataFieldId theField;
281
282 /**
283 * Constructor for the error.
284 *
285 * @param pError the error text
286 * @param pField the field
287 */
288 private MetisFieldError(final String pError,
289 final MetisDataFieldId pField) {
290 theError = pError;
291 theField = pField;
292 }
293
294 /**
295 * Get the text for the error.
296 *
297 * @return the text
298 */
299 public String getError() {
300 return theError;
301 }
302
303 /**
304 * Get the field for the error.
305 *
306 * @return the field
307 */
308 public MetisDataFieldId getField() {
309 return theField;
310 }
311
312 /**
313 * Format the error.
314 *
315 * @return the formatted error
316 */
317 private String formatError() {
318 return theField.getId()
319 + ": "
320 + theError;
321 }
322 }
323 }