STU
- 機能拡張マニュアルfor STU ver. 1.00
ユーザーマニュアルを開く
STU
は、いくつかの機能拡張を可能にしています。このマニュアルでは、機能拡張の具体的な方法について説明しています。
拡張可能な各機能の概要を次に示します。
機能拡張の詳細について説明します。
STU
のAPIについては、APIドキュメント(Javadoc)をご覧ください。
※クラスを追加する際には、クラスパスに注意してください。
任意のコマンドを追加できます。
追加するクラスは、パッケージnet.argius.stu.command
に属するようにします。net.argius.stu.Command
クラスを継承し、execute()
メソッドをオーバーライドするだけで実装完了です。
クラス名は、頭文字のみ大文字で、それ以外は小文字にしてください。コマンド名はケースが無視されますので、クラスを検索する際にケースを一意にする必要があるためです。
次のサンプルは、Userコマンドの実装です。
User
クラス(User.java)
package net.argius.stu.command; import java.io.*; import java.sql.*; import net.argius.stu.*; public class User extends Command { protected void execute(Connection conn, String parameter) throws IllegalArgumentException, IOException, SQLException { DatabaseMetaData dbmd = conn.getMetaData(); String userName = dbmd.getUserName(); println("INPUT : " + parameter); println("UserName : " + userName); } }
User
の実行結果
> user INPUT : user UserName : TEST > user 1 2 3 INPUT : user 1 2 3 UserName : TEST >
このコマンドでは、DBコネクションのユーザ名を表示します。conn
は、現在接続中のコネクションです。parameter
は、プロンプトから入力されたままの文字列が渡されます。
エラーが発生した場合、Exception
をメソッド外にスローします。スローされた例外は、システム側で処理されます。以下は、ここで扱う例外についての説明です。
java.lang.IllegalArgumentException
java.io.IOException
java.io
パッケージ)などから報告されるエラーです。例外が報告されないエラーについても、入出力に関連するエラーはこの例外で報告するようにしてください。java.sql.SQLException
JDBC
のAPIから報告されるエラーです。例外が報告されないエラー(例えば、null
が返されたため処理が続行不能になった場合など)についても、DB操作に関連するエラーはこの例外で報告するようにしてください。java.lang.RuntimeException
追加するコマンドのクラスは、公開リリースされない限りは、基本的な実装方法以外の制限は特にありません。自由に実装して問題ありません。但し、将来のバージョンアップに備えて、実装済みの機能を使用することを推奨します。
net.argius.stu.Command
クラスは、コマンドが使用する基本的な機能をサブクラスに提供します。また、STU
のAPIには、入出力に関するパッケージ(net.argius.stu.io
)、JDBCやSQLに関するパッケージ(net.argius.stu.sql
)、テキスト編集に関するパッケージ(net.argius.stu.text
)があり、これらはシステムに依存しないAPIとなっています。(APIドキュメント参照)
次のサンプルは、Getfunctionsコマンドの実装です。java.sql.DatabaseMetaData
を使用して、DBで使用可能な関数名を取得するコマンドです。
Getfunctions
クラス(Getfunctions.java)
package net.argius.stu.command; import java.io.*; import java.sql.*; import net.argius.stu.*; import net.argius.stu.text.*; public final class Getfunctions extends Command { private static final String USAGE = "[ STRING | NUMBER | NUMERIC | DATE | TIME | SYSTEM ]"; private static final IllegalArgumentException usageError = new IllegalArgumentException(USAGE); protected void execute(Connection conn, String parameter) throws IllegalArgumentException, SQLException, IOException { StringQueue pq = split(parameter, 3); // Parameter Queue pq.draw(); // "GetFunctions"を捨てる if (pq.size() == 0) { throw usageError; } String type = pq.draw(); final String label = "FUNCTIONS : "; DatabaseMetaData dbmd = conn.getMetaData(); if (type.equalsIgnoreCase("STRING")) { println(label + dbmd.getStringFunctions()); } else if (type.equalsIgnoreCase("NUMBER") || type.equalsIgnoreCase("NUMERIC")) { println(label + dbmd.getNumericFunctions()); } else if (type.equalsIgnoreCase("DATE") || type.equalsIgnoreCase("TIME")) { println(label + dbmd.getTimeDateFunctions()); } else if (type.equalsIgnoreCase("SYSTEM")) { println(label + dbmd.getSystemFunctions()); } else { throw usageError; } } }
Getfunctions
の実行結果(Oracleの場合)
> getfunctions 使い方:GETFUNCTIONS [ STRING | NUMBER | NUMERIC | DATE | TIME | SYSTEM ] > getfunctions string FUNCTIONS : ASCII,CHAR,CONCAT,LCASE,LENGTH,LTRIM,REPLACE,RTRIM,SOUNDEX,SUBSTRING,UCASE > getfunctions date FUNCTIONS : CURDATE,CURTIME,DAYOFMONTH,HOUR,MINUTE,MONTH,NOW,SECOND,YEAR > getfunctions system FUNCTIONS : USER > getfunctions others 使い方:GETFUNCTIONS [ STRING | NUMBER | NUMERIC | DATE | TIME | SYSTEM ] >
次のサンプルは、Groupコマンドの実装です。GROUP BY
を含むSQLを自動的に生成して、結果を表示します。結果の表示には、結果出力機能(Command.showResult()
)を使用しています。
Group
クラス(Group.java)
package net.argius.stu.command; import java.io.*; import java.sql.*; import net.argius.stu.*; import net.argius.stu.text.*; public class Group extends Command { private static final String USAGE = "<table> <group list>"; private static final IllegalArgumentException usageError = new IllegalArgumentException(USAGE); protected void execute(Connection conn, String parameter) throws IllegalArgumentException, IOException, SQLException { StringQueue pq = split(parameter, -1); // Parameter Queue pq.draw(); // "Group"を捨てる if (pq.size() <= 0) { throw usageError; } // テーブル名 String table = pq.draw(); // グループリスト StringBuffer sb = new StringBuffer(); for (int i = 0; !pq.isEmpty(); i++) { if (i != 0) { sb.append(", "); } sb.append(pq.draw()); } String group = sb.toString(); // SQL作成 String query = "SELECT " + group + ", count(*) FROM " + table + " GROUP BY " + group; println(query); writeLog(Log.DEBUG, "query : " + query); // SQL発行 Statement stmt = conn.createStatement(); try { setTimeout(stmt); ResultSet rs = stmt.executeQuery(query); try { // 実行&結果出力 int recordSize = showResult(rs); // メッセージ表示 if (recordSize > 0) { println(getMessage("selected1", String.valueOf(recordSize))); } else { println(getMessage("recordnotfound")); } } finally { rs.close(); } } finally { stmt.close(); } } }
Group
の実行結果
> group 使い方:GROUP <table> <group list> > group dairy_table date SELECT date, count(*) FROM dairy_table GROUP BY date DATE COUNT(*) ======== ====================== 200x0730 2 200x0731 3 200x0801 12 200x0802 4 200x0803 7 5 件 ヒットしました。 >
STU
は、ビューと構造を分離した構造を持っています。
STU
では、入力システムを「スキャナ」、出力システムを「プリンタ」(出力プリンタとエラープリンタ)と呼んでいます。それぞれを入出力システムであるnet.argius.stu.IOManager
の実装サブクラスが統括しています。
入出力システムのカスタマイズは、入力システムのみ、出力システムのみ、エラー出力システムのみ、入出力システムすべての4種類が可能となっています。
※切替えに使用するクラスは、必ずデフォルトコンストラクタだけで初期化ができるように実装してください。
入力は、既定の入出力システムであるnet.argius.stu.DefaultIOManager
の保持しているスキャナによって処理されます。入力された文字列はコマンドに変換されて、コマンドのサブクラスが起動される仕組みになっています。
既定の入力はnet.argius.stu.io.Scanner
インターフェイスを実装したnet.argius.stu.io.DefaultScanner
となっています。これを別のクラスに差し替えることができます(ユーザーマニュアル・プロパティ参照)。
次のサンプルは、javax.swing.JFrame
を使って入力だけをGUIで行えるようにするGUIOneLinerScannerの実装です。(最低限の実装なので、使い勝手は良くありません。)
GUIOneLinerScanner
クラス(GUIOneLinerScanner.java)
package net.argius.stu.plugin; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import net.argius.stu.io.*; public final class GUIOneLinerScanner implements Scanner { private JFrame frame; private JTextField input; private JButton button; private boolean blockerIsAvailable; private boolean blocking; // 入力が無いとき何もしないようにブロックする private Thread blocker = new Thread() { public void run() { while (blockerIsAvailable) { if (blocking) { getInputMessage(); } } } }; // ボタンが押された時のイベントハンドラ private ActionListener onSubmit = new ActionListener() { public void actionPerformed(ActionEvent e) { String s = input.getText(); if (s != null && s.trim().length() > 0) { blocking = false; blocker.interrupt(); } } }; public GUIOneLinerScanner() { frame = new JFrame("STU - GUIOneLiner"); input = new JTextField(); input.setBorder(BorderFactory.createLineBorder(Color.white, 4)); button = new JButton("実行"); button.setDefaultCapable(true); button.addActionListener(onSubmit); JPanel panel = new JPanel(new BorderLayout()); panel.add(input, BorderLayout.CENTER); panel.add(button, BorderLayout.EAST); frame.getContentPane().add(panel); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.show(); frame.setSize(800, 80); frame.validate(); blockerIsAvailable = true; blocking = true; blocker.start(); } public String scan() throws IOException { String s = getInputMessage(); blocking = true; return s; } private synchronized String getInputMessage() { while (blocking) { try { wait(); } catch (InterruptedException e) { } } notify(); return input.getText(); } public void close() { blockerIsAvailable = false; frame.dispose(); } }
出力とエラー出力は、既定の入出力システムであるnet.argius.stu.DefaultIOManager
の保持している出力プリンタとエラープリンタによって処理されます。コマンドは、net.argius.stu.IOManager
経由で結果セット(java.sql.ResultSet
)の表示や、メッセージの出力を依頼してきます。net.argius.stu.IOManager
のサブクラスであるnet.argius.stu.DefaultIOManager
がこれらの具体的な出力方法を制御します。
既定の出力はnet.argius.stu.io.DefaultOutputPrinter
、エラー出力はnet.argius.stu.io.DefaultErrorPrinter
となっています。どちらもnet.argius.stu.io.Printer
インターフェイスの実装クラスです。これらを別のクラスに差し替えることができます(ユーザーマニュアル・プロパティ参照)。
次のサンプルは、出力先をカレントディレクトリのテキストファイルに設定するLocalFilePrinterの実装です。
LocalFilePrinter
クラス(LocalFilePrinter.java)
package net.argius.stu.plugin; import java.io.*; import net.argius.stu.io.*; public final class LocalFilePrinter implements Printer { // ファイル名固定 private static final String FILE_NAME = "./stu.output.log"; private PrintStream out; public LocalFilePrinter() throws IOException { out = new PrintStream(new FileOutputStream(FILE_NAME, true), true); } public void print(String message) { out.print(message); } public void println(String message) { out.println(message); } public void close() { out.close(); } }
次のサンプルは、javax.swing.JFrame
を使って出力先を別ウインドウに設定するJFramePrinterの実装です。
JFramePrinter
クラス(JFramePrinter.java)
package net.argius.stu.plugin; import java.awt.Color; import java.awt.Font; import javax.swing.BorderFactory; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; import net.argius.stu.io.Printer; import net.argius.stu.IOManager; public final class JFramePrinter implements Printer { private JFrame frame; private JTextArea outputArea; public JFramePrinter() { frame = new JFrame("STU - OutputPrinter"); outputArea = new JTextArea(); outputArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); JScrollPane outputPane = new JScrollPane(outputArea); outputPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); outputPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); outputPane.setViewportBorder(BorderFactory.createLineBorder(Color.white, 4)); frame.getContentPane().add(outputPane); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.show(); frame.setSize(800, 600); frame.validate(); } public void print(String message) { outputArea.append(message); } public void println(String message) { outputArea.append(message); outputArea.append(IOManager.getLineSeparator()); // 改行文字 } public void close() { frame.dispose(); } }
冒頭でも説明していますが、STU
では入出力システムであるnet.argius.stu.IOManager
が入出力を統括していて、既定の入出力システムは標準入出力を使用したnet.argius.stu.DefaultIOManager
となっています。これをnet.argius.stu.IOManager
抽象クラスのサブクラスに差し替えることができます(ユーザーマニュアル・プロパティ参照)。
入出力システムを書き換えた場合は、「プリンタ」「スキャナ」を使用する必要はなくなります。その代わりに、入力(scanLine
メソッド)、結果出力(showResult
メソッド)、メッセージ出力(printMessage
メソッド)の3メソッドを実装します。
次のサンプルは、1つのウィンドウに入出力機能をすべて備えたJFrameIOManagerの実装です。このクラスを使えば、GUIのみのプラットフォームでも起動することができます。(これも使い勝手は改善の余地があります。)
JFrameIOManager
クラス(JFrameIOManager.java)
package net.argius.stu.plugin; import java.awt.*; import java.awt.event.*; import java.io.*; import java.sql.*; import javax.swing.*; import net.argius.stu.*; import net.argius.stu.io.*; public class JFrameIOManager extends IOManager { private static final Font font = new Font("Monospaced", Font.PLAIN, 12); private JFrame frame; private JTextField commandLine; private JButton submit; private JTextArea outputArea; private JTextArea messageArea; private boolean blockerIsAvailable; private boolean blocking; // 入力をブロックするスレッド private Thread blocker = new Thread() { public void run() { while (blockerIsAvailable) { if (blocking) { waitMessage(); } } } }; // 実行ボタンを押したときのイベントハンドラ private ActionListener onSubmit = new ActionListener() { public void actionPerformed(ActionEvent e) { String s = commandLine.getText(); if (s.trim().length() == 0) { return; } outputArea.setText(""); blocking = false; blocker.interrupt(); } }; // コンストラクタ public JFrameIOManager() { // フレーム初期化 frame = new JFrame("STU - JFrameIOManager"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 入力テキストボックスの定義 JLabel commandLabel = new JLabel("コマンド"); commandLine = new JTextField(); commandLine.setFont(font); commandLine.addActionListener(onSubmit); // 実行ボタンの定義 submit = new JButton("実行"); submit.setDefaultCapable(true); submit.addActionListener(onSubmit); // 出力エリアの定義 outputArea = new JTextArea(); outputArea.setFont(font); JScrollPane outputPane = new JScrollPane(outputArea); // メッセージエリアの定義 messageArea = new JTextArea(); messageArea.setFont(font); JScrollPane messagePane = new JScrollPane(messageArea); // 出力エリアとメッセージエリアのスプリットペイン JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setTopComponent(outputPane); splitPane.setBottomComponent(messagePane); splitPane.setResizeWeight(0.7); // レイアウト JPanel p1 = new JPanel(new BorderLayout()); p1.add(commandLabel, BorderLayout.WEST); p1.add(commandLine, BorderLayout.CENTER); p1.add(submit, BorderLayout.EAST); frame.getContentPane().add(p1, BorderLayout.NORTH); frame.getContentPane().add(splitPane, BorderLayout.CENTER); // 入力待ちスレッドの起動 blocking = true; blockerIsAvailable = true; blocker.start(); // 表示 frame.show(); frame.setSize(800, 600); frame.validate(); } // 入力スキャンのブロック private synchronized void waitMessage() { while (blocking) { try { wait(); } catch (InterruptedException e) { } } notify(); } // 入力のスキャン public String scanLine() throws IOException { waitMessage(); blocking = true; return commandLine.getText(); } // 結果出力処理 public int showResult(ResultSet rs, int limit) throws IOException, SQLException { // 必須パラメータチェック if (rs == null) { throw new SQLException("ResultSet is null."); } // 結果表示の準備 Printer sp = new StringPrinter(); TableWriter writer = new NullTableWriter(); ResultSetMetaData md = rs.getMetaData(); int cols = md.getColumnCount(); int[] sizes = new int[cols]; boolean isFirst = true; boolean limited = (limit > 0 ? true : false); // メインループ int recordSize = 0; try { while (rs.next()) { if (limited && recordSize >= limit) { break; } ++recordSize; if (isFirst) { // 1件目のみ同時にヘッダを生成 writer = new PrinterTableWriter(sp, sizes, '-'); writer.open(); for (int i = 1; i <= cols; i++) { sizes[i-1] = md.getColumnDisplaySize(i); String name = md.getColumnName(i); writer.addColumn(name); } writer.nextRow(); isFirst = false; } // 1レコード出力 for (int i = 1; i <= cols; i++) { writer.addColumn(rs.getString(i)); } writer.nextRow(); } outputArea.append(sp.toString()); return recordSize; } finally { writer.close(); } } // メッセージの表示 public void printMessage(String message, boolean newLine) throws IOException { messageArea.append(message); if (newLine) { messageArea.append(IOManager.getLineSeparator()); } } }
STU
では、原則として各DBの実装詳細については個別に対応しないようにしていますが、いくつか例外があります。
SQLには、SELECT文で検索実行する際に取得する件数を制限するキーワードがありますが、これは製品によって異なります。(下図)
PostgreSQL, MySQL など ... SELECT * FROM TEST LIMIT 3 Oracle ... SELECT * FROM TEST WHERE ROWNUM <= 3 Microsoft系 (SQLServer, Access など) ... SELECT TOP 3 * FROM TEST
このキーワードは無駄な検索を避けるために使いたい場合が良くありますが、JDBCのインターフェイスだけでは対応できません。この違いを吸収するために、stu.sql.LimitEditor
という編集部品クラスを使用します。
stu.sql.LimitEditor
の実装例(stu.sql.PostgreSQLLimitEditor
)
package stu.sql; // "stu.sql." + DatabaseMetaData.getDatabaseProductName() + "Editor" final class PostgreSQLLimitEditor extends LimitEditor { public String edit(String sql, int limit) { StringBuffer sb = new StringBuffer(); sb.append(sql); sb.append(" LIMIT "); sb.append(limit); return sb.toString(); } }
stu.sql.LimitEditor
の使用例
// conn = java.sql.Connection LimitEditor editor = LimitEditor.getObject(conn); String sql = editor.edit("SELECT * FROM TEST", 3);
LimitEditor.getObject(conn)
は、DatabaseMetaData.getDatabaseProductName()
から返された文字列に対応するLimitEditor
のサブクラスを検索し、見つかった場合はそのサブクラスのインスタンスを返します。見つからない場合は、LimitEditor
自体のインスタンスを返します。(LimitEditor.edit()
は元の文字列をそのまま返すメソッドです。)このため、編集部品が見つからなくても、エラーにはなりません。
以下のクラスについては定義済みです。これら以外のDBに接続する場合、必要であれば追加してください。
PostgreSQLLimitEditor MySQLLimitEditor OracleLimitEditor ACCESSLimitEditor EXCELLimitEditor Microsoft_SQL_ServerLimitEditor
デバッグ機能について説明します。
トレースログは、プロパティを設定することで出力されるようになります。
# プロパティの例 net.argius.stu.system.log.file = D:/STU/stu.log net.argius.stu.system.log.level = DEBUG net.argius.stu.system.log.size = 4096 net.argius.stu.system.log.file net.argius.stu.system.log.level
net.argius.stu.system.log.file
ログファイルのパスを指定します。無効なパスの場合は、ログは出力されません。
net.argius.stu.system.log.level
トレースレベルを指定します。
net.argius.stu.system.log.size
トレースログファイルの最大ファイルサイズを指定します。これを超えると、ローテート処理されます。(元のファイルが[ファイル名].bakにリネームされます。)
STU version 1.00 - Extension Manual (2005.08.23)
COPYRIGHT(C) ARGIUS project