Programming Assignment 2
CS 33600
Network Programming
Spring, 2026

This assignment makes use of the files contained in this zip file. This assignment is due Tuesday, February 10.

In this assignment you will use the command-line, command-line arguments, standard I/O streams, pipes, environment variables, and a configuration (properties) file.

This assignment is based on the code examples from the simple_ipc sub folder of streams_and_processes.zip and the explanations in Readme_simple_ipc.md and Readme_streams.md.

In this assignment you will write a Java filter program that formats a jumbled stream of input numbers into nicely organized columns. To determine the parameters for the formatting your program will make use of command-line arguments, environment variables, and a configuration file. To test your program you will use the Windows command-line.

Write a Java program called Filter.java that reads from standard input and writes to standard output. The input to your program will be a sequence of positive decimal numbers, which are all less than 10,000, separated by random amounts of white space. The output of your program will be the input numbers grouped and formatted into columns.

Here is an example of a stream of input numbers.

5163.1575670644243                 6806.8180791650138
                 8977.2739646595655
      2598.0792870876185                 7162.5237586596268          6691.2041993469038
 1043.6422009949033
    6922.8216193121125          3.0480056154058  9926.6081118198181  100.3082369457076
5135.1567125461588      7808.2382885219886     1439.6542863246559        249.6179692983795
  214.0065309610279                 9280.5883968626968             2687.3871883297220
7612.8426770836513         6644.2027649769589  8364.5604419080173      4740.7550279244360
254.6181218909269         2500.3814813684498
      2293.6803491317482                  835.3306680501725
        5962.7923215430155    9622.5988341929387    57.3069246498001
        1557.9630726035341                8398.5614795373385         5958.4870143742182
   2568.3835566270945             9935.9135715811644              3410.1040681173131
982.0299691763055
  8393.5613269447913           9066.2766808069100                 4896.4546037171549
     7597.8422193060087            8551.5661488692895   1076.6432081057162
 1911.3635059663686         7586.8418836024048       9282.8936429944770 4696.1433149204995
 1388.0423596911528
                 1936.3642689291055     3408.4091921750542              3556.4137089144565
     9241.8923917355878          5003.4578691976685               3366.7130954924160
  4270.1303140354621   620.6292916653950    4700.7538071840572         1766.0538956877347
              441.6238288521989            8153.8591875972779

Here are those numbers grouped into a single group, then formatted into three columns, and printed with 13 digits of precision. Notice that there are exactly two spaces between the columns.

5163.1575670644243  6806.8180791650138  8977.2739646595655
2598.0792870876185  7162.5237586596268  6691.2041993469038
1043.6422009949033  6922.8216193121125     3.0480056154058
9926.6081118198181   100.3082369457076  5135.1567125461588
7808.2382885219886  1439.6542863246559   249.6179692983795
 214.0065309610279  9280.5883968626968  2687.3871883297220
7612.8426770836513  6644.2027649769589  8364.5604419080173
4740.7550279244360   254.6181218909269  2500.3814813684498
2293.6803491317482   835.3306680501725  5962.7923215430155
9622.5988341929387    57.3069246498001  1557.9630726035341
8398.5614795373385  5958.4870143742182  2568.3835566270945
9935.9135715811644  3410.1040681173131   982.0299691763055
8393.5613269447913  9066.2766808069100  4896.4546037171549
7597.8422193060087  8551.5661488692895  1076.6432081057162
1911.3635059663686  7586.8418836024048  9282.8936429944770
4696.1433149204995  1388.0423596911528  1936.3642689291055
3408.4091921750542  3556.4137089144565  9241.8923917355878
5003.4578691976685  3366.7130954924160  4270.1303140354621
 620.6292916653950  4700.7538071840572  1766.0538956877347
 441.6238288521989  8153.8591875972779

On the other hand, here are those same numbers grouped into groups of 12, then formatted into seven columns, and printed with only two digits of precision. Notice the blank line between groups. Also notice that not every line of output must have all seven columns.

5163.16  6806.82  8977.27  2598.08  7162.52  6691.20  1043.64
6922.82     3.05  9926.61   100.31  5135.16

7808.24  1439.65   249.62   214.01  9280.59  2687.39  7612.84
6644.20  8364.56  4740.76   254.62  2500.38

2293.68   835.33  5962.79  9622.60    57.31  1557.96  8398.56
5958.49  2568.38  9935.91  3410.10   982.03

8393.56  9066.28  4896.45  7597.84  8551.57  1076.64  1911.36
7586.84  9282.89  4696.14  1388.04  1936.36

3408.41  3556.41  9241.89  5003.46  3366.71  4270.13   620.63
4700.75  1766.05   441.62  8153.86

The Filter.java program requires three parameters. One parameter determines the number of output columns. Another parameter determines the number of digits that are printed after a decimal point (this is called the precision of a decimal number). The third parameter determines how many numbers get grouped together.

The formatted numbers should be "right justified" in each output column, which means that all the decimal points in a column should line up vertically. The total width of an output column is 4 + 1 + precision characters. There should always be exactly two spaces between the output columns. Each group of numbers should be separated by a blank line.

The next several paragraphs specify how the Filter.java program finds a value for each of these three parameters using command-line arguments, environment variables, and a configuration file.

In Filter.java, the default number of output columns should be three. If the properties file filter.properties exits in the current directory, and if that properties file contains a key with the name "columns", and if the value of that key parses to a strictly positive integer value, then the value of the "columns" key overrides the default number of output columns. If there is an environment variable called CS336_COLUMNS, and if the value of that variable parses to a strictly positive integer value, then the value of that environment variable overrides the default number of columns and the number of columns set by the properties file (if it exits). If there is a first command-line argument, and if the value of that argument parses to a strictly positive integer value, then the value of that command-line argument overrides the environment variable (if there is one), the properties file (if it exits), and the default number of columns. Any improper value for the columns parameter should be ignored.

The decimal points of all the numbers in an output column must line up. You line up the decimal points by calling the printf method with an appropriate format string. The number of digits after a decimal point is called the precision of a decimal number. All the numbers in the output will have the same precision. Not all of the numbers in a column will have the same number of digits. Each number in a column should be padded with spaces on the left of the number to fill up its column.

In Filter.java, the default precision for your output numbers is 13 decimal places. If the properties file filter.properties exits in the current directory, and if that properties files contains a key with the name "precision", and if the value of that key parses to a strictly positive integer value, then the value of the "precision" key overrides the default precision. If there is an environment variable called CS336_PRECISION, and if the value of that variable parses to a strictly positive integer value, then the value of that environment variable overrides the default precision and the precision set by the properties file (if it exits). If there is a second command-line parameter, and if the value of that parameter parses to a strictly positive integer value, then the value of that command-line parameter overrides the environment variable (if there is one), the properties file (if it exits), and the default value of precision. Any improper value for the precision parameter should be ignored.

The input numbers should be combined into groups with the members of each group being formatted into the appropriate number of columns and with a blank line separating each group. In Filter.java the default length of a group should be 0, which means that all the input numbers should be placed in a single group. If the properties file filter.properties exits in the current directory, and if that properties file contains a key with the name "groups", and if the value of that key parses to a non-negative integer value, then the value of the "groups" key overrides the default length for groups. If there is an environment variable called CS336_GROUPS, and if the value of that variable parses to a non-negative integer value, then the value of that variable overrides the default group length and the group length set by the property file (if it exits). If there is a third command-line parameter, and if the value of that parameter parses to a non-negative integer value, then the value of that command-line parameter overrides the environment variable (if there is one), the property file (if it exits), and the default length for groups. Any improper value for the groups parameter should be ignored.

To retrieve property values from the filter.properties file (if it exists) your Filter.java program first needs to create a Properties object. Then your program should create a File object (for the file filter.properties) and a FileInputStream object (from the File object) and use it to load the properties from the properties file. After the properties have been loaded into the Properties object, you use the getProperty method to see if a particular key has a value. The properties file is a text file, so the "integer" values in it are actually strings. Use the parseInt() method to convert them to int values. If the load() method throws a FileNotFoundException, that means that the filter.properties file does not exist, so you can go on to looking for the environment variables.

Your program should use the getenv() method to see if there are environment variables named CS336_COLUMNS or CS336_PRECISION or CS336_GROUPS. If any of these environment variables exists, then its string value should be converted to an integer value by using the parseInt() method.

Your program should get its command-line arguments by using the args parameter to your program's main() method. Command-line arguments, like environment variables, are always strings. So you need to use parseInt() to convert a command-line argument into an int value.

Your program should read the sequence of input numbers from standard input by using the Scanner class methods hasNextDouble() and nextDouble().

Your program should NOT read all of its input numbers into an array or list. That is a very bad strategy, for several reasons. Your program does not know how much data it will receive, so it does not know how much memory is needed to store all the input data (it may be more that what is available to you on the current computer). Your program will not produce any output until all the input data has been received, so your program will appear "dead" and unresponsive. If your program is in the middle of a data pipeline, then your program becomes a bottle neck for the whole pipeline, blocking all the data that needs to pass through the pipeline. Your program (and almost any filter program) should process each piece of input data as soon as it receives it, and output the processed data right away.

Your program should write formatted numbers to standard output by using the printf() method and its associated formatting strings.

In the zip file for this assignment you will find a program called Source.java that you can use to test your program. The program Source.java writes to standard output a stream of random decimal numbers. The numbers are separated by random amounts of white space and there are a random number of random numbers on each line of output. You can test your Filter.java program by piping the standard output of Source into the standard input of your Filter. For example,

     hw2> java Source | java Filter

The Source program accepts three optional command-line arguments. The first command-line argument is an integer that determines how many lines of output Source should produce. The second command-line argument is an integer that determines how many numbers are on each output line. The third command-line argument is an integer that determines the range of the random numbers (the default range is from 0 to 10,000). So, for example, the following command-line will produce three lines of output, with five numbers per line, and the random numbers are all from the range 0 to 200.

     hw2> java Source 3 5 200

Without any command-line arguments, Source will produce a random number of output lines (up to 100) and each output line will contain a random number of doubles (up to 10) and each random double will be less than 10,000.

The Source program always outputs doubles with 13 digits after the decimal place.

You can give the Filter program command-line arguments. The following command-line will output groups of 16 numbers in 10 columns with 5 digits after the decimal place.

     hw2> java Source | java Filter 10 5 16

You can set an environment variable by using the following shell command.

     hw2> set CS336_GROUPS=14
     hw2> java Source | java Filter 4 5

The previous commands used a pipe (the character '|'). Using a pipe is equivalent to the following two commands that use I/O redirection.

     hw2> java Source > temp.txt
     hw2> java Filter 4 5 14 < temp.txt

The first command redirects the standard output from Source into a temporary file called temp.txt and then the second command redirects the contents of temp.txt into the standard input of Filter. (How would you save the resulting output from Filter in a file called data.txt?) The piped version of the command has the advantage of not needing a temporary file. But the redirected version of the commands has the advantage that you can reuse the same data file in multiple Filter trials.

For example, the following command-line will produce 10 lines with 3 doubles per line.

     hw2> java Source 10 3 500

The following command-line pipes the randomly generated data directly into the Filter process.

     hw2> java Source 10 3 500 | java Filter 4 6 10

The following command-line will save the randomly generated data in a file for possible use in testing the Filter program.

     hw2> java Source 10 3 500 > myData.txt

The following command-lines redirect the saved data file into the Filter process using different parameters.

     hw2> java Filter 4 6 10 < myData.txt
     hw2> java Filter 3 8 12 < myData.txt

Be sure to test your program with command-lines like the following. (Be sure you know what these command-lines mean.)

     hw2> java Filter 4 -1 10 < data.txt
     hw2> java Filter x  y 10 < data.txt
     hw2> java Filter 0 -1 0  < data.txt
     hw2> java Filter 2  x y  < data.txt

In the zip file for this assignment you will find an executable jar file demo program, called filter_demo.jar, that you can use to demo this assignment. You can pipe the output from Source into the demo program with this command-line.

     hw2> java Source 4 5 100 | java -jar filter_demo.jar 4 5 12

Your version of Filter.java should behave exactly like the demo version.

In the zip file for this assignment you will also find a sample properties file, filter.properties.

In the zip file there are files data.txt, test_filter.cmd, and test_filter_results_correct.txt that help you test your completed version of Filter.java. Once you have Filter.java written and compiled, you can double click on the file test_filter.cmd which will run your Filter program several times with the data from data.txt as stdin and gather all the results into a file called test_filter_results.txt which you can then compare with test_filter_results_correct.txt. Your test_filter_results.txt file should be exactly the same as test_filter_results_correct.txt.

In the zip file there is another test script, test2_filter.cmd, that contains several example command-lines that your Filter program should be able to handle properly.

Do not try to write Filter.java all at once! Write it in stages. Break the problem down into sub-problems and solve them one at a time. For example, here is an outline of how you might go about attacking this problem.

Make sure you test your program under a wide variety of conditions. With and without a configuration file, with and without environment variables, with and without command-line arguments.

When you want to set an environment variable from the command-line, be careful not to use a command like the following, with extra spaces around the =.

     hw2> set CS336_COLUMNS = 7

This creates an environment variable called "CS336_COLUMNS ", with a trailing space, and gives it the value " 7", with a leading space.

When you set an environment variable, it only exits in the command window where you created it. So, for example, you cannot create an environment variable in one command prompt window and then run your program from an IDE or an editor or another command prompt window.

In the zip file there are sub-folders Command-line_arguments, Environment_variables, and Properties_file, with sample code that demonstrates, respectively, using command-line arguments, environment variables, and a properties file.

Here is a list of some classes and methods that you can use.

Turn in a zip file called CS336Hw2Surname.zip (where Surname is your last name) containing just your version of Filter.java.

This assignment is due Tuesday, February 10.