void setRowNumbers() {
   for (Codeword codeword : getCodewords()) {
     if (codeword != null) {
       codeword.setRowNumberAsRowIndicatorColumn();
     }
   }
 }
 BarcodeMetadata getBarcodeMetadata() {
   Codeword[] codewords = getCodewords();
   BarcodeValue barcodeColumnCount = new BarcodeValue();
   BarcodeValue barcodeRowCountUpperPart = new BarcodeValue();
   BarcodeValue barcodeRowCountLowerPart = new BarcodeValue();
   BarcodeValue barcodeECLevel = new BarcodeValue();
   for (Codeword codeword : codewords) {
     if (codeword == null) {
       continue;
     }
     codeword.setRowNumberAsRowIndicatorColumn();
     int rowIndicatorValue = codeword.getValue() % 30;
     int codewordRowNumber = codeword.getRowNumber();
     if (!isLeft) {
       codewordRowNumber += 2;
     }
     switch (codewordRowNumber % 3) {
       case 0:
         barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1);
         break;
       case 1:
         barcodeECLevel.setValue(rowIndicatorValue / 3);
         barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3);
         break;
       case 2:
         barcodeColumnCount.setValue(rowIndicatorValue + 1);
         break;
     }
   }
   // Maybe we should check if we have ambiguous values?
   if ((barcodeColumnCount.getValue().length == 0)
       || (barcodeRowCountUpperPart.getValue().length == 0)
       || (barcodeRowCountLowerPart.getValue().length == 0)
       || (barcodeECLevel.getValue().length == 0)
       || barcodeColumnCount.getValue()[0] < 1
       || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0]
           < PDF417Common.MIN_ROWS_IN_BARCODE
       || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0]
           > PDF417Common.MAX_ROWS_IN_BARCODE) {
     return null;
   }
   BarcodeMetadata barcodeMetadata =
       new BarcodeMetadata(
           barcodeColumnCount.getValue()[0],
           barcodeRowCountUpperPart.getValue()[0],
           barcodeRowCountLowerPart.getValue()[0],
           barcodeECLevel.getValue()[0]);
   removeIncorrectCodewords(codewords, barcodeMetadata);
   return barcodeMetadata;
 }
  // TODO maybe we should add missing codewords to store the correct row number to make
  // finding row numbers for other columns easier
  // use row height count to make detection of invalid row numbers more reliable
  int adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) {
    BoundingBox boundingBox = getBoundingBox();
    ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
    ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
    int firstRow = imageRowToCodewordIndex((int) top.getY());
    int lastRow = imageRowToCodewordIndex((int) bottom.getY());
    float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
    Codeword[] codewords = getCodewords();
    int barcodeRow = -1;
    int maxRowHeight = 1;
    int currentRowHeight = 0;
    for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
      if (codewords[codewordsRow] == null) {
        continue;
      }
      Codeword codeword = codewords[codewordsRow];

      codeword.setRowNumberAsRowIndicatorColumn();

      int rowDifference = codeword.getRowNumber() - barcodeRow;

      // TODO improve handling with case where first row indicator doesn't start with 0

      if (rowDifference == 0) {
        currentRowHeight++;
      } else if (rowDifference == 1) {
        maxRowHeight = Math.max(maxRowHeight, currentRowHeight);
        currentRowHeight = 1;
        barcodeRow = codeword.getRowNumber();
      } else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) {
        codewords[codewordsRow] = null;
      } else {
        barcodeRow = codeword.getRowNumber();
        currentRowHeight = 1;
      }
    }
    return (int) (averageRowHeight + 0.5);
  }