/*
 * $Id: RobotAcceptanceRule.java,v 1.6 2006/02/06 15:32:50 rampil Exp $
 * Copyright (c) 2005 LOGICAL-PARADOX.ORG
 */
package org.logical_paradox.rss.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 * robot.txtの内容を格納するクラス．サイト別に生成される．
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.6 $
 */
public class RobotAcceptanceRule {
	/** robots.txtのプロパティ名 - User-agent */
	public static final String HEADER_USER_AGENT = "User-agent: ";
	/** robots.txtのプロパティ名 - Disallow */
	public static final String HEADER_DISALLOW = "Disallow: ";

	private LinkedList<UserAgentRuleDirective> disallows = null;

	/**
	 * コンストラクタ．
	 */
	protected RobotAcceptanceRule() {
		disallows = new LinkedList<UserAgentRuleDirective>();
	}

	/**
	 * robots.txtの情報を返す．
	 * @param in 入力ストリーム(robots.txtに対して開かれていること
	 * @return robots.txtの内容
	 * @throws IOException robots.txtの読み込みに失敗
	 */
	public static RobotAcceptanceRule getInstance(InputStream in) throws IOException {
		RobotAcceptanceRule r = new RobotAcceptanceRule();

		BufferedReader reader = new BufferedReader(new InputStreamReader(in));
		String linebuf = null;

		// ストリームからrobots.txtを読み込む
		int cnt = -1;
		while((linebuf = reader.readLine()) != null) {
			if(linebuf.startsWith(HEADER_USER_AGENT)) {
				// 新しいUser-Agentの場合
				// パラメータが正しい場合は新しいディレクティブを作成する
				String robot = linebuf.substring(HEADER_USER_AGENT.length()).trim();
				if(robot.length() > 0) {
					cnt++;
					r.disallows.add(r.newRuleDirective(robot));
				}
			} else if(linebuf.startsWith(HEADER_DISALLOW)) {
				// 既存のディレクティブにdisallowルールを追加する
				if(cnt >= 0) {
					UserAgentRuleDirective ua = r.disallows.get(cnt);
					String param = linebuf.substring(HEADER_DISALLOW.length()).trim();
					ua.add(param);
				}
			}
		}

		reader.close();

		return r;
	}
	/**
	 * 新しいルールを追加する．
	 * @param r 定義行
	 * @return 作成されたルール
	 */
	private UserAgentRuleDirective newRuleDirective(String r) {
		return new UserAgentRuleDirective(r);
	}
	/**
	 * 指定されたURL/User-agentの組み合わせが許可されているかどうかを返す．
	 * @param url 取得対象URL
	 * @param agent 取得エージェント名
	 * @return true: 許可されている / false: 禁止されている
	 */
	protected boolean isAllowed(String url, String agent) {
		UserAgentRuleDirective u = matchedDirective(agent);
		if(u == null) {
			// 指定されたユーザーエージェントは禁止されていない
			return true;
		}

		try {
			String path = (new URL(url)).getPath();
			ListIterator i = u.getIterator();
			while(i.hasNext()) {
				String ua = (String)i.next();
				if(ua.trim().length() > 0 && path.startsWith(ua)) {
					// 禁止区域だった場合
					return false;
				}
			}
		} catch(MalformedURLException e) {
			// URLがおかしい場合は禁止されているとみなす
			return false;
		}
		return true;
	}

	/**
	 * 指定されたユーザーエージェント名にマッチしたディレクティブを返す
	 * 一致するディレクティブが存在しない場合はnullを返す
	 */
	protected UserAgentRuleDirective matchedDirective(String agent) {
		if(agent == null) {
			return null;
		}

		for(UserAgentRuleDirective u : disallows) {
			if(agent.equals(u.getUserAgent()) || u.getUserAgent().equals("*")) {
				// ユーザーエージェント名が完全に一致しているか，あるいはワイルドカードの場合
				return u;
			}
		}

		// 一致するものが全然なかった
		return null;
	}

	/**
	 * User-Agent別の除外ルールを保持するクラス
	 * @author satoshi akabane@logical-paradox.org
	 * @version $Revision: 1.6 $
	 */
	class UserAgentRuleDirective {
		private String agent = null;
		/** 禁止ルールのリスト */
		private LinkedList<String> disallows = null;

		/**
		 * コンストラクタ．
		 * @param name User-Agent名
		 */
		public UserAgentRuleDirective(String name) {
			agent = name;
			disallows = new LinkedList<String>();
		}
		/**
		 * User-agentを返す．
		 * @return User-agent
		 */
		public String getUserAgent() {
			return agent;
		}
		/**
		 * 禁止項目のリストを示すイテレータを返す．
		 * @return イテレータ
		 */
		public ListIterator getIterator() {
			return disallows.listIterator();
		}
		/**
		 * 禁止項目リストの定義数を返す．
		 * @return 定義数
		 */
		public int size() {
			return disallows.size();
		}
		/**
		 * 禁止ルールを追加する．
		 * @param path 禁止するパス
		 */
		public void add(String path) {
			disallows.add(path);
		}
		/**
		 * 禁止ルールを削除する．
		 * @param path 削除するパス
		 */
		public void remove(String path) {
			disallows.remove(path);
		}
	}
}
