Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
RecordType |
|
| 2.823529411764706;2.824 |
1 | package us.daveread.utility.formatcheck.format; | |
2 | ||
3 | import java.util.*; | |
4 | import org.apache.log4j.Logger; | |
5 | ||
6 | /** | |
7 | * <p>Title: RecordType | |
8 | * <p>Description: Contains the definition of a record | |
9 | * <p>Copyright: Copyright (c) 2005 | |
10 | * <p>This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; either version 2 of the License, or | |
13 | * (at your option) any later version. | |
14 | * <p>This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * <p>You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
21 | * </p> | |
22 | * | |
23 | * @author David Read | |
24 | * @version $Id: RecordType.java,v 1.2 2006/06/14 23:30:22 daveread Exp $ | |
25 | */ | |
26 | public class RecordType { | |
27 | ||
28 | /** Constant defining a variable length record type */ | |
29 | public static final int VARIABLELEN = 0; | |
30 | ||
31 | /** Constant defining a fixed length record type */ | |
32 | public static final int FIXEDLEN = 1; | |
33 | ||
34 | /** Constant defining that record width is not validated */ | |
35 | public static final int WIDTH_ANY = -1; | |
36 | ||
37 | /** Constant defining that record width must match sum of field widths */ | |
38 | public static final int WIDTH_AUTO = -2; | |
39 | ||
40 | /** Constant for required occur row being first */ | |
41 | public static final String OCCUR_ROW_FIRST = "FIRST"; | |
42 | ||
43 | /** Constant for required occur row being first */ | |
44 | public static final String OCCUR_ROW_LAST = "LAST"; | |
45 | ||
46 | /** Group requirement for data validation is satisfied */ | |
47 | private static final String GROUP_REQUIREMENT_SATISFIED = "SATISFIED"; | |
48 | ||
49 | /** Group requirement for data validation is violated */ | |
50 | private static final String GROUP_REQUIREMENT_VIOLATED = "VIOLATED"; | |
51 | ||
52 | /** The id for this record type instance */ | |
53 | private String id; | |
54 | ||
55 | /** Type (fixed/variable) of record */ | |
56 | private int type; | |
57 | ||
58 | /** Field separator (variable length) pattern */ | |
59 | private String separator; | |
60 | ||
61 | /** Record width (fixed length) */ | |
62 | private int width; | |
63 | ||
64 | /** Record's maximum width */ | |
65 | private int maxWidth; | |
66 | ||
67 | /** Width is to be calculated from field widths */ | |
68 | private boolean calcWidth; | |
69 | ||
70 | /** Width is a fixed value */ | |
71 | private boolean setWidth; | |
72 | ||
73 | /** MaxWidth is set */ | |
74 | private boolean setMaxWidth; | |
75 | ||
76 | /** Width can be anything */ | |
77 | private boolean anyWidth; | |
78 | ||
79 | /** Human-readable description of this record type instance */ | |
80 | private String description; | |
81 | ||
82 | /** Row from the input source that must contain this record type */ | |
83 | private int requiredOccurRow; | |
84 | ||
85 | /** Row must be the first row from the input source */ | |
86 | private boolean mustBeFirst; | |
87 | ||
88 | /** Row must be the last row from the input source */ | |
89 | private boolean mustBeLast; | |
90 | ||
91 | /** Fields making up the record */ | |
92 | private FieldType fields[]; | |
93 | ||
94 | /** Record type IDs that can immediately preceed this record type */ | |
95 | private String followsIds[]; | |
96 | ||
97 | /** Logger */ | |
98 | 4 | private static final Logger logger = Logger.getLogger(RecordType.class); |
99 | ||
100 | /** | |
101 | * Expects the Id, Width and Description. This is used when creating | |
102 | * fixed-length record definitions. | |
103 | * | |
104 | * @param aId String The identifier for the record | |
105 | * @param aWidth int The width of the record | |
106 | * @param aDescription String A human-readable description of the record, | |
107 | * optional. | |
108 | */ | |
109 | 28 | public RecordType(String aId, int aWidth, String aDescription) { |
110 | 28 | setId(aId); |
111 | 28 | setWidth(aWidth); |
112 | 28 | setDescription(aDescription); |
113 | 28 | fields = new FieldType[0]; |
114 | 28 | followsIds = new String[0]; |
115 | 28 | setType(FIXEDLEN); |
116 | 28 | } |
117 | ||
118 | /** | |
119 | * Expects the Id, Separator and Description. This is used when creating | |
120 | * variable-length record definitions. | |
121 | * | |
122 | * @param aId String The identifier for the record | |
123 | * @param aSeparator String The field separator pattern | |
124 | * @param aDescription String A human-readable description of the record, | |
125 | * optional. | |
126 | */ | |
127 | 7 | public RecordType(String aId, String aSeparator, String aDescription) { |
128 | 7 | setId(aId); |
129 | 7 | setWidth(WIDTH_ANY); |
130 | 7 | setSeparator(aSeparator); |
131 | 7 | setDescription(aDescription); |
132 | 7 | fields = new FieldType[0]; |
133 | 7 | followsIds = new String[0]; |
134 | 7 | setType(VARIABLELEN); |
135 | 7 | } |
136 | ||
137 | /** | |
138 | * Sets the unique identifier for the record. | |
139 | * | |
140 | * @param aId String The unique identifier for the record | |
141 | */ | |
142 | private void setId(String aId) { | |
143 | 35 | id = aId; |
144 | 35 | } |
145 | ||
146 | /** | |
147 | * Gets the unique identifier for the record. | |
148 | * | |
149 | * @return String The unique identifier for the record | |
150 | */ | |
151 | public String getId() { | |
152 | 6 | return id; |
153 | } | |
154 | ||
155 | /** | |
156 | * Sets the type of record (fixed or variable length). | |
157 | * | |
158 | * @param aType int The constant value for the record type | |
159 | */ | |
160 | private void setType(int aType) { | |
161 | 35 | type = aType; |
162 | 35 | } |
163 | ||
164 | /** | |
165 | * Gets the type of record (fixed or variable length). | |
166 | * | |
167 | * @return int The constant value for the record type | |
168 | */ | |
169 | public int getType() { | |
170 | 3 | return type; |
171 | } | |
172 | ||
173 | /** | |
174 | * Sets the width of the record (for fixed width records. There are two | |
175 | * special values to control the width, other then setting to a fixed | |
176 | * number of characters. | |
177 | * <p><strong>AUTO</strong> - will automatically calculate the record width | |
178 | * based on the total width of all the fields in the record. | |
179 | * <p><strong>ANY</strong> - will allow the record to be any length. | |
180 | * | |
181 | * @param aWidth int The width of the record (in characters) or one of the | |
182 | * values AUTO or ANY | |
183 | */ | |
184 | private void setWidth(int aWidth) { | |
185 | 35 | width = aWidth; |
186 | 35 | if (aWidth == WIDTH_ANY) { |
187 | 7 | anyWidth = true; |
188 | 28 | } else if (aWidth == WIDTH_AUTO) { |
189 | 0 | calcWidth = true; |
190 | 0 | setWidth = true; |
191 | } else { | |
192 | 28 | setWidth = true; |
193 | } | |
194 | 35 | } |
195 | ||
196 | /** | |
197 | * Gets the width of the record (for fixed width records) | |
198 | * | |
199 | * @return int The width of the record (in characters) | |
200 | */ | |
201 | public int getWidth() { | |
202 | 20 | return width; |
203 | } | |
204 | ||
205 | /** | |
206 | * Returns true if the record can be any length. | |
207 | * | |
208 | * @return boolean Whether the record can be any length | |
209 | */ | |
210 | public boolean isAnyWidth() { | |
211 | 0 | return anyWidth; |
212 | } | |
213 | ||
214 | /** | |
215 | * Set the maximum width (number of characters) of the record | |
216 | * | |
217 | * @param aMaxWidth String The maximum width of the record | |
218 | */ | |
219 | public void setMaxWidth(String aMaxWidth) { | |
220 | 0 | if (aMaxWidth != null && aMaxWidth.trim().length() > 0) { |
221 | 0 | setMaxWidth(Integer.parseInt(aMaxWidth)); |
222 | } | |
223 | 0 | } |
224 | ||
225 | /** | |
226 | * Set the maximum width (number of characters) of the record | |
227 | * | |
228 | * @param aMaxWidth int The maximum width of the record | |
229 | */ | |
230 | public void setMaxWidth(int aMaxWidth) { | |
231 | 0 | if (aMaxWidth < 1) { |
232 | 0 | throw new IllegalArgumentException("Incorrect maxWidth value, " + |
233 | aMaxWidth + | |
234 | ". Must be 1 or higher."); | |
235 | ||
236 | } | |
237 | 0 | maxWidth = aMaxWidth; |
238 | 0 | setMaxWidth = true; |
239 | 0 | } |
240 | ||
241 | /** | |
242 | * Get the maximum width (number of characters) of the record | |
243 | * | |
244 | * @see isMaxWidthEnforced | |
245 | * | |
246 | * @return int The maximum width of the record | |
247 | */ | |
248 | public int getMaxWidth() { | |
249 | 0 | return maxWidth; |
250 | } | |
251 | ||
252 | /** | |
253 | * Returns true if the record is not to exceed its calculated width | |
254 | * | |
255 | * @see setMaxWidth | |
256 | * @see getMaxWidth | |
257 | * | |
258 | * @return boolean True if a maximum width has been set for the record | |
259 | */ | |
260 | public boolean isMaxWidthEnforced() { | |
261 | 2 | return setMaxWidth; |
262 | } | |
263 | ||
264 | /** | |
265 | * Returns true if the record width is calculated as the sum of field widths. | |
266 | * | |
267 | * @return boolean Whether the record width is calculated from field widths | |
268 | */ | |
269 | public boolean isCalcWidth() { | |
270 | 63 | return calcWidth; |
271 | } | |
272 | ||
273 | /** | |
274 | * Returns true if the record length must match the width value, which may | |
275 | * have been set manually or automatically. | |
276 | * | |
277 | * @return boolean Whether the record length must match the width value | |
278 | */ | |
279 | public boolean isSetWidth() { | |
280 | 51 | return setWidth; |
281 | } | |
282 | ||
283 | /** | |
284 | * Sets the field separator pattern for the record. | |
285 | * | |
286 | * @param aSeparator String The field separator pattern | |
287 | */ | |
288 | private void setSeparator(String aSeparator) { | |
289 | 7 | separator = aSeparator; |
290 | 7 | } |
291 | ||
292 | /** | |
293 | * Gets the field separator pattern for the record. | |
294 | * | |
295 | * @param aSeparator String The field separator pattern | |
296 | */ | |
297 | public String getSeparator() { | |
298 | 8 | return separator; |
299 | } | |
300 | ||
301 | /** | |
302 | * Sets the human-readable description for the record type. | |
303 | * | |
304 | * @param aDescription String The description for the record type | |
305 | */ | |
306 | private void setDescription(String aDescription) { | |
307 | 35 | description = aDescription; |
308 | 35 | } |
309 | ||
310 | /** | |
311 | * Gets the human-readable description for the record type. | |
312 | * | |
313 | * @return String The description for the record type | |
314 | */ | |
315 | public String getDescription() { | |
316 | 3 | if (description == null) { |
317 | 0 | description = type == VARIABLELEN ? "Variable Length Record" : |
318 | "Fixed Length Record - Size " + width; | |
319 | } | |
320 | ||
321 | 3 | return description; |
322 | } | |
323 | ||
324 | /** | |
325 | * Set a required row number, or keyword, for the record type. If a number | |
326 | * is supplied, then they record number but be of this record type or an | |
327 | * error will be reported. The special values "FIRST" and "LAST" may be used | |
328 | * in place of a record number. The "FIRST" value is a synonym for record | |
329 | * 1 (one), while "LAST" dictates that the record type must appear as the last | |
330 | * record of the input being checked. | |
331 | * | |
332 | * <p><strong>Note:</strong> this does not prevent the record type from | |
333 | * being used at other positions withint the input file. This only forces | |
334 | * the record type to be at a given row - not only at that row. | |
335 | * | |
336 | * @see addFollowsId | |
337 | * @see isMustBeFirst | |
338 | * @see isMustBeLast | |
339 | * | |
340 | * @param aRequiredOccurRow String The required row identifier for this record type. | |
341 | */ | |
342 | public void setRequiredOccurRow(String aRequiredOccurRow) { | |
343 | 7 | mustBeFirst = mustBeLast = false; |
344 | 7 | requiredOccurRow = -1; |
345 | 7 | if (aRequiredOccurRow != null) { |
346 | 7 | aRequiredOccurRow = aRequiredOccurRow.toUpperCase().trim(); |
347 | 7 | if (aRequiredOccurRow.equals(OCCUR_ROW_FIRST)) { |
348 | 0 | mustBeFirst = true; |
349 | 0 | requiredOccurRow = 1; |
350 | 7 | } else if (aRequiredOccurRow.equals(OCCUR_ROW_LAST)) { |
351 | 7 | mustBeLast = true; |
352 | } else { | |
353 | try { | |
354 | 0 | requiredOccurRow = Integer.parseInt(aRequiredOccurRow); |
355 | 0 | if (requiredOccurRow < 1) { |
356 | 0 | throw new IllegalArgumentException( |
357 | "Incorrect firstOccurRow value, " + | |
358 | aRequiredOccurRow + ". Must be 1 or higher."); | |
359 | } | |
360 | 0 | if (requiredOccurRow == 1) { |
361 | 0 | mustBeFirst = true; |
362 | } | |
363 | 0 | } catch (IllegalArgumentException iae) { |
364 | 0 | throw iae; |
365 | 0 | } catch (Throwable any) { |
366 | 0 | throw new IllegalArgumentException( |
367 | "Incorrect firstOccurRow setting [" + | |
368 | aRequiredOccurRow + "]"); | |
369 | 0 | } |
370 | } | |
371 | } | |
372 | 7 | } |
373 | ||
374 | /** | |
375 | * Gets a required row number. If a required row was set that cannot be | |
376 | * represented as a number (i.e. LAST) then this will return -1. | |
377 | * | |
378 | * @see isMustBeFirst | |
379 | * @see isMustBeLast | |
380 | * @see getFollowsId | |
381 | * | |
382 | * @param aRequiredOccurRow String The required row identifier for this | |
383 | * record type. | |
384 | */ | |
385 | public int getRequiredOccurRow() { | |
386 | 10 | return requiredOccurRow; |
387 | } | |
388 | ||
389 | /** | |
390 | * Returns whether this record type must appear as the first record of the | |
391 | * data source being checked. | |
392 | * | |
393 | * @see setRequiredOccurRow | |
394 | * | |
395 | * @return boolean True if the record type must be the first one in the input | |
396 | */ | |
397 | public boolean isMustBeFirst() { | |
398 | 10 | return mustBeFirst; |
399 | } | |
400 | ||
401 | /** | |
402 | * Returns whether this record type must appear as the last record of the | |
403 | * data source being checked. | |
404 | * | |
405 | * @see setRequiredOccurRow | |
406 | * | |
407 | * @return boolean True if the record type must be the last one in the input | |
408 | */ | |
409 | public boolean isMustBeLast() { | |
410 | 2 | return mustBeLast; |
411 | } | |
412 | ||
413 | /** | |
414 | * Adds a field definition to the record. | |
415 | * | |
416 | * @param aField FieldType The field definition | |
417 | */ | |
418 | public void addField(FieldType aField) { | |
419 | 63 | List tempFields = new ArrayList(Arrays.asList(fields)); |
420 | 63 | tempFields.add(aField); |
421 | 63 | fields = (FieldType[]) tempFields.toArray(new FieldType[tempFields.size()]); |
422 | 63 | if (isCalcWidth()) { |
423 | 0 | width += aField.getWidth(); |
424 | } | |
425 | 63 | } |
426 | ||
427 | /** | |
428 | * Gets the number of fields defined in the record type. | |
429 | * | |
430 | * @return The number of fields in the record type | |
431 | */ | |
432 | public int getNumFieldTypes() { | |
433 | 9 | return fields.length; |
434 | } | |
435 | ||
436 | /** | |
437 | * Gets the field definition at the supplied index. | |
438 | * | |
439 | * @param aIndex int The index of the field being retrieved | |
440 | * | |
441 | * @return FieldType The field type at the given index | |
442 | */ | |
443 | public FieldType getFieldType(int aIndex) { | |
444 | 4 | return fields[aIndex]; |
445 | } | |
446 | ||
447 | /** | |
448 | * Adds an id to the set of record ids that this record type must follow. | |
449 | * The record will be rejected if it is not first (has no previous record) | |
450 | * or if the previous record is not found in the set of follows ids. | |
451 | * | |
452 | * @param aFollowsId String The record id this record must follow. | |
453 | */ | |
454 | void addFollowsId(String aFollowsId) { | |
455 | 0 | List tempFollowsIds = new ArrayList(Arrays.asList(followsIds)); |
456 | 0 | tempFollowsIds.add(aFollowsId); |
457 | 0 | followsIds = (String[]) tempFollowsIds.toArray( |
458 | new String[tempFollowsIds.size()]); | |
459 | 0 | } |
460 | ||
461 | /** | |
462 | * Gets the number of record ids defined as predecessors to this record type. | |
463 | * | |
464 | * @return The number of record ids defined as predesessors | |
465 | */ | |
466 | public int getNumFollowsIds() { | |
467 | 3 | return followsIds.length; |
468 | } | |
469 | ||
470 | /** | |
471 | * Gets the predessor record id at the supplied index. | |
472 | * | |
473 | * @param aIndex int The index of the predesessor being retrieved | |
474 | * | |
475 | * @return FieldType The record idat the given index | |
476 | */ | |
477 | public String getFollowsIds(int aIndex) { | |
478 | 0 | return followsIds[aIndex]; |
479 | } | |
480 | ||
481 | /** | |
482 | * Parse the record into fields, based on the defined field types. | |
483 | * | |
484 | * @param aRecord String The record to be parsed | |
485 | * | |
486 | * @return String[] The set of fields | |
487 | */ | |
488 | public String[] parse(String aRecord) { | |
489 | int start; | |
490 | int effectiveWidth, remainingWidth; | |
491 | String[] parsedRecord; | |
492 | ||
493 | 22 | start = 0; |
494 | 22 | if (type == FIXEDLEN) { |
495 | 17 | List tempParsed = new ArrayList(); |
496 | 54 | for (int field = 0; field < fields.length; ++field) { |
497 | 37 | if (fields[field].getStart() > -1) { |
498 | 0 | start = fields[field].getStart(); |
499 | } | |
500 | try { | |
501 | 37 | effectiveWidth = fields[field].getWidth(); |
502 | ||
503 | // If the rec width is not fixed, allow the field to be shorter | |
504 | // than its stated width. | |
505 | 37 | if (!isSetWidth()) { |
506 | 0 | remainingWidth = aRecord.substring(start).length(); |
507 | 0 | if (remainingWidth < effectiveWidth && |
508 | remainingWidth > 0) { | |
509 | 0 | effectiveWidth = remainingWidth; |
510 | } | |
511 | } | |
512 | // tempParsed.add(aRecord.substring(start, | |
513 | // start + fields[field].getWidth())); | |
514 | 37 | tempParsed.add(aRecord.substring(start, |
515 | start + effectiveWidth)); | |
516 | 37 | logger.debug("Parsing field[" + field + |
517 | "] start[" + start + "] width[" + | |
518 | fields[field].getWidth() + "] end[" + | |
519 | (start + fields[field].getWidth() + 1) + | |
520 | "] result[" + | |
521 | aRecord.substring(start, | |
522 | start + fields[field].getWidth()) + | |
523 | "]"); | |
524 | 0 | } catch (Throwable any) { |
525 | 0 | logger.error( |
526 | "No Data Parsing field[" + | |
527 | field + | |
528 | "] start[" + start + "] width[" + | |
529 | fields[field].getWidth() + | |
530 | "] end[" + | |
531 | (start + fields[field].getWidth() + 1) + "]", any); | |
532 | 37 | } |
533 | 37 | start += fields[field].getWidth(); |
534 | } | |
535 | 17 | parsedRecord = (String[]) tempParsed.toArray( |
536 | new String[tempParsed.size()]); | |
537 | } else { | |
538 | 5 | parsedRecord = aRecord.split(getSeparator()); |
539 | } | |
540 | ||
541 | 22 | if (logger.isDebugEnabled()) { |
542 | 74 | for (int showIt = 0; showIt < parsedRecord.length; ++showIt) { |
543 | 52 | logger.debug("Field[" + showIt + "]=[" + parsedRecord[showIt] + |
544 | "]"); | |
545 | } | |
546 | } | |
547 | ||
548 | 22 | return parsedRecord; |
549 | } | |
550 | ||
551 | /** | |
552 | * Validate the record. This parses the record and checks each field | |
553 | * against its field type rules. | |
554 | * | |
555 | * @param aRecord String The input record being checked | |
556 | * @param variables Map The variables stored for this input source | |
557 | * verification | |
558 | * @return ValidationResult The result of the validation run. | |
559 | */ | |
560 | public ValidationResult validate(String aRecord, Map variables) { | |
561 | String parsedRecord[]; | |
562 | String fieldValue, fieldResult, variableValue; | |
563 | ValidationResult allResults; | |
564 | int field; | |
565 | boolean isEmpty; | |
566 | Map potentialVariables; | |
567 | ValidationResult potentialVariablesResults; | |
568 | Map orFieldStatus, xOrFieldStatus; | |
569 | Iterator orGroupKey; | |
570 | Integer orGroup; | |
571 | ||
572 | 14 | allResults = new ValidationResult(); |
573 | 14 | potentialVariablesResults = new ValidationResult(); |
574 | 14 | potentialVariables = new HashMap(); |
575 | 14 | orFieldStatus = new HashMap(); |
576 | 14 | xOrFieldStatus = new HashMap(); |
577 | ||
578 | 14 | if (isSetWidth()) { |
579 | 12 | allResults.addOpportunity(); |
580 | 12 | if (aRecord.length() != getWidth()) { |
581 | 5 | allResults.addDefectMessage( |
582 | "Record is not correct length (Required=" + | |
583 | getWidth() + ", Actual Length=" + | |
584 | aRecord.length() + ")"); | |
585 | } | |
586 | 2 | } else if (isMaxWidthEnforced()) { |
587 | 0 | allResults.addOpportunity(); ; |
588 | 0 | if (aRecord.length() > getMaxWidth()) { |
589 | 0 | allResults.addDefectMessage( |
590 | "Record is too long (Maximum length=" + | |
591 | getMaxWidth() + ", Actual length=" + | |
592 | aRecord.length() + ")"); | |
593 | } | |
594 | } | |
595 | ||
596 | // Parse Record | |
597 | 14 | parsedRecord = parse(aRecord); |
598 | ||
599 | // Loop through fields and validate each field | |
600 | 14 | for (field = 0; field < fields.length && field < parsedRecord.length; |
601 | 31 | ++field) { |
602 | ||
603 | 31 | fieldResult = null; |
604 | 31 | fieldValue = parsedRecord[field]; |
605 | 31 | isEmpty = fieldValue == null || fieldValue.trim().length() == 0; |
606 | ||
607 | /* If field is not empty, validate it */ | |
608 | 31 | if (!isEmpty) { |
609 | 31 | fieldResult = fields[field].validate(parsedRecord[field]); |
610 | } else { | |
611 | /*Handle empty field */ | |
612 | 0 | if (fields[field].isRequired()) { |
613 | 0 | fieldResult = fields[field].getName() + " is required"; |
614 | } | |
615 | } | |
616 | ||
617 | 31 | if (fields[field].isOr()) { |
618 | 0 | processOrField(orFieldStatus, fields[field], isEmpty); |
619 | 31 | } else if (fields[field].isXOr()) { |
620 | 0 | processXOrField(xOrFieldStatus, fields[field], isEmpty); |
621 | } | |
622 | ||
623 | // If an error was reported, add it to results | |
624 | 31 | if (fieldResult != null) { |
625 | 8 | allResults.addDefectMessage(fieldResult); |
626 | } | |
627 | ||
628 | // If we matched a matchlock field, set matchlock | |
629 | 31 | if (fieldResult == null && fields[field].isMatchLock()) { |
630 | 7 | allResults.setMatchLock(true); |
631 | } | |
632 | ||
633 | // Check for variable value match requirement | |
634 | 31 | if (fields[field].isToBeMatched()) { |
635 | 0 | potentialVariablesResults.addOpportunity(); |
636 | 0 | variableValue = (String) variables.get(fields[field]. |
637 | getVariableNameMatchValue()); | |
638 | 0 | if (variableValue == null) { |
639 | 0 | potentialVariablesResults.addDefectMessage(fields[field]. |
640 | getName() + | |
641 | " requires match to value in variable " + | |
642 | fields[field].getVariableNameMatchValue() + | |
643 | " - the variable is empty [Value Found:" + | |
644 | parsedRecord[field] + | |
645 | "]"); | |
646 | } | |
647 | // For fixed width records -- where width is enforced, the field | |
648 | // values must match, including left padding, i.e. width matches | |
649 | 0 | else if (isSetWidth() && |
650 | !variableValue.equals(parsedRecord[field])) { | |
651 | 0 | potentialVariablesResults.addDefectMessage(fields[field]. |
652 | getName() + | |
653 | " does not match value in variable " + | |
654 | fields[field].getVariableNameMatchValue() + " (" + | |
655 | variableValue + | |
656 | ") [Value Found:" + parsedRecord[field] + "]"); | |
657 | } | |
658 | // For variable width records or fixed width records where width is not | |
659 | // enforced, values must match with whitespace trimmed, i.e. width | |
660 | // needn't match | |
661 | 0 | else if (!isSetWidth() && |
662 | !variableValue.equals(parsedRecord[field].trim())) { | |
663 | 0 | potentialVariablesResults.addDefectMessage(fields[field]. |
664 | getName() + | |
665 | " does not match value in variable " + | |
666 | fields[field].getVariableNameMatchValue() + " (" + | |
667 | variableValue + | |
668 | ") [Value Found:" + parsedRecord[field] + "]"); | |
669 | } | |
670 | } | |
671 | ||
672 | // Check for variable storage requirement | |
673 | 31 | if (fields[field].isToBeStored()) { |
674 | // If width of record can be shorter than sum of widths, | |
675 | // trim resulting value to allow for shortened field | |
676 | 0 | potentialVariables.put(fields[field].getVariableNameStoreValue(), |
677 | isSetWidth() ? parsedRecord[field] : | |
678 | parsedRecord[field].trim()); | |
679 | } | |
680 | } | |
681 | ||
682 | // Report on any required missing fields at the end of the record | |
683 | 14 | for (; field < fields.length; ++field) { |
684 | 0 | if (fields[field].isRequired()) { |
685 | 0 | allResults.addDefectMessage("Missing data for field " + |
686 | fields[field].getName()); | |
687 | } | |
688 | 0 | if (fields[field].isOr()) { |
689 | 0 | processOrField(orFieldStatus, fields[field], true); |
690 | 0 | } else if (fields[field].isXOr()) { |
691 | 0 | processXOrField(xOrFieldStatus, fields[field], true); |
692 | } | |
693 | } | |
694 | ||
695 | // Report on any unfulfilled "Or" groups | |
696 | 14 | orGroupKey = orFieldStatus.keySet().iterator(); |
697 | 14 | while (orGroupKey.hasNext()) { |
698 | 0 | allResults.addOpportunity(); |
699 | 0 | orGroup = (Integer) orGroupKey.next(); |
700 | 0 | fieldResult = (String) orFieldStatus.get(orGroup); |
701 | 0 | logger.debug("orGroup[" + orGroup + "] fieldResult[" + fieldResult + |
702 | "]"); | |
703 | 0 | if (fieldResult != null && |
704 | !fieldResult.equals(GROUP_REQUIREMENT_SATISFIED)) { | |
705 | 0 | allResults.addDefectMessage( |
706 | "No data supplied for any field in group " + | |
707 | orGroup + " (" + fieldResult + ")"); | |
708 | 0 | logger.debug("add defect for orGroup[" + orGroup + |
709 | "] fieldResult[" + fieldResult + "]"); | |
710 | } | |
711 | } | |
712 | ||
713 | // Report on any unfulfilled "XOr" groups | |
714 | 14 | orGroupKey = xOrFieldStatus.keySet().iterator(); |
715 | 14 | while (orGroupKey.hasNext()) { |
716 | 0 | allResults.addOpportunity(); |
717 | 0 | orGroup = (Integer) orGroupKey.next(); |
718 | 0 | fieldResult = (String) xOrFieldStatus.get(orGroup); |
719 | 0 | logger.debug("XorGroup[" + orGroup + "] fieldResult[" + fieldResult + |
720 | "]"); | |
721 | 0 | if (!fieldResult.startsWith(GROUP_REQUIREMENT_SATISFIED)) { |
722 | 0 | if (fieldResult.startsWith(GROUP_REQUIREMENT_VIOLATED)) { |
723 | // Too many fields with data | |
724 | 0 | allResults.addDefectMessage( |
725 | "You may only place data in one, not all, of the fields: " + | |
726 | fieldResult.substring(GROUP_REQUIREMENT_VIOLATED. | |
727 | length())); | |
728 | 0 | logger.debug( |
729 | "add defect, too many populated, for XorGroup[" + | |
730 | orGroup + "] fieldResult[" + fieldResult + "]"); | |
731 | } else { | |
732 | // No data provided in any of the fields -- must have data in one | |
733 | 0 | allResults.addDefectMessage( |
734 | "You must place data in one, not all, of the fields: " + | |
735 | fieldResult); | |
736 | 0 | logger.debug("add defect, none populated, for XorGroup[" + |
737 | orGroup + "] fieldResult[" + fieldResult + "]"); | |
738 | } | |
739 | } | |
740 | } | |
741 | ||
742 | 14 | allResults.addOpportunities(fields.length); |
743 | ||
744 | // If this record type is matched, add the variables and variable-based | |
745 | // comparisons to the results | |
746 | 14 | if (allResults.isMatchLock() || allResults.getNumDefects() == 0) { |
747 | 7 | variables.putAll(potentialVariables); |
748 | 7 | allResults.addOpportunities(potentialVariablesResults. |
749 | getNumOpportunities()); | |
750 | 7 | for (int defect = 0; |
751 | 7 | defect < potentialVariablesResults.getNumDefects(); |
752 | 0 | ++defect) { |
753 | 0 | allResults.addDefectMessage(potentialVariablesResults. |
754 | getDefectMessage( | |
755 | defect)); | |
756 | } | |
757 | } | |
758 | ||
759 | 14 | return allResults; |
760 | } | |
761 | ||
762 | /** | |
763 | * Maintain the "Or" Group Status, keeping track of whether a member of the | |
764 | * group has been found with its data supplied. | |
765 | * | |
766 | * @param aOrGroupStatus Map The collection of or groups | |
767 | * @param aField FieldType A field which is a member of an "Or" group | |
768 | * @param aIsEmpty boolean Whether the field has data | |
769 | */ | |
770 | private void processOrField(Map aOrGroupStatus, FieldType aField, | |
771 | boolean aIsEmpty) { | |
772 | Integer group; | |
773 | String status; | |
774 | ||
775 | 0 | group = new Integer(aField.getGroup()); |
776 | ||
777 | 0 | logger.debug("group[" + group + "]"); |
778 | ||
779 | 0 | if (!aIsEmpty) { |
780 | 0 | aOrGroupStatus.put(group, GROUP_REQUIREMENT_SATISFIED); |
781 | 0 | logger.debug("group[" + group + "] satisfied"); |
782 | } else { | |
783 | 0 | status = (String) aOrGroupStatus.get(group); |
784 | 0 | if (status == null) { |
785 | 0 | status = ""; |
786 | } | |
787 | ||
788 | 0 | if (!status.equals(GROUP_REQUIREMENT_SATISFIED)) { |
789 | 0 | if (status.length() > 0) { |
790 | 0 | status += ", "; |
791 | } | |
792 | 0 | status += aField.getName(); |
793 | 0 | aOrGroupStatus.put(group, status); |
794 | } | |
795 | 0 | logger.debug("group[" + group + "] status[" + status + "]"); |
796 | } | |
797 | 0 | } |
798 | ||
799 | /** | |
800 | * Maintain the "XOr" Group Status, keeping track of whether a member of the | |
801 | * group has been found with its data supplied. | |
802 | * | |
803 | * @param aOrGroupStatus Map The collection of or groups | |
804 | * @param aField FieldType A field which is a member of an "Or" group | |
805 | * @param aIsEmpty boolean Whether the field has data | |
806 | */ | |
807 | private void processXOrField(Map aXOrGroupStatus, FieldType aField, | |
808 | boolean aIsEmpty) { | |
809 | Integer group; | |
810 | String status; | |
811 | ||
812 | 0 | group = new Integer(aField.getGroup()); |
813 | ||
814 | 0 | logger.debug("group[" + group + "]"); |
815 | ||
816 | 0 | status = (String) aXOrGroupStatus.get(group); |
817 | ||
818 | 0 | if (status == null) { |
819 | 0 | status = ""; |
820 | } | |
821 | ||
822 | 0 | if (!aIsEmpty) { |
823 | 0 | if (status.startsWith(GROUP_REQUIREMENT_SATISFIED)) { |
824 | // Error, already had data, exclusive Or violated | |
825 | 0 | status = GROUP_REQUIREMENT_VIOLATED + " " + |
826 | status.substring(GROUP_REQUIREMENT_SATISFIED.length()); | |
827 | 0 | status += ", " + aField.getName(); |
828 | 0 | logger.debug("group[" + group + "] violated[" + status + "]"); |
829 | 0 | } else if (status.startsWith(GROUP_REQUIREMENT_VIOLATED)) { |
830 | 0 | status += ", " + aField.getName(); |
831 | 0 | logger.debug("group[" + group + "] violated[" + status + "]"); |
832 | } else { | |
833 | 0 | status = GROUP_REQUIREMENT_SATISFIED + " " + aField.getName(); |
834 | 0 | logger.debug("group[" + group + "] satisfied[" + status + "]"); |
835 | } | |
836 | } else { | |
837 | 0 | if (!status.startsWith(GROUP_REQUIREMENT_SATISFIED) && |
838 | !status.startsWith(GROUP_REQUIREMENT_VIOLATED)) { | |
839 | 0 | if (status.length() > 0) { |
840 | 0 | status += ", "; |
841 | } | |
842 | 0 | status += aField.getName(); |
843 | } | |
844 | } | |
845 | ||
846 | 0 | logger.debug("group[" + group + "] status[" + status + "]"); |
847 | ||
848 | 0 | aXOrGroupStatus.put(group, status); |
849 | 0 | } |
850 | ||
851 | ||
852 | /** | |
853 | * Convenience to report display description as default string value. | |
854 | * | |
855 | * @return String The display description for the data type. | |
856 | */ | |
857 | public String toString() { | |
858 | 0 | return getDescription(); |
859 | } | |
860 | } |