Introduction
COVID-19 has changed our world, and as part of that change, Computer Science and various applications have shined. During isolation, everyone found the value of telecommuting, a concept which CS has used for industry for a while. At CSU, research around COVID-19 has continued throughout the entire pandemic, including the Computer Science department, who has research both in looking at the health data, and the Cyber Security Center is looking at security around data integrity and news. When the pandemic first started, numerous models came out, and the amount of data being collected and shared for all to research. John Hopkins, New York Times, CDC, and The World Health Organization are just a few of many examples of data visualizations of the pandemic.
For this assignment, you will be building an HTML Table generator based on the COVID-19 data provided by WHO. This is fairly common to do in industry, and many HTML pages are actually generated server-side using templates and data. Even this website uses a static site generator called Jekyll, so we can have templates for building the pages. You will only be implementing a single tag, but there is a lot to do for that one special customized tag. The customized tag will be a WHO Table, which connects the data from WHO to the web-page you are making.
What you will learn
- Loops
- File Reading and Writing
- String parsing
Provided Code
As with the previous two practical assignments, you will want to go through canvas to download the code, and add the files to the project. However, the one difference for this assignment, is that you will be adding some files from scratch, so there won’t be templates except for one file. Overall, we are providing the “data layer” for the assignment, and you are building the HTML generator or view layer of the assignment.
Suggested File Structure
After you download the files, we recommend you store the files as follows (from the project root)
- src
All .java files go in your src folder, including ones you will create. - tmpl
Storing the .tmpl files in this directory, will make it easier to edit them. - data
You should store th who-data.csv file here. - examples
You should store the .html files here. You will actually be writing .html to the root path, but by putting these files in a separate directory, you won’t accidentally overwrite them. You will want to copy the style.css file twice - into this directory, and your root directory. You can open them in your web-browser, to see what you will be working towards.
You can also see the examples of what you will be building here:
At this point, you should take the Code Tracing Quiz in canvas.
You will also have some questions about HTML to make your life easier for coding. If you haven’t learned HTML, you can go to W3Schools in particular, you will need to learn about the table tag. To save time, don’t learn about all of the HTML world now! You probably don’t need to spend five minutes on it.
We will be making use of the following tags:
Step 1 - HtmlFormat.java
Your first step is to write the HtmlFormat.java class. In Eclipse, you will want to create a new class, stored in the src directory. The classes name should be HtmlFormat with the matching file name being HtmlFormat.java.
You will implement the following methods in the class. They are all public, static, and all return a String.
header(String str, int size)
This method uses String.format to build a heading tag based on the size passed in. For example:
1
return String.format("<H%d>%s</H%d>%n", size, str, size);
Once you have this method written, you will implement the following methods
- h1(String str)
Returns an <h1> tag with the value of str between. hint:return header(str, 1);
- h2(String str)
returns an <h2> tag with value of str between. - h3(String str)
returns an <h3> tag with value of str between. - h4(String str)
returns an <h4> tag with value of str between.
Testing header and h1-h4
You will notice there are no Unit Tests written. You will want to write your own at this point to make sure the proper HTML is being outputted when the methods are called.
To run the unit tests, run the program with the --tests
program argument similar to what you did in practical 3.
table commands
Now you will write the heart of HtmlFormat - the table methods. You will implement the following methods:
- table(String str, String caption)
Returns a String in the format of
- thead(String str)
Returns a String in the format of
- tbody(String str)
Same as thead, but using the tbody tag. - th(String str)
Same as tbody, but using the th tag. - td(String str) Same as th, but using the td tag.
- tr(String str, String style)
Returns a String of format. Don’t forget to escape your quoted values.
To make reading the file easier, you can add tab characters \t\t\t<th>str</th>%n
.
You will notice there is no space between your String value and the tag. That does matter.
Any pre and post spacing (the tabs) won’t matter, and it makes looking at your output easier.
Testing table methods
You will really want to write unit tests to make sure you can build a properly formatted table. Make sure to have multiple columns, so you have to think about the order you have to call the methods in to get what you want (hint: table(String, String) is the last method you call, after you build the inside). Making sure you do this will not only help you debug your methods, but also give you an advantage on a later part of this assignment!
Example:
1
2
3
4
5
6
7
8
9
10
11
12
System.out.println(HtmlFormat.table(
HtmlFormat.thead(
HtmlFormat.tr(
HtmlFormat.th("Col Header") +
HtmlFormat.td("Col head2") +
HtmlFormat.td("Col head3"), "heads"
)) +
HtmlFormat.tbody(
HtmlFormat.tr(
HtmlFormat.th("Row Header") +
HtmlFormat.td("Cool") +
HtmlFormat.td("Runnings"), "odd")), "My caption"));
You could also set all the ‘inner sections’ to variables for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String tableHead = HtmlFormat.thead(HtmlFormat.tr(
HtmlFormat.th("Col Header") +
HtmlFormat.td("Col head2") +
HtmlFormat.td("Col head3"), ""));
String row1 = HtmlFormat.tr(HtmlFormat.th("Row Header") +
HtmlFormat.td("Cool") +
HtmlFormat.td("Runnings"), "odd");
String row2 = HtmlFormat.tr(HtmlFormat.th("Row2 header") +
HtmlFormat.td("Lost") +
HtmlFormat.td("In Space"), "even");
String tableBody = HtmlFormat.tbody(row1+row2);
String table = HtmlFormat.table(tableHead+tableBody, "This is a cool table");
System.out.println(table);
Should output (depending on your spacing and line returns-which are optional):
And if you are curious, this is what the table would look like in html
Col Header | Col head2 | Col head3 |
---|---|---|
Row Header | Cool | Runnings |
Row2 header | Lost | In Space |
Pro Tip
You will notice in this assignment, most every method takes in something, and tries to return something. This is called functional programming. It actually allows debugging to be much easier. Imagine if you had to keep track of where you were at in the table trying to build it from the top down. Instead, we build it from the inside out, based on sections, and everything is done without knowledge of the other.
Step 2: Query.java
You have completed HtmlFormat.java, so lets move onto the next class you will create, Query.java. As before, you will want to create a new file called Query.java and with the class name Query (always true for java, file names must match the class names). Query creates an immutable class (meaning once the data is created, it can’t be changed / mutated), that will be used to query the dataset, and also determine what you need to do with that data.
The methods you will implement are as follows:
- public String getRegion()
- public String getCountry()
- public boolean isCumulative()
- public Query(String[] tokens)
You will notice they are only accessors and the constructor. When we wrote it, we also added a private method to help the constructor, but that is optional / how you choose to break up your code.
getRegion(), getCountry(), isCumulative()
These three accessor methods are direct and return the values stored in private instance variables, that we called region, country, cumulative. All three of the variables have a default value.
- region and country, are Strings and default to “ALL”
- cumulative, is a boolean and defaults to true
Query(String[] tokens)
The constructor of Query does all the work, but we did write a helper method to simplify our code (called parse). What query does is go through each (hint: enhanced for loop), value in tokens. You then parse that value. What does parsing entail?
- Does the value contain a
:
? if it does, continue, if not, skip. Only values split by the colon character are valid. - Splitting on the
:
The first part is the variable to modify, the second part is the value.country:US
would take US and store it in the private instance variable country. (US could be anything)region:AMRO
would take AMRO and store it in the private instance region variable. (AMRO could be anything)cumulative:false
would take the string value, and convert it to a boolean, and store the answer in cumulative. (could be true or false)
You do not need to error check the second half of the statement, except in the case of Boolean (good use of try-catch, and also toLowerCase(). As such, the overall parse method is a series of if/else if checks, that then have a line to set values. Anything not in that list, you can ignore.
As a reminder, to convert to boolean, the code is as follows:
1
Boolean.parseBoolean(value.toLowerCase()); // depending on where the value is stored
Testing Query
Testing Query.java is extremely important, and if done correctly, will help you understand how it works with the provided code in addition to make sure it is working. You should go to UnitTests.java and write your own tests. One hint, declare a number of String arrays. For example:
1
2
3
4
5
6
7
8
String[] test1 = {"country:US"};
String[] test2 = {"region:AMRO"};
String[] test3 = {"country:US,JP,IT", "cumulative:false"};
String[] test3 = {"should", "skip", "these", "country:US,JP,IT", "cumulative:false"};
Query q1 = new Query(test1);
Query q2 = new Query(test2);
Query q3 = new Query(test3);
Query q4 = new Query(test4);
How would you check your values are getting set properly? Use your accessors and print the results!
You can look at who-data.csv
to see various options for regions and countries.
Testing Query with WhoData
Often in the testing stage, you want to test your object with other objects you didn’t necessarily write, so you can better understand what they are doing. It is also essential for the next step, so before you move on, you will want to get a better idea of how WhoData works.
To construct a WhoData object, it needs the file to read. The if you stored the file in the data directory, it would look like the following:
1
2
3
4
5
WhoData data = new WhoData(new CSVReader("data/who-data.csv"));
WhoDataItem[] items = data.queryData(query);
for(WhoDataItem item : items) {
System.out.println(item);
}
You should try using different queries. Also, you could reduce the lines to be the following:
1
2
3
4
WhoData data = new WhoData(new CSVReader("data/who-data.csv"));
for(WhoDataItem item : data.queryData(query)) {
System.out.println(item);
}
It is important you understand how query is working. A couple things to notice, everything is always sorted by country, so if your country changes, you may want to print a line between! This would also be good to try to see if you can do that, and will help later.
After you complete this, you may want to submit for grading, using one of your submission attempts.
Pro Tip
The second way putting the call in the loop itself is actually the better way to write it. That way someone could change the type of underlining structure, and as long as you can iterate through it, you don’t care what it is.
Step 3 - Writing WhoTable.java
The WhoTable class follows the steps needed to build a specialized tag in the Template engine. For example, in the .tmpl files, you will see:
The format of the commands should look familiar based on the Query.java class you just finished! Your ultimate goal is to take in a String[] array, and a WhoData object, and build series of HTML tables to return out of the class as a String.
public WhoTable(WhoData data, String[] queryData)
When creating the class, you will want to build the following constructor. The class needs two instance variables to work:
- final WhoData data;
- final Query query;
The constructor sets the values of those two variables. The data is
passed in from outside the object, so you can set that directly, this.data = data;
The Query object needs to be constructed, but as you can see the String[] queryData array matches the constructor for Query, so that can be passed in directly. Making your constructor look like:
1
2
3
4
public WhoTable(WhoData data ,String[] queryData) {
this.data = data;
this.query = new Query(queryData);
}
Step 4 - WhoTable Helper Methods
While the primary method in WhoTable is .render(), there are a number of helper methods we found essential to make sure we are keeping it simple and divide, conquering, and gluing our problem! While these methods are often private, we are going to ask you to specify them as package protected (no scope/default scope), so we can grade them. You are also free to add additional methods beyond these ones.
headerRow() and closeTable()
You can copy the following bit of code directly into WhoTable.java. This is to give you an idea of what you need to do for the other methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String headerRow() {
return HtmlFormat.thead(
HtmlFormat.tr(
HtmlFormat.th("Date") +
HtmlFormat.th("Cases") +
HtmlFormat.th("Deaths"), ""));
}
String closeTable(String str, WhoDataItem last) {
String caption = "";
if(last != null) {
caption = String.format("%s <br/>Region: %s", last.getCountry(), last.getWhoRegion());
}
return HtmlFormat.table(headerRow() +
HtmlFormat.tbody(str), caption);
}
void setTotals(Query query, WhoDataItem data, int[] totals)
Set setTotals uses the fact that Arrays are objects to modify the totals array. It uses both the Query object and the WhoDataItem to modify totals. Most notably, query.isCumulative() and data.getCases(), data.getDeaths(). The totals array is only two deep, and tracks cases in the 0 index, and deaths in the 1 index. However, the total changes based on the query.
If the query is cumulative, then you add newCases() and newDeaths() to the current value in totals.
If the query is not cumulative, then you just reset the value in totals with the values in newCases() and newDeaths()
As always, you should test this method. You can construct both a Query and WhoDataItem directly without using the data from the CSV file.
String buildRow(WhoDataItem data, int[] totals, boolean isOdd)
The buidRow method takes in the totals array, the data item and whether or not the row is an odd row in the table or not. The output of the method would be as follows:
Needless to say, you are going to make use of the following methods:
- HtmlFormat.tr
- HtmlFormat.th
- HtmlFormat.td
For the ‘style’ you will use the isOdd parameter to set the style to “odd” if isOdd is true, or “even” if isOdd is false.
Testing
Remember, keep it simple. These methods only uses the parameters passed into them! That is it. You can test these methods in RunTests before you move on and should!
Step 5 - WhoTable : render()
It will help to write out what you want to do in .render() before you do it. It is an easy method to get confused if you overthink it.
What you are doing in render is building a very large String, and returning that String. What
that String contains is the html code for a table for every country in the query! For example
if the query is country:US,IT,JP
, it would contain three separate tables, all stored as in the String.
If the query is country:ALL
, it would a table for every country!
The problem is you never know exactly how many tables you will need to generate, but you do know every time the country changes, you can add a table as the entries are always in order of coutries.
This means the algorithm was as follows for us:
- Declare and initialize a return (often called rtn) String
- Have a rowCounter set to 0, so you can keep track of even odd rows
- Create an int[] array of two spots (we called our totals)
- Set WhoDataItem last to null (You will quickly change this)
- Query the data with the query (both stored in instance variables), and start looping through returned results (think about the tests you wrote!)
- Check to see if the country is different from last
- if the same:
- Update totals using
setTotals
- add the row to a String tracking the single table contents using
buildRow
. Note forbuildRow
modulo will help with even or odd. - after updating totals using . Update rowCounter.
- Update totals using
- if different, call the closeTable() method, and add the result to the rtn String, reset totals, reset the temp String, reset rowCounter
- if the same:
- At the bottom of my loop, we set our WhoDataItem last equal to the current item
Don’t forget to close the table after the loop for the last country.
Essentially, the loop would be three lines if it wasn’t for the condition that every country needs a separate table.
Testing WhoTable.java
Now is the time you can test it. Query the data, and print out the results from calling .render()! Make sure this is working as expected.
After you complete this, you may want to submit for grading, and use one of your submission attempts, as this is now completes 90% of the practical. The last 10% is the part that glues it all together in this divide-conquer-glue.
Step 6 - Templater.java
The Templater.java focuses on reading the template provided, and outputting valid HTML. It involves both reading in the template file, and writing out the contents of a new file. There are only two methods you have to implement, but we would suggest thinking about how you can break the problem up into smaller methods (we used two private helper methods).
The two required methods are:
- public Templater(WhoData data)
Your constructor. It takes WhoData data, and sets it to an instance variable. - public void write(String templateFile)
You will notice in your template, it has “throw new NotImplementedException();”. You can remove this line. It is common in industry to use that exception, as it makes it obvious if you call a method that isn’t written yet.
The heart of the work is in write. A process is as follows:
- While reading in from your template, either:
- write the line directly to your outfile (
PrintWriter
) - if the line matches the format below:
- use WhoTable to generate HTML table code, and then write the entire result of the WhoTable render method to the your outfile. - make sure to remember to close the files when done.
- write the line directly to your outfile (
How do you know when to build a WhoTable as compared to just write the line directly? We have designed a specialized “tag” that your program is looking for.
who-table tag
The format for the who-table tag is:
You can also open one of the .tmpl files to see it in action.
Valid Assumption/Pre-condition
While not common in templating languages, for this class
you can assume the tag is the only thing on that line in the template file. As such, you
can use the following code (depending a bit on how you set it up)
1
2
3
4
5
6
7
8
9
10
11
12
Scanner fileScanner = new Scanner(new File(templateFile));
// loop
// inside the loop
String line = fileScanner.getNextLine().trim();
if(line.startsWith("{{")) {
// parse the tokens by grabbing the substring between {{ and }} (Split)
int first = line.indexOf("{{")+2;
String[] tokens = line.substring(first, line.indexOf("}}", first)).trim().split("\\s+");
// construct a WhoTable, set value of render to lineToWrite
}
//
This may change slightly based on your helper methods. The \s+ is something you should use as it causes the String to split on any white space (tabs or spaces).
Pro Tip
While not learned until a future class \s+ is a regular expression. Regular expressions are powerful ways in which to parse Strings, look for matches in Strings and more! While we don’t suggest you look at them for this class, if you are comfortable with the content, here is a tutorial for later use about them.
Testing Templater.java
You will want to test as you develop, including using ample print statements like Logger.debug
.
As a reminder, your program args options are as follows:
At this point, you can also modify your program arguments to read-in the datafile. For example:
--tests -d data/who-data.csv tmpl/test.tmpl
or
--tests -d data/who-data.csv tmpl/test.tmpl tmpl/us.tmpl tmpl/euro.tmpl tmpl/us-it-jp.tmpl tmpl/regions.tmpl
Pro Tip
The {{ tag }} syntax is known as mustache templating. Yes, think of the emote face :{). There are many types of templating markups out there, and the markup used for this website is Liquid, which is based on mustache.
Running the Program
At this point, you should have a fully functional program. As long as your style.css file is in the same directory as your .html files, you should have some nicely formatted web pages. If it isn’t in the same directory, the formatting may look odd, but the content is there. If you open them in Eclipse, it will show just the text (markup) behind the hyper text markup language. However, if you open those files in your web browser, you will see them as web pages.
Here are some examples of running the program without the tests
data/who-data.csv tmpl/test.tmpl
or
data/who-data.csv tmpl/test.tmpl tmpl/us.tmpl tmpl/euro.tmpl tmpl/us-it-jp.tmpl tmpl/regions.tmpl
You can view each of the files below to view how they should look:
Step 8 - Turning in and Reflection
You should now turn in your code, and write the reflection in Canvas. What did you learn? What could you improve upon in the future? How did designing a class from scratch go? The next practical will involve you writing all th code from scratch, but you do have all these classes to help you! Are you ready?
Did working with HTML spark interest in designing your own website? As someone with a course in this department, you already have a web server setup. You can learn about this here. Though many students also setup their git web page as part of their git account.
Going Further
You explored two major areas in computer science with this practical. Data Visualization / Human Centered Computing and Big Data. While admittedly, the COVID-19 data we provided is not big data, the amount of information being generated by COVID-19, even to break it down to that file is big data. Going beyond this course, we have two concentrations in the department you may want to look at. If you enjoyed the idea of how to display and interact with the data, HCC would be something good to look with a focus on stats. If you really like getting and analyzing the data, Systems is important in how you do that. You can learn about the research labs at CSU, and many undergrads are encouraged to get involved in research.