Khabju
Khabju

Reputation: 17

How to return a Collector in Java, that forms a special printable sting?

I have been trying to write a method called printableStringCollector, which basically returns a collector that forms a special printable String as described below -

Printable String spec

It is a multi-line String containing a formatted table of all results and total values and marks. The format of your table should meet the following requirements:

Example:

Student........|Phalaxing |Shieldwalling |Tercioing |Wedging |Total |Mark |
Eco Betty......|0 ........|83............|89........|59......|57.75 |F ...|
Lodbrok Johnny |61 .......|92............|67........|0.......|55.00 |F....|
Paige Umberto..|75....... |94............|0.........|52......|55.25 |F....|
Average........|45.33.....|89.67.........|52.00.....|37.00...|56.00 |F....|

Can anyone help me implement this method?

public Collector<CourseResult, ?, String> printableStringCollector() {

}

CourseResult class:

public class CourseResult {
    private final Person person;
    private final Map<String, Integer> taskResults;

    public CourseResult(final Person person, final Map<String, Integer> taskResults) {
        this.person = person;
        this.taskResults = taskResults;
    }

    public Person getPerson() {
        return person;
    }

    public Map<String, Integer> getTaskResults() {
        return taskResults;
    }
}

Person class:

public class Person {
    private final String firstName;
    private final String lastName;
    private final int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        final Person person = (Person) o;
        return age == person.age &&
                      Objects.equals(firstName, person.firstName) &&
                      Objects.equals(lastName, person.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
                .add("firstName='" + firstName + "'")
                .add("lastName='" + lastName + "'")
                .add("age=" + age)
                .toString();
    }
}

Upvotes: 0

Views: 346

Answers (1)

Rob Spoor
Rob Spoor

Reputation: 9090

Most of the time, when I need to create a custom collector, I create a new class for it and give it methods accumulate, combine and finish, to match the operators in a collector. That class can do whatever is needed. For instance, where the finish is optional:

Collector<T, ?, R> collector = Collector.of(
        MyCollector::new,
        MyCollector::accumulate,
        MyCollector::combine,
        MyCollector::finish);

In your case you will have to stream twice though, as the collector needs to have seen the length of each student name before it can output anything. The collector would store its CourseResult instances, as well as the maximum length(s). It would then stream again, and use String.format to do all the hard work in adding padding (e.g. "%" + maxNameLength + "s | %10s | ...").

The finish method would look like this (simplified):

public String finish() {
    String header = "...\n"; // use same padding
    String pattern = "...";
    return results.stream()
            .map(result -> String.format(pattern, result.getPerson().getName(), ...)
            .collect(Collectors.joining("\n", header, ""));
}

I wouldn't use the exact same pattern for the header, because the rows contain numbers where the header does not.

Upvotes: 1

Related Questions