package asandatabasebrowser.model;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;

import asandatabasebrowser.AsanDatabaseBrowser;
import asandatabasebrowser.util.JarClassLoader;
import blanco.commons.sql.format.BlancoSqlRule;

import net.sourceforge.swingx.SxTextUtilities;



/**
 * データベースの情報を保持し、DBへのアクセスを行う
 * 
 * @author Asan
 */
public class VvDatabase {
	static JarClassLoader loader = new JarClassLoader();
	public static final String ORACLE = "Oracle";
	public static final String DB2 = "DB2";
	public static final String SQLSERVER = "SQLServer";
	public static final String MYSQL = "MySQL";
	public static final String POSTGRES = "Postgres";
	public static final String OTHER = "other";
	public static String[] productNames = {
		ORACLE, 
		DB2, 
		SQLSERVER,
		MYSQL,
		POSTGRES,
		OTHER
	};
	private static DecimalFormat f = new DecimalFormat();
	
	/** データベースの設定名。任意の名前で構わないが、ユニークで必須。 */
	public String name = "テスト";
	/** ドライバのクラス名。必須。 */
	public String driverName = "org.gjt.mm.mysql.Driver";
	public String url = "jdbc:mysql:///mysql?useUnicode=true&characterEncoding=SJIS";
	public String user = "";
	public String password = "";
	/** JDBCドライバのクラスパス */
	public ArrayList jarnames = null;	// ArrayList<String>
	/** 接続時にユーザ名とパスワードを問い合わせる. */
	public boolean isShowLoginDialog = false;
	/** Close前にRollbackをおこなうかどうか？ DB2では必要. */
	public boolean rollbackBeforeClose = true;
	/** 検索を行ったとき、即座にRollbackを行うかどうか？ DB2同期レベルによりロックしてしまう。*/
	public boolean rollbackAfterQuery = true;
	
	
	/** 強調表示ファイル名.null可 */
	public String highlightFilename;
	/** ハイライトの設定.null可 */
	public HighlightList highlightList = null;
	/** オラクルのとき、SYS.ALL_TAB_COMMENTS のコメントを remark にするか？ */
	public boolean useOracleTableComments = false;
	/** オラクルのとき、SYS.ALL_COL_COMMENTS のコメントを remark にするか？ */
	public boolean useOracleColumnComments = false;
	/** オラクル10g以降のとき、ゴミ箱テーブルを表示しない。 */
	public boolean hideOracleBinTable = false;
	/** SQL整形を行うとき、大文字/小文字に変換するか？ */
	public int keywordCase = BlancoSqlRule.KEYWORD_NONE;
	
	/** データベースがサポートしている関数名。SQL整形を行う際に使う。 */
	public String functionNames = "";
	/** データベース名("Oracle","DB2"など) */
	public String productName = "other";
	
	/** @deprecated */
	public String nameFilter = null;
	
	// グローバル変数
	private Connection conn = null;
	private DatabaseMetaData meta = null;
	private ArrayList schemaList = null;
	private ArrayList typeInfoList = null; 
	
	public VvDatabase(String name, String driverName, 
			String url, String user, String password, 
			ArrayList jarnames, String productName, String highlightFilename, 
			boolean isShow, boolean rollBackBeforeClose, boolean rollbackAfterQuery,
			boolean useOracleTableComments, boolean useOracleColumnComments,
			boolean hideOracleBinTable,
			int keywordCase, String functionNames) {
		assert name != null;
		assert driverName != null: "driverNameは必須です。";
		assert url != null;
		assert keywordCase == BlancoSqlRule.KEYWORD_NONE ||
				keywordCase == BlancoSqlRule.KEYWORD_UPPER_CASE ||
				keywordCase == BlancoSqlRule.KEYWORD_LOWER_CASE: keywordCase;
		// user, password, highlightFilename は任意。
		this.name = name;
		this.driverName = driverName;
		this.url = url;
		this.user = user;
		this.password = password;
		this.jarnames = jarnames;
		this.productName = productName;
		this.isShowLoginDialog = isShow;
		this.rollbackBeforeClose = rollBackBeforeClose;
		this.rollbackAfterQuery = rollbackAfterQuery;
		this.highlightFilename = highlightFilename;
		this.useOracleTableComments = useOracleTableComments;
		this.useOracleColumnComments = useOracleColumnComments;
		this.hideOracleBinTable = hideOracleBinTable;
		this.keywordCase = keywordCase;
		this.functionNames = functionNames;
		if (highlightFilename != null) {
			this.highlightList = HighlightList.loadHighlightList(highlightFilename);
			//System.out.println("highlightList="+highlightList);
		}
	}
	
	public void copyFrom(VvDatabase db) {
		assert db != null;
		this.name = db.name;
		this.driverName = db.driverName;
		this.url = db.url;
		this.user = db.user;
		this.password = db.password;
		this.jarnames = db.jarnames;
		this.productName = db.productName;
		this.isShowLoginDialog = db.isShowLoginDialog;
		this.rollbackBeforeClose = db.rollbackBeforeClose;
		this.rollbackAfterQuery = db.rollbackAfterQuery;
		this.highlightFilename = db.highlightFilename;
		this.useOracleTableComments = db.useOracleTableComments;
		this.useOracleColumnComments = db.useOracleColumnComments;
		this.hideOracleBinTable = db.hideOracleBinTable;
		this.keywordCase = db.keywordCase;
		this.functionNames = db.functionNames;
		if (highlightFilename != null) {
			this.highlightList = HighlightList.loadHighlightList(highlightFilename);
			//System.out.println("highlightList="+highlightList);
		}
	}

	public void connect(String user, String pass) throws DbException {
		// ドライバクラスをロード
		Driver driver = null;
		try {
/*			
			URL[] urls = new URL[jarnames.size()];
			for (int i=0; i<jarnames.size(); i++) {
				String jarname = (String) jarnames.get(i);
				try {
					// file:/C:/user/java/eclipse/DbTools/lib/mysql-connector-java-3.0.10-stable-bin.jar
					urls[i] = new File(jarname).toURL();
					System.out.println(urls[i]);
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}
			}
			URLClassLoader loader = new URLClassLoader(urls, getClass().getClassLoader());
*/
			loader.setJarFilenames(jarnames);
			Class driverClass = Class.forName(driverName, true, loader);
			driver = (Driver) driverClass.newInstance();
			outputLog("driver="+driver);
			DriverManager.registerDriver(driver);
		} catch (InstantiationException e) {
			throw new DbException("オブジェクトのインスタンス化に失敗しました:"+driverName, e);
		} catch (IllegalAccessException e) {
			throw new DbException("不正アクセスを行いました:"+driverName, e);
		} catch (SQLException e) {
			throw new DbException("SQLエラー:"+driverName, e);
		} catch (ClassNotFoundException e) {
			throw new DbException("クラスが見つかりません:"+driverName, e);
		}
		// データベースへ接続
		try {
			Properties prop = new Properties();
			prop.put("user", user);
			prop.put("password", pass);
			outputLog("connect "+name+" "+user+"/"+pass);
			conn = driver.connect(url, prop);
			conn.setAutoCommit(false);
		} catch (SQLException e) {
			throw new DbException("DBの接続に失敗しました url="+url+" user="+user, e);
		}
		// テーブル情報を取得
		try {
			meta = conn.getMetaData();
			long start = System.currentTimeMillis();
			createSchemaList();
			System.out.println((System.currentTimeMillis()-start)+"msec");
		} catch (SQLException e) {
			throw new DbException("メタデータの取得に失敗しました", e);
		}
		// 型情報を取得
		typeInfoList = getTypeInfoList();
		// オラクルの場合、COMMENTSが登録されていればそれを取得する。
		if (productName.equals(ORACLE) && useOracleTableComments) {
			String sql = "select * from SYS.ALL_TAB_COMMENTS where COMMENTS is not null";
			Statement statement = null;
			ResultSet rs = null;
			try {
				statement = conn.createStatement();
				outputLog(sql);
				rs = statement.executeQuery(sql);
				while (rs.next()) {
					String owner = rs.getString("OWNER");
					String table_name = rs.getString("TABLE_NAME");
					String table_type = rs.getString("TABLE_TYPE");
					String comments = rs.getString("COMMENTS");
					getSchemaInfo(null, owner).getTableTypeInfo(table_type).getTableInfo(table_name).remarks = comments;
				}
			} catch (SQLException e) {
				e.printStackTrace();
				// ここでの例外は何もしない。
			} finally {
				if (rs!=null) try { rs.close(); } catch (SQLException e) {}
				if (statement!=null) try { statement.close(); } catch (SQLException e) {}
				if (rollbackAfterQuery) rollback();
			}
		}
	}

	public void disconnect() throws DbException {
		try {
			if (rollbackBeforeClose) {
				rollback();				
			}
			outputLog("disconnect "+name);
			conn.close();
			System.out.println("disconnect()");
		} catch (SQLException e) {
			throw new DbException("DBの切断に失敗しました", e);
		} finally {
			// 切断に成功しようが失敗しようが、必ず切断したことにする。
			// そうでなければ、接続中にネットワークが切れたとき、切断も再接続もできなくなる。
			conn = null;
			meta = null;
		}
	}
	/** コネクションを返す。接続していないときにはnullを返す。 */
	public Connection getConnection() {
		return conn;
	}
	/** メタデータを返す。必ず接続しているときに呼び出すこと。 */
	public DatabaseMetaData getMetaData() {
		assert meta != null;
		return meta;
	}
	/** コミットを行う。必ず接続しているときに呼び出すこと。 */
	public void commit() throws DbException {
		assert conn != null;
		try {
			outputLog("commit");
			conn.commit();
		} catch (SQLException e) {
			throw new DbException("コミットに失敗しました", e);
		}
	}
	/** ロールバックを行う。必ず接続しているときに呼び出すこと。 */
	public void rollback() throws DbException {
		assert conn != null;
		try {
			outputLog("rollback");
			conn.rollback();
		} catch (SQLException e) {
			throw new DbException("ロールバックに失敗しました", e);
		}
	}
	/**
	 * 処理を実行する.
	 * INSET, UPDATE, DELETEなど
	 */
	public int executeUpdate(String sql) throws DbException {
		assert sql != null;
		assert conn != null;
		Statement statement = null;
		try {
			statement = conn.createStatement();
			outputLog(sql);
			int rc = statement.executeUpdate(sql);
			return rc;
		} catch (SQLException e) {
			throw new DbException("ＤＢの更新に失敗しました。\n"+sql, e);
		} finally {
			if (statement!=null) try { statement.close(); } catch (SQLException e) {} 
		}
	}

	/** 結果セットから、その情報を生成する. */
	public ResultSetInfo createResultSetInfo(ResultSet rs) throws DbException {
		assert rs != null;
		try {
			ResultSetMetaData meta = rs.getMetaData();
			ArrayList resultInfoList = new ArrayList();
			int cols = meta.getColumnCount();
			for (int i=0; i<cols; i++) {
				// Oracleの場合、ドライバが例外を発生することがある。このファイルの末尾を参照。
				int precision = -1;
				try {
					precision = meta.getPrecision(i+1);
				} catch (NumberFormatException ex) {}
				// 検索結果情報を作成
				ResultInfo info = new ResultInfo(
					meta.isAutoIncrement(i+1),
					meta.isCaseSensitive(i+1),
					meta.isSearchable(i+1),
					meta.isCurrency(i+1),
					meta.isNullable(i+1),
					meta.isSigned(i+1),
					meta.getColumnDisplaySize(i+1),
					meta.getColumnLabel(i+1),
					meta.getColumnName(i+1),
					meta.getSchemaName(i+1),
					//meta.getPrecision(i+1),
					precision,
					meta.getScale(i+1),
					meta.getTableName(i+1),
					meta.getCatalogName(i+1),
					meta.getColumnType(i+1),
					meta.getColumnTypeName(i+1),
					meta.isReadOnly(i+1),
					meta.isWritable(i+1),
					meta.isDefinitelyWritable(i+1),
					meta.getColumnClassName(i+1));
				resultInfoList.add(info);
			}			
			return new ResultSetInfo(this, resultInfoList);
		}
		catch (SQLException e) {
			throw new DbException("検索結果の情報の取得に失敗しました。", e);
		}
	}
	
	/**
	 * 結果セットを、配列のリストに変換する。
	 * @return	ArrayList<Object[]> で、各要素の杯列数はすべて同じ。
	 *  */
	public ArrayList getRecordList(ResultSet rs) throws SQLException {
		assert rs != null;
		ArrayList records = new ArrayList();
		ResultSetMetaData rsmeta = rs.getMetaData();
		int cols = rsmeta.getColumnCount();
		// すべてのレコードに対して
		for (int r=0; rs.next(); r++) {
			// 最大レコード数を超えたら中断。
			if (AsanDatabaseBrowser.theApp.doc.maxRecord != -1 && r >= AsanDatabaseBrowser.theApp.doc.maxRecord) break;
			
			Object[] rec = new Object[cols];
			for (int i=0; i<cols; i++) {
				try {
					rec[i] = rs.getObject(i+1);
					
					// Oracle対策 start
					// Oracleでは、取得したオブジェクトのクラスがJDBCのクラスではなく、oracle.sql.Datumから継承されたクラスになる。
					// そのままでは値の型の判断ができないので、JDBC型の値を取得しなおす。
					if (productName.equals(ORACLE)) {
						try {
							if (rec[i] != null) {
								//System.out.println(rec[i].getClass().getSuperclass());			
								if (rec[i].getClass().getSuperclass().getName().equals("oracle.sql.Datum")) {
									Method m = rec[i].getClass().getMethod("toJdbc", null);
									rec[i] = m.invoke(rec[i], null);
								}
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
					}	// Oracle対策 end

					// CLOB,BLOB等でOutOfMemoryになるのを防ぐ。ただし、NULLの場合はDbLargeObjectにはしない。
					if (rec[i] != null) {
						String typename = rsmeta.getColumnTypeName(i+1);
						rec[i] = convertLargeObject(rec[i], typename, true);
//						String title;
//						if (rec[i] instanceof Blob) {
//							Blob blob = (Blob) rec[i];
//							long len = blob.length();
//							title = typename + "(" + f.format(len) + " バイト)";
//							byte[] bytes = blob.getBytes(1L, (int) len);
//							rec[i] = new DbLargeObject(typename, title, bytes);
//						} else if (rec[i] instanceof Clob) {
//							Clob clob = (Clob) rec[i];
//							long len = clob.length();
//							title = typename + "(" + f.format(len) + " 文字)";
//							String str = clob.getSubString(1L, (int) len);
//							rec[i] = new DbLargeObject(typename, title, str);
//						} else if (rec[i] instanceof byte[]) {
//							byte[] bytes = (byte[]) rec[i];
//							title = typename + "(" + f.format(bytes.length) + " バイト)";
//							rec[i] = new DbLargeObject(typename, title, bytes);
//						} else {
//							// TODO:他に考慮すべき型はないか？
//						}
					}
				} catch (SQLException e) {
					if (rs.getMetaData().getColumnType(i+1) == Types.VARCHAR) {
						rec[i] = rs.getString(i+1);
					}
					else {
						rec[i] = "取得失敗:"+rs.getMetaData().getColumnTypeName(i+1);
						System.out.println("i="+i+" getColumnType="+rs.getMetaData().getColumnType(i+1));
						e.printStackTrace();
					}
				}
			}
			records.add(rec);
		}
		return records;
	}
	static Object convertLargeObject(Object obj, String typename, boolean enableCache) {
		if (obj != null) {
			try {
				String title;
				if (obj instanceof Blob) {
					Blob blob = (Blob) obj;
					long len = blob.length();
					title = typename + "(" + f.format(len) + " バイト)";
					byte[] bytes = blob.getBytes(1L, (int) len);
					obj = new DbLargeObject(typename, title, bytes, enableCache);
				} else if (obj instanceof Clob) {
					Clob clob = (Clob) obj;
					long len = clob.length();
					title = typename + "(" + f.format(len) + " 文字)";
					String str = clob.getSubString(1L, (int) len);
					obj = new DbLargeObject(typename, title, str, enableCache);
				} else if (obj instanceof byte[]) {
					byte[] bytes = (byte[]) obj;
					title = typename + "(" + f.format(bytes.length) + " バイト)";
					obj = new DbLargeObject(typename, title, bytes, enableCache);
				} else {
					// TODO:他に考慮すべき型はないか？
				}
			} catch (SQLException e) {
				// TODO 自動生成された catch ブロック
				e.printStackTrace();
			}
		}
		return obj;
	}
	
	/**
	 * SQLを検索し、結果セットを配列のリストに変換する。
	 * @return ArrayList<Object[]>
	 * @throws DbException
	 */
	public DbData getRecordList(String sql) throws DbException {
		assert sql != null;
		Statement statement = null;
		ResultSet rs = null;
		try {
			statement = conn.createStatement();
			outputLog(sql);
			rs = statement.executeQuery(sql);
			ResultSetInfo rsinfo = createResultSetInfo(rs);
			ArrayList records = getRecordList(rs);
			return new DbData(rsinfo, records);
		} catch (SQLException e) {
			throw new DbException("ＤＢの検索に失敗しました:"+sql, e);
		} finally {
			if (rs!=null) try { rs.close(); } catch (SQLException e) {}
			if (statement!=null) try { statement.close(); } catch (SQLException e) {}
			if (rollbackAfterQuery) rollback();
		}
	}
	
	/** テーブルの内容を検索する。 */
	public DbData queryTable(TableInfo tableInfo, String sql, boolean doRollback) throws DbException {
		assert tableInfo != null;
		assert sql != null;
		Statement statement = null;
		ResultSet rs = null;
		try {
			statement = conn.createStatement();
			outputLog(sql);
			rs = statement.executeQuery(sql);
			ArrayList records = getRecordList(rs);
			return new DbData(tableInfo, records);
		} catch (SQLException e) {
			throw new DbException("ＤＢの検索に失敗しました:"+sql, e);
		} finally {
			if (rs!=null) try { rs.close(); } catch (SQLException e) {}
			if (statement!=null) try { statement.close(); } catch (SQLException e) {}
			if (doRollback && rollbackAfterQuery) rollback();
		}
	}
	/**
	 * レコードの件数を返します.
	 * TODO:作りがあまい
	 */
	public int getRecordCount(TableInfo table) throws DbException {
		String sql = "select count(*) from "+table.getSqlTableName();
		try {
			Statement stat = conn.createStatement();
			outputLog(sql);
			ResultSet rs = stat.executeQuery(sql);
			rs.next();
			int count = rs.getInt(1);
			//System.out.println(tablename + "\t" + count);
			rs.close();
			stat.close();
			if (rollbackAfterQuery) rollback();
			return count;
		} catch (SQLException e) {
			throw new DbException("件数の取得に失敗しました["+sql+"]", e);
		}
	}
	
	/** 指定テーブルの指定カラムの値を再検索します。 */
	public Object getColumnValue(TableInfo table, Object[] rec, int col)
			throws DbException {
		assert table != null;
		assert rec != null;
		assert 0 <= col && col < rec.length: col;
		// SQL文を作成
		ColumnInfo colInfo = table.getColumnInfo(col);
		String sql = "select " + colInfo.columnName;
		sql += " from " + table.getSqlTableName();
		String where = "";
		// PKがあるときには、PKで検索する。
		if (table.pkList != null && table.pkList.size() > 0) {
			for (int i = 0; i < table.pkList.size(); i++) {
				if (i != 0) {
					where += " and ";
				}
				String pk = (String) table.pkList.get(i);
				int index = table.getColumnIndexByName(pk);
				assert index != -1 : index;
				ColumnInfo c = table.getColumnInfo(index);
				if (rec[index] != null) {
					String sqlvalue = getValueStr(c, rec[index]);
					where += c.columnName + "=" + sqlvalue;
				} else {
					where += c.columnName + " is null";
				}
			}
		} else {
			// PKがないときは、すべての値をキーにする
			for (int i = 0; i < table.getColumnCount(); i++) {
				ColumnInfo c = table.getColumnInfo(i);
				if (rec[i] != null) {
					if (rec[i] instanceof Blob ||
						rec[i] instanceof Clob ||
						rec[i] instanceof byte[] ||
						rec[i] instanceof DbLargeObject) {
						// これらの値は条件にしない。
					} else {
						if (where.length() > 0) {
							where += " and ";
						}
						String sqlvalue = getValueStr(c, rec[i]);
						where += c.columnName + "=" + sqlvalue;
					}
				} else {
					if (where.length() > 0) {
						where += " and ";
					}
					where += c.columnName + " is null";
				}
			}
		}
		// where句がないと特定できない。
		if (where.length() == 0) {
			throw new DbException("このテーブルではレコードが特定できません:"+table.getSqlTableName());
		}
		sql += " where " + where;
		// 検索する。
		Statement statement = null;
		ResultSet rs = null;
		try {
			statement = conn.createStatement();
			outputLog(sql);
			rs = statement.executeQuery(sql);
			System.out.println("rs.getFetchSize()="+rs.getFetchSize());
			boolean rc = rs.next();
			if (! rc) {
				throw new DbException("レコードが特定できませんでした。0件");
			}
			Object obj = rs.getObject(1);
			String typename = rs.getMetaData().getColumnTypeName(1);
			obj = convertLargeObject(obj, typename, false);
			rc = rs.next();
			if (rc) {
				throw new DbException("レコードが特定できませんでした。2件以上");
			}
			return obj;
		} catch (SQLException e) {
			throw new DbException("ＤＢの検索に失敗しました:" + sql, e);
		} finally {
			if (rs != null) try { rs.close(); } catch (SQLException e) {}
			if (statement != null) try { statement.close(); } catch (SQLException e) {}
			if (rollbackAfterQuery) rollback();
		}
	}
	/**
	 * メタデータからスキーマ情報のリストを取得。
	 * ArrayList<SchemaInfo>
	 */
	public ArrayList getSchemaInfoList() {
		return schemaList;
	}

	public void createSchemaList() throws DbException {
		assert meta!=null;
		ResultSet rs = null;
		schemaList = new ArrayList();
		Exception ex = null;
		String msg = "";
		try {
			// スキーマがサポートされているか？
			if (meta.supportsSchemasInDataManipulation()) {
				rs = meta.getSchemas();
				while (rs.next()) {
					String schem = rs.getString("TABLE_SCHEM");
					String cat = (rs.getMetaData().getColumnCount()>=2) ?
							rs.getString("TABLE_CATALOG"):
							null;	// for JDK1.3
					try {
						SchemaInfo info = getSchemaInfo(cat, schem, null, null);
						schemaList.add(info);	
					} catch (DbException e) {
						if (ex == null) ex = e;
						msg += "catalog="+cat+" schema="+schem+"\n";
					}
				}
			}
			
			// MySQLの場合、スキーマがない。そのため、カタログだけで作る。
			if (schemaList.size() == 0) {
				rs = meta.getCatalogs();
				while (rs.next()) {
					String cat = rs.getString("TABLE_CAT");
					try {
						SchemaInfo info = getSchemaInfo(cat, null, null, null);
						schemaList.add(info);
					} catch (DbException e) {
						if (ex == null) ex = e;
						msg += "catalog="+cat+"\n";
					}
				}
			}
			if (ex != null) {
				throw new DbException("スキーマ/カタログ情報の取得に失敗しました\n"+msg, ex);
			}
		} catch (SQLException e) {
			throw new DbException("スキーマ/カタログ情報の取得に失敗しました", e);
		}finally {
			if (rs!=null) try { rs.close(); } catch (SQLException e) {}
			//if (rollbackAfterQuery) rollback();
		}
	}

	/** メタデータからテーブル情報のリストを取得。*/
	SchemaInfo getSchemaInfo(String cat, String schema,
					String tableNamePattern, String[] types)
											 throws DbException {
		ResultSet rs = null;
//		SchemaInfo schemaInfo = new SchemaInfo(this, cat, schema, new ArrayList());
		ArrayList list = new ArrayList();
		try {
			HashMap map = new HashMap();
			rs = meta.getTables(cat, schema, tableNamePattern, types);
			while( rs.next() ) {
				String catalog = rs.getString("TABLE_CAT");
				String schem = rs.getString("TABLE_SCHEM");
				String tableName = rs.getString("TABLE_NAME");
				String tableType = rs.getString("TABLE_TYPE");
				//ArrayList collist = getColumnInfoList(catalog, schem, tableName);
				ArrayList collist = null;
				TableInfo info = new TableInfo(this,
						catalog,
						schem,
						tableName,
						tableType,
						rs.getString("REMARKS"),
						collist);
				TableTypeInfo ti = (TableTypeInfo) map.get(tableType);
				if (ti == null) {
					ti = new TableTypeInfo(tableType);
					map.put(tableType, ti);
					list.add(ti);
				}
				ti.add(info);
			}
			return new SchemaInfo(this, cat, schema, list);
		} catch (SQLException e) {
			throw new DbException("テーブル情報の取得に失敗しました: catalog="+cat+" schema="+schema, e);
		}
		finally {
			if (rs!=null) try { rs.close(); } catch (SQLException e) {}
			//if (rollbackAfterQuery) rollback();
		}
	}
//	/** メタデータから1つのテーブル情報のリストを取得。*/
//	public TableInfo getTableInfo(SchemaInfo schema, 
//							String tableNamePattern, String[] types)
//											 throws DbException {
//		ArrayList list = getTableInfoList(schema.tableCatalog, schema.tableSchema, tableNamePattern, types);
//		if (list==null) return null;
//		if (list.size()!=1) return null;	// 複数のテーブルがあった場合
//		return (TableInfo) list.get(0);
//	}

	/** メタデータからカラム情報のリストを取得。*/
	ArrayList getColumnInfoList(String catalog, String schemaPattern, String tableName)
											 throws DbException {
		ResultSet rs = null;
		ArrayList list = new ArrayList();
		try {
			// カラムを宣言
			rs = meta.getColumns(catalog, schemaPattern, tableName, null);
			//System.out.println(rs.getMetaData().getColumnCount());
			while (rs.next()) {
				String table_cat 		= rs.getString("TABLE_CAT");
				String table_schem 		= rs.getString("TABLE_SCHEM");
				String table_name 		= rs.getString("TABLE_NAME");
				String column_name		= rs.getString("COLUMN_NAME");
				short data_type			= rs.getShort("DATA_TYPE");
				String type_name		= rs.getString("TYPE_NAME");
				int column_size			= rs.getInt("COLUMN_SIZE");
				int decimal_digits		= rs.getInt("DECIMAL_DIGITS");
				int num_prec_radix		= rs.getInt("NUM_PREC_RADIX");
				int nullable			= rs.getInt("NULLABLE");
				String remarks			= rs.getString("REMARKS");
				String column_def		= rs.getString("COLUMN_DEF");
				int sql_data_type 		= rs.getInt("SQL_DATA_TYPE");
				int sql_datetime_sub 	= rs.getInt("SQL_DATETIME_SUB");
				int char_octet_length	= rs.getInt("CHAR_OCTET_LENGTH");
				int ordinal_position	= rs.getInt("ORDINAL_POSITION");
				String is_nullable		= rs.getString("IS_NULLABLE");
				ArrayList others = new ArrayList();
				for (int i=19; i<=rs.getMetaData().getColumnCount(); i++) {
					others.add(rs.getObject(i));
				}
				ColumnInfo col = new ColumnInfo(
					table_cat,
					table_schem,
					table_name,
					column_name,
					data_type,
					type_name,
					column_size,
					decimal_digits,
					num_prec_radix,
					nullable,
					remarks,
					column_def,
					sql_data_type,
					sql_datetime_sub,
					char_octet_length,
					ordinal_position,
					is_nullable,
					others);
				list.add(col);
			
//			if (rs.getMetaData().getColumnCount()==18) {
//				while (rs.next()) {
//					ColumnInfo col = new ColumnInfo(
//									rs.getString("TABLE_CAT"),
//									rs.getString("TABLE_SCHEM"),
//									rs.getString("TABLE_NAME"),
//									rs.getString("COLUMN_NAME"),
//									rs.getShort("DATA_TYPE"),
//									rs.getString("TYPE_NAME"),
//									rs.getInt("COLUMN_SIZE"),
//									rs.getInt("DECIMAL_DIGITS"),
//									rs.getInt("NUM_PREC_RADIX"),
//									rs.getInt("NULLABLE"),
//									rs.getString("REMARKS"),
//									rs.getString("COLUMN_DEF"),
//									//rs.getInt("SQL_DATA_TYPE"),
//									//rs.getInt("SQL_DATETIME_SUB"),
//									rs.getInt("CHAR_OCTET_LENGTH"),
//									rs.getInt("ORDINAL_POSITION"),
//									rs.getString("IS_NULLABLE"));
//									//rs.getString("SCOPE_CATLOG"),
//									//rs.getString("SCOPE_SCHEMA"),
//									//rs.getString("SCOPE_TABLE"),
//									//rs.getShort("SOURCE_DATA_TYPE"));
//					list.add(col);
//				}
//			}
//			else {
//				while (rs.next()) {
//					ColumnInfo col = new ColumnInfo(
//									rs.getString("TABLE_CAT"),
//									rs.getString("TABLE_SCHEM"),
//									rs.getString("TABLE_NAME"),
//									rs.getString("COLUMN_NAME"),
//									rs.getShort("DATA_TYPE"),
//									rs.getString("TYPE_NAME"),
//									rs.getInt("COLUMN_SIZE"),
//									rs.getInt("DECIMAL_DIGITS"),
//									rs.getInt("NUM_PREC_RADIX"),
//									rs.getInt("NULLABLE"),
//									rs.getString("REMARKS"),
//									rs.getString("COLUMN_DEF"),
//									//rs.getInt("SQL_DATA_TYPE"),
//									//rs.getInt("SQL_DATETIME_SUB"),
//									rs.getInt("CHAR_OCTET_LENGTH"),
//									rs.getInt("ORDINAL_POSITION"),
//									rs.getString("IS_NULLABLE"),
//									rs.getString("SCOPE_CATLOG"),
//									rs.getString("SCOPE_SCHEMA"),
//									rs.getString("SCOPE_TABLE"),
//									rs.getShort("SOURCE_DATA_TYPE"));
//
//					list.add(col);
//				}
			}
		} catch (SQLException e) {
			throw new DbException("カラム情報の取得に失敗しました", e);
		}
		finally {
			if (rs!=null) try { rs.close(); } catch (SQLException ex) {}
			//if (rollbackAfterQuery) rollback();
		}
		// オラクルのとき、SYS.ALL_COL_COMMENTS のコメントを remark にするか？
		if (productName.equals(ORACLE) && useOracleColumnComments) {
			String sql = "select * from SYS.ALL_COL_COMMENTS "+
					"where OWNER='"+schemaPattern+
					"' and TABLE_NAME='"+tableName+
					"' and COMMENTS is not null";
			Statement statement = null;
			rs = null;
			try {
				statement = conn.createStatement();
				outputLog(sql);
				rs = statement.executeQuery(sql);
				while (rs.next()) {
					String column_name = rs.getString("COLUMN_NAME");
					String comments = rs.getString("COMMENTS");
					for (int i=0; i<list.size(); i++) {
						ColumnInfo col = (ColumnInfo) list.get(i);
						if (col.columnName.equals(column_name)) {
							col.remarks = comments;
							break;
						}
					}
				}
			} catch (SQLException e) {
				e.printStackTrace();
				// ここでの例外は何もしない。
			} finally {
				if (rs!=null) try { rs.close(); } catch (SQLException e) {}
				if (statement!=null) try { statement.close(); } catch (SQLException e) {}
				if (rollbackAfterQuery) rollback();
			}
		}
		return list;
	}

	/**
	 * メタデータからプライマリキーのリストを取得。
	 * @return	ArrayList<String>	プライマリーキーのカラム名。プライマリキー順に並んでいる。
	 */
	public ArrayList getPrimaryKeyList(String catalog, String schemaPattern, String tableName)
											 throws DbException {
		// Oracle10gの場合、KEY_SEQ順にはならない(気が利かない)。仕方ないので、ソートする。
		class StrInt {
			final int 		num;
			final String	str;
			StrInt(int num, String str) {
				this.num = num;
				this.str = str;
			}
		}
		ArrayList list = new ArrayList();	// ArrayList<StrInt>
		ResultSet rs = null;
		try {
			rs = meta.getPrimaryKeys(catalog, schemaPattern, tableName);
			while (rs.next()) {
				list.add(new StrInt(rs.getInt("KEY_SEQ"), rs.getString("COLUMN_NAME")));
			}
			// ソート
			Collections.sort(list, new Comparator() {
			    public int compare(Object o1, Object o2) {
			    	StrInt s1 = (StrInt) o1;
			    	StrInt s2 = (StrInt) o2;
			    	return s1.num - s2.num;
			    }
			});
			// カラム名の配列にする。
			ArrayList result = new ArrayList();
			for (int i=0; i<list.size(); i++) {
				StrInt s = (StrInt) list.get(i);
				result.add(s.str);
			}
			return result;
		} catch (SQLException e) {
			e.printStackTrace();
			//throw new DbException("プライマリキーの取得に失敗しました"+tableName, e);
			// JDBCドライバはgetPrimaryKeys()を呼ぶと例外になる。
			// Caused by: java.sql.SQLException: [Microsoft][ODBC Driver Manager] ドライバはこの関数をサポートしていません。
			return null;
		}
		finally {
			if (rs!=null) try { rs.close(); } catch (SQLException ex) {}
			//if (rollbackAfterQuery) rollback();
		}
		
//		ResultSet rs = null;
//		ArrayList list = new ArrayList();
//		try {
//			rs = meta.getPrimaryKeys(catalog, schemaPattern, tableName);
//			while (rs.next()) {
//				list.add(rs.getString("COLUMN_NAME"));
//			}
//			return list;
//		} catch (SQLException e) {
//			e.printStackTrace();
//			//throw new DbException("プライマリキーの取得に失敗しました"+tableName, e);
//			// JDBCドライバはgetPrimaryKeys()を呼ぶと例外になる。
//			// Caused by: java.sql.SQLException: [Microsoft][ODBC Driver Manager] ドライバはこの関数をサポートしていません。
//			return null;
//		}
//		finally {
//			if (rs!=null) try { rs.close(); } catch (SQLException ex) {}
//		}

	}
	/**
	 * メタデータから型情報のリストを取得。
	 * @return	ArrayList<DbTypeInfo>	型情報のリスト
	 */
	public ArrayList getTypeInfoList() throws DbException {
		ArrayList list = new ArrayList();	// ArrayList<DbTypeInfo>
		ResultSet rs = null;
		try {
			rs = meta.getTypeInfo();
			while (rs.next()) {
				DbTypeInfo info = new DbTypeInfo(
						rs.getString("TYPE_NAME"),
						rs.getInt("DATA_TYPE"),
						rs.getInt("PRECISION"),
						rs.getString("LITERAL_PREFIX"),
						rs.getString("LITERAL_SUFFIX"),
						rs.getString("CREATE_PARAMS"),
						rs.getShort("NULLABLE"),
						rs.getBoolean("CASE_SENSITIVE"),
						rs.getShort("SEARCHABLE"),
						rs.getBoolean("UNSIGNED_ATTRIBUTE"),
						rs.getBoolean("FIXED_PREC_SCALE"),
						rs.getBoolean("AUTO_INCREMENT"),
						rs.getString("LOCAL_TYPE_NAME"),
						rs.getShort("MINIMUM_SCALE"),
						rs.getShort("MAXIMUM_SCALE"),
						rs.getInt("SQL_DATA_TYPE"),
						rs.getInt("SQL_DATETIME_SUB"),
						rs.getInt("NUM_PREC_RADIX")
						);
				list.add(info);
			}
			return list;
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DbException("メタデータから型情報の取得に失敗しました", e);
		}
		finally {
			if (rs!=null) try { rs.close(); } catch (SQLException ex) {}
			//if (rollbackAfterQuery) rollback();
		}
	}
	
	public DbTypeInfo getTypeInfo(int dataType) {
		for (int i=0; i<typeInfoList.size(); i++) {
			DbTypeInfo info = (DbTypeInfo) typeInfoList.get(i);
			if (info.dataType == dataType) return info;
		}
		// オラクルの場合、なぜかdataType=3(DECIMAL)がない。仕方ないので自分で作る。
		if (ORACLE.equals(productName) && dataType == Types.DECIMAL) {
			// 多分この値であってるハズ・・・
			DbTypeInfo info = new DbTypeInfo("DECIMAL", 3, 1, null, null, "(1)",
					(short)1, false, (short)3, false, true, false, 
					"NUMBER", (short)-84, (short)127, 0, 0, 10);
			typeInfoList.add(info);
			return info;
		}
		assert false: dataType;
		return null;
	}
	/** メタデータからカラム情報のリストを取得。*/
	public Object[][] getMetaDataProperties() {
		ArrayList names = new ArrayList();
		ArrayList values = new ArrayList();
		// メタデータの値を取得する。
		Method[] methods = DatabaseMetaData.class.getDeclaredMethods();
		for (int i=0; i<methods.length; i++) {
			Object val = null;
			Method m = methods[i];
			try {
				if (m.getParameterTypes().length > 0) continue;
				Class retClass = m.getReturnType();
				if (retClass.equals(Integer.TYPE)) {
					val = (Integer) m.invoke(meta, null);
				}
				else if (retClass.equals(Boolean.TYPE)) {
					val = (Boolean) m.invoke(meta, null);
				}
				else if (retClass.equals(String.class)) {
					val = (String) m.invoke(meta, null);
				}
				else {
					continue;
				}
			}
			catch (Throwable e) {
				System.out.println(methods[i]);
				System.out.println(e);
				val = "取得失敗:" + e.toString();
			}
			names.add(m.getName());
			values.add(val);
		}
		// 結果を Object[][] に変換する。
		Object[][] result = new Object[names.size()][2];
		for (int i=0; i<names.size(); i++) {
			result[i][0] = names.get(i);
			result[i][1] = values.get(i);
//			System.out.println(""+result[i][0]+"="+result[i][1]);
		}
		return result;
	}
	
	public String toString() { return name; }
	
//	/** 
//	 * SQLで表示するときの文字列を返す。
//	 * TODO:DBの種類ごとに設定ファイルに記述できないか？
//	 * 例
//	 * 10			NUMBER   		-> "10"
//	 * abc  		VARCHAR2(80)	-> "'abc'"
//	 * 2006/10/26	TIMESTAMP		-> "TO_DATE('2006/10/26')"
//	 * NULL      	NUMBER			-> "NULL"  
//	 */
//	public static String getSqlValue(int type, Object obj) {
//		if (obj == null) return "NULL";
//		switch (type) {
//		case Types.DOUBLE:
//		case Types.FLOAT:
//		case Types.INTEGER:
//		case Types.NUMERIC:
//		case Types.REAL:
//		case Types.SMALLINT:
//		case Types.TINYINT:
//		case Types.DECIMAL:
//			return obj.toString();
//		case Types.VARCHAR:
//		case Types.CHAR:
//		case Types.LONGVARCHAR:
//			return "'" + obj.toString() + "'";
//		case Types.NULL:
//			return "NULL";
//		case Types.DATE:
//		case Types.TIME:
//		case Types.TIMESTAMP:
//			return "TO_DATE('" + obj.toString() + "')";
//		case Types.ARRAY:
//		case Types.BIGINT:
//		case Types.BINARY:
//		case Types.BIT:
//		case Types.BLOB:
//		case Types.BOOLEAN:
//		case Types.CLOB:
//		case Types.DATALINK:
//		case Types.DISTINCT:
//		case Types.JAVA_OBJECT:
//		case Types.LONGVARBINARY:
//		case Types.OTHER:
//		case Types.REF:
//		case Types.STRUCT:
//		case Types.VARBINARY:
//		default:
//			return obj.toString();
//		}	
//	}
	/**
	 * 値の文字列を返す。
	 * "'あいうえお'" "123" "BLOB(X'00')"
	 * @param db
	 * @param value
	 * @return
	 */
	public String getValueStr(ColumnInfo col, Object value) {
		DbTypeInfo info = getTypeInfo(col.dataType);
		if (value == null) return "NULL";
		String result = value.toString();
		if (info.literalPrefix != null) result = info.literalPrefix + result;
		if (info.literalSuffix != null) result = result + info.literalSuffix;
		// オラクルのとき、TIMESTAMP型などは’’が必要なのに、メタデータではnullになっている。BUG?
		if (ORACLE.equals(productName)) {
			switch (info.dataType) {
			case Types.DATE:
			case Types.TIME:
			case Types.TIMESTAMP:
			case -104:	// INTERVALDS	-104
			case -103:	// INTERVALYM	-103
			case -102:	// TIMESTAMP WITH LOCAL TIME ZONE	-102
			case -101:	// TIMESTAMP WITH TIME ZONE	-101
				if (info.literalPrefix == null || info.literalSuffix == null) {
					result = "'" + value.toString() + "'";
				}
				break;
			}
		}
		// PostgreSQL のとき、literalPrefix/literalSuffixに値が入っていない。
		else if (POSTGRES.equals(productName)) {
			switch (info.dataType) {
			case Types.CHAR:
			case Types.VARCHAR:
			case Types.DATE:
			case Types.TIME:
			case Types.TIMESTAMP:
				if (info.literalPrefix == null || info.literalSuffix == null) {
					result = "'" + value.toString() + "'";
				}
				break;
			}
		}
		return result;
	}
	/**
	 * 型の文字列を返す。
	 * "DECIMAL(7,2)" "VARCHAR2(80)" "SMALLINT NOT NULL" など
	 * @return
	 */
	public String getTypeStr(ColumnInfo col) {
		if (DB2.equals(productName)) {
			// DB2のとき、TypeInfoのCREATE_PARAMSで作成できる。
			DbTypeInfo type = getTypeInfo(col.dataType);
	        String typestr = col.typeName;
	        if (type.createParams != null) {
	        	if (type.createParams.equals("PRECISION,SCALE") ||
	        		type.createParams.equals("精度,位取り")) {
	        		typestr += "(" + col.columnSize + "," + col.decimalDigits + ")";
	        	} else if (type.createParams.equals("LENGTH") ||
	        			type.createParams.equals("最大長")) {
	        		typestr += "(" + col.columnSize + ")";
	        	} else {
	        		System.out.println("???"+type.createParams);
	        	}
	        }
			typestr += "YES".equals(col.isNullable) ? "" : " NOT NULL";
	        return typestr;
		} if (MYSQL.equals(productName)) {
			// MySQLのときも、TypeInfoのCREATE_PARAMSで作成できる。もう少し詳しく調べる必要がある。
			DbTypeInfo type = getTypeInfo(col.dataType);
	        String typestr = col.typeName;
	        if ("(M)".equals(type.createParams)) {
        		typestr += "(" + col.columnSize + ")";
	        }
			typestr += "YES".equals(col.isNullable) ? "" : " NOT NULL";
	        return typestr;
		} else {
			// それ以外のとき
	        String typestr = col.typeName;
	        if (col.columnSize != 0) {
				typestr += "("+col.columnSize+
				(col.decimalDigits != 0 ? (", "+col.decimalDigits) : "")
				+")";
			}
			typestr += "YES".equals(col.isNullable) ? "" : " NOT NULL";
			return typestr;
		}
	}
	public String getToolTipText(TableInfo table, ColumnInfo info) {
		String msg = "<b>"+getTypeStr(info)+"</b><br>"; 
		// PKか？
		int pk = table.getPrimaryKeySeq(info.columnName);
		if (pk != -1) {
			msg += "<b>PK</b>:"+pk+"<br>";
		}
		msg +=
//			"catalog:<b>"+info.tableCatalog+"</b><br>"+
//			"schema:<b>"+info.tableSchema+"</b><br>"+
//			"table:<b>"+info.tableName+"</b><br>"+
			"column:<b>"+info.columnName+"</b><br>"+
			"dataType:<b>"+info.dataType+"</b><br>"+
			"typeName:<b>"+info.typeName+"</b><br>"+
			"columnSize:<b>"+info.columnSize+"</b><br>"+
			"decimalDigits:<b>"+info.decimalDigits+"</b><br>"+
			"numPrecRadix:<b>"+info.numPrecRadix+"</b><br>"+
			"nullable:<b>"+info.nullable+"</b><br>"+
			"remarks:<b>"+info.remarks+"</b><br>"+
			"columnDefault:<b>"+info.columnDefault+"</b><br>"+
			"charOctetLength:<b>"+info.charOctetLength+"</b><br>"+
			"ordinalPosition:<b>"+info.ordinalPosition+"</b><br>"+
			"isNullable:<b>"+info.isNullable+"</b><br>"+
			"scopeCatalog:<b>"+info.scopeCatalog+"</b><br>"+
			"scopeSchema:<b>"+info.scopeSchema+"</b><br>"+
			"scopeTable:<b>"+info.scopeTable+"</b><br>"+
			"sourceDataType:<b>"+info.sourceDataType+"</b>";
		return "<html>"+msg+"</html>";
	}
	public String getToolTipText(ResultInfo info) {
		return "<html>"+
		"isAutoIncrement:<b>"+info.isAutoIncrement+"</b><br>"+
		"isCaseSensitive:<b>"+info.isCaseSensitive+"</b><br>"+
		"isSearchable:<b>"+info.isSearchable+"</b><br>"+
		"isCurrency:<b>"+info.isCurrency+"</b><br>"+
		"isNullable:<b>"+info.isNullable+"</b><br>"+
		"isSigned:<b>"+info.isSigned+"</b><br>"+
		"columnDisplaySize:<b>"+info.columnDisplaySize+"</b><br>"+
		"columnLabel:<b>"+info.columnLabel+"</b><br>"+
		"columnName:<b>"+info.columnName+"</b><br>"+
		"schemaName:<b>"+info.schemaName+"</b><br>"+
		"precision:<b>"+info.precision+"</b><br>"+
		"scale:<b>"+info.scale+"</b><br>"+
		"tableName:<b>"+info.tableName+"</b><br>"+
		"catalogName:<b>"+info.catalogName+"</b><br>"+
		"columnType:<b>"+info.columnType+"</b><br>"+
		"columnTypeName:<b>"+info.columnTypeName+"</b><br>"+
		"isReadOnly:<b>"+info.isReadOnly+"</b><br>"+
		"isWritable:<b>"+info.isWritable+"</b><br>"+
		"isDefinitelyWritable:<b>"+info.isDefinitelyWritable+"</b><br>"+
		"columnClassName:<b>"+info.columnClassName+"</b></html>";
	}

	SchemaInfo getSchemaInfo(String catalog, String schema) {
		for (int i=0; i<schemaList.size(); i++) {
			SchemaInfo s = (SchemaInfo) schemaList.get(i);
			if (SxTextUtilities.compareString(s.tableCatalog, catalog) == 0 &&
					SxTextUtilities.compareString(s.tableSchema, schema) == 0) {
				return s;
			}
		}
		return null;
	}
	public BlancoSqlRule getBlancoSqlRule() {
		// SQL整形のルールを作成する。
        BlancoSqlRule rule = new BlancoSqlRule();
        rule.setKeywordCase(this.keywordCase);
//        rule.indentString = "    ";
		// 関数名を登録する。
		String[] lines = this.functionNames.split("\n");
		ArrayList funclist = new ArrayList();
		for (int i=0; i<lines.length; i++) {
			if (lines[i].length() < 1) continue;		// 空文
			if (lines[i].charAt(0) == ';') continue;	// 先頭がセミコロンはコメント
			StringTokenizer st = new StringTokenizer(lines[i], " ,\t");
			while (st.hasMoreTokens()) {
				funclist.add(st.nextToken());
			}
		}
		String[] funcs = (String[]) funclist.toArray(new String[funclist.size()]);
		rule.setFunctionNames(funcs);
		return rule;
	}
	static SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");

	public static void outputLog(String message) {
		String str = formatter.format(new Date()) + " "+message;
		File file = new File("AsanDatabaseBrowser.log");
		PrintWriter out = null;
		try {
			out = new PrintWriter(new FileWriter(file, true));
			out.println(str);
			AsanDatabaseBrowser.theApp.logView.append(str+"\n");
		} catch (IOException e) {
			// 例外は無視する。
			e.printStackTrace();
		} finally {
			if (out != null) out.close();
		}
	}
}

/*
Oracle9iにおいてCLOB,BLOBで以下のエラーを起こす。
Oracle本体は、符号なし４バイト整数の最大値である4294967295を返すが、
JDBCのPrecisionはint(つまり符号付き４バイト整数)なので、それの変換ができずに失敗する。
OracleのJDBCドライバは、その範囲内になるようにPrecisionを下げるべき。
あるいは、JDBCはPrecisionの範囲を拡張するか、範囲以上の精度をあらわす特別な値（例えば、-1）を定義すべき。
run:
     [java] java.lang.NumberFormatException: For input string: "4294967295"
     [java]     at java.lang.NumberFormatException.forInputString(NumberFormatEx
ception.java:48)
     [java]     at java.lang.Integer.parseInt(Integer.java:480)
     [java]     at java.lang.Integer.parseInt(Integer.java:518)
     [java]     at oracle.jdbc.driver.OracleResultSetMetaData.getPrecision(Oracl
eResultSetMetaData.java:331)
     [java]     at dbtools.model.ResultInfo.getResultInfoList(ResultInfo.java:11
5)
     [java]     at dbtools.model.DbAccess.getResultInfoList(DbAccess.java:60)
     [java]     at dbtools.model.ResultSetInfo.getResultInfoList(ResultSetInfo.j
ava:19)
     [java]     at dbtools.model.ResultSetInfo.<init>(ResultSetInfo.java:14)
     [java]     at dbtools.view.DbSqlFrame.<init>(DbSqlFrame.java:33)
     [java]     at dbtools.DbTools.openSqlFrame(DbTools.java:39)
     [java]     at dbtools.view.DbTableFrame$OpenSqlFrameAction.actionPerformed(
DbTableFrame.java:141)
     [java]     at javax.swing.AbstractButton.fireActionPerformed(AbstractButton
.java:1786)
*/
