[Solved] CS2030s Lab 6-Logging

$25

File Name: CS2030s_Lab_6-Logging.zip
File Size: 197.82 KB

SKU: [Solved] CS2030s Lab 6-Logging Category: Tag:
5/5 - (1 vote)

Topic Coverage

  • Generics
  • Function
  • Lambda expression
  • map and flatMap

Problem Description

In this lab, we are going to write an immutable Logger class to handle the context of logging changes to values while they are operated upon. In a way, the logger separates the code for logging from the main processing. Each logger wraps around a value, and everytime map or flatMap is called, a new Logger object is created. The printlog method can be invoked the output the log.

A sample logging session is shown below:

jshell> Logger.make(5)$.. ==> Logger[5]jshell> Logger.make(5).printlog()Value initialized. Value = 5jshell> Logger.make(5).map(x -> x + 1)$.. ==> Logger[6]jshell> Logger.make(5).map(x -> x + 1).printlog()Value initialized. Value = 5Value changed! New value = 6jshell> Logger.make(5).map(x -> x * 1)$.. ==> Logger[5]jshell> Logger.make(5).map(x -> x * 1).printlog()Value initialized. Value = 5Value unchanged. Value = 5

Notice that the log of value changes through time is output whenever the printlog() method is called. There are three different types of log messages:

  • During initialization: Value initialized. Value = ..
  • When value is modified: Value changed! New value = ..
  • When value remains unchanged: Value unchanged. Value = ..

Task

Your task is to write a Logger class that provides the operations make, equals, printlog, map, flatMap and test. You will also write several applications using the Logger as solutions to classic computation problems. This would allow us to look at the values changes when solving each problem.

This task is divided into several levels. Read through all the levels to see how the different levels are related.

Remember to:

  • always compile your program files first before using jshell to test your program
  • use checkstyle and javadoc comments to enhance code readability and facilitating code review

Level 1

Creating the logger

Start by writing a static method make to wrap a value within a Logger. Include the toString() method as well as the printlog() method to return the string representation of the Logger and output the log messages respectively. At this point of time, there are only initialization messages. Finally, include an equals method that returns true if the argument Logger object is the same as this, or false otherwise. Two Loggers are equal if and only if both the wrapped value as well as the logs are the same.

jshell> Logger.make(5)$.. ==> Logger[5]jshell> Logger.make(5).printlog()Value initialized. Value = 5jshell> Logger.make("hello")$.. ==> Logger[hello]jshell> Logger.make("hello").printlog()Value initialized. Value = hellojshell> Logger.make(5).equals(Logger.make(5))$.. ==> truejshell> Logger.make(5).equals(5)$.. ==> falsejshell> Logger.make(5).equals(Logger.make("five"))$.. ==> falsejshell> Logger.make(5).equals((Object)(Logger.make(5)))$.. ==> truejshell> /exit

Check the format correctness of the output by running the following on the command line:

$ javac -Xlint:rawtypes *.java$ jshell -q your_java_files_in_bottom-up_dependency_order < test1.jsh

Check your styling by issuing the following

$ checkstyle *.java

Level 2

Creating a valid logger

We need to prevent the make method from being misused by nesting or chaining the methods, e.g.

  • Logger.make(Logger.make(5))
  • Logger.make(5).make(7)

In the case of nesting make methods, we throw an IllegalArgumentException everytime the make method takes in another Logger as argument.

To prevent chaining, we can make the Logger an interface with the static method make, and let LoggerImpl be the concrete class that implements the functionalities of logging. Here, we restrict the chaining of make method calls at the expense of introducing a cyclic dependency between Logger and LoggerImpl that is, unless someone comes up with a better idea

In addition, passing a null to make will also result in an IllegalArgumentException thrown.

jshell> Logger.make(5)$.. ==> Logger[5]jshell> Logger.make(5) instanceof Logger $.. ==> truejshell> try { Logger.make(Logger.make(7)); } catch (Exception e) { System.out.println(e); }java.lang.IllegalArgumentException: already a Loggerjshell> Logger.make(5).make(7)|  Error:|  illegal static interface method call|    the receiver expression should be replaced with the type qualifier 'Logger<java.lang.Integer>'|  Logger.make(5).make(7)|  ^--------------------^jshell> try { Logger.make(null); } catch (Exception e) { System.out.println(e); }java.lang.IllegalArgumentException: argument cannot be nulljshell> /exit

Check the format correctness of the output by running the following on the command line:

$ javac -Xlint:rawtypes *.java$ jshell -q your_java_files_in_bottom-up_dependency_order < test2.jsh

Check your styling by issuing the following

$ checkstyle *.java

Level 3

The map method

Include a map method that takes in a function of the form Function<? super T, ? extends U>, applies it on the value, and wraps the result in a Logger.

jshell> Logger.make(5)$.. ==> Logger[5]jshell> Logger.make(5).printlog()Value initialized. Value = 5jshell> Logger.make(5).map(x -> x + 1)$.. ==> Logger[6]jshell> Logger.make(5).map(x -> x + 1).printlog()Value initialized. Value = 5Value changed! New value = 6jshell> Logger<Integer> a = Logger.make(5)jshell> a.printlog()Value initialized. Value = 5jshell> a.map(x -> x + 1)$.. ==> Logger[6]jshell> a.printlog()Value initialized. Value = 5jshell> Logger.make(5).map(x -> x)$.. ==> Logger[5]jshell> Logger.make(5).map(x -> x).printlog()Value initialized. Value = 5Value unchanged. Value = 5jshell> Logger.make(5).equals(Logger.make(5).map(x -> x))$.. ==> falsejshell> Logger.make(5).map(x -> x).equals(Logger.make(5))$.. ==> falsejshell> Logger.make(5).map(x -> x + 1).map(x -> x - 1)$.. ==> Logger[5]jshell> Logger.make(5).map(x -> x + 1).map(x -> x - 1).printlog()Value initialized. Value = 5Value changed! New value = 6Value changed! New value = 5jshell> Logger.make(5).map(x -> x + 1).map(x -> x - 1).equals(Logger.make(5))$.. ==> falsejshell> Logger.make("hello").map(String::length)$.. ==> Logger[5]jshell> Logger.make("hello").map(String::length).printlog()Value initialized. Value = helloValue changed! New value = 5jshell> Function<Object, Boolean> f = x -> x.equals(x)jshell> Logger.make("hello").map(f)$.. ==> Logger[true]jshell> Function<String, Number> g = x -> x.length();jshell> Logger<Number> lognum = Logger.make("hello").map(g)jshell> lognumlognum ==> Logger[5]jshell> /exit

Check the format correctness of the output by running the following on the command line:

$ javac -Xlint:rawtypes *.java$ jshell -q your_java_files_in_bottom-up_dependency_order < test3.jsh

Check your styling by issuing the following

$ checkstyle *.java

Level 4

The flatMap method

We are now ready to write the flatMap method that takes a function of the form Function<? super T, ? extends Logger<? extends U>>. Pay special attention to how the logs are to be combined.

jshell> Logger.make(5).printlog()Value initialized. Value = 5jshell> Logger.make(5).flatMap(x -> Logger.make(x))$.. ==> Logger[5]jshell> Logger.make(5).flatMap(x -> Logger.make(x)).printlog()Value initialized. Value = 5jshell> Logger.make(5).flatMap(x -> Logger.make(x)).equals(Logger.make(5))$.. ==> truejshell> Logger<Integer> a = Logger.make(5).flatMap(x -> Logger.make(x).map(y -> y + 2)).flatMap(y -> Logger.make(y).map(z -> z * 10))jshell> Logger<Integer> b = Logger.make(5).flatMap(x -> Logger.make(x).map(y -> y + 2).flatMap(y -> Logger.make(y).map(z -> z * 10)))jshell> a.printlog()Value initialized. Value = 5Value changed! New value = 7Value changed! New value = 70jshell> b.printlog()Value initialized. Value = 5Value changed! New value = 7Value changed! New value = 70jshell> a.equals(b)$.. ==> truejshell> Logger<Integer> c = Logger.make(5).map(x -> x + 2).map(x -> x * 10)jshell> a.equals(c)$.. ==> truejshell> Function<Object, Logger<Boolean>> f = x -> Logger.make(x).map(y -> y.equals(y))jshell> Logger.make("hello").flatMap(f)$.. ==> Logger[true]jshell> Function<String, Logger<Number>> g = x -> Logger.make(x).map(y -> y.length())jshell> Logger<Number> lognum = Logger.make("hello").flatMap(g)jshell> lognumlognum ==> Logger[5]jshell> /exit

Check the format correctness of the output by running the following on the command line:

$ javac -Xlint:rawtypes *.java$ jshell -q your_java_files_in_bottom-up_dependency_order < test4.jsh

Check your styling by issuing the following

$ checkstyle *.java

Level 5

Lets write some applications using JShell that makes use of our Logger so as to observe how the values changes over the course computation. Save your methods in the file level5.jsh.

Define an add(Logger<Integer> a, int b) method that returns the result of a added to b wrapped in a Logger that preserves the log of all operations of a, as well as the addition to b.

jshell> add(Logger.make(5), 6)$.. ==> Logger[11]jshell> add(Logger.make(5), 6).printlog()Value initialized. Value = 5Value changed! New value = 11jshell> add(Logger.make(5).map(x -> x * 2), 6)$.. ==> Logger[16]jshell> add(Logger.make(5).map(x -> x * 2), 6).printlog()Value initialized. Value = 5Value changed! New value = 10Value changed! New value = 16

The sum of non-negative integers from 0 to n (inclusive of both) can be computed recursively using

int sum(int n) {    if (n == 0) {        return 0;    } else {        return sum(n - 1) + n;    }}

Redefine the above method such that it returns the result wrapped in a Logger. You may find the above add method useful.

jshell> sum(0)$.. ==> Logger[0]jshell> sum(0).printlog()Value initialized. Value = 0jshell> sum(5)$.. ==> Logger[15]jshell> sum(5).printlog()Value initialized. Value = 0Value changed! New value = 1Value changed! New value = 3Value changed! New value = 6Value changed! New value = 10Value changed! New value = 15

The Collatz conjecture (or the 3n+1 Conjecture) is a process of generating a sequence of numbers starting with a positive integer that seems to always end with 1.

int f(int n) {   if (n == 1) {      return 1;   } else if (n % 2 == 0) {      return f(n / 2);   } else {      return f(3 * n + 1);   } }

Write the function f that takes in n and returns a Logger<Integer> that wraps around the final value, with a log of the value changes over time. You should include a test method in the Logger that takes in a Predicate and returns true if the value within the Logger satisfies the predicate, and false otherwise.

jshell> Logger.make(5).test(x -> x == 5)$.. ==> truejshell> Logger.make(5).map(x -> x + 1).test(x -> x % 2 != 0)$.. ==> falsejshell> Logger.make("hello").test(x -> x.length() == 5)$.. ==> truejshell> f(16)$.. ==> Logger[1]jshell> f(16).printlog()Value initialized. Value = 16Value changed! New value = 8Value changed! New value = 4Value changed! New value = 2Value changed! New value = 1jshell> f(10)$.. ==> Logger[1]jshell> f(10).printlog()Value initialized. Value = 10Value changed! New value = 5Value changed! New value = 15Value changed! New value = 16Value changed! New value = 8Value changed! New value = 4Value changed! New value = 2Value changed! New value = 1

Check the format correctness of the output by running the following on the command line:

$ javac -Xlint:rawtypes *.java$ jshell -q your_java_files_in_bottom-up_dependency_order < test5.jsh

Check your styling by issuing the following

$ checkstyle *.java

Reviews

There are no reviews yet.

Only logged in customers who have purchased this product may leave a review.

Shopping Cart
[Solved] CS2030s Lab 6-Logging
$25