POP3クライアントをSocketとJavaMailで作る その2

はじめに

その1で別々だったSocket版とJavaMail版のプログラムを一つにまとめてみた.

ユースケース図・クラス図・シーケンス図

プログラムが結構でかくなったので,UMLで図など描いた.

ソース

Socket版とJavaMail版で異なるのはMailserverBySocket.javaとMailserverByJavaMail.javaのみである.

//Mailserver.java
public abstract class Mailserver extends java.util.Observable{
  protected String name;

  public Mailserver(String name){
    this.name=name;
  }
  public void execute() throws Exception{
    setChanged();
    notifyObservers();
  }
  public abstract void connect(User user) throws Exception;
  public abstract void close() throws Exception;
  public abstract int getMessageCount() throws Exception;
  public abstract Object getMessage(int messageNumber) throws Exception;
}
//end

MailserverBySocket.java
import java.net.Socket;
import java.io.*;

public class MailserverBySocket extends Mailserver{
  private Socket socket;
  private BufferedReader reader;
  private BufferedWriter writer;

  public MailserverBySocket(String name){
    super(name);
  }
  public void connect(User user) throws Exception{
    socket=new Socket(name,110);
    reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
    writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    if(isReady()&&isReady("USER "+user.getName())&&isReady("PASS "+user.getPassword())){
      //メールサーバにログインできれば何もしない
    }
    else{
      socket.close();
    }
  }
  public void close() throws Exception{
    if(isReady("quit")){
      socket.close();
    }
  }
  public int getMessageCount() throws Exception{
    return isReady("list")? Integer.parseInt(getMessageOrCount().toString()): 0;
  }
  public Object getMessage(int messageNumber) throws Exception{
    return isReady("retr "+messageNumber)? getMessageOrCount(): "NG";
  }
  private boolean isReady() throws IOException{
    final boolean OK=true;
    final boolean NG=!OK;

    return reader.readLine().matches("^\\+OK.*$")? OK: NG;
  }
  private boolean isReady(String command) throws IOException{
    final String end="\r\n";
    writer.write(command+end);
    writer.flush();
    return isReady();
  }
  private Object getMessageOrCount() throws Exception{
    StringBuffer message=new StringBuffer();
    while(true){
      String s=reader.readLine();
      if(s.equals(".")){
        if(message.length()>0){
          message.deleteCharAt(message.length()-1);
        }
        break;
      }
      else{
        message.append(s+"\n");
      }
    }
    if(message.toString().matches("\\d+ \\d+")){
      message=new StringBuffer(message.toString().split(" ")[0]);
    }
    return message;
  }
}
//end

//MailserverByJavaMail
import java.io.*;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.*;

public class MailserverByJavaMail extends Mailserver{
  private Store store;
  private Folder folder;
  
  public MailserverByJavaMail(String name){
    super(name);
  }
  public void connect(final User user) throws Exception{
    //userをfinalにしないとAuthenticatorの無名クラスへインスタンスを渡せない
    Properties prop=new Properties();
    prop.put("mail.host",name);
    prop.put("mail.store.protocol","pop3");
    Session session=Session.getDefaultInstance(prop,new Authenticator(){
      protected PasswordAuthentication getPasswordAuthentication(){
        return new PasswordAuthentication(user.getName(),user.getPassword());
      }
    });
    store=session.getStore();
    store.connect();
    folder = store.getFolder("INBOX");
    folder.open(Folder.READ_ONLY);
  }
  public void close() throws Exception{
    folder.close(false);
    store.close();
  }
  public int getMessageCount() throws Exception{
    return folder.getMessageCount();
  }
  public Object getMessage(int messageNumber) throws Exception{
    ByteArrayOutputStream out=new ByteArrayOutputStream();
    folder.getMessage(messageNumber).writeTo(out);
    return out;
  }
}
//end

//MailserverDispatcher.java
public class MailserverDispatcher{
  private static MailserverDispatcher dispatcher=new MailserverDispatcher();

  private MailserverDispatcher(){
  }
  public static MailserverDispatcher getInstance(){
    return dispatcher;
  }
  public Mailserver getMailserver(String name,User user,Connect connect) throws Exception{
    Mailserver mailserver=
      connect==Connect.SOCKET?
        (Mailserver)new MailserverBySocket(name): (Mailserver)new MailserverByJavaMail(name);
    mailserver.connect(user);
    return mailserver;
  }
}
//end

//Command.java
public abstract class Command{
  protected Mailserver mailserver;
  
  public Command(Mailserver mailserver){
    this.mailserver=mailserver;
  }
  public abstract Object execute() throws Exception;
}
//end

//ListCommand.java
public class ListCommand extends Command{
  public ListCommand(Mailserver mailserver){
    super(mailserver);
  }
  public Object execute() throws Exception{
    return new Integer(mailserver.getMessageCount());
  }
}
//end

//ReadCommand.java
public class ReadCommand extends Command{
  private int messageNumber;

  public ReadCommand(Mailserver mailserver){
    super(mailserver);
  }
  public void setMessageNumber(int messageNumber){
    this.messageNumber=messageNumber;
  }
  public Object execute() throws Exception{
    return mailserver.getMessage(messageNumber);
  }
}
//end

//QuitCommand.java
public class QuitCommand extends Command{
  public QuitCommand(Mailserver mailserver){
    super(mailserver);
  }
  public Object execute() throws Exception{
    mailserver.close();
    return "quit";
  }
}
//end

//CommandDispatcher.java
import java.util.Properties;
import java.lang.reflect.*;

public class CommandDispatcher{
  private static CommandDispatcher dispatcher=new CommandDispatcher();
  private static Properties commandList=new Properties();

  static{
    commandList.put("list","ListCommand");
    commandList.put("read","ReadCommand");
    commandList.put("quit","QuitCommand");
  }
  private CommandDispatcher(){
  }
  public static CommandDispatcher getInstance(){
    return dispatcher;
  }
  public static Command getCommand(Mailserver mailserver,String command) throws Exception{
    int messageNumber=1;
    if(command.matches("(?i)^read \\d+$")){
      messageNumber=Integer.parseInt(command.replaceAll("(?i)read\\s",""));
      command="read";
    }

    Command com=(Command)Class.forName(commandList.get(command.toLowerCase()).toString())
      .getConstructor(new Class[]{Mailserver.class})
        .newInstance(new Object[]{mailserver});
    //getConstructor(new Class[]{Mailserver.class})ではなくgetConstructor(new Class[]{mailserver.getClass()})ではエラー(NoSuchMethodException)になる.
    //mailserver.getClass()で帰ってくるのはMailserverByJavaMailかMailserverBySocket
    //Commandクラスのコンストラクタの引数はMailserver.MailserverByJavaMail/MailserverBySocketを引数に持つコンストラクタはない.
    //違いはSystem.out.println(Mailserver.class+"<==>"+mailserver.getClass());で確認できる.

    if(command=="read"){
      ((ReadCommand)com).setMessageNumber(messageNumber);
    }
    return com;
  }
}
//end

//User.java
public class User{
  private String name;
  private String password;

  public User(String name,String password){
    this.name=name;
    this.password=password;
  }
  public String getName(){
    return name;
  }
  public String getPassword(){
    return password;
  }
}
//end
          
//Connect.java
public final class Connect{
  public static final Connect SOCKET=new Connect();
  public static final Connect JAVAMAIL=new Connect();
}
//end

//Controller.java
import java.util.Observer;

public class Controller{
  private Mailserver mailserver;

  public Controller(Mailserver mailserver) throws Exception{
    this.mailserver=mailserver;
  }
  public void addObserver(Observer observer){
    mailserver.addObserver(observer);
  }
  public void actionPerformed() throws Exception{
    mailserver.execute();
  }
}
//end
          
//View.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Observer;
import java.util.Observable;
import java.util.Properties;

public class View implements java.util.Observer{
  private String command;
  private Controller controller;
  private static Properties isContinue=new Properties();

  static{
    isContinue.put("list",new Boolean(true));
    isContinue.put("read",new Boolean(true));
    isContinue.put("quit",new Boolean(false));
  }
  public View(Controller controller){
    this.controller=controller;
  }
  public void show() throws Exception{
    BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
    System.out.print("ready: ");
    command=reader.readLine();
    controller.actionPerformed(); //イベントは発生しないので,自分でactionPerformedを呼ぶ
  }
  public void update(Observable observer,Object object){
    try{
      System.out.println(
        CommandDispatcher.getInstance().getCommand((Mailserver)observer,command).execute());
      if(((Boolean)isContinue.get(command.split(" ")[0])).booleanValue()){
        show();
      }
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }
}
//end

//Main.java
import java.util.Observer;

public class Main{
  public static void main(String[] args){
    try{
      Controller controller=new Controller(
        MailserverDispatcher.getInstance()
          .getMailserver("mailserver",
                         new User("username","password"),
                         args[0].equalsIgnoreCase("socket")? Connect.SOCKET: Connect.JAVAMAIL));
                         //mailserver,username,passwordは適当に書換える.
      View view=new View(controller);
      controller.addObserver((Observer)view);
      view.show();
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }
}
//end
        

参考文献・サイト