/*
 * Copyright (C) 2005 NTT DATA Corporation
 * 
 */
package org.postgresforest.tool.util;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Hashtable;

/**
 * SQL構文解析クラス.
 * SQL文の構文解析の実行し、コマンドタイプを判別。
 * 各コマンドタイプにあわせて、テーブルを取り出す。
 */

public class Parser {

	//コマンド・キーワードID
	public static final int NONE = -1; //解析不可
	public static final int CREATE_TABLE = 1;
	public static final int DROP_TABLE = 2;
	public static final int CREATE_VIEW = 3;
	public static final int DROP_VIEW = 4;
	public static final int CREATE_INDEX = 5;
	public static final int ALTER_TABLE = 6;

	public static final int OTHER_TABLE = 100; //テーブル処理対象
	public static final int DML = 200; //DML(select,update,insert,delete,copy)

	protected int m_type = NONE;
	protected ArrayList m_table = null;
	protected String m_replaceWord = null;

	protected static final int CREATE = 0;
	protected static final int DROP = 1;
	protected static final int TABLE = 2;
	protected static final int ALTER = 3;
	protected static final int VACUUM = 4;
	protected static final int FULL = 5;
	protected static final int FREEZE = 6;
	protected static final int VERBOSE = 7;
	protected static final int ANALYZE = 8;
	protected static final int TRUNCATE = 9;
	protected static final int REVOKE = 10;
	protected static final int REINDEX = 11;
	protected static final int LOCK = 12;
	protected static final int GRANT = 13;
	protected static final int VIEW = 14;
	protected static final int TRIGGER = 15;
	protected static final int INDEX = 16;
	protected static final int ONLY = 17;
	protected static final int ON = 18;
	protected static final int CASCADE = 19;
	protected static final int RESTRICT = 20;

	protected static final int SELECT = 51;
	protected static final int UPDATE = 52;
	protected static final int INSERT = 53;
	protected static final int DELETE = 54;
	protected static final int COPY = 55;
	
	
	/** コマンド・キーワードIDと文字列マッピングテーブル */
	protected static final CommandMap commandSet = new CommandMap();

	// SQLコマンド判別用のマップ初期化

	static {

		commandSet.put("create", CREATE);
		commandSet.put("drop", DROP);
		commandSet.put("table", TABLE);
		commandSet.put("alter", ALTER);
		commandSet.put("vacuum", VACUUM);
		commandSet.put("full", FULL);
		commandSet.put("freeze", FREEZE);
		commandSet.put("verbose", VERBOSE);
		commandSet.put("analyze", ANALYZE);
		commandSet.put("truncate", TRUNCATE);
		commandSet.put("revoke", REVOKE);
		commandSet.put("reindex", REINDEX);
		commandSet.put("lock", LOCK);
		commandSet.put("grant", GRANT);
		commandSet.put("view", VIEW);
		commandSet.put("trigger", TRIGGER);
		commandSet.put("index", INDEX);
		commandSet.put("only", ONLY);
		commandSet.put("on", ON);
		commandSet.put("cascade", CASCADE);
		commandSet.put("restrict", RESTRICT);

		commandSet.put("select", SELECT);
		commandSet.put("update", UPDATE);
		commandSet.put("insert", INSERT);
		commandSet.put("delete", DELETE);
		commandSet.put("copy", COPY);


	}

	/**
	 * コンストラクタ
	 * @param sql - SQL文
	 * @throws SQLException
	 */
	public Parser(String sql) throws Exception {

		//StreamTokenizerの初期化
		StringReader fr = new StringReader(sql);
		StreamTokenizer tokenizer = new StreamTokenizer(fr);
		tokenizer.wordChars('_', '_');
		tokenizer.whitespaceChars('\t', '\t');
		tokenizer.whitespaceChars('\n', '\n');
		tokenizer.whitespaceChars('\r', '\r');
		tokenizer.quoteChar('\'');
		tokenizer.parseNumbers();
		tokenizer.eolIsSignificant(false);

		m_table = new ArrayList();
		int typeCommand = NONE;

		//コマンドタイプ取得
		int token = StreamTokenizer.TT_EOF;
		while ((token = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
			if (token == StreamTokenizer.TT_WORD) {

				int type = commandSet.getInt(tokenizer.sval);

				switch (type) {

					case ALTER :
					case CREATE :
					case DROP :
					case REINDEX :

						typeCommand = type;
						break;

					case TRUNCATE :
					case LOCK :

						m_type = OTHER_TABLE;

						tokenizer.nextToken();
						type = commandSet.getInt(tokenizer.sval);
						if (type == TABLE) {
							tokenizer.pushBack();
							break;
						}

						m_table.add(new String(tokenizer.sval));
						break;

					case TABLE :

						//コマンドタイプ判別
						switch (typeCommand) {
							case ALTER :
								m_type = ALTER_TABLE;
								tokenizer.nextToken();
								type = commandSet.getInt(tokenizer.sval);
								if (type == ONLY) {
									tokenizer.nextToken();
								}
								m_table.add(new String(tokenizer.sval));
								return;

							case CREATE :
								m_type = CREATE_TABLE;
								//テーブル名取得
								while (
								(token = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
										m_table.add(new String(tokenizer.sval));
										break;
								}
								break;

							case DROP :
								m_type = DROP_TABLE;
								//テーブル名取得
								searchDropTables(tokenizer);
								break;

						}
						break;

					case INDEX :
						switch (typeCommand) {

							case CREATE :
								m_type = CREATE_INDEX;

								//INDEX名
								tokenizer.nextToken();
								m_replaceWord = new String(tokenizer.sval);

								//ON
								tokenizer.nextToken();

								//テーブル名取得
								tokenizer.nextToken();
								m_table.add(new String(tokenizer.sval));

								break;

						}

						break;

					case VIEW :
						switch (typeCommand) {

							//CREATE VIEW を多重化テーブルとして登録する
							case CREATE :
								m_type = CREATE_VIEW;
								//テーブル名取得
								tokenizer.nextToken();
								m_table.add(new String(tokenizer.sval));
								break;
							case DROP :
								m_type = DROP_VIEW;
								//テーブル名取得
								searchDropTables(tokenizer);
								break;

						}
						break;

					case VACUUM :
						m_type = OTHER_TABLE;

						while ((token = tokenizer.nextToken())
							!= StreamTokenizer.TT_EOF) {
							if (token == StreamTokenizer.TT_WORD) {

								type = commandSet.getInt(tokenizer.sval);
								switch (type) {

									case FULL :
									case FREEZE :
									case VERBOSE :
									case ANALYZE :
										break;
									default :
										m_table.add(new String(tokenizer.sval));
										break;
								}
							}
						}
						break;

					case REVOKE :
					case GRANT :
					case TRIGGER :
						//ONまで探す							
						m_type = OTHER_TABLE;

						searchTableTypeOn(tokenizer);
						break;

						
					case SELECT :
					case UPDATE :
					case DELETE :
					case INSERT :
					case COPY :
					    if(m_type == NONE){
					        m_type = DML;
					    }
						return;
						
					default :

						break;
				}

			}

		}
	}
	/**
	 * 
	 * @param tokenizer
	 * @return 
	 * @throws IOException
	 */
	protected boolean searchTableTypeOn(StreamTokenizer tokenizer)
		throws IOException {
		int token;
		int type;
		while ((token = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
			if (token == StreamTokenizer.TT_WORD) {

				type = commandSet.getInt(tokenizer.sval);
				if (type == ON) {
					tokenizer.nextToken();
					type = commandSet.getInt(tokenizer.sval);
					if (type == TABLE) {
						tokenizer.pushBack();
						return false; //テーブル名を見つけたら
					}

					m_table.add(new String(tokenizer.sval));
					break;
				}

			}
		}
		return true;
		
	}


	/**
	 * 
	 * @param tokenizer
	 * @throws IOException
	 */
	protected void searchDropTables(StreamTokenizer tokenizer)
		throws IOException {
		int token;
		int type;
		while ((token = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
			if (token == StreamTokenizer.TT_WORD) {
				type = commandSet.getInt(tokenizer.sval);
				if(type != CASCADE &&
				   type != RESTRICT){

					m_table.add(new String(tokenizer.sval));
				}

			}
		}
		return;
		
	}

	/**
	 * @return
	 */
	public int getType() {
		return m_type;
	}

	/**
	 * @return
	 */
	public ArrayList getTables() {
		return m_table;
	}

	/**
	 * SQLコマンド判別用のマップ
	 *
	 */
	static class CommandMap extends Hashtable {

		public int getInt(Object key) {

			Integer value = (Integer) get(key);

			return value == null ? -1 : value.intValue();

		}

		public int put(Object key, int value) {

			Integer oldvalue = (Integer) put(key, new Integer(value));

			return oldvalue == null ? -1 : oldvalue.intValue();

		}

	}

	/**
	 * @return
	 */
	public String getReplaceWord() {
		return m_replaceWord;
	}

	/**
	 * @param string
	 */
	public void setReplaceWord(String string) {
		m_replaceWord = string;
	}

}
