Worked build: the Poem-of-the-Day server
◈ 7 cardsImplement the specified FSM over ServerSocket — poems loaded once at startup, one state variable and one switch, CRLF framing with a flush before every read — then accept-test it with telnet, one session per table row.
1. Load the poems once, at startup
The assignment fixes the shape: java PodServer poems.txt. Read args[0] before the server socket is opened, parse the file into a List<Poem> once, and hold it for the lifetime of the process. Re-reading per connection is the classic mistake — it turns a memory lookup into a disk read on every request, and it means a file edited mid-run silently changes what different clients see.
Fail loudly. No argument: print a usage line and exit non-zero. File missing, unreadable, or parsing to zero poems: say which and exit. A server that starts happily and then answers every client with 'no poems available' is worse than one that refuses to start, because the failure surfaces at the marker rather than at you.
2. Accept, then hand off
The outer loop is module 7's, unchanged: bind, accept() forever, submit each returned Socket to a fixed thread pool. The entire per-connection try/catch lives inside the handler, so one client's IOException can never end the accept loop. PoD sessions are short — greet, one line in, one poem out, close — so a small pool is ample.
3. One state variable, one switch
This is the step that makes the marker's job, and yours, easy. The handler holds exactly one field of the state enum and loops until it reaches the terminal state. Every case is one row of your transition table, in the same order, using the same names. When the table says LISTED × out-of-range → write 400, go to REJECTED, the code says it too, and the two can be read side by side and checked line by line.
The alternative — a chain of nested ifs with the state implied by how deep you have nested — is functionally equivalent and impossible to audit. You cannot point at it and say 'this is row four'.
4. Framing: CRLF out, flush before every read
Write \r\n explicitly. Never println, which emits the platform separator and swallows IOException — PrintWriter only reports failure through checkError(). Read with BufferedReader.readLine(), which strips the terminator, so the string you get back has no CRLF and you must put one back on every line you send.
And flush before every read. A buffered writer holds your prompt in memory; if you then block in readLine() waiting for a choice the client has not been asked for yet, both sides wait forever. That is the hang framing bugs produce, and it is the single commonest reason a PoD server that 'works' fails its telnet test.
5. The error transition, then the close
One handler covers every input the grammar forbids: non-numeric, empty, out of range, end-of-stream. Each writes its specified line and moves to REJECTED — never back to LISTED, because the specification says the session ends after one choice, right or wrong. Then close in a finally, or put the Socket in a try-with-resources, so the socket is released down every path including the exceptional ones.
Worked example — the telnet acceptance script
Your test plan is the transition table, one telnet session per row. Start the server, then telnet localhost 7000:
- Row 2 — type
2. Expect the second poem, thenConnection closed by foreign host. - Row 3 — reconnect, type
99. Expect your out-of-range line, then a close. - Row 4 — reconnect, type
banana. Expect your bad-request line, then a close. - Row 5 — reconnect and disconnect without typing anything. Expect the server to log the drop and release the thread, not to spin.
Every row exercised, including the ones you did not want to write. That transcript, pasted into your documentation, is the test plan the assignment asks for.