Coverage Report - us.daveread.utility.formatcheck.format.RecordType
 
Classes in this File Line Coverage Branch Coverage Complexity
RecordType
50%
115/231
52%
30/58
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  
 }