시리즈: Angelia
java
122 줄
· 업데이트 2026-09-27
ChatMessage.java
Angelia/Java/Week_6_7/code/08_chat_protocol/ChatMessage.java
/**
* 聊天協定的「訊息」ADT:用 sealed interface + record 把每一種合法訊息列舉出來。
*
* 協定草案(一行一則訊息,欄位以單一空白分隔,最後一個欄位可含空白):
*
* 客戶端 → 伺服器 伺服器 → 客戶端
* --------------------------------- --------------------------------------
* LOGIN <name> OK [<text>]
* JOIN <room> ERR <code> <text>
* SAY <room> <text...> MSG <room> <name> <text...>
* QUIT BYE
*
* 這個類別把「文字 ↔ 結構化資料」的轉換集中在一個地方(parse / encode),
* 伺服器與客戶端的其他程式碼就完全不需要碰字串切割。
*/
public sealed interface ChatMessage
permits ChatMessage.Login, ChatMessage.Join, ChatMessage.Say, ChatMessage.Quit,
ChatMessage.Ok, ChatMessage.Err, ChatMessage.Msg, ChatMessage.Bye {
// ---- 客戶端 → 伺服器 ----
record Login(String name) implements ChatMessage {}
record Join(String room) implements ChatMessage {}
record Say(String room, String text) implements ChatMessage {}
record Quit() implements ChatMessage {}
// ---- 伺服器 → 客戶端 ----
record Ok(String text) implements ChatMessage {}
record Err(int code, String text) implements ChatMessage {}
record Msg(String room, String name, String text) implements ChatMessage {}
record Bye() implements ChatMessage {}
/** 協定錯誤:收到不符合規格的一行。 */
class ProtocolException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ProtocolException(String message) {
super(message);
}
}
/**
* 把一行文字解析成 ChatMessage。
* @throws ProtocolException 若格式不合法
*/
static ChatMessage parse(String line) {
if (line == null) {
throw new ProtocolException("連線已關閉(讀到 null)");
}
String[] parts = line.split(" ", 2); // 先切出指令字
String command = parts[0].toUpperCase();
String rest = parts.length > 1 ? parts[1] : "";
switch (command) {
case "LOGIN": {
requireNonEmpty(rest, "LOGIN 需要名字");
requireNoSpace(rest, "名字不可含空白");
return new Login(rest);
}
case "JOIN": {
requireNonEmpty(rest, "JOIN 需要房間名");
requireNoSpace(rest, "房間名不可含空白");
return new Join(rest);
}
case "SAY": {
String[] p = rest.split(" ", 2); // <room> <text...>
if (p.length < 2 || p[0].isEmpty()) {
throw new ProtocolException("SAY 需要房間名與內容");
}
return new Say(p[0], p[1]);
}
case "QUIT": return new Quit();
case "OK": return new Ok(rest);
case "ERR": {
String[] p = rest.split(" ", 2);
if (p.length < 2) {
throw new ProtocolException("ERR 需要錯誤碼與說明");
}
try {
return new Err(Integer.parseInt(p[0]), p[1]);
} catch (NumberFormatException e) {
throw new ProtocolException("ERR 的錯誤碼必須是整數:" + p[0]);
}
}
case "MSG": {
String[] p = rest.split(" ", 3); // <room> <name> <text...>
if (p.length < 3) {
throw new ProtocolException("MSG 需要房間、名字與內容");
}
return new Msg(p[0], p[1], p[2]);
}
case "BYE": return new Bye();
default:
throw new ProtocolException("未知的指令:" + parts[0]);
}
}
/** 把 ChatMessage 編碼回協定文字(不含換行)。parse(encode(m)) 必須等於 m。 */
static String encode(ChatMessage m) {
// switch 模式比對(Java 21):sealed 讓編譯器確認我們沒有漏掉任何一種訊息
return switch (m) {
case Login l -> "LOGIN " + l.name();
case Join j -> "JOIN " + j.room();
case Say s -> "SAY " + s.room() + " " + s.text();
case Quit q -> "QUIT";
case Ok o -> o.text().isEmpty() ? "OK" : "OK " + o.text();
case Err e -> "ERR " + e.code() + " " + e.text();
case Msg g -> "MSG " + g.room() + " " + g.name() + " " + g.text();
case Bye b -> "BYE";
};
}
private static void requireNonEmpty(String s, String why) {
if (s.isEmpty()) {
throw new ProtocolException(why);
}
}
private static void requireNoSpace(String s, String why) {
if (s.contains(" ")) {
throw new ProtocolException(why);
}
}
}
관련 글
Angelia
haskell
EN
업데이트 2026-02-03
Adding.hs
Adding.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Adding.hs).
글 읽기 →
Angelia
haskell
EN
업데이트 2026-02-03
Area.hs
Area.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Area.hs).
글 읽기 →
Angelia
haskell
EN
업데이트 2026-02-03
Chess.hs
Chess.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Chess.hs).
글 읽기 →
Angelia
haskell
EN
업데이트 2026-02-03
ChessGraphics.hs
ChessGraphics.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/ChessGraphics.hs).
글 읽기 →
Angelia
haskell
EN
업데이트 2026-02-03
Examples.hs
Examples.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Examples.hs).
글 읽기 →
Angelia
haskell
EN
업데이트 2026-02-03
Fibonacci.hs
Fibonacci.hs — haskell source code from the Angelia learning materials (Angelia/Haskell/Fibonacci.hs).
글 읽기 →