Character Streams


In Lesson 3, you created the RosterReporter class to write a report of students enrolled in a course session. You wrote the report to a string, then printed the report using System.out. Here is the existing RosterReporter class:

 package sis.report; import java.util.*; import sis.studentinfo.*; import static sis.report.ReportConstant.NEWLINE; class RosterReporter {    static final String ROSTER_REPORT_HEADER =       "Student" + NEWLINE +       "-" + NEWLINE;    static final String ROSTER_REPORT_FOOTER =       NEWLINE + "# students = ";    private Session session;    RosterReporter(Session session) {       this.session = session;    }    String getReport() {       StringBuilder buffer = new StringBuilder();       writeHeader(buffer);       writeBody(buffer);       writeFooter(buffer);       return buffer.toString();    }    void writeHeader(StringBuilder buffer) {       buffer.append(ROSTER_REPORT_HEADER);    }    void writeBody(StringBuilder buffer) {       for (Student student: session.getAllStudents()) {          buffer.append(student.getName());          buffer.append(NEWLINE);       }    }    void writeFooter(StringBuilder buffer) {       buffer.append(          ROSTER_REPORT_FOOTER + session.getAllStudents().size() +          NEWLINE);    } } 

Currently the RosterReporter class builds a string representing the entire report. Another class using RosterReporter would take this string and send it to a desired destinationthe console, a file, or perhaps the Internet. Thus, you write every character in the report twicefirst to the String, then to a final destination. With a larger report, the receiver of the report may experience an unacceptable delay while waiting for the entire report to be produced.

A preferable solution would involve writing each character directly to a stream that represents the ultimate destination. This also means that you wouldn't need to store the entire report in a large string buffer, something that has the potential to cause memory problems.

You have been told to make the student information system flexible. Initially the system must be able to write reports to either the console or to local files. To meet this requirement, you will first update the RosterReporter class to write directly to a character stream. First, you will need to update the test.


 package sis.report; import junit.framework.TestCase; import sis.studentinfo.*; import static sis.report.ReportConstant.NEWLINE; import java.io.*; public class RosterReporterTest extends TestCase {    public void testRosterReport() throws IOException {       Session session =          CourseSession.create(             new Course("ENGL", "101"),             DateUtil.createDate(2003, 1, 6));       session.enroll(new Student("A"));       session.enroll(new Student("B"));       Writer writer = new StringWriter();       new RosterReporter(session).writeReport(writer);       String rosterReport = writer.toString();       assertEquals(          RosterReporter.ROSTER_REPORT_HEADER +          "A" + NEWLINE +          "B" + NEWLINE +          RosterReporter.ROSTER_REPORT_FOOTER + "2" +          NEWLINE, rosterReport);    } } 

To use Java's IO classes, you must import the package java.io. Many IO operations can generate an exception. You'll need to declare that the test method throws an IOException.

You want the RosterReporter to write its report to a Writer object provided by the client. The class java.io.Writer is the base abstraction for character streams. For purposes of testing, you can create an object of the Writer subclass StringWriter. When you send the message toString to a StringWriter, it returns a String of all characters written to it.

Note the improvement to the design. Instead of asking the RosterReporter object for a report (geTReport), you are telling it to write a report (writeReport).

In RosterReporter, like RosterReporter test, you will need to add an import statement and throws clauses to methods that perform IO operations. (Instead of believing me, the better approach is to code this without the tHRows clauses and let the compiler tell you what to do.)

 package sis.report; import java.util.*; import sis.studentinfo.*; import static sis.report.ReportConstant.NEWLINE; import java.io.*; class RosterReporter {    static final String ROSTER_REPORT_HEADER =       "Student" + NEWLINE +       "-" + NEWLINE;    static final String ROSTER_REPORT_FOOTER =       NEWLINE + "# students = ";    private Session session;    private Writer writer;    RosterReporter(Session session) {       this.session = session;    }    void writeReport(Writer writer) throws IOException {       this.writer = writer;       writeHeader();       writeBody();       writeFooter();    }    private void writeHeader() throws IOException {       writer.write(ROSTER_REPORT_HEADER);    }    private void writeBody() throws IOException {       for (Student student: session.getAllStudents())          writer.write(student.getName() + NEWLINE);    }    private void writeFooter() throws IOException {       writer.write(          ROSTER_REPORT_FOOTER + session.getAllStudents().size() +          NEWLINE);    } } 

In writeHeader, writeBody, and writeFooter, you have replaced calls to the StringBuilder method append with calls to the Writer method write. Also, previously the code passed a StringBuilder from method to method. Now you have a Writer instance variable. By not passing the same pervasive variable to every method, you can eliminate some duplication.

Watch your tests pass, then make the following refactorings to take advantage of Java String formatting.

 package sis.report; import java.util.*; import sis.studentinfo.*; import java.io.*; class RosterReporter {    static final String ROSTER_REPORT_HEADER = "Student%n-%n";    static final String ROSTER_REPORT_FOOTER = "%n# students = %d%n";    private Session session;    private Writer writer;    RosterReporter(Session session) {       this.session = session;    }    void writeReport(Writer writer) throws IOException {       this.writer = writer;       writeHeader();       writeBody();       writeFooter();    }    private void writeHeader() throws IOException {       writer.write(String.format(ROSTER_REPORT_HEADER));    }    private void writeBody() throws IOException {       for (Student student: session.getAllStudents())          writer.write(String.format(student.getName() + "%n"));    }    private void writeFooter() throws IOException {       writer.write(          String.format(ROSTER_REPORT_FOOTER,                        session.getAllStudents().size()));    } } 

The requisite changes to the test:

 package sis.report; import junit.framework.TestCase; import sis.studentinfo.*; import java.io.*; public class RosterReporterTest extends TestCase {    public void testRosterReport() throws IOException {       Session session =          CourseSession.create(             new Course("ENGL", "101"),             DateUtil.createDate(2003, 1, 6));       session.enroll(new Student("A"));       session.enroll(new Student("B"));       Writer writer = new StringWriter();       new RosterReporter(session).writeReport(writer);       String rosterReport = writer.toString();       assertEquals(          String.format(RosterReporter.ROSTER_REPORT_HEADER +                        "A%n" +                        "B%n" +                        RosterReporter.ROSTER_REPORT_FOOTER, 2),          rosterReport) ;    } } 



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net