Barcodes
Universal Product Codes (UPC) are used worldwide to uniquely identify products sold in stores. The basic UPC is a 12-digit number (the UPC-A standard), but you are likely more familiar with UPCs in their barcode format. Barcodes are a 1-dimensional image representing a piece of data, here the 12-digit UPC code. Barcodes are easily scannable by a computer, alleviating the need for manual, error-prone data entry.
An example of a UPC and its corresponding barcode can be found below:
(By toguro - Own work, CC0, https://commons.wikimedia.org/w/index.php?curid=67365482)
Each digit of the UPC-A is represented by a collection of bars rendered sequentially in the barcode. There is a unique sequence of bars for each possible digit, leading to a simple, linear method for producing a barcode from a UPC-A number.
In this project, we’ll write a UPC-A barcode creator that takes a code as input, checks whether the code is a valid UPC-A number, and if so, prints the code in barcode form to the console.
UPC-A Validation
A string is a valid UPC-A number if it obeys the following properties:
- The string is exactly 12 characters.
- Each character of the string is a digit, i.e.,
'0'–'9'.
Implement this logic in the static isValidCode(String code) method of the Barcodes class.
The following String methods from the standard library will be useful for this task:
s.length()returns the length of strings.s.charAt(i)returns the character located at indexiof strings.
Furthermore, the static method Character.isDigit(ch) is useful to test whether character ch is a digit.
Finally, curiously, there is no single method to convert a char to a digit!
To do this, we must employ our knowledge of character values.
Recall that:
- Every character is really stored as a natural number, its associated character value.
- The base-10 digit characters,
'0'–'9', have sequential character values. - Addition
kto a characterchproduces the character whose value iskaway fromch. Dually, subtraction works in the other direction.
We can put these facts to convert characters to digits by writing an expression that, when given a character-digit, produces the corresponding digit.
You will use this functionality in several places in your program, so it is worthwhile to make a helper function, e.g., toDigit(char ch), to do this work.
Check digit computation
In addition to formatting, UPC-A numbers also employ a check digit to quickly verify whether a digit has been entered correctly. It turns out that only the first eleven of the twelve digits carry meaning! The twelfth digit is computed as a function of the first eleven digits. Thus, we can detect whether a UPC-A number is valid by computing the expected check digit from the first eleven digits and see if this expected value matches the actual check digit, i.e., the twelfth.
Let be the 12 digits of the code. The formula for the expected check-digit can be expressed as follows:
In other words:
- We sum up the first, third, etc., digits and multiply the result by three.
- We sum up the remaining digits.
- We add the two results together and take that result, modulo 10.
Remember that is the remainder of the division .
The corresponding mod operator in Java is %.
If , then the check digit is . Otherwise, the check digit is .
Implement this computation in the static method Barcode.computeCheckDigit(code), which returns the expected check digit of the given code.
The method assumes that code is a valid code as verified by Barcode.isValidCode(code).
Barcode Rendering
With a valid UPC-A barcode in-hand, we can now render it to the screen. We’ll render the barcodes by printing individual squares to the console, called modules in the barcode world.
Printing Modules
The character we’ll print is the Unicode square block:
█
Each module will be colored either black or white.
To color each block, we’ll take advantage of ANSI color codes, extended escape sequences that the terminal interprets as color/formatting commands.
The format of an ANSI escape code is: \033[<code>m where <code> is a number denoting the desired color or formatting.
Note
What is going on with that escape code sequence? Really,
\0is a Java-level escape code that allows us to enter a character value in octal, i.e., base-8. The character value we provide is octal33, which is decimal27. The character with this value is theESCcharacter. Thus, to enter ANSI color code “mode,” the terminal must first see the character sequence:ESC,[. Once we’re in this mode, we provide a numeric code and exit the mode withm.
The codes that we will need are:
- Black text:
30. - White text:
37. - Reset:
0.
This last code is important because the escape codes are stateful in the sense that once you instruct the terminal to, e.g., write in blue, it will write all subsequent text as blue. We use the reset code to return the terminal back to its original writing state.
With this in mind, to write a black or white square to the terminal, we would use the following strings:
- Black:
"\033[30m█\033[0m" - White:
"\033[37m█\033[0m"
Which change the terminal color to black or white, prints a square, and then resets the formatting. I recommend writing helper functions that print single colored squares to the console!
Barcode Formats
A valid UPC-A barcode is 113 modules, i.e., squares, wide, consisting of the following sections.
In our description of these sections, we’ll use B to denote a single black module/square and W for a white module/square.
- A quiet zone consisting of 9 white modules, i.e.,
WWWWWWWWW. - A start zone of the form
BWB. - The first six digits of the UPC-A number, the left side of the number.
- A middle zone of the form
WBWBW. - The last six digits of the UPC-a number, the right side of the number.
- An end zone of the same form as the start zone,
BWB. - A quiet zone similar to the first,
WWWWWWWWW.
The UPC-A format defines a seven module pattern for each possible digit, defined below:
0: WWW BB W B
1: WW BB WW B
2: WW B WW BB
3: W BBBB W B
4: W B WWW BB
5: W BB WWW B
6: W B W BBBB
7: W BBB W BB
8: W BB W BBB
9: WWW B W BB
The spaces in the table do not appear in the output. They are present to help make clear the transition between black and white modules.
When printing the first six numbers of the code (the left side of the barcode), we use the format above.
When printing the last six numbers (the right side), we invert the colors.
For example, the module pattern for 0 is WWWBBWB on the left side and BBBWWBW on the right side!
Use this information to implement the key static function, Barcode.printBarcodeRow(String code), which prints a line to the console consisting of one row of the barcode for given UPC-A code.
The function assumes that code is a valid UPC-A code (as determined by the verification functions you wrote above).
Keeping in mind good program decomposition principles, the structure of printBarcodeRow should directly reflect, via calls to appropriately-named helper functions, the format of a barcode.
Additionally, rather than hard-coding these module patterns within printBarcodeRow, we’ll use a basic data structure to store the patterns in such a way that allows us to easily lookup the module patterns for a given digit and programmatically iterate over the pattern so we can generate bars from it.
Our choice of data structure will be an array-of-arrays called ENCODINGS in the Barcode class.
The first level of arrays corresponds to each digit, e.g., index 0 corresponds to digit 0.
The second level of arrays is the module pattern for that digit as a collection of integers.
These integers represent the widths of each collection of alternating white-black modules for that digit.
For example, the sub-array for digit 0 should be { 3, 2, 1, 1 } corresponding to its WWW BB W B pattern described above.
Fill in the ENCODINGS array with this data taken from the table above and use it to power your definition of printBarcodeRow.
Note that you can use nested array literal notation, e.g.,
int[][] arrs = {
{ 1, 2, 3 },
{ 4, 5 },
{ 6, 7, 8, 9 }
};
To concisely specify the ENCODINGS array.
Once completed, you should find this data-driven approach to solving the problem to be much more elegant than hard-coding the patterns as a collection of conditionals!
This will be a recurring theme in the course as we explore our fundamental data structures!
The Barcode Program
The overall Barcode program puts together these components to verify and print a UPC-A barcode to the screen. The Barcode program takes exactly two command-line arguments as input:
- The UPC-A code as described above.
- The height of the barcode to print to the screen. The height is a positive integer denoting the number of rows to be printed.
When executing the program via Maven, pass the -Dexec.args flag to mvn exec:java to specify these arguments.
To specify multiple command-line arguments to the -Dexec.args flag, you’ll need to enclose the entire flag in quotes.
For example, here is a sample invocation of the program with the UPC-A number 036000291452 and height 5.
mvn compile exec:java -q "-Dexec.args=036000291452 5"

A fun way to verify that your image is correct is to use your phone’s camera app or a desktop-based barcode scanning app. With enough rows, your app should be able to read the barcode and report back the UPC-A number that you used!
When testing your app, you should try out other UPC-A numbers. You can find these on many trade products, e.g., books or food labels. Note that our program only works for UPC-A numbers and that these products frequently have multiple codes on them. UPC-A numbers are always 12 digits and their barcodes usually look like the example provided at the top of this write-up.
In addition to this positive behavior, your barcode program should also perform appropriate error checking on the inputs.
It should cover each of the following cases and output the given error message before exiting.
To exit with an error, you can use the static System.exit(errCode) method, passing a non-zero errCode value, e.g., 1.
- The user does not provide exactly two command-line arguments:
Usage: barcode <UPC-A code> <height>. - The user provides a code that is not a string of 12 digits:
Code must be a string of 12 digits.. - The user provides a height that is not a positive integer:
Height must be a positive integer.. - The user provides a code whose check digit is inconsistent:
Expected check digit X but found Y.whereXis the expected check digit andYis the actual check digit found in the provided code.
Here are example invocations of the program and the expected output in these erroneous cases:
➜ mvn compile exec:java -q "-Dexec.args=20"
Usage: barcode <upc-a code> <height>
➜ mvn compile exec:java -q "-Dexec.args=1234567 20"
Code must be a string of 12 digits.
➜ mvn compile exec:java -q "-Dexec.args=036000291452 -10"
Height must be a positive integer.
➜ mvn compile exec:java -q "-Dexec.args=036000291450 20"
Expected check digit 2 but found 0.
Turning in Your Work
To turn in your lab/project work for this week, first make sure that all your code has been committed to your Git repository and pushed to Github. When you click on the source control tab in the left-hand sidebar of Visual Studio Code, the tab should report that no files have been changed and no changes need to be pushed/synced with main.
Once you have done this, you can navigate to the appropriate entry on Gradescope and upload your work. When uploading your project, you must select the option to upload it via Github. If you choose another method, then the directory structure of your project will be either inconsistent with what our grading infrastructure expects or deleted entirely from the submission.
Note that the Gradescope submission page features an autograding script that runs on every submission. Check the autograder results to ensure that your submission meets basic correctness tests. Note that not all aspects of your submission will be checked via the autograder, but we will try to check as much as possible, so you can receive quick feedback on your work!