From 6cafa10a956e553e83b6f4dabefea86d262806b7 Mon Sep 17 00:00:00 2001 From: Olyutorskii Date: Wed, 11 May 2011 12:18:44 +0900 Subject: [PATCH] =?utf8?q?=E6=94=B9=E8=A1=8C=E3=82=B3=E3=83=BC=E3=83=89?= =?utf8?q?=E6=8C=87=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .hgeol | 13 + CHANGELOG.txt | 458 +-- LICENSE.txt | 66 +- README.txt | 196 +- .../jp/sourceforge/jindolf/AbstractTextRow.java | 314 +- .../java/jp/sourceforge/jindolf/AccountCookie.java | 374 +-- .../java/jp/sourceforge/jindolf/AccountPanel.java | 952 +++--- .../java/jp/sourceforge/jindolf/ActionManager.java | 1056 +++--- src/main/java/jp/sourceforge/jindolf/Anchor.java | 694 ++-- .../java/jp/sourceforge/jindolf/AnchorDraw.java | 602 ++-- .../jp/sourceforge/jindolf/AnchorHitEvent.java | 126 +- .../jp/sourceforge/jindolf/AnchorHitListener.java | 44 +- .../java/jp/sourceforge/jindolf/AppSetting.java | 832 ++--- src/main/java/jp/sourceforge/jindolf/Avatar.java | 628 ++-- .../java/jp/sourceforge/jindolf/BalloonBorder.java | 354 +- .../jp/sourceforge/jindolf/ClipboardAction.java | 266 +- .../java/jp/sourceforge/jindolf/CmdOption.java | 336 +- .../java/jp/sourceforge/jindolf/ConfigFile.java | 1700 +++++----- .../java/jp/sourceforge/jindolf/Controller.java | 3380 ++++++++++---------- .../java/jp/sourceforge/jindolf/CsvExporter.java | 1112 +++---- .../java/jp/sourceforge/jindolf/DaySummary.java | 1048 +++--- .../java/jp/sourceforge/jindolf/DialogPref.java | 256 +- .../jp/sourceforge/jindolf/DialogPrefPanel.java | 498 +-- .../java/jp/sourceforge/jindolf/Discussion.java | 2516 +++++++-------- .../java/jp/sourceforge/jindolf/EditArray.java | 1480 ++++----- src/main/java/jp/sourceforge/jindolf/EnvInfo.java | 302 +- .../java/jp/sourceforge/jindolf/FaceIconSet.java | 182 +- .../java/jp/sourceforge/jindolf/FileUtils.java | 788 ++--- .../java/jp/sourceforge/jindolf/FilterPanel.java | 1052 +++--- .../java/jp/sourceforge/jindolf/FindPanel.java | 1548 ++++----- .../java/jp/sourceforge/jindolf/FontChooser.java | 1280 ++++---- src/main/java/jp/sourceforge/jindolf/FontInfo.java | 526 +-- .../java/jp/sourceforge/jindolf/FontUtils.java | 320 +- src/main/java/jp/sourceforge/jindolf/GUIUtils.java | 798 ++--- .../java/jp/sourceforge/jindolf/GameSummary.java | 2170 ++++++------- .../java/jp/sourceforge/jindolf/GlyphDraw.java | 1540 ++++----- .../java/jp/sourceforge/jindolf/HelpFrame.java | 370 +-- .../java/jp/sourceforge/jindolf/HtmlSequence.java | 214 +- .../java/jp/sourceforge/jindolf/HttpUtils.java | 450 +-- .../java/jp/sourceforge/jindolf/ImtblAffineTx.java | 402 +-- .../java/jp/sourceforge/jindolf/InterVMLock.java | 476 +-- src/main/java/jp/sourceforge/jindolf/Jindolf.java | 1634 +++++----- src/main/java/jp/sourceforge/jindolf/Land.java | 850 ++--- .../java/jp/sourceforge/jindolf/LandInfoPanel.java | 334 +- .../java/jp/sourceforge/jindolf/LandsModel.java | 910 +++--- .../java/jp/sourceforge/jindolf/LandsTree.java | 496 +-- src/main/java/jp/sourceforge/jindolf/LogFrame.java | 714 ++--- .../java/jp/sourceforge/jindolf/LogWrapper.java | 304 +- .../java/jp/sourceforge/jindolf/Monodizer.java | 352 +- .../java/jp/sourceforge/jindolf/OptionInfo.java | 590 ++-- .../java/jp/sourceforge/jindolf/OptionPanel.java | 478 +-- src/main/java/jp/sourceforge/jindolf/Period.java | 2484 +++++++------- .../java/jp/sourceforge/jindolf/PeriodView.java | 764 ++--- .../java/jp/sourceforge/jindolf/PileHandler.java | 168 +- src/main/java/jp/sourceforge/jindolf/Player.java | 370 +-- .../java/jp/sourceforge/jindolf/ProxyChooser.java | 618 ++-- .../java/jp/sourceforge/jindolf/ProxyInfo.java | 470 +-- .../java/jp/sourceforge/jindolf/RegexPattern.java | 606 ++-- .../java/jp/sourceforge/jindolf/Selectable.java | 78 +- .../jindolf/SequenceCharacterIterator.java | 368 +-- .../java/jp/sourceforge/jindolf/ServerAccess.java | 1240 +++---- .../java/jp/sourceforge/jindolf/StringUtils.java | 366 +-- src/main/java/jp/sourceforge/jindolf/SysEvent.java | 494 +-- .../java/jp/sourceforge/jindolf/SysEventDraw.java | 452 +-- .../java/jp/sourceforge/jindolf/TabBrowser.java | 798 ++--- src/main/java/jp/sourceforge/jindolf/Talk.java | 518 +-- src/main/java/jp/sourceforge/jindolf/TalkDraw.java | 1446 ++++----- .../java/jp/sourceforge/jindolf/TalkEditor.java | 1098 +++---- .../java/jp/sourceforge/jindolf/TalkPreview.java | 1012 +++--- .../jp/sourceforge/jindolf/TallyInputStream.java | 352 +- .../jp/sourceforge/jindolf/TallyOutputStream.java | 290 +- .../java/jp/sourceforge/jindolf/TextEditor.java | 568 ++-- .../java/jp/sourceforge/jindolf/TextPopup.java | 306 +- src/main/java/jp/sourceforge/jindolf/TextRow.java | 162 +- src/main/java/jp/sourceforge/jindolf/TopView.java | 600 ++-- src/main/java/jp/sourceforge/jindolf/Topic.java | 30 +- .../java/jp/sourceforge/jindolf/TopicFilter.java | 82 +- .../java/jp/sourceforge/jindolf/UriExporter.java | 222 +- src/main/java/jp/sourceforge/jindolf/Village.java | 1750 +++++----- .../java/jp/sourceforge/jindolf/VillageDigest.java | 1676 +++++----- .../sourceforge/jindolf/VillageIconRenderer.java | 200 +- .../jp/sourceforge/jindolf/VillageInfoPanel.java | 408 +-- .../java/jp/sourceforge/jindolf/WebButton.java | 330 +- src/main/java/jp/sourceforge/jindolf/WebIPC.java | 694 ++-- .../java/jp/sourceforge/jindolf/WebIPCDialog.java | 932 +++--- src/main/java/jp/sourceforge/jindolf/WolfBBS.java | 1036 +++--- .../sourceforge/jindolf/XmlResourceResolver.java | 716 ++--- src/main/java/jp/sourceforge/jindolf/XmlUtils.java | 316 +- .../sourceforge/jindolf/json/AbstractJsValue.java | 106 +- .../java/jp/sourceforge/jindolf/json/JsArray.java | 464 +-- .../jp/sourceforge/jindolf/json/JsBoolean.java | 242 +- .../java/jp/sourceforge/jindolf/json/JsNull.java | 142 +- .../java/jp/sourceforge/jindolf/json/JsNumber.java | 656 ++-- .../java/jp/sourceforge/jindolf/json/JsObject.java | 610 ++-- .../java/jp/sourceforge/jindolf/json/JsPair.java | 246 +- .../sourceforge/jindolf/json/JsParseException.java | 66 +- .../java/jp/sourceforge/jindolf/json/JsString.java | 544 ++-- .../java/jp/sourceforge/jindolf/json/JsValue.java | 66 +- .../sourceforge/jindolf/json/JsVisitException.java | 66 +- .../java/jp/sourceforge/jindolf/json/Json.java | 388 +-- .../jp/sourceforge/jindolf/json/JsonAppender.java | 570 ++-- .../jp/sourceforge/jindolf/json/JsonReader.java | 76 +- .../jp/sourceforge/jindolf/json/ValueVisitor.java | 72 +- .../jp/sourceforge/jindolf/json/package-info.java | 114 +- .../java/jp/sourceforge/jindolf/package-info.java | 124 +- .../java/jp/sourceforge/jindolf/AvatarTest.java | 370 +-- .../java/jp/sourceforge/jindolf/HttpUtilsTest.java | 186 +- .../jp/sourceforge/jindolf/StringUtilsTest.java | 554 ++-- .../jp/sourceforge/jindolf/json/JsArrayTest.java | 848 ++--- .../jp/sourceforge/jindolf/json/JsBooleanTest.java | 542 ++-- .../jp/sourceforge/jindolf/json/JsNullTest.java | 260 +- .../jp/sourceforge/jindolf/json/JsNumberTest.java | 940 +++--- .../jp/sourceforge/jindolf/json/JsObjectTest.java | 926 +++--- .../jp/sourceforge/jindolf/json/JsPairTest.java | 208 +- .../jp/sourceforge/jindolf/json/JsStringTest.java | 862 ++--- 115 files changed, 36331 insertions(+), 36318 deletions(-) create mode 100644 .hgeol diff --git a/.hgeol b/.hgeol new file mode 100644 index 0000000..ca5f1c1 --- /dev/null +++ b/.hgeol @@ -0,0 +1,13 @@ +[patterns] + +**.txt = native + +**.java = native +**.properties = LF + +**.xml = LF +**.xsd = LF + +**.css = LF +**.html = LF +**.png = BIN diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1a62635..315ce66 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,229 +1,229 @@ -[UTF-8 Japanese] - - -Jindolf 変更履歴 - - -3.206.4 (2011-05-10) - ・10億をこえる発言番号へのアンカー参照を抑止。(バグ報告#24477,#24946) - -3.206.2 (2010-12-02) - ・G国新メッセージ「宿を去った。」に伴い -  JinParser1.407.2版に対処。 - -3.205.2 (2010-11-29) - ・G国新メッセージ「まだ村人達は揃っていないようだ。」に伴い -  JinParser1.406.2版に対処。 - -3.204.2 (2010-11-27) - ・G国の発言行数制限が5行から10行へ緩和された仕様変更に対処。 - -3.203.2 (2010-08-26) - ・G国定員増(Max16名)に伴い JinCore 1.204.2版に対応。 - ・凪庵氏作の顔アイコンおよびハムスター画像のWiki表記を変更。 - ・1000発言を超えるエピローグの強制リロード時に警告。 - -3.202.8 (2010-08-26) - ・MavenとMercurialによる開発体制に移行。 - -3.202.6 (2010-05-13) - ・偽装GJの成立条件を見直し。(バグ報告#21686) - ・投票結果に処刑者が表れない場合に対処。(バグ報告#21688) - ・JinParser1.404.2に対応。 - -3.202.4 (2010-03-25) - ・G国のorder赤字メッセージに対応。 - ・JinParser1.403.2に対応。 - -3.202.2 (2010-03-15) - ・G国失踪イベントに対応。 - ・「>>00」のようなありえないG国アンカーを無視。 - -3.201.2 (2010-03-15) - ・G国に対応。 - ・ロゴイメージを変更。 - ・UTF-8化に伴い発言エディタのJISX0208文字チェックを外す。 - -3.104.2 (2010-01-17) - ・シンプルモードの追加。(改善要求#12773) - ・フキダシ幅統一オプション追加。(改善要求#17055) - ・進行中1dのパースに失敗するエラーに対処。(バグ報告#20321) - -3.103.2 (2009-12-21) - ・デカキャラモードの追加。(改善要求#12761) - ・遺影モードの追加。 - -3.102.2 (2009-12-06) - ・GUIを通じたプロクシ設定が可能になる。(改善要求#12762) - ・フォント設定を自動的に保存。 - ・発言エディタの作業内容を自動的に保存。 - ・まとめサイトの&charプラグインが役職に対応しなくなった件に対処。 - ・顔アイコン見本ページ表示ボタンを追加。 - -3.101.2 (2009-11-17) - ・設定格納ディレクトリのサポートを開始。 - ・検索履歴の保存。 - ・多重起動の監視。 - ・-confdir, -noconfdirオプションの追加。 - -2.20.2 (2009-09-10) - ・過去ログ提供サーバのドメイン変更に対処。 - ・PukiWikiブラケット表記の改善。(バグ#16501) - ・パーサ部分をJinParser 1.357.2 版として分離。 - ・ビルド環境を Apache Ant に移行。 - ・F1556村プロローグ8人目登場箇所に対処。 - -2.19.2 (2009-05-22) - ・F国時刻表記の変更に対処。 - ・コアライブラリ分離の準備。 - ・各種設定ファイルのXML化。 - ・初期化時の異常を標準エラーに報告。 - ・不安定なタブ操作を改善。 - -2.18.2 (2009-03-31) - ・村のダイジェスト報告機能追加。(改善要求#13944) - ・まとめサイト(wolfbbs)用Wikiの自動生成機能追加。(改善要求#13644) - ・発言フィルタの非リアルタイム化。(改善要求#13946) - ・ツールバーにフィルタやエディタのボタンを追加。 - -2.17.4 (2009-03-02) - ・JRE1.5系の一部のエンコーディング実装のバグを回避。(バグ#15371) - -2.17.2 (2009-02-28) - ・CSVファイルへのエクスポート機能追加。(改善要求#12759) - ・アンカーの認識率アップ。(改善要求#12858) - ・ヘルプ表示障害の回避。(バグ#13738) - ・ゲームが成り立っていない村をマーク(改善要求#12769) - ・エピローグへのアンカーを展開しないバグを修正。(バグ#15043) - ・イベントキューのカスタム化。 - ・スループットの計算が1バイト間違えていた。(バグ#15045) - ・発言エディタにクリアボタン追加。 - ・E国のシステムメッセージ解析不備を修正。(バグ#15311) - ・環境依存文字情報の収集。 - ・XHTML解析の省メモリ化。 - -2.16.2 (2009-01-25) - ・WebブラウザとのURL連携を実装。(JRE1.5では一部機能に制限あり) - ・他アプリに対するURLのDrag&Dropを実装。(改善要求#12766) - ・まとめサイトやキャスト紹介表ジェネレータとの連携。(改善要求#12753) - ・見終わった村の発言をメモリ上から解放するようにした。(バグ#13942) - -2.15.2 (2009-01-16) - ・OR検索の実装。(改善要求#13640) - ・検索のクリアボタンが反映されるようにした。(バグ#14643) - ・村情報パネルに更新日時を表示。 - ・パーマネント画像のキャッシング。 - ・ログにHTTPスループットを表示。 - ・ログ表示に等幅フォントを適用。 - ・進行中の村のログイン状態を表示。 - -2.14.2 (2008-11-30) - ・発言エディタの実装。(改善要求#13643) - ・各種テキストコンポーネントにクリップボード操作用の -  ポップアップメニューを付ける。 - -2.13.2 (2008-10-30) - ・ポップアップメニューからアンカーへジャンプ。(改善要求#13642) - ・検索文字列履歴機能。(改善要求#13639) - ・セキュリティ要件緩和。(LoggingPermissionはもはや必須でない) - ・発言フィルタのキャラ順序を修正(バグ#13799) - -2.12.2 (2008-10-17) - ・プルダウンメニューによる発言ジャンプ。(改善要求#13641) - ・村名と更新時刻を常に表示。(改善要求#13506) - ・集計表に着色。 - ・ヘルプ画面を分割。 - ・ログの消去機能。 - ・キャラクタ定義のリソース化。 - ・メニュー構成を若干変更。 - -2.11.2 (2008-10-10) - ・日毎の発言集計機能追加。(改善要求#12756) - ・アンカーの認識率アップ。(改善要求#12858) -  今回対処したアンカーの例) 「1d-23:34」 「3d_02:13」 「5d[11:02]」 - ・F1603 ペータ2d21:12 対策。(バグ#13599) - ・C1085 レジーナ0d15:00 対策。(バグ#13600) - ・1件のみの検索ヒットでも、必ずナビゲーションボタンで -  ヒット位置までスクロールするようにした。 - -2.10.2 (2008-10-01) - ・好きな位置から検索ナビゲーションの開始が可能になった。 - ・一部メニューにキーボードアクセラレーションを設定。(改善要求#12777) -  (F5キーでリロードなど) - ・各種ウィンドウをダイアログ化。(改善要求#12779) -  (アカウント管理、発言フィルタ、ログ表示など) - ・正規表現でない普通のリテラル文字列での検索が可能になった。 -  (改善要求#13049) - ・ドッキング可能なツールバーの導入。 - ・村全体の一括ロード・検索機能を追加。 - -2.9.4 (2008-09-27) - ・発言検索機能の改善。(ナビゲーションボタンの追加など)(改善要求#12776) - ・クリップボード操作の結果をステータスバーに表示。 - ・アンカー展開時の異常系(存在しないアンカー)をステータスバーに表示。 - ・「編集」メニューの追加。 - ・画像リソースの移動。 - ・リソースの多層化構造に伴うWindows用ビルドスクリプトの修正。 - -2.8.2 (2008-09-11) - ・GUIやコマンドラインを通じて発言表示フォントが選択できるようになった。 -  (改善要求#12755) - ・メニューを日本語化した。 - ・F282村などの長大なエピローグを読めるようにした。(バグ#13406) - ・フィルタ中の発言がマウス操作に反応するバグに対処。(バグ#13494) - ・フィルタ中の発言をクリップボードにコピーしなくなった。 - -2.7.2 (2008-09-05) - ・発言表示処理の全面リニューアル。 - ・Extraシステムメッセージのフィルタリングに対応。(改善要求#13050) - ・禁則処理の撤廃(バグ#12820) - ・村選択ツリーでの複数選択操作を禁止。 - -2.6.4 (2008-08-26) - ・ログインできていないバグに対処。(バグ#13391) - -2.6.2 (2008-07-18) - ・発言回数・字数を表示。 - ・ログ出力ウィンドウ新設。(改善要求#12767) - ・-geometryオプションでウィンドウサイズの指定が可能。(改善要求#12748) - ・メモリ不足時に適切なメッセージと共に操作を続行できるよう改善。 - ・村一覧ツリー表示はデフォルトで降順に。 - ・-help,-version,-vminfoなど、アプリ・実行環境の表示オプションを追加。 - ・JRE1.5でスプラッシュ画面を出さない-nosplashオプションを追加。 - ・MetalLook&FeelでBOLDフォントを許可する-boldMetalオプションを追加。 - ・従来どおりコンソールにもログを出力する-consolelogオプションを追加。 - -2.5.2 (2008-07-11) - ・1発言のみワンタッチでクリップボードにコピーする機能を追加。 - ・村種別ごとに色つきアイコンで区別を付けやすくした。 - ・村一覧を強制リロードできるようになった。 - ・村一覧をJEのように降順表示させることが可能となった。 - ・発言のリロード後もスクロール位置を保持するように変更。(改善要求#12772) - ・全般的なスレッドセーフ対策。 - ・ビジー処理改善。(ステータスバー、マウスカーソル、GUIイベントマスク) - ・検索ヒット数をプログレスバーに表示。 - ・JRE1.5でもスプラッシュウィンドウが出るようにした。 - -2.4.2 (2008-07-03) - ・ポップアップメニューからの文字コピー操作を可能にした。 - ・国/村一覧を三段ツリー表示にした。(改善要求#12765) - ・検索ヒットの色を赤から黄色に変更。 - ・プログレスバーの位置を変更 - -2.2.2 (2008-06-28) - ・発言アンカーの認識率が大幅に向上。(改善要求#12858) - ・発言フィルタGUIに「反転」ボタンなどを追加。 - ・ネットワーク異常系を全体的に見直し。(改善要求#12775の一部) - ・ログ出力の統一。(改善要求#12767の一部) - ・認証GUIの細かな改善。(リターンキーの押下で即ログインなど) - ・イベントの細分化に伴う色分け。(占い結果を灰色で出力など) - ・WindowsおよびUNIX用にビルドスクリプトを添付。 - -2.1.8 (2008-06-20) - ・発言フィルタリング処理の高速化。(改善要求#12774の一部) - ・顔アイコン表示時のNullPointerException発生に対処。(バグ#12811) - -2.1.6 (2008-06-12) - ・初回リリース。 - ---- EOF --- +[UTF-8 Japanese] + + +Jindolf 変更履歴 + + +3.206.4 (2011-05-10) + ・10億をこえる発言番号へのアンカー参照を抑止。(バグ報告#24477,#24946) + +3.206.2 (2010-12-02) + ・G国新メッセージ「宿を去った。」に伴い +  JinParser1.407.2版に対処。 + +3.205.2 (2010-11-29) + ・G国新メッセージ「まだ村人達は揃っていないようだ。」に伴い +  JinParser1.406.2版に対処。 + +3.204.2 (2010-11-27) + ・G国の発言行数制限が5行から10行へ緩和された仕様変更に対処。 + +3.203.2 (2010-08-26) + ・G国定員増(Max16名)に伴い JinCore 1.204.2版に対応。 + ・凪庵氏作の顔アイコンおよびハムスター画像のWiki表記を変更。 + ・1000発言を超えるエピローグの強制リロード時に警告。 + +3.202.8 (2010-08-26) + ・MavenとMercurialによる開発体制に移行。 + +3.202.6 (2010-05-13) + ・偽装GJの成立条件を見直し。(バグ報告#21686) + ・投票結果に処刑者が表れない場合に対処。(バグ報告#21688) + ・JinParser1.404.2に対応。 + +3.202.4 (2010-03-25) + ・G国のorder赤字メッセージに対応。 + ・JinParser1.403.2に対応。 + +3.202.2 (2010-03-15) + ・G国失踪イベントに対応。 + ・「>>00」のようなありえないG国アンカーを無視。 + +3.201.2 (2010-03-15) + ・G国に対応。 + ・ロゴイメージを変更。 + ・UTF-8化に伴い発言エディタのJISX0208文字チェックを外す。 + +3.104.2 (2010-01-17) + ・シンプルモードの追加。(改善要求#12773) + ・フキダシ幅統一オプション追加。(改善要求#17055) + ・進行中1dのパースに失敗するエラーに対処。(バグ報告#20321) + +3.103.2 (2009-12-21) + ・デカキャラモードの追加。(改善要求#12761) + ・遺影モードの追加。 + +3.102.2 (2009-12-06) + ・GUIを通じたプロクシ設定が可能になる。(改善要求#12762) + ・フォント設定を自動的に保存。 + ・発言エディタの作業内容を自動的に保存。 + ・まとめサイトの&charプラグインが役職に対応しなくなった件に対処。 + ・顔アイコン見本ページ表示ボタンを追加。 + +3.101.2 (2009-11-17) + ・設定格納ディレクトリのサポートを開始。 + ・検索履歴の保存。 + ・多重起動の監視。 + ・-confdir, -noconfdirオプションの追加。 + +2.20.2 (2009-09-10) + ・過去ログ提供サーバのドメイン変更に対処。 + ・PukiWikiブラケット表記の改善。(バグ#16501) + ・パーサ部分をJinParser 1.357.2 版として分離。 + ・ビルド環境を Apache Ant に移行。 + ・F1556村プロローグ8人目登場箇所に対処。 + +2.19.2 (2009-05-22) + ・F国時刻表記の変更に対処。 + ・コアライブラリ分離の準備。 + ・各種設定ファイルのXML化。 + ・初期化時の異常を標準エラーに報告。 + ・不安定なタブ操作を改善。 + +2.18.2 (2009-03-31) + ・村のダイジェスト報告機能追加。(改善要求#13944) + ・まとめサイト(wolfbbs)用Wikiの自動生成機能追加。(改善要求#13644) + ・発言フィルタの非リアルタイム化。(改善要求#13946) + ・ツールバーにフィルタやエディタのボタンを追加。 + +2.17.4 (2009-03-02) + ・JRE1.5系の一部のエンコーディング実装のバグを回避。(バグ#15371) + +2.17.2 (2009-02-28) + ・CSVファイルへのエクスポート機能追加。(改善要求#12759) + ・アンカーの認識率アップ。(改善要求#12858) + ・ヘルプ表示障害の回避。(バグ#13738) + ・ゲームが成り立っていない村をマーク(改善要求#12769) + ・エピローグへのアンカーを展開しないバグを修正。(バグ#15043) + ・イベントキューのカスタム化。 + ・スループットの計算が1バイト間違えていた。(バグ#15045) + ・発言エディタにクリアボタン追加。 + ・E国のシステムメッセージ解析不備を修正。(バグ#15311) + ・環境依存文字情報の収集。 + ・XHTML解析の省メモリ化。 + +2.16.2 (2009-01-25) + ・WebブラウザとのURL連携を実装。(JRE1.5では一部機能に制限あり) + ・他アプリに対するURLのDrag&Dropを実装。(改善要求#12766) + ・まとめサイトやキャスト紹介表ジェネレータとの連携。(改善要求#12753) + ・見終わった村の発言をメモリ上から解放するようにした。(バグ#13942) + +2.15.2 (2009-01-16) + ・OR検索の実装。(改善要求#13640) + ・検索のクリアボタンが反映されるようにした。(バグ#14643) + ・村情報パネルに更新日時を表示。 + ・パーマネント画像のキャッシング。 + ・ログにHTTPスループットを表示。 + ・ログ表示に等幅フォントを適用。 + ・進行中の村のログイン状態を表示。 + +2.14.2 (2008-11-30) + ・発言エディタの実装。(改善要求#13643) + ・各種テキストコンポーネントにクリップボード操作用の +  ポップアップメニューを付ける。 + +2.13.2 (2008-10-30) + ・ポップアップメニューからアンカーへジャンプ。(改善要求#13642) + ・検索文字列履歴機能。(改善要求#13639) + ・セキュリティ要件緩和。(LoggingPermissionはもはや必須でない) + ・発言フィルタのキャラ順序を修正(バグ#13799) + +2.12.2 (2008-10-17) + ・プルダウンメニューによる発言ジャンプ。(改善要求#13641) + ・村名と更新時刻を常に表示。(改善要求#13506) + ・集計表に着色。 + ・ヘルプ画面を分割。 + ・ログの消去機能。 + ・キャラクタ定義のリソース化。 + ・メニュー構成を若干変更。 + +2.11.2 (2008-10-10) + ・日毎の発言集計機能追加。(改善要求#12756) + ・アンカーの認識率アップ。(改善要求#12858) +  今回対処したアンカーの例) 「1d-23:34」 「3d_02:13」 「5d[11:02]」 + ・F1603 ペータ2d21:12 対策。(バグ#13599) + ・C1085 レジーナ0d15:00 対策。(バグ#13600) + ・1件のみの検索ヒットでも、必ずナビゲーションボタンで +  ヒット位置までスクロールするようにした。 + +2.10.2 (2008-10-01) + ・好きな位置から検索ナビゲーションの開始が可能になった。 + ・一部メニューにキーボードアクセラレーションを設定。(改善要求#12777) +  (F5キーでリロードなど) + ・各種ウィンドウをダイアログ化。(改善要求#12779) +  (アカウント管理、発言フィルタ、ログ表示など) + ・正規表現でない普通のリテラル文字列での検索が可能になった。 +  (改善要求#13049) + ・ドッキング可能なツールバーの導入。 + ・村全体の一括ロード・検索機能を追加。 + +2.9.4 (2008-09-27) + ・発言検索機能の改善。(ナビゲーションボタンの追加など)(改善要求#12776) + ・クリップボード操作の結果をステータスバーに表示。 + ・アンカー展開時の異常系(存在しないアンカー)をステータスバーに表示。 + ・「編集」メニューの追加。 + ・画像リソースの移動。 + ・リソースの多層化構造に伴うWindows用ビルドスクリプトの修正。 + +2.8.2 (2008-09-11) + ・GUIやコマンドラインを通じて発言表示フォントが選択できるようになった。 +  (改善要求#12755) + ・メニューを日本語化した。 + ・F282村などの長大なエピローグを読めるようにした。(バグ#13406) + ・フィルタ中の発言がマウス操作に反応するバグに対処。(バグ#13494) + ・フィルタ中の発言をクリップボードにコピーしなくなった。 + +2.7.2 (2008-09-05) + ・発言表示処理の全面リニューアル。 + ・Extraシステムメッセージのフィルタリングに対応。(改善要求#13050) + ・禁則処理の撤廃(バグ#12820) + ・村選択ツリーでの複数選択操作を禁止。 + +2.6.4 (2008-08-26) + ・ログインできていないバグに対処。(バグ#13391) + +2.6.2 (2008-07-18) + ・発言回数・字数を表示。 + ・ログ出力ウィンドウ新設。(改善要求#12767) + ・-geometryオプションでウィンドウサイズの指定が可能。(改善要求#12748) + ・メモリ不足時に適切なメッセージと共に操作を続行できるよう改善。 + ・村一覧ツリー表示はデフォルトで降順に。 + ・-help,-version,-vminfoなど、アプリ・実行環境の表示オプションを追加。 + ・JRE1.5でスプラッシュ画面を出さない-nosplashオプションを追加。 + ・MetalLook&FeelでBOLDフォントを許可する-boldMetalオプションを追加。 + ・従来どおりコンソールにもログを出力する-consolelogオプションを追加。 + +2.5.2 (2008-07-11) + ・1発言のみワンタッチでクリップボードにコピーする機能を追加。 + ・村種別ごとに色つきアイコンで区別を付けやすくした。 + ・村一覧を強制リロードできるようになった。 + ・村一覧をJEのように降順表示させることが可能となった。 + ・発言のリロード後もスクロール位置を保持するように変更。(改善要求#12772) + ・全般的なスレッドセーフ対策。 + ・ビジー処理改善。(ステータスバー、マウスカーソル、GUIイベントマスク) + ・検索ヒット数をプログレスバーに表示。 + ・JRE1.5でもスプラッシュウィンドウが出るようにした。 + +2.4.2 (2008-07-03) + ・ポップアップメニューからの文字コピー操作を可能にした。 + ・国/村一覧を三段ツリー表示にした。(改善要求#12765) + ・検索ヒットの色を赤から黄色に変更。 + ・プログレスバーの位置を変更 + +2.2.2 (2008-06-28) + ・発言アンカーの認識率が大幅に向上。(改善要求#12858) + ・発言フィルタGUIに「反転」ボタンなどを追加。 + ・ネットワーク異常系を全体的に見直し。(改善要求#12775の一部) + ・ログ出力の統一。(改善要求#12767の一部) + ・認証GUIの細かな改善。(リターンキーの押下で即ログインなど) + ・イベントの細分化に伴う色分け。(占い結果を灰色で出力など) + ・WindowsおよびUNIX用にビルドスクリプトを添付。 + +2.1.8 (2008-06-20) + ・発言フィルタリング処理の高速化。(改善要求#12774の一部) + ・顔アイコン表示時のNullPointerException発生に対処。(バグ#12811) + +2.1.6 (2008-06-12) + ・初回リリース。 + +--- EOF --- diff --git a/LICENSE.txt b/LICENSE.txt index bf51138..15091f5 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,33 +1,33 @@ -[UTF-8 Japanese] - -The MIT License - - -Copyright(c) 2008 olyutorskii - - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - -Jindolf作者自身からのコメント: - - ※ 少なくともこのソフトウェアの実行、複製、配布、改造は自由です。 - ※ 少なくともこのソフトウェアは無保証です。 - ---- EOF --- +[UTF-8 Japanese] + +The MIT License + + +Copyright(c) 2008 olyutorskii + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Jindolf作者自身からのコメント: + + ※ 少なくともこのソフトウェアの実行、複製、配布、改造は自由です。 + ※ 少なくともこのソフトウェアは無保証です。 + +--- EOF --- diff --git a/README.txt b/README.txt index c5ecf50..5d34e8c 100644 --- a/README.txt +++ b/README.txt @@ -1,98 +1,98 @@ -[UTF-8 Japanese] - - J i n d o l f - README - - Copyright(c) 2008 olyutorskii - - -=== Jindolfとは === - - Jindolfプロジェクトは、CGIゲーム「人狼BBS」を快適にプレイするための -専用クライアントを製作するために発足したオープンソースプロジェクトです。 - -※ このアーカイブは、開発者向けにJindolfのソースコードのみをまとめたものです。 -  ただJindolfをプレイしたいだけの人は、別途JARファイルを入手してください。 -※ 人狼BBSのURLは [ http://homepage2.nifty.com/ninjinia/ ] まで -※ 人狼BBSを主催するninjin氏は、Jindolfの製作に一切関与していません。 -  Jindolfに関する問い合わせををninjin氏へ投げかけないように!約束だよ! - - -=== 実行環境 === - - - JindolfはJava言語(JLS3)で記述されたプログラムです。 - - JindolfはJRE1.5に準拠したJava実行環境で利用できるように作られています。 - 原則として、JRE1.5に準拠した実行系であれば、プラットフォームを選びません。 - - JindolfはGUIを通じて操作するプログラムのため、その実行においては - ビットマップディスプレイとポインティングデバイスとキーボードへの接続を - 必要とします。 - - Jindolfは人狼BBSサーバとHTTP通信を行うため、TCP/IPネットワーク環境を - 必要とします。 - - 人狼BBSは符号化された日本語で遊ばれるため、Jindolfの実行には日本語環境が - 必要です。 - - -=== 依存ライブラリ === - - - Jindolfはビルドおよび実行に際してJinCoreおよびJinParserライブラリを - 必要とします。開発時はMaven等を用いてこれらのライブラリを用意してください。 - - - -=== アーカイブ管理体制 === - - このアーカイブは、UTF-8による開発環境を前提として構成されています。 - このアーカイブの原本となる開発資産は、 - http://hg.sourceforge.jp/view/jindolf/Jindolf - を上位に持つMercurialリポジトリで管理されています。 - - -=== 開発プロジェクト運営元 === - - http://sourceforge.jp/projects/jindolf/devel/ まで。 - - -=== ソフトウェア利用者向けポータルサイト === - - http://jindolf.sourceforge.jp/ まで。 - - -=== ディレクトリ内訳構成 === - -基本的にはMaven2のmaven-archetype-quickstart構成に準じます。 - -./README.txt - あなたが今見てるこれ。 - -./CHANGELOG.txt - 変更履歴。 - -./LICENSE.txt - ライセンスに関して。 - -./pom.xml - Maven2用プロジェクト構成定義ファイル。 - -./build.xml - Ant用追加タスク。 - -./src/main/java/ - Javaのソースコード。 - -./src/main/resources/ - プロパティファイルなどの各種リソース。 - -./src/test/java/ - JUnit 4.* 用のユニットテストコード。 - -./src/main/config/checks.xml - Checkstyle用configファイル。 - -./src/main/config/pmdrules.xml - PMD用ルール定義ファイル。 - -./src/main/assembly/descriptor.xml - ソースアーカイブ構成定義ファイル。 - - ---- EOF --- +[UTF-8 Japanese] + + J i n d o l f + README + + Copyright(c) 2008 olyutorskii + + +=== Jindolfとは === + + Jindolfプロジェクトは、CGIゲーム「人狼BBS」を快適にプレイするための +専用クライアントを製作するために発足したオープンソースプロジェクトです。 + +※ このアーカイブは、開発者向けにJindolfのソースコードのみをまとめたものです。 +  ただJindolfをプレイしたいだけの人は、別途JARファイルを入手してください。 +※ 人狼BBSのURLは [ http://homepage2.nifty.com/ninjinia/ ] まで +※ 人狼BBSを主催するninjin氏は、Jindolfの製作に一切関与していません。 +  Jindolfに関する問い合わせををninjin氏へ投げかけないように!約束だよ! + + +=== 実行環境 === + + - JindolfはJava言語(JLS3)で記述されたプログラムです。 + - JindolfはJRE1.5に準拠したJava実行環境で利用できるように作られています。 + 原則として、JRE1.5に準拠した実行系であれば、プラットフォームを選びません。 + - JindolfはGUIを通じて操作するプログラムのため、その実行においては + ビットマップディスプレイとポインティングデバイスとキーボードへの接続を + 必要とします。 + - Jindolfは人狼BBSサーバとHTTP通信を行うため、TCP/IPネットワーク環境を + 必要とします。 + - 人狼BBSは符号化された日本語で遊ばれるため、Jindolfの実行には日本語環境が + 必要です。 + + +=== 依存ライブラリ === + + - Jindolfはビルドおよび実行に際してJinCoreおよびJinParserライブラリを + 必要とします。開発時はMaven等を用いてこれらのライブラリを用意してください。 + + + +=== アーカイブ管理体制 === + + このアーカイブは、UTF-8による開発環境を前提として構成されています。 + このアーカイブの原本となる開発資産は、 + http://hg.sourceforge.jp/view/jindolf/Jindolf + を上位に持つMercurialリポジトリで管理されています。 + + +=== 開発プロジェクト運営元 === + + http://sourceforge.jp/projects/jindolf/devel/ まで。 + + +=== ソフトウェア利用者向けポータルサイト === + + http://jindolf.sourceforge.jp/ まで。 + + +=== ディレクトリ内訳構成 === + +基本的にはMaven2のmaven-archetype-quickstart構成に準じます。 + +./README.txt + あなたが今見てるこれ。 + +./CHANGELOG.txt + 変更履歴。 + +./LICENSE.txt + ライセンスに関して。 + +./pom.xml + Maven2用プロジェクト構成定義ファイル。 + +./build.xml + Ant用追加タスク。 + +./src/main/java/ + Javaのソースコード。 + +./src/main/resources/ + プロパティファイルなどの各種リソース。 + +./src/test/java/ + JUnit 4.* 用のユニットテストコード。 + +./src/main/config/checks.xml + Checkstyle用configファイル。 + +./src/main/config/pmdrules.xml + PMD用ルール定義ファイル。 + +./src/main/assembly/descriptor.xml + ソースアーカイブ構成定義ファイル。 + + +--- EOF --- diff --git a/src/main/java/jp/sourceforge/jindolf/AbstractTextRow.java b/src/main/java/jp/sourceforge/jindolf/AbstractTextRow.java index ef4dc65..fe42f33 100644 --- a/src/main/java/jp/sourceforge/jindolf/AbstractTextRow.java +++ b/src/main/java/jp/sourceforge/jindolf/AbstractTextRow.java @@ -1,157 +1,157 @@ -/* - * 矩形領域テキスト描画抽象クラス - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Rectangle; -import java.awt.font.GlyphVector; -import java.text.CharacterIterator; - -/** - * TextRowの実装を助けるクラス。 - */ -public abstract class AbstractTextRow implements TextRow{ - - /** 描画領域矩形。 */ - protected final Rectangle bounds = new Rectangle(); - /** フォント指定。 */ - protected FontInfo fontInfo; - - private boolean visible = true; - - /** - * コンストラクタ。 - */ - protected AbstractTextRow(){ - this(FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param fontInfo フォント設定 - */ - protected AbstractTextRow(FontInfo fontInfo){ - this.fontInfo = fontInfo; - return; - } - - /** - * {@inheritDoc} - * @param newWidth {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Rectangle setWidth(int newWidth){ - this.bounds.width = newWidth; - recalcBounds(); - return this.bounds; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Rectangle getBounds(){ - return this.bounds; - } - - /** - * {@inheritDoc} - * @param xPos {@inheritDoc} - * @param yPos {@inheritDoc} - */ - @Override - public void setPos(int xPos, int yPos){ - this.bounds.x = xPos; - this.bounds.y = yPos; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getWidth(){ - return this.bounds.width; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getHeight(){ - return this.bounds.height; - } - - /** - * {@inheritDoc} - * @param fontInfo {@inheritDoc} - */ - @Override - public void setFontInfo(FontInfo fontInfo){ - this.fontInfo = fontInfo; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean isVisible(){ - return this.visible; - } - - /** - * {@inheritDoc} - * @param visible {@inheritDoc} - */ - @Override - public void setVisible(boolean visible){ - this.visible = visible; - return; - } - - /** - * 文字列からグリフ集合を生成する。 - * @param iterator 文字列 - * @return グリフ集合 - */ - public GlyphVector createGlyphVector(CharacterIterator iterator){ - return this.fontInfo.createGlyphVector(iterator); - } - - /** - * 文字列からグリフ集合を生成する。 - * @param seq 文字列 - * @return グリフ集合 - */ - public GlyphVector createGlyphVector(CharSequence seq){ - CharacterIterator iterator; - iterator = new SequenceCharacterIterator(seq); - return this.fontInfo.createGlyphVector(iterator); - } - - /** - * 文字列からグリフ集合を生成する。 - * @param seq 文字列 - * @param from 開始位置 - * @param to 終了位置 - * @return グリフ集合 - */ - public GlyphVector createGlyphVector(CharSequence seq, - int from, int to ){ - CharacterIterator iterator; - iterator = new SequenceCharacterIterator(seq, from, to); - return this.fontInfo.createGlyphVector(iterator); - } - -} +/* + * 矩形領域テキスト描画抽象クラス + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Rectangle; +import java.awt.font.GlyphVector; +import java.text.CharacterIterator; + +/** + * TextRowの実装を助けるクラス。 + */ +public abstract class AbstractTextRow implements TextRow{ + + /** 描画領域矩形。 */ + protected final Rectangle bounds = new Rectangle(); + /** フォント指定。 */ + protected FontInfo fontInfo; + + private boolean visible = true; + + /** + * コンストラクタ。 + */ + protected AbstractTextRow(){ + this(FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param fontInfo フォント設定 + */ + protected AbstractTextRow(FontInfo fontInfo){ + this.fontInfo = fontInfo; + return; + } + + /** + * {@inheritDoc} + * @param newWidth {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Rectangle setWidth(int newWidth){ + this.bounds.width = newWidth; + recalcBounds(); + return this.bounds; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Rectangle getBounds(){ + return this.bounds; + } + + /** + * {@inheritDoc} + * @param xPos {@inheritDoc} + * @param yPos {@inheritDoc} + */ + @Override + public void setPos(int xPos, int yPos){ + this.bounds.x = xPos; + this.bounds.y = yPos; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getWidth(){ + return this.bounds.width; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getHeight(){ + return this.bounds.height; + } + + /** + * {@inheritDoc} + * @param fontInfo {@inheritDoc} + */ + @Override + public void setFontInfo(FontInfo fontInfo){ + this.fontInfo = fontInfo; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean isVisible(){ + return this.visible; + } + + /** + * {@inheritDoc} + * @param visible {@inheritDoc} + */ + @Override + public void setVisible(boolean visible){ + this.visible = visible; + return; + } + + /** + * 文字列からグリフ集合を生成する。 + * @param iterator 文字列 + * @return グリフ集合 + */ + public GlyphVector createGlyphVector(CharacterIterator iterator){ + return this.fontInfo.createGlyphVector(iterator); + } + + /** + * 文字列からグリフ集合を生成する。 + * @param seq 文字列 + * @return グリフ集合 + */ + public GlyphVector createGlyphVector(CharSequence seq){ + CharacterIterator iterator; + iterator = new SequenceCharacterIterator(seq); + return this.fontInfo.createGlyphVector(iterator); + } + + /** + * 文字列からグリフ集合を生成する。 + * @param seq 文字列 + * @param from 開始位置 + * @param to 終了位置 + * @return グリフ集合 + */ + public GlyphVector createGlyphVector(CharSequence seq, + int from, int to ){ + CharacterIterator iterator; + iterator = new SequenceCharacterIterator(seq, from, to); + return this.fontInfo.createGlyphVector(iterator); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AccountCookie.java b/src/main/java/jp/sourceforge/jindolf/AccountCookie.java index 720064d..51573de 100644 --- a/src/main/java/jp/sourceforge/jindolf/AccountCookie.java +++ b/src/main/java/jp/sourceforge/jindolf/AccountCookie.java @@ -1,187 +1,187 @@ -/* - * account cookie - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.text.DateFormatSymbols; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.TimeZone; - -/** - * 人狼BBSアカウント管理用のCookie。 - * JRE1.6 HttpCookie の代用品。 - */ -class AccountCookie{ // TODO JRE 1.6対応とともにHttpCookieへ移行予定 - - // 人狼BBSのCookie期限表記例: 「Thu, 26 Jun 2008 06:44:34 GMT」 - private static final String DATE_FORM = "EEE, dd MMM yyyy HH:mm:ss z"; - private static final SimpleDateFormat FORMAT; - - static{ - Calendar calendar = new GregorianCalendar(); - TimeZone zoneGMT = TimeZone.getTimeZone("GMT"); - DateFormatSymbols customSyms = new DateFormatSymbols(); - String[] sweekdays = customSyms.getShortWeekdays(); - sweekdays[Calendar.SUNDAY] = "Sun"; - sweekdays[Calendar.MONDAY] = "Mon"; - sweekdays[Calendar.TUESDAY] = "Tue"; - sweekdays[Calendar.WEDNESDAY] = "Wed"; - sweekdays[Calendar.THURSDAY] = "Thu"; - sweekdays[Calendar.FRIDAY] = "Fri"; - sweekdays[Calendar.SATURDAY] = "Sat"; - customSyms.setShortWeekdays(sweekdays); - String[] months = customSyms.getShortMonths(); - months[Calendar.JANUARY] = "Jan"; - months[Calendar.FEBRUARY] = "Feb"; - months[Calendar.MARCH] = "Mar"; - months[Calendar.APRIL] = "Apr"; - months[Calendar.MAY] = "May"; - months[Calendar.JUNE] = "Jun"; - months[Calendar.JULY] = "Jul"; - months[Calendar.AUGUST] = "Aug"; - months[Calendar.SEPTEMBER] = "Sep"; - months[Calendar.OCTOBER] = "Oct"; - months[Calendar.NOVEMBER] = "Nov"; - months[Calendar.DECEMBER] = "Dec"; - customSyms.setShortMonths(months); - - FORMAT = new SimpleDateFormat(DATE_FORM, Locale.JAPAN); - FORMAT.setCalendar(calendar); - FORMAT.setTimeZone(zoneGMT); - FORMAT.setDateFormatSymbols(customSyms); - FORMAT.setLenient(true); - } - - private final String loginData; - private final URI pathURI; - private final Date expireDate; - - /** - * 認証クッキーの生成。 - * @param loginData 認証データ - * @param path Cookieパス - * @param expireDate expire日付 - * @throws java.lang.NullPointerException 引数がnull - * @throws java.lang.IllegalArgumentException パスが変 - */ - public AccountCookie(String loginData, String path, Date expireDate) - throws NullPointerException, IllegalArgumentException{ - super(); - - if(loginData == null || path == null || expireDate == null){ - throw new NullPointerException(); - } - - this.loginData = loginData; - try{ - this.pathURI = new URI(path); - }catch(URISyntaxException e){ - throw new IllegalArgumentException(path, e); - } - this.expireDate = expireDate; - - return; - } - - /** - * Cookie期限が切れてないか判定する。 - * @return 期限が切れていたらtrue - */ - public boolean hasExpired(){ - long nowMs = System.currentTimeMillis(); - long expireMs = this.expireDate.getTime(); - if(expireMs < nowMs) return true; - return false; - } - - /** - * Cookieパスを返す。 - * @return Cookieパス - */ - public URI getPathURI(){ - return this.pathURI; - } - - /** - * 認証データを返す。 - * @return 認証データ - */ - public String getLoginData(){ - return this.loginData; - } - - /** - * 認証Cookieを抽出する。 - * @param cookieSource HTTPヘッダ 「Cookie=」の値 - * @return 認証Cookie - */ - public static AccountCookie createCookie(String cookieSource){ - String[] cookieParts = cookieSource.split("; "); - if(cookieParts.length <= 0) return null; - - String login = null; - String path = null; - String expires = null; - for(String part : cookieParts){ - String[] nmval = part.split("=", 2); - if(nmval == null) continue; - if(nmval.length != 2) continue; - String name = nmval[0]; - String value = nmval[1]; - - if(name.equals("login")){ - login = value; - }else if(name.equals("path")){ - path = value; - }else if(name.equals("expires")){ - expires = value; - } - } - if(login == null || path == null || expires == null) return null; - - Date date; - try{ - date = FORMAT.parse(expires); - }catch(ParseException e){ - return null; - } - - AccountCookie cookie = new AccountCookie(login, path, date); - - return cookie; - } - - /** - * 認証Cookieを抽出する。 - * @param connection HTTP接続 - * @return 認証Cookie - */ - public static AccountCookie createCookie(HttpURLConnection connection){ - String cookieHeader = connection.getHeaderField("Set-Cookie"); - if(cookieHeader == null) return null; - AccountCookie cookie = createCookie(cookieHeader); - return cookie; - } - - /** - * 認証Cookieの文字列表記。 - * @return String文字列 - */ - @Override - public String toString(){ - return this.loginData; - } - -} +/* + * account cookie + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.DateFormatSymbols; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * 人狼BBSアカウント管理用のCookie。 + * JRE1.6 HttpCookie の代用品。 + */ +class AccountCookie{ // TODO JRE 1.6対応とともにHttpCookieへ移行予定 + + // 人狼BBSのCookie期限表記例: 「Thu, 26 Jun 2008 06:44:34 GMT」 + private static final String DATE_FORM = "EEE, dd MMM yyyy HH:mm:ss z"; + private static final SimpleDateFormat FORMAT; + + static{ + Calendar calendar = new GregorianCalendar(); + TimeZone zoneGMT = TimeZone.getTimeZone("GMT"); + DateFormatSymbols customSyms = new DateFormatSymbols(); + String[] sweekdays = customSyms.getShortWeekdays(); + sweekdays[Calendar.SUNDAY] = "Sun"; + sweekdays[Calendar.MONDAY] = "Mon"; + sweekdays[Calendar.TUESDAY] = "Tue"; + sweekdays[Calendar.WEDNESDAY] = "Wed"; + sweekdays[Calendar.THURSDAY] = "Thu"; + sweekdays[Calendar.FRIDAY] = "Fri"; + sweekdays[Calendar.SATURDAY] = "Sat"; + customSyms.setShortWeekdays(sweekdays); + String[] months = customSyms.getShortMonths(); + months[Calendar.JANUARY] = "Jan"; + months[Calendar.FEBRUARY] = "Feb"; + months[Calendar.MARCH] = "Mar"; + months[Calendar.APRIL] = "Apr"; + months[Calendar.MAY] = "May"; + months[Calendar.JUNE] = "Jun"; + months[Calendar.JULY] = "Jul"; + months[Calendar.AUGUST] = "Aug"; + months[Calendar.SEPTEMBER] = "Sep"; + months[Calendar.OCTOBER] = "Oct"; + months[Calendar.NOVEMBER] = "Nov"; + months[Calendar.DECEMBER] = "Dec"; + customSyms.setShortMonths(months); + + FORMAT = new SimpleDateFormat(DATE_FORM, Locale.JAPAN); + FORMAT.setCalendar(calendar); + FORMAT.setTimeZone(zoneGMT); + FORMAT.setDateFormatSymbols(customSyms); + FORMAT.setLenient(true); + } + + private final String loginData; + private final URI pathURI; + private final Date expireDate; + + /** + * 認証クッキーの生成。 + * @param loginData 認証データ + * @param path Cookieパス + * @param expireDate expire日付 + * @throws java.lang.NullPointerException 引数がnull + * @throws java.lang.IllegalArgumentException パスが変 + */ + public AccountCookie(String loginData, String path, Date expireDate) + throws NullPointerException, IllegalArgumentException{ + super(); + + if(loginData == null || path == null || expireDate == null){ + throw new NullPointerException(); + } + + this.loginData = loginData; + try{ + this.pathURI = new URI(path); + }catch(URISyntaxException e){ + throw new IllegalArgumentException(path, e); + } + this.expireDate = expireDate; + + return; + } + + /** + * Cookie期限が切れてないか判定する。 + * @return 期限が切れていたらtrue + */ + public boolean hasExpired(){ + long nowMs = System.currentTimeMillis(); + long expireMs = this.expireDate.getTime(); + if(expireMs < nowMs) return true; + return false; + } + + /** + * Cookieパスを返す。 + * @return Cookieパス + */ + public URI getPathURI(){ + return this.pathURI; + } + + /** + * 認証データを返す。 + * @return 認証データ + */ + public String getLoginData(){ + return this.loginData; + } + + /** + * 認証Cookieを抽出する。 + * @param cookieSource HTTPヘッダ 「Cookie=」の値 + * @return 認証Cookie + */ + public static AccountCookie createCookie(String cookieSource){ + String[] cookieParts = cookieSource.split("; "); + if(cookieParts.length <= 0) return null; + + String login = null; + String path = null; + String expires = null; + for(String part : cookieParts){ + String[] nmval = part.split("=", 2); + if(nmval == null) continue; + if(nmval.length != 2) continue; + String name = nmval[0]; + String value = nmval[1]; + + if(name.equals("login")){ + login = value; + }else if(name.equals("path")){ + path = value; + }else if(name.equals("expires")){ + expires = value; + } + } + if(login == null || path == null || expires == null) return null; + + Date date; + try{ + date = FORMAT.parse(expires); + }catch(ParseException e){ + return null; + } + + AccountCookie cookie = new AccountCookie(login, path, date); + + return cookie; + } + + /** + * 認証Cookieを抽出する。 + * @param connection HTTP接続 + * @return 認証Cookie + */ + public static AccountCookie createCookie(HttpURLConnection connection){ + String cookieHeader = connection.getHeaderField("Set-Cookie"); + if(cookieHeader == null) return null; + AccountCookie cookie = createCookie(cookieHeader); + return cookie; + } + + /** + * 認証Cookieの文字列表記。 + * @return String文字列 + */ + @Override + public String toString(){ + return this.loginData; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AccountPanel.java b/src/main/java/jp/sourceforge/jindolf/AccountPanel.java index 3f932eb..750c9fa 100644 --- a/src/main/java/jp/sourceforge/jindolf/AccountPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/AccountPanel.java @@ -1,476 +1,476 @@ -/* - * Account panel - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JSeparator; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.border.Border; -import jp.sourceforge.jindolf.corelib.LandState; - -/** - * ログインパネル。 - */ -@SuppressWarnings("serial") -public class AccountPanel - extends JDialog - implements ActionListener, ItemListener{ - - private static final String FRAMETITLE = - "アカウント管理 - " + Jindolf.TITLE; - - private final Map landUserIDMap = - new HashMap(); - private final Map landPasswordMap = - new HashMap(); - - private final JComboBox landBox = new JComboBox(); - private final JTextField idField = new JTextField(15); - private final JPasswordField pwField = new JPasswordField(15); - private final JButton loginButton = new JButton("ログイン"); - private final JButton logoutButton = new JButton("ログアウト"); - private final JButton closeButton = new JButton("閉じる"); - private final JTextArea status = new JTextArea(); - - /** - * アカウントパネルを生成。 - * @param owner フレームオーナー - * @param landsModel 国モデル - * @throws java.lang.NullPointerException 引数がnull - */ - public AccountPanel(Frame owner, LandsModel landsModel) - throws NullPointerException{ - super(owner, FRAMETITLE, true); - - if(landsModel == null) throw new NullPointerException(); - for(Land land : landsModel.getLandList()){ - String userID = ""; - char[] password = {}; - this.landUserIDMap.put(land, userID); - this.landPasswordMap.put(land, password); - this.landBox.addItem(land); - } - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - this.landBox.setToolTipText("アカウント管理する国を選ぶ"); - this.idField.setToolTipText("IDを入力してください"); - this.pwField.setToolTipText("パスワードを入力してください"); - - Monodizer.monodize(this.idField); - Monodizer.monodize(this.pwField); - - this.idField.setMargin(new Insets(1, 4, 1, 4)); - this.pwField.setMargin(new Insets(1, 4, 1, 4)); - - this.idField.setComponentPopupMenu(new TextPopup()); - - this.landBox.setEditable(false); - this.landBox.addItemListener(this); - - this.status.setEditable(false); - this.status.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - this.status.setRows(2); - this.status.setLineWrap(true); - - this.loginButton.addActionListener(this); - this.logoutButton.addActionListener(this); - this.closeButton.addActionListener(this); - - getRootPane().setDefaultButton(this.loginButton); - - Container content = getContentPane(); - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - content.setLayout(layout); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.weightx = 1.0; - constraints.insets = new Insets(5, 5, 5, 5); - - JComponent accountPanel = createCredential(); - JComponent buttonPanel = createButtonPanel(); - - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(accountPanel, constraints); - - Border border = BorderFactory.createTitledBorder("ログインステータス"); - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(this.status, BorderLayout.CENTER); - panel.setBorder(border); - - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - content.add(panel, constraints); - - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - content.add(buttonPanel, constraints); - - preSelectActiveLand(); - - updateGUI(); - - return; - } - - /** - * 認証パネルを生成する。 - * @return 認証パネル - */ - private JComponent createCredential(){ - JPanel credential = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - credential.setLayout(layout); - - constraints.insets = new Insets(5, 5, 5, 5); - constraints.fill = GridBagConstraints.NONE; - - constraints.anchor = GridBagConstraints.EAST; - credential.add(new JLabel("国名 :"), constraints); - constraints.anchor = GridBagConstraints.WEST; - credential.add(this.landBox, constraints); - - constraints.gridy = 1; - constraints.anchor = GridBagConstraints.EAST; - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.NONE; - credential.add(new JLabel("ID :"), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - credential.add(this.idField, constraints); - - constraints.gridy = 2; - constraints.anchor = GridBagConstraints.EAST; - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.NONE; - credential.add(new JLabel("パスワード :"), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - credential.add(this.pwField, constraints); - - return credential; - } - - /** - * ボタンパネルの作成。 - * @return ボタンパネル - */ - private JComponent createButtonPanel(){ - JPanel buttonPanel = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - buttonPanel.setLayout(layout); - - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.WEST; - constraints.weightx = 0.0; - constraints.weighty = 0.0; - - buttonPanel.add(this.loginButton, constraints); - - constraints.insets = new Insets(0, 5, 0, 0); - buttonPanel.add(this.logoutButton, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.weightx = 1.0; - constraints.insets = new Insets(0, 15, 0, 0); - buttonPanel.add(this.closeButton, constraints); - - return buttonPanel; - } - - /** - * 現在コンボボックスで選択中の国を返す。 - * @return 現在選択中のLand - */ - private Land getSelectedLand(){ - Land land = (Land)( this.landBox.getSelectedItem() ); - return land; - } - - /** - * ACTIVEな最初の国がコンボボックスで既に選択されている状態にする。 - */ - private void preSelectActiveLand(){ - for(int index = 0; index < this.landBox.getItemCount(); index++){ - Object item = this.landBox.getItemAt(index); - Land land = (Land) item; - LandState state = land.getLandDef().getLandState(); - if(state == LandState.ACTIVE){ - this.landBox.setSelectedItem(land); - return; - } - } - return; - } - - /** - * 指定された国のユーザIDを返す。 - * @param land 国 - * @return ユーザID - */ - private String getUserID(Land land){ - return this.landUserIDMap.get(land); - } - - /** - * 指定された国のパスワードを返す。 - * @param land 国 - * @return パスワード - */ - private char[] getPassword(Land land){ - return this.landPasswordMap.get(land); - } - - /** - * ネットワークエラーを通知するモーダルダイアログを表示する。 - * OKボタンを押すまでこのメソッドは戻ってこない。 - * @param e ネットワークエラー - */ - protected void showNetworkError(IOException e){ - Jindolf.logger().warn( - "アカウント処理中にネットワークのトラブルが発生しました", e); - - Land land = getSelectedLand(); - ServerAccess server = land.getServerAccess(); - String message = - land.getLandDef().getLandName() - +"を運営するサーバとの間の通信で" - +"何らかのトラブルが発生しました。\n" - +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n" - +"Webブラウザでも遊べないか確認してみてね!\n"; - - JOptionPane pane = new JOptionPane(message, - JOptionPane.WARNING_MESSAGE, - JOptionPane.DEFAULT_OPTION ); - - JDialog dialog = pane.createDialog(this, - "通信異常発生 - " + Jindolf.TITLE); - - dialog.pack(); - dialog.setVisible(true); - dialog.dispose(); - - return; - } - - /** - * アカウントエラーを通知するモーダルダイアログを表示する。 - * OKボタンを押すまでこのメソッドは戻ってこない。 - */ - protected void showIllegalAccountDialog(){ - Land land = getSelectedLand(); - String message = - land.getLandDef().getLandName() - +"へのログインに失敗しました。\n" - +"ユーザ名とパスワードは本当に正しいかな?\n" - +"あなたは本当に [ " + getUserID(land) + " ] さんかな?\n" - +"WebブラウザによるID登録手続きは本当に完了してるかな?\n" - +"Webブラウザでもログインできないか試してみて!\n" - +"…ユーザ名やパスワードにある種の特殊文字を使っている人は" - +"問題があるかも。"; - - JOptionPane pane = new JOptionPane(message, - JOptionPane.WARNING_MESSAGE, - JOptionPane.DEFAULT_OPTION ); - - JDialog dialog = - pane.createDialog(this, "ログイン認証失敗 - " + Jindolf.TITLE); - - dialog.pack(); - dialog.setVisible(true); - dialog.dispose(); - - return; - } - - /** - * 入力されたアカウント情報を基に現在選択中の国へログインする。 - * @return ログインに成功すればtrueを返す。 - */ - protected boolean login(){ - Land land = getSelectedLand(); - ServerAccess server = land.getServerAccess(); - - String id = this.idField.getText(); - char[] password = this.pwField.getPassword(); - this.landUserIDMap.put(land, id); - this.landPasswordMap.put(land, password); - - boolean result = false; - try{ - result = server.login(id, password); - }catch(IOException e){ - showNetworkError(e); - return false; - } - - if( ! result ){ - showIllegalAccountDialog(); - } - - return result; - } - - /** - * 現在選択中の国からログアウトする。 - */ - protected void logout(){ - try{ - logoutInternal(); - }catch(IOException e){ - showNetworkError(e); - } - return; - } - - /** - * 現在選択中の国からログアウトする。 - * @throws java.io.IOException ネットワークエラー - */ - protected void logoutInternal() throws IOException{ - Land land = getSelectedLand(); - ServerAccess server = land.getServerAccess(); - server.logout(); - return; - } - - /** - * 現在選択中の国のログイン状態に合わせてGUIを更新する。 - */ - private void updateGUI(){ - Land land = getSelectedLand(); - LandState state = land.getLandDef().getLandState(); - ServerAccess server = land.getServerAccess(); - boolean hasLoggedIn = server.hasLoggedIn(); - - if(state != LandState.ACTIVE){ - this.status.setText( - "この国は既に募集を停止しました。\n" - +"ログインは無意味です" ); - this.idField.setEnabled(false); - this.pwField.setEnabled(false); - this.loginButton.setEnabled(false); - this.logoutButton.setEnabled(false); - }else if(hasLoggedIn){ - this.status.setText("ユーザ [ " + getUserID(land) + " ] として\n" - +"現在ログイン中です"); - this.idField.setEnabled(false); - this.pwField.setEnabled(false); - this.loginButton.setEnabled(false); - this.logoutButton.setEnabled(true); - }else{ - this.status.setText("現在ログインしていません"); - this.idField.setEnabled(true); - this.pwField.setEnabled(true); - this.loginButton.setEnabled(true); - this.logoutButton.setEnabled(false); - } - - return; - } - - /** - * {@inheritDoc} - * ボタン操作のリスナ。 - * @param event イベント {@inheritDoc} - */ - // TODO Return キー押下によるログインもサポートしたい - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - - if(source == this.closeButton){ - setVisible(false); - dispose(); - return; - } - - if(source == this.loginButton){ - login(); - }else if(source == this.logoutButton){ - logout(); - } - - updateGUI(); - - return; - } - - /** - * {@inheritDoc} - * コンボボックス操作のリスナ。 - * @param event イベント {@inheritDoc} - */ - @Override - public void itemStateChanged(ItemEvent event){ - Object source = event.getSource(); - if(source != this.landBox) return; - - Land land = (Land) event.getItem(); - String id; - char[] password; - - switch(event.getStateChange()){ - case ItemEvent.SELECTED: - id = getUserID(land); - password = getPassword(land); - this.idField.setText(id); - this.pwField.setText(new String(password)); - updateGUI(); - break; - case ItemEvent.DESELECTED: - id = this.idField.getText(); - password = this.pwField.getPassword(); - this.landUserIDMap.put(land, id); - this.landPasswordMap.put(land, password); - break; - default: - assert false; - return; - } - - return; - } - - // TODO IDかパスワードが空の場合はログインボタンを無効にしたい -} +/* + * Account panel + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JSeparator; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.border.Border; +import jp.sourceforge.jindolf.corelib.LandState; + +/** + * ログインパネル。 + */ +@SuppressWarnings("serial") +public class AccountPanel + extends JDialog + implements ActionListener, ItemListener{ + + private static final String FRAMETITLE = + "アカウント管理 - " + Jindolf.TITLE; + + private final Map landUserIDMap = + new HashMap(); + private final Map landPasswordMap = + new HashMap(); + + private final JComboBox landBox = new JComboBox(); + private final JTextField idField = new JTextField(15); + private final JPasswordField pwField = new JPasswordField(15); + private final JButton loginButton = new JButton("ログイン"); + private final JButton logoutButton = new JButton("ログアウト"); + private final JButton closeButton = new JButton("閉じる"); + private final JTextArea status = new JTextArea(); + + /** + * アカウントパネルを生成。 + * @param owner フレームオーナー + * @param landsModel 国モデル + * @throws java.lang.NullPointerException 引数がnull + */ + public AccountPanel(Frame owner, LandsModel landsModel) + throws NullPointerException{ + super(owner, FRAMETITLE, true); + + if(landsModel == null) throw new NullPointerException(); + for(Land land : landsModel.getLandList()){ + String userID = ""; + char[] password = {}; + this.landUserIDMap.put(land, userID); + this.landPasswordMap.put(land, password); + this.landBox.addItem(land); + } + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + this.landBox.setToolTipText("アカウント管理する国を選ぶ"); + this.idField.setToolTipText("IDを入力してください"); + this.pwField.setToolTipText("パスワードを入力してください"); + + Monodizer.monodize(this.idField); + Monodizer.monodize(this.pwField); + + this.idField.setMargin(new Insets(1, 4, 1, 4)); + this.pwField.setMargin(new Insets(1, 4, 1, 4)); + + this.idField.setComponentPopupMenu(new TextPopup()); + + this.landBox.setEditable(false); + this.landBox.addItemListener(this); + + this.status.setEditable(false); + this.status.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + this.status.setRows(2); + this.status.setLineWrap(true); + + this.loginButton.addActionListener(this); + this.logoutButton.addActionListener(this); + this.closeButton.addActionListener(this); + + getRootPane().setDefaultButton(this.loginButton); + + Container content = getContentPane(); + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + content.setLayout(layout); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.weightx = 1.0; + constraints.insets = new Insets(5, 5, 5, 5); + + JComponent accountPanel = createCredential(); + JComponent buttonPanel = createButtonPanel(); + + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(accountPanel, constraints); + + Border border = BorderFactory.createTitledBorder("ログインステータス"); + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(this.status, BorderLayout.CENTER); + panel.setBorder(border); + + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + content.add(panel, constraints); + + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + content.add(buttonPanel, constraints); + + preSelectActiveLand(); + + updateGUI(); + + return; + } + + /** + * 認証パネルを生成する。 + * @return 認証パネル + */ + private JComponent createCredential(){ + JPanel credential = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + credential.setLayout(layout); + + constraints.insets = new Insets(5, 5, 5, 5); + constraints.fill = GridBagConstraints.NONE; + + constraints.anchor = GridBagConstraints.EAST; + credential.add(new JLabel("国名 :"), constraints); + constraints.anchor = GridBagConstraints.WEST; + credential.add(this.landBox, constraints); + + constraints.gridy = 1; + constraints.anchor = GridBagConstraints.EAST; + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + credential.add(new JLabel("ID :"), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + credential.add(this.idField, constraints); + + constraints.gridy = 2; + constraints.anchor = GridBagConstraints.EAST; + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + credential.add(new JLabel("パスワード :"), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + credential.add(this.pwField, constraints); + + return credential; + } + + /** + * ボタンパネルの作成。 + * @return ボタンパネル + */ + private JComponent createButtonPanel(){ + JPanel buttonPanel = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + buttonPanel.setLayout(layout); + + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 0.0; + constraints.weighty = 0.0; + + buttonPanel.add(this.loginButton, constraints); + + constraints.insets = new Insets(0, 5, 0, 0); + buttonPanel.add(this.logoutButton, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.weightx = 1.0; + constraints.insets = new Insets(0, 15, 0, 0); + buttonPanel.add(this.closeButton, constraints); + + return buttonPanel; + } + + /** + * 現在コンボボックスで選択中の国を返す。 + * @return 現在選択中のLand + */ + private Land getSelectedLand(){ + Land land = (Land)( this.landBox.getSelectedItem() ); + return land; + } + + /** + * ACTIVEな最初の国がコンボボックスで既に選択されている状態にする。 + */ + private void preSelectActiveLand(){ + for(int index = 0; index < this.landBox.getItemCount(); index++){ + Object item = this.landBox.getItemAt(index); + Land land = (Land) item; + LandState state = land.getLandDef().getLandState(); + if(state == LandState.ACTIVE){ + this.landBox.setSelectedItem(land); + return; + } + } + return; + } + + /** + * 指定された国のユーザIDを返す。 + * @param land 国 + * @return ユーザID + */ + private String getUserID(Land land){ + return this.landUserIDMap.get(land); + } + + /** + * 指定された国のパスワードを返す。 + * @param land 国 + * @return パスワード + */ + private char[] getPassword(Land land){ + return this.landPasswordMap.get(land); + } + + /** + * ネットワークエラーを通知するモーダルダイアログを表示する。 + * OKボタンを押すまでこのメソッドは戻ってこない。 + * @param e ネットワークエラー + */ + protected void showNetworkError(IOException e){ + Jindolf.logger().warn( + "アカウント処理中にネットワークのトラブルが発生しました", e); + + Land land = getSelectedLand(); + ServerAccess server = land.getServerAccess(); + String message = + land.getLandDef().getLandName() + +"を運営するサーバとの間の通信で" + +"何らかのトラブルが発生しました。\n" + +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n" + +"Webブラウザでも遊べないか確認してみてね!\n"; + + JOptionPane pane = new JOptionPane(message, + JOptionPane.WARNING_MESSAGE, + JOptionPane.DEFAULT_OPTION ); + + JDialog dialog = pane.createDialog(this, + "通信異常発生 - " + Jindolf.TITLE); + + dialog.pack(); + dialog.setVisible(true); + dialog.dispose(); + + return; + } + + /** + * アカウントエラーを通知するモーダルダイアログを表示する。 + * OKボタンを押すまでこのメソッドは戻ってこない。 + */ + protected void showIllegalAccountDialog(){ + Land land = getSelectedLand(); + String message = + land.getLandDef().getLandName() + +"へのログインに失敗しました。\n" + +"ユーザ名とパスワードは本当に正しいかな?\n" + +"あなたは本当に [ " + getUserID(land) + " ] さんかな?\n" + +"WebブラウザによるID登録手続きは本当に完了してるかな?\n" + +"Webブラウザでもログインできないか試してみて!\n" + +"…ユーザ名やパスワードにある種の特殊文字を使っている人は" + +"問題があるかも。"; + + JOptionPane pane = new JOptionPane(message, + JOptionPane.WARNING_MESSAGE, + JOptionPane.DEFAULT_OPTION ); + + JDialog dialog = + pane.createDialog(this, "ログイン認証失敗 - " + Jindolf.TITLE); + + dialog.pack(); + dialog.setVisible(true); + dialog.dispose(); + + return; + } + + /** + * 入力されたアカウント情報を基に現在選択中の国へログインする。 + * @return ログインに成功すればtrueを返す。 + */ + protected boolean login(){ + Land land = getSelectedLand(); + ServerAccess server = land.getServerAccess(); + + String id = this.idField.getText(); + char[] password = this.pwField.getPassword(); + this.landUserIDMap.put(land, id); + this.landPasswordMap.put(land, password); + + boolean result = false; + try{ + result = server.login(id, password); + }catch(IOException e){ + showNetworkError(e); + return false; + } + + if( ! result ){ + showIllegalAccountDialog(); + } + + return result; + } + + /** + * 現在選択中の国からログアウトする。 + */ + protected void logout(){ + try{ + logoutInternal(); + }catch(IOException e){ + showNetworkError(e); + } + return; + } + + /** + * 現在選択中の国からログアウトする。 + * @throws java.io.IOException ネットワークエラー + */ + protected void logoutInternal() throws IOException{ + Land land = getSelectedLand(); + ServerAccess server = land.getServerAccess(); + server.logout(); + return; + } + + /** + * 現在選択中の国のログイン状態に合わせてGUIを更新する。 + */ + private void updateGUI(){ + Land land = getSelectedLand(); + LandState state = land.getLandDef().getLandState(); + ServerAccess server = land.getServerAccess(); + boolean hasLoggedIn = server.hasLoggedIn(); + + if(state != LandState.ACTIVE){ + this.status.setText( + "この国は既に募集を停止しました。\n" + +"ログインは無意味です" ); + this.idField.setEnabled(false); + this.pwField.setEnabled(false); + this.loginButton.setEnabled(false); + this.logoutButton.setEnabled(false); + }else if(hasLoggedIn){ + this.status.setText("ユーザ [ " + getUserID(land) + " ] として\n" + +"現在ログイン中です"); + this.idField.setEnabled(false); + this.pwField.setEnabled(false); + this.loginButton.setEnabled(false); + this.logoutButton.setEnabled(true); + }else{ + this.status.setText("現在ログインしていません"); + this.idField.setEnabled(true); + this.pwField.setEnabled(true); + this.loginButton.setEnabled(true); + this.logoutButton.setEnabled(false); + } + + return; + } + + /** + * {@inheritDoc} + * ボタン操作のリスナ。 + * @param event イベント {@inheritDoc} + */ + // TODO Return キー押下によるログインもサポートしたい + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + + if(source == this.closeButton){ + setVisible(false); + dispose(); + return; + } + + if(source == this.loginButton){ + login(); + }else if(source == this.logoutButton){ + logout(); + } + + updateGUI(); + + return; + } + + /** + * {@inheritDoc} + * コンボボックス操作のリスナ。 + * @param event イベント {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent event){ + Object source = event.getSource(); + if(source != this.landBox) return; + + Land land = (Land) event.getItem(); + String id; + char[] password; + + switch(event.getStateChange()){ + case ItemEvent.SELECTED: + id = getUserID(land); + password = getPassword(land); + this.idField.setText(id); + this.pwField.setText(new String(password)); + updateGUI(); + break; + case ItemEvent.DESELECTED: + id = this.idField.getText(); + password = this.pwField.getPassword(); + this.landUserIDMap.put(land, id); + this.landPasswordMap.put(land, password); + break; + default: + assert false; + return; + } + + return; + } + + // TODO IDかパスワードが空の場合はログインボタンを無効にしたい +} diff --git a/src/main/java/jp/sourceforge/jindolf/ActionManager.java b/src/main/java/jp/sourceforge/jindolf/ActionManager.java index 54450d8..63b5b0e 100644 --- a/src/main/java/jp/sourceforge/jindolf/ActionManager.java +++ b/src/main/java/jp/sourceforge/jindolf/ActionManager.java @@ -1,528 +1,528 @@ -/* - * action manager - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Insets; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.swing.AbstractButton; -import javax.swing.ButtonGroup; -import javax.swing.ButtonModel; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.JToolBar; -import javax.swing.KeyStroke; -import javax.swing.LookAndFeel; -import javax.swing.UIManager; - -/** - * メニュー、ボタン、その他各種Actionを伴うイベントを生成する - * コンポーネントの一括管理。 - */ -public class ActionManager{ - - /** アクション{@value}。 */ - public static final String CMD_ACCOUNT = "ACCOUNT"; - /** アクション{@value}。 */ - public static final String CMD_EXIT = "EXIT"; - /** アクション{@value}。 */ - public static final String CMD_COPY = "COPY"; - /** アクション{@value}。 */ - public static final String CMD_SHOWFIND = "SHOWFIND"; - /** アクション{@value}。 */ - public static final String CMD_SEARCHNEXT = "SEARCHNEXT"; - /** アクション{@value}。 */ - public static final String CMD_SEARCHPREV = "SEARCHPREV"; - /** アクション{@value}。 */ - public static final String CMD_ALLPERIOD = "ALLPERIOD"; - /** アクション{@value}。 */ - public static final String CMD_SHOWDIGEST = "DIGEST"; - /** アクション{@value}。 */ - public static final String CMD_WEBVILL = "WEBVILL"; - /** アクション{@value}。 */ - public static final String CMD_WEBCAST = "WEBCAST"; - /** アクション{@value}。 */ - public static final String CMD_WEBWIKI = "WEBWIKI"; - /** アクション{@value}。 */ - public static final String CMD_RELOAD = "RELOAD"; - /** アクション{@value}。 */ - public static final String CMD_DAYSUMMARY = "DAYSUMMARY"; - /** アクション{@value}。 */ - public static final String CMD_DAYEXPCSV = "DAYEXPCSV"; - /** アクション{@value}。 */ - public static final String CMD_WEBDAY = "WEBDAY"; - /** アクション{@value}。 */ - public static final String CMD_OPTION = "OPTION"; - /** アクション{@value}。 */ - public static final String CMD_LANDF = "LANDF"; - /** アクション{@value}。 */ - public static final String CMD_SHOWFILT = "SHOWFILT"; - /** アクション{@value}。 */ - public static final String CMD_SHOWEDIT = "SHOWEDIT"; - /** アクション{@value}。 */ - public static final String CMD_SHOWLOG = "SHOWLOG"; - /** アクション{@value}。 */ - public static final String CMD_HELPDOC = "HELPDOC"; - /** アクション{@value}。 */ - public static final String CMD_SHOWPORTAL = "SHOWPORTAL"; - /** アクション{@value}。 */ - public static final String CMD_ABOUT = "ABOUT"; - - /** アクション{@value}。 */ - public static final String CMD_COPYTALK = "COPYTALK"; - /** アクション{@value}。 */ - public static final String CMD_JUMPANCHOR = "JUMPANCHOR"; - /** アクション{@value}。 */ - public static final String CMD_WEBTALK = "WEBTALK"; - /** アクション{@value}。 */ - public static final String CMD_SWITCHORDER = "SWITCHORDER"; - /** アクション{@value}。 */ - public static final String CMD_VILLAGELIST = "VILLAGELIST"; - /** アクション{@value}。 */ - public static final String CMD_FONTSIZESEL = "FONTSIZESEL"; - - /** WWWアイコン。 */ - public static final Icon ICON_WWW = GUIUtils.getWWWIcon(); - /** 検索アイコン。 */ - public static final Icon ICON_FIND; - /** 前検索アイコン。 */ - public static final Icon ICON_SEARCH_PREV; - /** 次検索アイコン。 */ - public static final Icon ICON_SEARCH_NEXT; - /** リロードアイコン。 */ - public static final Icon ICON_RELOAD; - /** フィルタアイコン。 */ - public static final Icon ICON_FILTER; - /** 発言エディタアイコン。 */ - public static final Icon ICON_EDITOR; - - private static final KeyStroke KEY_F1 = KeyStroke.getKeyStroke("F1"); - private static final KeyStroke KEY_F3 = KeyStroke.getKeyStroke("F3"); - private static final KeyStroke KEY_SHIFT_F3 = - KeyStroke.getKeyStroke("shift F3"); - private static final KeyStroke KEY_F5 = KeyStroke.getKeyStroke("F5"); - private static final KeyStroke KEY_CTRL_F = - KeyStroke.getKeyStroke("ctrl F"); - - static{ - URL iconurl; - - iconurl = Jindolf.getResource("resources/image/find.png"); - ICON_FIND = new ImageIcon(iconurl); - - iconurl = Jindolf.getResource("resources/image/findprev.png"); - ICON_SEARCH_PREV = new ImageIcon(iconurl); - - iconurl = Jindolf.getResource("resources/image/findnext.png"); - ICON_SEARCH_NEXT = new ImageIcon(iconurl); - - iconurl = Jindolf.getResource("resources/image/reload.png"); - ICON_RELOAD = new ImageIcon(iconurl); - - iconurl = Jindolf.getResource("resources/image/filter.png"); - ICON_FILTER = new ImageIcon(iconurl); - - iconurl = Jindolf.getResource("resources/image/editor.png"); - ICON_EDITOR = new ImageIcon(iconurl); - } - - private final Set actionItems = - new HashSet(); - private final Map namedMenuItems = - new HashMap(); - private final Map namedToolButtons = - new HashMap(); - - private final JMenuBar menuBar; - - private final JMenu menuFile; - private final JMenu menuEdit; - private final JMenu menuVillage; - private final JMenu menuDay; - private final JMenu menuPreference; - private final JMenu menuTool; - private final JMenu menuHelp; - - private final JMenu menuLook; - private final ButtonGroup landfGroup = new ButtonGroup(); - private final Map landfMap = - new HashMap(); - - private final JToolBar browseToolBar; - - /** - * コンストラクタ。 - */ - public ActionManager(){ - this.menuFile = buildMenu("ファイル", KeyEvent.VK_F); - this.menuEdit = buildMenu("編集", KeyEvent.VK_E); - this.menuVillage = buildMenu("村", KeyEvent.VK_V); - this.menuDay = buildMenu("日", KeyEvent.VK_D); - this.menuPreference = buildMenu("設定", KeyEvent.VK_P); - this.menuTool = buildMenu("ツール", KeyEvent.VK_T); - this.menuHelp = buildMenu("ヘルプ", KeyEvent.VK_H); - - this.menuLook = buildLookAndFeelMenu("ルック&フィール", KeyEvent.VK_L); - - buildMenuItem(CMD_ACCOUNT, "アカウント管理", KeyEvent.VK_M); - buildMenuItem(CMD_EXIT, "終了", KeyEvent.VK_X); - buildMenuItem(CMD_COPY, "選択範囲をコピー", KeyEvent.VK_C); - buildMenuItem(CMD_SHOWFIND, "検索...", KeyEvent.VK_F); - buildMenuItem(CMD_SEARCHNEXT, "次候補", KeyEvent.VK_N); - buildMenuItem(CMD_SEARCHPREV, "前候補", KeyEvent.VK_P); - buildMenuItem(CMD_ALLPERIOD, "全日程の一括読み込み", KeyEvent.VK_R); - buildMenuItem(CMD_SHOWDIGEST, "村のダイジェストを表示...", - KeyEvent.VK_D); - buildMenuItem(CMD_WEBVILL, "この村をブラウザで表示...", KeyEvent.VK_N); - buildMenuItem(CMD_WEBWIKI, - "まとめサイトの村ページを表示...", KeyEvent.VK_M); - buildMenuItem(CMD_WEBCAST, "キャスト紹介表ジェネレータ...", - KeyEvent.VK_H); - buildMenuItem(CMD_RELOAD, "この日を強制リロード", KeyEvent.VK_R); - buildMenuItem(CMD_DAYSUMMARY, "この日の発言を集計...", KeyEvent.VK_D); - buildMenuItem(CMD_DAYEXPCSV, "CSVへエクスポート...", KeyEvent.VK_C); - buildMenuItem(CMD_WEBDAY, "この日をブラウザで表示...", KeyEvent.VK_B); - buildMenuItem(CMD_OPTION, "オプション...", KeyEvent.VK_O); - buildMenuItem(CMD_SHOWFILT, "発言フィルタ", KeyEvent.VK_F); - buildMenuItem(CMD_SHOWEDIT, "発言エディタ", KeyEvent.VK_E); - buildMenuItem(CMD_SHOWLOG, "ログ表示", KeyEvent.VK_S); - buildMenuItem(CMD_HELPDOC, "ヘルプ表示", KeyEvent.VK_H); - buildMenuItem(CMD_SHOWPORTAL, "ポータルサイト...", KeyEvent.VK_P); - buildMenuItem(CMD_ABOUT, Jindolf.TITLE + "について...", KeyEvent.VK_A); - - buildToolButton(CMD_RELOAD, "選択中の日を強制リロード", ICON_RELOAD); - buildToolButton(CMD_SHOWFIND, "検索", ICON_FIND); - buildToolButton(CMD_SEARCHPREV, "↑前候補", ICON_SEARCH_PREV); - buildToolButton(CMD_SEARCHNEXT, "↓次候補", ICON_SEARCH_NEXT); - buildToolButton(CMD_SHOWFILT, "発言フィルタ", ICON_FILTER); - buildToolButton(CMD_SHOWEDIT, "発言エディタ", ICON_EDITOR); - - getMenuItem(CMD_SHOWPORTAL).setIcon(ICON_WWW); - getMenuItem(CMD_WEBVILL) .setIcon(ICON_WWW); - getMenuItem(CMD_WEBWIKI) .setIcon(ICON_WWW); - getMenuItem(CMD_WEBCAST) .setIcon(ICON_WWW); - getMenuItem(CMD_WEBDAY) .setIcon(ICON_WWW); - getMenuItem(CMD_SHOWFIND) .setIcon(ICON_FIND); - getMenuItem(CMD_SEARCHPREV).setIcon(ICON_SEARCH_PREV); - getMenuItem(CMD_SEARCHNEXT).setIcon(ICON_SEARCH_NEXT); - getMenuItem(CMD_SHOWFILT) .setIcon(ICON_FILTER); - getMenuItem(CMD_SHOWEDIT) .setIcon(ICON_EDITOR); - - registKeyAccelerator(); - - this.menuBar = buildMenuBar(); - this.browseToolBar = buildBrowseToolBar(); - - appearVillageImpl(false); - appearPeriodImpl(false); - - return; - } - - /** - * メニューを生成する。 - * @param label メニューラベル - * @param nemonic ニモニックキー - * @return メニュー - */ - private JMenu buildMenu(String label, int nemonic){ - JMenu result = new JMenu(); - - String keyText = label + "(" + KeyEvent.getKeyText(nemonic) + ")"; - - result.setText(keyText); - result.setMnemonic(nemonic); - - return result; - } - - /** - * メニューアイテムを生成する。 - * @param command アクションコマンド名 - * @param label メニューラベル - * @param nemonic ニモニックキー - * @return メニューアイテム - */ - private JMenuItem buildMenuItem(String command, - String label, - int nemonic ){ - JMenuItem result = new JMenuItem(); - - String keyText = label + "(" + KeyEvent.getKeyText(nemonic) + ")"; - - result.setActionCommand(command); - result.setText(keyText); - result.setMnemonic(nemonic); - - this.actionItems.add(result); - this.namedMenuItems.put(command, result); - - return result; - } - - /** - * ツールボタンを生成する。 - * @param command アクションコマンド名 - * @param tooltip ツールチップ文字列 - * @param icon アイコン画像 - * @return ツールボタン - */ - private JButton buildToolButton(String command, - String tooltip, - Icon icon){ - JButton result = new JButton(); - - result.setIcon(icon); - result.setToolTipText(tooltip); - result.setMargin(new Insets(1, 1, 1, 1)); - result.setActionCommand(command); - - this.actionItems.add(result); - this.namedToolButtons.put(command, result); - - return result; - } - - /** - * L&F 一覧メニューを作成する。 - * @param label メニューラベル - * @param nemonic ニモニックキー - * @return L&F 一覧メニュー - */ - private JMenu buildLookAndFeelMenu(String label, int nemonic){ - JMenu result = buildMenu(label, nemonic); - - LookAndFeel currentLookAndFeel = UIManager.getLookAndFeel(); - String currentName = currentLookAndFeel.getClass().getName(); - JMenuItem matchedButton = null; - - UIManager.LookAndFeelInfo[] landfs = - UIManager.getInstalledLookAndFeels(); - for(UIManager.LookAndFeelInfo lafInfo : landfs){ - String name = lafInfo.getName(); - String className = lafInfo.getClassName(); - - JRadioButtonMenuItem button = new JRadioButtonMenuItem(name); - button.setActionCommand(CMD_LANDF); - - if(className.equals(currentName)) matchedButton = button; - - this.actionItems.add(button); - this.landfGroup.add(button); - this.landfMap.put(button.getModel(), className); - - result.add(button); - } - - if(matchedButton != null) matchedButton.setSelected(true); - - return result; - } - - /** - * アクセラレータの設定。 - */ - private void registKeyAccelerator(){ - getMenuItem(CMD_HELPDOC) .setAccelerator(KEY_F1); - getMenuItem(CMD_SHOWFIND) .setAccelerator(KEY_CTRL_F); - getMenuItem(CMD_SEARCHNEXT) .setAccelerator(KEY_F3); - getMenuItem(CMD_SEARCHPREV) .setAccelerator(KEY_SHIFT_F3); - getMenuItem(CMD_RELOAD) .setAccelerator(KEY_F5); - return; - } - - /** - * アクションコマンド名からメニューアイテムを探す。 - * @param command アクションコマンド名 - * @return メニューアイテム - */ - private JMenuItem getMenuItem(String command){ - JMenuItem result = this.namedMenuItems.get(command); - return result; - } - - /** - * アクションコマンド名からツールボタンを探す。 - * @param command アクションコマンド名 - * @return ツールボタン - */ - private JButton getToolButton(String command){ - JButton result = this.namedToolButtons.get(command); - return result; - } - - /** - * 現在メニューで選択中のL&Fのクラス名を返す。 - * @return L&F クラス名 - */ - public String getSelectedLookAndFeel(){ - ButtonModel selected = this.landfGroup.getSelection(); - if(selected == null) return null; - String className = this.landfMap.get(selected); - return className; - } - - /** - * 全てのボタンにアクションリスナーを登録する。 - * @param listener アクションリスナー - */ - public void addActionListener(ActionListener listener){ - for(AbstractButton button : this.actionItems){ - button.addActionListener(listener); - } - return; - } - - /** - * メニューバーを生成する。 - * @return メニューバー - */ - private JMenuBar buildMenuBar(){ - this.menuFile.add(getMenuItem(CMD_ACCOUNT)); - this.menuFile.addSeparator(); - this.menuFile.add(getMenuItem(CMD_EXIT)); - - this.menuEdit.add(getMenuItem(CMD_COPY)); - this.menuEdit.addSeparator(); - this.menuEdit.add(getMenuItem(CMD_SHOWFIND)); - this.menuEdit.add(getMenuItem(CMD_SEARCHPREV)); - this.menuEdit.add(getMenuItem(CMD_SEARCHNEXT)); - - this.menuVillage.add(getMenuItem(CMD_ALLPERIOD)); - this.menuVillage.add(getMenuItem(CMD_SHOWDIGEST)); - this.menuVillage.addSeparator(); - this.menuVillage.add(getMenuItem(CMD_WEBVILL)); - this.menuVillage.add(getMenuItem(CMD_WEBWIKI)); - this.menuVillage.add(getMenuItem(CMD_WEBCAST)); - - this.menuDay.add(getMenuItem(CMD_RELOAD)); - this.menuDay.add(getMenuItem(CMD_DAYSUMMARY)); - this.menuDay.add(getMenuItem(CMD_DAYEXPCSV)); - this.menuDay.addSeparator(); - this.menuDay.add(getMenuItem(CMD_WEBDAY)); - - this.menuPreference.add(getMenuItem(CMD_OPTION)); - this.menuPreference.addSeparator(); - this.menuPreference.add(this.menuLook); - - this.menuTool.add(getMenuItem(CMD_SHOWFILT)); - this.menuTool.add(getMenuItem(CMD_SHOWEDIT)); - this.menuTool.add(getMenuItem(CMD_SHOWLOG)); - - this.menuHelp.add(getMenuItem(CMD_HELPDOC)); - this.menuHelp.addSeparator(); - this.menuHelp.add(getMenuItem(CMD_SHOWPORTAL)); - this.menuHelp.add(getMenuItem(CMD_ABOUT)); - - JMenuBar bar = new JMenuBar(); - - bar.add(this.menuFile); - bar.add(this.menuEdit); - bar.add(this.menuVillage); - bar.add(this.menuDay); - bar.add(this.menuPreference); - bar.add(this.menuTool); - bar.add(this.menuHelp); - - return bar; - } - - /** - * メニューバーを取得する。 - * @return メニューバー - */ - public JMenuBar getMenuBar(){ - return this.menuBar; - } - - /** - * ブラウザ用ツールバーの生成を行う。 - * @return ツールバー - */ - private JToolBar buildBrowseToolBar(){ - JToolBar toolBar = new JToolBar(); - - toolBar.add(getToolButton(CMD_RELOAD)); - toolBar.addSeparator(); - toolBar.add(getToolButton(CMD_SHOWFIND)); - toolBar.add(getToolButton(CMD_SEARCHNEXT)); - toolBar.add(getToolButton(CMD_SEARCHPREV)); - toolBar.addSeparator(); - toolBar.add(getToolButton(CMD_SHOWFILT)); - toolBar.add(getToolButton(CMD_SHOWEDIT)); - - return toolBar; - } - - /** - * ブラウザ用ツールバーを取得する。 - * @return ツールバー - */ - public JToolBar getBrowseToolBar(){ - return this.browseToolBar; - } - - /** - * Periodが表示されているか通知を受ける。 - * @param appear 表示されているときはtrue - */ - private void appearPeriodImpl(boolean appear){ - if(appear) appearVillageImpl(appear); - - this.menuEdit.setEnabled(appear); - this.menuDay .setEnabled(appear); - - getToolButton(CMD_RELOAD) .setEnabled(appear); - getToolButton(CMD_SHOWFIND) .setEnabled(appear); - getToolButton(CMD_SEARCHNEXT) .setEnabled(appear); - getToolButton(CMD_SEARCHPREV) .setEnabled(appear); - - return; - } - - /** - * Periodが表示されているか通知を受ける。 - * @param appear 表示されているときはtrue - */ - public void appearPeriod(boolean appear){ - appearPeriodImpl(appear); - return; - } - - /** - * 村が表示されているか通知を受ける。 - * @param appear 表示されているときはtrue - */ - private void appearVillageImpl(boolean appear){ - if( ! appear) appearPeriodImpl(appear); - - this.menuVillage.setEnabled(appear); - - return; - } - - /** - * 村が表示されているか通知を受ける。 - * @param appear 表示されているときはtrue - */ - public void appearVillage(boolean appear){ - appearVillageImpl(appear); - return; - } - -} +/* + * action manager + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Insets; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; + +/** + * メニュー、ボタン、その他各種Actionを伴うイベントを生成する + * コンポーネントの一括管理。 + */ +public class ActionManager{ + + /** アクション{@value}。 */ + public static final String CMD_ACCOUNT = "ACCOUNT"; + /** アクション{@value}。 */ + public static final String CMD_EXIT = "EXIT"; + /** アクション{@value}。 */ + public static final String CMD_COPY = "COPY"; + /** アクション{@value}。 */ + public static final String CMD_SHOWFIND = "SHOWFIND"; + /** アクション{@value}。 */ + public static final String CMD_SEARCHNEXT = "SEARCHNEXT"; + /** アクション{@value}。 */ + public static final String CMD_SEARCHPREV = "SEARCHPREV"; + /** アクション{@value}。 */ + public static final String CMD_ALLPERIOD = "ALLPERIOD"; + /** アクション{@value}。 */ + public static final String CMD_SHOWDIGEST = "DIGEST"; + /** アクション{@value}。 */ + public static final String CMD_WEBVILL = "WEBVILL"; + /** アクション{@value}。 */ + public static final String CMD_WEBCAST = "WEBCAST"; + /** アクション{@value}。 */ + public static final String CMD_WEBWIKI = "WEBWIKI"; + /** アクション{@value}。 */ + public static final String CMD_RELOAD = "RELOAD"; + /** アクション{@value}。 */ + public static final String CMD_DAYSUMMARY = "DAYSUMMARY"; + /** アクション{@value}。 */ + public static final String CMD_DAYEXPCSV = "DAYEXPCSV"; + /** アクション{@value}。 */ + public static final String CMD_WEBDAY = "WEBDAY"; + /** アクション{@value}。 */ + public static final String CMD_OPTION = "OPTION"; + /** アクション{@value}。 */ + public static final String CMD_LANDF = "LANDF"; + /** アクション{@value}。 */ + public static final String CMD_SHOWFILT = "SHOWFILT"; + /** アクション{@value}。 */ + public static final String CMD_SHOWEDIT = "SHOWEDIT"; + /** アクション{@value}。 */ + public static final String CMD_SHOWLOG = "SHOWLOG"; + /** アクション{@value}。 */ + public static final String CMD_HELPDOC = "HELPDOC"; + /** アクション{@value}。 */ + public static final String CMD_SHOWPORTAL = "SHOWPORTAL"; + /** アクション{@value}。 */ + public static final String CMD_ABOUT = "ABOUT"; + + /** アクション{@value}。 */ + public static final String CMD_COPYTALK = "COPYTALK"; + /** アクション{@value}。 */ + public static final String CMD_JUMPANCHOR = "JUMPANCHOR"; + /** アクション{@value}。 */ + public static final String CMD_WEBTALK = "WEBTALK"; + /** アクション{@value}。 */ + public static final String CMD_SWITCHORDER = "SWITCHORDER"; + /** アクション{@value}。 */ + public static final String CMD_VILLAGELIST = "VILLAGELIST"; + /** アクション{@value}。 */ + public static final String CMD_FONTSIZESEL = "FONTSIZESEL"; + + /** WWWアイコン。 */ + public static final Icon ICON_WWW = GUIUtils.getWWWIcon(); + /** 検索アイコン。 */ + public static final Icon ICON_FIND; + /** 前検索アイコン。 */ + public static final Icon ICON_SEARCH_PREV; + /** 次検索アイコン。 */ + public static final Icon ICON_SEARCH_NEXT; + /** リロードアイコン。 */ + public static final Icon ICON_RELOAD; + /** フィルタアイコン。 */ + public static final Icon ICON_FILTER; + /** 発言エディタアイコン。 */ + public static final Icon ICON_EDITOR; + + private static final KeyStroke KEY_F1 = KeyStroke.getKeyStroke("F1"); + private static final KeyStroke KEY_F3 = KeyStroke.getKeyStroke("F3"); + private static final KeyStroke KEY_SHIFT_F3 = + KeyStroke.getKeyStroke("shift F3"); + private static final KeyStroke KEY_F5 = KeyStroke.getKeyStroke("F5"); + private static final KeyStroke KEY_CTRL_F = + KeyStroke.getKeyStroke("ctrl F"); + + static{ + URL iconurl; + + iconurl = Jindolf.getResource("resources/image/find.png"); + ICON_FIND = new ImageIcon(iconurl); + + iconurl = Jindolf.getResource("resources/image/findprev.png"); + ICON_SEARCH_PREV = new ImageIcon(iconurl); + + iconurl = Jindolf.getResource("resources/image/findnext.png"); + ICON_SEARCH_NEXT = new ImageIcon(iconurl); + + iconurl = Jindolf.getResource("resources/image/reload.png"); + ICON_RELOAD = new ImageIcon(iconurl); + + iconurl = Jindolf.getResource("resources/image/filter.png"); + ICON_FILTER = new ImageIcon(iconurl); + + iconurl = Jindolf.getResource("resources/image/editor.png"); + ICON_EDITOR = new ImageIcon(iconurl); + } + + private final Set actionItems = + new HashSet(); + private final Map namedMenuItems = + new HashMap(); + private final Map namedToolButtons = + new HashMap(); + + private final JMenuBar menuBar; + + private final JMenu menuFile; + private final JMenu menuEdit; + private final JMenu menuVillage; + private final JMenu menuDay; + private final JMenu menuPreference; + private final JMenu menuTool; + private final JMenu menuHelp; + + private final JMenu menuLook; + private final ButtonGroup landfGroup = new ButtonGroup(); + private final Map landfMap = + new HashMap(); + + private final JToolBar browseToolBar; + + /** + * コンストラクタ。 + */ + public ActionManager(){ + this.menuFile = buildMenu("ファイル", KeyEvent.VK_F); + this.menuEdit = buildMenu("編集", KeyEvent.VK_E); + this.menuVillage = buildMenu("村", KeyEvent.VK_V); + this.menuDay = buildMenu("日", KeyEvent.VK_D); + this.menuPreference = buildMenu("設定", KeyEvent.VK_P); + this.menuTool = buildMenu("ツール", KeyEvent.VK_T); + this.menuHelp = buildMenu("ヘルプ", KeyEvent.VK_H); + + this.menuLook = buildLookAndFeelMenu("ルック&フィール", KeyEvent.VK_L); + + buildMenuItem(CMD_ACCOUNT, "アカウント管理", KeyEvent.VK_M); + buildMenuItem(CMD_EXIT, "終了", KeyEvent.VK_X); + buildMenuItem(CMD_COPY, "選択範囲をコピー", KeyEvent.VK_C); + buildMenuItem(CMD_SHOWFIND, "検索...", KeyEvent.VK_F); + buildMenuItem(CMD_SEARCHNEXT, "次候補", KeyEvent.VK_N); + buildMenuItem(CMD_SEARCHPREV, "前候補", KeyEvent.VK_P); + buildMenuItem(CMD_ALLPERIOD, "全日程の一括読み込み", KeyEvent.VK_R); + buildMenuItem(CMD_SHOWDIGEST, "村のダイジェストを表示...", + KeyEvent.VK_D); + buildMenuItem(CMD_WEBVILL, "この村をブラウザで表示...", KeyEvent.VK_N); + buildMenuItem(CMD_WEBWIKI, + "まとめサイトの村ページを表示...", KeyEvent.VK_M); + buildMenuItem(CMD_WEBCAST, "キャスト紹介表ジェネレータ...", + KeyEvent.VK_H); + buildMenuItem(CMD_RELOAD, "この日を強制リロード", KeyEvent.VK_R); + buildMenuItem(CMD_DAYSUMMARY, "この日の発言を集計...", KeyEvent.VK_D); + buildMenuItem(CMD_DAYEXPCSV, "CSVへエクスポート...", KeyEvent.VK_C); + buildMenuItem(CMD_WEBDAY, "この日をブラウザで表示...", KeyEvent.VK_B); + buildMenuItem(CMD_OPTION, "オプション...", KeyEvent.VK_O); + buildMenuItem(CMD_SHOWFILT, "発言フィルタ", KeyEvent.VK_F); + buildMenuItem(CMD_SHOWEDIT, "発言エディタ", KeyEvent.VK_E); + buildMenuItem(CMD_SHOWLOG, "ログ表示", KeyEvent.VK_S); + buildMenuItem(CMD_HELPDOC, "ヘルプ表示", KeyEvent.VK_H); + buildMenuItem(CMD_SHOWPORTAL, "ポータルサイト...", KeyEvent.VK_P); + buildMenuItem(CMD_ABOUT, Jindolf.TITLE + "について...", KeyEvent.VK_A); + + buildToolButton(CMD_RELOAD, "選択中の日を強制リロード", ICON_RELOAD); + buildToolButton(CMD_SHOWFIND, "検索", ICON_FIND); + buildToolButton(CMD_SEARCHPREV, "↑前候補", ICON_SEARCH_PREV); + buildToolButton(CMD_SEARCHNEXT, "↓次候補", ICON_SEARCH_NEXT); + buildToolButton(CMD_SHOWFILT, "発言フィルタ", ICON_FILTER); + buildToolButton(CMD_SHOWEDIT, "発言エディタ", ICON_EDITOR); + + getMenuItem(CMD_SHOWPORTAL).setIcon(ICON_WWW); + getMenuItem(CMD_WEBVILL) .setIcon(ICON_WWW); + getMenuItem(CMD_WEBWIKI) .setIcon(ICON_WWW); + getMenuItem(CMD_WEBCAST) .setIcon(ICON_WWW); + getMenuItem(CMD_WEBDAY) .setIcon(ICON_WWW); + getMenuItem(CMD_SHOWFIND) .setIcon(ICON_FIND); + getMenuItem(CMD_SEARCHPREV).setIcon(ICON_SEARCH_PREV); + getMenuItem(CMD_SEARCHNEXT).setIcon(ICON_SEARCH_NEXT); + getMenuItem(CMD_SHOWFILT) .setIcon(ICON_FILTER); + getMenuItem(CMD_SHOWEDIT) .setIcon(ICON_EDITOR); + + registKeyAccelerator(); + + this.menuBar = buildMenuBar(); + this.browseToolBar = buildBrowseToolBar(); + + appearVillageImpl(false); + appearPeriodImpl(false); + + return; + } + + /** + * メニューを生成する。 + * @param label メニューラベル + * @param nemonic ニモニックキー + * @return メニュー + */ + private JMenu buildMenu(String label, int nemonic){ + JMenu result = new JMenu(); + + String keyText = label + "(" + KeyEvent.getKeyText(nemonic) + ")"; + + result.setText(keyText); + result.setMnemonic(nemonic); + + return result; + } + + /** + * メニューアイテムを生成する。 + * @param command アクションコマンド名 + * @param label メニューラベル + * @param nemonic ニモニックキー + * @return メニューアイテム + */ + private JMenuItem buildMenuItem(String command, + String label, + int nemonic ){ + JMenuItem result = new JMenuItem(); + + String keyText = label + "(" + KeyEvent.getKeyText(nemonic) + ")"; + + result.setActionCommand(command); + result.setText(keyText); + result.setMnemonic(nemonic); + + this.actionItems.add(result); + this.namedMenuItems.put(command, result); + + return result; + } + + /** + * ツールボタンを生成する。 + * @param command アクションコマンド名 + * @param tooltip ツールチップ文字列 + * @param icon アイコン画像 + * @return ツールボタン + */ + private JButton buildToolButton(String command, + String tooltip, + Icon icon){ + JButton result = new JButton(); + + result.setIcon(icon); + result.setToolTipText(tooltip); + result.setMargin(new Insets(1, 1, 1, 1)); + result.setActionCommand(command); + + this.actionItems.add(result); + this.namedToolButtons.put(command, result); + + return result; + } + + /** + * L&F 一覧メニューを作成する。 + * @param label メニューラベル + * @param nemonic ニモニックキー + * @return L&F 一覧メニュー + */ + private JMenu buildLookAndFeelMenu(String label, int nemonic){ + JMenu result = buildMenu(label, nemonic); + + LookAndFeel currentLookAndFeel = UIManager.getLookAndFeel(); + String currentName = currentLookAndFeel.getClass().getName(); + JMenuItem matchedButton = null; + + UIManager.LookAndFeelInfo[] landfs = + UIManager.getInstalledLookAndFeels(); + for(UIManager.LookAndFeelInfo lafInfo : landfs){ + String name = lafInfo.getName(); + String className = lafInfo.getClassName(); + + JRadioButtonMenuItem button = new JRadioButtonMenuItem(name); + button.setActionCommand(CMD_LANDF); + + if(className.equals(currentName)) matchedButton = button; + + this.actionItems.add(button); + this.landfGroup.add(button); + this.landfMap.put(button.getModel(), className); + + result.add(button); + } + + if(matchedButton != null) matchedButton.setSelected(true); + + return result; + } + + /** + * アクセラレータの設定。 + */ + private void registKeyAccelerator(){ + getMenuItem(CMD_HELPDOC) .setAccelerator(KEY_F1); + getMenuItem(CMD_SHOWFIND) .setAccelerator(KEY_CTRL_F); + getMenuItem(CMD_SEARCHNEXT) .setAccelerator(KEY_F3); + getMenuItem(CMD_SEARCHPREV) .setAccelerator(KEY_SHIFT_F3); + getMenuItem(CMD_RELOAD) .setAccelerator(KEY_F5); + return; + } + + /** + * アクションコマンド名からメニューアイテムを探す。 + * @param command アクションコマンド名 + * @return メニューアイテム + */ + private JMenuItem getMenuItem(String command){ + JMenuItem result = this.namedMenuItems.get(command); + return result; + } + + /** + * アクションコマンド名からツールボタンを探す。 + * @param command アクションコマンド名 + * @return ツールボタン + */ + private JButton getToolButton(String command){ + JButton result = this.namedToolButtons.get(command); + return result; + } + + /** + * 現在メニューで選択中のL&Fのクラス名を返す。 + * @return L&F クラス名 + */ + public String getSelectedLookAndFeel(){ + ButtonModel selected = this.landfGroup.getSelection(); + if(selected == null) return null; + String className = this.landfMap.get(selected); + return className; + } + + /** + * 全てのボタンにアクションリスナーを登録する。 + * @param listener アクションリスナー + */ + public void addActionListener(ActionListener listener){ + for(AbstractButton button : this.actionItems){ + button.addActionListener(listener); + } + return; + } + + /** + * メニューバーを生成する。 + * @return メニューバー + */ + private JMenuBar buildMenuBar(){ + this.menuFile.add(getMenuItem(CMD_ACCOUNT)); + this.menuFile.addSeparator(); + this.menuFile.add(getMenuItem(CMD_EXIT)); + + this.menuEdit.add(getMenuItem(CMD_COPY)); + this.menuEdit.addSeparator(); + this.menuEdit.add(getMenuItem(CMD_SHOWFIND)); + this.menuEdit.add(getMenuItem(CMD_SEARCHPREV)); + this.menuEdit.add(getMenuItem(CMD_SEARCHNEXT)); + + this.menuVillage.add(getMenuItem(CMD_ALLPERIOD)); + this.menuVillage.add(getMenuItem(CMD_SHOWDIGEST)); + this.menuVillage.addSeparator(); + this.menuVillage.add(getMenuItem(CMD_WEBVILL)); + this.menuVillage.add(getMenuItem(CMD_WEBWIKI)); + this.menuVillage.add(getMenuItem(CMD_WEBCAST)); + + this.menuDay.add(getMenuItem(CMD_RELOAD)); + this.menuDay.add(getMenuItem(CMD_DAYSUMMARY)); + this.menuDay.add(getMenuItem(CMD_DAYEXPCSV)); + this.menuDay.addSeparator(); + this.menuDay.add(getMenuItem(CMD_WEBDAY)); + + this.menuPreference.add(getMenuItem(CMD_OPTION)); + this.menuPreference.addSeparator(); + this.menuPreference.add(this.menuLook); + + this.menuTool.add(getMenuItem(CMD_SHOWFILT)); + this.menuTool.add(getMenuItem(CMD_SHOWEDIT)); + this.menuTool.add(getMenuItem(CMD_SHOWLOG)); + + this.menuHelp.add(getMenuItem(CMD_HELPDOC)); + this.menuHelp.addSeparator(); + this.menuHelp.add(getMenuItem(CMD_SHOWPORTAL)); + this.menuHelp.add(getMenuItem(CMD_ABOUT)); + + JMenuBar bar = new JMenuBar(); + + bar.add(this.menuFile); + bar.add(this.menuEdit); + bar.add(this.menuVillage); + bar.add(this.menuDay); + bar.add(this.menuPreference); + bar.add(this.menuTool); + bar.add(this.menuHelp); + + return bar; + } + + /** + * メニューバーを取得する。 + * @return メニューバー + */ + public JMenuBar getMenuBar(){ + return this.menuBar; + } + + /** + * ブラウザ用ツールバーの生成を行う。 + * @return ツールバー + */ + private JToolBar buildBrowseToolBar(){ + JToolBar toolBar = new JToolBar(); + + toolBar.add(getToolButton(CMD_RELOAD)); + toolBar.addSeparator(); + toolBar.add(getToolButton(CMD_SHOWFIND)); + toolBar.add(getToolButton(CMD_SEARCHNEXT)); + toolBar.add(getToolButton(CMD_SEARCHPREV)); + toolBar.addSeparator(); + toolBar.add(getToolButton(CMD_SHOWFILT)); + toolBar.add(getToolButton(CMD_SHOWEDIT)); + + return toolBar; + } + + /** + * ブラウザ用ツールバーを取得する。 + * @return ツールバー + */ + public JToolBar getBrowseToolBar(){ + return this.browseToolBar; + } + + /** + * Periodが表示されているか通知を受ける。 + * @param appear 表示されているときはtrue + */ + private void appearPeriodImpl(boolean appear){ + if(appear) appearVillageImpl(appear); + + this.menuEdit.setEnabled(appear); + this.menuDay .setEnabled(appear); + + getToolButton(CMD_RELOAD) .setEnabled(appear); + getToolButton(CMD_SHOWFIND) .setEnabled(appear); + getToolButton(CMD_SEARCHNEXT) .setEnabled(appear); + getToolButton(CMD_SEARCHPREV) .setEnabled(appear); + + return; + } + + /** + * Periodが表示されているか通知を受ける。 + * @param appear 表示されているときはtrue + */ + public void appearPeriod(boolean appear){ + appearPeriodImpl(appear); + return; + } + + /** + * 村が表示されているか通知を受ける。 + * @param appear 表示されているときはtrue + */ + private void appearVillageImpl(boolean appear){ + if( ! appear) appearPeriodImpl(appear); + + this.menuVillage.setEnabled(appear); + + return; + } + + /** + * 村が表示されているか通知を受ける。 + * @param appear 表示されているときはtrue + */ + public void appearVillage(boolean appear){ + appearVillageImpl(appear); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Anchor.java b/src/main/java/jp/sourceforge/jindolf/Anchor.java index 05ee1e6..7171090 100644 --- a/src/main/java/jp/sourceforge/jindolf/Anchor.java +++ b/src/main/java/jp/sourceforge/jindolf/Anchor.java @@ -1,347 +1,347 @@ -/* - * anchor - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 発言アンカー。 - */ -public final class Anchor{ - - private static final int EPILOGUEDAY = 99; - private static final Pattern ANCHOR_PATTERN; - - static{ - String spchar = "\u0020\u3000\\t"; - String sp = "[" +spchar+ "]"; - String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?"; - - String day = // TODO 「昨日」なども含めるか? - "(" - +"(?:" - + "(プロ(?:ローグ)?)" - +"|"+"(エピ(?:ローグ)?)" - +"|"+"(?:" - + "([1-91-9]?[0-90-9])" - +sp_n+ "(?:[dDdD]|(?:日目?))" - +")" - +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n - +")?"; - String ampm = - "(" - +"(?:" - + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))" - +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))" - +")" +sp_n - +")?"; - String hhmm = - "(?:"+ - "(" - +"([0-20-2]?[0-90-9])" - +sp_n+ "[:;:;]?" +sp_n - +"([0-50-5][0-90-9])" - +")" - +"|" - +"(" - +"([0-20-2]?[0-90-9])" - +sp_n+ "時" +sp_n - +"([0-50-5]?[0-90-9])" - +sp_n+ "分" - +")" - +")"; - - String talkNum = - "(?:>>([1-9][0-9]{0,8}))"; - - ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum, - Pattern.DOTALL); - } - - - private final CharSequence source; - private final int startPos; - private final int endPos; - private final int day; - private final int hour; - private final int minute; - private final int talkNo; - - - /** - * アンカーのコンストラクタ。 - * @param source アンカーが含まれる文字列 - * @param startPos アンカーの始まる位置 - * @param endPos アンカーの終わる位置 - * @param talkNo 公開発言番号 - */ - private Anchor(CharSequence source, int startPos, int endPos, - int talkNo ){ - super(); - - if(talkNo <= 0) throw new IllegalArgumentException(); - - this.source = source; - this.startPos = startPos; - this.endPos = endPos; - this.day = -1; - this.hour = -1; - this.minute = -1; - this.talkNo = talkNo; - - return; - } - - /** - * アンカーのコンストラクタ。 - * @param source アンカーが含まれる文字列 - * @param startPos アンカーの始まる位置 - * @param endPos アンカーの終わる位置 - * @param day 日 - * @param hour 時間(0-23) - * @param minute 分(0-59) - */ - private Anchor(CharSequence source, int startPos, int endPos, - int day, int hour, int minute ){ - super(); - - this.source = source; - this.startPos = startPos; - this.endPos = endPos; - this.day = day; - this.hour = hour; - this.minute = minute; - this.talkNo = -1; - - return; - } - - - /** - * 与えられた範囲指定文字列からアンカーを抽出する。 - * @param source 検索対象文字列 - * @param regionStart 範囲開始位置 - * @param regionEnd 範囲終了位置 - * @param currentDay 相対日付の基本となる日 - * @return アンカー - */ - public static Anchor getAnchor(CharSequence source, - int regionStart, - int regionEnd, - int currentDay ){ - Matcher matcher = ANCHOR_PATTERN.matcher(source); - matcher.region(regionStart, regionEnd); - - if( ! matcher.find() ) return null; - - Anchor anchor = getAnchorFromMatched(source, matcher, currentDay); - - return anchor; - } - - /** - * 与えられた文字列から全アンカーを抽出する。 - * @param source 検索対象文字列 - * @param currentDay 相対日付の基本となる日 - * @return アンカーのリスト(出現順) - */ - public static List getAnchorList(CharSequence source, - int currentDay ){ - List result = new LinkedList(); - - Matcher matcher = ANCHOR_PATTERN.matcher(source); - int regionEnd = source.length(); - - while(matcher.find()){ - Anchor anchor = getAnchorFromMatched(source, matcher, currentDay); - result.add(anchor); - int regionStart = matcher.end(); - matcher.region(regionStart, regionEnd); - } - - return result; - } - - /** - * 文字列とそのMatcherからアンカーを抽出する。 - * @param source 検索対象文字列 - * @param matcher Matcher - * @param currentDay 相対日付の基本となる日 - * @return アンカー - */ - private static Anchor getAnchorFromMatched(CharSequence source, - Matcher matcher, - int currentDay){ - int startPos = matcher.start(); - int endPos = matcher.end(); - - /* G国アンカー */ - if(matcher.start(14) < matcher.end(14)){ - int talkNo = StringUtils.parseInt(source, matcher, 14); - Anchor anchor = new Anchor(source, startPos, endPos, talkNo); - return anchor; - } - - int day = currentDay; - if(matcher.start(1) < matcher.end(1)){ - if(matcher.start(2) < matcher.end(2)){ // prologue - day = 0; - }else if(matcher.start(3) < matcher.end(3)){ // epilogue - day = EPILOGUEDAY; - }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d" - day = StringUtils.parseInt(source, matcher, 4); - }else{ - assert false; - return null; - } - } - - boolean isPM = false; - if(matcher.start(5) < matcher.end(5)){ - if(matcher.start(6) < matcher.end(6)){ // AM - isPM = false; - }else if(matcher.start(7) < matcher.end(7)){ // PM - isPM = true; - }else{ - assert false; - return null; - } - } - - int hourGroup; - int minuteGroup; - if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm - hourGroup = 9; - minuteGroup = 10; - }else if(matcher.start(11) < matcher.end(11)){ // h時m分 - hourGroup = 12; - minuteGroup = 13; - }else{ - assert false; - return null; - } - int hour = StringUtils.parseInt(source, matcher, hourGroup); - int minute = StringUtils.parseInt(source, matcher, minuteGroup); - - if(isPM && hour < 12) hour += 12; - hour %= 24; - // 午後12:34は午後00:34になる - - // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか? - // とりあえず前者 - - Anchor anchor = new Anchor(source, startPos, endPos, - day, hour, minute); - - return anchor; - } - - /** - * アンカーの含まれる文字列を返す。 - * @return アンカーの含まれる文字列 - */ - public CharSequence getSource(){ - return this.source; - } - - /** - * アンカーの開始位置を返す。 - * @return アンカー開始位置 - */ - public int getStartPos(){ - return this.startPos; - } - - /** - * アンカーの終了位置を返す。 - * @return アンカー終了位置 - */ - public int getEndPos(){ - return this.endPos; - } - - /** - * アンカーの示す日付を返す。 - * @return 日付 - */ - public int getDay(){ - return this.day; - } - - /** - * アンカーの示す時刻を返す。 - * @return 時刻(0-23) - */ - public int getHour(){ - return this.hour; - } - - /** - * アンカーの示す分を返す。 - * @return 分(0-59) - */ - public int getMinute(){ - return this.minute; - } - - /** - * アンカーの示す公開発言番号を返す。 - * @return 公開発言番号。公開発言番号でない場合は0以下の値。 - */ - public int getTalkNo(){ - return this.talkNo; - } - - /** - * このアンカーが公開発言番号による物か判定する。 - * @return 公開発言番号由来であるならtrue - */ - public boolean hasTalkNo(){ - return 0 < this.talkNo; - } - - /** - * 明示的なエピローグへのアンカーか判定する。 - * @return 明示的なエピローグへのアンカーならtrue - */ - public boolean isEpilogueDay(){ - if(this.day >= EPILOGUEDAY) return true; - return false; - } - - /** - * アンカーの文字列表記を返す。 - * 出典:まとめサイトの用語集 - * @return アンカーの文字列表記 - */ - @Override - public String toString(){ - /* G国表記 */ - if(hasTalkNo()){ - return ">>" + this.talkNo; - } - - StringBuilder result = new StringBuilder(); - - result.append(getDay()).append('d'); - - int anchorHour = getHour(); - if(anchorHour < 10) result.append('0'); - result.append(anchorHour).append(':'); - - int anchorMinute = getMinute(); - if(anchorMinute < 10) result.append('0'); - result.append(anchorMinute); - - return result.toString(); - } - -} +/* + * anchor + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 発言アンカー。 + */ +public final class Anchor{ + + private static final int EPILOGUEDAY = 99; + private static final Pattern ANCHOR_PATTERN; + + static{ + String spchar = "\u0020\u3000\\t"; + String sp = "[" +spchar+ "]"; + String sp_n = "(?:" + sp + "|" + "(?:\\Q \\E)" + ")*?"; + + String day = // TODO 「昨日」なども含めるか? + "(" + +"(?:" + + "(プロ(?:ローグ)?)" + +"|"+"(エピ(?:ローグ)?)" + +"|"+"(?:" + + "([1-91-9]?[0-90-9])" + +sp_n+ "(?:[dDdD]|(?:日目?))" + +")" + +")" +"[\\-\\[\\(/_-ー―[_]?" +sp_n + +")?"; + String ampm = + "(" + +"(?:" + + "((?:[aAaA][\\..]?[mMmM][\\..]?)|(?:午前))" + +"|"+"((?:[pPpP][\\..]?[mMmM][\\..]?)|(?:午後))" + +")" +sp_n + +")?"; + String hhmm = + "(?:"+ + "(" + +"([0-20-2]?[0-90-9])" + +sp_n+ "[:;:;]?" +sp_n + +"([0-50-5][0-90-9])" + +")" + +"|" + +"(" + +"([0-20-2]?[0-90-9])" + +sp_n+ "時" +sp_n + +"([0-50-5]?[0-90-9])" + +sp_n+ "分" + +")" + +")"; + + String talkNum = + "(?:>>([1-9][0-9]{0,8}))"; + + ANCHOR_PATTERN = Pattern.compile(day + ampm + hhmm +"|"+ talkNum, + Pattern.DOTALL); + } + + + private final CharSequence source; + private final int startPos; + private final int endPos; + private final int day; + private final int hour; + private final int minute; + private final int talkNo; + + + /** + * アンカーのコンストラクタ。 + * @param source アンカーが含まれる文字列 + * @param startPos アンカーの始まる位置 + * @param endPos アンカーの終わる位置 + * @param talkNo 公開発言番号 + */ + private Anchor(CharSequence source, int startPos, int endPos, + int talkNo ){ + super(); + + if(talkNo <= 0) throw new IllegalArgumentException(); + + this.source = source; + this.startPos = startPos; + this.endPos = endPos; + this.day = -1; + this.hour = -1; + this.minute = -1; + this.talkNo = talkNo; + + return; + } + + /** + * アンカーのコンストラクタ。 + * @param source アンカーが含まれる文字列 + * @param startPos アンカーの始まる位置 + * @param endPos アンカーの終わる位置 + * @param day 日 + * @param hour 時間(0-23) + * @param minute 分(0-59) + */ + private Anchor(CharSequence source, int startPos, int endPos, + int day, int hour, int minute ){ + super(); + + this.source = source; + this.startPos = startPos; + this.endPos = endPos; + this.day = day; + this.hour = hour; + this.minute = minute; + this.talkNo = -1; + + return; + } + + + /** + * 与えられた範囲指定文字列からアンカーを抽出する。 + * @param source 検索対象文字列 + * @param regionStart 範囲開始位置 + * @param regionEnd 範囲終了位置 + * @param currentDay 相対日付の基本となる日 + * @return アンカー + */ + public static Anchor getAnchor(CharSequence source, + int regionStart, + int regionEnd, + int currentDay ){ + Matcher matcher = ANCHOR_PATTERN.matcher(source); + matcher.region(regionStart, regionEnd); + + if( ! matcher.find() ) return null; + + Anchor anchor = getAnchorFromMatched(source, matcher, currentDay); + + return anchor; + } + + /** + * 与えられた文字列から全アンカーを抽出する。 + * @param source 検索対象文字列 + * @param currentDay 相対日付の基本となる日 + * @return アンカーのリスト(出現順) + */ + public static List getAnchorList(CharSequence source, + int currentDay ){ + List result = new LinkedList(); + + Matcher matcher = ANCHOR_PATTERN.matcher(source); + int regionEnd = source.length(); + + while(matcher.find()){ + Anchor anchor = getAnchorFromMatched(source, matcher, currentDay); + result.add(anchor); + int regionStart = matcher.end(); + matcher.region(regionStart, regionEnd); + } + + return result; + } + + /** + * 文字列とそのMatcherからアンカーを抽出する。 + * @param source 検索対象文字列 + * @param matcher Matcher + * @param currentDay 相対日付の基本となる日 + * @return アンカー + */ + private static Anchor getAnchorFromMatched(CharSequence source, + Matcher matcher, + int currentDay){ + int startPos = matcher.start(); + int endPos = matcher.end(); + + /* G国アンカー */ + if(matcher.start(14) < matcher.end(14)){ + int talkNo = StringUtils.parseInt(source, matcher, 14); + Anchor anchor = new Anchor(source, startPos, endPos, talkNo); + return anchor; + } + + int day = currentDay; + if(matcher.start(1) < matcher.end(1)){ + if(matcher.start(2) < matcher.end(2)){ // prologue + day = 0; + }else if(matcher.start(3) < matcher.end(3)){ // epilogue + day = EPILOGUEDAY; + }else if(matcher.start(4) < matcher.end(4)){ // etc) "6d" + day = StringUtils.parseInt(source, matcher, 4); + }else{ + assert false; + return null; + } + } + + boolean isPM = false; + if(matcher.start(5) < matcher.end(5)){ + if(matcher.start(6) < matcher.end(6)){ // AM + isPM = false; + }else if(matcher.start(7) < matcher.end(7)){ // PM + isPM = true; + }else{ + assert false; + return null; + } + } + + int hourGroup; + int minuteGroup; + if(matcher.start(8) < matcher.end(8)){ // hhmm hmm hh:mm + hourGroup = 9; + minuteGroup = 10; + }else if(matcher.start(11) < matcher.end(11)){ // h時m分 + hourGroup = 12; + minuteGroup = 13; + }else{ + assert false; + return null; + } + int hour = StringUtils.parseInt(source, matcher, hourGroup); + int minute = StringUtils.parseInt(source, matcher, minuteGroup); + + if(isPM && hour < 12) hour += 12; + hour %= 24; + // 午後12:34は午後00:34になる + + // TODO 3d25:30 は 3d01:30 か 4d01:30 どちらにすべきか? + // とりあえず前者 + + Anchor anchor = new Anchor(source, startPos, endPos, + day, hour, minute); + + return anchor; + } + + /** + * アンカーの含まれる文字列を返す。 + * @return アンカーの含まれる文字列 + */ + public CharSequence getSource(){ + return this.source; + } + + /** + * アンカーの開始位置を返す。 + * @return アンカー開始位置 + */ + public int getStartPos(){ + return this.startPos; + } + + /** + * アンカーの終了位置を返す。 + * @return アンカー終了位置 + */ + public int getEndPos(){ + return this.endPos; + } + + /** + * アンカーの示す日付を返す。 + * @return 日付 + */ + public int getDay(){ + return this.day; + } + + /** + * アンカーの示す時刻を返す。 + * @return 時刻(0-23) + */ + public int getHour(){ + return this.hour; + } + + /** + * アンカーの示す分を返す。 + * @return 分(0-59) + */ + public int getMinute(){ + return this.minute; + } + + /** + * アンカーの示す公開発言番号を返す。 + * @return 公開発言番号。公開発言番号でない場合は0以下の値。 + */ + public int getTalkNo(){ + return this.talkNo; + } + + /** + * このアンカーが公開発言番号による物か判定する。 + * @return 公開発言番号由来であるならtrue + */ + public boolean hasTalkNo(){ + return 0 < this.talkNo; + } + + /** + * 明示的なエピローグへのアンカーか判定する。 + * @return 明示的なエピローグへのアンカーならtrue + */ + public boolean isEpilogueDay(){ + if(this.day >= EPILOGUEDAY) return true; + return false; + } + + /** + * アンカーの文字列表記を返す。 + * 出典:まとめサイトの用語集 + * @return アンカーの文字列表記 + */ + @Override + public String toString(){ + /* G国表記 */ + if(hasTalkNo()){ + return ">>" + this.talkNo; + } + + StringBuilder result = new StringBuilder(); + + result.append(getDay()).append('d'); + + int anchorHour = getHour(); + if(anchorHour < 10) result.append('0'); + result.append(anchorHour).append(':'); + + int anchorMinute = getMinute(); + if(anchorMinute < 10) result.append('0'); + result.append(anchorMinute); + + return result.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AnchorDraw.java b/src/main/java/jp/sourceforge/jindolf/AnchorDraw.java index 949d03c..67a4bfe 100644 --- a/src/main/java/jp/sourceforge/jindolf/AnchorDraw.java +++ b/src/main/java/jp/sourceforge/jindolf/AnchorDraw.java @@ -1,301 +1,301 @@ -/* - * アンカー描画 - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Stroke; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.regex.Pattern; - -/** - * アンカー描画。 - */ -public class AnchorDraw extends AbstractTextRow{ - - private static final Color COLOR_ANCHOR = new Color(0xffff99); - private static final Color COLOR_SIMPLEFG = Color.BLACK; - private static final int UPPER_MARGIN = 3; - private static final int UNDER_MARGIN = 3; - - private static final Stroke STROKE_DASH; - - static{ - float[] dash = {3.0f}; - STROKE_DASH = new BasicStroke(1.0f, - BasicStroke.CAP_SQUARE, - BasicStroke.JOIN_MITER, - 10.0f, - dash, - 0.0f); - } - - private final Talk talk; - private final GlyphDraw caption; - private final GlyphDraw dialog; - private final BufferedImage faceImage; - private final Point imageOrigin = new Point(); - private final Point captionOrigin = new Point(); - private final Point dialogOrigin = new Point(); - - private DialogPref dialogPref; - - /** - * コンストラクタ。 - * @param talk 発言 - */ - public AnchorDraw(Talk talk){ - this(talk, new DialogPref(), FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param talk 発言 - * @param pref 発言表示設定 - * @param fontInfo フォント設定 - */ - public AnchorDraw(Talk talk, DialogPref pref, FontInfo fontInfo){ - super(fontInfo); - - this.talk = talk; - this.caption = new GlyphDraw(getCaptionString(), this.fontInfo); - this.dialog = new GlyphDraw(this.talk.getDialog(), this.fontInfo); - this.dialogPref = pref; - this.faceImage = getFaceImage(); - - setColorDesign(); - - return; - } - - /** - * 顔アイコンイメージを返す。 - * @return アイコンイメージ - */ - private BufferedImage getFaceImage(){ - Period period = this.talk.getPeriod(); - Village village = period.getVillage(); - - BufferedImage image; - if(this.talk.isGrave()){ - image = village.getGraveImage(); - }else{ - Avatar avatar = this.talk.getAvatar(); - image = village.getAvatarFaceImage(avatar); - } - - return image; - } - - /** - * 配色を設定する。 - */ - private void setColorDesign(){ - Color fgColor; - if(this.dialogPref.isSimpleMode()){ - fgColor = COLOR_SIMPLEFG; - }else{ - fgColor = COLOR_ANCHOR; - } - this.caption.setColor(fgColor); - this.dialog .setColor(fgColor); - return; - } - - /** - * キャプション文字列の取得。 - * @return キャプション文字列 - */ - private CharSequence getCaptionString(){ - StringBuilder result = new StringBuilder(); - Avatar avatar = this.talk.getAvatar(); - - if(this.talk.hasTalkNo()){ - result.append(this.talk.getAnchorNotation_G()).append(' '); - } - result.append(avatar.getFullName()) - .append(' ') - .append(this.talk.getAnchorNotation()); - - return result; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Rectangle recalcBounds(){ - int newWidth = getWidth(); - - int imageWidth = 0; - int imageHeight = 0; - if( ! this.dialogPref.isSimpleMode() ){ - imageWidth = this.faceImage.getWidth(null); - imageHeight = this.faceImage.getHeight(null); - } - - this.caption.setWidth(newWidth - imageWidth); - int captionWidth = this.caption.getWidth(); - int captionHeight = this.caption.getHeight(); - - this.dialog.setWidth(newWidth); - int dialogWidth = this.dialog.getWidth(); - int dialogHeight = this.dialog.getHeight(); - - int headerHeight = Math.max(imageHeight, captionHeight); - - int totalWidth = Math.max(imageWidth + captionWidth, dialogWidth); - - int totalHeight = headerHeight; - totalHeight += dialogHeight; - - int imageYpos; - int captionYpos; - if(imageHeight > captionHeight){ - imageYpos = 0; - captionYpos = (imageHeight - captionHeight) / 2; - }else{ - imageYpos = (captionHeight - imageHeight) / 2; - captionYpos = 0; - } - - this.imageOrigin .setLocation(0, - UPPER_MARGIN + imageYpos); - this.captionOrigin.setLocation(imageWidth, - UPPER_MARGIN + captionYpos); - this.dialogOrigin .setLocation(0, - UPPER_MARGIN + headerHeight); - - if(this.dialogPref.isSimpleMode()){ - this.bounds.width = newWidth; - }else{ - this.bounds.width = totalWidth; - } - this.bounds.height = UPPER_MARGIN + totalHeight + UNDER_MARGIN; - - return this.bounds; - } - - /** - * {@inheritDoc} - * @param fontInfo {@inheritDoc} - */ - @Override - public void setFontInfo(FontInfo fontInfo){ - super.setFontInfo(fontInfo); - - this.caption.setFontInfo(this.fontInfo); - this.dialog .setFontInfo(this.fontInfo); - - recalcBounds(); - - return; - } - - /** - * 発言設定を更新する。 - * @param pref 発言設定 - */ - public void setDialogPref(DialogPref pref){ - this.dialogPref = pref; - - setColorDesign(); - recalcBounds(); - - return; - } - - /** - * {@inheritDoc} - * @param from {@inheritDoc} - * @param to {@inheritDoc} - */ - @Override - public void drag(Point from, Point to){ - this.caption.drag(from, to); - this.dialog.drag(from, to); - return; - } - - /** - * {@inheritDoc} - * @param appendable {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public Appendable appendSelected(Appendable appendable) - throws IOException{ - this.caption.appendSelected(appendable); - this.dialog.appendSelected(appendable); - return appendable; - } - - /** - * {@inheritDoc} - */ - @Override - public void clearSelect(){ - this.caption.clearSelect(); - this.dialog.clearSelect(); - return; - } - - /** - * 検索文字列パターンを設定する。 - * @param searchRegex パターン - * @return ヒット数 - */ - public int setRegex(Pattern searchRegex){ - int total = 0; - - total += this.dialog.setRegex(searchRegex); - - return total; - } - - /** - * {@inheritDoc} - * @param g {@inheritDoc} - */ - @Override - public void paint(Graphics2D g){ - final int xPos = this.bounds.x; - final int yPos = this.bounds.y; - - if(this.dialogPref.isSimpleMode()){ - Stroke oldStroke = g.getStroke(); - g.setStroke(STROKE_DASH); - g.drawLine(xPos, this.bounds.y, - xPos + this.bounds.width, this.bounds.y ); - g.setStroke(oldStroke); - }else{ - g.drawImage(this.faceImage, - xPos + this.imageOrigin.x, - yPos + this.imageOrigin.y, - null ); - } - - this.caption.setPos(xPos + this.captionOrigin.x, - yPos + this.captionOrigin.y ); - this.caption.paint(g); - - this.dialog.setPos(xPos + this.dialogOrigin.x, - yPos + this.dialogOrigin.y ); - this.dialog.paint(g); - - return; - } - -} +/* + * アンカー描画 + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Stroke; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.regex.Pattern; + +/** + * アンカー描画。 + */ +public class AnchorDraw extends AbstractTextRow{ + + private static final Color COLOR_ANCHOR = new Color(0xffff99); + private static final Color COLOR_SIMPLEFG = Color.BLACK; + private static final int UPPER_MARGIN = 3; + private static final int UNDER_MARGIN = 3; + + private static final Stroke STROKE_DASH; + + static{ + float[] dash = {3.0f}; + STROKE_DASH = new BasicStroke(1.0f, + BasicStroke.CAP_SQUARE, + BasicStroke.JOIN_MITER, + 10.0f, + dash, + 0.0f); + } + + private final Talk talk; + private final GlyphDraw caption; + private final GlyphDraw dialog; + private final BufferedImage faceImage; + private final Point imageOrigin = new Point(); + private final Point captionOrigin = new Point(); + private final Point dialogOrigin = new Point(); + + private DialogPref dialogPref; + + /** + * コンストラクタ。 + * @param talk 発言 + */ + public AnchorDraw(Talk talk){ + this(talk, new DialogPref(), FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param talk 発言 + * @param pref 発言表示設定 + * @param fontInfo フォント設定 + */ + public AnchorDraw(Talk talk, DialogPref pref, FontInfo fontInfo){ + super(fontInfo); + + this.talk = talk; + this.caption = new GlyphDraw(getCaptionString(), this.fontInfo); + this.dialog = new GlyphDraw(this.talk.getDialog(), this.fontInfo); + this.dialogPref = pref; + this.faceImage = getFaceImage(); + + setColorDesign(); + + return; + } + + /** + * 顔アイコンイメージを返す。 + * @return アイコンイメージ + */ + private BufferedImage getFaceImage(){ + Period period = this.talk.getPeriod(); + Village village = period.getVillage(); + + BufferedImage image; + if(this.talk.isGrave()){ + image = village.getGraveImage(); + }else{ + Avatar avatar = this.talk.getAvatar(); + image = village.getAvatarFaceImage(avatar); + } + + return image; + } + + /** + * 配色を設定する。 + */ + private void setColorDesign(){ + Color fgColor; + if(this.dialogPref.isSimpleMode()){ + fgColor = COLOR_SIMPLEFG; + }else{ + fgColor = COLOR_ANCHOR; + } + this.caption.setColor(fgColor); + this.dialog .setColor(fgColor); + return; + } + + /** + * キャプション文字列の取得。 + * @return キャプション文字列 + */ + private CharSequence getCaptionString(){ + StringBuilder result = new StringBuilder(); + Avatar avatar = this.talk.getAvatar(); + + if(this.talk.hasTalkNo()){ + result.append(this.talk.getAnchorNotation_G()).append(' '); + } + result.append(avatar.getFullName()) + .append(' ') + .append(this.talk.getAnchorNotation()); + + return result; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Rectangle recalcBounds(){ + int newWidth = getWidth(); + + int imageWidth = 0; + int imageHeight = 0; + if( ! this.dialogPref.isSimpleMode() ){ + imageWidth = this.faceImage.getWidth(null); + imageHeight = this.faceImage.getHeight(null); + } + + this.caption.setWidth(newWidth - imageWidth); + int captionWidth = this.caption.getWidth(); + int captionHeight = this.caption.getHeight(); + + this.dialog.setWidth(newWidth); + int dialogWidth = this.dialog.getWidth(); + int dialogHeight = this.dialog.getHeight(); + + int headerHeight = Math.max(imageHeight, captionHeight); + + int totalWidth = Math.max(imageWidth + captionWidth, dialogWidth); + + int totalHeight = headerHeight; + totalHeight += dialogHeight; + + int imageYpos; + int captionYpos; + if(imageHeight > captionHeight){ + imageYpos = 0; + captionYpos = (imageHeight - captionHeight) / 2; + }else{ + imageYpos = (captionHeight - imageHeight) / 2; + captionYpos = 0; + } + + this.imageOrigin .setLocation(0, + UPPER_MARGIN + imageYpos); + this.captionOrigin.setLocation(imageWidth, + UPPER_MARGIN + captionYpos); + this.dialogOrigin .setLocation(0, + UPPER_MARGIN + headerHeight); + + if(this.dialogPref.isSimpleMode()){ + this.bounds.width = newWidth; + }else{ + this.bounds.width = totalWidth; + } + this.bounds.height = UPPER_MARGIN + totalHeight + UNDER_MARGIN; + + return this.bounds; + } + + /** + * {@inheritDoc} + * @param fontInfo {@inheritDoc} + */ + @Override + public void setFontInfo(FontInfo fontInfo){ + super.setFontInfo(fontInfo); + + this.caption.setFontInfo(this.fontInfo); + this.dialog .setFontInfo(this.fontInfo); + + recalcBounds(); + + return; + } + + /** + * 発言設定を更新する。 + * @param pref 発言設定 + */ + public void setDialogPref(DialogPref pref){ + this.dialogPref = pref; + + setColorDesign(); + recalcBounds(); + + return; + } + + /** + * {@inheritDoc} + * @param from {@inheritDoc} + * @param to {@inheritDoc} + */ + @Override + public void drag(Point from, Point to){ + this.caption.drag(from, to); + this.dialog.drag(from, to); + return; + } + + /** + * {@inheritDoc} + * @param appendable {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public Appendable appendSelected(Appendable appendable) + throws IOException{ + this.caption.appendSelected(appendable); + this.dialog.appendSelected(appendable); + return appendable; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSelect(){ + this.caption.clearSelect(); + this.dialog.clearSelect(); + return; + } + + /** + * 検索文字列パターンを設定する。 + * @param searchRegex パターン + * @return ヒット数 + */ + public int setRegex(Pattern searchRegex){ + int total = 0; + + total += this.dialog.setRegex(searchRegex); + + return total; + } + + /** + * {@inheritDoc} + * @param g {@inheritDoc} + */ + @Override + public void paint(Graphics2D g){ + final int xPos = this.bounds.x; + final int yPos = this.bounds.y; + + if(this.dialogPref.isSimpleMode()){ + Stroke oldStroke = g.getStroke(); + g.setStroke(STROKE_DASH); + g.drawLine(xPos, this.bounds.y, + xPos + this.bounds.width, this.bounds.y ); + g.setStroke(oldStroke); + }else{ + g.drawImage(this.faceImage, + xPos + this.imageOrigin.x, + yPos + this.imageOrigin.y, + null ); + } + + this.caption.setPos(xPos + this.captionOrigin.x, + yPos + this.captionOrigin.y ); + this.caption.paint(g); + + this.dialog.setPos(xPos + this.dialogOrigin.x, + yPos + this.dialogOrigin.y ); + this.dialog.paint(g); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AnchorHitEvent.java b/src/main/java/jp/sourceforge/jindolf/AnchorHitEvent.java index ad387de..9dd4b33 100644 --- a/src/main/java/jp/sourceforge/jindolf/AnchorHitEvent.java +++ b/src/main/java/jp/sourceforge/jindolf/AnchorHitEvent.java @@ -1,63 +1,63 @@ -/* - * anchor hit event - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Point; -import java.util.EventObject; - -/** - * 発言アンカーがクリックされたときのイベント。 - */ -@SuppressWarnings("serial") -public class AnchorHitEvent extends EventObject{ - - private final TalkDraw talkDraw; - private final Anchor anchor; - private final Point point; - - /** - * コンストラクタ。 - * @param source イベント発生源 - * @param talkDraw 会話描画コンポーネント - * @param anchor アンカー - * @param point マウス座標 - */ - public AnchorHitEvent(Object source, - TalkDraw talkDraw, Anchor anchor, Point point){ - super(source); - this.talkDraw = talkDraw; - this.anchor = anchor; - this.point = point; - return; - } - - /** - * 会話描画コンポーネントを返す。 - * @return 会話描画コンポーネント - */ - public TalkDraw getTalkDraw(){ - return this.talkDraw; - } - - /** - * アンカーを返す。 - * @return アンカー - */ - public Anchor getAnchor(){ - return this.anchor; - } - - /** - * マウス座標を返す。 - * @return マウス座標 - */ - public Point getPoint(){ - return this.point; - } - -} +/* + * anchor hit event + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Point; +import java.util.EventObject; + +/** + * 発言アンカーがクリックされたときのイベント。 + */ +@SuppressWarnings("serial") +public class AnchorHitEvent extends EventObject{ + + private final TalkDraw talkDraw; + private final Anchor anchor; + private final Point point; + + /** + * コンストラクタ。 + * @param source イベント発生源 + * @param talkDraw 会話描画コンポーネント + * @param anchor アンカー + * @param point マウス座標 + */ + public AnchorHitEvent(Object source, + TalkDraw talkDraw, Anchor anchor, Point point){ + super(source); + this.talkDraw = talkDraw; + this.anchor = anchor; + this.point = point; + return; + } + + /** + * 会話描画コンポーネントを返す。 + * @return 会話描画コンポーネント + */ + public TalkDraw getTalkDraw(){ + return this.talkDraw; + } + + /** + * アンカーを返す。 + * @return アンカー + */ + public Anchor getAnchor(){ + return this.anchor; + } + + /** + * マウス座標を返す。 + * @return マウス座標 + */ + public Point getPoint(){ + return this.point; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AnchorHitListener.java b/src/main/java/jp/sourceforge/jindolf/AnchorHitListener.java index f5d7796..c65f8d4 100644 --- a/src/main/java/jp/sourceforge/jindolf/AnchorHitListener.java +++ b/src/main/java/jp/sourceforge/jindolf/AnchorHitListener.java @@ -1,22 +1,22 @@ -/* - * anchor hit listener - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.EventListener; - -/** - * 発言アンカーがヒットしたときのリスナ。 - */ -public interface AnchorHitListener extends EventListener{ - /** - * アンカーがクリックされたときに呼び出される。 - * @param event イベント - */ - void anchorHitted(AnchorHitEvent event); - -} +/* + * anchor hit listener + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.EventListener; + +/** + * 発言アンカーがヒットしたときのリスナ。 + */ +public interface AnchorHitListener extends EventListener{ + /** + * アンカーがクリックされたときに呼び出される。 + * @param event イベント + */ + void anchorHitted(AnchorHitEvent event); + +} diff --git a/src/main/java/jp/sourceforge/jindolf/AppSetting.java b/src/main/java/jp/sourceforge/jindolf/AppSetting.java index d2f621a..655768a 100644 --- a/src/main/java/jp/sourceforge/jindolf/AppSetting.java +++ b/src/main/java/jp/sourceforge/jindolf/AppSetting.java @@ -1,416 +1,416 @@ -/* - * application settings - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Font; -import java.awt.font.FontRenderContext; -import java.io.File; -import jp.sourceforge.jindolf.json.JsBoolean; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsPair; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * アプリケーションの各種設定。 - */ -public class AppSetting{ - - private static final String NETCONFIG_FILE = "netconfig.json"; - private static final String HASH_PROXY = "proxy"; - - private static final String TALKCONFIG_FILE = "talkconfig.json"; - private static final String HASH_FONT = "font"; - private static final String HASH_USEBODYICON = "useBodyIcon"; - private static final String HASH_USEMONOTOMB = "useMonoTomb"; - private static final String HASH_SIMPLEMODE = "isSimpleMode"; - private static final String HASH_ALIGNBALOON = "alignBaloonWidth"; - - private OptionInfo optInfo; - - private boolean useConfigPath; - private File configPath; - - private FontInfo fontInfo = FontInfo.DEFAULT_FONTINFO; - - private int frameWidth = 800; - private int frameHeight = 600; - private int frameXpos = Integer.MIN_VALUE; - private int frameYpos = Integer.MIN_VALUE; - - private ProxyInfo proxyInfo = ProxyInfo.DEFAULT; - - private DialogPref dialogPref = new DialogPref(); - - private JsValue loadedNetConfig; - private JsValue loadedTalkConfig; - - /** - * コンストラクタ。 - */ - public AppSetting(){ - super(); - return; - } - - /** - * コマンドラインオプションからアプリ設定を展開する。 - * @param optionInfo オプション情報 - */ - public void applyOptionInfo(OptionInfo optionInfo){ - this.optInfo = optionInfo; - applyConfigPathSetting(); - applyFontSetting(); - applyGeometrySetting(); - return; - } - - /** - * 設定格納ディレクトリ関係の設定。 - */ - private void applyConfigPathSetting(){ - CmdOption opt = this.optInfo - .getExclusiveOption(CmdOption.OPT_CONFDIR, - CmdOption.OPT_NOCONF ); - if(opt == CmdOption.OPT_NOCONF){ - this.useConfigPath = false; - this.configPath = null; - }else if(opt == CmdOption.OPT_CONFDIR){ - this.useConfigPath = true; - String path = this.optInfo.getStringArg(CmdOption.OPT_CONFDIR); - this.configPath = FileUtils.supplyFullPath(new File(path)); - }else{ - this.useConfigPath = true; - File path = ConfigFile.getImplicitConfigDirectory(); - this.configPath = path; - } - - return; - } - - /** - * フォント関係の設定。 - */ - private void applyFontSetting(){ - String fontName = this.optInfo.getStringArg(CmdOption.OPT_INITFONT); - Boolean useAntiAlias = - this.optInfo.getBooleanArg(CmdOption.OPT_ANTIALIAS); - Boolean useFractional = - this.optInfo.getBooleanArg(CmdOption.OPT_FRACTIONAL); - - if(fontName != null){ - Font font = Font.decode(fontName); - this.fontInfo = this.fontInfo.deriveFont(font); - } - - if(useAntiAlias != null){ - FontRenderContext context = this.fontInfo.getFontRenderContext(); - FontRenderContext newContext = - new FontRenderContext(context.getTransform(), - useAntiAlias.booleanValue(), - context.usesFractionalMetrics() ); - this.fontInfo = this.fontInfo.deriveRenderContext(newContext); - } - - if(useFractional != null){ - FontRenderContext context = this.fontInfo.getFontRenderContext(); - FontRenderContext newContext = - new FontRenderContext(context.getTransform(), - context.isAntiAliased(), - useFractional.booleanValue() ); - this.fontInfo = this.fontInfo.deriveRenderContext(newContext); - } - - return; - } - - /** - * ジオメトリ関係の設定。 - */ - private void applyGeometrySetting(){ - Integer ival; - - ival = this.optInfo.initialFrameWidth(); - if(ival != null) this.frameWidth = ival; - - ival = this.optInfo.initialFrameHeight(); - if(ival != null) this.frameHeight = ival; - - ival = this.optInfo.initialFrameXpos(); - if(ival != null) this.frameXpos = ival; - - ival = this.optInfo.initialFrameYpos(); - if(ival != null) this.frameYpos = ival; - - return; - } - - /** - * 設定格納ディレクトリを返す。 - * @return 設定格納ディレクトリ。 - */ - public File getConfigPath(){ - return this.configPath; - } - - /** - * 設定格納ディレクトリを設定する。 - * @param path 設定格納ディレクトリ - */ - public void setConfigPath(File path){ - this.configPath = path; - return; - } - - /** - * 設定格納ディレクトリを使うか否かを返す。 - * @return 使うならtrue - */ - public boolean useConfigPath(){ - return this.useConfigPath; - } - - /** - * 設定格納ディレクトリを使うか否か設定する。 - * @param need 使うならtrue - */ - public void setUseConfigPath(boolean need){ - this.useConfigPath = need; - return; - } - - /** - * 初期のフレーム幅を返す。 - * @return 初期のフレーム幅 - */ - public int initialFrameWidth(){ - return this.frameWidth; - } - - /** - * 初期のフレーム高を返す。 - * @return 初期のフレーム高 - */ - public int initialFrameHeight(){ - return this.frameHeight; - } - - /** - * 初期のフレーム位置のX座標を返す。 - * 特に指示されていなければInteger.MIN_VALUEを返す。 - * @return 初期のフレーム位置のX座標 - */ - public int initialFrameXpos(){ - return this.frameXpos; - } - - /** - * 初期のフレーム位置のY座標を返す。 - * 特に指示されていなければInteger.MIN_VALUEを返す。 - * @return 初期のフレーム位置のY座標 - */ - public int initialFrameYpos(){ - return this.frameYpos; - } - - /** - * フォント設定を返す。 - * @return フォント設定 - */ - public FontInfo getFontInfo(){ - return this.fontInfo; - } - - /** - * フォント設定を更新する。 - * @param fontInfo フォント設定 - */ - public void setFontInfo(FontInfo fontInfo){ - this.fontInfo = fontInfo; - return; - } - - /** - * プロクシ設定を返す。 - * @return プロクシ設定 - */ - public ProxyInfo getProxyInfo(){ - return this.proxyInfo; - } - - /** - * プロクシ設定を更新する。 - * @param proxyInfo プロクシ設定。nullならプロクシなしと解釈される。 - */ - public void setProxyInfo(ProxyInfo proxyInfo){ - if(proxyInfo == null) this.proxyInfo = ProxyInfo.DEFAULT; - else this.proxyInfo = proxyInfo; - return; - } - - /** - * 発言表示設定を返す。 - * @return 表示設定 - */ - public DialogPref getDialogPref(){ - return this.dialogPref; - } - - /** - * 発言表示設定を返す。 - * @param pref 表示設定 - */ - public void setDialogPref(DialogPref pref){ - if(pref == null) this.dialogPref = new DialogPref(); - else this.dialogPref = pref; - return; - } - - /** - * ネットワーク設定をロードする。 - */ - private void loadNetConfig(){ - if( ! useConfigPath() ) return; - - JsValue value = ConfigFile.loadJson(new File(NETCONFIG_FILE)); - if(value == null) return; - this.loadedNetConfig = value; - - if( ! (value instanceof JsObject) ) return; - JsObject root = (JsObject) value; - - value = root.getValue(HASH_PROXY); - if( ! (value instanceof JsObject) ) return; - JsObject proxy = (JsObject) value; - - ProxyInfo info = ProxyInfo.decodeJson(proxy); - - setProxyInfo(info); - - return; - } - - /** - * 会話表示設定をロードする。 - */ - private void loadTalkConfig(){ - if( ! useConfigPath() ) return; - - JsValue value = ConfigFile.loadJson(new File(TALKCONFIG_FILE)); - if(value == null) return; - this.loadedTalkConfig = value; - - if( ! (value instanceof JsObject) ) return; - JsObject root = (JsObject) value; - - value = root.getValue(HASH_FONT); - if(value instanceof JsObject){ - JsObject font = (JsObject) value; - FontInfo info = FontInfo.decodeJson(font); - setFontInfo(info); - applyFontSetting(); - } - - DialogPref pref = new DialogPref(); - JsBoolean boolValue; - value = root.getValue(HASH_USEBODYICON); - if(value instanceof JsBoolean){ - boolValue = (JsBoolean) value; - pref.setBodyImageSetting(boolValue.booleanValue()); - } - value = root.getValue(HASH_USEMONOTOMB); - if(value instanceof JsBoolean){ - boolValue = (JsBoolean) value; - pref.setMonoImageSetting(boolValue.booleanValue()); - } - value = root.getValue(HASH_SIMPLEMODE); - if(value instanceof JsBoolean){ - boolValue = (JsBoolean) value; - pref.setSimpleMode(boolValue.booleanValue()); - } - value = root.getValue(HASH_ALIGNBALOON); - if(value instanceof JsBoolean){ - boolValue = (JsBoolean) value; - pref.setAlignBalooonWidthSetting(boolValue.booleanValue()); - } - setDialogPref(pref); - - return; - } - - /** - * ネットワーク設定をセーブする。 - */ - private void saveNetConfig(){ - if( ! useConfigPath() ) return; - - JsObject root = new JsObject(); - JsObject proxy = ProxyInfo.buildJson(getProxyInfo()); - root.putValue(HASH_PROXY, proxy); - - if(this.loadedNetConfig != null){ - if(this.loadedNetConfig.equals(root)) return; - } - - ConfigFile.saveJson(new File(NETCONFIG_FILE), root); - - return; - } - - /** - * 会話表示設定をセーブする。 - */ - private void saveTalkConfig(){ - if( ! useConfigPath() ) return; - - JsObject root = new JsObject(); - - JsObject font = FontInfo.buildJson(getFontInfo()); - root.putValue(HASH_FONT, font); - - DialogPref pref = getDialogPref(); - JsPair useBodyIcon = - new JsPair(HASH_USEBODYICON, pref.useBodyImage()); - JsPair useMonoTomb = - new JsPair(HASH_USEMONOTOMB, pref.useMonoImage()); - JsPair isSimple = - new JsPair(HASH_SIMPLEMODE, pref.isSimpleMode()); - JsPair alignBaloon = - new JsPair(HASH_ALIGNBALOON, pref.alignBaloonWidth()); - root.putPair(useBodyIcon); - root.putPair(useMonoTomb); - root.putPair(isSimple); - root.putPair(alignBaloon); - - if(this.loadedTalkConfig != null){ - if(this.loadedTalkConfig.equals(root)) return; - } - - ConfigFile.saveJson(new File(TALKCONFIG_FILE), root); - - return; - } - - /** - * 各種設定を設定格納ディレクトリからロードする。 - */ - public void loadConfig(){ - loadNetConfig(); - loadTalkConfig(); - return; - } - - /** - * 各種設定を設定格納ディレクトリへセーブする。 - */ - public void saveConfig(){ - saveNetConfig(); - saveTalkConfig(); - return; - } - -} +/* + * application settings + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.io.File; +import jp.sourceforge.jindolf.json.JsBoolean; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsPair; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * アプリケーションの各種設定。 + */ +public class AppSetting{ + + private static final String NETCONFIG_FILE = "netconfig.json"; + private static final String HASH_PROXY = "proxy"; + + private static final String TALKCONFIG_FILE = "talkconfig.json"; + private static final String HASH_FONT = "font"; + private static final String HASH_USEBODYICON = "useBodyIcon"; + private static final String HASH_USEMONOTOMB = "useMonoTomb"; + private static final String HASH_SIMPLEMODE = "isSimpleMode"; + private static final String HASH_ALIGNBALOON = "alignBaloonWidth"; + + private OptionInfo optInfo; + + private boolean useConfigPath; + private File configPath; + + private FontInfo fontInfo = FontInfo.DEFAULT_FONTINFO; + + private int frameWidth = 800; + private int frameHeight = 600; + private int frameXpos = Integer.MIN_VALUE; + private int frameYpos = Integer.MIN_VALUE; + + private ProxyInfo proxyInfo = ProxyInfo.DEFAULT; + + private DialogPref dialogPref = new DialogPref(); + + private JsValue loadedNetConfig; + private JsValue loadedTalkConfig; + + /** + * コンストラクタ。 + */ + public AppSetting(){ + super(); + return; + } + + /** + * コマンドラインオプションからアプリ設定を展開する。 + * @param optionInfo オプション情報 + */ + public void applyOptionInfo(OptionInfo optionInfo){ + this.optInfo = optionInfo; + applyConfigPathSetting(); + applyFontSetting(); + applyGeometrySetting(); + return; + } + + /** + * 設定格納ディレクトリ関係の設定。 + */ + private void applyConfigPathSetting(){ + CmdOption opt = this.optInfo + .getExclusiveOption(CmdOption.OPT_CONFDIR, + CmdOption.OPT_NOCONF ); + if(opt == CmdOption.OPT_NOCONF){ + this.useConfigPath = false; + this.configPath = null; + }else if(opt == CmdOption.OPT_CONFDIR){ + this.useConfigPath = true; + String path = this.optInfo.getStringArg(CmdOption.OPT_CONFDIR); + this.configPath = FileUtils.supplyFullPath(new File(path)); + }else{ + this.useConfigPath = true; + File path = ConfigFile.getImplicitConfigDirectory(); + this.configPath = path; + } + + return; + } + + /** + * フォント関係の設定。 + */ + private void applyFontSetting(){ + String fontName = this.optInfo.getStringArg(CmdOption.OPT_INITFONT); + Boolean useAntiAlias = + this.optInfo.getBooleanArg(CmdOption.OPT_ANTIALIAS); + Boolean useFractional = + this.optInfo.getBooleanArg(CmdOption.OPT_FRACTIONAL); + + if(fontName != null){ + Font font = Font.decode(fontName); + this.fontInfo = this.fontInfo.deriveFont(font); + } + + if(useAntiAlias != null){ + FontRenderContext context = this.fontInfo.getFontRenderContext(); + FontRenderContext newContext = + new FontRenderContext(context.getTransform(), + useAntiAlias.booleanValue(), + context.usesFractionalMetrics() ); + this.fontInfo = this.fontInfo.deriveRenderContext(newContext); + } + + if(useFractional != null){ + FontRenderContext context = this.fontInfo.getFontRenderContext(); + FontRenderContext newContext = + new FontRenderContext(context.getTransform(), + context.isAntiAliased(), + useFractional.booleanValue() ); + this.fontInfo = this.fontInfo.deriveRenderContext(newContext); + } + + return; + } + + /** + * ジオメトリ関係の設定。 + */ + private void applyGeometrySetting(){ + Integer ival; + + ival = this.optInfo.initialFrameWidth(); + if(ival != null) this.frameWidth = ival; + + ival = this.optInfo.initialFrameHeight(); + if(ival != null) this.frameHeight = ival; + + ival = this.optInfo.initialFrameXpos(); + if(ival != null) this.frameXpos = ival; + + ival = this.optInfo.initialFrameYpos(); + if(ival != null) this.frameYpos = ival; + + return; + } + + /** + * 設定格納ディレクトリを返す。 + * @return 設定格納ディレクトリ。 + */ + public File getConfigPath(){ + return this.configPath; + } + + /** + * 設定格納ディレクトリを設定する。 + * @param path 設定格納ディレクトリ + */ + public void setConfigPath(File path){ + this.configPath = path; + return; + } + + /** + * 設定格納ディレクトリを使うか否かを返す。 + * @return 使うならtrue + */ + public boolean useConfigPath(){ + return this.useConfigPath; + } + + /** + * 設定格納ディレクトリを使うか否か設定する。 + * @param need 使うならtrue + */ + public void setUseConfigPath(boolean need){ + this.useConfigPath = need; + return; + } + + /** + * 初期のフレーム幅を返す。 + * @return 初期のフレーム幅 + */ + public int initialFrameWidth(){ + return this.frameWidth; + } + + /** + * 初期のフレーム高を返す。 + * @return 初期のフレーム高 + */ + public int initialFrameHeight(){ + return this.frameHeight; + } + + /** + * 初期のフレーム位置のX座標を返す。 + * 特に指示されていなければInteger.MIN_VALUEを返す。 + * @return 初期のフレーム位置のX座標 + */ + public int initialFrameXpos(){ + return this.frameXpos; + } + + /** + * 初期のフレーム位置のY座標を返す。 + * 特に指示されていなければInteger.MIN_VALUEを返す。 + * @return 初期のフレーム位置のY座標 + */ + public int initialFrameYpos(){ + return this.frameYpos; + } + + /** + * フォント設定を返す。 + * @return フォント設定 + */ + public FontInfo getFontInfo(){ + return this.fontInfo; + } + + /** + * フォント設定を更新する。 + * @param fontInfo フォント設定 + */ + public void setFontInfo(FontInfo fontInfo){ + this.fontInfo = fontInfo; + return; + } + + /** + * プロクシ設定を返す。 + * @return プロクシ設定 + */ + public ProxyInfo getProxyInfo(){ + return this.proxyInfo; + } + + /** + * プロクシ設定を更新する。 + * @param proxyInfo プロクシ設定。nullならプロクシなしと解釈される。 + */ + public void setProxyInfo(ProxyInfo proxyInfo){ + if(proxyInfo == null) this.proxyInfo = ProxyInfo.DEFAULT; + else this.proxyInfo = proxyInfo; + return; + } + + /** + * 発言表示設定を返す。 + * @return 表示設定 + */ + public DialogPref getDialogPref(){ + return this.dialogPref; + } + + /** + * 発言表示設定を返す。 + * @param pref 表示設定 + */ + public void setDialogPref(DialogPref pref){ + if(pref == null) this.dialogPref = new DialogPref(); + else this.dialogPref = pref; + return; + } + + /** + * ネットワーク設定をロードする。 + */ + private void loadNetConfig(){ + if( ! useConfigPath() ) return; + + JsValue value = ConfigFile.loadJson(new File(NETCONFIG_FILE)); + if(value == null) return; + this.loadedNetConfig = value; + + if( ! (value instanceof JsObject) ) return; + JsObject root = (JsObject) value; + + value = root.getValue(HASH_PROXY); + if( ! (value instanceof JsObject) ) return; + JsObject proxy = (JsObject) value; + + ProxyInfo info = ProxyInfo.decodeJson(proxy); + + setProxyInfo(info); + + return; + } + + /** + * 会話表示設定をロードする。 + */ + private void loadTalkConfig(){ + if( ! useConfigPath() ) return; + + JsValue value = ConfigFile.loadJson(new File(TALKCONFIG_FILE)); + if(value == null) return; + this.loadedTalkConfig = value; + + if( ! (value instanceof JsObject) ) return; + JsObject root = (JsObject) value; + + value = root.getValue(HASH_FONT); + if(value instanceof JsObject){ + JsObject font = (JsObject) value; + FontInfo info = FontInfo.decodeJson(font); + setFontInfo(info); + applyFontSetting(); + } + + DialogPref pref = new DialogPref(); + JsBoolean boolValue; + value = root.getValue(HASH_USEBODYICON); + if(value instanceof JsBoolean){ + boolValue = (JsBoolean) value; + pref.setBodyImageSetting(boolValue.booleanValue()); + } + value = root.getValue(HASH_USEMONOTOMB); + if(value instanceof JsBoolean){ + boolValue = (JsBoolean) value; + pref.setMonoImageSetting(boolValue.booleanValue()); + } + value = root.getValue(HASH_SIMPLEMODE); + if(value instanceof JsBoolean){ + boolValue = (JsBoolean) value; + pref.setSimpleMode(boolValue.booleanValue()); + } + value = root.getValue(HASH_ALIGNBALOON); + if(value instanceof JsBoolean){ + boolValue = (JsBoolean) value; + pref.setAlignBalooonWidthSetting(boolValue.booleanValue()); + } + setDialogPref(pref); + + return; + } + + /** + * ネットワーク設定をセーブする。 + */ + private void saveNetConfig(){ + if( ! useConfigPath() ) return; + + JsObject root = new JsObject(); + JsObject proxy = ProxyInfo.buildJson(getProxyInfo()); + root.putValue(HASH_PROXY, proxy); + + if(this.loadedNetConfig != null){ + if(this.loadedNetConfig.equals(root)) return; + } + + ConfigFile.saveJson(new File(NETCONFIG_FILE), root); + + return; + } + + /** + * 会話表示設定をセーブする。 + */ + private void saveTalkConfig(){ + if( ! useConfigPath() ) return; + + JsObject root = new JsObject(); + + JsObject font = FontInfo.buildJson(getFontInfo()); + root.putValue(HASH_FONT, font); + + DialogPref pref = getDialogPref(); + JsPair useBodyIcon = + new JsPair(HASH_USEBODYICON, pref.useBodyImage()); + JsPair useMonoTomb = + new JsPair(HASH_USEMONOTOMB, pref.useMonoImage()); + JsPair isSimple = + new JsPair(HASH_SIMPLEMODE, pref.isSimpleMode()); + JsPair alignBaloon = + new JsPair(HASH_ALIGNBALOON, pref.alignBaloonWidth()); + root.putPair(useBodyIcon); + root.putPair(useMonoTomb); + root.putPair(isSimple); + root.putPair(alignBaloon); + + if(this.loadedTalkConfig != null){ + if(this.loadedTalkConfig.equals(root)) return; + } + + ConfigFile.saveJson(new File(TALKCONFIG_FILE), root); + + return; + } + + /** + * 各種設定を設定格納ディレクトリからロードする。 + */ + public void loadConfig(){ + loadNetConfig(); + loadTalkConfig(); + return; + } + + /** + * 各種設定を設定格納ディレクトリへセーブする。 + */ + public void saveConfig(){ + saveNetConfig(); + saveTalkConfig(); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Avatar.java b/src/main/java/jp/sourceforge/jindolf/Avatar.java index bd1fe5a..0d483f2 100644 --- a/src/main/java/jp/sourceforge/jindolf/Avatar.java +++ b/src/main/java/jp/sourceforge/jindolf/Avatar.java @@ -1,314 +1,314 @@ -/* - * characters in village - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.RandomAccess; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.ParserConfigurationException; -import jp.sourceforge.jindolf.corelib.PreDefAvatar; -import org.xml.sax.SAXException; - -/** - * Avatar またの名をキャラクター。 - */ -public class Avatar implements Comparable { - - /** ゲルト。 */ - public static final Avatar AVATAR_GERD; - - private static final List AVATAR_LIST; - private static final Map AVATAR_MAP; - - private static final Pattern AVATAR_PATTERN; - - static{ - List predefs; - try{ - DocumentBuilder builder = XmlUtils.createDocumentBuilder(); - predefs = PreDefAvatar.buildPreDefAvatarList(builder); - }catch(IOException e){ - throw new ExceptionInInitializerError(e); - }catch(ParserConfigurationException e){ - throw new ExceptionInInitializerError(e); - }catch(SAXException e){ - throw new ExceptionInInitializerError(e); - }catch(URISyntaxException e){ - throw new ExceptionInInitializerError(e); - } - - AVATAR_LIST = buildAvatarList(predefs); - - AVATAR_MAP = new HashMap(); - for(Avatar avatar : AVATAR_LIST){ - String fullName = avatar.getFullName(); - AVATAR_MAP.put(fullName, avatar); - } - - StringBuilder avatarGroupRegex = new StringBuilder(); - for(Avatar avatar : AVATAR_LIST){ - String fullName = avatar.getFullName(); - if(avatarGroupRegex.length() > 0){ - avatarGroupRegex.append('|'); - } - avatarGroupRegex.append('(') - .append(Pattern.quote(fullName)) - .append(')'); - } - AVATAR_PATTERN = Pattern.compile(avatarGroupRegex.toString()); - - AVATAR_GERD = getPredefinedAvatar("楽天家 ゲルト"); - - assert AVATAR_LIST instanceof RandomAccess; - assert AVATAR_GERD != null; - } - - - private final String name; - private final String jobTitle; - private final String fullName; - private final int idNum; - private final String identifier; - private final int hashNum; - - - /** - * Avatarを生成する。 - * @param name 名前 - * @param jobTitle 職業名 - * @param idNum 通し番号 - * @param identifier 識別文字列 - */ - private Avatar(String name, - String jobTitle, - int idNum, - String identifier ){ - this.name = name.intern(); - this.jobTitle = jobTitle.intern(); - this.idNum = idNum; - this.identifier = identifier.intern(); - - this.fullName = (this.jobTitle + " " + this.name).intern(); - - this.hashNum = this.fullName.hashCode() ^ this.idNum; - - return; - } - - /** - * Avatarを生成する。 - * @param fullName フルネーム - */ - // TODO 当面は呼ばれないはず。Z国とか向け。 - public Avatar(String fullName){ - this.fullName = fullName.intern(); - this.idNum = -1; - - String[] tokens = this.fullName.split("\\p{Blank}+", 2); - if(tokens.length == 1){ - this.jobTitle = null; - this.name = this.fullName; - }else if(tokens.length == 2){ - this.jobTitle = tokens[0].intern(); - this.name = tokens[1].intern(); - }else{ - this.jobTitle = null; - this.name = null; - assert false; - } - - this.identifier = "???".intern(); - - this.hashNum = this.fullName.hashCode() ^ this.idNum; - - return; - } - - - /** - * 定義済みAvatar群の生成。 - * @param predefs 定義済みAvatar元データ群 - * @return ソートされた定義済みAvatarのリスト - */ - private static List buildAvatarList(List predefs){ - List result = new ArrayList(predefs.size()); - - for(PreDefAvatar preDefAvatar : predefs){ - String shortName = preDefAvatar.getShortName(); - String jobTitle = preDefAvatar.getJobTitle(); - int serialNo = preDefAvatar.getSerialNo(); - String avatarId = preDefAvatar.getAvatarId(); - Avatar avatar = new Avatar(shortName, - jobTitle, - serialNo, - avatarId ); - result.add(avatar); - } - - Collections.sort(result); - result = Collections.unmodifiableList(result); - - return result; - } - - /** - * 定義済みAvatar群のリストを返す。 - * @return Avatarのリスト - */ - public static List getPredefinedAvatarList(){ - return AVATAR_LIST; - } - - /** - * 定義済みAvatarを返す。 - * @param fullName Avatarのフルネーム - * @return Avatar。フルネームが一致するAvatarが無ければnull - */ - // TODO 20キャラ程度ならListをなめる方が早いか? - public static Avatar getPredefinedAvatar(String fullName){ - return AVATAR_MAP.get(fullName); - } - - /** - * 定義済みAvatarを返す。 - * @param fullName Avatarのフルネーム - * @return Avatar。フルネームが一致するAvatarが無ければnull - */ - public static Avatar getPredefinedAvatar(CharSequence fullName){ - for(Avatar avatar : AVATAR_LIST){ - String avatarName = avatar.getFullName(); - if(avatarName.contentEquals(fullName)){ - return avatar; - } - } - return null; - } - - /** - * 定義済みAvatar名に一致しないか調べる。 - * @param matcher マッチャ - * @return 一致したAvatar。一致しなければnull。 - */ - public static Avatar lookingAtAvatar(Matcher matcher){ - matcher.usePattern(AVATAR_PATTERN); - - if( ! matcher.lookingAt() ) return null; - int groupCt = matcher.groupCount(); - for(int group = 1; group <= groupCt; group++){ - if(matcher.start(group) >= 0){ - Avatar avatar = AVATAR_LIST.get(group - 1); - return avatar; - } - } - - return null; - } - - /** - * フルネームを取得する。 - * @return フルネーム - */ - public String getFullName(){ - return this.fullName; - } - - /** - * 職業名を取得する。 - * @return 職業名 - */ - public String getJobTitle(){ - return this.jobTitle; - } - - /** - * 通常名を取得する。 - * @return 通常名 - */ - public String getName(){ - return this.name; - } - - /** - * 通し番号を返す。 - * @return 通し番号 - */ - public int getIdNum(){ - return this.idNum; - } - - /** - * 識別文字列を返す。 - * @return 識別文字列 - */ - public String getIdentifier(){ - return this.identifier; - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(this == obj){ - return true; - } - - if( ! (obj instanceof Avatar) ){ - return false; - } - Avatar other = (Avatar) obj; - - boolean nameMatch = this.fullName.equals(other.fullName); - boolean idMatch = this.idNum == other.idNum; - - if(nameMatch && idMatch) return true; - - return false; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.hashNum; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return getFullName(); - } - - /** - * {@inheritDoc} - * 通し番号順に順序づける。 - * @param avatar {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(Avatar avatar){ - if(avatar == null) return +1; - return this.idNum - avatar.idNum; - } - -} +/* + * characters in village + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.RandomAccess; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; +import jp.sourceforge.jindolf.corelib.PreDefAvatar; +import org.xml.sax.SAXException; + +/** + * Avatar またの名をキャラクター。 + */ +public class Avatar implements Comparable { + + /** ゲルト。 */ + public static final Avatar AVATAR_GERD; + + private static final List AVATAR_LIST; + private static final Map AVATAR_MAP; + + private static final Pattern AVATAR_PATTERN; + + static{ + List predefs; + try{ + DocumentBuilder builder = XmlUtils.createDocumentBuilder(); + predefs = PreDefAvatar.buildPreDefAvatarList(builder); + }catch(IOException e){ + throw new ExceptionInInitializerError(e); + }catch(ParserConfigurationException e){ + throw new ExceptionInInitializerError(e); + }catch(SAXException e){ + throw new ExceptionInInitializerError(e); + }catch(URISyntaxException e){ + throw new ExceptionInInitializerError(e); + } + + AVATAR_LIST = buildAvatarList(predefs); + + AVATAR_MAP = new HashMap(); + for(Avatar avatar : AVATAR_LIST){ + String fullName = avatar.getFullName(); + AVATAR_MAP.put(fullName, avatar); + } + + StringBuilder avatarGroupRegex = new StringBuilder(); + for(Avatar avatar : AVATAR_LIST){ + String fullName = avatar.getFullName(); + if(avatarGroupRegex.length() > 0){ + avatarGroupRegex.append('|'); + } + avatarGroupRegex.append('(') + .append(Pattern.quote(fullName)) + .append(')'); + } + AVATAR_PATTERN = Pattern.compile(avatarGroupRegex.toString()); + + AVATAR_GERD = getPredefinedAvatar("楽天家 ゲルト"); + + assert AVATAR_LIST instanceof RandomAccess; + assert AVATAR_GERD != null; + } + + + private final String name; + private final String jobTitle; + private final String fullName; + private final int idNum; + private final String identifier; + private final int hashNum; + + + /** + * Avatarを生成する。 + * @param name 名前 + * @param jobTitle 職業名 + * @param idNum 通し番号 + * @param identifier 識別文字列 + */ + private Avatar(String name, + String jobTitle, + int idNum, + String identifier ){ + this.name = name.intern(); + this.jobTitle = jobTitle.intern(); + this.idNum = idNum; + this.identifier = identifier.intern(); + + this.fullName = (this.jobTitle + " " + this.name).intern(); + + this.hashNum = this.fullName.hashCode() ^ this.idNum; + + return; + } + + /** + * Avatarを生成する。 + * @param fullName フルネーム + */ + // TODO 当面は呼ばれないはず。Z国とか向け。 + public Avatar(String fullName){ + this.fullName = fullName.intern(); + this.idNum = -1; + + String[] tokens = this.fullName.split("\\p{Blank}+", 2); + if(tokens.length == 1){ + this.jobTitle = null; + this.name = this.fullName; + }else if(tokens.length == 2){ + this.jobTitle = tokens[0].intern(); + this.name = tokens[1].intern(); + }else{ + this.jobTitle = null; + this.name = null; + assert false; + } + + this.identifier = "???".intern(); + + this.hashNum = this.fullName.hashCode() ^ this.idNum; + + return; + } + + + /** + * 定義済みAvatar群の生成。 + * @param predefs 定義済みAvatar元データ群 + * @return ソートされた定義済みAvatarのリスト + */ + private static List buildAvatarList(List predefs){ + List result = new ArrayList(predefs.size()); + + for(PreDefAvatar preDefAvatar : predefs){ + String shortName = preDefAvatar.getShortName(); + String jobTitle = preDefAvatar.getJobTitle(); + int serialNo = preDefAvatar.getSerialNo(); + String avatarId = preDefAvatar.getAvatarId(); + Avatar avatar = new Avatar(shortName, + jobTitle, + serialNo, + avatarId ); + result.add(avatar); + } + + Collections.sort(result); + result = Collections.unmodifiableList(result); + + return result; + } + + /** + * 定義済みAvatar群のリストを返す。 + * @return Avatarのリスト + */ + public static List getPredefinedAvatarList(){ + return AVATAR_LIST; + } + + /** + * 定義済みAvatarを返す。 + * @param fullName Avatarのフルネーム + * @return Avatar。フルネームが一致するAvatarが無ければnull + */ + // TODO 20キャラ程度ならListをなめる方が早いか? + public static Avatar getPredefinedAvatar(String fullName){ + return AVATAR_MAP.get(fullName); + } + + /** + * 定義済みAvatarを返す。 + * @param fullName Avatarのフルネーム + * @return Avatar。フルネームが一致するAvatarが無ければnull + */ + public static Avatar getPredefinedAvatar(CharSequence fullName){ + for(Avatar avatar : AVATAR_LIST){ + String avatarName = avatar.getFullName(); + if(avatarName.contentEquals(fullName)){ + return avatar; + } + } + return null; + } + + /** + * 定義済みAvatar名に一致しないか調べる。 + * @param matcher マッチャ + * @return 一致したAvatar。一致しなければnull。 + */ + public static Avatar lookingAtAvatar(Matcher matcher){ + matcher.usePattern(AVATAR_PATTERN); + + if( ! matcher.lookingAt() ) return null; + int groupCt = matcher.groupCount(); + for(int group = 1; group <= groupCt; group++){ + if(matcher.start(group) >= 0){ + Avatar avatar = AVATAR_LIST.get(group - 1); + return avatar; + } + } + + return null; + } + + /** + * フルネームを取得する。 + * @return フルネーム + */ + public String getFullName(){ + return this.fullName; + } + + /** + * 職業名を取得する。 + * @return 職業名 + */ + public String getJobTitle(){ + return this.jobTitle; + } + + /** + * 通常名を取得する。 + * @return 通常名 + */ + public String getName(){ + return this.name; + } + + /** + * 通し番号を返す。 + * @return 通し番号 + */ + public int getIdNum(){ + return this.idNum; + } + + /** + * 識別文字列を返す。 + * @return 識別文字列 + */ + public String getIdentifier(){ + return this.identifier; + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(this == obj){ + return true; + } + + if( ! (obj instanceof Avatar) ){ + return false; + } + Avatar other = (Avatar) obj; + + boolean nameMatch = this.fullName.equals(other.fullName); + boolean idMatch = this.idNum == other.idNum; + + if(nameMatch && idMatch) return true; + + return false; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.hashNum; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return getFullName(); + } + + /** + * {@inheritDoc} + * 通し番号順に順序づける。 + * @param avatar {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(Avatar avatar){ + if(avatar == null) return +1; + return this.idNum - avatar.idNum; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/BalloonBorder.java b/src/main/java/jp/sourceforge/jindolf/BalloonBorder.java index 7668827..cd7465f 100644 --- a/src/main/java/jp/sourceforge/jindolf/BalloonBorder.java +++ b/src/main/java/jp/sourceforge/jindolf/BalloonBorder.java @@ -1,177 +1,177 @@ -/* - * baloon border - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.RenderingHints; -import javax.swing.JComponent; -import javax.swing.border.Border; - -/** - * フキダシ風Border。 - */ -public class BalloonBorder implements Border{ - - private static final int RADIUS = 5; - - - /** - * コンストラクタ。 - */ - public BalloonBorder(){ - super(); - return; - } - - - /** - * 隙間が透明なフキダシ装飾を任意のコンポーネントに施す。 - * @param inner 装飾対象のコンポーネント - * @return 装飾されたコンポーネント - */ - public static JComponent decorateTransparentBorder(JComponent inner){ - JComponent result = new TransparentContainer(inner); - - Border border = new BalloonBorder(); - result.setBorder(border); - - return result; - } - - /** - * {@inheritDoc} - * @param comp {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Insets getBorderInsets(Component comp){ - Insets insets = new Insets(RADIUS, RADIUS, RADIUS, RADIUS); - return insets; - } - - /** - * {@inheritDoc} - * 必ずfalseを返す(このBorderは透明)。 - * @return {@inheritDoc} - */ - @Override - public boolean isBorderOpaque(){ - return false; - } - - /** - * {@inheritDoc} - * @param comp {@inheritDoc} - * @param g {@inheritDoc} - * @param x {@inheritDoc} - * @param y {@inheritDoc} - * @param width {@inheritDoc} - * @param height {@inheritDoc} - */ - @Override - public void paintBorder(Component comp, - Graphics g, - int x, int y, - int width, int height ){ - final int diameter = RADIUS * 2; - final int innerWidth = width - diameter; - final int innerHeight = height - diameter; - - Graphics2D g2d = (Graphics2D) g; - - Color bgColor = comp.getBackground(); - g2d.setColor(bgColor); - - Object antiAliaseHint = - g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); - - g2d.fillRect(x + RADIUS, y, - innerWidth, RADIUS); - g2d.fillRect(x, y + RADIUS, - RADIUS, innerHeight); - g2d.fillRect(x + RADIUS + innerWidth, y + RADIUS, - RADIUS, innerHeight); - g2d.fillRect(x + RADIUS, y + RADIUS + innerHeight, - innerWidth, RADIUS); - - int right = 90; // 90 degree right angle - - g2d.fillArc(x + innerWidth, y, - diameter, diameter, right * 0, right); - g2d.fillArc(x, y, - diameter, diameter, right * 1, right); - g2d.fillArc(x, y + innerHeight, - diameter, diameter, right * 2, right); - g2d.fillArc(x + innerWidth, y + innerHeight, - diameter, diameter, right * 3, right); - - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAliaseHint); - - return; - } - - /** - * 透明コンテナ。 - * 1つの子を持ち、背景色操作を委譲する。 - * つまりこのコンテナにBorderを設定すると子の背景色が反映される。 - */ - @SuppressWarnings("serial") - private static class TransparentContainer extends JComponent{ - - private final JComponent inner; - - /** - * コンストラクタ。 - * @param inner 内部コンポーネント - */ - public TransparentContainer(JComponent inner){ - super(); - - this.inner = inner; - - setOpaque(false); - - LayoutManager layout = new BorderLayout(); - setLayout(layout); - add(this.inner, BorderLayout.CENTER); - - return; - } - - /** - * {@inheritDoc} - * 子の背景色を返す。 - * @return {@inheritDoc} - */ - @Override - public Color getBackground(){ - Color bg = this.inner.getBackground(); - return bg; - } - - /** - * {@inheritDoc} - * 背景色指定をフックし、子の背景色を指定する。 - * @param bg {@inheritDoc} - */ - @Override - public void setBackground(Color bg){ - this.inner.setBackground(bg); - return; - } - } - -} +/* + * baloon border + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.RenderingHints; +import javax.swing.JComponent; +import javax.swing.border.Border; + +/** + * フキダシ風Border。 + */ +public class BalloonBorder implements Border{ + + private static final int RADIUS = 5; + + + /** + * コンストラクタ。 + */ + public BalloonBorder(){ + super(); + return; + } + + + /** + * 隙間が透明なフキダシ装飾を任意のコンポーネントに施す。 + * @param inner 装飾対象のコンポーネント + * @return 装飾されたコンポーネント + */ + public static JComponent decorateTransparentBorder(JComponent inner){ + JComponent result = new TransparentContainer(inner); + + Border border = new BalloonBorder(); + result.setBorder(border); + + return result; + } + + /** + * {@inheritDoc} + * @param comp {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Insets getBorderInsets(Component comp){ + Insets insets = new Insets(RADIUS, RADIUS, RADIUS, RADIUS); + return insets; + } + + /** + * {@inheritDoc} + * 必ずfalseを返す(このBorderは透明)。 + * @return {@inheritDoc} + */ + @Override + public boolean isBorderOpaque(){ + return false; + } + + /** + * {@inheritDoc} + * @param comp {@inheritDoc} + * @param g {@inheritDoc} + * @param x {@inheritDoc} + * @param y {@inheritDoc} + * @param width {@inheritDoc} + * @param height {@inheritDoc} + */ + @Override + public void paintBorder(Component comp, + Graphics g, + int x, int y, + int width, int height ){ + final int diameter = RADIUS * 2; + final int innerWidth = width - diameter; + final int innerHeight = height - diameter; + + Graphics2D g2d = (Graphics2D) g; + + Color bgColor = comp.getBackground(); + g2d.setColor(bgColor); + + Object antiAliaseHint = + g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON ); + + g2d.fillRect(x + RADIUS, y, + innerWidth, RADIUS); + g2d.fillRect(x, y + RADIUS, + RADIUS, innerHeight); + g2d.fillRect(x + RADIUS + innerWidth, y + RADIUS, + RADIUS, innerHeight); + g2d.fillRect(x + RADIUS, y + RADIUS + innerHeight, + innerWidth, RADIUS); + + int right = 90; // 90 degree right angle + + g2d.fillArc(x + innerWidth, y, + diameter, diameter, right * 0, right); + g2d.fillArc(x, y, + diameter, diameter, right * 1, right); + g2d.fillArc(x, y + innerHeight, + diameter, diameter, right * 2, right); + g2d.fillArc(x + innerWidth, y + innerHeight, + diameter, diameter, right * 3, right); + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAliaseHint); + + return; + } + + /** + * 透明コンテナ。 + * 1つの子を持ち、背景色操作を委譲する。 + * つまりこのコンテナにBorderを設定すると子の背景色が反映される。 + */ + @SuppressWarnings("serial") + private static class TransparentContainer extends JComponent{ + + private final JComponent inner; + + /** + * コンストラクタ。 + * @param inner 内部コンポーネント + */ + public TransparentContainer(JComponent inner){ + super(); + + this.inner = inner; + + setOpaque(false); + + LayoutManager layout = new BorderLayout(); + setLayout(layout); + add(this.inner, BorderLayout.CENTER); + + return; + } + + /** + * {@inheritDoc} + * 子の背景色を返す。 + * @return {@inheritDoc} + */ + @Override + public Color getBackground(){ + Color bg = this.inner.getBackground(); + return bg; + } + + /** + * {@inheritDoc} + * 背景色指定をフックし、子の背景色を指定する。 + * @param bg {@inheritDoc} + */ + @Override + public void setBackground(Color bg){ + this.inner.setBackground(bg); + return; + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ClipboardAction.java b/src/main/java/jp/sourceforge/jindolf/ClipboardAction.java index 4df16e5..b1e23f9 100644 --- a/src/main/java/jp/sourceforge/jindolf/ClipboardAction.java +++ b/src/main/java/jp/sourceforge/jindolf/ClipboardAction.java @@ -1,133 +1,133 @@ -/* - * クリップボード操作用Action - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.awt.event.ActionEvent; -import javax.swing.Action; -import javax.swing.text.JTextComponent; -import javax.swing.text.TextAction; - -/** - * テキストコンポーネント-クリップボード間操作用にカスタム化したAction。 - */ -@SuppressWarnings("serial") -public class ClipboardAction extends TextAction{ - - /** アクション{@value}。 */ - public static final String ACTION_CUT = "ACTION_CUT"; - /** アクション{@value}。 */ - public static final String ACTION_COPY = "ACTION_COPY"; - /** アクション{@value}。 */ - public static final String ACTION_PASTE = "ACTION_PASTE"; - /** アクション{@value}。 */ - public static final String ACTION_SELALL = "ACTION_SELALL"; - - - /** - * コンストラクタ。 - * @param name ポップアップメニュー名 - * @param command アクションコマンド名 - */ - protected ClipboardAction(String name, String command){ - super(name); - setActionCommand(command); - return; - } - - - /** - * 文字列をクリップボードにコピーする。 - * @param data 文字列 - */ - public static void copyToClipboard(CharSequence data){ - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Clipboard clipboard = toolkit.getSystemClipboard(); - StringSelection selection = new StringSelection(data.toString()); - clipboard.setContents(selection, selection); - return; - } - - /** - * カット用Actionの生成。 - * @return カット用Action - */ - public static ClipboardAction cutAction(){ - return new ClipboardAction("選択範囲をカット", ACTION_CUT); - } - - /** - * コピー用Actionの生成。 - * @return コピー用Action - */ - public static ClipboardAction copyAction(){ - return new ClipboardAction("選択範囲をコピー", ACTION_COPY); - } - - /** - * ペースト用Actionの生成。 - * @return ペースト用Action - */ - public static ClipboardAction pasteAction(){ - return new ClipboardAction("ペースト", ACTION_PASTE); - } - - /** - * 全選択用Actionの生成。 - * @return 全選択用Action - */ - public static ClipboardAction selectallAction(){ - return new ClipboardAction("すべて選択", ACTION_SELALL); - } - - /** - * アクションコマンド名を設定する。 - * @param actionCommand アクションコマンド名 - */ - private void setActionCommand(String actionCommand){ - putValue(Action.ACTION_COMMAND_KEY, actionCommand); - return; - } - - /** - * アクションコマンド名を取得する。 - * @return アクションコマンド名 - */ - protected String getActionCommand(){ - Object value = getValue(Action.ACTION_COMMAND_KEY); - if( ! (value instanceof String) ) return null; - - String command = (String) value; - - return command; - } - - /** - * {@inheritDoc} - * アクションの受信によってクリップボード操作を行う。 - * @param event {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - JTextComponent textComp = getTextComponent(event); - if(textComp == null) return; - - String command = getActionCommand(); - - if (ACTION_CUT .equals(command)) textComp.cut(); - else if(ACTION_COPY .equals(command)) textComp.copy(); - else if(ACTION_PASTE .equals(command)) textComp.paste(); - else if(ACTION_SELALL.equals(command)) textComp.selectAll(); - - return; - } - - // TODO 文字列以外の物をペーストしたときに無視したい。 -} +/* + * クリップボード操作用Action + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import javax.swing.Action; +import javax.swing.text.JTextComponent; +import javax.swing.text.TextAction; + +/** + * テキストコンポーネント-クリップボード間操作用にカスタム化したAction。 + */ +@SuppressWarnings("serial") +public class ClipboardAction extends TextAction{ + + /** アクション{@value}。 */ + public static final String ACTION_CUT = "ACTION_CUT"; + /** アクション{@value}。 */ + public static final String ACTION_COPY = "ACTION_COPY"; + /** アクション{@value}。 */ + public static final String ACTION_PASTE = "ACTION_PASTE"; + /** アクション{@value}。 */ + public static final String ACTION_SELALL = "ACTION_SELALL"; + + + /** + * コンストラクタ。 + * @param name ポップアップメニュー名 + * @param command アクションコマンド名 + */ + protected ClipboardAction(String name, String command){ + super(name); + setActionCommand(command); + return; + } + + + /** + * 文字列をクリップボードにコピーする。 + * @param data 文字列 + */ + public static void copyToClipboard(CharSequence data){ + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Clipboard clipboard = toolkit.getSystemClipboard(); + StringSelection selection = new StringSelection(data.toString()); + clipboard.setContents(selection, selection); + return; + } + + /** + * カット用Actionの生成。 + * @return カット用Action + */ + public static ClipboardAction cutAction(){ + return new ClipboardAction("選択範囲をカット", ACTION_CUT); + } + + /** + * コピー用Actionの生成。 + * @return コピー用Action + */ + public static ClipboardAction copyAction(){ + return new ClipboardAction("選択範囲をコピー", ACTION_COPY); + } + + /** + * ペースト用Actionの生成。 + * @return ペースト用Action + */ + public static ClipboardAction pasteAction(){ + return new ClipboardAction("ペースト", ACTION_PASTE); + } + + /** + * 全選択用Actionの生成。 + * @return 全選択用Action + */ + public static ClipboardAction selectallAction(){ + return new ClipboardAction("すべて選択", ACTION_SELALL); + } + + /** + * アクションコマンド名を設定する。 + * @param actionCommand アクションコマンド名 + */ + private void setActionCommand(String actionCommand){ + putValue(Action.ACTION_COMMAND_KEY, actionCommand); + return; + } + + /** + * アクションコマンド名を取得する。 + * @return アクションコマンド名 + */ + protected String getActionCommand(){ + Object value = getValue(Action.ACTION_COMMAND_KEY); + if( ! (value instanceof String) ) return null; + + String command = (String) value; + + return command; + } + + /** + * {@inheritDoc} + * アクションの受信によってクリップボード操作を行う。 + * @param event {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + JTextComponent textComp = getTextComponent(event); + if(textComp == null) return; + + String command = getActionCommand(); + + if (ACTION_CUT .equals(command)) textComp.cut(); + else if(ACTION_COPY .equals(command)) textComp.copy(); + else if(ACTION_PASTE .equals(command)) textComp.paste(); + else if(ACTION_SELALL.equals(command)) textComp.selectAll(); + + return; + } + + // TODO 文字列以外の物をペーストしたときに無視したい。 +} diff --git a/src/main/java/jp/sourceforge/jindolf/CmdOption.java b/src/main/java/jp/sourceforge/jindolf/CmdOption.java index c6b3f0b..933d142 100644 --- a/src/main/java/jp/sourceforge/jindolf/CmdOption.java +++ b/src/main/java/jp/sourceforge/jindolf/CmdOption.java @@ -1,168 +1,168 @@ -/* - * command line options - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -/** - * コマンドラインオプションの列挙。 - */ -public enum CmdOption{ - - /** ヘルプ。 */ - OPT_HELP("help", "h", "-help", "?"), - /** 版数表示。 */ - OPT_VERSION("version"), - /** UI文字制御。 */ - OPT_BOLDMETAL("boldMetal"), - /** スプラッシュ制御。 */ - OPT_NOSPLASH("nosplash"), - /** ウィンドウ位置指定。 */ - OPT_GEOMETRY("geometry"), - /** 実行環境出力。 */ - OPT_VMINFO("vminfo"), - /** コンソールログ。 */ - OPT_CONSOLELOG("consolelog"), - /** フォント指定。 */ - OPT_INITFONT("initfont"), - /** アンチエイリアス。 */ - OPT_ANTIALIAS("antialias"), - /** サブピクセル制御。 */ - OPT_FRACTIONAL("fractional"), - /** 設定格納ディレクトリ指定。 */ - OPT_CONFDIR("confdir"), - /** 設定格納ディレクトリ不使用。 */ - OPT_NOCONF("noconfdir"), - ; - - - private final List nameList = new LinkedList(); - - - /** - * コンストラクタ。 - * @param names 頭のハイフンを除いたオプション名の一覧 - */ - private CmdOption(CharSequence ... names){ - if(names == null) throw new NullPointerException(); - if(names.length <= 0) throw new IllegalArgumentException(); - - for(CharSequence name : names){ - if(name == null) throw new NullPointerException(); - this.nameList.add(name.toString().intern()); - } - - return; - } - - - /** - * オプション名に合致するEnumを返す。 - * @param seq ハイフン付きオプション名 - * @return 合致したEnum。どれとも合致しなければnull - */ - public static CmdOption parseCmdOption(CharSequence seq){ - for(CmdOption option : values()){ - if(option.matchHyphened(seq)) return option; - } - return null; - } - - /** - * 単体で意味をなすオプションか判定する。 - * @param option オプション - * @return 単体で意味をなすならtrue - */ - public static boolean isIndepOption(CmdOption option){ - switch(option){ - case OPT_HELP: - case OPT_VERSION: - case OPT_VMINFO: - case OPT_BOLDMETAL: - case OPT_NOSPLASH: - case OPT_CONSOLELOG: - case OPT_NOCONF: - return true; - default: - break; - } - - return false; - } - - /** - * 真偽指定を一つ必要とするオプションか判定する。 - * @param option オプション - * @return 真偽指定を一つ必要とするオプションならtrue - */ - public static boolean isBooleanOption(CmdOption option){ - switch(option){ - case OPT_ANTIALIAS: - case OPT_FRACTIONAL: - return true; - default: - break; - } - - return false; - } - - /** - * ヘルプメッセージ(オプションの説明)を返す。 - * @return ヘルプメッセージ - */ - public static CharSequence getHelpText(){ - CharSequence helpText; - - try{ - helpText = Jindolf.loadResourceText("resources/help.txt"); - }catch(IOException e){ - helpText = ""; - } - - return helpText; - } - - /** - * 頭のハイフンを除いたオプション名を返す。 - * オプション名が複数指定されていた場合は最初のオプション名 - * @return オプション名 - */ - @Override - public String toString(){ - return this.nameList.get(0); - } - - /** - * 頭のハイフンが付いたオプション名を返す。 - * オプション名が複数指定されていた場合は最初のオプション名 - * @return オプション名 - */ - public String toHyphened(){ - return "-" + toString(); - } - - /** - * 任意のオプション文字列がこのオプションに合致するか判定する。 - * @param option ハイフンの付いたオプション文字列 - * @return 合致すればtrue - */ - public boolean matchHyphened(CharSequence option){ - if(option == null) return false; - - for(String name : this.nameList){ - String hyphened = "-" + name; - if(hyphened.equals(option.toString())) return true; - } - - return false; - } - -} +/* + * command line options + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * コマンドラインオプションの列挙。 + */ +public enum CmdOption{ + + /** ヘルプ。 */ + OPT_HELP("help", "h", "-help", "?"), + /** 版数表示。 */ + OPT_VERSION("version"), + /** UI文字制御。 */ + OPT_BOLDMETAL("boldMetal"), + /** スプラッシュ制御。 */ + OPT_NOSPLASH("nosplash"), + /** ウィンドウ位置指定。 */ + OPT_GEOMETRY("geometry"), + /** 実行環境出力。 */ + OPT_VMINFO("vminfo"), + /** コンソールログ。 */ + OPT_CONSOLELOG("consolelog"), + /** フォント指定。 */ + OPT_INITFONT("initfont"), + /** アンチエイリアス。 */ + OPT_ANTIALIAS("antialias"), + /** サブピクセル制御。 */ + OPT_FRACTIONAL("fractional"), + /** 設定格納ディレクトリ指定。 */ + OPT_CONFDIR("confdir"), + /** 設定格納ディレクトリ不使用。 */ + OPT_NOCONF("noconfdir"), + ; + + + private final List nameList = new LinkedList(); + + + /** + * コンストラクタ。 + * @param names 頭のハイフンを除いたオプション名の一覧 + */ + private CmdOption(CharSequence ... names){ + if(names == null) throw new NullPointerException(); + if(names.length <= 0) throw new IllegalArgumentException(); + + for(CharSequence name : names){ + if(name == null) throw new NullPointerException(); + this.nameList.add(name.toString().intern()); + } + + return; + } + + + /** + * オプション名に合致するEnumを返す。 + * @param seq ハイフン付きオプション名 + * @return 合致したEnum。どれとも合致しなければnull + */ + public static CmdOption parseCmdOption(CharSequence seq){ + for(CmdOption option : values()){ + if(option.matchHyphened(seq)) return option; + } + return null; + } + + /** + * 単体で意味をなすオプションか判定する。 + * @param option オプション + * @return 単体で意味をなすならtrue + */ + public static boolean isIndepOption(CmdOption option){ + switch(option){ + case OPT_HELP: + case OPT_VERSION: + case OPT_VMINFO: + case OPT_BOLDMETAL: + case OPT_NOSPLASH: + case OPT_CONSOLELOG: + case OPT_NOCONF: + return true; + default: + break; + } + + return false; + } + + /** + * 真偽指定を一つ必要とするオプションか判定する。 + * @param option オプション + * @return 真偽指定を一つ必要とするオプションならtrue + */ + public static boolean isBooleanOption(CmdOption option){ + switch(option){ + case OPT_ANTIALIAS: + case OPT_FRACTIONAL: + return true; + default: + break; + } + + return false; + } + + /** + * ヘルプメッセージ(オプションの説明)を返す。 + * @return ヘルプメッセージ + */ + public static CharSequence getHelpText(){ + CharSequence helpText; + + try{ + helpText = Jindolf.loadResourceText("resources/help.txt"); + }catch(IOException e){ + helpText = ""; + } + + return helpText; + } + + /** + * 頭のハイフンを除いたオプション名を返す。 + * オプション名が複数指定されていた場合は最初のオプション名 + * @return オプション名 + */ + @Override + public String toString(){ + return this.nameList.get(0); + } + + /** + * 頭のハイフンが付いたオプション名を返す。 + * オプション名が複数指定されていた場合は最初のオプション名 + * @return オプション名 + */ + public String toHyphened(){ + return "-" + toString(); + } + + /** + * 任意のオプション文字列がこのオプションに合致するか判定する。 + * @param option ハイフンの付いたオプション文字列 + * @return 合致すればtrue + */ + public boolean matchHyphened(CharSequence option){ + if(option == null) return false; + + for(String name : this.nameList){ + String hyphened = "-" + name; + if(hyphened.equals(option.toString())) return true; + } + + return false; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ConfigFile.java b/src/main/java/jp/sourceforge/jindolf/ConfigFile.java index 69ad681..7e34b6c 100644 --- a/src/main/java/jp/sourceforge/jindolf/ConfigFile.java +++ b/src/main/java/jp/sourceforge/jindolf/ConfigFile.java @@ -1,850 +1,850 @@ -/* - * configuration file & directory - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.awt.HeadlessException; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.Writer; -import java.nio.charset.Charset; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JOptionPane; -import javax.swing.JRadioButton; -import jp.sourceforge.jindolf.json.JsParseException; -import jp.sourceforge.jindolf.json.JsValue; -import jp.sourceforge.jindolf.json.Json; - -/** - * Jindolf設定格納ディレクトリに関するあれこれ。 - */ -public final class ConfigFile{ - - private static final String TITLE_BUILDCONF = - Jindolf.TITLE + "設定格納ディレクトリの設定"; - - private static final String JINCONF = "Jindolf"; - private static final String JINCONF_DOT = ".jindolf"; - private static final String FILE_README = "README.txt"; - private static final Charset CHARSET_README = Charset.forName("UTF-8"); - private static final Charset CHARSET_JSON = Charset.forName("UTF-8"); - - private static final String MSG_POST = - "
    " - + "
  • " + CmdOption.OPT_CONFDIR.toHyphened() + "" - + " ã‚ªãƒ—ション指定により、
    " - + "任意の設定格納ディレクトリを指定することができます。
    " - + "
  • " + CmdOption.OPT_NOCONF.toHyphened() + "" - + " ã‚ªãƒ—ション指定により、
    " - + "設定格納ディレクトリを使わずに起動することができます。
    " - + "
"; - - - /** - * 隠れコンストラクタ。 - */ - private ConfigFile(){ - super(); - return; - } - - - /** - * 設定格納ディレクトリのセットアップ。 - * @return 設定格納ディレクトリ - */ - public static File setupConfigDirectory(){ - AppSetting setting = Jindolf.getAppSetting(); - File configPath; - - if( ! setting.useConfigPath() ){ - configPath = null; - }else{ - String optName; - if(setting.getConfigPath() != null){ - configPath = setting.getConfigPath(); - optName = CmdOption.OPT_CONFDIR.toHyphened(); - }else{ - configPath = ConfigFile.getImplicitConfigDirectory(); - optName = null; - } - if( ! configPath.exists() ){ - configPath = - ConfigFile.buildConfigDirectory(configPath, optName); - } - ConfigFile.checkAccessibility(configPath); - } - - setting.setConfigPath(configPath); - - return configPath; - } - - /** - * ロックファイルのセットアップ。 - * @return ロックオブジェクト - */ - public static InterVMLock setupLockFile(){ - AppSetting setting = Jindolf.getAppSetting(); - - File configPath = setting.getConfigPath(); - if(configPath == null) return null; - - File lockFile = new File(configPath, "lock"); - InterVMLock lock = new InterVMLock(lockFile); - - lock.tryLock(); - - if( ! lock.isFileOwner() ){ - confirmLockError(lock); - if( ! lock.isFileOwner() ){ - setting.setConfigPath(null); - setting.setUseConfigPath(false); - } - } - - return lock; - } - - /** - * 暗黙的な設定格納ディレクトリを返す。 - * 起動元JARファイルと同じディレクトリに、 - * アクセス可能なディレクトリ"Jindolf"が - * すでに存在していればそれを返す。 - * 起動元JARファイルおよび"Jindolf"が発見できなければ、 - * MacOSX環境の場合"~/Library/Application Support/Jindolf/"を返す。 - * Windows環境の場合"%USERPROFILE%\Jindolf\"を返す。 - * それ以外の環境(Linux,etc?)の場合"~/.jindolf/"を返す。 - * 返すディレクトリが存在しているか否か、 - * アクセス可能か否かは呼び出し元で判断せよ。 - * @return 設定格納ディレクトリ - */ - public static File getImplicitConfigDirectory(){ - File result; - - File jarParent = FileUtils.getJarDirectory(Jindolf.class); - if(jarParent != null && FileUtils.isAccessibleDirectory(jarParent)){ - result = new File(jarParent, JINCONF); - if(FileUtils.isAccessibleDirectory(result)){ - return result; - } - } - - File appset = FileUtils.getAppSetDir(); - if(appset == null) return null; - - if(FileUtils.isMacOSXFs() || FileUtils.isWindowsOSFs()){ - result = new File(appset, JINCONF); - }else{ - result = new File(appset, JINCONF_DOT); - } - - return result; - } - - /** - * まだ存在しない設定格納ディレクトリを新規に作成する。 - * エラーがあればダイアログ提示とともにVM終了する。 - * @param confPath 設定格納ディレクトリ - * @param optName 設定を指定したオプション名。 - * 暗黙的に指示されたものならnullを渡すべし。 - * @return 新規に作成した設定格納ディレクトリ - * @throws IllegalArgumentException すでにそのディレクトリは存在する。 - */ - public static File buildConfigDirectory(File confPath, - String optName) - throws IllegalArgumentException{ - if(confPath.exists()) throw new IllegalArgumentException(); - - File absPath = FileUtils.supplyFullPath(confPath); - - String preErrMessage = - "設定格納ディレクトリ
" - + getCenteredFileName(absPath) - + "の作成に失敗しました。"; - if(optName != null){ - preErrMessage = - "" + optName + " ã‚ªãƒ—ション" - + "で指定された、
" - + preErrMessage; - } - - File existsAncestor = FileUtils.findExistsAncestor(absPath); - if(existsAncestor == null){ - abortNoRoot(absPath, preErrMessage); - }else if( ! existsAncestor.canWrite() ){ - abortCantWriteAncestor(existsAncestor, preErrMessage); - } - - String prompt = - "設定ファイル格納ディレクトリ
" - + getCenteredFileName(absPath) - + "を作成します。"; - boolean confirmed = confirmBuildConfigDir(existsAncestor, prompt); - if( ! confirmed ){ - abortQuitBuildConfigDir(); - } - - boolean success; - try{ - success = absPath.mkdirs(); - }catch(SecurityException e){ - success = false; - } - - if( ! success || ! absPath.exists() ){ - abortCantBuildConfigDir(absPath); - } - - FileUtils.setOwnerOnlyAccess(absPath); - - checkAccessibility(absPath); - - touchReadme(absPath); - - return absPath; - } - - /** - * 設定ディレクトリ操作の - * 共通エラーメッセージ確認ダイアログを表示する。 - * 閉じるまで待つ。 - * @param seq メッセージ - */ - private static void showErrorMessage(CharSequence seq){ - JOptionPane pane = - new JOptionPane(seq.toString(), - JOptionPane.ERROR_MESSAGE); - showDialog(pane); - return; - } - - /** - * 設定ディレクトリ操作の - * 共通エラーメッセージ確認ダイアログを表示する。 - * 閉じるまで待つ。 - * @param seq メッセージ - */ - private static void showWarnMessage(CharSequence seq){ - JOptionPane pane = - new JOptionPane(seq.toString(), - JOptionPane.WARNING_MESSAGE); - showDialog(pane); - return; - } - - /** - * 設定ディレクトリ操作の - * 情報提示メッセージ確認ダイアログを表示する。 - * 閉じるまで待つ。 - * @param seq メッセージ - */ - private static void showInfoMessage(CharSequence seq){ - JOptionPane pane = - new JOptionPane(seq.toString(), - JOptionPane.INFORMATION_MESSAGE); - showDialog(pane); - return; - } - - /** - * ダイアログを表示し、閉じられるまで待つ。 - * @param pane ダイアログの元となるペイン - */ - private static void showDialog(JOptionPane pane){ - JDialog dialog = pane.createDialog(null, TITLE_BUILDCONF); - dialog.setResizable(true); - dialog.pack(); - - dialog.setVisible(true); - dialog.dispose(); - - return; - } - - /** - * 設定ディレクトリのルートファイルシステムもしくはドライブレターに - * アクセスできないエラーをダイアログに提示し、VM終了する。 - * @param path 設定ディレクトリ - * @param preMessage メッセージ前半 - */ - private static void abortNoRoot(File path, String preMessage){ - File root = FileUtils.findRootFile(path); - showErrorMessage( - "" - + preMessage + "
" - + getCenteredFileName(root) - + "を用意する方法が不明です。
" - + "起動を中止します。
" - + MSG_POST - + "" ); - Jindolf.exit(1); - return; - } - - /** - * 設定ディレクトリの祖先に書き込めないエラーをダイアログで提示し、 - * VM終了する。 - * @param existsAncestor 存在するもっとも近い祖先 - * @param preMessage メッセージ前半 - */ - private static void abortCantWriteAncestor(File existsAncestor, - String preMessage ){ - showErrorMessage( - "" - + preMessage + "
" - + getCenteredFileName(existsAncestor) - + "への書き込みができないため、" - + "処理の続行は不可能です。
" - + "起動を中止します。
" - + MSG_POST - + "" ); - Jindolf.exit(1); - return; - } - - /** - * 設定ディレクトリを新規に生成してよいかダイアログで問い合わせる。 - * @param existsAncestor 存在するもっとも近い祖先 - * @param preMessage メッセージ前半 - * @return 生成してよいと指示があればtrue - */ - private static boolean confirmBuildConfigDir(File existsAncestor, - String preMessage){ - String message = - "" - + preMessage + "
" - + "このディレクトリを今から
" - + getCenteredFileName(existsAncestor) - + "に作成して構いませんか?
" - + "このディレクトリ名は、後からいつでもヘルプウィンドウで
" - + "確認することができます。" - + ""; - - JOptionPane pane = - new JOptionPane(message, - JOptionPane.QUESTION_MESSAGE, - JOptionPane.YES_NO_OPTION); - - showDialog(pane); - - Object result = pane.getValue(); - if(result == null) return false; - else if( ! (result instanceof Integer) ) return false; - - int ival = (Integer) result; - if(ival == JOptionPane.YES_OPTION) return true; - - return false; - } - - /** - * 設定ディレクトリ生成をやめた操作への警告をダイアログで提示し、 - * VM終了する。 - */ - private static void abortQuitBuildConfigDir(){ - showWarnMessage( - "" - + "設定ディレクトリの作成をせずに起動を中止します。
" - + MSG_POST - + "" ); - Jindolf.exit(1); - return; - } - - /** - * 設定ディレクトリが生成できないエラーをダイアログで提示し、 - * VM終了する。 - * @param path 生成できなかったディレクトリ - */ - private static void abortCantBuildConfigDir(File path){ - showErrorMessage( - "" - + "設定ディレクトリ
" - + getCenteredFileName(path) - + "の作成に失敗しました。" - + "起動を中止します。
" - + MSG_POST - + "" ); - Jindolf.exit(1); - return; - } - - /** - * 設定ディレクトリへアクセスできないエラーをダイアログで提示し、 - * VM終了する。 - * @param path アクセスできないディレクトリ - */ - private static void abortCantAccessConfigDir(File path){ - showErrorMessage( - "" - + "設定ディレクトリ
" - + getCenteredFileName(path) - + "へのアクセスができません。" - + "起動を中止します。
" - + "このディレクトリへのアクセス権を調整し" - + "読み書きできるようにしてください。
" - + MSG_POST - + "" ); - Jindolf.exit(1); - return; - } - - /** - * ファイルに書き込めないエラーをダイアログで提示し、VM終了する。 - * @param file 書き込めなかったファイル - */ - private static void abortCantWrite(File file){ - showErrorMessage( - "" - + "ファイル
" - + getCenteredFileName(file) - + "への書き込みができません。" - + "起動を中止します。
" - + "" ); - Jindolf.exit(1); - return; - } - - /** - * 指定されたディレクトリにREADMEファイルを生成する。 - * 生成できなければダイアログ表示とともにVM終了する。 - * @param path READMEの格納ディレクトリ - */ - private static void touchReadme(File path){ - File file = new File(path, FILE_README); - - try{ - file.createNewFile(); - }catch(IOException e){ - abortCantAccessConfigDir(path); - } - - PrintWriter writer = null; - try{ - OutputStream ostream = new FileOutputStream(file); - Writer owriter = new OutputStreamWriter(ostream, CHARSET_README); - writer = new PrintWriter(owriter); - writer.println(CHARSET_README.name() + " Japanese"); - writer.println( - "このディレクトリは、" - + "Jindolfの各種設定が格納されるディレクトリです。"); - writer.println( - "Jindolfの詳細は " - + "http://jindolf.sourceforge.jp/" - + " を参照してください。"); - writer.println( - "このディレクトリを" - + "「" + JINCONF + "」" - + "の名前で起動元JARファイルと" - + "同じ位置に"); - writer.println( - "コピーすれば、そちらの設定が優先して使われます。"); - writer.println( - "「lock」の名前を持つファイルはロックファイルです。"); - }catch(IOException e){ - abortCantWrite(file); - }catch(SecurityException e){ - abortCantWrite(file); - }finally{ - if(writer != null){ - writer.close(); - } - } - - return; - } - - /** - * 設定ディレクトリがアクセス可能でなければ - * エラーダイアログを出してVM終了する。 - * @param confDir 設定ディレクトリ - */ - public static void checkAccessibility(File confDir){ - if( ! FileUtils.isAccessibleDirectory(confDir) ){ - abortCantAccessConfigDir(confDir); - } - - return; - } - - /** - * センタリングされたファイル名表示のHTML表記を出力する。 - * @param path ファイル - * @return HTML表記 - */ - public static String getCenteredFileName(File path){ - return "
[ " - + FileUtils.getHtmledFileName(path) - + " ]
" - + "
"; - } - - /** - * ロックエラーダイアログの表示。 - * 呼び出しから戻ってもまだロックオブジェクトが - * ロックファイルのオーナーでない場合、 - * 今後設定ディレクトリは一切使わずに起動を続行するものとする。 - * ロックファイルの強制解除に失敗した場合はVM終了する。 - * @param lock エラーを起こしたロック - */ - public static void confirmLockError(InterVMLock lock){ - LockErrorPane pane = new LockErrorPane(lock); - JDialog dialog = pane.createDialog(null, TITLE_BUILDCONF); - dialog.setResizable(true); - dialog.pack(); - - for(;;){ - dialog.setVisible(true); - dialog.dispose(); - - if(pane.isAborted() || pane.getValue() == null){ - Jindolf.exit(1); - break; - }else if(pane.isRadioRetry()){ - lock.tryLock(); - if(lock.isFileOwner()) break; - }else if(pane.isRadioContinue()){ - showInfoMessage( - "" - + "設定ディレクトリを使わずに起動を続行します。
" - + "今回、各種設定の読み込み・保存はできません。
" - + "" - + CmdOption.OPT_NOCONF.toHyphened() - + " オプション" - + "を使うとこの警告は出なくなります。" - + ""); - break; - }else if(pane.isRadioForce()){ - lock.forceRemove(); - if(lock.isExistsFile()){ - showErrorMessage( - "" - + "ロックファイルの強制解除に失敗しました。
" - + "他に動いているJindolf" - + "が見つからないのであれば、
" - + "なんとかしてロックファイル
" - + getCenteredFileName(lock.getLockFile()) - + "を削除してください。
" - + "起動を中止します。" - + ""); - Jindolf.exit(1); - break; - } - lock.tryLock(); - if(lock.isFileOwner()) break; - showErrorMessage( - "" - + "ロックファイル
" - + getCenteredFileName(lock.getLockFile()) - + "を確保することができません。
" - + "起動を中止します。" - + ""); - Jindolf.exit(1); - break; - } - } - - return; - } - - /** - * 設定ディレクトリ上のJSONファイルを読み込む。 - * @param file JSONファイルの相対パス - * @return JSON objectまたはarray。 - * 設定ディレクトリを使わない設定、 - * もしくはJSONファイルが存在しない、 - * もしくは入力エラーがあればnull - */ - public static JsValue loadJson(File file){ - AppSetting setting = Jindolf.getAppSetting(); - if( ! setting.useConfigPath() ) return null; - - File absFile; - if(file.isAbsolute()){ - absFile = file; - }else{ - File configPath = setting.getConfigPath(); - if(configPath == null) return null; - absFile = new File(configPath, file.getPath()); - if( ! absFile.exists() ) return null; - if( ! absFile.isAbsolute() ) return null; - } - - InputStream istream; - try{ - istream = new FileInputStream(absFile); - }catch(FileNotFoundException e){ - assert false; - return null; - } - istream = new BufferedInputStream(istream); - - Reader reader = new InputStreamReader(istream, CHARSET_JSON); - - JsValue value; - try{ - value = Json.parseValue(reader); - }catch(IOException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]の読み込み時に支障がありました。", e); - return null; - }catch(JsParseException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]の内容に不備があります。", e); - return null; - }finally{ - try{ - reader.close(); - }catch(IOException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]を閉じることができません。", e); - return null; - } - } - - return value; - } - - /** - * 設定ディレクトリ上のJSONファイルに書き込む。 - * @param file JSONファイルの相対パス - * @param value JSON objectまたはarray - * @return 正しくセーブが行われればtrue。 - * 何らかの理由でセーブが完了できなければfalse - */ - public static boolean saveJson(File file, JsValue value){ - AppSetting setting = Jindolf.getAppSetting(); - if( ! setting.useConfigPath() ) return false; - File configPath = setting.getConfigPath(); - if(configPath == null) return false; - - // TODO テンポラリファイルを用いたより安全なファイル更新 - File absFile = new File(configPath, file.getPath()); - absFile.delete(); - try{ - if(absFile.createNewFile() != true) return false; - }catch(IOException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]の新規生成ができません。", e); - return false; - } - - OutputStream ostream; - try{ - ostream = new FileOutputStream(absFile); - }catch(FileNotFoundException e){ - assert false; - return false; - } - ostream = new BufferedOutputStream(ostream); - Writer writer = new OutputStreamWriter(ostream, CHARSET_JSON); - - try{ - Json.writeJsonTop(writer, value); - }catch(IOException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]の書き込み時に支障がありました。", e); - return false; - }finally{ - try{ - writer.close(); - }catch(IOException e){ - Jindolf.logger().fatal( - "JSONファイル[" - + absFile.getPath() - + "]を閉じることができません。", e); - return false; - } - } - - return true; - } - - /** - * ロックエラー用ダイアログ。 - *
    - *
  • 強制解除 - *
  • リトライ - *
  • 設定ディレクトリを無視 - *
  • 起動中止 - *
- * の選択を利用者に求める。 - */ - @SuppressWarnings("serial") - private static class LockErrorPane - extends JOptionPane - implements ActionListener{ - - private final InterVMLock lock; - - private final JRadioButton continueButton = - new JRadioButton("設定ディレクトリを使わずに起動を続行"); - private final JRadioButton retryButton = - new JRadioButton("再度ロック取得を試す"); - private final JRadioButton forceButton = - new JRadioButton( - "" - + "ロックを強制解除
" - + " (※他のJindolfと設定ファイル書き込みが衝突するかも…)" - + ""); - - private final JButton okButton = new JButton("OK"); - private final JButton abortButton = new JButton("起動中止"); - - private boolean aborted = false; - - /** - * コンストラクタ。 - * @param lock 失敗したロック - */ - public LockErrorPane(InterVMLock lock){ - super(); - - this.lock = lock; - - String htmlMessage = - "" - + "設定ディレクトリのロックファイル
" - + getCenteredFileName(this.lock.getLockFile()) - + "のロックに失敗しました。
" - + "考えられる原因としては、
" - + "
    " - + "
  • 前回起動したJindolfの終了が正しく行われなかった" - + "
  • 今どこかで他のJindolfが動いている" - + "
" - + "などが考えられます。
" - + "
" - + ""; - - ButtonGroup bgrp = new ButtonGroup(); - bgrp.add(this.continueButton); - bgrp.add(this.retryButton); - bgrp.add(this.forceButton); - this.continueButton.setSelected(true); - - Object[] msg = { - htmlMessage, - this.continueButton, - this.retryButton, - this.forceButton, - }; - setMessage(msg); - - Object[] opts = { - this.okButton, - this.abortButton, - }; - setOptions(opts); - - setMessageType(JOptionPane.ERROR_MESSAGE); - - this.okButton .addActionListener(this); - this.abortButton.addActionListener(this); - - return; - } - - /** - * 「設定ディレクトリを無視して続行」が選択されたか判定する。 - * @return 「無視して続行」が選択されていればtrue - */ - public boolean isRadioContinue(){ - return this.continueButton.isSelected(); - } - - /** - * 「リトライ」が選択されたか判定する。 - * @return 「リトライ」が選択されていればtrue - */ - public boolean isRadioRetry(){ - return this.retryButton.isSelected(); - } - - /** - * 「強制解除」が選択されたか判定する。 - * @return 「強制解除」が選択されていればtrue - */ - public boolean isRadioForce(){ - return this.forceButton.isSelected(); - } - - /** - * 「起動中止」が選択されたか判定する。 - * @return 「起動中止」が押されていたならtrue - */ - public boolean isAborted(){ - return this.aborted; - } - - /** - * {@inheritDoc} - * @param parentComponent {@inheritDoc} - * @param title {@inheritDoc} - * @return {@inheritDoc} - * @throws HeadlessException {@inheritDoc} - */ - @Override - public JDialog createDialog(Component parentComponent, - String title) - throws HeadlessException{ - final JDialog dialog = - super.createDialog(parentComponent, title); - - ActionListener listener = new ActionListener(){ - public void actionPerformed(ActionEvent event){ - dialog.setVisible(false); - return; - } - }; - - this.okButton .addActionListener(listener); - this.abortButton.addActionListener(listener); - - return dialog; - } - - /** - * ボタン押下を受信する。 - * @param event イベント - */ - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if(source == this.okButton) this.aborted = false; - else this.aborted = true; - return; - } - - } - -} +/* + * configuration file & directory + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JRadioButton; +import jp.sourceforge.jindolf.json.JsParseException; +import jp.sourceforge.jindolf.json.JsValue; +import jp.sourceforge.jindolf.json.Json; + +/** + * Jindolf設定格納ディレクトリに関するあれこれ。 + */ +public final class ConfigFile{ + + private static final String TITLE_BUILDCONF = + Jindolf.TITLE + "設定格納ディレクトリの設定"; + + private static final String JINCONF = "Jindolf"; + private static final String JINCONF_DOT = ".jindolf"; + private static final String FILE_README = "README.txt"; + private static final Charset CHARSET_README = Charset.forName("UTF-8"); + private static final Charset CHARSET_JSON = Charset.forName("UTF-8"); + + private static final String MSG_POST = + "
    " + + "
  • " + CmdOption.OPT_CONFDIR.toHyphened() + "" + + " ã‚ªãƒ—ション指定により、
    " + + "任意の設定格納ディレクトリを指定することができます。
    " + + "
  • " + CmdOption.OPT_NOCONF.toHyphened() + "" + + " ã‚ªãƒ—ション指定により、
    " + + "設定格納ディレクトリを使わずに起動することができます。
    " + + "
"; + + + /** + * 隠れコンストラクタ。 + */ + private ConfigFile(){ + super(); + return; + } + + + /** + * 設定格納ディレクトリのセットアップ。 + * @return 設定格納ディレクトリ + */ + public static File setupConfigDirectory(){ + AppSetting setting = Jindolf.getAppSetting(); + File configPath; + + if( ! setting.useConfigPath() ){ + configPath = null; + }else{ + String optName; + if(setting.getConfigPath() != null){ + configPath = setting.getConfigPath(); + optName = CmdOption.OPT_CONFDIR.toHyphened(); + }else{ + configPath = ConfigFile.getImplicitConfigDirectory(); + optName = null; + } + if( ! configPath.exists() ){ + configPath = + ConfigFile.buildConfigDirectory(configPath, optName); + } + ConfigFile.checkAccessibility(configPath); + } + + setting.setConfigPath(configPath); + + return configPath; + } + + /** + * ロックファイルのセットアップ。 + * @return ロックオブジェクト + */ + public static InterVMLock setupLockFile(){ + AppSetting setting = Jindolf.getAppSetting(); + + File configPath = setting.getConfigPath(); + if(configPath == null) return null; + + File lockFile = new File(configPath, "lock"); + InterVMLock lock = new InterVMLock(lockFile); + + lock.tryLock(); + + if( ! lock.isFileOwner() ){ + confirmLockError(lock); + if( ! lock.isFileOwner() ){ + setting.setConfigPath(null); + setting.setUseConfigPath(false); + } + } + + return lock; + } + + /** + * 暗黙的な設定格納ディレクトリを返す。 + * 起動元JARファイルと同じディレクトリに、 + * アクセス可能なディレクトリ"Jindolf"が + * すでに存在していればそれを返す。 + * 起動元JARファイルおよび"Jindolf"が発見できなければ、 + * MacOSX環境の場合"~/Library/Application Support/Jindolf/"を返す。 + * Windows環境の場合"%USERPROFILE%\Jindolf\"を返す。 + * それ以外の環境(Linux,etc?)の場合"~/.jindolf/"を返す。 + * 返すディレクトリが存在しているか否か、 + * アクセス可能か否かは呼び出し元で判断せよ。 + * @return 設定格納ディレクトリ + */ + public static File getImplicitConfigDirectory(){ + File result; + + File jarParent = FileUtils.getJarDirectory(Jindolf.class); + if(jarParent != null && FileUtils.isAccessibleDirectory(jarParent)){ + result = new File(jarParent, JINCONF); + if(FileUtils.isAccessibleDirectory(result)){ + return result; + } + } + + File appset = FileUtils.getAppSetDir(); + if(appset == null) return null; + + if(FileUtils.isMacOSXFs() || FileUtils.isWindowsOSFs()){ + result = new File(appset, JINCONF); + }else{ + result = new File(appset, JINCONF_DOT); + } + + return result; + } + + /** + * まだ存在しない設定格納ディレクトリを新規に作成する。 + * エラーがあればダイアログ提示とともにVM終了する。 + * @param confPath 設定格納ディレクトリ + * @param optName 設定を指定したオプション名。 + * 暗黙的に指示されたものならnullを渡すべし。 + * @return 新規に作成した設定格納ディレクトリ + * @throws IllegalArgumentException すでにそのディレクトリは存在する。 + */ + public static File buildConfigDirectory(File confPath, + String optName) + throws IllegalArgumentException{ + if(confPath.exists()) throw new IllegalArgumentException(); + + File absPath = FileUtils.supplyFullPath(confPath); + + String preErrMessage = + "設定格納ディレクトリ
" + + getCenteredFileName(absPath) + + "の作成に失敗しました。"; + if(optName != null){ + preErrMessage = + "" + optName + " ã‚ªãƒ—ション" + + "で指定された、
" + + preErrMessage; + } + + File existsAncestor = FileUtils.findExistsAncestor(absPath); + if(existsAncestor == null){ + abortNoRoot(absPath, preErrMessage); + }else if( ! existsAncestor.canWrite() ){ + abortCantWriteAncestor(existsAncestor, preErrMessage); + } + + String prompt = + "設定ファイル格納ディレクトリ
" + + getCenteredFileName(absPath) + + "を作成します。"; + boolean confirmed = confirmBuildConfigDir(existsAncestor, prompt); + if( ! confirmed ){ + abortQuitBuildConfigDir(); + } + + boolean success; + try{ + success = absPath.mkdirs(); + }catch(SecurityException e){ + success = false; + } + + if( ! success || ! absPath.exists() ){ + abortCantBuildConfigDir(absPath); + } + + FileUtils.setOwnerOnlyAccess(absPath); + + checkAccessibility(absPath); + + touchReadme(absPath); + + return absPath; + } + + /** + * 設定ディレクトリ操作の + * 共通エラーメッセージ確認ダイアログを表示する。 + * 閉じるまで待つ。 + * @param seq メッセージ + */ + private static void showErrorMessage(CharSequence seq){ + JOptionPane pane = + new JOptionPane(seq.toString(), + JOptionPane.ERROR_MESSAGE); + showDialog(pane); + return; + } + + /** + * 設定ディレクトリ操作の + * 共通エラーメッセージ確認ダイアログを表示する。 + * 閉じるまで待つ。 + * @param seq メッセージ + */ + private static void showWarnMessage(CharSequence seq){ + JOptionPane pane = + new JOptionPane(seq.toString(), + JOptionPane.WARNING_MESSAGE); + showDialog(pane); + return; + } + + /** + * 設定ディレクトリ操作の + * 情報提示メッセージ確認ダイアログを表示する。 + * 閉じるまで待つ。 + * @param seq メッセージ + */ + private static void showInfoMessage(CharSequence seq){ + JOptionPane pane = + new JOptionPane(seq.toString(), + JOptionPane.INFORMATION_MESSAGE); + showDialog(pane); + return; + } + + /** + * ダイアログを表示し、閉じられるまで待つ。 + * @param pane ダイアログの元となるペイン + */ + private static void showDialog(JOptionPane pane){ + JDialog dialog = pane.createDialog(null, TITLE_BUILDCONF); + dialog.setResizable(true); + dialog.pack(); + + dialog.setVisible(true); + dialog.dispose(); + + return; + } + + /** + * 設定ディレクトリのルートファイルシステムもしくはドライブレターに + * アクセスできないエラーをダイアログに提示し、VM終了する。 + * @param path 設定ディレクトリ + * @param preMessage メッセージ前半 + */ + private static void abortNoRoot(File path, String preMessage){ + File root = FileUtils.findRootFile(path); + showErrorMessage( + "" + + preMessage + "
" + + getCenteredFileName(root) + + "を用意する方法が不明です。
" + + "起動を中止します。
" + + MSG_POST + + "" ); + Jindolf.exit(1); + return; + } + + /** + * 設定ディレクトリの祖先に書き込めないエラーをダイアログで提示し、 + * VM終了する。 + * @param existsAncestor 存在するもっとも近い祖先 + * @param preMessage メッセージ前半 + */ + private static void abortCantWriteAncestor(File existsAncestor, + String preMessage ){ + showErrorMessage( + "" + + preMessage + "
" + + getCenteredFileName(existsAncestor) + + "への書き込みができないため、" + + "処理の続行は不可能です。
" + + "起動を中止します。
" + + MSG_POST + + "" ); + Jindolf.exit(1); + return; + } + + /** + * 設定ディレクトリを新規に生成してよいかダイアログで問い合わせる。 + * @param existsAncestor 存在するもっとも近い祖先 + * @param preMessage メッセージ前半 + * @return 生成してよいと指示があればtrue + */ + private static boolean confirmBuildConfigDir(File existsAncestor, + String preMessage){ + String message = + "" + + preMessage + "
" + + "このディレクトリを今から
" + + getCenteredFileName(existsAncestor) + + "に作成して構いませんか?
" + + "このディレクトリ名は、後からいつでもヘルプウィンドウで
" + + "確認することができます。" + + ""; + + JOptionPane pane = + new JOptionPane(message, + JOptionPane.QUESTION_MESSAGE, + JOptionPane.YES_NO_OPTION); + + showDialog(pane); + + Object result = pane.getValue(); + if(result == null) return false; + else if( ! (result instanceof Integer) ) return false; + + int ival = (Integer) result; + if(ival == JOptionPane.YES_OPTION) return true; + + return false; + } + + /** + * 設定ディレクトリ生成をやめた操作への警告をダイアログで提示し、 + * VM終了する。 + */ + private static void abortQuitBuildConfigDir(){ + showWarnMessage( + "" + + "設定ディレクトリの作成をせずに起動を中止します。
" + + MSG_POST + + "" ); + Jindolf.exit(1); + return; + } + + /** + * 設定ディレクトリが生成できないエラーをダイアログで提示し、 + * VM終了する。 + * @param path 生成できなかったディレクトリ + */ + private static void abortCantBuildConfigDir(File path){ + showErrorMessage( + "" + + "設定ディレクトリ
" + + getCenteredFileName(path) + + "の作成に失敗しました。" + + "起動を中止します。
" + + MSG_POST + + "" ); + Jindolf.exit(1); + return; + } + + /** + * 設定ディレクトリへアクセスできないエラーをダイアログで提示し、 + * VM終了する。 + * @param path アクセスできないディレクトリ + */ + private static void abortCantAccessConfigDir(File path){ + showErrorMessage( + "" + + "設定ディレクトリ
" + + getCenteredFileName(path) + + "へのアクセスができません。" + + "起動を中止します。
" + + "このディレクトリへのアクセス権を調整し" + + "読み書きできるようにしてください。
" + + MSG_POST + + "" ); + Jindolf.exit(1); + return; + } + + /** + * ファイルに書き込めないエラーをダイアログで提示し、VM終了する。 + * @param file 書き込めなかったファイル + */ + private static void abortCantWrite(File file){ + showErrorMessage( + "" + + "ファイル
" + + getCenteredFileName(file) + + "への書き込みができません。" + + "起動を中止します。
" + + "" ); + Jindolf.exit(1); + return; + } + + /** + * 指定されたディレクトリにREADMEファイルを生成する。 + * 生成できなければダイアログ表示とともにVM終了する。 + * @param path READMEの格納ディレクトリ + */ + private static void touchReadme(File path){ + File file = new File(path, FILE_README); + + try{ + file.createNewFile(); + }catch(IOException e){ + abortCantAccessConfigDir(path); + } + + PrintWriter writer = null; + try{ + OutputStream ostream = new FileOutputStream(file); + Writer owriter = new OutputStreamWriter(ostream, CHARSET_README); + writer = new PrintWriter(owriter); + writer.println(CHARSET_README.name() + " Japanese"); + writer.println( + "このディレクトリは、" + + "Jindolfの各種設定が格納されるディレクトリです。"); + writer.println( + "Jindolfの詳細は " + + "http://jindolf.sourceforge.jp/" + + " を参照してください。"); + writer.println( + "このディレクトリを" + + "「" + JINCONF + "」" + + "の名前で起動元JARファイルと" + + "同じ位置に"); + writer.println( + "コピーすれば、そちらの設定が優先して使われます。"); + writer.println( + "「lock」の名前を持つファイルはロックファイルです。"); + }catch(IOException e){ + abortCantWrite(file); + }catch(SecurityException e){ + abortCantWrite(file); + }finally{ + if(writer != null){ + writer.close(); + } + } + + return; + } + + /** + * 設定ディレクトリがアクセス可能でなければ + * エラーダイアログを出してVM終了する。 + * @param confDir 設定ディレクトリ + */ + public static void checkAccessibility(File confDir){ + if( ! FileUtils.isAccessibleDirectory(confDir) ){ + abortCantAccessConfigDir(confDir); + } + + return; + } + + /** + * センタリングされたファイル名表示のHTML表記を出力する。 + * @param path ファイル + * @return HTML表記 + */ + public static String getCenteredFileName(File path){ + return "
[ " + + FileUtils.getHtmledFileName(path) + + " ]
" + + "
"; + } + + /** + * ロックエラーダイアログの表示。 + * 呼び出しから戻ってもまだロックオブジェクトが + * ロックファイルのオーナーでない場合、 + * 今後設定ディレクトリは一切使わずに起動を続行するものとする。 + * ロックファイルの強制解除に失敗した場合はVM終了する。 + * @param lock エラーを起こしたロック + */ + public static void confirmLockError(InterVMLock lock){ + LockErrorPane pane = new LockErrorPane(lock); + JDialog dialog = pane.createDialog(null, TITLE_BUILDCONF); + dialog.setResizable(true); + dialog.pack(); + + for(;;){ + dialog.setVisible(true); + dialog.dispose(); + + if(pane.isAborted() || pane.getValue() == null){ + Jindolf.exit(1); + break; + }else if(pane.isRadioRetry()){ + lock.tryLock(); + if(lock.isFileOwner()) break; + }else if(pane.isRadioContinue()){ + showInfoMessage( + "" + + "設定ディレクトリを使わずに起動を続行します。
" + + "今回、各種設定の読み込み・保存はできません。
" + + "" + + CmdOption.OPT_NOCONF.toHyphened() + + " オプション" + + "を使うとこの警告は出なくなります。" + + ""); + break; + }else if(pane.isRadioForce()){ + lock.forceRemove(); + if(lock.isExistsFile()){ + showErrorMessage( + "" + + "ロックファイルの強制解除に失敗しました。
" + + "他に動いているJindolf" + + "が見つからないのであれば、
" + + "なんとかしてロックファイル
" + + getCenteredFileName(lock.getLockFile()) + + "を削除してください。
" + + "起動を中止します。" + + ""); + Jindolf.exit(1); + break; + } + lock.tryLock(); + if(lock.isFileOwner()) break; + showErrorMessage( + "" + + "ロックファイル
" + + getCenteredFileName(lock.getLockFile()) + + "を確保することができません。
" + + "起動を中止します。" + + ""); + Jindolf.exit(1); + break; + } + } + + return; + } + + /** + * 設定ディレクトリ上のJSONファイルを読み込む。 + * @param file JSONファイルの相対パス + * @return JSON objectまたはarray。 + * 設定ディレクトリを使わない設定、 + * もしくはJSONファイルが存在しない、 + * もしくは入力エラーがあればnull + */ + public static JsValue loadJson(File file){ + AppSetting setting = Jindolf.getAppSetting(); + if( ! setting.useConfigPath() ) return null; + + File absFile; + if(file.isAbsolute()){ + absFile = file; + }else{ + File configPath = setting.getConfigPath(); + if(configPath == null) return null; + absFile = new File(configPath, file.getPath()); + if( ! absFile.exists() ) return null; + if( ! absFile.isAbsolute() ) return null; + } + + InputStream istream; + try{ + istream = new FileInputStream(absFile); + }catch(FileNotFoundException e){ + assert false; + return null; + } + istream = new BufferedInputStream(istream); + + Reader reader = new InputStreamReader(istream, CHARSET_JSON); + + JsValue value; + try{ + value = Json.parseValue(reader); + }catch(IOException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]の読み込み時に支障がありました。", e); + return null; + }catch(JsParseException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]の内容に不備があります。", e); + return null; + }finally{ + try{ + reader.close(); + }catch(IOException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]を閉じることができません。", e); + return null; + } + } + + return value; + } + + /** + * 設定ディレクトリ上のJSONファイルに書き込む。 + * @param file JSONファイルの相対パス + * @param value JSON objectまたはarray + * @return 正しくセーブが行われればtrue。 + * 何らかの理由でセーブが完了できなければfalse + */ + public static boolean saveJson(File file, JsValue value){ + AppSetting setting = Jindolf.getAppSetting(); + if( ! setting.useConfigPath() ) return false; + File configPath = setting.getConfigPath(); + if(configPath == null) return false; + + // TODO テンポラリファイルを用いたより安全なファイル更新 + File absFile = new File(configPath, file.getPath()); + absFile.delete(); + try{ + if(absFile.createNewFile() != true) return false; + }catch(IOException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]の新規生成ができません。", e); + return false; + } + + OutputStream ostream; + try{ + ostream = new FileOutputStream(absFile); + }catch(FileNotFoundException e){ + assert false; + return false; + } + ostream = new BufferedOutputStream(ostream); + Writer writer = new OutputStreamWriter(ostream, CHARSET_JSON); + + try{ + Json.writeJsonTop(writer, value); + }catch(IOException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]の書き込み時に支障がありました。", e); + return false; + }finally{ + try{ + writer.close(); + }catch(IOException e){ + Jindolf.logger().fatal( + "JSONファイル[" + + absFile.getPath() + + "]を閉じることができません。", e); + return false; + } + } + + return true; + } + + /** + * ロックエラー用ダイアログ。 + *
    + *
  • 強制解除 + *
  • リトライ + *
  • 設定ディレクトリを無視 + *
  • 起動中止 + *
+ * の選択を利用者に求める。 + */ + @SuppressWarnings("serial") + private static class LockErrorPane + extends JOptionPane + implements ActionListener{ + + private final InterVMLock lock; + + private final JRadioButton continueButton = + new JRadioButton("設定ディレクトリを使わずに起動を続行"); + private final JRadioButton retryButton = + new JRadioButton("再度ロック取得を試す"); + private final JRadioButton forceButton = + new JRadioButton( + "" + + "ロックを強制解除
" + + " (※他のJindolfと設定ファイル書き込みが衝突するかも…)" + + ""); + + private final JButton okButton = new JButton("OK"); + private final JButton abortButton = new JButton("起動中止"); + + private boolean aborted = false; + + /** + * コンストラクタ。 + * @param lock 失敗したロック + */ + public LockErrorPane(InterVMLock lock){ + super(); + + this.lock = lock; + + String htmlMessage = + "" + + "設定ディレクトリのロックファイル
" + + getCenteredFileName(this.lock.getLockFile()) + + "のロックに失敗しました。
" + + "考えられる原因としては、
" + + "
    " + + "
  • 前回起動したJindolfの終了が正しく行われなかった" + + "
  • 今どこかで他のJindolfが動いている" + + "
" + + "などが考えられます。
" + + "
" + + ""; + + ButtonGroup bgrp = new ButtonGroup(); + bgrp.add(this.continueButton); + bgrp.add(this.retryButton); + bgrp.add(this.forceButton); + this.continueButton.setSelected(true); + + Object[] msg = { + htmlMessage, + this.continueButton, + this.retryButton, + this.forceButton, + }; + setMessage(msg); + + Object[] opts = { + this.okButton, + this.abortButton, + }; + setOptions(opts); + + setMessageType(JOptionPane.ERROR_MESSAGE); + + this.okButton .addActionListener(this); + this.abortButton.addActionListener(this); + + return; + } + + /** + * 「設定ディレクトリを無視して続行」が選択されたか判定する。 + * @return 「無視して続行」が選択されていればtrue + */ + public boolean isRadioContinue(){ + return this.continueButton.isSelected(); + } + + /** + * 「リトライ」が選択されたか判定する。 + * @return 「リトライ」が選択されていればtrue + */ + public boolean isRadioRetry(){ + return this.retryButton.isSelected(); + } + + /** + * 「強制解除」が選択されたか判定する。 + * @return 「強制解除」が選択されていればtrue + */ + public boolean isRadioForce(){ + return this.forceButton.isSelected(); + } + + /** + * 「起動中止」が選択されたか判定する。 + * @return 「起動中止」が押されていたならtrue + */ + public boolean isAborted(){ + return this.aborted; + } + + /** + * {@inheritDoc} + * @param parentComponent {@inheritDoc} + * @param title {@inheritDoc} + * @return {@inheritDoc} + * @throws HeadlessException {@inheritDoc} + */ + @Override + public JDialog createDialog(Component parentComponent, + String title) + throws HeadlessException{ + final JDialog dialog = + super.createDialog(parentComponent, title); + + ActionListener listener = new ActionListener(){ + public void actionPerformed(ActionEvent event){ + dialog.setVisible(false); + return; + } + }; + + this.okButton .addActionListener(listener); + this.abortButton.addActionListener(listener); + + return dialog; + } + + /** + * ボタン押下を受信する。 + * @param event イベント + */ + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if(source == this.okButton) this.aborted = false; + else this.aborted = true; + return; + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Controller.java b/src/main/java/jp/sourceforge/jindolf/Controller.java index 32b383b..6781c54 100644 --- a/src/main/java/jp/sourceforge/jindolf/Controller.java +++ b/src/main/java/jp/sourceforge/jindolf/Controller.java @@ -1,1690 +1,1690 @@ -/* - * MVC controller - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.LayoutManager; -import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.MouseAdapter; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.logging.Handler; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.JToolBar; -import javax.swing.JTree; -import javax.swing.LookAndFeel; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; -import javax.swing.WindowConstants; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.event.TreeWillExpandListener; -import javax.swing.tree.TreePath; -import jp.sourceforge.jindolf.corelib.LandDef; -import jp.sourceforge.jindolf.corelib.VillageState; - -/** - * いわゆるMVCでいうとこのコントローラ。 - */ -public class Controller - implements ActionListener, - TreeWillExpandListener, - TreeSelectionListener, - ChangeListener, - AnchorHitListener { - - private final ActionManager actionManager; - private final TopView topView; - private final LandsModel model; - - private final FilterPanel filterFrame; - private final LogFrame showlogFrame; - private final OptionPanel optionPanel; - private final FindPanel findPanel; - private final TalkPreview talkPreview; - private JFrame helpFrame; - private AccountPanel accountFrame; - private DaySummary daySummaryPanel; - private VillageDigest digestPanel; - private final Map windowMap = - new HashMap(); - - private volatile boolean isBusyNow; - - private JFrame topFrame = null; - - /** - * コントローラの生成。 - * @param actionManager アクション管理 - * @param topView 最上位ビュー - * @param model 最上位データモデル - */ - public Controller(ActionManager actionManager, - TopView topView, - LandsModel model){ - super(); - - this.actionManager = actionManager; - this.topView = topView; - this.model = model; - - JToolBar toolbar = this.actionManager.getBrowseToolBar(); - this.topView.setBrowseToolBar(toolbar); - - this.actionManager.addActionListener(this); - - JTree treeView = this.topView.getTreeView(); - treeView.setModel(this.model); - treeView.addTreeWillExpandListener(this); - treeView.addTreeSelectionListener(this); - - this.topView.getTabBrowser().addChangeListener(this); - this.topView.getTabBrowser().addActionListener(this); - this.topView.getTabBrowser().addAnchorHitListener(this); - - JButton reloadVillageListButton = this.topView - .getLandsTree() - .getReloadVillageListButton(); - reloadVillageListButton.addActionListener(this); - reloadVillageListButton.setEnabled(false); - - this.filterFrame = new FilterPanel(this.topFrame); - this.filterFrame.addChangeListener(this); - this.filterFrame.pack(); - this.filterFrame.setVisible(false); - - this.showlogFrame = new LogFrame(this.topFrame); - this.showlogFrame.pack(); - this.showlogFrame.setSize(600, 500); - this.showlogFrame.setLocationByPlatform(true); - this.showlogFrame.setVisible(false); - if(Jindolf.hasLoggingPermission()){ - Handler newHandler = this.showlogFrame.getHandler(); - Logger jre14Logger = Jindolf.logger().getJre14Logger(); - jre14Logger.addHandler(newHandler); - Handler[] handlers = jre14Logger.getHandlers(); - for(Handler handler : handlers){ - if( ! (handler instanceof PileHandler) ) continue; - PileHandler pile = (PileHandler) handler; - pile.delegate(newHandler); - pile.close(); - } - } - - this.talkPreview = new TalkPreview(); - this.talkPreview.pack(); - this.talkPreview.setSize(700, 500); - this.talkPreview.setVisible(false); - this.talkPreview.loadDraft(); - - this.optionPanel = new OptionPanel(this.topFrame); - this.optionPanel.pack(); - this.optionPanel.setSize(450, 500); - this.optionPanel.setVisible(false); - - this.findPanel = new FindPanel(this.topFrame); - this.findPanel.pack(); - this.findPanel.setVisible(false); - this.findPanel.loadHistory(); - - this.windowMap.put(this.filterFrame, true); - this.windowMap.put(this.showlogFrame, false); - this.windowMap.put(this.talkPreview, false); - this.windowMap.put(this.optionPanel, false); - this.windowMap.put(this.findPanel, true); - - AppSetting setting = Jindolf.getAppSetting(); - - FontInfo fontInfo = setting.getFontInfo(); - this.topView.getTabBrowser().setFontInfo(fontInfo); - this.talkPreview.setFontInfo(fontInfo); - this.optionPanel.getFontChooser().setFontInfo(fontInfo); - - ProxyInfo proxyInfo = setting.getProxyInfo(); - this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo); - - DialogPref pref = setting.getDialogPref(); - this.topView.getTabBrowser().setDialogPref(pref); - this.optionPanel.getDialogPrefPanel().setDialogPref(pref); - - return; - } - - /** - * トップフレームを生成する。 - * @return トップフレーム - */ - @SuppressWarnings("serial") - public JFrame createTopFrame(){ - this.topFrame = new JFrame(); - - Container content = this.topFrame.getContentPane(); - LayoutManager layout = new BorderLayout(); - content.setLayout(layout); - content.add(this.topView, BorderLayout.CENTER); - - Component glassPane = new JComponent() {}; - glassPane.addMouseListener(new MouseAdapter() {}); - glassPane.addKeyListener(new KeyAdapter() {}); - this.topFrame.setGlassPane(glassPane); - - this.topFrame.setJMenuBar(this.actionManager.getMenuBar()); - setFrameTitle(null); - - this.windowMap.put(this.topFrame, false); - - this.topFrame.setDefaultCloseOperation( - WindowConstants.DISPOSE_ON_CLOSE); - this.topFrame.addWindowListener(new WindowAdapter(){ - @Override - public void windowClosed(WindowEvent event){ - shutdown(); - } - }); - - return this.topFrame; - } - - /** - * About画面を表示する。 - */ - private void actionAbout(){ - String message = - Jindolf.TITLE - + " Version " + Jindolf.VERSION + "\n" - + Jindolf.COPYRIGHT + "\n" - + "ライセンス: " + Jindolf.LICENSE + "\n" - + "連絡先: " + Jindolf.CONTACT; - - if(Jindolf.COMMENT.length() > 0){ - message += "\n" + Jindolf.COMMENT; - } - - JOptionPane pane = new JOptionPane(message, - JOptionPane.INFORMATION_MESSAGE, - JOptionPane.DEFAULT_OPTION, - GUIUtils.getLogoIcon()); - - JDialog dialog = pane.createDialog(this.topFrame, - Jindolf.TITLE + "について"); - - dialog.pack(); - dialog.setVisible(true); - dialog.dispose(); - - return; - } - - /** - * アプリ終了。 - */ - private void actionExit(){ - shutdown(); - return; - } - - /** - * Help画面を表示する。 - */ - private void actionHelp(){ - if(this.helpFrame != null){ // show Toggle - toggleWindow(this.helpFrame); - return; - } - - this.helpFrame = new HelpFrame(); - this.helpFrame.pack(); - this.helpFrame.setSize(450, 450); - - this.windowMap.put(this.helpFrame, false); - - this.helpFrame.setVisible(true); - - return; - } - - /** - * 村をWebブラウザで表示する。 - */ - private void actionShowWebVillage(){ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - - Land land = village.getParentLand(); - ServerAccess server = land.getServerAccess(); - - URL url = server.getVillageURL(village); - - String urlText = url.toString(); - if(village.getState() != VillageState.GAMEOVER){ - urlText += "#bottom"; - } - - WebIPCDialog.showDialog(this.topFrame, urlText); - - return; - } - - /** - * 村に対応するまとめサイトをWebブラウザで表示する。 - */ - private void actionShowWebWiki(){ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - - String villageName; - LandDef landDef = village.getParentLand().getLandDef(); - if(landDef.getLandId().equals("wolfg")){ - String vnum = "000" + village.getVillageID(); - vnum = vnum.substring(vnum.length() - 3); - villageName = landDef.getLandPrefix() + vnum; - }else{ - villageName = village.getVillageName(); - } - - StringBuilder url = - new StringBuilder() - .append("http://wolfbbs.jp/") - .append(villageName) - .append("%C2%BC.html"); - - WebIPCDialog.showDialog(this.topFrame, url.toString()); - - return; - } - - /** - * 村に対応するキャスト紹介表ジェネレーターをWebブラウザで表示する。 - */ - private void actionShowWebCast(){ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - - Land land = village.getParentLand(); - ServerAccess server = land.getServerAccess(); - - URL villageUrl = server.getVillageURL(village); - - StringBuilder url = new StringBuilder("http://hon5.com/jinro/"); - - try{ - url.append("?u=") - .append(URLEncoder.encode(villageUrl.toString(), "UTF-8")); - }catch(UnsupportedEncodingException e){ - return; - } - - url.append("&s=1"); - - WebIPCDialog.showDialog(this.topFrame, url.toString()); - - return; - } - - /** - * 日(Period)をWebブラウザで表示する。 - */ - private void actionShowWebDay(){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - - Period period = periodView.getPeriod(); - if(period == null) return; - - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - - Land land = village.getParentLand(); - ServerAccess server = land.getServerAccess(); - - URL url = server.getPeriodURL(period); - - String urlText = url.toString(); - if(period.isHot()) urlText += "#bottom"; - - WebIPCDialog.showDialog(this.topFrame, urlText); - - return; - } - - /** - * 個別の発言をWebブラウザで表示する。 - */ - private void actionShowWebTalk(){ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - - Discussion discussion = periodView.getDiscussion(); - Talk talk = discussion.getPopupedTalk(); - if(talk == null) return; - - Period period = periodView.getPeriod(); - if(period == null) return; - - Land land = village.getParentLand(); - ServerAccess server = land.getServerAccess(); - - URL url = server.getPeriodURL(period); - - String urlText = url.toString(); - urlText += "#" + talk.getMessageID(); - WebIPCDialog.showDialog(this.topFrame, urlText); - - return; - } - - /** - * ポータルサイトをWebブラウザで表示する。 - */ - private void actionShowPortal(){ - WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT); - return; - } - - /** - * 例外発生による警告ダイアログへの反応を促す。 - * @param title タイトル文字列 - * @param message メッセージ - * @param e 例外 - */ - private void warnDialog(String title, String message, Throwable e){ - Jindolf.logger().warn(message, e); - JOptionPane.showMessageDialog( - this.topFrame, - message, - title + " - " + Jindolf.TITLE, - JOptionPane.WARNING_MESSAGE ); - return; - } - - /** - * L&Fの変更を行う。 - */ - private void actionChangeLaF(){ - String className = this.actionManager.getSelectedLookAndFeel(); - - String warnTitle = "Look&Feel"; - String warnMsg; - - Class lnfClass; - warnMsg = "このLook&Feel[" + className + "]を読み込む事ができません。"; - try{ - lnfClass = Class.forName(className); - }catch(ClassNotFoundException e){ - warnDialog(warnTitle, warnMsg, e); - return; - } - - LookAndFeel lnf; - warnMsg = "このLook&Feel[" + className + "]を生成する事ができません。"; - try{ - lnf = (LookAndFeel)( lnfClass.newInstance() ); - }catch(InstantiationException e){ - warnDialog(warnTitle, warnMsg, e); - return; - }catch(IllegalAccessException e){ - warnDialog(warnTitle, warnMsg, e); - return; - }catch(ClassCastException e){ - warnDialog(warnTitle, warnMsg, e); - return; - } - - warnMsg = "このLook&Feel[" + lnf.getName() + "]はサポートされていません。"; - try{ - UIManager.setLookAndFeel(lnf); - }catch(UnsupportedLookAndFeelException e){ - warnDialog(warnTitle, warnMsg, e); - return; - } - - Jindolf.logger().info( - "Look&Feelが[" - +lnf.getName() - +"]に変更されました。"); - - final Runnable updateUITask = new Runnable(){ - public void run(){ - Set windows = Controller.this.windowMap.keySet(); - for(Window window : windows){ - SwingUtilities.updateComponentTreeUI(window); - window.validate(); - boolean needPack = Controller.this.windowMap.get(window); - if(needPack){ - window.pack(); - } - } - - return; - } - }; - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("Look&Feelを更新中…"); - try{ - SwingUtilities.invokeAndWait(updateUITask); - }catch(InvocationTargetException e){ - Jindolf.logger().warn( - "Look&Feelの更新に失敗しました。", e); - }catch(InterruptedException e){ - Jindolf.logger().warn( - "Look&Feelの更新に失敗しました。", e); - }finally{ - updateStatusBar("Look&Feelが更新されました"); - setBusy(false); - } - return; - } - }); - - return; - } - - /** - * 発言フィルタ画面を表示する。 - */ - private void actionShowFilter(){ - toggleWindow(this.filterFrame); - return; - } - - /** - * アカウント管理画面を表示する。 - */ - private void actionShowAccount(){ - if(this.accountFrame != null){ // show Toggle - toggleWindow(this.accountFrame); - return; - } - - this.accountFrame = new AccountPanel(this.topFrame, this.model); - this.accountFrame.pack(); - this.accountFrame.setVisible(true); - - this.windowMap.put(this.accountFrame, true); - - return; - } - - /** - * ログ表示画面を表示する。 - */ - private void actionShowLog(){ - toggleWindow(this.showlogFrame); - return; - } - - /** - * 発言エディタを表示する。 - */ - private void actionTalkPreview(){ - toggleWindow(this.talkPreview); - return; - } - - /** - * オプション設定画面を表示する。 - */ - private void actionOption(){ - AppSetting setting = Jindolf.getAppSetting(); - - FontInfo fontInfo = setting.getFontInfo(); - this.optionPanel.getFontChooser().setFontInfo(fontInfo); - - ProxyInfo proxyInfo = setting.getProxyInfo(); - this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo); - - DialogPref dialogPref = setting.getDialogPref(); - this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref); - - this.optionPanel.setVisible(true); - if(this.optionPanel.isCanceled()) return; - - fontInfo = this.optionPanel.getFontChooser().getFontInfo(); - updateFontInfo(fontInfo); - - proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo(); - updateProxyInfo(proxyInfo); - - dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref(); - updateDialogPref(dialogPref); - - return; - } - - /** - * フォント設定を変更する。 - * @param newFontInfo 新フォント設定 - */ - private void updateFontInfo(final FontInfo newFontInfo){ - AppSetting setting = Jindolf.getAppSetting(); - FontInfo oldInfo = setting.getFontInfo(); - - if(newFontInfo.equals(oldInfo)) return; - setting.setFontInfo(newFontInfo); - - this.topView.getTabBrowser().setFontInfo(newFontInfo); - this.talkPreview.setFontInfo(newFontInfo); - this.optionPanel.getFontChooser().setFontInfo(newFontInfo); - - return; - } - - /** - * プロクシ設定を変更する。 - * @param newProxyInfo 新プロクシ設定 - */ - private void updateProxyInfo(ProxyInfo newProxyInfo){ - AppSetting setting = Jindolf.getAppSetting(); - ProxyInfo oldProxyInfo = setting.getProxyInfo(); - - if(newProxyInfo.equals(oldProxyInfo)) return; - setting.setProxyInfo(newProxyInfo); - - for(Land land : this.model.getLandList()){ - ServerAccess server = land.getServerAccess(); - server.setProxy(newProxyInfo.getProxy()); - } - - return; - } - - /** - * 発言表示設定を更新する。 - * @param newDialogPref 表示設定 - */ - private void updateDialogPref(DialogPref newDialogPref){ - AppSetting setting = Jindolf.getAppSetting(); - DialogPref oldDialogPref = setting.getDialogPref(); - - if(newDialogPref.equals(oldDialogPref)) return; - setting.setDialogPref(newDialogPref); - - this.topView.getTabBrowser().setDialogPref(newDialogPref); - - return; - } - - /** - * 村ダイジェスト画面を表示する。 - */ - private void actionShowDigest(){ - TabBrowser browser = this.topView.getTabBrowser(); - final Village village = browser.getVillage(); - if(village == null) return; - - VillageState villageState = village.getState(); - if(( villageState != VillageState.EPILOGUE - && villageState != VillageState.GAMEOVER - ) || ! village.isValid() ){ - String message = "エピローグを正常に迎えていない村は\n" - +"ダイジェスト機能を利用できません"; - String title = "ダイジェスト不可 - " + Jindolf.TITLE; - JOptionPane pane = new JOptionPane(message, - JOptionPane.WARNING_MESSAGE, - JOptionPane.DEFAULT_OPTION ); - JDialog dialog = pane.createDialog(this.topFrame, title); - dialog.pack(); - dialog.setVisible(true); - dialog.dispose(); - return; - } - - if(this.digestPanel == null){ - this.digestPanel = new VillageDigest(this.topFrame); - this.digestPanel.pack(); - this.digestPanel.setSize(600, 550); - this.windowMap.put(this.digestPanel, false); - } - - final VillageDigest digest = this.digestPanel; - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - taskFullOpenAllPeriod(); - EventQueue.invokeLater(new Runnable(){ - public void run(){ - digest.setVillage(village); - digest.setVisible(true); - return; - } - }); - return; - } - }); - - return; - } - - /** - * 全日程の一括フルオープン。ヘビータスク版。 - */ - // TODO taskLoadAllPeriodtと一体化したい。 - private void taskFullOpenAllPeriod(){ - setBusy(true); - updateStatusBar("一括読み込み開始"); - try{ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - for(PeriodView periodView : browser.getPeriodViewList()){ - Period period = periodView.getPeriod(); - if(period == null) continue; - if(period.isFullOpen()) continue; - String message = - period.getDay() - + "日目のデータを読み込んでいます"; - updateStatusBar(message); - try{ - Period.parsePeriod(period, true); - }catch(IOException e){ - showNetworkError(village, e); - return; - } - periodView.showTopics(); - } - }finally{ - updateStatusBar("一括読み込み完了"); - setBusy(false); - } - return; - } - - /** - * 検索パネルを表示する。 - */ - private void actionShowFind(){ - this.findPanel.setVisible(true); - if(this.findPanel.isCanceled()){ - updateFindPanel(); - return; - } - if(this.findPanel.isBulkSearch()){ - bulkSearch(); - }else{ - regexSearch(); - } - return; - } - - /** - * 検索処理。 - */ - private void regexSearch(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - - RegexPattern regPattern = this.findPanel.getRegexPattern(); - int hits = discussion.setRegexPattern(regPattern); - - String hitMessage = "ï¼»" + hits + "]件ヒットしました"; - updateStatusBar(hitMessage); - - String loginfo = ""; - if(regPattern != null){ - Pattern pattern = regPattern.getPattern(); - if(pattern != null){ - loginfo = "正規表現 " + pattern.pattern() + " に"; - } - } - loginfo += hitMessage; - Jindolf.logger().info(loginfo); - - return; - } - - /** - * 一括検索処理。 - */ - private void bulkSearch(){ - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - taskBulkSearch(); - return; - } - }); - } - - /** - * 一括検索処理。ヘビータスク版。 - */ - private void taskBulkSearch(){ - taskLoadAllPeriod(); - int totalhits = 0; - RegexPattern regPattern = this.findPanel.getRegexPattern(); - StringBuilder hitDesc = new StringBuilder(); - TabBrowser browser = this.topView.getTabBrowser(); - for(PeriodView periodView : browser.getPeriodViewList()){ - Discussion discussion = periodView.getDiscussion(); - int hits = discussion.setRegexPattern(regPattern); - totalhits += hits; - - if(hits > 0){ - Period period = discussion.getPeriod(); - hitDesc.append(' ').append(period.getDay()).append("d:"); - hitDesc.append(hits).append("件"); - } - } - String hitMessage = - "ï¼»" + totalhits + "]件ヒットしました。" - + hitDesc.toString(); - updateStatusBar(hitMessage); - - String loginfo = ""; - if(regPattern != null){ - Pattern pattern = regPattern.getPattern(); - if(pattern != null){ - loginfo = "正規表現 " + pattern.pattern() + " に"; - } - } - loginfo += hitMessage; - Jindolf.logger().info(loginfo); - - return; - } - - /** - * 検索パネルに現在選択中のPeriodを反映させる。 - */ - private void updateFindPanel(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - RegexPattern pattern = discussion.getRegexPattern(); - this.findPanel.setRegexPattern(pattern); - return; - } - - /** - * 発言集計パネルを表示。 - */ - private void actionDaySummary(){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - - Period period = periodView.getPeriod(); - if(period == null) return; - - if(this.daySummaryPanel == null){ - this.daySummaryPanel = new DaySummary(this.topFrame); - this.daySummaryPanel.pack(); - this.daySummaryPanel.setSize(400, 500); - } - - this.daySummaryPanel.summaryPeriod(period); - this.daySummaryPanel.setVisible(true); - - this.windowMap.put(this.daySummaryPanel, false); - - return; - } - - /** - * 表示中PeriodをCSVファイルへエクスポートする。 - */ - private void actionDayExportCsv(){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - - Period period = periodView.getPeriod(); - if(period == null) return; - - File file = CsvExporter.exportPeriod(period, this.filterFrame); - if(file != null){ - String message = "CSVファイル(" - +file.getName() - +")へのエクスポートが完了しました"; - updateStatusBar(message); - } - - // TODO 長そうなジョブなら別スレッドにした方がいいか? - - return; - } - - /** - * 検索結果の次候補へジャンプ。 - */ - private void actionSearchNext(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - - discussion.nextHotTarget(); - - return; - } - - /** - * 検索結果の全候補へジャンプ。 - */ - private void actionSearchPrev(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - - discussion.prevHotTarget(); - - return; - } - - /** - * Period表示の強制再更新処理。 - */ - private void actionReloadPeriod(){ - updatePeriod(true); - - TabBrowser tabBrowser = this.topView.getTabBrowser(); - Village village = tabBrowser.getVillage(); - if(village == null) return; - if(village.getState() != VillageState.EPILOGUE) return; - - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - Period period = discussion.getPeriod(); - if(period == null) return; - if(period.getTopics() > 1000){ - JOptionPane.showMessageDialog(this.topFrame, - "エピローグが1000発言を超えはじめたら、\n" - +"負荷対策のためWebブラウザによるアクセスを" - +"心がけましょう", - "長大エピローグ警告", - JOptionPane.WARNING_MESSAGE - ); - } - - return; - } - - /** - * 全日程の一括ロード。 - */ - private void actionLoadAllPeriod(){ - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - taskLoadAllPeriod(); - return; - } - }); - - return; - } - - /** - * 全日程の一括ロード。ヘビータスク版。 - */ - private void taskLoadAllPeriod(){ - setBusy(true); - updateStatusBar("一括読み込み開始"); - try{ - TabBrowser browser = this.topView.getTabBrowser(); - Village village = browser.getVillage(); - if(village == null) return; - for(PeriodView periodView : browser.getPeriodViewList()){ - Period period = periodView.getPeriod(); - if(period == null) continue; - String message = - period.getDay() - + "日目のデータを読み込んでいます"; - updateStatusBar(message); - try{ - Period.parsePeriod(period, false); - }catch(IOException e){ - showNetworkError(village, e); - return; - } - periodView.showTopics(); - } - }finally{ - updateStatusBar("一括読み込み完了"); - setBusy(false); - } - return; - } - - /** - * 村一覧の再読み込み。 - */ - private void actionReloadVillageList(){ - JTree tree = this.topView.getTreeView(); - TreePath path = tree.getSelectionPath(); - if(path == null) return; - - Land land = null; - for(int ct = 0; ct < path.getPathCount(); ct++){ - Object obj = path.getPathComponent(ct); - if(obj instanceof Land){ - land = (Land) obj; - break; - } - } - if(land == null) return; - - this.topView.showInitPanel(); - - execReloadVillageList(land); - - return; - } - - /** - * 選択文字列をクリップボードにコピーする。 - */ - private void actionCopySelected(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - - CharSequence copied = discussion.copySelected(); - if(copied == null) return; - - copied = StringUtils.suppressString(copied); - updateStatusBar( - "[" + copied + "]をクリップボードにコピーしました"); - return; - } - - /** - * 一発言のみクリップボードにコピーする。 - */ - private void actionCopyTalk(){ - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - - CharSequence copied = discussion.copyTalk(); - if(copied == null) return; - - copied = StringUtils.suppressString(copied); - updateStatusBar( - "[" + copied + "]をクリップボードにコピーしました"); - return; - } - - /** - * アンカーにジャンプする。 - */ - private void actionJumpAnchor(){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - Discussion discussion = periodView.getDiscussion(); - - final TabBrowser browser = this.topView.getTabBrowser(); - final Village village = browser.getVillage(); - final Anchor anchor = discussion.getPopupedAnchor(); - if(anchor == null) return; - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("ジャンプ先の読み込み中…"); - - if(anchor.hasTalkNo()){ - // TODO もう少し賢くならない? - taskLoadAllPeriod(); - } - - final List talkList; - try{ - talkList = village.getTalkListFromAnchor(anchor); - if(talkList == null || talkList.size() <= 0){ - updateStatusBar( - "アンカーのジャンプ先[" - + anchor.toString() - + "]が見つかりません"); - return; - } - - final Talk targetTalk = talkList.get(0); - final Period targetPeriod = targetTalk.getPeriod(); - final int tabIndex = targetPeriod.getDay() + 1; - final PeriodView target = browser.getPeriodView(tabIndex); - - EventQueue.invokeLater(new Runnable(){ - public void run(){ - browser.setSelectedIndex(tabIndex); - target.setPeriod(targetPeriod); - target.scrollToTalk(targetTalk); - return; - } - }); - updateStatusBar( - "アンカー[" - + anchor.toString() - + "]にジャンプしました"); - }catch(IOException e){ - updateStatusBar( - "アンカーの展開中にエラーが起きました"); - }finally{ - setBusy(false); - } - - return; - } - }); - - return; - } - - /** - * 指定した国の村一覧を読み込む。 - * @param land 国 - */ - private void execReloadVillageList(final Land land){ - final LandsTree treePanel = this.topView.getLandsTree(); - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("村一覧を読み込み中…"); - try{ - try{ - Controller.this.model.loadVillageList(land); - }catch(IOException e){ - showNetworkError(land, e); - } - treePanel.expandLand(land); - }finally{ - updateStatusBar("村一覧の読み込み完了"); - setBusy(false); - } - return; - } - }); - return; - } - - /** - * Period表示の更新処理。 - * @param force trueならPeriodデータを強制再読み込み。 - */ - private void updatePeriod(final boolean force){ - final TabBrowser tabBrowser = this.topView.getTabBrowser(); - final Village village = tabBrowser.getVillage(); - if(village == null) return; - setFrameTitle(village.getVillageFullName()); - - final PeriodView periodView = currentPeriodView(); - Discussion discussion = currentDiscussion(); - if(discussion == null) return; - discussion.setTopicFilter(this.filterFrame); - final Period period = discussion.getPeriod(); - if(period == null) return; - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - try{ - boolean wasHot = loadPeriod(); - - if(wasHot && ! period.isHot() ){ - if( ! updatePeriodList() ) return; - } - - renderBrowser(); - }finally{ - setBusy(false); - } - return; - } - - private boolean loadPeriod(){ - updateStatusBar("1日分のデータを読み込んでいます…"); - boolean wasHot; - try{ - wasHot = period.isHot(); - try{ - Period.parsePeriod(period, force); - }catch(IOException e){ - showNetworkError(village, e); - } - }finally{ - updateStatusBar("1日分のデータを読み終わりました"); - } - return wasHot; - } - - private boolean updatePeriodList(){ - updateStatusBar("村情報を読み直しています…"); - try{ - Village.updateVillage(village); - }catch(IOException e){ - showNetworkError(village, e); - return false; - } - try{ - SwingUtilities.invokeAndWait(new Runnable(){ - public void run(){ - tabBrowser.setVillage(village); - return; - } - }); - }catch(InvocationTargetException e){ - Jindolf.logger().fatal( - "タブ操作で致命的な障害が発生しました", e); - }catch(InterruptedException e){ - Jindolf.logger().fatal( - "タブ操作で致命的な障害が発生しました", e); - } - updateStatusBar("村情報を読み直しました…"); - return true; - } - - private void renderBrowser(){ - updateStatusBar("レンダリング中…"); - try{ - final int lastPos = periodView.getVerticalPosition(); - try{ - SwingUtilities.invokeAndWait(new Runnable(){ - public void run(){ - periodView.showTopics(); - return; - } - }); - }catch(InvocationTargetException e){ - Jindolf.logger().fatal( - "ブラウザ表示で致命的な障害が発生しました", e); - }catch(InterruptedException e){ - Jindolf.logger().fatal( - "ブラウザ表示で致命的な障害が発生しました", e); - } - EventQueue.invokeLater(new Runnable(){ - public void run(){ - periodView.setVerticalPosition(lastPos); - } - }); - }finally{ - updateStatusBar("レンダリング完了"); - } - return; - } - }); - - return; - } - - /** - * 発言フィルタの操作による更新処理。 - */ - private void filterChanged(){ - final Discussion discussion = currentDiscussion(); - if(discussion == null) return; - discussion.setTopicFilter(this.filterFrame); - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("フィルタリング中…"); - try{ - discussion.filtering(); - }finally{ - updateStatusBar("フィルタリング完了"); - setBusy(false); - } - return; - } - }); - - return; - } - - /** - * 現在選択中のPeriodを内包するPeriodViewを返す。 - * @return PeriodView - */ - private PeriodView currentPeriodView(){ - TabBrowser tb = this.topView.getTabBrowser(); - PeriodView result = tb.currentPeriodView(); - return result; - } - - /** - * 現在選択中のPeriodを内包するDiscussionを返す。 - * @return Discussion - */ - private Discussion currentDiscussion(){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return null; - Discussion result = periodView.getDiscussion(); - return result; - } - - /** - * フレーム表示のトグル処理。 - * @param window フレーム - */ - private void toggleWindow(Window window){ - if(window == null) return; - - if(window instanceof Frame){ - Frame frame = (Frame) window; - int winState = frame.getExtendedState(); - boolean isIconified = (winState & Frame.ICONIFIED) != 0; - if(isIconified){ - winState &= ~(Frame.ICONIFIED); - frame.setExtendedState(winState); - frame.setVisible(true); - return; - } - } - - if(window.isVisible()){ - window.setVisible(false); - window.dispose(); - }else{ - window.setVisible(true); - } - return; - } - - /** - * ネットワークエラーを通知するモーダルダイアログを表示する。 - * OKボタンを押すまでこのメソッドは戻ってこない。 - * @param village 村 - * @param e ネットワークエラー - */ - public void showNetworkError(Village village, IOException e){ - Land land = village.getParentLand(); - showNetworkError(land, e); - return; - } - - /** - * ネットワークエラーを通知するモーダルダイアログを表示する。 - * OKボタンを押すまでこのメソッドは戻ってこない。 - * @param land 国 - * @param e ネットワークエラー - */ - public void showNetworkError(Land land, IOException e){ - Jindolf.logger().warn("ネットワークで障害が発生しました", e); - - ServerAccess server = land.getServerAccess(); - String message = - land.getLandDef().getLandName() - +"を運営するサーバとの間の通信で" - +"何らかのトラブルが発生しました。\n" - +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n" - +"プロクシ設定は正しいかな?\n" - +"Webブラウザでも遊べないか確認してみてね!\n"; - - JOptionPane pane = new JOptionPane(message, - JOptionPane.WARNING_MESSAGE, - JOptionPane.DEFAULT_OPTION ); - - JDialog dialog = pane.createDialog(this.topFrame, - "通信異常発生 - " + Jindolf.TITLE); - - dialog.pack(); - dialog.setVisible(true); - dialog.dispose(); - - return; - } - - /** - * {@inheritDoc} - * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。 - * @param event イベント {@inheritDoc} - */ - @Override - public void valueChanged(TreeSelectionEvent event){ - TreePath path = event.getNewLeadSelectionPath(); - if(path == null) return; - - Object selObj = path.getLastPathComponent(); - - if( selObj instanceof Land ){ - Land land = (Land)selObj; - setFrameTitle(land.getLandDef().getLandName()); - this.topView.showLandInfo(land); - this.actionManager.appearVillage(false); - this.actionManager.appearPeriod(false); - }else if( selObj instanceof Village ){ - final Village village = (Village)selObj; - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("村情報を読み込み中…"); - - try{ - Village.updateVillage(village); - }catch(IOException e){ - showNetworkError(village, e); - return; - }finally{ - updateStatusBar("村情報の読み込み完了"); - setBusy(false); - } - - Controller.this.actionManager.appearVillage(true); - setFrameTitle(village.getVillageFullName()); - - EventQueue.invokeLater(new Runnable(){ - public void run(){ - Controller.this.topView.showVillageInfo(village); - return; - } - }); - - return; - } - }); - } - - return; - } - - /** - * {@inheritDoc} - * Periodがタブ選択されたときもしくは発言フィルタが操作されたときの処理。 - * @param event イベント {@inheritDoc} - */ - @Override - public void stateChanged(ChangeEvent event){ - Object source = event.getSource(); - - if(source == this.filterFrame){ - filterChanged(); - }else if(source instanceof TabBrowser){ - updateFindPanel(); - updatePeriod(false); - PeriodView periodView = currentPeriodView(); - if(periodView == null) this.actionManager.appearPeriod(false); - else this.actionManager.appearPeriod(true); - } - return; - } - - /** - * {@inheritDoc} - * 主にメニュー選択やボタン押下など。 - * @param e イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent e){ - if(this.isBusyNow) return; - - String cmd = e.getActionCommand(); - if(cmd.equals(ActionManager.CMD_ACCOUNT)){ - actionShowAccount(); - }else if(cmd.equals(ActionManager.CMD_EXIT)){ - actionExit(); - }else if(cmd.equals(ActionManager.CMD_COPY)){ - actionCopySelected(); - }else if(cmd.equals(ActionManager.CMD_SHOWFIND)){ - actionShowFind(); - }else if(cmd.equals(ActionManager.CMD_SEARCHNEXT)){ - actionSearchNext(); - }else if(cmd.equals(ActionManager.CMD_SEARCHPREV)){ - actionSearchPrev(); - }else if(cmd.equals(ActionManager.CMD_ALLPERIOD)){ - actionLoadAllPeriod(); - }else if(cmd.equals(ActionManager.CMD_SHOWDIGEST)){ - actionShowDigest(); - }else if(cmd.equals(ActionManager.CMD_WEBVILL)){ - actionShowWebVillage(); - }else if(cmd.equals(ActionManager.CMD_WEBWIKI)){ - actionShowWebWiki(); - }else if(cmd.equals(ActionManager.CMD_WEBCAST)){ - actionShowWebCast(); - }else if(cmd.equals(ActionManager.CMD_RELOAD)){ - actionReloadPeriod(); - }else if(cmd.equals(ActionManager.CMD_DAYSUMMARY)){ - actionDaySummary(); - }else if(cmd.equals(ActionManager.CMD_DAYEXPCSV)){ - actionDayExportCsv(); - }else if(cmd.equals(ActionManager.CMD_WEBDAY)){ - actionShowWebDay(); - }else if(cmd.equals(ActionManager.CMD_OPTION)){ - actionOption(); - }else if(cmd.equals(ActionManager.CMD_LANDF)){ - actionChangeLaF(); - }else if(cmd.equals(ActionManager.CMD_SHOWFILT)){ - actionShowFilter(); - }else if(cmd.equals(ActionManager.CMD_SHOWEDIT)){ - actionTalkPreview(); - }else if(cmd.equals(ActionManager.CMD_SHOWLOG)){ - actionShowLog(); - }else if(cmd.equals(ActionManager.CMD_HELPDOC)){ - actionHelp(); - }else if(cmd.equals(ActionManager.CMD_SHOWPORTAL)){ - actionShowPortal(); - }else if(cmd.equals(ActionManager.CMD_ABOUT)){ - actionAbout(); - }else if(cmd.equals(ActionManager.CMD_VILLAGELIST)){ - actionReloadVillageList(); - }else if(cmd.equals(ActionManager.CMD_COPYTALK)){ - actionCopyTalk(); - }else if(cmd.equals(ActionManager.CMD_JUMPANCHOR)){ - actionJumpAnchor(); - }else if(cmd.equals(ActionManager.CMD_WEBTALK)){ - actionShowWebTalk(); - } - return; - } - - /** - * {@inheritDoc} - * 村選択ツリーリストが畳まれるとき呼ばれる。 - * @param event ツリーイベント {@inheritDoc} - */ - @Override - public void treeWillCollapse(TreeExpansionEvent event){ - return; - } - - /** - * {@inheritDoc} - * 村選択ツリーリストが展開されるとき呼ばれる。 - * @param event ツリーイベント {@inheritDoc} - */ - @Override - public void treeWillExpand(TreeExpansionEvent event){ - if(!(event.getSource() instanceof JTree)){ - return; - } - - TreePath path = event.getPath(); - Object lastObj = path.getLastPathComponent(); - if(!(lastObj instanceof Land)){ - return; - } - final Land land = (Land) lastObj; - if(land.getVillageCount() > 0){ - return; - } - - execReloadVillageList(land); - - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void anchorHitted(AnchorHitEvent event){ - PeriodView periodView = currentPeriodView(); - if(periodView == null) return; - Period period = periodView.getPeriod(); - if(period == null) return; - final Village village = period.getVillage(); - - final TalkDraw talkDraw = event.getTalkDraw(); - final Anchor anchor = event.getAnchor(); - final Discussion discussion = periodView.getDiscussion(); - - Executor executor = Executors.newCachedThreadPool(); - executor.execute(new Runnable(){ - public void run(){ - setBusy(true); - updateStatusBar("アンカーの展開中…"); - - if(anchor.hasTalkNo()){ - // TODO もう少し賢くならない? - taskLoadAllPeriod(); - } - - final List talkList; - try{ - talkList = village.getTalkListFromAnchor(anchor); - if(talkList == null || talkList.size() <= 0){ - updateStatusBar( - "アンカーの展開先[" - + anchor.toString() - + "]が見つかりません"); - return; - } - EventQueue.invokeLater(new Runnable(){ - public void run(){ - talkDraw.showAnchorTalks(anchor, talkList); - discussion.layoutRows(); - return; - } - }); - updateStatusBar( - "アンカー[" - + anchor.toString() - + "]の展開完了"); - }catch(IOException e){ - updateStatusBar( - "アンカーの展開中にエラーが起きました"); - }finally{ - setBusy(false); - } - - return; - } - }); - - return; - } - - /** - * ヘビーなタスク実行をアピール。 - * プログレスバーとカーソルの設定を行う。 - * @param isBusy trueならプログレスバーのアニメ開始&WAITカーソル。 - * falseなら停止&通常カーソル。 - */ - private void setBusy(final boolean isBusy){ - this.isBusyNow = isBusy; - - Runnable microJob = new Runnable(){ - public void run(){ - Cursor cursor; - if(isBusy){ - cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); - }else{ - cursor = Cursor.getDefaultCursor(); - } - - Component glass = Controller.this.topFrame.getGlassPane(); - glass.setCursor(cursor); - glass.setVisible(isBusy); - Controller.this.topView.setBusy(isBusy); - - return; - } - }; - - if(SwingUtilities.isEventDispatchThread()){ - microJob.run(); - }else{ - try{ - SwingUtilities.invokeAndWait(microJob); - }catch(InvocationTargetException e){ - Jindolf.logger().fatal("ビジー処理で失敗", e); - }catch(InterruptedException e){ - Jindolf.logger().fatal("ビジー処理で失敗", e); - } - } - - return; - } - - /** - * ステータスバーを更新する。 - * @param message メッセージ - */ - private void updateStatusBar(String message){ - this.topView.updateSysMessage(message); - } - - /** - * トップフレームのタイトルを設定する。 - * タイトルは指定された国or村名 + " - Jindolf" - * @param name 国or村名 - */ - private void setFrameTitle(CharSequence name){ - String title = Jindolf.TITLE; - - if(name != null && name.length() > 0){ - title = name + " - " + title; - } - - this.topFrame.setTitle(title); - - return; - } - - /** - * アプリ正常終了処理。 - */ - private void shutdown(){ - this.findPanel.saveHistory(); - this.talkPreview.saveDraft(); - Jindolf.getAppSetting().saveConfig(); - Jindolf.exit(0); - return; - } - -} +/* + * MVC controller + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.LayoutManager; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.MouseAdapter; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.logging.Handler; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; +import javax.swing.JTree; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.WindowConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.event.TreeWillExpandListener; +import javax.swing.tree.TreePath; +import jp.sourceforge.jindolf.corelib.LandDef; +import jp.sourceforge.jindolf.corelib.VillageState; + +/** + * いわゆるMVCでいうとこのコントローラ。 + */ +public class Controller + implements ActionListener, + TreeWillExpandListener, + TreeSelectionListener, + ChangeListener, + AnchorHitListener { + + private final ActionManager actionManager; + private final TopView topView; + private final LandsModel model; + + private final FilterPanel filterFrame; + private final LogFrame showlogFrame; + private final OptionPanel optionPanel; + private final FindPanel findPanel; + private final TalkPreview talkPreview; + private JFrame helpFrame; + private AccountPanel accountFrame; + private DaySummary daySummaryPanel; + private VillageDigest digestPanel; + private final Map windowMap = + new HashMap(); + + private volatile boolean isBusyNow; + + private JFrame topFrame = null; + + /** + * コントローラの生成。 + * @param actionManager アクション管理 + * @param topView 最上位ビュー + * @param model 最上位データモデル + */ + public Controller(ActionManager actionManager, + TopView topView, + LandsModel model){ + super(); + + this.actionManager = actionManager; + this.topView = topView; + this.model = model; + + JToolBar toolbar = this.actionManager.getBrowseToolBar(); + this.topView.setBrowseToolBar(toolbar); + + this.actionManager.addActionListener(this); + + JTree treeView = this.topView.getTreeView(); + treeView.setModel(this.model); + treeView.addTreeWillExpandListener(this); + treeView.addTreeSelectionListener(this); + + this.topView.getTabBrowser().addChangeListener(this); + this.topView.getTabBrowser().addActionListener(this); + this.topView.getTabBrowser().addAnchorHitListener(this); + + JButton reloadVillageListButton = this.topView + .getLandsTree() + .getReloadVillageListButton(); + reloadVillageListButton.addActionListener(this); + reloadVillageListButton.setEnabled(false); + + this.filterFrame = new FilterPanel(this.topFrame); + this.filterFrame.addChangeListener(this); + this.filterFrame.pack(); + this.filterFrame.setVisible(false); + + this.showlogFrame = new LogFrame(this.topFrame); + this.showlogFrame.pack(); + this.showlogFrame.setSize(600, 500); + this.showlogFrame.setLocationByPlatform(true); + this.showlogFrame.setVisible(false); + if(Jindolf.hasLoggingPermission()){ + Handler newHandler = this.showlogFrame.getHandler(); + Logger jre14Logger = Jindolf.logger().getJre14Logger(); + jre14Logger.addHandler(newHandler); + Handler[] handlers = jre14Logger.getHandlers(); + for(Handler handler : handlers){ + if( ! (handler instanceof PileHandler) ) continue; + PileHandler pile = (PileHandler) handler; + pile.delegate(newHandler); + pile.close(); + } + } + + this.talkPreview = new TalkPreview(); + this.talkPreview.pack(); + this.talkPreview.setSize(700, 500); + this.talkPreview.setVisible(false); + this.talkPreview.loadDraft(); + + this.optionPanel = new OptionPanel(this.topFrame); + this.optionPanel.pack(); + this.optionPanel.setSize(450, 500); + this.optionPanel.setVisible(false); + + this.findPanel = new FindPanel(this.topFrame); + this.findPanel.pack(); + this.findPanel.setVisible(false); + this.findPanel.loadHistory(); + + this.windowMap.put(this.filterFrame, true); + this.windowMap.put(this.showlogFrame, false); + this.windowMap.put(this.talkPreview, false); + this.windowMap.put(this.optionPanel, false); + this.windowMap.put(this.findPanel, true); + + AppSetting setting = Jindolf.getAppSetting(); + + FontInfo fontInfo = setting.getFontInfo(); + this.topView.getTabBrowser().setFontInfo(fontInfo); + this.talkPreview.setFontInfo(fontInfo); + this.optionPanel.getFontChooser().setFontInfo(fontInfo); + + ProxyInfo proxyInfo = setting.getProxyInfo(); + this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo); + + DialogPref pref = setting.getDialogPref(); + this.topView.getTabBrowser().setDialogPref(pref); + this.optionPanel.getDialogPrefPanel().setDialogPref(pref); + + return; + } + + /** + * トップフレームを生成する。 + * @return トップフレーム + */ + @SuppressWarnings("serial") + public JFrame createTopFrame(){ + this.topFrame = new JFrame(); + + Container content = this.topFrame.getContentPane(); + LayoutManager layout = new BorderLayout(); + content.setLayout(layout); + content.add(this.topView, BorderLayout.CENTER); + + Component glassPane = new JComponent() {}; + glassPane.addMouseListener(new MouseAdapter() {}); + glassPane.addKeyListener(new KeyAdapter() {}); + this.topFrame.setGlassPane(glassPane); + + this.topFrame.setJMenuBar(this.actionManager.getMenuBar()); + setFrameTitle(null); + + this.windowMap.put(this.topFrame, false); + + this.topFrame.setDefaultCloseOperation( + WindowConstants.DISPOSE_ON_CLOSE); + this.topFrame.addWindowListener(new WindowAdapter(){ + @Override + public void windowClosed(WindowEvent event){ + shutdown(); + } + }); + + return this.topFrame; + } + + /** + * About画面を表示する。 + */ + private void actionAbout(){ + String message = + Jindolf.TITLE + + " Version " + Jindolf.VERSION + "\n" + + Jindolf.COPYRIGHT + "\n" + + "ライセンス: " + Jindolf.LICENSE + "\n" + + "連絡先: " + Jindolf.CONTACT; + + if(Jindolf.COMMENT.length() > 0){ + message += "\n" + Jindolf.COMMENT; + } + + JOptionPane pane = new JOptionPane(message, + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.DEFAULT_OPTION, + GUIUtils.getLogoIcon()); + + JDialog dialog = pane.createDialog(this.topFrame, + Jindolf.TITLE + "について"); + + dialog.pack(); + dialog.setVisible(true); + dialog.dispose(); + + return; + } + + /** + * アプリ終了。 + */ + private void actionExit(){ + shutdown(); + return; + } + + /** + * Help画面を表示する。 + */ + private void actionHelp(){ + if(this.helpFrame != null){ // show Toggle + toggleWindow(this.helpFrame); + return; + } + + this.helpFrame = new HelpFrame(); + this.helpFrame.pack(); + this.helpFrame.setSize(450, 450); + + this.windowMap.put(this.helpFrame, false); + + this.helpFrame.setVisible(true); + + return; + } + + /** + * 村をWebブラウザで表示する。 + */ + private void actionShowWebVillage(){ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + + Land land = village.getParentLand(); + ServerAccess server = land.getServerAccess(); + + URL url = server.getVillageURL(village); + + String urlText = url.toString(); + if(village.getState() != VillageState.GAMEOVER){ + urlText += "#bottom"; + } + + WebIPCDialog.showDialog(this.topFrame, urlText); + + return; + } + + /** + * 村に対応するまとめサイトをWebブラウザで表示する。 + */ + private void actionShowWebWiki(){ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + + String villageName; + LandDef landDef = village.getParentLand().getLandDef(); + if(landDef.getLandId().equals("wolfg")){ + String vnum = "000" + village.getVillageID(); + vnum = vnum.substring(vnum.length() - 3); + villageName = landDef.getLandPrefix() + vnum; + }else{ + villageName = village.getVillageName(); + } + + StringBuilder url = + new StringBuilder() + .append("http://wolfbbs.jp/") + .append(villageName) + .append("%C2%BC.html"); + + WebIPCDialog.showDialog(this.topFrame, url.toString()); + + return; + } + + /** + * 村に対応するキャスト紹介表ジェネレーターをWebブラウザで表示する。 + */ + private void actionShowWebCast(){ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + + Land land = village.getParentLand(); + ServerAccess server = land.getServerAccess(); + + URL villageUrl = server.getVillageURL(village); + + StringBuilder url = new StringBuilder("http://hon5.com/jinro/"); + + try{ + url.append("?u=") + .append(URLEncoder.encode(villageUrl.toString(), "UTF-8")); + }catch(UnsupportedEncodingException e){ + return; + } + + url.append("&s=1"); + + WebIPCDialog.showDialog(this.topFrame, url.toString()); + + return; + } + + /** + * 日(Period)をWebブラウザで表示する。 + */ + private void actionShowWebDay(){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + + Period period = periodView.getPeriod(); + if(period == null) return; + + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + + Land land = village.getParentLand(); + ServerAccess server = land.getServerAccess(); + + URL url = server.getPeriodURL(period); + + String urlText = url.toString(); + if(period.isHot()) urlText += "#bottom"; + + WebIPCDialog.showDialog(this.topFrame, urlText); + + return; + } + + /** + * 個別の発言をWebブラウザで表示する。 + */ + private void actionShowWebTalk(){ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + + Discussion discussion = periodView.getDiscussion(); + Talk talk = discussion.getPopupedTalk(); + if(talk == null) return; + + Period period = periodView.getPeriod(); + if(period == null) return; + + Land land = village.getParentLand(); + ServerAccess server = land.getServerAccess(); + + URL url = server.getPeriodURL(period); + + String urlText = url.toString(); + urlText += "#" + talk.getMessageID(); + WebIPCDialog.showDialog(this.topFrame, urlText); + + return; + } + + /** + * ポータルサイトをWebブラウザで表示する。 + */ + private void actionShowPortal(){ + WebIPCDialog.showDialog(this.topFrame, Jindolf.CONTACT); + return; + } + + /** + * 例外発生による警告ダイアログへの反応を促す。 + * @param title タイトル文字列 + * @param message メッセージ + * @param e 例外 + */ + private void warnDialog(String title, String message, Throwable e){ + Jindolf.logger().warn(message, e); + JOptionPane.showMessageDialog( + this.topFrame, + message, + title + " - " + Jindolf.TITLE, + JOptionPane.WARNING_MESSAGE ); + return; + } + + /** + * L&Fの変更を行う。 + */ + private void actionChangeLaF(){ + String className = this.actionManager.getSelectedLookAndFeel(); + + String warnTitle = "Look&Feel"; + String warnMsg; + + Class lnfClass; + warnMsg = "このLook&Feel[" + className + "]を読み込む事ができません。"; + try{ + lnfClass = Class.forName(className); + }catch(ClassNotFoundException e){ + warnDialog(warnTitle, warnMsg, e); + return; + } + + LookAndFeel lnf; + warnMsg = "このLook&Feel[" + className + "]を生成する事ができません。"; + try{ + lnf = (LookAndFeel)( lnfClass.newInstance() ); + }catch(InstantiationException e){ + warnDialog(warnTitle, warnMsg, e); + return; + }catch(IllegalAccessException e){ + warnDialog(warnTitle, warnMsg, e); + return; + }catch(ClassCastException e){ + warnDialog(warnTitle, warnMsg, e); + return; + } + + warnMsg = "このLook&Feel[" + lnf.getName() + "]はサポートされていません。"; + try{ + UIManager.setLookAndFeel(lnf); + }catch(UnsupportedLookAndFeelException e){ + warnDialog(warnTitle, warnMsg, e); + return; + } + + Jindolf.logger().info( + "Look&Feelが[" + +lnf.getName() + +"]に変更されました。"); + + final Runnable updateUITask = new Runnable(){ + public void run(){ + Set windows = Controller.this.windowMap.keySet(); + for(Window window : windows){ + SwingUtilities.updateComponentTreeUI(window); + window.validate(); + boolean needPack = Controller.this.windowMap.get(window); + if(needPack){ + window.pack(); + } + } + + return; + } + }; + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("Look&Feelを更新中…"); + try{ + SwingUtilities.invokeAndWait(updateUITask); + }catch(InvocationTargetException e){ + Jindolf.logger().warn( + "Look&Feelの更新に失敗しました。", e); + }catch(InterruptedException e){ + Jindolf.logger().warn( + "Look&Feelの更新に失敗しました。", e); + }finally{ + updateStatusBar("Look&Feelが更新されました"); + setBusy(false); + } + return; + } + }); + + return; + } + + /** + * 発言フィルタ画面を表示する。 + */ + private void actionShowFilter(){ + toggleWindow(this.filterFrame); + return; + } + + /** + * アカウント管理画面を表示する。 + */ + private void actionShowAccount(){ + if(this.accountFrame != null){ // show Toggle + toggleWindow(this.accountFrame); + return; + } + + this.accountFrame = new AccountPanel(this.topFrame, this.model); + this.accountFrame.pack(); + this.accountFrame.setVisible(true); + + this.windowMap.put(this.accountFrame, true); + + return; + } + + /** + * ログ表示画面を表示する。 + */ + private void actionShowLog(){ + toggleWindow(this.showlogFrame); + return; + } + + /** + * 発言エディタを表示する。 + */ + private void actionTalkPreview(){ + toggleWindow(this.talkPreview); + return; + } + + /** + * オプション設定画面を表示する。 + */ + private void actionOption(){ + AppSetting setting = Jindolf.getAppSetting(); + + FontInfo fontInfo = setting.getFontInfo(); + this.optionPanel.getFontChooser().setFontInfo(fontInfo); + + ProxyInfo proxyInfo = setting.getProxyInfo(); + this.optionPanel.getProxyChooser().setProxyInfo(proxyInfo); + + DialogPref dialogPref = setting.getDialogPref(); + this.optionPanel.getDialogPrefPanel().setDialogPref(dialogPref); + + this.optionPanel.setVisible(true); + if(this.optionPanel.isCanceled()) return; + + fontInfo = this.optionPanel.getFontChooser().getFontInfo(); + updateFontInfo(fontInfo); + + proxyInfo = this.optionPanel.getProxyChooser().getProxyInfo(); + updateProxyInfo(proxyInfo); + + dialogPref = this.optionPanel.getDialogPrefPanel().getDialogPref(); + updateDialogPref(dialogPref); + + return; + } + + /** + * フォント設定を変更する。 + * @param newFontInfo 新フォント設定 + */ + private void updateFontInfo(final FontInfo newFontInfo){ + AppSetting setting = Jindolf.getAppSetting(); + FontInfo oldInfo = setting.getFontInfo(); + + if(newFontInfo.equals(oldInfo)) return; + setting.setFontInfo(newFontInfo); + + this.topView.getTabBrowser().setFontInfo(newFontInfo); + this.talkPreview.setFontInfo(newFontInfo); + this.optionPanel.getFontChooser().setFontInfo(newFontInfo); + + return; + } + + /** + * プロクシ設定を変更する。 + * @param newProxyInfo 新プロクシ設定 + */ + private void updateProxyInfo(ProxyInfo newProxyInfo){ + AppSetting setting = Jindolf.getAppSetting(); + ProxyInfo oldProxyInfo = setting.getProxyInfo(); + + if(newProxyInfo.equals(oldProxyInfo)) return; + setting.setProxyInfo(newProxyInfo); + + for(Land land : this.model.getLandList()){ + ServerAccess server = land.getServerAccess(); + server.setProxy(newProxyInfo.getProxy()); + } + + return; + } + + /** + * 発言表示設定を更新する。 + * @param newDialogPref 表示設定 + */ + private void updateDialogPref(DialogPref newDialogPref){ + AppSetting setting = Jindolf.getAppSetting(); + DialogPref oldDialogPref = setting.getDialogPref(); + + if(newDialogPref.equals(oldDialogPref)) return; + setting.setDialogPref(newDialogPref); + + this.topView.getTabBrowser().setDialogPref(newDialogPref); + + return; + } + + /** + * 村ダイジェスト画面を表示する。 + */ + private void actionShowDigest(){ + TabBrowser browser = this.topView.getTabBrowser(); + final Village village = browser.getVillage(); + if(village == null) return; + + VillageState villageState = village.getState(); + if(( villageState != VillageState.EPILOGUE + && villageState != VillageState.GAMEOVER + ) || ! village.isValid() ){ + String message = "エピローグを正常に迎えていない村は\n" + +"ダイジェスト機能を利用できません"; + String title = "ダイジェスト不可 - " + Jindolf.TITLE; + JOptionPane pane = new JOptionPane(message, + JOptionPane.WARNING_MESSAGE, + JOptionPane.DEFAULT_OPTION ); + JDialog dialog = pane.createDialog(this.topFrame, title); + dialog.pack(); + dialog.setVisible(true); + dialog.dispose(); + return; + } + + if(this.digestPanel == null){ + this.digestPanel = new VillageDigest(this.topFrame); + this.digestPanel.pack(); + this.digestPanel.setSize(600, 550); + this.windowMap.put(this.digestPanel, false); + } + + final VillageDigest digest = this.digestPanel; + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + taskFullOpenAllPeriod(); + EventQueue.invokeLater(new Runnable(){ + public void run(){ + digest.setVillage(village); + digest.setVisible(true); + return; + } + }); + return; + } + }); + + return; + } + + /** + * 全日程の一括フルオープン。ヘビータスク版。 + */ + // TODO taskLoadAllPeriodtと一体化したい。 + private void taskFullOpenAllPeriod(){ + setBusy(true); + updateStatusBar("一括読み込み開始"); + try{ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + for(PeriodView periodView : browser.getPeriodViewList()){ + Period period = periodView.getPeriod(); + if(period == null) continue; + if(period.isFullOpen()) continue; + String message = + period.getDay() + + "日目のデータを読み込んでいます"; + updateStatusBar(message); + try{ + Period.parsePeriod(period, true); + }catch(IOException e){ + showNetworkError(village, e); + return; + } + periodView.showTopics(); + } + }finally{ + updateStatusBar("一括読み込み完了"); + setBusy(false); + } + return; + } + + /** + * 検索パネルを表示する。 + */ + private void actionShowFind(){ + this.findPanel.setVisible(true); + if(this.findPanel.isCanceled()){ + updateFindPanel(); + return; + } + if(this.findPanel.isBulkSearch()){ + bulkSearch(); + }else{ + regexSearch(); + } + return; + } + + /** + * 検索処理。 + */ + private void regexSearch(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + + RegexPattern regPattern = this.findPanel.getRegexPattern(); + int hits = discussion.setRegexPattern(regPattern); + + String hitMessage = "ï¼»" + hits + "]件ヒットしました"; + updateStatusBar(hitMessage); + + String loginfo = ""; + if(regPattern != null){ + Pattern pattern = regPattern.getPattern(); + if(pattern != null){ + loginfo = "正規表現 " + pattern.pattern() + " に"; + } + } + loginfo += hitMessage; + Jindolf.logger().info(loginfo); + + return; + } + + /** + * 一括検索処理。 + */ + private void bulkSearch(){ + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + taskBulkSearch(); + return; + } + }); + } + + /** + * 一括検索処理。ヘビータスク版。 + */ + private void taskBulkSearch(){ + taskLoadAllPeriod(); + int totalhits = 0; + RegexPattern regPattern = this.findPanel.getRegexPattern(); + StringBuilder hitDesc = new StringBuilder(); + TabBrowser browser = this.topView.getTabBrowser(); + for(PeriodView periodView : browser.getPeriodViewList()){ + Discussion discussion = periodView.getDiscussion(); + int hits = discussion.setRegexPattern(regPattern); + totalhits += hits; + + if(hits > 0){ + Period period = discussion.getPeriod(); + hitDesc.append(' ').append(period.getDay()).append("d:"); + hitDesc.append(hits).append("件"); + } + } + String hitMessage = + "ï¼»" + totalhits + "]件ヒットしました。" + + hitDesc.toString(); + updateStatusBar(hitMessage); + + String loginfo = ""; + if(regPattern != null){ + Pattern pattern = regPattern.getPattern(); + if(pattern != null){ + loginfo = "正規表現 " + pattern.pattern() + " に"; + } + } + loginfo += hitMessage; + Jindolf.logger().info(loginfo); + + return; + } + + /** + * 検索パネルに現在選択中のPeriodを反映させる。 + */ + private void updateFindPanel(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + RegexPattern pattern = discussion.getRegexPattern(); + this.findPanel.setRegexPattern(pattern); + return; + } + + /** + * 発言集計パネルを表示。 + */ + private void actionDaySummary(){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + + Period period = periodView.getPeriod(); + if(period == null) return; + + if(this.daySummaryPanel == null){ + this.daySummaryPanel = new DaySummary(this.topFrame); + this.daySummaryPanel.pack(); + this.daySummaryPanel.setSize(400, 500); + } + + this.daySummaryPanel.summaryPeriod(period); + this.daySummaryPanel.setVisible(true); + + this.windowMap.put(this.daySummaryPanel, false); + + return; + } + + /** + * 表示中PeriodをCSVファイルへエクスポートする。 + */ + private void actionDayExportCsv(){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + + Period period = periodView.getPeriod(); + if(period == null) return; + + File file = CsvExporter.exportPeriod(period, this.filterFrame); + if(file != null){ + String message = "CSVファイル(" + +file.getName() + +")へのエクスポートが完了しました"; + updateStatusBar(message); + } + + // TODO 長そうなジョブなら別スレッドにした方がいいか? + + return; + } + + /** + * 検索結果の次候補へジャンプ。 + */ + private void actionSearchNext(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + + discussion.nextHotTarget(); + + return; + } + + /** + * 検索結果の全候補へジャンプ。 + */ + private void actionSearchPrev(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + + discussion.prevHotTarget(); + + return; + } + + /** + * Period表示の強制再更新処理。 + */ + private void actionReloadPeriod(){ + updatePeriod(true); + + TabBrowser tabBrowser = this.topView.getTabBrowser(); + Village village = tabBrowser.getVillage(); + if(village == null) return; + if(village.getState() != VillageState.EPILOGUE) return; + + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + Period period = discussion.getPeriod(); + if(period == null) return; + if(period.getTopics() > 1000){ + JOptionPane.showMessageDialog(this.topFrame, + "エピローグが1000発言を超えはじめたら、\n" + +"負荷対策のためWebブラウザによるアクセスを" + +"心がけましょう", + "長大エピローグ警告", + JOptionPane.WARNING_MESSAGE + ); + } + + return; + } + + /** + * 全日程の一括ロード。 + */ + private void actionLoadAllPeriod(){ + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + taskLoadAllPeriod(); + return; + } + }); + + return; + } + + /** + * 全日程の一括ロード。ヘビータスク版。 + */ + private void taskLoadAllPeriod(){ + setBusy(true); + updateStatusBar("一括読み込み開始"); + try{ + TabBrowser browser = this.topView.getTabBrowser(); + Village village = browser.getVillage(); + if(village == null) return; + for(PeriodView periodView : browser.getPeriodViewList()){ + Period period = periodView.getPeriod(); + if(period == null) continue; + String message = + period.getDay() + + "日目のデータを読み込んでいます"; + updateStatusBar(message); + try{ + Period.parsePeriod(period, false); + }catch(IOException e){ + showNetworkError(village, e); + return; + } + periodView.showTopics(); + } + }finally{ + updateStatusBar("一括読み込み完了"); + setBusy(false); + } + return; + } + + /** + * 村一覧の再読み込み。 + */ + private void actionReloadVillageList(){ + JTree tree = this.topView.getTreeView(); + TreePath path = tree.getSelectionPath(); + if(path == null) return; + + Land land = null; + for(int ct = 0; ct < path.getPathCount(); ct++){ + Object obj = path.getPathComponent(ct); + if(obj instanceof Land){ + land = (Land) obj; + break; + } + } + if(land == null) return; + + this.topView.showInitPanel(); + + execReloadVillageList(land); + + return; + } + + /** + * 選択文字列をクリップボードにコピーする。 + */ + private void actionCopySelected(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + + CharSequence copied = discussion.copySelected(); + if(copied == null) return; + + copied = StringUtils.suppressString(copied); + updateStatusBar( + "[" + copied + "]をクリップボードにコピーしました"); + return; + } + + /** + * 一発言のみクリップボードにコピーする。 + */ + private void actionCopyTalk(){ + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + + CharSequence copied = discussion.copyTalk(); + if(copied == null) return; + + copied = StringUtils.suppressString(copied); + updateStatusBar( + "[" + copied + "]をクリップボードにコピーしました"); + return; + } + + /** + * アンカーにジャンプする。 + */ + private void actionJumpAnchor(){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + Discussion discussion = periodView.getDiscussion(); + + final TabBrowser browser = this.topView.getTabBrowser(); + final Village village = browser.getVillage(); + final Anchor anchor = discussion.getPopupedAnchor(); + if(anchor == null) return; + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("ジャンプ先の読み込み中…"); + + if(anchor.hasTalkNo()){ + // TODO もう少し賢くならない? + taskLoadAllPeriod(); + } + + final List talkList; + try{ + talkList = village.getTalkListFromAnchor(anchor); + if(talkList == null || talkList.size() <= 0){ + updateStatusBar( + "アンカーのジャンプ先[" + + anchor.toString() + + "]が見つかりません"); + return; + } + + final Talk targetTalk = talkList.get(0); + final Period targetPeriod = targetTalk.getPeriod(); + final int tabIndex = targetPeriod.getDay() + 1; + final PeriodView target = browser.getPeriodView(tabIndex); + + EventQueue.invokeLater(new Runnable(){ + public void run(){ + browser.setSelectedIndex(tabIndex); + target.setPeriod(targetPeriod); + target.scrollToTalk(targetTalk); + return; + } + }); + updateStatusBar( + "アンカー[" + + anchor.toString() + + "]にジャンプしました"); + }catch(IOException e){ + updateStatusBar( + "アンカーの展開中にエラーが起きました"); + }finally{ + setBusy(false); + } + + return; + } + }); + + return; + } + + /** + * 指定した国の村一覧を読み込む。 + * @param land 国 + */ + private void execReloadVillageList(final Land land){ + final LandsTree treePanel = this.topView.getLandsTree(); + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("村一覧を読み込み中…"); + try{ + try{ + Controller.this.model.loadVillageList(land); + }catch(IOException e){ + showNetworkError(land, e); + } + treePanel.expandLand(land); + }finally{ + updateStatusBar("村一覧の読み込み完了"); + setBusy(false); + } + return; + } + }); + return; + } + + /** + * Period表示の更新処理。 + * @param force trueならPeriodデータを強制再読み込み。 + */ + private void updatePeriod(final boolean force){ + final TabBrowser tabBrowser = this.topView.getTabBrowser(); + final Village village = tabBrowser.getVillage(); + if(village == null) return; + setFrameTitle(village.getVillageFullName()); + + final PeriodView periodView = currentPeriodView(); + Discussion discussion = currentDiscussion(); + if(discussion == null) return; + discussion.setTopicFilter(this.filterFrame); + final Period period = discussion.getPeriod(); + if(period == null) return; + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + try{ + boolean wasHot = loadPeriod(); + + if(wasHot && ! period.isHot() ){ + if( ! updatePeriodList() ) return; + } + + renderBrowser(); + }finally{ + setBusy(false); + } + return; + } + + private boolean loadPeriod(){ + updateStatusBar("1日分のデータを読み込んでいます…"); + boolean wasHot; + try{ + wasHot = period.isHot(); + try{ + Period.parsePeriod(period, force); + }catch(IOException e){ + showNetworkError(village, e); + } + }finally{ + updateStatusBar("1日分のデータを読み終わりました"); + } + return wasHot; + } + + private boolean updatePeriodList(){ + updateStatusBar("村情報を読み直しています…"); + try{ + Village.updateVillage(village); + }catch(IOException e){ + showNetworkError(village, e); + return false; + } + try{ + SwingUtilities.invokeAndWait(new Runnable(){ + public void run(){ + tabBrowser.setVillage(village); + return; + } + }); + }catch(InvocationTargetException e){ + Jindolf.logger().fatal( + "タブ操作で致命的な障害が発生しました", e); + }catch(InterruptedException e){ + Jindolf.logger().fatal( + "タブ操作で致命的な障害が発生しました", e); + } + updateStatusBar("村情報を読み直しました…"); + return true; + } + + private void renderBrowser(){ + updateStatusBar("レンダリング中…"); + try{ + final int lastPos = periodView.getVerticalPosition(); + try{ + SwingUtilities.invokeAndWait(new Runnable(){ + public void run(){ + periodView.showTopics(); + return; + } + }); + }catch(InvocationTargetException e){ + Jindolf.logger().fatal( + "ブラウザ表示で致命的な障害が発生しました", e); + }catch(InterruptedException e){ + Jindolf.logger().fatal( + "ブラウザ表示で致命的な障害が発生しました", e); + } + EventQueue.invokeLater(new Runnable(){ + public void run(){ + periodView.setVerticalPosition(lastPos); + } + }); + }finally{ + updateStatusBar("レンダリング完了"); + } + return; + } + }); + + return; + } + + /** + * 発言フィルタの操作による更新処理。 + */ + private void filterChanged(){ + final Discussion discussion = currentDiscussion(); + if(discussion == null) return; + discussion.setTopicFilter(this.filterFrame); + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("フィルタリング中…"); + try{ + discussion.filtering(); + }finally{ + updateStatusBar("フィルタリング完了"); + setBusy(false); + } + return; + } + }); + + return; + } + + /** + * 現在選択中のPeriodを内包するPeriodViewを返す。 + * @return PeriodView + */ + private PeriodView currentPeriodView(){ + TabBrowser tb = this.topView.getTabBrowser(); + PeriodView result = tb.currentPeriodView(); + return result; + } + + /** + * 現在選択中のPeriodを内包するDiscussionを返す。 + * @return Discussion + */ + private Discussion currentDiscussion(){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return null; + Discussion result = periodView.getDiscussion(); + return result; + } + + /** + * フレーム表示のトグル処理。 + * @param window フレーム + */ + private void toggleWindow(Window window){ + if(window == null) return; + + if(window instanceof Frame){ + Frame frame = (Frame) window; + int winState = frame.getExtendedState(); + boolean isIconified = (winState & Frame.ICONIFIED) != 0; + if(isIconified){ + winState &= ~(Frame.ICONIFIED); + frame.setExtendedState(winState); + frame.setVisible(true); + return; + } + } + + if(window.isVisible()){ + window.setVisible(false); + window.dispose(); + }else{ + window.setVisible(true); + } + return; + } + + /** + * ネットワークエラーを通知するモーダルダイアログを表示する。 + * OKボタンを押すまでこのメソッドは戻ってこない。 + * @param village 村 + * @param e ネットワークエラー + */ + public void showNetworkError(Village village, IOException e){ + Land land = village.getParentLand(); + showNetworkError(land, e); + return; + } + + /** + * ネットワークエラーを通知するモーダルダイアログを表示する。 + * OKボタンを押すまでこのメソッドは戻ってこない。 + * @param land 国 + * @param e ネットワークエラー + */ + public void showNetworkError(Land land, IOException e){ + Jindolf.logger().warn("ネットワークで障害が発生しました", e); + + ServerAccess server = land.getServerAccess(); + String message = + land.getLandDef().getLandName() + +"を運営するサーバとの間の通信で" + +"何らかのトラブルが発生しました。\n" + +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n" + +"プロクシ設定は正しいかな?\n" + +"Webブラウザでも遊べないか確認してみてね!\n"; + + JOptionPane pane = new JOptionPane(message, + JOptionPane.WARNING_MESSAGE, + JOptionPane.DEFAULT_OPTION ); + + JDialog dialog = pane.createDialog(this.topFrame, + "通信異常発生 - " + Jindolf.TITLE); + + dialog.pack(); + dialog.setVisible(true); + dialog.dispose(); + + return; + } + + /** + * {@inheritDoc} + * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。 + * @param event イベント {@inheritDoc} + */ + @Override + public void valueChanged(TreeSelectionEvent event){ + TreePath path = event.getNewLeadSelectionPath(); + if(path == null) return; + + Object selObj = path.getLastPathComponent(); + + if( selObj instanceof Land ){ + Land land = (Land)selObj; + setFrameTitle(land.getLandDef().getLandName()); + this.topView.showLandInfo(land); + this.actionManager.appearVillage(false); + this.actionManager.appearPeriod(false); + }else if( selObj instanceof Village ){ + final Village village = (Village)selObj; + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("村情報を読み込み中…"); + + try{ + Village.updateVillage(village); + }catch(IOException e){ + showNetworkError(village, e); + return; + }finally{ + updateStatusBar("村情報の読み込み完了"); + setBusy(false); + } + + Controller.this.actionManager.appearVillage(true); + setFrameTitle(village.getVillageFullName()); + + EventQueue.invokeLater(new Runnable(){ + public void run(){ + Controller.this.topView.showVillageInfo(village); + return; + } + }); + + return; + } + }); + } + + return; + } + + /** + * {@inheritDoc} + * Periodがタブ選択されたときもしくは発言フィルタが操作されたときの処理。 + * @param event イベント {@inheritDoc} + */ + @Override + public void stateChanged(ChangeEvent event){ + Object source = event.getSource(); + + if(source == this.filterFrame){ + filterChanged(); + }else if(source instanceof TabBrowser){ + updateFindPanel(); + updatePeriod(false); + PeriodView periodView = currentPeriodView(); + if(periodView == null) this.actionManager.appearPeriod(false); + else this.actionManager.appearPeriod(true); + } + return; + } + + /** + * {@inheritDoc} + * 主にメニュー選択やボタン押下など。 + * @param e イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent e){ + if(this.isBusyNow) return; + + String cmd = e.getActionCommand(); + if(cmd.equals(ActionManager.CMD_ACCOUNT)){ + actionShowAccount(); + }else if(cmd.equals(ActionManager.CMD_EXIT)){ + actionExit(); + }else if(cmd.equals(ActionManager.CMD_COPY)){ + actionCopySelected(); + }else if(cmd.equals(ActionManager.CMD_SHOWFIND)){ + actionShowFind(); + }else if(cmd.equals(ActionManager.CMD_SEARCHNEXT)){ + actionSearchNext(); + }else if(cmd.equals(ActionManager.CMD_SEARCHPREV)){ + actionSearchPrev(); + }else if(cmd.equals(ActionManager.CMD_ALLPERIOD)){ + actionLoadAllPeriod(); + }else if(cmd.equals(ActionManager.CMD_SHOWDIGEST)){ + actionShowDigest(); + }else if(cmd.equals(ActionManager.CMD_WEBVILL)){ + actionShowWebVillage(); + }else if(cmd.equals(ActionManager.CMD_WEBWIKI)){ + actionShowWebWiki(); + }else if(cmd.equals(ActionManager.CMD_WEBCAST)){ + actionShowWebCast(); + }else if(cmd.equals(ActionManager.CMD_RELOAD)){ + actionReloadPeriod(); + }else if(cmd.equals(ActionManager.CMD_DAYSUMMARY)){ + actionDaySummary(); + }else if(cmd.equals(ActionManager.CMD_DAYEXPCSV)){ + actionDayExportCsv(); + }else if(cmd.equals(ActionManager.CMD_WEBDAY)){ + actionShowWebDay(); + }else if(cmd.equals(ActionManager.CMD_OPTION)){ + actionOption(); + }else if(cmd.equals(ActionManager.CMD_LANDF)){ + actionChangeLaF(); + }else if(cmd.equals(ActionManager.CMD_SHOWFILT)){ + actionShowFilter(); + }else if(cmd.equals(ActionManager.CMD_SHOWEDIT)){ + actionTalkPreview(); + }else if(cmd.equals(ActionManager.CMD_SHOWLOG)){ + actionShowLog(); + }else if(cmd.equals(ActionManager.CMD_HELPDOC)){ + actionHelp(); + }else if(cmd.equals(ActionManager.CMD_SHOWPORTAL)){ + actionShowPortal(); + }else if(cmd.equals(ActionManager.CMD_ABOUT)){ + actionAbout(); + }else if(cmd.equals(ActionManager.CMD_VILLAGELIST)){ + actionReloadVillageList(); + }else if(cmd.equals(ActionManager.CMD_COPYTALK)){ + actionCopyTalk(); + }else if(cmd.equals(ActionManager.CMD_JUMPANCHOR)){ + actionJumpAnchor(); + }else if(cmd.equals(ActionManager.CMD_WEBTALK)){ + actionShowWebTalk(); + } + return; + } + + /** + * {@inheritDoc} + * 村選択ツリーリストが畳まれるとき呼ばれる。 + * @param event ツリーイベント {@inheritDoc} + */ + @Override + public void treeWillCollapse(TreeExpansionEvent event){ + return; + } + + /** + * {@inheritDoc} + * 村選択ツリーリストが展開されるとき呼ばれる。 + * @param event ツリーイベント {@inheritDoc} + */ + @Override + public void treeWillExpand(TreeExpansionEvent event){ + if(!(event.getSource() instanceof JTree)){ + return; + } + + TreePath path = event.getPath(); + Object lastObj = path.getLastPathComponent(); + if(!(lastObj instanceof Land)){ + return; + } + final Land land = (Land) lastObj; + if(land.getVillageCount() > 0){ + return; + } + + execReloadVillageList(land); + + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void anchorHitted(AnchorHitEvent event){ + PeriodView periodView = currentPeriodView(); + if(periodView == null) return; + Period period = periodView.getPeriod(); + if(period == null) return; + final Village village = period.getVillage(); + + final TalkDraw talkDraw = event.getTalkDraw(); + final Anchor anchor = event.getAnchor(); + final Discussion discussion = periodView.getDiscussion(); + + Executor executor = Executors.newCachedThreadPool(); + executor.execute(new Runnable(){ + public void run(){ + setBusy(true); + updateStatusBar("アンカーの展開中…"); + + if(anchor.hasTalkNo()){ + // TODO もう少し賢くならない? + taskLoadAllPeriod(); + } + + final List talkList; + try{ + talkList = village.getTalkListFromAnchor(anchor); + if(talkList == null || talkList.size() <= 0){ + updateStatusBar( + "アンカーの展開先[" + + anchor.toString() + + "]が見つかりません"); + return; + } + EventQueue.invokeLater(new Runnable(){ + public void run(){ + talkDraw.showAnchorTalks(anchor, talkList); + discussion.layoutRows(); + return; + } + }); + updateStatusBar( + "アンカー[" + + anchor.toString() + + "]の展開完了"); + }catch(IOException e){ + updateStatusBar( + "アンカーの展開中にエラーが起きました"); + }finally{ + setBusy(false); + } + + return; + } + }); + + return; + } + + /** + * ヘビーなタスク実行をアピール。 + * プログレスバーとカーソルの設定を行う。 + * @param isBusy trueならプログレスバーのアニメ開始&WAITカーソル。 + * falseなら停止&通常カーソル。 + */ + private void setBusy(final boolean isBusy){ + this.isBusyNow = isBusy; + + Runnable microJob = new Runnable(){ + public void run(){ + Cursor cursor; + if(isBusy){ + cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); + }else{ + cursor = Cursor.getDefaultCursor(); + } + + Component glass = Controller.this.topFrame.getGlassPane(); + glass.setCursor(cursor); + glass.setVisible(isBusy); + Controller.this.topView.setBusy(isBusy); + + return; + } + }; + + if(SwingUtilities.isEventDispatchThread()){ + microJob.run(); + }else{ + try{ + SwingUtilities.invokeAndWait(microJob); + }catch(InvocationTargetException e){ + Jindolf.logger().fatal("ビジー処理で失敗", e); + }catch(InterruptedException e){ + Jindolf.logger().fatal("ビジー処理で失敗", e); + } + } + + return; + } + + /** + * ステータスバーを更新する。 + * @param message メッセージ + */ + private void updateStatusBar(String message){ + this.topView.updateSysMessage(message); + } + + /** + * トップフレームのタイトルを設定する。 + * タイトルは指定された国or村名 + " - Jindolf" + * @param name 国or村名 + */ + private void setFrameTitle(CharSequence name){ + String title = Jindolf.TITLE; + + if(name != null && name.length() > 0){ + title = name + " - " + title; + } + + this.topFrame.setTitle(title); + + return; + } + + /** + * アプリ正常終了処理。 + */ + private void shutdown(){ + this.findPanel.saveHistory(); + this.talkPreview.saveDraft(); + Jindolf.getAppSetting().saveConfig(); + Jindolf.exit(0); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/CsvExporter.java b/src/main/java/jp/sourceforge/jindolf/CsvExporter.java index 9c8bf41..064b1e5 100644 --- a/src/main/java/jp/sourceforge/jindolf/CsvExporter.java +++ b/src/main/java/jp/sourceforge/jindolf/CsvExporter.java @@ -1,556 +1,556 @@ -/* - * CSV file exporter - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.util.LinkedList; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.border.Border; -import javax.swing.filechooser.FileFilter; -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * 任意のPeriodの発言内容をCSVファイルへエクスポートする。 - * according to RFC4180 (text/csv) - * @see RFC4180 - */ -public final class CsvExporter{ - - private static final String[] ENCNAMES = { - "UTF-8", - - "ISO-2022-JP", - "ISO-2022-JP-2", - "ISO-2022-JP-3", - "ISO-2022-JP-2004", - - "EUC-JP", - "x-euc-jp-linux", - "x-eucJP-Open", - - "Shift_JIS", - "windows-31j", - "x-MS932_0213", - "x-SJIS_0213", - "x-PCK", - }; - private static final String JPCHECK = - "[]09AZ" - + "あんアンアンゐゑヵヶヴヰヱヮ" - + "亜瑤凜熙壷壺尭堯" - + "å³ " - + "〒╋"; - private static final String CSVEXT = ".csv"; - private static final char CR = '\r'; - private static final char LF = '\n'; - private static final String CRLF = CR +""+ LF; - private static final int BUFSIZ = 1024; - - private static final List CHARSET_LIST = buildCharsetList(); - private static final FileFilter CSV_FILTER = new CsvFileFilter(); - private static final JComboBox encodeBox = new JComboBox(); - private static final JFileChooser chooser = buildChooser(); - // TODO staticなGUIパーツってどうなんだ… - - - /** - * 隠しコンストラクタ。 - */ - private CsvExporter(){ - assert false; - throw new AssertionError(); - } - - - /** - * Charsetが日本語エンコーダを持っているか確認する。 - * @param cs Charset - * @return 日本語エンコーダを持っていればtrue - */ - private static boolean hasJPencoder(Charset cs){ - if( ! cs.canEncode() ) return false; - CharsetEncoder encoder = cs.newEncoder(); - try{ - if(encoder.canEncode(JPCHECK)) return true; - }catch(Exception e){ - return false; - // 一部JRE1.5系の「x-euc-jp-linux」エンコーディング実装には - // canEncode()が例外を投げるバグがあるので、その対処。 - } - return false; - } - - /** - * 日本語Charset一覧を生成する。 - * @return 日本語Charset一覧 - */ - private static List buildCharsetList(){ - List csList = new LinkedList(); - for(String name : ENCNAMES){ - if( ! Charset.isSupported(name) ) continue; - Charset cs = Charset.forName(name); - - if(csList.contains(cs)) continue; - - if( ! hasJPencoder(cs) ) continue; - - csList.add(cs); - } - - Charset defcs = Charset.defaultCharset(); - if( defcs.name().equals("windows-31j") - && Charset.isSupported("Shift_JIS") ){ - defcs = Charset.forName("Shift_JIS"); - } - - if( hasJPencoder(defcs) || csList.size() <= 0 ){ - if(csList.contains(defcs)){ - csList.remove(defcs); - } - csList.add(0, defcs); - } - - return csList; - } - - /** - * チューザーをビルドする。 - * @return チューザー - */ - private static JFileChooser buildChooser(){ - JFileChooser result = new JFileChooser(); - - result.setFileSelectionMode(JFileChooser.FILES_ONLY); - result.setMultiSelectionEnabled(false); - result.setFileHidingEnabled(true); - - result.setAcceptAllFileFilterUsed(true); - - result.setFileFilter(CSV_FILTER); - - JComponent accessory = buildAccessory(); - result.setAccessory(accessory); - - return result; - } - - /** - * チューザのアクセサリを生成する。 - * エンコード指定のコンボボックス。 - * @return アクセサリ - */ - private static JComponent buildAccessory(){ - for(Charset cs : CHARSET_LIST){ - encodeBox.addItem(cs); - } - - Border border = BorderFactory.createTitledBorder("出力エンコード"); - encodeBox.setBorder(border); - - JPanel accessory = new JPanel(); - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - accessory.setLayout(layout); - - constraints.insets = new Insets(3, 3, 3, 3); - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.NONE; - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.anchor = GridBagConstraints.NORTHWEST; - - accessory.add(encodeBox, constraints); - - constraints.fill = GridBagConstraints.BOTH; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - - accessory.add(new JPanel(), constraints); // dummy - - return accessory; - } - - /** - * ファイルに書き込めない/作れないエラー用のダイアログを表示する。 - * @param file 書き込もうとしたファイル。 - */ - private static void writeError(File file){ - Component parent = null; - String title = "ファイル書き込みエラー"; - String message = "ファイル「" + file.toString() + "」\n" - +"に書き込むことができません。"; - - JOptionPane.showMessageDialog(parent, message, title, - JOptionPane.ERROR_MESSAGE ); - - return; - } - - /** - * ファイル上書き確認ダイアログを表示する。 - * @param file 上書き対象ファイル - * @return 上書きOKが指示されたらtrue - */ - private static boolean confirmOverwrite(File file){ - Component parent = null; - String title = "上書き確認"; - String message = "既存のファイル「" + file.toString() + "」\n" - +"を上書きしようとしています。続けますか?"; - - int confirm = JOptionPane.showConfirmDialog( - parent, message, title, - JOptionPane.WARNING_MESSAGE, - JOptionPane.OK_CANCEL_OPTION ); - - if(confirm == JOptionPane.OK_OPTION) return true; - - return false; - } - - /** - * チューザーのタイトルを設定する。 - * @param period エクスポート対象の日 - */ - private static void setTitle(Period period){ - Village village = period.getVillage(); - String villageName = village.getVillageName(); - String title = villageName + "村 " + period.getCaption(); - title += "の発言をCSVファイルへエクスポートします"; - chooser.setDialogTitle(title); - return; - } - - /** - * エクスポート先ファイルの名前を生成する。 - * @param period エクスポート対象の日 - * @return エクスポートファイル名 - */ - private static String createUniqueFileName(Period period){ - Village village = period.getVillage(); - String villageName = village.getVillageName(); - - String base = "JIN_" + villageName; - - switch(period.getType()){ - case PROLOGUE: - base += "_Prologue"; - break; - case EPILOGUE: - base += "_Epilogue"; - break; - case PROGRESS: - base += "_Day"; - base += period.getDay(); - break; - default: - assert false; - break; - } - - File saveFile; - String csvName; - int serial = 1; - do{ - csvName = base; - if(serial > 1){ - csvName += "("+ serial +")"; - } - serial++; - csvName += CSVEXT; - - File current = chooser.getCurrentDirectory(); - saveFile = new File(current, csvName); - }while(saveFile.exists()); - - return csvName; - } - - /** - * Period情報をダンプする。 - * @param out 格納先 - * @param period ダンプ対象Period - * @param topicFilter 発言フィルタ - * @throws java.io.IOException 出力エラー - */ - private static void dumpPeriod(Appendable out, - Period period, - TopicFilter topicFilter) - throws IOException{ - String day = String.valueOf(period.getDay()); - - List topicList = period.getTopicList(); - for(Topic topic : topicList){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - if(talk.getTalkCount() <= 0) continue; - - if(topicFilter.isFiltered(talk)) continue; - - Avatar avatar = talk.getAvatar(); - - String name = avatar.getName(); - int hour = talk.getHour(); - int minute = talk.getMinute(); - TalkType type = talk.getTalkType(); - CharSequence dialog = talk.getDialog(); - - out.append(name).append(','); - - out.append(day).append(','); - - out.append(Character.forDigit(hour / 10, 10)); - out.append(Character.forDigit(hour % 10, 10)); - out.append(':'); - out.append(Character.forDigit(minute / 10, 10)); - out.append(Character.forDigit(minute % 10, 10)); - out.append(','); - - switch(type){ - case PUBLIC: out.append("say"); break; - case PRIVATE: out.append("think"); break; - case WOLFONLY: out.append("whisper"); break; - case GRAVE: out.append("groan"); break; - default: assert false; break; - } - out.append(','); - - escapeCSV(out, dialog); - out.append(CRLF); - } - - return; - } - - /** - * ダイアログ操作に従いPeriodをエクスポートする。 - * @param period エクスポート対象のPeriod - * @param topicFilter 発言フィルタ - * @return エクスポートしたファイル - */ - public static File exportPeriod(Period period, TopicFilter topicFilter){ - setTitle(period); - - String uniqName = createUniqueFileName(period); - File uniqFile = new File(uniqName); - chooser.setSelectedFile(uniqFile); - - int result = chooser.showSaveDialog(null); - - if(result != JFileChooser.APPROVE_OPTION) return null; - - File selected = chooser.getSelectedFile(); - - if( ! hasExtent(selected.getName()) ){ - FileFilter filter = chooser.getFileFilter(); - if(filter == CSV_FILTER){ - String path = selected.getPath(); - path += CSVEXT; - selected = new File(path); - } - } - - if(selected.exists()){ - if( ! selected.isFile() || ! selected.canWrite() ){ - writeError(selected); - return null; - } - boolean confirmed = confirmOverwrite(selected); - if( ! confirmed ) return null; - }else{ - boolean created; - try{ - created = selected.createNewFile(); - }catch(IOException e){ - writeError(selected); - return null; - } - - if( ! created ){ - boolean confirmed = confirmOverwrite(selected); - if( ! confirmed ) return null; - } - } - - OutputStream os; - try{ - os = new FileOutputStream(selected); - }catch(FileNotFoundException e){ - writeError(selected); - return null; - } - os = new BufferedOutputStream(os, BUFSIZ); - - Charset cs = (Charset)( encodeBox.getSelectedItem() ); - - boolean hasIOError = false; - Writer writer = new OutputStreamWriter(os, cs); - try{ - dumpPeriod(writer, period, topicFilter); - }catch(IOException e){ - hasIOError = true; - }finally{ - try{ - writer.close(); - }catch(IOException e){ - hasIOError = true; - } - } - if(hasIOError) writeError(selected); - - return selected; - } - - /** - * CSV用のエスケープシーケンス処理を行う。 - * RFC4180準拠。 - * @param app 格納先 - * @param seq エスケープシーケンス対象 - * @return appと同じもの - * @throws java.io.IOException 出力エラー - */ - public static Appendable escapeCSV(Appendable app, CharSequence seq) - throws IOException{ - app.append('"'); - - int length = seq.length(); - - for(int pos = 0; pos < length; pos++){ - char ch = seq.charAt(pos); - switch(ch){ - case '"': - app.append("\"\""); - continue; - case '\n': - app.append(CRLF); - continue; - default: - app.append(ch); - break; - } - } - - app.append('"'); - - return app; - } - - /** - * ファイル名が任意の拡張子を持つか判定する。 - * 英字大小は同一視される。 - * 拡張子の前は必ず一文字以上何かがなければならない。 - * @param filename ファイル名 - * @param extent '.'で始まる拡張子文字列 - * @return 指定された拡張子を持つならtrue - */ - public static boolean hasExtent(CharSequence filename, - CharSequence extent ){ - int flength = filename.length(); - int elength = extent .length(); - if(elength < 2) return false; - if(flength <= elength) return false; - - if(filename.charAt(0) == '.') return false; - - int offset = flength - elength; - assert offset > 0; - - for(int pos = 0; pos < elength; pos++){ - char ech = Character.toLowerCase(extent .charAt(pos )); - char fch = Character.toLowerCase(filename.charAt(pos + offset)); - if(fch != ech) return false; - } - - return true; - } - - /** - * パス名抜きのファイル名が拡張子を持つか判定する。 - * 先頭が.で始まるファイル名は拡張子を持たない。 - * 末尾が.で終わるファイル名は拡張子を持たない。 - * それ以外の.を含むファイル名は拡張子を持つとみなす。 - * @param filename パス名抜きのファイル名 - * @return 拡張子を持っていればtrue - */ - public static boolean hasExtent(CharSequence filename){ - int length = filename.length(); - if(length < 3) return false; - - if(filename.charAt(0) == '.') return false; - int lastPos = length - 1; - if(filename.charAt(lastPos) == '.') return false; - - for(int pos = 1; pos <= lastPos - 1; pos++){ - char ch = filename.charAt(pos); - if(ch == '.') return true; - } - - return false; - } - - /** - * CSVファイル表示用フィルタ。 - * 名前が「*.csv」の通常ファイルとディレクトリのみ表示させる。 - * ※ 表示の可否を問うものであって、選択の可否を問うものではない。 - */ - private static class CsvFileFilter extends FileFilter{ - - /** - * コンストラクタ。 - */ - public CsvFileFilter(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param file {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean accept(File file){ - if(file.isDirectory()) return true; - if( ! file.isFile() ) return false; - - if( ! hasExtent(file.getName(), CSVEXT) ) return false; - - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getDescription(){ - return "CSVファイル (*.csv)"; - } - } - - // TODO SecurityExceptionの捕捉 - // 書き込み中のファイルロック -} +/* + * CSV file exporter + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.LinkedList; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.border.Border; +import javax.swing.filechooser.FileFilter; +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * 任意のPeriodの発言内容をCSVファイルへエクスポートする。 + * according to RFC4180 (text/csv) + * @see RFC4180 + */ +public final class CsvExporter{ + + private static final String[] ENCNAMES = { + "UTF-8", + + "ISO-2022-JP", + "ISO-2022-JP-2", + "ISO-2022-JP-3", + "ISO-2022-JP-2004", + + "EUC-JP", + "x-euc-jp-linux", + "x-eucJP-Open", + + "Shift_JIS", + "windows-31j", + "x-MS932_0213", + "x-SJIS_0213", + "x-PCK", + }; + private static final String JPCHECK = + "[]09AZ" + + "あんアンアンゐゑヵヶヴヰヱヮ" + + "亜瑤凜熙壷壺尭堯" + + "å³ " + + "〒╋"; + private static final String CSVEXT = ".csv"; + private static final char CR = '\r'; + private static final char LF = '\n'; + private static final String CRLF = CR +""+ LF; + private static final int BUFSIZ = 1024; + + private static final List CHARSET_LIST = buildCharsetList(); + private static final FileFilter CSV_FILTER = new CsvFileFilter(); + private static final JComboBox encodeBox = new JComboBox(); + private static final JFileChooser chooser = buildChooser(); + // TODO staticなGUIパーツってどうなんだ… + + + /** + * 隠しコンストラクタ。 + */ + private CsvExporter(){ + assert false; + throw new AssertionError(); + } + + + /** + * Charsetが日本語エンコーダを持っているか確認する。 + * @param cs Charset + * @return 日本語エンコーダを持っていればtrue + */ + private static boolean hasJPencoder(Charset cs){ + if( ! cs.canEncode() ) return false; + CharsetEncoder encoder = cs.newEncoder(); + try{ + if(encoder.canEncode(JPCHECK)) return true; + }catch(Exception e){ + return false; + // 一部JRE1.5系の「x-euc-jp-linux」エンコーディング実装には + // canEncode()が例外を投げるバグがあるので、その対処。 + } + return false; + } + + /** + * 日本語Charset一覧を生成する。 + * @return 日本語Charset一覧 + */ + private static List buildCharsetList(){ + List csList = new LinkedList(); + for(String name : ENCNAMES){ + if( ! Charset.isSupported(name) ) continue; + Charset cs = Charset.forName(name); + + if(csList.contains(cs)) continue; + + if( ! hasJPencoder(cs) ) continue; + + csList.add(cs); + } + + Charset defcs = Charset.defaultCharset(); + if( defcs.name().equals("windows-31j") + && Charset.isSupported("Shift_JIS") ){ + defcs = Charset.forName("Shift_JIS"); + } + + if( hasJPencoder(defcs) || csList.size() <= 0 ){ + if(csList.contains(defcs)){ + csList.remove(defcs); + } + csList.add(0, defcs); + } + + return csList; + } + + /** + * チューザーをビルドする。 + * @return チューザー + */ + private static JFileChooser buildChooser(){ + JFileChooser result = new JFileChooser(); + + result.setFileSelectionMode(JFileChooser.FILES_ONLY); + result.setMultiSelectionEnabled(false); + result.setFileHidingEnabled(true); + + result.setAcceptAllFileFilterUsed(true); + + result.setFileFilter(CSV_FILTER); + + JComponent accessory = buildAccessory(); + result.setAccessory(accessory); + + return result; + } + + /** + * チューザのアクセサリを生成する。 + * エンコード指定のコンボボックス。 + * @return アクセサリ + */ + private static JComponent buildAccessory(){ + for(Charset cs : CHARSET_LIST){ + encodeBox.addItem(cs); + } + + Border border = BorderFactory.createTitledBorder("出力エンコード"); + encodeBox.setBorder(border); + + JPanel accessory = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + accessory.setLayout(layout); + + constraints.insets = new Insets(3, 3, 3, 3); + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.NONE; + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.anchor = GridBagConstraints.NORTHWEST; + + accessory.add(encodeBox, constraints); + + constraints.fill = GridBagConstraints.BOTH; + constraints.weightx = 1.0; + constraints.weighty = 1.0; + + accessory.add(new JPanel(), constraints); // dummy + + return accessory; + } + + /** + * ファイルに書き込めない/作れないエラー用のダイアログを表示する。 + * @param file 書き込もうとしたファイル。 + */ + private static void writeError(File file){ + Component parent = null; + String title = "ファイル書き込みエラー"; + String message = "ファイル「" + file.toString() + "」\n" + +"に書き込むことができません。"; + + JOptionPane.showMessageDialog(parent, message, title, + JOptionPane.ERROR_MESSAGE ); + + return; + } + + /** + * ファイル上書き確認ダイアログを表示する。 + * @param file 上書き対象ファイル + * @return 上書きOKが指示されたらtrue + */ + private static boolean confirmOverwrite(File file){ + Component parent = null; + String title = "上書き確認"; + String message = "既存のファイル「" + file.toString() + "」\n" + +"を上書きしようとしています。続けますか?"; + + int confirm = JOptionPane.showConfirmDialog( + parent, message, title, + JOptionPane.WARNING_MESSAGE, + JOptionPane.OK_CANCEL_OPTION ); + + if(confirm == JOptionPane.OK_OPTION) return true; + + return false; + } + + /** + * チューザーのタイトルを設定する。 + * @param period エクスポート対象の日 + */ + private static void setTitle(Period period){ + Village village = period.getVillage(); + String villageName = village.getVillageName(); + String title = villageName + "村 " + period.getCaption(); + title += "の発言をCSVファイルへエクスポートします"; + chooser.setDialogTitle(title); + return; + } + + /** + * エクスポート先ファイルの名前を生成する。 + * @param period エクスポート対象の日 + * @return エクスポートファイル名 + */ + private static String createUniqueFileName(Period period){ + Village village = period.getVillage(); + String villageName = village.getVillageName(); + + String base = "JIN_" + villageName; + + switch(period.getType()){ + case PROLOGUE: + base += "_Prologue"; + break; + case EPILOGUE: + base += "_Epilogue"; + break; + case PROGRESS: + base += "_Day"; + base += period.getDay(); + break; + default: + assert false; + break; + } + + File saveFile; + String csvName; + int serial = 1; + do{ + csvName = base; + if(serial > 1){ + csvName += "("+ serial +")"; + } + serial++; + csvName += CSVEXT; + + File current = chooser.getCurrentDirectory(); + saveFile = new File(current, csvName); + }while(saveFile.exists()); + + return csvName; + } + + /** + * Period情報をダンプする。 + * @param out 格納先 + * @param period ダンプ対象Period + * @param topicFilter 発言フィルタ + * @throws java.io.IOException 出力エラー + */ + private static void dumpPeriod(Appendable out, + Period period, + TopicFilter topicFilter) + throws IOException{ + String day = String.valueOf(period.getDay()); + + List topicList = period.getTopicList(); + for(Topic topic : topicList){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + if(talk.getTalkCount() <= 0) continue; + + if(topicFilter.isFiltered(talk)) continue; + + Avatar avatar = talk.getAvatar(); + + String name = avatar.getName(); + int hour = talk.getHour(); + int minute = talk.getMinute(); + TalkType type = talk.getTalkType(); + CharSequence dialog = talk.getDialog(); + + out.append(name).append(','); + + out.append(day).append(','); + + out.append(Character.forDigit(hour / 10, 10)); + out.append(Character.forDigit(hour % 10, 10)); + out.append(':'); + out.append(Character.forDigit(minute / 10, 10)); + out.append(Character.forDigit(minute % 10, 10)); + out.append(','); + + switch(type){ + case PUBLIC: out.append("say"); break; + case PRIVATE: out.append("think"); break; + case WOLFONLY: out.append("whisper"); break; + case GRAVE: out.append("groan"); break; + default: assert false; break; + } + out.append(','); + + escapeCSV(out, dialog); + out.append(CRLF); + } + + return; + } + + /** + * ダイアログ操作に従いPeriodをエクスポートする。 + * @param period エクスポート対象のPeriod + * @param topicFilter 発言フィルタ + * @return エクスポートしたファイル + */ + public static File exportPeriod(Period period, TopicFilter topicFilter){ + setTitle(period); + + String uniqName = createUniqueFileName(period); + File uniqFile = new File(uniqName); + chooser.setSelectedFile(uniqFile); + + int result = chooser.showSaveDialog(null); + + if(result != JFileChooser.APPROVE_OPTION) return null; + + File selected = chooser.getSelectedFile(); + + if( ! hasExtent(selected.getName()) ){ + FileFilter filter = chooser.getFileFilter(); + if(filter == CSV_FILTER){ + String path = selected.getPath(); + path += CSVEXT; + selected = new File(path); + } + } + + if(selected.exists()){ + if( ! selected.isFile() || ! selected.canWrite() ){ + writeError(selected); + return null; + } + boolean confirmed = confirmOverwrite(selected); + if( ! confirmed ) return null; + }else{ + boolean created; + try{ + created = selected.createNewFile(); + }catch(IOException e){ + writeError(selected); + return null; + } + + if( ! created ){ + boolean confirmed = confirmOverwrite(selected); + if( ! confirmed ) return null; + } + } + + OutputStream os; + try{ + os = new FileOutputStream(selected); + }catch(FileNotFoundException e){ + writeError(selected); + return null; + } + os = new BufferedOutputStream(os, BUFSIZ); + + Charset cs = (Charset)( encodeBox.getSelectedItem() ); + + boolean hasIOError = false; + Writer writer = new OutputStreamWriter(os, cs); + try{ + dumpPeriod(writer, period, topicFilter); + }catch(IOException e){ + hasIOError = true; + }finally{ + try{ + writer.close(); + }catch(IOException e){ + hasIOError = true; + } + } + if(hasIOError) writeError(selected); + + return selected; + } + + /** + * CSV用のエスケープシーケンス処理を行う。 + * RFC4180準拠。 + * @param app 格納先 + * @param seq エスケープシーケンス対象 + * @return appと同じもの + * @throws java.io.IOException 出力エラー + */ + public static Appendable escapeCSV(Appendable app, CharSequence seq) + throws IOException{ + app.append('"'); + + int length = seq.length(); + + for(int pos = 0; pos < length; pos++){ + char ch = seq.charAt(pos); + switch(ch){ + case '"': + app.append("\"\""); + continue; + case '\n': + app.append(CRLF); + continue; + default: + app.append(ch); + break; + } + } + + app.append('"'); + + return app; + } + + /** + * ファイル名が任意の拡張子を持つか判定する。 + * 英字大小は同一視される。 + * 拡張子の前は必ず一文字以上何かがなければならない。 + * @param filename ファイル名 + * @param extent '.'で始まる拡張子文字列 + * @return 指定された拡張子を持つならtrue + */ + public static boolean hasExtent(CharSequence filename, + CharSequence extent ){ + int flength = filename.length(); + int elength = extent .length(); + if(elength < 2) return false; + if(flength <= elength) return false; + + if(filename.charAt(0) == '.') return false; + + int offset = flength - elength; + assert offset > 0; + + for(int pos = 0; pos < elength; pos++){ + char ech = Character.toLowerCase(extent .charAt(pos )); + char fch = Character.toLowerCase(filename.charAt(pos + offset)); + if(fch != ech) return false; + } + + return true; + } + + /** + * パス名抜きのファイル名が拡張子を持つか判定する。 + * 先頭が.で始まるファイル名は拡張子を持たない。 + * 末尾が.で終わるファイル名は拡張子を持たない。 + * それ以外の.を含むファイル名は拡張子を持つとみなす。 + * @param filename パス名抜きのファイル名 + * @return 拡張子を持っていればtrue + */ + public static boolean hasExtent(CharSequence filename){ + int length = filename.length(); + if(length < 3) return false; + + if(filename.charAt(0) == '.') return false; + int lastPos = length - 1; + if(filename.charAt(lastPos) == '.') return false; + + for(int pos = 1; pos <= lastPos - 1; pos++){ + char ch = filename.charAt(pos); + if(ch == '.') return true; + } + + return false; + } + + /** + * CSVファイル表示用フィルタ。 + * 名前が「*.csv」の通常ファイルとディレクトリのみ表示させる。 + * ※ 表示の可否を問うものであって、選択の可否を問うものではない。 + */ + private static class CsvFileFilter extends FileFilter{ + + /** + * コンストラクタ。 + */ + public CsvFileFilter(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param file {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean accept(File file){ + if(file.isDirectory()) return true; + if( ! file.isFile() ) return false; + + if( ! hasExtent(file.getName(), CSVEXT) ) return false; + + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getDescription(){ + return "CSVファイル (*.csv)"; + } + } + + // TODO SecurityExceptionの捕捉 + // 書き込み中のファイルロック +} diff --git a/src/main/java/jp/sourceforge/jindolf/DaySummary.java b/src/main/java/jp/sourceforge/jindolf/DaySummary.java index c757951..00963c4 100644 --- a/src/main/java/jp/sourceforge/jindolf/DaySummary.java +++ b/src/main/java/jp/sourceforge/jindolf/DaySummary.java @@ -1,524 +1,524 @@ -/* - * summary of day panel - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Image; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.text.NumberFormat; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.SwingConstants; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * その日ごとの集計。 - */ -@SuppressWarnings("serial") -public class DaySummary extends JDialog - implements WindowListener, ActionListener, ItemListener{ - - private static final String FRAMETITLE = - "発言集計 - " + Jindolf.TITLE; - private static final NumberFormat AVERAGE_FORM; - private static final String PUBTALK = "白発言"; - private static final String WOLFTALK = "赤発言"; - private static final String GRAVETALK = "青発言"; - private static final String PRVTALK = "灰発言"; - private static final String ALLTALK = "全発言"; - private static final int HORIZONTAL_GAP = 5; - private static final int VERTICAL_GAP = 1; - private static final Color COLOR_ALL = new Color(0xffff80); - - static{ - AVERAGE_FORM = NumberFormat.getInstance(); - AVERAGE_FORM.setMaximumFractionDigits(1); - AVERAGE_FORM.setMinimumFractionDigits(1); - } - - - private final DefaultTableModel tableModel; - private final TableColumn avatarColumn; - - private final JTable tableComp; - private final JComboBox typeSelector = new JComboBox(); - private final JButton closeButton = new JButton("閉じる"); - private final JLabel caption = new JLabel(); - private final JLabel totalSum = new JLabel(); - - private TalkType talkFilter; - private Period period; - - - /** - * コンストラクタ。 - * 集計結果を表示するモーダルダイアログを生成する。 - * @param owner オーナー - */ - public DaySummary(Frame owner){ - super(owner, FRAMETITLE, true); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(this); - - this.tableModel = createInitModel(); - this.tableComp = new JTable(); - this.tableComp.setModel(this.tableModel); - this.tableComp.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - this.tableComp.setIntercellSpacing( - new Dimension(HORIZONTAL_GAP, VERTICAL_GAP) ); - this.tableComp.setDefaultEditor(Object.class, null); - this.tableComp.setDefaultRenderer(Object.class, new CustomRenderer()); - this.tableComp.setShowGrid(true); - - TableColumnModel tcolModel = this.tableComp.getColumnModel(); - - this.avatarColumn = tcolModel.getColumn(0); - - DefaultTableCellRenderer renderer; - - renderer = new DefaultTableCellRenderer(); - renderer.setHorizontalAlignment(SwingConstants.RIGHT); - tcolModel.getColumn(1).setCellRenderer(renderer); - - renderer = new DefaultTableCellRenderer(); - renderer.setHorizontalAlignment(SwingConstants.RIGHT); - tcolModel.getColumn(2).setCellRenderer(renderer); - - renderer = new DefaultTableCellRenderer(); - renderer.setHorizontalAlignment(SwingConstants.RIGHT); - tcolModel.getColumn(3).setCellRenderer(renderer); - - this.typeSelector.addItem(PUBTALK); - this.typeSelector.addItem(WOLFTALK); - this.typeSelector.addItem(GRAVETALK); - this.typeSelector.addItem(PRVTALK); - this.typeSelector.addItem(ALLTALK); - - this.closeButton.addActionListener(this); - this.typeSelector.addItemListener(this); - - this.typeSelector.setSelectedItem(null); - this.typeSelector.setSelectedItem(PUBTALK); - - design(); - - clearModel(); - - return; - } - - - /** - * 初期のデータモデルを生成する。 - * @return データモデル - */ - private static DefaultTableModel createInitModel(){ - DefaultTableModel result; - result = new DefaultTableModel(); - - Object[] rowHeads = {"名前", "発言回数", "平均文字列長", "最終発言"}; - result.setColumnCount(rowHeads.length); - result.setColumnIdentifiers(rowHeads); - - return result; - } - - - /** - * テーブルをクリアする。 - */ - private void clearModel(){ - int rows = this.tableModel.getRowCount(); - for(int ct = 1; ct <= rows; ct++){ - this.tableModel.removeRow(0); - } - } - - /** - * 行を追加する。 - * @param avatar アバター - * @param talkCount 発言回数 - * @param totalChars 発言文字総数 - * @param lastTime 最終発言時刻 - */ - private void appendRow(Avatar avatar, - Integer talkCount, - Integer totalChars, - String lastTime ){ - String talks = talkCount + " 回"; - - double average; - if(talkCount <= 0) average = 0.0; - else average = (double)totalChars / (double)talkCount; - String chars = AVERAGE_FORM.format(average) + " 文字"; - - Object[] row = {avatar, talks, chars, lastTime}; - int rowIndex = this.tableModel.getRowCount(); - - this.tableModel.insertRow(rowIndex, row); - - return; - } - - /** - * デザインを行う。 - */ - private void design(){ - Container content = getContentPane(); - - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - content.setLayout(layout); - - constraints.insets = new Insets(5, 5, 5, 5); - - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.WEST; - content.add(this.caption, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.NONE; - content.add(this.typeSelector, constraints); - - JScrollPane scroller = new JScrollPane(this.tableComp); - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - content.add(scroller, constraints); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.WEST; - content.add(this.totalSum, constraints); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.EAST; - content.add(this.closeButton, constraints); - - return; - } - - /** - * 与えられたPeriodで集計を更新する。 - * @param newPeriod 日 - */ - public void summaryPeriod(Period newPeriod){ - this.period = newPeriod; - summaryPeriod(); - } - - /** - * 集計を更新する。 - */ - private void summaryPeriod(){ - clearModel(); - - if(this.period == null) return; - - SortedSet avatarSet = new TreeSet(); - Map talkCount = new HashMap(); - Map totalChars = new HashMap(); - Map lastTalk = new HashMap(); - - List topicList = this.period.getTopicList(); - for(Topic topic : topicList){ - if( ! (topic instanceof Talk)) continue; - Talk talk = (Talk) topic; - if(talk.getTalkCount() <= 0) continue; - if( this.talkFilter != null - && talk.getTalkType() != this.talkFilter) continue; - - Avatar avatar = talk.getAvatar(); - - Integer counts = talkCount.get(avatar); - if(counts == null) counts = Integer.valueOf(0); - counts++; - talkCount.put(avatar, counts); - - Integer total = totalChars.get(avatar); - if(total == null) total = Integer.valueOf(0); - total += talk.getTotalChars(); - totalChars.put(avatar, total); - - lastTalk.put(avatar, talk); - - avatarSet.add(avatar); - } - - int sum = 0; - for(Avatar avatar : avatarSet){ - Integer counts = talkCount.get(avatar); - Integer total = totalChars.get(avatar); - String lastTime = lastTalk.get(avatar).getAnchorNotation(); - appendRow(avatar, counts, total, lastTime); - sum += counts; - } - - this.totalSum.setText("合計:" + sum + " 発言"); - - Village village = this.period.getVillage(); - String villageName = village.getVillageName(); - String periodCaption = this.period.getCaption(); - this.caption.setText(villageName + "村 " + periodCaption); - - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowActivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeactivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowIconified(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeiconified(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowOpened(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * ダイアログのクローズボタン押下処理を行う。 - * @param event ウィンドウ変化イベント {@inheritDoc} - */ - @Override - public void windowClosing(WindowEvent event){ - close(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowClosed(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * クローズボタン押下処理。 - * @param event イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - if(event.getSource() != this.closeButton) return; - close(); - return; - } - - /** - * {@inheritDoc} - * コンボボックス操作処理。 - * @param event イベント {@inheritDoc} - */ - @Override - public void itemStateChanged(ItemEvent event){ - if(event.getStateChange() != ItemEvent.SELECTED) return; - - Object selected = this.typeSelector.getSelectedItem(); - if (selected == PUBTALK) this.talkFilter = TalkType.PUBLIC; - else if(selected == WOLFTALK) this.talkFilter = TalkType.WOLFONLY; - else if(selected == GRAVETALK) this.talkFilter = TalkType.GRAVE; - else if(selected == PRVTALK) this.talkFilter = TalkType.PRIVATE; - else if(selected == ALLTALK) this.talkFilter = null; - - summaryPeriod(); - - return; - } - - /** - * このパネルを閉じる。 - */ - private void close(){ - clearModel(); - this.period = null; - setVisible(false); - return; - } - - /** - * Avatar 顔イメージ描画用カスタムセルレンダラ。 - */ - private class CustomRenderer extends DefaultTableCellRenderer{ - - /** - * コンストラクタ。 - */ - public CustomRenderer(){ - super(); - return; - } - - /** - * {@inheritDoc} - * セルに{@link Avatar}がきたら顔アイコンと名前を表示する。 - * @param value {@inheritDoc} - */ - @Override - public void setValue(Object value){ - if(value instanceof Avatar){ - Avatar avatar = (Avatar) value; - - Village village = DaySummary.this.period.getVillage(); - Image image = village.getAvatarFaceImage(avatar); - if(image == null) image = village.getGraveImage(); - if(image != null){ - ImageIcon icon = new ImageIcon(image); - setIcon(icon); - } - - setText(avatar.getName()); - - Dimension prefSize = getPreferredSize(); - - int cellHeight = VERTICAL_GAP * 2 + prefSize.height; - if(DaySummary.this.tableComp.getRowHeight() < cellHeight){ - DaySummary.this.tableComp.setRowHeight(cellHeight); - } - - int cellWidth = HORIZONTAL_GAP * 2 + prefSize.width; - if( DaySummary.this.avatarColumn.getPreferredWidth() - < cellWidth ){ - DaySummary.this.avatarColumn.setPreferredWidth(cellWidth); - } - - return; - } - - super.setValue(value); - - return; - } - - /** - * {@inheritDoc} - * 統計種別によってセル色を変える。 - * @param table {@inheritDoc} - * @param value {@inheritDoc} - * @param isSelected {@inheritDoc} - * @param hasFocus {@inheritDoc} - * @param row {@inheritDoc} - * @param column {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Component getTableCellRendererComponent(JTable table, - Object value, - boolean isSelected, - boolean hasFocus, - int row, - int column ){ - Component result = super.getTableCellRendererComponent(table, - value, - isSelected, - hasFocus, - row, - column ); - - Object selected = DaySummary.this.typeSelector.getSelectedItem(); - Color bgColor = null; - if(selected == PUBTALK){ - bgColor = TalkDraw.COLOR_PUBLIC; - }else if(selected == WOLFTALK){ - bgColor = TalkDraw.COLOR_WOLFONLY; - }else if(selected == GRAVETALK){ - bgColor = TalkDraw.COLOR_GRAVE; - }else if(selected == PRVTALK){ - bgColor = TalkDraw.COLOR_PRIVATE; - }else if(selected == ALLTALK){ - bgColor = COLOR_ALL; - }else{ - assert false; - return null; - } - - result.setForeground(Color.BLACK); - result.setBackground(bgColor); - - return result; - } - } - -} +/* + * summary of day panel + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * その日ごとの集計。 + */ +@SuppressWarnings("serial") +public class DaySummary extends JDialog + implements WindowListener, ActionListener, ItemListener{ + + private static final String FRAMETITLE = + "発言集計 - " + Jindolf.TITLE; + private static final NumberFormat AVERAGE_FORM; + private static final String PUBTALK = "白発言"; + private static final String WOLFTALK = "赤発言"; + private static final String GRAVETALK = "青発言"; + private static final String PRVTALK = "灰発言"; + private static final String ALLTALK = "全発言"; + private static final int HORIZONTAL_GAP = 5; + private static final int VERTICAL_GAP = 1; + private static final Color COLOR_ALL = new Color(0xffff80); + + static{ + AVERAGE_FORM = NumberFormat.getInstance(); + AVERAGE_FORM.setMaximumFractionDigits(1); + AVERAGE_FORM.setMinimumFractionDigits(1); + } + + + private final DefaultTableModel tableModel; + private final TableColumn avatarColumn; + + private final JTable tableComp; + private final JComboBox typeSelector = new JComboBox(); + private final JButton closeButton = new JButton("閉じる"); + private final JLabel caption = new JLabel(); + private final JLabel totalSum = new JLabel(); + + private TalkType talkFilter; + private Period period; + + + /** + * コンストラクタ。 + * 集計結果を表示するモーダルダイアログを生成する。 + * @param owner オーナー + */ + public DaySummary(Frame owner){ + super(owner, FRAMETITLE, true); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(this); + + this.tableModel = createInitModel(); + this.tableComp = new JTable(); + this.tableComp.setModel(this.tableModel); + this.tableComp.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + this.tableComp.setIntercellSpacing( + new Dimension(HORIZONTAL_GAP, VERTICAL_GAP) ); + this.tableComp.setDefaultEditor(Object.class, null); + this.tableComp.setDefaultRenderer(Object.class, new CustomRenderer()); + this.tableComp.setShowGrid(true); + + TableColumnModel tcolModel = this.tableComp.getColumnModel(); + + this.avatarColumn = tcolModel.getColumn(0); + + DefaultTableCellRenderer renderer; + + renderer = new DefaultTableCellRenderer(); + renderer.setHorizontalAlignment(SwingConstants.RIGHT); + tcolModel.getColumn(1).setCellRenderer(renderer); + + renderer = new DefaultTableCellRenderer(); + renderer.setHorizontalAlignment(SwingConstants.RIGHT); + tcolModel.getColumn(2).setCellRenderer(renderer); + + renderer = new DefaultTableCellRenderer(); + renderer.setHorizontalAlignment(SwingConstants.RIGHT); + tcolModel.getColumn(3).setCellRenderer(renderer); + + this.typeSelector.addItem(PUBTALK); + this.typeSelector.addItem(WOLFTALK); + this.typeSelector.addItem(GRAVETALK); + this.typeSelector.addItem(PRVTALK); + this.typeSelector.addItem(ALLTALK); + + this.closeButton.addActionListener(this); + this.typeSelector.addItemListener(this); + + this.typeSelector.setSelectedItem(null); + this.typeSelector.setSelectedItem(PUBTALK); + + design(); + + clearModel(); + + return; + } + + + /** + * 初期のデータモデルを生成する。 + * @return データモデル + */ + private static DefaultTableModel createInitModel(){ + DefaultTableModel result; + result = new DefaultTableModel(); + + Object[] rowHeads = {"名前", "発言回数", "平均文字列長", "最終発言"}; + result.setColumnCount(rowHeads.length); + result.setColumnIdentifiers(rowHeads); + + return result; + } + + + /** + * テーブルをクリアする。 + */ + private void clearModel(){ + int rows = this.tableModel.getRowCount(); + for(int ct = 1; ct <= rows; ct++){ + this.tableModel.removeRow(0); + } + } + + /** + * 行を追加する。 + * @param avatar アバター + * @param talkCount 発言回数 + * @param totalChars 発言文字総数 + * @param lastTime 最終発言時刻 + */ + private void appendRow(Avatar avatar, + Integer talkCount, + Integer totalChars, + String lastTime ){ + String talks = talkCount + " 回"; + + double average; + if(talkCount <= 0) average = 0.0; + else average = (double)totalChars / (double)talkCount; + String chars = AVERAGE_FORM.format(average) + " 文字"; + + Object[] row = {avatar, talks, chars, lastTime}; + int rowIndex = this.tableModel.getRowCount(); + + this.tableModel.insertRow(rowIndex, row); + + return; + } + + /** + * デザインを行う。 + */ + private void design(){ + Container content = getContentPane(); + + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + content.setLayout(layout); + + constraints.insets = new Insets(5, 5, 5, 5); + + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.WEST; + content.add(this.caption, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.NONE; + content.add(this.typeSelector, constraints); + + JScrollPane scroller = new JScrollPane(this.tableComp); + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + content.add(scroller, constraints); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.WEST; + content.add(this.totalSum, constraints); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + content.add(this.closeButton, constraints); + + return; + } + + /** + * 与えられたPeriodで集計を更新する。 + * @param newPeriod 日 + */ + public void summaryPeriod(Period newPeriod){ + this.period = newPeriod; + summaryPeriod(); + } + + /** + * 集計を更新する。 + */ + private void summaryPeriod(){ + clearModel(); + + if(this.period == null) return; + + SortedSet avatarSet = new TreeSet(); + Map talkCount = new HashMap(); + Map totalChars = new HashMap(); + Map lastTalk = new HashMap(); + + List topicList = this.period.getTopicList(); + for(Topic topic : topicList){ + if( ! (topic instanceof Talk)) continue; + Talk talk = (Talk) topic; + if(talk.getTalkCount() <= 0) continue; + if( this.talkFilter != null + && talk.getTalkType() != this.talkFilter) continue; + + Avatar avatar = talk.getAvatar(); + + Integer counts = talkCount.get(avatar); + if(counts == null) counts = Integer.valueOf(0); + counts++; + talkCount.put(avatar, counts); + + Integer total = totalChars.get(avatar); + if(total == null) total = Integer.valueOf(0); + total += talk.getTotalChars(); + totalChars.put(avatar, total); + + lastTalk.put(avatar, talk); + + avatarSet.add(avatar); + } + + int sum = 0; + for(Avatar avatar : avatarSet){ + Integer counts = talkCount.get(avatar); + Integer total = totalChars.get(avatar); + String lastTime = lastTalk.get(avatar).getAnchorNotation(); + appendRow(avatar, counts, total, lastTime); + sum += counts; + } + + this.totalSum.setText("合計:" + sum + " 発言"); + + Village village = this.period.getVillage(); + String villageName = village.getVillageName(); + String periodCaption = this.period.getCaption(); + this.caption.setText(villageName + "村 " + periodCaption); + + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowActivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeactivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowIconified(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeiconified(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowOpened(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * ダイアログのクローズボタン押下処理を行う。 + * @param event ウィンドウ変化イベント {@inheritDoc} + */ + @Override + public void windowClosing(WindowEvent event){ + close(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowClosed(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * クローズボタン押下処理。 + * @param event イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + if(event.getSource() != this.closeButton) return; + close(); + return; + } + + /** + * {@inheritDoc} + * コンボボックス操作処理。 + * @param event イベント {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent event){ + if(event.getStateChange() != ItemEvent.SELECTED) return; + + Object selected = this.typeSelector.getSelectedItem(); + if (selected == PUBTALK) this.talkFilter = TalkType.PUBLIC; + else if(selected == WOLFTALK) this.talkFilter = TalkType.WOLFONLY; + else if(selected == GRAVETALK) this.talkFilter = TalkType.GRAVE; + else if(selected == PRVTALK) this.talkFilter = TalkType.PRIVATE; + else if(selected == ALLTALK) this.talkFilter = null; + + summaryPeriod(); + + return; + } + + /** + * このパネルを閉じる。 + */ + private void close(){ + clearModel(); + this.period = null; + setVisible(false); + return; + } + + /** + * Avatar 顔イメージ描画用カスタムセルレンダラ。 + */ + private class CustomRenderer extends DefaultTableCellRenderer{ + + /** + * コンストラクタ。 + */ + public CustomRenderer(){ + super(); + return; + } + + /** + * {@inheritDoc} + * セルに{@link Avatar}がきたら顔アイコンと名前を表示する。 + * @param value {@inheritDoc} + */ + @Override + public void setValue(Object value){ + if(value instanceof Avatar){ + Avatar avatar = (Avatar) value; + + Village village = DaySummary.this.period.getVillage(); + Image image = village.getAvatarFaceImage(avatar); + if(image == null) image = village.getGraveImage(); + if(image != null){ + ImageIcon icon = new ImageIcon(image); + setIcon(icon); + } + + setText(avatar.getName()); + + Dimension prefSize = getPreferredSize(); + + int cellHeight = VERTICAL_GAP * 2 + prefSize.height; + if(DaySummary.this.tableComp.getRowHeight() < cellHeight){ + DaySummary.this.tableComp.setRowHeight(cellHeight); + } + + int cellWidth = HORIZONTAL_GAP * 2 + prefSize.width; + if( DaySummary.this.avatarColumn.getPreferredWidth() + < cellWidth ){ + DaySummary.this.avatarColumn.setPreferredWidth(cellWidth); + } + + return; + } + + super.setValue(value); + + return; + } + + /** + * {@inheritDoc} + * 統計種別によってセル色を変える。 + * @param table {@inheritDoc} + * @param value {@inheritDoc} + * @param isSelected {@inheritDoc} + * @param hasFocus {@inheritDoc} + * @param row {@inheritDoc} + * @param column {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column ){ + Component result = super.getTableCellRendererComponent(table, + value, + isSelected, + hasFocus, + row, + column ); + + Object selected = DaySummary.this.typeSelector.getSelectedItem(); + Color bgColor = null; + if(selected == PUBTALK){ + bgColor = TalkDraw.COLOR_PUBLIC; + }else if(selected == WOLFTALK){ + bgColor = TalkDraw.COLOR_WOLFONLY; + }else if(selected == GRAVETALK){ + bgColor = TalkDraw.COLOR_GRAVE; + }else if(selected == PRVTALK){ + bgColor = TalkDraw.COLOR_PRIVATE; + }else if(selected == ALLTALK){ + bgColor = COLOR_ALL; + }else{ + assert false; + return null; + } + + result.setForeground(Color.BLACK); + result.setBackground(bgColor); + + return result; + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/DialogPref.java b/src/main/java/jp/sourceforge/jindolf/DialogPref.java index 54eaf6a..84b17e3 100644 --- a/src/main/java/jp/sourceforge/jindolf/DialogPref.java +++ b/src/main/java/jp/sourceforge/jindolf/DialogPref.java @@ -1,128 +1,128 @@ -/* - * dialog preferences - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -/** - * 発言表示設定。 - */ -public class DialogPref{ - - private boolean useBodyImage = false; - private boolean useMonoImage = false; - private boolean isSimpleMode = false; - private boolean alignBaloonWidth = false; - - /** - * コンストラクタ。 - */ - public DialogPref(){ - super(); - return; - } - - /** - * デカキャラモードを使うか否か状態を返す。 - * @return デカキャラモードを使うならtrue - */ - public boolean useBodyImage(){ - return this.useBodyImage; - } - - /** - * 遺影モードを使うか否か状態を返す。 - * @return 遺影モードを使うならtrue - */ - public boolean useMonoImage(){ - return this.useMonoImage; - } - - /** - * シンプル表示モードを使うか否か状態を返す。 - * @return シンプルモードならtrue - */ - public boolean isSimpleMode(){ - return this.isSimpleMode; - } - - /** - * バルーン幅揃えモードを使うか否か状態を返す。 - * @return バルーン幅揃えモードならtrue - */ - public boolean alignBaloonWidth(){ - return this.alignBaloonWidth; - } - - /** - * デカキャラモードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setBodyImageSetting(boolean setting){ - this.useBodyImage = setting; - return; - } - - /** - * 遺影モードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setMonoImageSetting(boolean setting){ - this.useMonoImage = setting; - return; - } - - /** - * シンプルモードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setSimpleMode(boolean setting){ - this.isSimpleMode = setting; - return; - } - - /** - * バルーン幅揃えの設定を行う。 - * @param setting バルーン幅を揃えたいならtrue - */ - public void setAlignBalooonWidthSetting(boolean setting){ - this.alignBaloonWidth = setting; - return; - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj instanceof DialogPref) return false; - DialogPref target = (DialogPref) obj; - - if(this.useBodyImage != target.useBodyImage) return false; - if(this.useMonoImage != target.useMonoImage) return false; - if(this.isSimpleMode != target.isSimpleMode) return false; - if(this.alignBaloonWidth != target.alignBaloonWidth) return false; - - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - int hash; - hash = Boolean.valueOf(this.useBodyImage) .hashCode() << 0; - hash ^= Boolean.valueOf(this.useMonoImage) .hashCode() << 4; - hash ^= Boolean.valueOf(this.isSimpleMode) .hashCode() << 8; - hash ^= Boolean.valueOf(this.alignBaloonWidth).hashCode() << 12; - return hash; - } - -} +/* + * dialog preferences + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +/** + * 発言表示設定。 + */ +public class DialogPref{ + + private boolean useBodyImage = false; + private boolean useMonoImage = false; + private boolean isSimpleMode = false; + private boolean alignBaloonWidth = false; + + /** + * コンストラクタ。 + */ + public DialogPref(){ + super(); + return; + } + + /** + * デカキャラモードを使うか否か状態を返す。 + * @return デカキャラモードを使うならtrue + */ + public boolean useBodyImage(){ + return this.useBodyImage; + } + + /** + * 遺影モードを使うか否か状態を返す。 + * @return 遺影モードを使うならtrue + */ + public boolean useMonoImage(){ + return this.useMonoImage; + } + + /** + * シンプル表示モードを使うか否か状態を返す。 + * @return シンプルモードならtrue + */ + public boolean isSimpleMode(){ + return this.isSimpleMode; + } + + /** + * バルーン幅揃えモードを使うか否か状態を返す。 + * @return バルーン幅揃えモードならtrue + */ + public boolean alignBaloonWidth(){ + return this.alignBaloonWidth; + } + + /** + * デカキャラモードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setBodyImageSetting(boolean setting){ + this.useBodyImage = setting; + return; + } + + /** + * 遺影モードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setMonoImageSetting(boolean setting){ + this.useMonoImage = setting; + return; + } + + /** + * シンプルモードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setSimpleMode(boolean setting){ + this.isSimpleMode = setting; + return; + } + + /** + * バルーン幅揃えの設定を行う。 + * @param setting バルーン幅を揃えたいならtrue + */ + public void setAlignBalooonWidthSetting(boolean setting){ + this.alignBaloonWidth = setting; + return; + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj instanceof DialogPref) return false; + DialogPref target = (DialogPref) obj; + + if(this.useBodyImage != target.useBodyImage) return false; + if(this.useMonoImage != target.useMonoImage) return false; + if(this.isSimpleMode != target.isSimpleMode) return false; + if(this.alignBaloonWidth != target.alignBaloonWidth) return false; + + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + int hash; + hash = Boolean.valueOf(this.useBodyImage) .hashCode() << 0; + hash ^= Boolean.valueOf(this.useMonoImage) .hashCode() << 4; + hash ^= Boolean.valueOf(this.isSimpleMode) .hashCode() << 8; + hash ^= Boolean.valueOf(this.alignBaloonWidth).hashCode() << 12; + return hash; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/DialogPrefPanel.java b/src/main/java/jp/sourceforge/jindolf/DialogPrefPanel.java index cd58f68..803d480 100644 --- a/src/main/java/jp/sourceforge/jindolf/DialogPrefPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/DialogPrefPanel.java @@ -1,249 +1,249 @@ -/* - * dialog preference panel - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.border.Border; - -/** - * 発言表示の各種設定パネル。 - */ -@SuppressWarnings("serial") -public class DialogPrefPanel - extends JPanel - implements ActionListener, - ItemListener { - - private final JCheckBox useBodyImage = new JCheckBox("デカキャラモード"); - private final JCheckBox useMonoImage = - new JCheckBox("墓石を遺影に置き換える"); - private final JCheckBox isSimpleMode = - new JCheckBox("シンプル表示モード"); - private final JCheckBox alignBaloon = - new JCheckBox("フキダシ幅を揃える"); - private final JButton resetDefault = new JButton("出荷時に戻す"); - - /** - * コンストラクタ。 - */ - public DialogPrefPanel(){ - this.resetDefault.addActionListener(this); - this.isSimpleMode.addItemListener(this); - - design(this); - modifyGUIState(); - - return; - } - - /** - * レイアウトを行う。 - * @param content コンテナ - */ - private void design(Container content){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.weightx = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHWEST; - - content.add(this.isSimpleMode, constraints); - content.add(this.alignBaloon, constraints); - content.add(buildIconPanel(), constraints); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.SOUTHEAST; - content.add(this.resetDefault, constraints); - - return; - } - - /** - * アイコン設定パネルを生成する。 - * @return アイコン設定パネル - */ - private JComponent buildIconPanel(){ - JPanel result = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - result.setLayout(layout); - - constraints.insets = new Insets(1, 1, 1, 1); - - constraints.weightx = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHWEST; - - result.add(this.useBodyImage, constraints); - result.add(this.useMonoImage, constraints); - - Border border = BorderFactory.createTitledBorder("アイコン表示"); - result.setBorder(border); - - return result; - } - - /** - * GUI間の一貫性を維持する。 - */ - private void modifyGUIState(){ - if(this.isSimpleMode.isSelected()){ - this.useBodyImage.setEnabled(false); - this.useMonoImage.setEnabled(false); - this.alignBaloon .setEnabled(false); - }else{ - this.useBodyImage.setEnabled(true); - this.useMonoImage.setEnabled(true); - this.alignBaloon .setEnabled(true); - } - - return; - } - - /** - * デカキャラモードを使うか否か画面の状態を返す。 - * @return デカキャラモードを使うならtrue - */ - public boolean useBodyImage(){ - return this.useBodyImage.isSelected(); - } - - /** - * 遺影モードを使うか否か画面の状態を返す。 - * @return 遺影モードを使うならtrue - */ - public boolean useMonoImage(){ - return this.useMonoImage.isSelected(); - } - - /** - * シンプル表示モードか否か画面の状態を返す。 - * @return シンプル表示モードならtrue - */ - public boolean isSimpleMode(){ - return this.isSimpleMode.isSelected(); - } - - /** - * フキダシ幅を揃えるか否か画面の状態を返す。 - * @return フキダシ幅を揃えるならtrue - */ - public boolean alignBaloon(){ - return this.alignBaloon.isSelected(); - } - - /** - * デカキャラモードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setBodyImageSetting(boolean setting){ - this.useBodyImage.setSelected(setting); - return; - } - - /** - * 遺影モードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setMonoImageSetting(boolean setting){ - this.useMonoImage.setSelected(setting); - return; - } - - /** - * シンプル表示モードの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setSimpleModeSetting(boolean setting){ - this.isSimpleMode.setSelected(setting); - modifyGUIState(); - return; - } - - /** - * フキダシ幅揃えの設定を行う。 - * @param setting 有効にするならtrue - */ - public void setAlignBaloonSetting(boolean setting){ - this.alignBaloon.setSelected(setting); - return; - } - - /** - * 発言表示設定を設定する。 - * @param pref 表示設定 - */ - public void setDialogPref(DialogPref pref){ - setBodyImageSetting(pref.useBodyImage()); - setMonoImageSetting(pref.useMonoImage()); - setSimpleModeSetting(pref.isSimpleMode()); - setAlignBaloonSetting(pref.alignBaloonWidth()); - modifyGUIState(); - return; - } - - /** - * 発言表示設定を返す。 - * @return 表示設定 - */ - public DialogPref getDialogPref(){ - DialogPref result = new DialogPref(); - result.setBodyImageSetting(useBodyImage()); - result.setMonoImageSetting(useMonoImage()); - result.setSimpleMode(isSimpleMode()); - result.setAlignBalooonWidthSetting(alignBaloon()); - return result; - } - - /** - * デフォルトボタン押下処理。 - * @param event ボタン押下イベント - */ - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if(source != this.resetDefault) return; - this.useBodyImage.setSelected(false); - this.useMonoImage.setSelected(false); - this.isSimpleMode.setSelected(false); - this.alignBaloon.setSelected(false); - modifyGUIState(); - return; - } - - /** - * チェックボックス操作の受信。 - * @param event チェックボックス操作イベント - */ - public void itemStateChanged(ItemEvent event){ - modifyGUIState(); - return; - } - -} +/* + * dialog preference panel + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.border.Border; + +/** + * 発言表示の各種設定パネル。 + */ +@SuppressWarnings("serial") +public class DialogPrefPanel + extends JPanel + implements ActionListener, + ItemListener { + + private final JCheckBox useBodyImage = new JCheckBox("デカキャラモード"); + private final JCheckBox useMonoImage = + new JCheckBox("墓石を遺影に置き換える"); + private final JCheckBox isSimpleMode = + new JCheckBox("シンプル表示モード"); + private final JCheckBox alignBaloon = + new JCheckBox("フキダシ幅を揃える"); + private final JButton resetDefault = new JButton("出荷時に戻す"); + + /** + * コンストラクタ。 + */ + public DialogPrefPanel(){ + this.resetDefault.addActionListener(this); + this.isSimpleMode.addItemListener(this); + + design(this); + modifyGUIState(); + + return; + } + + /** + * レイアウトを行う。 + * @param content コンテナ + */ + private void design(Container content){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.weightx = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + + content.add(this.isSimpleMode, constraints); + content.add(this.alignBaloon, constraints); + content.add(buildIconPanel(), constraints); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.SOUTHEAST; + content.add(this.resetDefault, constraints); + + return; + } + + /** + * アイコン設定パネルを生成する。 + * @return アイコン設定パネル + */ + private JComponent buildIconPanel(){ + JPanel result = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + result.setLayout(layout); + + constraints.insets = new Insets(1, 1, 1, 1); + + constraints.weightx = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + + result.add(this.useBodyImage, constraints); + result.add(this.useMonoImage, constraints); + + Border border = BorderFactory.createTitledBorder("アイコン表示"); + result.setBorder(border); + + return result; + } + + /** + * GUI間の一貫性を維持する。 + */ + private void modifyGUIState(){ + if(this.isSimpleMode.isSelected()){ + this.useBodyImage.setEnabled(false); + this.useMonoImage.setEnabled(false); + this.alignBaloon .setEnabled(false); + }else{ + this.useBodyImage.setEnabled(true); + this.useMonoImage.setEnabled(true); + this.alignBaloon .setEnabled(true); + } + + return; + } + + /** + * デカキャラモードを使うか否か画面の状態を返す。 + * @return デカキャラモードを使うならtrue + */ + public boolean useBodyImage(){ + return this.useBodyImage.isSelected(); + } + + /** + * 遺影モードを使うか否か画面の状態を返す。 + * @return 遺影モードを使うならtrue + */ + public boolean useMonoImage(){ + return this.useMonoImage.isSelected(); + } + + /** + * シンプル表示モードか否か画面の状態を返す。 + * @return シンプル表示モードならtrue + */ + public boolean isSimpleMode(){ + return this.isSimpleMode.isSelected(); + } + + /** + * フキダシ幅を揃えるか否か画面の状態を返す。 + * @return フキダシ幅を揃えるならtrue + */ + public boolean alignBaloon(){ + return this.alignBaloon.isSelected(); + } + + /** + * デカキャラモードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setBodyImageSetting(boolean setting){ + this.useBodyImage.setSelected(setting); + return; + } + + /** + * 遺影モードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setMonoImageSetting(boolean setting){ + this.useMonoImage.setSelected(setting); + return; + } + + /** + * シンプル表示モードの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setSimpleModeSetting(boolean setting){ + this.isSimpleMode.setSelected(setting); + modifyGUIState(); + return; + } + + /** + * フキダシ幅揃えの設定を行う。 + * @param setting 有効にするならtrue + */ + public void setAlignBaloonSetting(boolean setting){ + this.alignBaloon.setSelected(setting); + return; + } + + /** + * 発言表示設定を設定する。 + * @param pref 表示設定 + */ + public void setDialogPref(DialogPref pref){ + setBodyImageSetting(pref.useBodyImage()); + setMonoImageSetting(pref.useMonoImage()); + setSimpleModeSetting(pref.isSimpleMode()); + setAlignBaloonSetting(pref.alignBaloonWidth()); + modifyGUIState(); + return; + } + + /** + * 発言表示設定を返す。 + * @return 表示設定 + */ + public DialogPref getDialogPref(){ + DialogPref result = new DialogPref(); + result.setBodyImageSetting(useBodyImage()); + result.setMonoImageSetting(useMonoImage()); + result.setSimpleMode(isSimpleMode()); + result.setAlignBalooonWidthSetting(alignBaloon()); + return result; + } + + /** + * デフォルトボタン押下処理。 + * @param event ボタン押下イベント + */ + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if(source != this.resetDefault) return; + this.useBodyImage.setSelected(false); + this.useMonoImage.setSelected(false); + this.isSimpleMode.setSelected(false); + this.alignBaloon.setSelected(false); + modifyGUIState(); + return; + } + + /** + * チェックボックス操作の受信。 + * @param event チェックボックス操作イベント + */ + public void itemStateChanged(ItemEvent event){ + modifyGUIState(); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Discussion.java b/src/main/java/jp/sourceforge/jindolf/Discussion.java index 0a0260f..890ec86 100644 --- a/src/main/java/jp/sourceforge/jindolf/Discussion.java +++ b/src/main/java/jp/sourceforge/jindolf/Discussion.java @@ -1,1258 +1,1258 @@ -/* - * discussion viewer - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseEvent; -import java.awt.font.FontRenderContext; -import java.io.IOException; -import java.util.EventListener; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.regex.Pattern; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.JComponent; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.JTextField; -import javax.swing.KeyStroke; -import javax.swing.Scrollable; -import javax.swing.SwingConstants; -import javax.swing.event.EventListenerList; -import javax.swing.event.MouseInputListener; -import javax.swing.text.DefaultEditorKit; - -/** - * 発言表示画面。 - * - * 表示に影響する要因は、Periodの中身、LayoutManagerによるサイズ変更、 - * フォント属性の指定、フィルタリング操作、ドラッギングによる文字列選択操作、 - * 文字列検索および検索ナビゲーション。 - */ -@SuppressWarnings("serial") -public class Discussion extends JComponent - implements Scrollable, MouseInputListener, ComponentListener{ - - private static final Color COLOR_NORMALBG = Color.BLACK; - private static final Color COLOR_SIMPLEBG = Color.WHITE; - - private static final int MARGINTOP = 50; - private static final int MARGINBOTTOM = 100; - - private Period period; - private final List rowList = new LinkedList(); - private final List talkDrawList = new LinkedList(); - - private TopicFilter topicFilter; - private TopicFilter.FilterContext filterContext; - private RegexPattern regexPattern; - - private Point dragFrom; - - private FontInfo fontInfo; - private final RenderingHints hints = new RenderingHints(null); - - private DialogPref dialogPref; - - private Dimension idealSize; - private int lastWidth = -1; - - private final DiscussionPopup popup = new DiscussionPopup(); - - private final EventListenerList thisListenerList = - new EventListenerList(); - - private final Action copySelectedAction = - new ProxyAction(ActionManager.CMD_COPY); - - /** - * 発言表示画面を作成する。 - */ - public Discussion(){ - super(); - - this.fontInfo = FontInfo.DEFAULT_FONTINFO; - this.dialogPref = new DialogPref(); - - this.hints.put(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - this.hints.put(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY); - updateRenderingHints(); - - setPeriod(null); - - addMouseListener(this); - addMouseMotionListener(this); - addComponentListener(this); - - setComponentPopupMenu(this.popup); - - updateInputMap(); - ActionMap actionMap = getActionMap(); - actionMap.put(DefaultEditorKit.copyAction, this.copySelectedAction); - - setColorDesign(); - - return; - } - - /** - * 描画設定の更新。 - * FontRenderContextが更新された後は必ず呼び出す必要がある。 - */ - private void updateRenderingHints(){ - Object textAliaseValue; - FontRenderContext context = this.fontInfo.getFontRenderContext(); - if(context.isAntiAliased()){ - textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_ON; - }else{ - textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; - } - this.hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, - textAliaseValue); - - Object textFractionalValue; - if(context.usesFractionalMetrics()){ - textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_ON; - }else{ - textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_OFF; - } - this.hints.put(RenderingHints.KEY_FRACTIONALMETRICS, - textFractionalValue); - - return; - } - - /** - * 配色を設定する。 - */ - private void setColorDesign(){ - Color fgColor; - if(this.dialogPref.isSimpleMode()){ - fgColor = COLOR_SIMPLEBG; - }else{ - fgColor = COLOR_NORMALBG; - } - - setForeground(fgColor); - repaint(); - - return; - } - - /** - * フォント描画設定を変更する。 - * @param newFontInfo フォント設定 - */ - public void setFontInfo(FontInfo newFontInfo){ - this.fontInfo = newFontInfo; - - updateRenderingHints(); - - for(TextRow row : this.rowList){ - row.setFontInfo(this.fontInfo); - } - - setColorDesign(); - layoutRows(); - - revalidate(); - repaint(); - - return; - } - - /** - * 発言表示設定を変更する。 - * @param newPref 発言表示設定 - */ - public void setDialogPref(DialogPref newPref){ - this.dialogPref = newPref; - - for(TextRow row : this.rowList){ - if(row instanceof TalkDraw){ - TalkDraw talkDraw = (TalkDraw) row; - talkDraw.setDialogPref(this.dialogPref); - }else if(row instanceof SysEventDraw){ - SysEventDraw sysDraw = (SysEventDraw) row; - sysDraw.setDialogPref(this.dialogPref); - } - } - - setColorDesign(); - layoutRows(); - - revalidate(); - repaint(); - - return; - } - - /** - * 現在のPeriodを返す。 - * @return 現在のPeriod - */ - public Period getPeriod(){ - return this.period; - } - - /** - * Periodを更新する。 - * 新しいPeriodの表示内容はまだ反映されない。 - * @param period 新しいPeriod - */ - public final void setPeriod(Period period){ - if(period == null){ - this.period = null; - this.rowList.clear(); - this.talkDrawList.clear(); - return; - } - - if( this.period == period - && period.getTopics() == this.rowList.size() ){ - filterTopics(); - return; - } - - this.period = period; - - this.filterContext = null; - - this.rowList.clear(); - this.talkDrawList.clear(); - for(Topic topic : this.period.getTopicList()){ - TextRow row; - if(topic instanceof Talk){ - Talk talk = (Talk) topic; - TalkDraw talkDraw = new TalkDraw(talk, - this.dialogPref, - this.fontInfo ); - this.talkDrawList.add(talkDraw); - row = talkDraw; - }else if(topic instanceof SysEvent){ - SysEvent sysEvent = (SysEvent) topic; - row = new SysEventDraw(sysEvent, - this.dialogPref, - this.fontInfo ); - }else{ - assert false; - continue; - } - this.rowList.add(row); - } - - filterTopics(); - - clearSizeCache(); - - layoutRows(); - - return; - } - - /** - * 発言フィルタを設定する。 - * @param filter 発言フィルタ - */ - public void setTopicFilter(TopicFilter filter){ - this.topicFilter = filter; - filtering(); - return; - } - - /** - * 発言フィルタを適用する。 - */ - public void filtering(){ - if( this.topicFilter != null - && this.topicFilter.isSame(this.filterContext)){ - return; - } - - if(this.topicFilter != null){ - this.filterContext = this.topicFilter.getFilterContext(); - }else{ - this.filterContext = null; - } - - filterTopics(); - layoutVertical(); - - clearSelect(); - - return; - } - - /** - * 検索パターンを取得する。 - * @return 検索パターン - */ - public RegexPattern getRegexPattern(){ - return this.regexPattern; - } - - /** - * 与えられた正規表現にマッチする文字列をハイライト描画する。 - * @param newPattern 検索パターン - * @return ヒット件数 - */ - public int setRegexPattern(RegexPattern newPattern){ - this.regexPattern = newPattern; - - int total = 0; - - clearHotTarget(); - - Pattern pattern = null; - if(this.regexPattern != null){ - pattern = this.regexPattern.getPattern(); - } - - for(TalkDraw talkDraw : this.talkDrawList){ - total += talkDraw.setRegex(pattern); - } - - repaint(); - - return total; - } - - /** - * 検索結果の次候補をハイライト表示する。 - */ - public void nextHotTarget(){ - TalkDraw oldTalk = null; - int oldIndex = -1; - TalkDraw newTalk = null; - int newIndex = -1; - TalkDraw firstTalk = null; - - boolean findOld = true; - for(TalkDraw talkDraw : this.talkDrawList){ - int matches = talkDraw.getRegexMatches(); - if(firstTalk == null && matches > 0){ - firstTalk = talkDraw; - } - if(findOld){ - int index = talkDraw.getHotTargetIndex(); - if(index < 0) continue; - oldTalk = talkDraw; - oldIndex = index; - scrollRectWithMargin(talkDraw.getHotTargetRectangle()); - if(oldIndex < matches - 1 && ! isFiltered(talkDraw) ){ - newTalk = talkDraw; - newIndex = oldIndex + 1; - break; - } - findOld = false; - }else{ - if(isFiltered(talkDraw)) continue; - if(matches <= 0) continue; - newTalk = talkDraw; - newIndex = 0; - break; - } - } - - Rectangle showRect = null; - if(oldTalk == null && firstTalk != null){ - firstTalk.setHotTargetIndex(0); - showRect = firstTalk.getHotTargetRectangle(); - }else if( oldTalk != null - && newTalk != null){ - oldTalk.clearHotTarget(); - newTalk.setHotTargetIndex(newIndex); - showRect = newTalk.getHotTargetRectangle(); - } - - if(showRect != null){ - scrollRectWithMargin(showRect); - } - - repaint(); - - return; - } - - /** - * 検索結果の前候補をハイライト表示する。 - */ - public void prevHotTarget(){ - TalkDraw oldTalk = null; - int oldIndex = -1; - TalkDraw newTalk = null; - int newIndex = -1; - TalkDraw firstTalk = null; - - boolean findOld = true; - int size = this.talkDrawList.size(); - ListIterator iterator = - this.talkDrawList.listIterator(size); - while(iterator.hasPrevious()){ - TalkDraw talkDraw = iterator.previous(); - int matches = talkDraw.getRegexMatches(); - if(firstTalk == null && matches > 0){ - firstTalk = talkDraw; - } - if(findOld){ - int index = talkDraw.getHotTargetIndex(); - if(index < 0) continue; - oldTalk = talkDraw; - oldIndex = index; - scrollRectWithMargin(talkDraw.getHotTargetRectangle()); - if(oldIndex > 0 && ! isFiltered(talkDraw) ){ - newTalk = talkDraw; - newIndex = oldIndex - 1; - break; - } - findOld = false; - }else{ - if(isFiltered(talkDraw)) continue; - if(matches <= 0) continue; - newTalk = talkDraw; - newIndex = matches - 1; - break; - } - } - - Rectangle showRect = null; - if(oldTalk == null && firstTalk != null){ - int matches = firstTalk.getRegexMatches(); - firstTalk.setHotTargetIndex(matches - 1); - showRect = firstTalk.getHotTargetRectangle(); - }else if( oldTalk != null - && newTalk != null){ - oldTalk.clearHotTarget(); - newTalk.setHotTargetIndex(newIndex); - showRect = newTalk.getHotTargetRectangle(); - } - - if(showRect != null){ - scrollRectWithMargin(showRect); - } - - repaint(); - - return; - } - - /** - * 検索結果の特殊ハイライト表示を解除。 - */ - public void clearHotTarget(){ - for(TalkDraw talkDraw : this.talkDrawList){ - talkDraw.clearHotTarget(); - } - repaint(); - return; - } - - /** - * 指定した領域に若干の上下マージンを付けて - * スクロールウィンドウに表示させる。 - * @param rectangle 指定領域 - */ - private void scrollRectWithMargin(Rectangle rectangle){ - Rectangle show = new Rectangle(rectangle); - show.y -= MARGINTOP; - show.height += MARGINTOP + MARGINBOTTOM; - - scrollRectToVisible(show); - - return; - } - - /** - * 過去に計算した寸法を破棄する。 - */ - private void clearSizeCache(){ - this.idealSize = null; - this.lastWidth = -1; - revalidate(); - return; - } - - /** - * 指定した矩形がフィルタリング対象か判定する。 - * @param row 矩形 - * @return フィルタリング対象ならtrue - */ - private boolean isFiltered(TextRow row){ - if(this.topicFilter == null) return false; - - Topic topic; - if(row instanceof TalkDraw){ - topic = ((TalkDraw)row).getTalk(); - }else if(row instanceof SysEventDraw){ - topic = ((SysEventDraw)row).getSysEvent(); - }else{ - return false; - } - - return this.topicFilter.isFiltered(topic); - } - - /** - * フィルタリング指定に従いTextRowを表示するか否か設定する。 - */ - private void filterTopics(){ - for(TextRow row : this.rowList){ - if(isFiltered(row)) row.setVisible(false); - else row.setVisible(true); - } - return; - } - - /** - * 幅を設定する。 - * 全子TextRowがリサイズされる。 - * @param width コンポーネント幅 - */ - private void setWidth(int width){ - this.lastWidth = width; - Insets insets = getInsets(); - int rowWidth = width - (insets.left + insets.right); - for(TextRow row : this.rowList){ - row.setWidth(rowWidth); - } - - layoutVertical(); - - return; - } - - /** - * 子TextRowの縦位置レイアウトを行う。 - * フィルタリングが反映される。 - * TextRowは必要に応じて移動させられるがリサイズされることはない。 - */ - private void layoutVertical(){ - Rectangle unionRect = null; - Insets insets = getInsets(); - int vertPos = insets.top; - - for(TextRow row : this.rowList){ - if( ! row.isVisible() ) continue; - - row.setPos(insets.left, vertPos); - Rectangle rowBound = row.getBounds(); - vertPos += rowBound.height; - - if(unionRect == null){ - unionRect = new Rectangle(rowBound); - }else{ - unionRect.add(rowBound); - } - } - - if(unionRect == null){ - unionRect = new Rectangle(insets.left, insets.top, 0, 0); - } - - if(this.idealSize == null){ - this.idealSize = new Dimension(); - } - - int newWidth = insets.left + unionRect.width + insets.right; - int newHeight = insets.top + unionRect.height + insets.bottom; - - this.idealSize.setSize(newWidth, newHeight); - - setPreferredSize(this.idealSize); - - revalidate(); - repaint(); - - return; - } - - /** - * Rowsの縦位置を再レイアウトする。 - */ - public void layoutRows(){ - int width = getWidth(); - setWidth(width); - return; - } - - /** - * {@inheritDoc} - * @param g {@inheritDoc} - */ - @Override - public void paintComponent(Graphics g){ - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHints(this.hints); - - Rectangle clipRect = g2.getClipBounds(); - g2.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); - - for(TextRow row : this.rowList){ - if( ! row.isVisible() ) continue; - - Rectangle rowRect = row.getBounds(); - if( ! rowRect.intersects(clipRect) ) continue; - - row.paint(g2); - } - - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Dimension getPreferredScrollableViewportSize(){ - return getPreferredSize(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean getScrollableTracksViewportWidth(){ - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean getScrollableTracksViewportHeight(){ - return false; - } - - /** - * {@inheritDoc} - * @param visibleRect {@inheritDoc} - * @param orientation {@inheritDoc} - * @param direction {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getScrollableBlockIncrement(Rectangle visibleRect, - int orientation, - int direction ){ - if(orientation == SwingConstants.VERTICAL){ - return visibleRect.height; - } - return 30; // TODO フォント高 × 1.5 ぐらい? - } - - /** - * {@inheritDoc} - * @param visibleRect {@inheritDoc} - * @param orientation {@inheritDoc} - * @param direction {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getScrollableUnitIncrement(Rectangle visibleRect, - int orientation, - int direction ){ - return 30; - } - - /** - * 任意の発言の表示が占める画面領域を返す。 - * 発言がフィルタリング対象の時はnullを返す。 - * @param talk 発言 - * @return 領域 - */ - public Rectangle getTalkBounds(Talk talk){ - if( this.topicFilter != null - && this.topicFilter.isFiltered(talk)) return null; - - for(TalkDraw talkDraw : this.talkDrawList){ - if(talkDraw.getTalk() == talk){ - Rectangle rect = talkDraw.getBounds(); - return rect; - } - } - - return null; - } - - /** - * ドラッグ処理を行う。 - * @param from ドラッグ開始位置 - * @param to 現在のドラッグ位置 - */ - private void drag(Point from, Point to){ - Rectangle dragRegion = new Rectangle(); - dragRegion.setFrameFromDiagonal(from, to); - - for(TextRow row : this.rowList){ - if(isFiltered(row)) continue; - if( ! row.getBounds().intersects(dragRegion) ) continue; - row.drag(from, to); - } - repaint(); - return; - } - - /** - * 選択範囲の解除。 - */ - private void clearSelect(){ - for(TextRow row : this.rowList){ - row.clearSelect(); - } - repaint(); - return; - } - - /** - * 与えられた点座標を包含する発言を返す。 - * @param pt 点座標(JComponent基準) - * @return 点座標を含む発言。含む発言がなければnullを返す。 - */ - // TODO 二分探索とかしたい。 - private TalkDraw getHittedTalkDraw(Point pt){ - for(TalkDraw talkDraw : this.talkDrawList){ - if(isFiltered(talkDraw)) continue; - Rectangle bounds = talkDraw.getBounds(); - if(bounds.contains(pt)) return talkDraw; - } - return null; - } - - /** - * アンカークリック動作の処理。 - * @param pt クリックポイント - */ - private void hitAnchor(Point pt){ - TalkDraw talkDraw = getHittedTalkDraw(pt); - if(talkDraw == null) return; - - Anchor anchor = talkDraw.getAnchor(pt); - if(anchor == null) return; - - for(AnchorHitListener listener : getAnchorHitListeners()){ - AnchorHitEvent event = - new AnchorHitEvent(this, talkDraw, anchor, pt); - listener.anchorHitted(event); - } - - return; - } - - /** - * 検索マッチ文字列クリック動作の処理。 - * @param pt クリックポイント - */ - private void hitRegex(Point pt){ - TalkDraw talkDraw = getHittedTalkDraw(pt); - if(talkDraw == null) return; - - int index = talkDraw.getRegexMatchIndex(pt); - if(index < 0) return; - - clearHotTarget(); - talkDraw.setHotTargetIndex(index); - - return; - } - - /** - * {@inheritDoc} - * アンカーヒット処理を行う。 - * MouseInputListenerを参照せよ。 - * @param event {@inheritDoc} - */ - // TODO 距離判定がシビアすぎ - @Override - public void mouseClicked(MouseEvent event){ - Point pt = event.getPoint(); - if(event.getButton() == MouseEvent.BUTTON1){ - clearSelect(); - hitAnchor(pt); - hitRegex(pt); - } - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void mouseEntered(MouseEvent event){ - // TODO ここでキーボードフォーカス処理が必要? - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void mouseExited(MouseEvent event){ - return; - } - - /** - * {@inheritDoc} - * ドラッグ開始処理を行う。 - * @param event {@inheritDoc} - */ - @Override - public void mousePressed(MouseEvent event){ - requestFocusInWindow(); - - if(event.getButton() == MouseEvent.BUTTON1){ - clearSelect(); - this.dragFrom = event.getPoint(); - } - - return; - } - - /** - * {@inheritDoc} - * ドラッグ終了処理を行う。 - * @param event {@inheritDoc} - */ - @Override - public void mouseReleased(MouseEvent event){ - if(event.getButton() == MouseEvent.BUTTON1){ - this.dragFrom = null; - } - return; - } - - /** - * {@inheritDoc} - * ドラッグ処理を行う。 - * @param event {@inheritDoc} - */ - // TODO ドラッグ範囲がビューポートを超えたら自動的にスクロールしてほしい。 - @Override - public void mouseDragged(MouseEvent event){ - if(this.dragFrom == null) return; - Point dragTo = event.getPoint(); - drag(this.dragFrom, dragTo); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void mouseMoved(MouseEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void componentShown(ComponentEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void componentHidden(ComponentEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void componentMoved(ComponentEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void componentResized(ComponentEvent event){ - int width = getWidth(); - int height = getHeight(); - if(width != this.lastWidth){ - setWidth(width); - } - if( this.idealSize.width != width - || this.idealSize.height != height ){ - revalidate(); - } - return; - } - - /** - * 選択文字列を返す。 - * @return 選択文字列 - */ - public CharSequence getSelected(){ - StringBuilder selected = new StringBuilder(); - - for(TextRow row : this.rowList){ - if(isFiltered(row)) continue; - try{ - row.appendSelected(selected); - }catch(IOException e){ - assert false; // ありえない - return null; - } - } - - if(selected.length() <= 0) return null; - - return selected; - } - - /** - * 選択文字列をクリップボードにコピーする。 - * @return 選択文字列 - */ - public CharSequence copySelected(){ - CharSequence selected = getSelected(); - if(selected == null) return null; - ClipboardAction.copyToClipboard(selected); - return selected; - } - - /** - * 矩形の示す一発言をクリップボードにコピーする。 - * @return コピーした文字列 - */ - public CharSequence copyTalk(){ - TalkDraw talkDraw = this.popup.lastPopupedTalkDraw; - if(talkDraw == null) return null; - Talk talk = talkDraw.getTalk(); - - StringBuilder selected = new StringBuilder(); - - Avatar avatar = talk.getAvatar(); - selected.append(avatar.getName()).append(' '); - - String anchor = talk.getAnchorNotation(); - selected.append(anchor); - if(talk.hasTalkNo()){ - selected.append(' ').append(talk.getAnchorNotation_G()); - } - selected.append('\n'); - - selected.append(talk.getDialog()); - if(selected.charAt(selected.length() - 1) != '\n'){ - selected.append('\n'); - } - - ClipboardAction.copyToClipboard(selected); - - return selected; - } - - /** - * ポップアップメニュートリガ座標に発言があればそれを返す。 - * @return 発言 - */ - public Talk getPopupedTalk(){ - TalkDraw talkDraw = this.popup.lastPopupedTalkDraw; - if(talkDraw == null) return null; - Talk talk = talkDraw.getTalk(); - return talk; - } - - /** - * ポップアップメニュートリガ座標にアンカーがあればそれを返す。 - * @return アンカー - */ - public Anchor getPopupedAnchor(){ - return this.popup.lastPopupedAnchor; - } - - /** - * {@inheritDoc} - */ - @Override - public void updateUI(){ - super.updateUI(); - this.popup.updateUI(); - - updateInputMap(); - - return; - } - - /** - * COPY処理を行うキーの設定をJTextFieldから流用する。 - * おそらくはCtrl-C。MacならCommand-Cかも。 - */ - private void updateInputMap(){ - InputMap thisInputMap = getInputMap(); - - InputMap sampleInputMap; - sampleInputMap = new JTextField().getInputMap(); - KeyStroke[] strokes = sampleInputMap.allKeys(); - for(KeyStroke stroke : strokes){ - Object bind = sampleInputMap.get(stroke); - if(bind.equals(DefaultEditorKit.copyAction)){ - thisInputMap.put(stroke, DefaultEditorKit.copyAction); - } - } - - return; - } - - /** - * ActionListenerを追加する。 - * @param listener リスナー - */ - public void addActionListener(ActionListener listener){ - this.thisListenerList.add(ActionListener.class, listener); - - this.popup.menuCopy .addActionListener(listener); - this.popup.menuSelTalk .addActionListener(listener); - this.popup.menuJumpAnchor .addActionListener(listener); - this.popup.menuWebTalk .addActionListener(listener); - this.popup.menuSummary .addActionListener(listener); - - return; - } - - /** - * ActionListenerを削除する。 - * @param listener リスナー - */ - public void removeActionListener(ActionListener listener){ - this.thisListenerList.remove(ActionListener.class, listener); - - this.popup.menuCopy .removeActionListener(listener); - this.popup.menuSelTalk .removeActionListener(listener); - this.popup.menuJumpAnchor .removeActionListener(listener); - this.popup.menuWebTalk .removeActionListener(listener); - this.popup.menuSummary .removeActionListener(listener); - - return; - } - - /** - * ActionListenerを列挙する。 - * @return すべてのActionListener - */ - public ActionListener[] getActionListeners(){ - return this.thisListenerList.getListeners(ActionListener.class); - } - - /** - * AnchorHitListenerを追加する。 - * @param listener リスナー - */ - public void addAnchorHitListener(AnchorHitListener listener){ - this.thisListenerList.add(AnchorHitListener.class, listener); - return; - } - - /** - * AnchorHitListenerを削除する。 - * @param listener リスナー - */ - public void removeAnchorHitListener(AnchorHitListener listener){ - this.thisListenerList.remove(AnchorHitListener.class, listener); - return; - } - - /** - * AnchorHitListenerを列挙する。 - * @return すべてのAnchorHitListener - */ - public AnchorHitListener[] getAnchorHitListeners(){ - return this.thisListenerList.getListeners(AnchorHitListener.class); - } - - /** - * {@inheritDoc} - * @param {@inheritDoc} - * @param listenerType {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public T[] getListeners(Class listenerType){ - T[] result; - result = this.thisListenerList.getListeners(listenerType); - - if(result.length <= 0){ - result = super.getListeners(listenerType); - } - - return result; - } - - /** - * キーボード入力用ダミーAction。 - */ - private class ProxyAction extends AbstractAction{ - - private final String command; - - /** - * コンストラクタ。 - * @param command コマンド - * @throws NullPointerException 引数がnull - */ - public ProxyAction(String command) throws NullPointerException{ - super(); - if(command == null) throw new NullPointerException(); - this.command = command; - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - int id = event.getID(); - String actcmd = this.command; - long when = event.getWhen(); - int modifiers = event.getModifiers(); - - for(ActionListener listener : getActionListeners()){ - ActionEvent newEvent = new ActionEvent(source, - id, - actcmd, - when, - modifiers ); - listener.actionPerformed(newEvent); - } - - return; - } - }; - - /** - * ポップアップメニュー。 - */ - private class DiscussionPopup extends JPopupMenu{ - - private final JMenuItem menuCopy = - new JMenuItem("選択範囲をコピー"); - private final JMenuItem menuSelTalk = - new JMenuItem("この発言をコピー"); - private final JMenuItem menuJumpAnchor = - new JMenuItem("アンカーの示す先へジャンプ"); - private final JMenuItem menuWebTalk = - new JMenuItem("この発言をブラウザで表示..."); - private final JMenuItem menuSummary = - new JMenuItem("発言を集計..."); - - private TalkDraw lastPopupedTalkDraw; - private Anchor lastPopupedAnchor; - - /** - * コンストラクタ。 - */ - public DiscussionPopup(){ - super(); - - add(this.menuCopy); - add(this.menuSelTalk); - addSeparator(); - add(this.menuJumpAnchor); - add(this.menuWebTalk); - addSeparator(); - add(this.menuSummary); - - this.menuCopy - .setActionCommand(ActionManager.CMD_COPY); - this.menuSelTalk - .setActionCommand(ActionManager.CMD_COPYTALK); - this.menuJumpAnchor - .setActionCommand(ActionManager.CMD_JUMPANCHOR); - this.menuWebTalk - .setActionCommand(ActionManager.CMD_WEBTALK); - this.menuSummary - .setActionCommand(ActionManager.CMD_DAYSUMMARY); - - this.menuWebTalk.setIcon(GUIUtils.getWWWIcon()); - - return; - } - - /** - * {@inheritDoc} - * @param comp {@inheritDoc} - * @param x {@inheritDoc} - * @param y {@inheritDoc} - */ - @Override - public void show(Component comp, int x, int y){ - Point point = new Point(x, y); - - this.lastPopupedTalkDraw = getHittedTalkDraw(point); - if(this.lastPopupedTalkDraw != null){ - this.menuSelTalk.setEnabled(true); - this.menuWebTalk.setEnabled(true); - }else{ - this.menuSelTalk.setEnabled(false); - this.menuWebTalk.setEnabled(false); - } - - if(this.lastPopupedTalkDraw != null){ - this.lastPopupedAnchor = - this.lastPopupedTalkDraw.getAnchor(point); - }else{ - this.lastPopupedAnchor = null; - } - - if(this.lastPopupedAnchor != null){ - this.menuJumpAnchor.setEnabled(true); - }else{ - this.menuJumpAnchor.setEnabled(false); - } - - if(getSelected() != null){ - this.menuCopy.setEnabled(true); - }else{ - this.menuCopy.setEnabled(false); - } - - super.show(comp, x, y); - - return; - } - } - - // TODO シンプルモードの追加 - // Period変更を追跡するリスナ化 -} +/* + * discussion viewer + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.awt.font.FontRenderContext; +import java.io.IOException; +import java.util.EventListener; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.regex.Pattern; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.event.EventListenerList; +import javax.swing.event.MouseInputListener; +import javax.swing.text.DefaultEditorKit; + +/** + * 発言表示画面。 + * + * 表示に影響する要因は、Periodの中身、LayoutManagerによるサイズ変更、 + * フォント属性の指定、フィルタリング操作、ドラッギングによる文字列選択操作、 + * 文字列検索および検索ナビゲーション。 + */ +@SuppressWarnings("serial") +public class Discussion extends JComponent + implements Scrollable, MouseInputListener, ComponentListener{ + + private static final Color COLOR_NORMALBG = Color.BLACK; + private static final Color COLOR_SIMPLEBG = Color.WHITE; + + private static final int MARGINTOP = 50; + private static final int MARGINBOTTOM = 100; + + private Period period; + private final List rowList = new LinkedList(); + private final List talkDrawList = new LinkedList(); + + private TopicFilter topicFilter; + private TopicFilter.FilterContext filterContext; + private RegexPattern regexPattern; + + private Point dragFrom; + + private FontInfo fontInfo; + private final RenderingHints hints = new RenderingHints(null); + + private DialogPref dialogPref; + + private Dimension idealSize; + private int lastWidth = -1; + + private final DiscussionPopup popup = new DiscussionPopup(); + + private final EventListenerList thisListenerList = + new EventListenerList(); + + private final Action copySelectedAction = + new ProxyAction(ActionManager.CMD_COPY); + + /** + * 発言表示画面を作成する。 + */ + public Discussion(){ + super(); + + this.fontInfo = FontInfo.DEFAULT_FONTINFO; + this.dialogPref = new DialogPref(); + + this.hints.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + this.hints.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + updateRenderingHints(); + + setPeriod(null); + + addMouseListener(this); + addMouseMotionListener(this); + addComponentListener(this); + + setComponentPopupMenu(this.popup); + + updateInputMap(); + ActionMap actionMap = getActionMap(); + actionMap.put(DefaultEditorKit.copyAction, this.copySelectedAction); + + setColorDesign(); + + return; + } + + /** + * 描画設定の更新。 + * FontRenderContextが更新された後は必ず呼び出す必要がある。 + */ + private void updateRenderingHints(){ + Object textAliaseValue; + FontRenderContext context = this.fontInfo.getFontRenderContext(); + if(context.isAntiAliased()){ + textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_ON; + }else{ + textAliaseValue = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; + } + this.hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, + textAliaseValue); + + Object textFractionalValue; + if(context.usesFractionalMetrics()){ + textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_ON; + }else{ + textFractionalValue = RenderingHints.VALUE_FRACTIONALMETRICS_OFF; + } + this.hints.put(RenderingHints.KEY_FRACTIONALMETRICS, + textFractionalValue); + + return; + } + + /** + * 配色を設定する。 + */ + private void setColorDesign(){ + Color fgColor; + if(this.dialogPref.isSimpleMode()){ + fgColor = COLOR_SIMPLEBG; + }else{ + fgColor = COLOR_NORMALBG; + } + + setForeground(fgColor); + repaint(); + + return; + } + + /** + * フォント描画設定を変更する。 + * @param newFontInfo フォント設定 + */ + public void setFontInfo(FontInfo newFontInfo){ + this.fontInfo = newFontInfo; + + updateRenderingHints(); + + for(TextRow row : this.rowList){ + row.setFontInfo(this.fontInfo); + } + + setColorDesign(); + layoutRows(); + + revalidate(); + repaint(); + + return; + } + + /** + * 発言表示設定を変更する。 + * @param newPref 発言表示設定 + */ + public void setDialogPref(DialogPref newPref){ + this.dialogPref = newPref; + + for(TextRow row : this.rowList){ + if(row instanceof TalkDraw){ + TalkDraw talkDraw = (TalkDraw) row; + talkDraw.setDialogPref(this.dialogPref); + }else if(row instanceof SysEventDraw){ + SysEventDraw sysDraw = (SysEventDraw) row; + sysDraw.setDialogPref(this.dialogPref); + } + } + + setColorDesign(); + layoutRows(); + + revalidate(); + repaint(); + + return; + } + + /** + * 現在のPeriodを返す。 + * @return 現在のPeriod + */ + public Period getPeriod(){ + return this.period; + } + + /** + * Periodを更新する。 + * 新しいPeriodの表示内容はまだ反映されない。 + * @param period 新しいPeriod + */ + public final void setPeriod(Period period){ + if(period == null){ + this.period = null; + this.rowList.clear(); + this.talkDrawList.clear(); + return; + } + + if( this.period == period + && period.getTopics() == this.rowList.size() ){ + filterTopics(); + return; + } + + this.period = period; + + this.filterContext = null; + + this.rowList.clear(); + this.talkDrawList.clear(); + for(Topic topic : this.period.getTopicList()){ + TextRow row; + if(topic instanceof Talk){ + Talk talk = (Talk) topic; + TalkDraw talkDraw = new TalkDraw(talk, + this.dialogPref, + this.fontInfo ); + this.talkDrawList.add(talkDraw); + row = talkDraw; + }else if(topic instanceof SysEvent){ + SysEvent sysEvent = (SysEvent) topic; + row = new SysEventDraw(sysEvent, + this.dialogPref, + this.fontInfo ); + }else{ + assert false; + continue; + } + this.rowList.add(row); + } + + filterTopics(); + + clearSizeCache(); + + layoutRows(); + + return; + } + + /** + * 発言フィルタを設定する。 + * @param filter 発言フィルタ + */ + public void setTopicFilter(TopicFilter filter){ + this.topicFilter = filter; + filtering(); + return; + } + + /** + * 発言フィルタを適用する。 + */ + public void filtering(){ + if( this.topicFilter != null + && this.topicFilter.isSame(this.filterContext)){ + return; + } + + if(this.topicFilter != null){ + this.filterContext = this.topicFilter.getFilterContext(); + }else{ + this.filterContext = null; + } + + filterTopics(); + layoutVertical(); + + clearSelect(); + + return; + } + + /** + * 検索パターンを取得する。 + * @return 検索パターン + */ + public RegexPattern getRegexPattern(){ + return this.regexPattern; + } + + /** + * 与えられた正規表現にマッチする文字列をハイライト描画する。 + * @param newPattern 検索パターン + * @return ヒット件数 + */ + public int setRegexPattern(RegexPattern newPattern){ + this.regexPattern = newPattern; + + int total = 0; + + clearHotTarget(); + + Pattern pattern = null; + if(this.regexPattern != null){ + pattern = this.regexPattern.getPattern(); + } + + for(TalkDraw talkDraw : this.talkDrawList){ + total += talkDraw.setRegex(pattern); + } + + repaint(); + + return total; + } + + /** + * 検索結果の次候補をハイライト表示する。 + */ + public void nextHotTarget(){ + TalkDraw oldTalk = null; + int oldIndex = -1; + TalkDraw newTalk = null; + int newIndex = -1; + TalkDraw firstTalk = null; + + boolean findOld = true; + for(TalkDraw talkDraw : this.talkDrawList){ + int matches = talkDraw.getRegexMatches(); + if(firstTalk == null && matches > 0){ + firstTalk = talkDraw; + } + if(findOld){ + int index = talkDraw.getHotTargetIndex(); + if(index < 0) continue; + oldTalk = talkDraw; + oldIndex = index; + scrollRectWithMargin(talkDraw.getHotTargetRectangle()); + if(oldIndex < matches - 1 && ! isFiltered(talkDraw) ){ + newTalk = talkDraw; + newIndex = oldIndex + 1; + break; + } + findOld = false; + }else{ + if(isFiltered(talkDraw)) continue; + if(matches <= 0) continue; + newTalk = talkDraw; + newIndex = 0; + break; + } + } + + Rectangle showRect = null; + if(oldTalk == null && firstTalk != null){ + firstTalk.setHotTargetIndex(0); + showRect = firstTalk.getHotTargetRectangle(); + }else if( oldTalk != null + && newTalk != null){ + oldTalk.clearHotTarget(); + newTalk.setHotTargetIndex(newIndex); + showRect = newTalk.getHotTargetRectangle(); + } + + if(showRect != null){ + scrollRectWithMargin(showRect); + } + + repaint(); + + return; + } + + /** + * 検索結果の前候補をハイライト表示する。 + */ + public void prevHotTarget(){ + TalkDraw oldTalk = null; + int oldIndex = -1; + TalkDraw newTalk = null; + int newIndex = -1; + TalkDraw firstTalk = null; + + boolean findOld = true; + int size = this.talkDrawList.size(); + ListIterator iterator = + this.talkDrawList.listIterator(size); + while(iterator.hasPrevious()){ + TalkDraw talkDraw = iterator.previous(); + int matches = talkDraw.getRegexMatches(); + if(firstTalk == null && matches > 0){ + firstTalk = talkDraw; + } + if(findOld){ + int index = talkDraw.getHotTargetIndex(); + if(index < 0) continue; + oldTalk = talkDraw; + oldIndex = index; + scrollRectWithMargin(talkDraw.getHotTargetRectangle()); + if(oldIndex > 0 && ! isFiltered(talkDraw) ){ + newTalk = talkDraw; + newIndex = oldIndex - 1; + break; + } + findOld = false; + }else{ + if(isFiltered(talkDraw)) continue; + if(matches <= 0) continue; + newTalk = talkDraw; + newIndex = matches - 1; + break; + } + } + + Rectangle showRect = null; + if(oldTalk == null && firstTalk != null){ + int matches = firstTalk.getRegexMatches(); + firstTalk.setHotTargetIndex(matches - 1); + showRect = firstTalk.getHotTargetRectangle(); + }else if( oldTalk != null + && newTalk != null){ + oldTalk.clearHotTarget(); + newTalk.setHotTargetIndex(newIndex); + showRect = newTalk.getHotTargetRectangle(); + } + + if(showRect != null){ + scrollRectWithMargin(showRect); + } + + repaint(); + + return; + } + + /** + * 検索結果の特殊ハイライト表示を解除。 + */ + public void clearHotTarget(){ + for(TalkDraw talkDraw : this.talkDrawList){ + talkDraw.clearHotTarget(); + } + repaint(); + return; + } + + /** + * 指定した領域に若干の上下マージンを付けて + * スクロールウィンドウに表示させる。 + * @param rectangle 指定領域 + */ + private void scrollRectWithMargin(Rectangle rectangle){ + Rectangle show = new Rectangle(rectangle); + show.y -= MARGINTOP; + show.height += MARGINTOP + MARGINBOTTOM; + + scrollRectToVisible(show); + + return; + } + + /** + * 過去に計算した寸法を破棄する。 + */ + private void clearSizeCache(){ + this.idealSize = null; + this.lastWidth = -1; + revalidate(); + return; + } + + /** + * 指定した矩形がフィルタリング対象か判定する。 + * @param row 矩形 + * @return フィルタリング対象ならtrue + */ + private boolean isFiltered(TextRow row){ + if(this.topicFilter == null) return false; + + Topic topic; + if(row instanceof TalkDraw){ + topic = ((TalkDraw)row).getTalk(); + }else if(row instanceof SysEventDraw){ + topic = ((SysEventDraw)row).getSysEvent(); + }else{ + return false; + } + + return this.topicFilter.isFiltered(topic); + } + + /** + * フィルタリング指定に従いTextRowを表示するか否か設定する。 + */ + private void filterTopics(){ + for(TextRow row : this.rowList){ + if(isFiltered(row)) row.setVisible(false); + else row.setVisible(true); + } + return; + } + + /** + * 幅を設定する。 + * 全子TextRowがリサイズされる。 + * @param width コンポーネント幅 + */ + private void setWidth(int width){ + this.lastWidth = width; + Insets insets = getInsets(); + int rowWidth = width - (insets.left + insets.right); + for(TextRow row : this.rowList){ + row.setWidth(rowWidth); + } + + layoutVertical(); + + return; + } + + /** + * 子TextRowの縦位置レイアウトを行う。 + * フィルタリングが反映される。 + * TextRowは必要に応じて移動させられるがリサイズされることはない。 + */ + private void layoutVertical(){ + Rectangle unionRect = null; + Insets insets = getInsets(); + int vertPos = insets.top; + + for(TextRow row : this.rowList){ + if( ! row.isVisible() ) continue; + + row.setPos(insets.left, vertPos); + Rectangle rowBound = row.getBounds(); + vertPos += rowBound.height; + + if(unionRect == null){ + unionRect = new Rectangle(rowBound); + }else{ + unionRect.add(rowBound); + } + } + + if(unionRect == null){ + unionRect = new Rectangle(insets.left, insets.top, 0, 0); + } + + if(this.idealSize == null){ + this.idealSize = new Dimension(); + } + + int newWidth = insets.left + unionRect.width + insets.right; + int newHeight = insets.top + unionRect.height + insets.bottom; + + this.idealSize.setSize(newWidth, newHeight); + + setPreferredSize(this.idealSize); + + revalidate(); + repaint(); + + return; + } + + /** + * Rowsの縦位置を再レイアウトする。 + */ + public void layoutRows(){ + int width = getWidth(); + setWidth(width); + return; + } + + /** + * {@inheritDoc} + * @param g {@inheritDoc} + */ + @Override + public void paintComponent(Graphics g){ + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHints(this.hints); + + Rectangle clipRect = g2.getClipBounds(); + g2.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); + + for(TextRow row : this.rowList){ + if( ! row.isVisible() ) continue; + + Rectangle rowRect = row.getBounds(); + if( ! rowRect.intersects(clipRect) ) continue; + + row.paint(g2); + } + + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Dimension getPreferredScrollableViewportSize(){ + return getPreferredSize(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean getScrollableTracksViewportWidth(){ + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean getScrollableTracksViewportHeight(){ + return false; + } + + /** + * {@inheritDoc} + * @param visibleRect {@inheritDoc} + * @param orientation {@inheritDoc} + * @param direction {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, + int direction ){ + if(orientation == SwingConstants.VERTICAL){ + return visibleRect.height; + } + return 30; // TODO フォント高 × 1.5 ぐらい? + } + + /** + * {@inheritDoc} + * @param visibleRect {@inheritDoc} + * @param orientation {@inheritDoc} + * @param direction {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction ){ + return 30; + } + + /** + * 任意の発言の表示が占める画面領域を返す。 + * 発言がフィルタリング対象の時はnullを返す。 + * @param talk 発言 + * @return 領域 + */ + public Rectangle getTalkBounds(Talk talk){ + if( this.topicFilter != null + && this.topicFilter.isFiltered(talk)) return null; + + for(TalkDraw talkDraw : this.talkDrawList){ + if(talkDraw.getTalk() == talk){ + Rectangle rect = talkDraw.getBounds(); + return rect; + } + } + + return null; + } + + /** + * ドラッグ処理を行う。 + * @param from ドラッグ開始位置 + * @param to 現在のドラッグ位置 + */ + private void drag(Point from, Point to){ + Rectangle dragRegion = new Rectangle(); + dragRegion.setFrameFromDiagonal(from, to); + + for(TextRow row : this.rowList){ + if(isFiltered(row)) continue; + if( ! row.getBounds().intersects(dragRegion) ) continue; + row.drag(from, to); + } + repaint(); + return; + } + + /** + * 選択範囲の解除。 + */ + private void clearSelect(){ + for(TextRow row : this.rowList){ + row.clearSelect(); + } + repaint(); + return; + } + + /** + * 与えられた点座標を包含する発言を返す。 + * @param pt 点座標(JComponent基準) + * @return 点座標を含む発言。含む発言がなければnullを返す。 + */ + // TODO 二分探索とかしたい。 + private TalkDraw getHittedTalkDraw(Point pt){ + for(TalkDraw talkDraw : this.talkDrawList){ + if(isFiltered(talkDraw)) continue; + Rectangle bounds = talkDraw.getBounds(); + if(bounds.contains(pt)) return talkDraw; + } + return null; + } + + /** + * アンカークリック動作の処理。 + * @param pt クリックポイント + */ + private void hitAnchor(Point pt){ + TalkDraw talkDraw = getHittedTalkDraw(pt); + if(talkDraw == null) return; + + Anchor anchor = talkDraw.getAnchor(pt); + if(anchor == null) return; + + for(AnchorHitListener listener : getAnchorHitListeners()){ + AnchorHitEvent event = + new AnchorHitEvent(this, talkDraw, anchor, pt); + listener.anchorHitted(event); + } + + return; + } + + /** + * 検索マッチ文字列クリック動作の処理。 + * @param pt クリックポイント + */ + private void hitRegex(Point pt){ + TalkDraw talkDraw = getHittedTalkDraw(pt); + if(talkDraw == null) return; + + int index = talkDraw.getRegexMatchIndex(pt); + if(index < 0) return; + + clearHotTarget(); + talkDraw.setHotTargetIndex(index); + + return; + } + + /** + * {@inheritDoc} + * アンカーヒット処理を行う。 + * MouseInputListenerを参照せよ。 + * @param event {@inheritDoc} + */ + // TODO 距離判定がシビアすぎ + @Override + public void mouseClicked(MouseEvent event){ + Point pt = event.getPoint(); + if(event.getButton() == MouseEvent.BUTTON1){ + clearSelect(); + hitAnchor(pt); + hitRegex(pt); + } + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void mouseEntered(MouseEvent event){ + // TODO ここでキーボードフォーカス処理が必要? + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void mouseExited(MouseEvent event){ + return; + } + + /** + * {@inheritDoc} + * ドラッグ開始処理を行う。 + * @param event {@inheritDoc} + */ + @Override + public void mousePressed(MouseEvent event){ + requestFocusInWindow(); + + if(event.getButton() == MouseEvent.BUTTON1){ + clearSelect(); + this.dragFrom = event.getPoint(); + } + + return; + } + + /** + * {@inheritDoc} + * ドラッグ終了処理を行う。 + * @param event {@inheritDoc} + */ + @Override + public void mouseReleased(MouseEvent event){ + if(event.getButton() == MouseEvent.BUTTON1){ + this.dragFrom = null; + } + return; + } + + /** + * {@inheritDoc} + * ドラッグ処理を行う。 + * @param event {@inheritDoc} + */ + // TODO ドラッグ範囲がビューポートを超えたら自動的にスクロールしてほしい。 + @Override + public void mouseDragged(MouseEvent event){ + if(this.dragFrom == null) return; + Point dragTo = event.getPoint(); + drag(this.dragFrom, dragTo); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void mouseMoved(MouseEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void componentShown(ComponentEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void componentHidden(ComponentEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void componentMoved(ComponentEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void componentResized(ComponentEvent event){ + int width = getWidth(); + int height = getHeight(); + if(width != this.lastWidth){ + setWidth(width); + } + if( this.idealSize.width != width + || this.idealSize.height != height ){ + revalidate(); + } + return; + } + + /** + * 選択文字列を返す。 + * @return 選択文字列 + */ + public CharSequence getSelected(){ + StringBuilder selected = new StringBuilder(); + + for(TextRow row : this.rowList){ + if(isFiltered(row)) continue; + try{ + row.appendSelected(selected); + }catch(IOException e){ + assert false; // ありえない + return null; + } + } + + if(selected.length() <= 0) return null; + + return selected; + } + + /** + * 選択文字列をクリップボードにコピーする。 + * @return 選択文字列 + */ + public CharSequence copySelected(){ + CharSequence selected = getSelected(); + if(selected == null) return null; + ClipboardAction.copyToClipboard(selected); + return selected; + } + + /** + * 矩形の示す一発言をクリップボードにコピーする。 + * @return コピーした文字列 + */ + public CharSequence copyTalk(){ + TalkDraw talkDraw = this.popup.lastPopupedTalkDraw; + if(talkDraw == null) return null; + Talk talk = talkDraw.getTalk(); + + StringBuilder selected = new StringBuilder(); + + Avatar avatar = talk.getAvatar(); + selected.append(avatar.getName()).append(' '); + + String anchor = talk.getAnchorNotation(); + selected.append(anchor); + if(talk.hasTalkNo()){ + selected.append(' ').append(talk.getAnchorNotation_G()); + } + selected.append('\n'); + + selected.append(talk.getDialog()); + if(selected.charAt(selected.length() - 1) != '\n'){ + selected.append('\n'); + } + + ClipboardAction.copyToClipboard(selected); + + return selected; + } + + /** + * ポップアップメニュートリガ座標に発言があればそれを返す。 + * @return 発言 + */ + public Talk getPopupedTalk(){ + TalkDraw talkDraw = this.popup.lastPopupedTalkDraw; + if(talkDraw == null) return null; + Talk talk = talkDraw.getTalk(); + return talk; + } + + /** + * ポップアップメニュートリガ座標にアンカーがあればそれを返す。 + * @return アンカー + */ + public Anchor getPopupedAnchor(){ + return this.popup.lastPopupedAnchor; + } + + /** + * {@inheritDoc} + */ + @Override + public void updateUI(){ + super.updateUI(); + this.popup.updateUI(); + + updateInputMap(); + + return; + } + + /** + * COPY処理を行うキーの設定をJTextFieldから流用する。 + * おそらくはCtrl-C。MacならCommand-Cかも。 + */ + private void updateInputMap(){ + InputMap thisInputMap = getInputMap(); + + InputMap sampleInputMap; + sampleInputMap = new JTextField().getInputMap(); + KeyStroke[] strokes = sampleInputMap.allKeys(); + for(KeyStroke stroke : strokes){ + Object bind = sampleInputMap.get(stroke); + if(bind.equals(DefaultEditorKit.copyAction)){ + thisInputMap.put(stroke, DefaultEditorKit.copyAction); + } + } + + return; + } + + /** + * ActionListenerを追加する。 + * @param listener リスナー + */ + public void addActionListener(ActionListener listener){ + this.thisListenerList.add(ActionListener.class, listener); + + this.popup.menuCopy .addActionListener(listener); + this.popup.menuSelTalk .addActionListener(listener); + this.popup.menuJumpAnchor .addActionListener(listener); + this.popup.menuWebTalk .addActionListener(listener); + this.popup.menuSummary .addActionListener(listener); + + return; + } + + /** + * ActionListenerを削除する。 + * @param listener リスナー + */ + public void removeActionListener(ActionListener listener){ + this.thisListenerList.remove(ActionListener.class, listener); + + this.popup.menuCopy .removeActionListener(listener); + this.popup.menuSelTalk .removeActionListener(listener); + this.popup.menuJumpAnchor .removeActionListener(listener); + this.popup.menuWebTalk .removeActionListener(listener); + this.popup.menuSummary .removeActionListener(listener); + + return; + } + + /** + * ActionListenerを列挙する。 + * @return すべてのActionListener + */ + public ActionListener[] getActionListeners(){ + return this.thisListenerList.getListeners(ActionListener.class); + } + + /** + * AnchorHitListenerを追加する。 + * @param listener リスナー + */ + public void addAnchorHitListener(AnchorHitListener listener){ + this.thisListenerList.add(AnchorHitListener.class, listener); + return; + } + + /** + * AnchorHitListenerを削除する。 + * @param listener リスナー + */ + public void removeAnchorHitListener(AnchorHitListener listener){ + this.thisListenerList.remove(AnchorHitListener.class, listener); + return; + } + + /** + * AnchorHitListenerを列挙する。 + * @return すべてのAnchorHitListener + */ + public AnchorHitListener[] getAnchorHitListeners(){ + return this.thisListenerList.getListeners(AnchorHitListener.class); + } + + /** + * {@inheritDoc} + * @param {@inheritDoc} + * @param listenerType {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public T[] getListeners(Class listenerType){ + T[] result; + result = this.thisListenerList.getListeners(listenerType); + + if(result.length <= 0){ + result = super.getListeners(listenerType); + } + + return result; + } + + /** + * キーボード入力用ダミーAction。 + */ + private class ProxyAction extends AbstractAction{ + + private final String command; + + /** + * コンストラクタ。 + * @param command コマンド + * @throws NullPointerException 引数がnull + */ + public ProxyAction(String command) throws NullPointerException{ + super(); + if(command == null) throw new NullPointerException(); + this.command = command; + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + int id = event.getID(); + String actcmd = this.command; + long when = event.getWhen(); + int modifiers = event.getModifiers(); + + for(ActionListener listener : getActionListeners()){ + ActionEvent newEvent = new ActionEvent(source, + id, + actcmd, + when, + modifiers ); + listener.actionPerformed(newEvent); + } + + return; + } + }; + + /** + * ポップアップメニュー。 + */ + private class DiscussionPopup extends JPopupMenu{ + + private final JMenuItem menuCopy = + new JMenuItem("選択範囲をコピー"); + private final JMenuItem menuSelTalk = + new JMenuItem("この発言をコピー"); + private final JMenuItem menuJumpAnchor = + new JMenuItem("アンカーの示す先へジャンプ"); + private final JMenuItem menuWebTalk = + new JMenuItem("この発言をブラウザで表示..."); + private final JMenuItem menuSummary = + new JMenuItem("発言を集計..."); + + private TalkDraw lastPopupedTalkDraw; + private Anchor lastPopupedAnchor; + + /** + * コンストラクタ。 + */ + public DiscussionPopup(){ + super(); + + add(this.menuCopy); + add(this.menuSelTalk); + addSeparator(); + add(this.menuJumpAnchor); + add(this.menuWebTalk); + addSeparator(); + add(this.menuSummary); + + this.menuCopy + .setActionCommand(ActionManager.CMD_COPY); + this.menuSelTalk + .setActionCommand(ActionManager.CMD_COPYTALK); + this.menuJumpAnchor + .setActionCommand(ActionManager.CMD_JUMPANCHOR); + this.menuWebTalk + .setActionCommand(ActionManager.CMD_WEBTALK); + this.menuSummary + .setActionCommand(ActionManager.CMD_DAYSUMMARY); + + this.menuWebTalk.setIcon(GUIUtils.getWWWIcon()); + + return; + } + + /** + * {@inheritDoc} + * @param comp {@inheritDoc} + * @param x {@inheritDoc} + * @param y {@inheritDoc} + */ + @Override + public void show(Component comp, int x, int y){ + Point point = new Point(x, y); + + this.lastPopupedTalkDraw = getHittedTalkDraw(point); + if(this.lastPopupedTalkDraw != null){ + this.menuSelTalk.setEnabled(true); + this.menuWebTalk.setEnabled(true); + }else{ + this.menuSelTalk.setEnabled(false); + this.menuWebTalk.setEnabled(false); + } + + if(this.lastPopupedTalkDraw != null){ + this.lastPopupedAnchor = + this.lastPopupedTalkDraw.getAnchor(point); + }else{ + this.lastPopupedAnchor = null; + } + + if(this.lastPopupedAnchor != null){ + this.menuJumpAnchor.setEnabled(true); + }else{ + this.menuJumpAnchor.setEnabled(false); + } + + if(getSelected() != null){ + this.menuCopy.setEnabled(true); + }else{ + this.menuCopy.setEnabled(false); + } + + super.show(comp, x, y); + + return; + } + } + + // TODO シンプルモードの追加 + // Period変更を追跡するリスナ化 +} diff --git a/src/main/java/jp/sourceforge/jindolf/EditArray.java b/src/main/java/jp/sourceforge/jindolf/EditArray.java index 386c2c9..1fc6cd3 100644 --- a/src/main/java/jp/sourceforge/jindolf/EditArray.java +++ b/src/main/java/jp/sourceforge/jindolf/EditArray.java @@ -1,740 +1,740 @@ -/* - * エディタ集合の操作 - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.LayoutManager; -import java.awt.Rectangle; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JPanel; -import javax.swing.Scrollable; -import javax.swing.SwingConstants; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.JTextComponent; -import javax.swing.text.NavigationFilter; -import javax.swing.text.Position.Bias; - -/** - * エディタ集合の操作。 - * ※ このクラスはすべてシングルスレッドモデルで作られている。 - */ -@SuppressWarnings("serial") -public class EditArray extends JPanel - implements Scrollable, - FocusListener { - - private static final int MAX_EDITORS = 50; - - private final List editorList = new ArrayList(); - private boolean onAdjusting = false; - - private final NavigationFilter keyNavigator = new CustomNavigation(); - private final DocumentListener documentListener = new DocWatcher(); - - private TalkEditor activeEditor; - - private Font textFont; - - /** - * コンストラクタ。 - */ - public EditArray(){ - super(); - - setOpaque(false); - - LayoutManager layout = new GridBagLayout(); - setLayout(layout); - - TalkEditor firstEditor = incrementTalkEditor(); - setActiveEditor(firstEditor); - - return; - } - - /** - * 個別エディタの生成を行う。 - * @return エディタ - */ - private TalkEditor createTalkEditor(){ - TalkEditor editor = new TalkEditor(); - editor.setNavigationFilter(this.keyNavigator); - editor.addTextFocusListener(this); - Document document = editor.getDocument(); - document.addDocumentListener(this.documentListener); - - if(this.textFont == null){ - this.textFont = editor.getTextFont(); - }else{ - editor.setTextFont(this.textFont); - } - - return editor; - } - - /** - * エディタ集合を一つ増やす。 - * @return 増えたエディタ - */ - private TalkEditor incrementTalkEditor(){ - TalkEditor editor = createTalkEditor(); - - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.gridx = 0; - constraints.gridy = GridBagConstraints.RELATIVE; - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.gridheight = 1; - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.NORTHEAST; - - add(editor, constraints); - - this.editorList.add(editor); - - int sequenceNumber = this.editorList.size(); - editor.setSequenceNumber(sequenceNumber); - - return editor; - } - - /** - * 1から始まる通し番号指定でエディタを取得する。 - * 存在しない通し番号が指定された場合は新たにエディタが追加される。 - * @param sequenceNumber 通し番号 - * @return エディタ - */ - private TalkEditor getTalkEditor(int sequenceNumber){ - while(this.editorList.size() < sequenceNumber){ - incrementTalkEditor(); - } - - TalkEditor result = this.editorList.get(sequenceNumber - 1); - - return result; - } - - /** - * 指定したエディタの次の通し番号を持つエディタを返す。 - * エディタがなければ追加される。 - * @param editor エディタ - * @return 次のエディタ - */ - private TalkEditor nextEditor(TalkEditor editor){ - int sequenceNumber = editor.getSequenceNumber(); - TalkEditor nextEditor = getTalkEditor(sequenceNumber + 1); - return nextEditor; - } - - /** - * 指定したエディタの前の通し番号を持つエディタを返す。 - * @param editor エディタ - * @return 前のエディタ。 - * 最初のエディタ(通し番号1)が指定されればnullを返す。 - */ - private TalkEditor prevEditor(TalkEditor editor){ - int sequenceNumber = editor.getSequenceNumber(); - if(sequenceNumber <= 1) return null; - TalkEditor prevEditor = getTalkEditor(sequenceNumber - 1); - return prevEditor; - } - - /** - * 指定したエディタがエディタ集合の最後のエディタか判定する。 - * @param editor エディタ - * @return 最後のエディタならtrue - */ - private boolean isLastEditor(TalkEditor editor){ - int seqNo = editor.getSequenceNumber(); - int size = this.editorList.size(); - if(seqNo >= size) return true; - return false; - } - - /** - * Documentからその持ち主であるエディタを取得する。 - * @param document Documentインスタンス - * @return 持ち主のエディタ。見つからなければnull。 - */ - private TalkEditor getEditorFromDocument(Document document){ - for(TalkEditor editor : this.editorList){ - if(editor.getDocument() == document) return editor; - } - return null; - } - - /** - * エディタ集合から任意のエディタを除く。 - * ただし最初のエディタは消去不可。 - * @param editor エディタ - */ - private void removeEditor(TalkEditor editor){ - if(editor.getParent() != this) return; - - int seqNo = editor.getSequenceNumber(); - if(seqNo <= 1) return; - TalkEditor prevEditor = prevEditor(editor); - if(editor.isActive()){ - setActiveEditor(prevEditor); - } - if(editor.hasEditorFocus()){ - prevEditor.requestEditorFocus(); - } - - this.editorList.remove(seqNo - 1); - - editor.setNavigationFilter(null); - editor.removeTextFocusListener(this); - Document document = editor.getDocument(); - document.removeDocumentListener(this.documentListener); - editor.clearText(); - - remove(editor); - revalidate(); - - int renumber = 1; - for(TalkEditor newEditor : this.editorList){ - newEditor.setSequenceNumber(renumber++); - } - - return; - } - - /** - * エディタ間文字調整タスクをディスパッチスレッドとして事後投入する。 - * エディタ間文字調整タスクが実行中であれば何もしない。 - * きっかけとなったエディタ上でIME操作が確定していなければ何もしない。 - * @param triggerEvent ドキュメント変更イベント - */ - private void detachAdjustTask(DocumentEvent triggerEvent){ - if(this.onAdjusting) return; - - Document document = triggerEvent.getDocument(); - final TalkEditor triggerEditor = getEditorFromDocument(document); - if(triggerEditor.onIMEoperation()) return; - - this.onAdjusting = true; - - EventQueue.invokeLater(new Runnable(){ - public void run(){ - try{ - adjustTask(triggerEditor); - }finally{ - EditArray.this.onAdjusting = false; - } - return; - } - }); - - return; - } - - /** - * エディタ間文字調整タスク本体。 - * @param triggerEditor タスク実行のきっかけとなったエディタ - */ - private void adjustTask(TalkEditor triggerEditor){ - int initCaretPos = triggerEditor.getCaretPosition(); - - TalkEditor newFocus = null; - int newCaretPos = -1; - - TalkEditor current = triggerEditor; - for(;;){ - TalkEditor next; - - if( ! isLastEditor(current) ){ - next = nextEditor(current); - String nextContents = next.getText(); - int nextLength = nextContents.length(); - - current.appendTail(nextContents); - String rest = current.chopRest(); - int restLength; - if(rest == null) restLength = 0; - else restLength = rest.length(); - - int chopLength = nextLength - restLength; - if(chopLength > 0){ - next.chopHead(chopLength); - }else if(chopLength < 0){ - rest = rest.substring(0, -chopLength); - next.appendHead(rest); - }else{ - if(newFocus == null){ - newFocus = current; - newCaretPos = initCaretPos; - } - break; - } - }else{ - String rest = current.chopRest(); - if(rest == null || this.editorList.size() >= MAX_EDITORS){ - if(newFocus == null){ - newFocus = current; - if(current.getTextLength() >= initCaretPos){ - newCaretPos = initCaretPos; - }else{ - newCaretPos = current.getTextLength(); - } - } - break; - } - next = nextEditor(current); - next.appendHead(rest); - } - - if(newFocus == null){ - int currentLength = current.getTextLength(); - if(initCaretPos >= currentLength){ - initCaretPos -= currentLength; - }else{ - newFocus = current; - newCaretPos = initCaretPos; - } - } - - current = next; - } - - if(newFocus != null){ - newFocus.requestEditorFocus(); - newFocus.setCaretPosition(newCaretPos); - } - - adjustEditorsTail(); - - return; - } - - /** - * エディタ集合末尾の空エディタを切り詰める。 - * ただし最初のエディタ(通し番号1)は削除されない。 - * フォーカスを持つエディタが削除された場合は、 - * 削除されなかった最後のエディタにフォーカスが移る。 - */ - private void adjustEditorsTail(){ - int editorNum = this.editorList.size(); - if(editorNum <= 0) return; - TalkEditor lastEditor = this.editorList.get(editorNum - 1); - - TalkEditor prevlostEditor = null; - - boolean lostFocusedEditor = false; - - for(;;){ - int textLength = lastEditor.getTextLength(); - int seqNo = lastEditor.getSequenceNumber(); - - if(lostFocusedEditor){ - prevlostEditor = lastEditor; - } - - if(textLength > 0) break; - if(seqNo <= 1) break; - - if(lastEditor.hasEditorFocus()) lostFocusedEditor = true; - removeEditor(lastEditor); - - lastEditor = prevEditor(lastEditor); // TODO ちょっと変 - } - - if(prevlostEditor != null){ - int textLength = prevlostEditor.getTextLength(); - prevlostEditor.requestEditorFocus(); - prevlostEditor.setCaretPosition(textLength); - } - - return; - } - - /** - * フォーカスを持つエディタを取得する。 - * @return エディタ - */ - public TalkEditor getFocusedTalkEditor(){ - for(TalkEditor editor : this.editorList){ - if(editor.hasEditorFocus()) return editor; - } - return null; - } - - /** - * フォーカスを持つエディタの次エディタがあればフォーカスを移し、 - * カレット位置を0にする。 - */ - // TODO エディタのスクロール位置調整が必要。 - public void forwardEditor(){ - TalkEditor editor = getFocusedTalkEditor(); - if(isLastEditor(editor)) return; - TalkEditor next = nextEditor(editor); - next.setCaretPosition(0); - next.requestEditorFocus(); - return; - } - - /** - * フォーカスを持つエディタの前エディタがあればフォーカスを移し、 - * カレット位置を末尾に置く。 - */ - public void backwardEditor(){ - TalkEditor editor = getFocusedTalkEditor(); - TalkEditor prev = prevEditor(editor); - if(prev == null) return; - int length = prev.getTextLength(); - prev.setCaretPosition(length); - prev.requestEditorFocus(); - return; - } - - /** - * 任意のエディタをアクティブにする。 - * 同時にアクティブなエディタは一つのみ。 - * @param editor アクティブにするエディタ - */ - private void setActiveEditor(TalkEditor editor){ - if(this.activeEditor != null){ - this.activeEditor.setActive(false); - } - - this.activeEditor = editor; - - if(this.activeEditor != null){ - this.activeEditor.setActive(true); - } - - fireChangeActive(); - - return; - } - - /** - * アクティブなエディタを返す。 - * @return アクティブなエディタ。 - */ - public TalkEditor getActiveEditor(){ - return this.activeEditor; - } - - /** - * 全発言を連結した文字列を返す。 - * @return 連結文字列 - */ - public CharSequence getAllText(){ - StringBuilder result = new StringBuilder(); - - for(TalkEditor editor : this.editorList){ - String text = editor.getText(); - result.append(text); - } - - return result; - } - - /** - * 先頭エディタの0文字目から字を詰め込む。 - * 2番目移行のエディタへはみ出すかもしれない。 - * @param seq 詰め込む文字列 - */ - public void setAllText(CharSequence seq){ - TalkEditor firstEditor = getTalkEditor(1); - Document doc = firstEditor.getDocument(); - try{ - doc.insertString(0, seq.toString(), null); - }catch(BadLocationException e){ - assert false; - } - return; - } - - /** - * 全エディタをクリアする。 - */ - public void clearAllEditor(){ - int editorNum = this.editorList.size(); - if(editorNum <= 0) return; - - TalkEditor lastEditor = this.editorList.get(editorNum - 1); - for(;;){ - removeEditor(lastEditor); - lastEditor = prevEditor(lastEditor); - if(lastEditor == null) break; - } - - TalkEditor firstEditor = getTalkEditor(1); - firstEditor.clearText(); - setActiveEditor(firstEditor); - - return; - } - - /** - * テキスト編集用フォントを指定する。 - * @param textFont フォント - */ - public void setTextFont(Font textFont){ - this.textFont = textFont; - for(TalkEditor editor : this.editorList){ - editor.setTextFont(this.textFont); - editor.repaint(); - } - revalidate(); - return; - } - - /** - * テキスト編集用フォントを取得する。 - * @return フォント - */ - public Font getTextFont(){ - return this.textFont; - } - - /** - * アクティブエディタ変更通知用リスナの登録。 - * @param listener リスナ - */ - public void addChangeListener(ChangeListener listener){ - this.listenerList.add(ChangeListener.class, listener); - return; - } - - /** - * アクティブエディタ変更通知用リスナの削除。 - * @param listener リスナ - */ - public void removeChangeListener(ChangeListener listener){ - this.listenerList.remove(ChangeListener.class, listener); - return; - } - - /** - * アクティブエディタ変更通知を行う。 - */ - private void fireChangeActive(){ - ChangeEvent event = new ChangeEvent(this); - - ChangeListener[] listeners = - this.listenerList.getListeners(ChangeListener.class); - for(ChangeListener listener : listeners){ - listener.stateChanged(event); - } - - return; - } - - /** - * {@inheritDoc} - * エディタのフォーカス取得とともにアクティブ状態にする。 - * @param event {@inheritDoc} - */ - @Override - public void focusGained(FocusEvent event){ - Object source = event.getSource(); - if( ! (source instanceof JTextComponent) ) return; - JTextComponent textComp = (JTextComponent) source; - - Document document = textComp.getDocument(); - TalkEditor editor = getEditorFromDocument(document); - - setActiveEditor(editor); - - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void focusLost(FocusEvent event){ - // NOTHING - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Dimension getPreferredScrollableViewportSize(){ - Dimension result = getPreferredSize(); - return result; - } - - /** - * {@inheritDoc} - * 横スクロールバーを極力出さないようレイアウトでがんばる。 - * @return {@inheritDoc} - */ - @Override - public boolean getScrollableTracksViewportWidth(){ - return true; - } - - /** - * {@inheritDoc} - * 縦スクロールバーを出しても良いのでレイアウトでがんばらない。 - * @return {@inheritDoc} - */ - @Override - public boolean getScrollableTracksViewportHeight(){ - return false; - } - - /** - * {@inheritDoc} - * @param visibleRect {@inheritDoc} - * @param orientation {@inheritDoc} - * @param direction {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getScrollableBlockIncrement(Rectangle visibleRect, - int orientation, - int direction ){ - if(orientation == SwingConstants.VERTICAL){ - return visibleRect.height; - } - return 10; - } - - /** - * {@inheritDoc} - * @param visibleRect {@inheritDoc} - * @param orientation {@inheritDoc} - * @param direction {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getScrollableUnitIncrement(Rectangle visibleRect, - int orientation, - int direction ){ - return 30; // TODO フォント高の1.5倍くらい? - } - - /** - * エディタ内のカーソル移動を監視するための、 - * カスタム化したナビゲーションフィルター。 - * 必要に応じてエディタ間カーソル移動を行う。 - */ - private class CustomNavigation extends NavigationFilter{ - - /** - * コンストラクタ。 - */ - public CustomNavigation(){ - super(); - return; - } - - /** - * {@inheritDoc} - * カーソル移動が行き詰まった場合、 - * 隣接するエディタ間でカーソル移動を行う。 - * @param text {@inheritDoc} - * @param pos {@inheritDoc} - * @param bias {@inheritDoc} - * @param direction {@inheritDoc} - * @param biasRet {@inheritDoc} - * @return {@inheritDoc} - * @throws javax.swing.text.BadLocationException {@inheritDoc} - */ - @Override - public int getNextVisualPositionFrom(JTextComponent text, - int pos, - Bias bias, - int direction, - Bias[] biasRet ) - throws BadLocationException { - int result = super.getNextVisualPositionFrom(text, - pos, - bias, - direction, - biasRet ); - if(result != pos) return result; - - switch(direction){ - case SwingConstants.WEST: - case SwingConstants.NORTH: - backwardEditor(); - break; - case SwingConstants.EAST: - case SwingConstants.SOUTH: - forwardEditor(); - break; - default: - assert false; - } - - return result; - } - } - - /** - * エディタの内容変更を監視し、随時エディタ間調整を行う。 - */ - private class DocWatcher implements DocumentListener{ - - /** - * コンストラクタ。 - */ - public DocWatcher(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void changedUpdate(DocumentEvent event){ - detachAdjustTask(event); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void insertUpdate(DocumentEvent event){ - detachAdjustTask(event); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void removeUpdate(DocumentEvent event){ - detachAdjustTask(event); - return; - } - } - -} +/* + * エディタ集合の操作 + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JPanel; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.text.NavigationFilter; +import javax.swing.text.Position.Bias; + +/** + * エディタ集合の操作。 + * ※ このクラスはすべてシングルスレッドモデルで作られている。 + */ +@SuppressWarnings("serial") +public class EditArray extends JPanel + implements Scrollable, + FocusListener { + + private static final int MAX_EDITORS = 50; + + private final List editorList = new ArrayList(); + private boolean onAdjusting = false; + + private final NavigationFilter keyNavigator = new CustomNavigation(); + private final DocumentListener documentListener = new DocWatcher(); + + private TalkEditor activeEditor; + + private Font textFont; + + /** + * コンストラクタ。 + */ + public EditArray(){ + super(); + + setOpaque(false); + + LayoutManager layout = new GridBagLayout(); + setLayout(layout); + + TalkEditor firstEditor = incrementTalkEditor(); + setActiveEditor(firstEditor); + + return; + } + + /** + * 個別エディタの生成を行う。 + * @return エディタ + */ + private TalkEditor createTalkEditor(){ + TalkEditor editor = new TalkEditor(); + editor.setNavigationFilter(this.keyNavigator); + editor.addTextFocusListener(this); + Document document = editor.getDocument(); + document.addDocumentListener(this.documentListener); + + if(this.textFont == null){ + this.textFont = editor.getTextFont(); + }else{ + editor.setTextFont(this.textFont); + } + + return editor; + } + + /** + * エディタ集合を一つ増やす。 + * @return 増えたエディタ + */ + private TalkEditor incrementTalkEditor(){ + TalkEditor editor = createTalkEditor(); + + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.gridx = 0; + constraints.gridy = GridBagConstraints.RELATIVE; + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.gridheight = 1; + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.NORTHEAST; + + add(editor, constraints); + + this.editorList.add(editor); + + int sequenceNumber = this.editorList.size(); + editor.setSequenceNumber(sequenceNumber); + + return editor; + } + + /** + * 1から始まる通し番号指定でエディタを取得する。 + * 存在しない通し番号が指定された場合は新たにエディタが追加される。 + * @param sequenceNumber 通し番号 + * @return エディタ + */ + private TalkEditor getTalkEditor(int sequenceNumber){ + while(this.editorList.size() < sequenceNumber){ + incrementTalkEditor(); + } + + TalkEditor result = this.editorList.get(sequenceNumber - 1); + + return result; + } + + /** + * 指定したエディタの次の通し番号を持つエディタを返す。 + * エディタがなければ追加される。 + * @param editor エディタ + * @return 次のエディタ + */ + private TalkEditor nextEditor(TalkEditor editor){ + int sequenceNumber = editor.getSequenceNumber(); + TalkEditor nextEditor = getTalkEditor(sequenceNumber + 1); + return nextEditor; + } + + /** + * 指定したエディタの前の通し番号を持つエディタを返す。 + * @param editor エディタ + * @return 前のエディタ。 + * 最初のエディタ(通し番号1)が指定されればnullを返す。 + */ + private TalkEditor prevEditor(TalkEditor editor){ + int sequenceNumber = editor.getSequenceNumber(); + if(sequenceNumber <= 1) return null; + TalkEditor prevEditor = getTalkEditor(sequenceNumber - 1); + return prevEditor; + } + + /** + * 指定したエディタがエディタ集合の最後のエディタか判定する。 + * @param editor エディタ + * @return 最後のエディタならtrue + */ + private boolean isLastEditor(TalkEditor editor){ + int seqNo = editor.getSequenceNumber(); + int size = this.editorList.size(); + if(seqNo >= size) return true; + return false; + } + + /** + * Documentからその持ち主であるエディタを取得する。 + * @param document Documentインスタンス + * @return 持ち主のエディタ。見つからなければnull。 + */ + private TalkEditor getEditorFromDocument(Document document){ + for(TalkEditor editor : this.editorList){ + if(editor.getDocument() == document) return editor; + } + return null; + } + + /** + * エディタ集合から任意のエディタを除く。 + * ただし最初のエディタは消去不可。 + * @param editor エディタ + */ + private void removeEditor(TalkEditor editor){ + if(editor.getParent() != this) return; + + int seqNo = editor.getSequenceNumber(); + if(seqNo <= 1) return; + TalkEditor prevEditor = prevEditor(editor); + if(editor.isActive()){ + setActiveEditor(prevEditor); + } + if(editor.hasEditorFocus()){ + prevEditor.requestEditorFocus(); + } + + this.editorList.remove(seqNo - 1); + + editor.setNavigationFilter(null); + editor.removeTextFocusListener(this); + Document document = editor.getDocument(); + document.removeDocumentListener(this.documentListener); + editor.clearText(); + + remove(editor); + revalidate(); + + int renumber = 1; + for(TalkEditor newEditor : this.editorList){ + newEditor.setSequenceNumber(renumber++); + } + + return; + } + + /** + * エディタ間文字調整タスクをディスパッチスレッドとして事後投入する。 + * エディタ間文字調整タスクが実行中であれば何もしない。 + * きっかけとなったエディタ上でIME操作が確定していなければ何もしない。 + * @param triggerEvent ドキュメント変更イベント + */ + private void detachAdjustTask(DocumentEvent triggerEvent){ + if(this.onAdjusting) return; + + Document document = triggerEvent.getDocument(); + final TalkEditor triggerEditor = getEditorFromDocument(document); + if(triggerEditor.onIMEoperation()) return; + + this.onAdjusting = true; + + EventQueue.invokeLater(new Runnable(){ + public void run(){ + try{ + adjustTask(triggerEditor); + }finally{ + EditArray.this.onAdjusting = false; + } + return; + } + }); + + return; + } + + /** + * エディタ間文字調整タスク本体。 + * @param triggerEditor タスク実行のきっかけとなったエディタ + */ + private void adjustTask(TalkEditor triggerEditor){ + int initCaretPos = triggerEditor.getCaretPosition(); + + TalkEditor newFocus = null; + int newCaretPos = -1; + + TalkEditor current = triggerEditor; + for(;;){ + TalkEditor next; + + if( ! isLastEditor(current) ){ + next = nextEditor(current); + String nextContents = next.getText(); + int nextLength = nextContents.length(); + + current.appendTail(nextContents); + String rest = current.chopRest(); + int restLength; + if(rest == null) restLength = 0; + else restLength = rest.length(); + + int chopLength = nextLength - restLength; + if(chopLength > 0){ + next.chopHead(chopLength); + }else if(chopLength < 0){ + rest = rest.substring(0, -chopLength); + next.appendHead(rest); + }else{ + if(newFocus == null){ + newFocus = current; + newCaretPos = initCaretPos; + } + break; + } + }else{ + String rest = current.chopRest(); + if(rest == null || this.editorList.size() >= MAX_EDITORS){ + if(newFocus == null){ + newFocus = current; + if(current.getTextLength() >= initCaretPos){ + newCaretPos = initCaretPos; + }else{ + newCaretPos = current.getTextLength(); + } + } + break; + } + next = nextEditor(current); + next.appendHead(rest); + } + + if(newFocus == null){ + int currentLength = current.getTextLength(); + if(initCaretPos >= currentLength){ + initCaretPos -= currentLength; + }else{ + newFocus = current; + newCaretPos = initCaretPos; + } + } + + current = next; + } + + if(newFocus != null){ + newFocus.requestEditorFocus(); + newFocus.setCaretPosition(newCaretPos); + } + + adjustEditorsTail(); + + return; + } + + /** + * エディタ集合末尾の空エディタを切り詰める。 + * ただし最初のエディタ(通し番号1)は削除されない。 + * フォーカスを持つエディタが削除された場合は、 + * 削除されなかった最後のエディタにフォーカスが移る。 + */ + private void adjustEditorsTail(){ + int editorNum = this.editorList.size(); + if(editorNum <= 0) return; + TalkEditor lastEditor = this.editorList.get(editorNum - 1); + + TalkEditor prevlostEditor = null; + + boolean lostFocusedEditor = false; + + for(;;){ + int textLength = lastEditor.getTextLength(); + int seqNo = lastEditor.getSequenceNumber(); + + if(lostFocusedEditor){ + prevlostEditor = lastEditor; + } + + if(textLength > 0) break; + if(seqNo <= 1) break; + + if(lastEditor.hasEditorFocus()) lostFocusedEditor = true; + removeEditor(lastEditor); + + lastEditor = prevEditor(lastEditor); // TODO ちょっと変 + } + + if(prevlostEditor != null){ + int textLength = prevlostEditor.getTextLength(); + prevlostEditor.requestEditorFocus(); + prevlostEditor.setCaretPosition(textLength); + } + + return; + } + + /** + * フォーカスを持つエディタを取得する。 + * @return エディタ + */ + public TalkEditor getFocusedTalkEditor(){ + for(TalkEditor editor : this.editorList){ + if(editor.hasEditorFocus()) return editor; + } + return null; + } + + /** + * フォーカスを持つエディタの次エディタがあればフォーカスを移し、 + * カレット位置を0にする。 + */ + // TODO エディタのスクロール位置調整が必要。 + public void forwardEditor(){ + TalkEditor editor = getFocusedTalkEditor(); + if(isLastEditor(editor)) return; + TalkEditor next = nextEditor(editor); + next.setCaretPosition(0); + next.requestEditorFocus(); + return; + } + + /** + * フォーカスを持つエディタの前エディタがあればフォーカスを移し、 + * カレット位置を末尾に置く。 + */ + public void backwardEditor(){ + TalkEditor editor = getFocusedTalkEditor(); + TalkEditor prev = prevEditor(editor); + if(prev == null) return; + int length = prev.getTextLength(); + prev.setCaretPosition(length); + prev.requestEditorFocus(); + return; + } + + /** + * 任意のエディタをアクティブにする。 + * 同時にアクティブなエディタは一つのみ。 + * @param editor アクティブにするエディタ + */ + private void setActiveEditor(TalkEditor editor){ + if(this.activeEditor != null){ + this.activeEditor.setActive(false); + } + + this.activeEditor = editor; + + if(this.activeEditor != null){ + this.activeEditor.setActive(true); + } + + fireChangeActive(); + + return; + } + + /** + * アクティブなエディタを返す。 + * @return アクティブなエディタ。 + */ + public TalkEditor getActiveEditor(){ + return this.activeEditor; + } + + /** + * 全発言を連結した文字列を返す。 + * @return 連結文字列 + */ + public CharSequence getAllText(){ + StringBuilder result = new StringBuilder(); + + for(TalkEditor editor : this.editorList){ + String text = editor.getText(); + result.append(text); + } + + return result; + } + + /** + * 先頭エディタの0文字目から字を詰め込む。 + * 2番目移行のエディタへはみ出すかもしれない。 + * @param seq 詰め込む文字列 + */ + public void setAllText(CharSequence seq){ + TalkEditor firstEditor = getTalkEditor(1); + Document doc = firstEditor.getDocument(); + try{ + doc.insertString(0, seq.toString(), null); + }catch(BadLocationException e){ + assert false; + } + return; + } + + /** + * 全エディタをクリアする。 + */ + public void clearAllEditor(){ + int editorNum = this.editorList.size(); + if(editorNum <= 0) return; + + TalkEditor lastEditor = this.editorList.get(editorNum - 1); + for(;;){ + removeEditor(lastEditor); + lastEditor = prevEditor(lastEditor); + if(lastEditor == null) break; + } + + TalkEditor firstEditor = getTalkEditor(1); + firstEditor.clearText(); + setActiveEditor(firstEditor); + + return; + } + + /** + * テキスト編集用フォントを指定する。 + * @param textFont フォント + */ + public void setTextFont(Font textFont){ + this.textFont = textFont; + for(TalkEditor editor : this.editorList){ + editor.setTextFont(this.textFont); + editor.repaint(); + } + revalidate(); + return; + } + + /** + * テキスト編集用フォントを取得する。 + * @return フォント + */ + public Font getTextFont(){ + return this.textFont; + } + + /** + * アクティブエディタ変更通知用リスナの登録。 + * @param listener リスナ + */ + public void addChangeListener(ChangeListener listener){ + this.listenerList.add(ChangeListener.class, listener); + return; + } + + /** + * アクティブエディタ変更通知用リスナの削除。 + * @param listener リスナ + */ + public void removeChangeListener(ChangeListener listener){ + this.listenerList.remove(ChangeListener.class, listener); + return; + } + + /** + * アクティブエディタ変更通知を行う。 + */ + private void fireChangeActive(){ + ChangeEvent event = new ChangeEvent(this); + + ChangeListener[] listeners = + this.listenerList.getListeners(ChangeListener.class); + for(ChangeListener listener : listeners){ + listener.stateChanged(event); + } + + return; + } + + /** + * {@inheritDoc} + * エディタのフォーカス取得とともにアクティブ状態にする。 + * @param event {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent event){ + Object source = event.getSource(); + if( ! (source instanceof JTextComponent) ) return; + JTextComponent textComp = (JTextComponent) source; + + Document document = textComp.getDocument(); + TalkEditor editor = getEditorFromDocument(document); + + setActiveEditor(editor); + + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void focusLost(FocusEvent event){ + // NOTHING + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Dimension getPreferredScrollableViewportSize(){ + Dimension result = getPreferredSize(); + return result; + } + + /** + * {@inheritDoc} + * 横スクロールバーを極力出さないようレイアウトでがんばる。 + * @return {@inheritDoc} + */ + @Override + public boolean getScrollableTracksViewportWidth(){ + return true; + } + + /** + * {@inheritDoc} + * 縦スクロールバーを出しても良いのでレイアウトでがんばらない。 + * @return {@inheritDoc} + */ + @Override + public boolean getScrollableTracksViewportHeight(){ + return false; + } + + /** + * {@inheritDoc} + * @param visibleRect {@inheritDoc} + * @param orientation {@inheritDoc} + * @param direction {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, + int direction ){ + if(orientation == SwingConstants.VERTICAL){ + return visibleRect.height; + } + return 10; + } + + /** + * {@inheritDoc} + * @param visibleRect {@inheritDoc} + * @param orientation {@inheritDoc} + * @param direction {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction ){ + return 30; // TODO フォント高の1.5倍くらい? + } + + /** + * エディタ内のカーソル移動を監視するための、 + * カスタム化したナビゲーションフィルター。 + * 必要に応じてエディタ間カーソル移動を行う。 + */ + private class CustomNavigation extends NavigationFilter{ + + /** + * コンストラクタ。 + */ + public CustomNavigation(){ + super(); + return; + } + + /** + * {@inheritDoc} + * カーソル移動が行き詰まった場合、 + * 隣接するエディタ間でカーソル移動を行う。 + * @param text {@inheritDoc} + * @param pos {@inheritDoc} + * @param bias {@inheritDoc} + * @param direction {@inheritDoc} + * @param biasRet {@inheritDoc} + * @return {@inheritDoc} + * @throws javax.swing.text.BadLocationException {@inheritDoc} + */ + @Override + public int getNextVisualPositionFrom(JTextComponent text, + int pos, + Bias bias, + int direction, + Bias[] biasRet ) + throws BadLocationException { + int result = super.getNextVisualPositionFrom(text, + pos, + bias, + direction, + biasRet ); + if(result != pos) return result; + + switch(direction){ + case SwingConstants.WEST: + case SwingConstants.NORTH: + backwardEditor(); + break; + case SwingConstants.EAST: + case SwingConstants.SOUTH: + forwardEditor(); + break; + default: + assert false; + } + + return result; + } + } + + /** + * エディタの内容変更を監視し、随時エディタ間調整を行う。 + */ + private class DocWatcher implements DocumentListener{ + + /** + * コンストラクタ。 + */ + public DocWatcher(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void changedUpdate(DocumentEvent event){ + detachAdjustTask(event); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void insertUpdate(DocumentEvent event){ + detachAdjustTask(event); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void removeUpdate(DocumentEvent event){ + detachAdjustTask(event); + return; + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/EnvInfo.java b/src/main/java/jp/sourceforge/jindolf/EnvInfo.java index 07dc7de..0cb6d21 100644 --- a/src/main/java/jp/sourceforge/jindolf/EnvInfo.java +++ b/src/main/java/jp/sourceforge/jindolf/EnvInfo.java @@ -1,151 +1,151 @@ -/* - * environment information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.File; -import java.text.NumberFormat; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * 実行環境に関する各種情報。 - */ -public final class EnvInfo{ - - /** OS名。 */ - public static final String OS_NAME; - /** OSバージョン。 */ - public static final String OS_VERSION; - /** アーキテクチャ種別。 */ - public static final String OS_ARCH; - /** Java実行系ベンダ。 */ - public static final String JAVA_VENDOR; - /** Java実行形バージョン。 */ - public static final String JAVA_VERSION; - - private static final SortedMap propertyMap = - new TreeMap(); - - private static final SortedMap environmentMap = - new TreeMap(); - - private static final String[] classpaths; - - static{ - OS_NAME = getSecureProperty("os.name"); - OS_VERSION = getSecureProperty("os.version"); - OS_ARCH = getSecureProperty("os.arch"); - JAVA_VENDOR = getSecureProperty("java.vendor"); - JAVA_VERSION = getSecureProperty("java.version"); - - getSecureEnvironment("LANG"); - getSecureEnvironment("DISPLAY"); - - String classpath = getSecureProperty("java.class.path"); - if(classpath != null){ - classpaths = classpath.split(File.pathSeparator); - }else{ - classpaths = new String[0]; - } - } - - - /** - * 隠れコンストラクタ。 - */ - private EnvInfo(){ - throw new AssertionError(); - } - - - /** - * 可能ならシステムプロパティを読み込む。 - * @param key キー - * @return プロパティ値。セキュリティ上読み込み禁止の場合はnull。 - */ - private static String getSecureProperty(String key){ - String result; - try{ - result = System.getProperty(key); - if(result != null) propertyMap.put(key, result); - }catch(SecurityException e){ - result = null; - } - return result; - } - - /** - * 可能なら環境変数を読み込む。 - * @param name 環境変数名 - * @return 環境変数値。セキュリティ上読み込み禁止の場合はnull。 - */ - private static String getSecureEnvironment(String name){ - String result; - try{ - result = System.getenv(name); - if(result != null) environmentMap.put(name, result); - }catch(SecurityException e){ - result = null; - } - return result; - } - - /** - * VM詳細情報を文字列化する。 - * @return VM詳細情報 - */ - public static String getVMInfo(){ - StringBuilder result = new StringBuilder(); - NumberFormat nform = NumberFormat.getNumberInstance(); - - result.append("最大ヒープメモリ量: " - + nform.format(Jindolf.RUNTIME.maxMemory()) + " bytes\n"); - - result.append("\n"); - - result.append("起動時引数:\n"); - for(String arg : Jindolf.getOptionInfo().getInvokeArgList()){ - result.append(" ").append(arg).append("\n"); - } - - result.append("\n"); - - result.append("主要システムプロパティ:\n"); - Set propKeys = propertyMap.keySet(); - for(String propKey : propKeys){ - if(propKey.equals("java.class.path")) continue; - String value = propertyMap.get(propKey); - result.append(" "); - result.append(propKey).append("=").append(value).append("\n"); - } - - result.append("\n"); - - result.append("主要環境変数:\n"); - Set envKeys = environmentMap.keySet(); - for(String envKey : envKeys){ - String value = environmentMap.get(envKey); - result.append(" "); - result.append(envKey).append("=").append(value).append("\n"); - } - - result.append("\n"); - - result.append("クラスパス:\n"); - for(String path : classpaths){ - result.append(" "); - result.append(path).append("\n"); - } - - result.append("\n"); - - return result.toString(); - } - -} +/* + * environment information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.File; +import java.text.NumberFormat; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 実行環境に関する各種情報。 + */ +public final class EnvInfo{ + + /** OS名。 */ + public static final String OS_NAME; + /** OSバージョン。 */ + public static final String OS_VERSION; + /** アーキテクチャ種別。 */ + public static final String OS_ARCH; + /** Java実行系ベンダ。 */ + public static final String JAVA_VENDOR; + /** Java実行形バージョン。 */ + public static final String JAVA_VERSION; + + private static final SortedMap propertyMap = + new TreeMap(); + + private static final SortedMap environmentMap = + new TreeMap(); + + private static final String[] classpaths; + + static{ + OS_NAME = getSecureProperty("os.name"); + OS_VERSION = getSecureProperty("os.version"); + OS_ARCH = getSecureProperty("os.arch"); + JAVA_VENDOR = getSecureProperty("java.vendor"); + JAVA_VERSION = getSecureProperty("java.version"); + + getSecureEnvironment("LANG"); + getSecureEnvironment("DISPLAY"); + + String classpath = getSecureProperty("java.class.path"); + if(classpath != null){ + classpaths = classpath.split(File.pathSeparator); + }else{ + classpaths = new String[0]; + } + } + + + /** + * 隠れコンストラクタ。 + */ + private EnvInfo(){ + throw new AssertionError(); + } + + + /** + * 可能ならシステムプロパティを読み込む。 + * @param key キー + * @return プロパティ値。セキュリティ上読み込み禁止の場合はnull。 + */ + private static String getSecureProperty(String key){ + String result; + try{ + result = System.getProperty(key); + if(result != null) propertyMap.put(key, result); + }catch(SecurityException e){ + result = null; + } + return result; + } + + /** + * 可能なら環境変数を読み込む。 + * @param name 環境変数名 + * @return 環境変数値。セキュリティ上読み込み禁止の場合はnull。 + */ + private static String getSecureEnvironment(String name){ + String result; + try{ + result = System.getenv(name); + if(result != null) environmentMap.put(name, result); + }catch(SecurityException e){ + result = null; + } + return result; + } + + /** + * VM詳細情報を文字列化する。 + * @return VM詳細情報 + */ + public static String getVMInfo(){ + StringBuilder result = new StringBuilder(); + NumberFormat nform = NumberFormat.getNumberInstance(); + + result.append("最大ヒープメモリ量: " + + nform.format(Jindolf.RUNTIME.maxMemory()) + " bytes\n"); + + result.append("\n"); + + result.append("起動時引数:\n"); + for(String arg : Jindolf.getOptionInfo().getInvokeArgList()){ + result.append(" ").append(arg).append("\n"); + } + + result.append("\n"); + + result.append("主要システムプロパティ:\n"); + Set propKeys = propertyMap.keySet(); + for(String propKey : propKeys){ + if(propKey.equals("java.class.path")) continue; + String value = propertyMap.get(propKey); + result.append(" "); + result.append(propKey).append("=").append(value).append("\n"); + } + + result.append("\n"); + + result.append("主要環境変数:\n"); + Set envKeys = environmentMap.keySet(); + for(String envKey : envKeys){ + String value = environmentMap.get(envKey); + result.append(" "); + result.append(envKey).append("=").append(value).append("\n"); + } + + result.append("\n"); + + result.append("クラスパス:\n"); + for(String path : classpaths){ + result.append(" "); + result.append(path).append("\n"); + } + + result.append("\n"); + + return result.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FaceIconSet.java b/src/main/java/jp/sourceforge/jindolf/FaceIconSet.java index b296121..e5ccb06 100644 --- a/src/main/java/jp/sourceforge/jindolf/FaceIconSet.java +++ b/src/main/java/jp/sourceforge/jindolf/FaceIconSet.java @@ -1,91 +1,91 @@ -/* - * Face icon set - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.HashMap; -import java.util.Map; - -/** - * 顔アイコンWiki表記のセット。 - */ -public class FaceIconSet{ - - private final String caption; - private final String author; - private final String urlText; - private final Map wikiMap = new HashMap(); - - /** - * コンストラクタ。 - * @param caption 説明 - * @param author 作者 - * @param urlText URL文字列 - */ - public FaceIconSet(String caption, String author, String urlText){ - super(); - this.caption = caption; - this.author = author; - this.urlText = urlText; - return; - } - - /** - * 説明文字列を得る。 - * @return 説明文字列 - */ - public String getCaption(){ - return this.caption; - } - - /** - * 作者名を得る。 - * @return 作者名 - */ - public String getAuthor(){ - return this.author; - } - - /** - * URL文字列を得る。 - * @return URL文字列 - */ - public String getUrlText(){ - return this.urlText; - } - - /** - * Avatarに対するWiki表記を登録する。 - * @param avatar Avatar - * @param wiki Wiki表記 - */ - public void registIconWiki(Avatar avatar, String wiki){ - this.wikiMap.put(avatar, wiki); - return; - } - - /** - * Avatarに対するWiki表記を取得する。 - * @param avatar Avatar - * @return Wiki表記 - */ - public String getAvatarIconWiki(Avatar avatar){ - String wiki = this.wikiMap.get(avatar); - return wiki; - } - - /** - * アイコンセットの文字列化。 - * コンボボックスアイテムの表記などで使われることを想定。 - * @return 文字列 - */ - @Override - public String toString(){ - return getCaption(); - } - -} +/* + * Face icon set + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.HashMap; +import java.util.Map; + +/** + * 顔アイコンWiki表記のセット。 + */ +public class FaceIconSet{ + + private final String caption; + private final String author; + private final String urlText; + private final Map wikiMap = new HashMap(); + + /** + * コンストラクタ。 + * @param caption 説明 + * @param author 作者 + * @param urlText URL文字列 + */ + public FaceIconSet(String caption, String author, String urlText){ + super(); + this.caption = caption; + this.author = author; + this.urlText = urlText; + return; + } + + /** + * 説明文字列を得る。 + * @return 説明文字列 + */ + public String getCaption(){ + return this.caption; + } + + /** + * 作者名を得る。 + * @return 作者名 + */ + public String getAuthor(){ + return this.author; + } + + /** + * URL文字列を得る。 + * @return URL文字列 + */ + public String getUrlText(){ + return this.urlText; + } + + /** + * Avatarに対するWiki表記を登録する。 + * @param avatar Avatar + * @param wiki Wiki表記 + */ + public void registIconWiki(Avatar avatar, String wiki){ + this.wikiMap.put(avatar, wiki); + return; + } + + /** + * Avatarに対するWiki表記を取得する。 + * @param avatar Avatar + * @return Wiki表記 + */ + public String getAvatarIconWiki(Avatar avatar){ + String wiki = this.wikiMap.get(avatar); + return wiki; + } + + /** + * アイコンセットの文字列化。 + * コンボボックスアイテムの表記などで使われることを想定。 + * @return 文字列 + */ + @Override + public String toString(){ + return getCaption(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FileUtils.java b/src/main/java/jp/sourceforge/jindolf/FileUtils.java index b80b9dc..d90e41e 100644 --- a/src/main/java/jp/sourceforge/jindolf/FileUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/FileUtils.java @@ -1,394 +1,394 @@ -/* - * file utilities - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.Locale; - -/** - * 諸々のファイル操作ユーティリティ。 - * JRE1.6 API へのリフレクションアクセスを含む。 - */ -public final class FileUtils{ - - private static final String SCHEME_FILE = "file"; - - /** JRE1.6のjava.io.File#setReadableに相当。 */ - private static final Method METHOD_SETREADABLE; - /** JRE1.6のjava.io.File#setWritableに相当。 */ - private static final Method METHOD_SETWRITABLE; - /** Locale.ROOT代替品。 */ - private static final Locale ROOT = new Locale("", "", ""); - - static{ - Method method; - int modifiers; - - try{ - method = File.class.getMethod( - "setReadable", Boolean.TYPE, Boolean.TYPE); - modifiers = method.getModifiers(); - if( ! Modifier.isPublic(modifiers) ){ - method = null; - } - }catch(NoSuchMethodException e){ - method = null; - }catch(SecurityException e){ - method = null; - } - METHOD_SETREADABLE = method; - - try{ - method = File.class.getMethod( - "setWritable", Boolean.TYPE, Boolean.TYPE); - modifiers = method.getModifiers(); - if( ! Modifier.isPublic(modifiers) ){ - method = null; - } - }catch(NoSuchMethodException e){ - method = null; - }catch(SecurityException e){ - method = null; - } - METHOD_SETWRITABLE = method; - - assert ! ( isMacOSXFs() && isWindowsOSFs() ); - } - - - /** - * 隠しコンストラクタ。 - */ - private FileUtils(){ - assert false; - throw new AssertionError(); - } - - - /** - * なるべく自分にだけ許可を与え自分以外には許可を与えないように - * ファイル属性を操作する。 - * @param method setReadableかsetWritableのいずれかのメソッド。 - * nullならなにもしない。 - * @param file 操作対象のファイル。 - * @return 成功すればtrue - * @throws SecurityException セキュリティ上の許可が無い場合 - */ - private static boolean invokeOwnerOnly(Method method, File file) - throws SecurityException{ - if(method == null) return false; - if(file == null) throw new NullPointerException(); - - Object result1; - Object result2; - try{ - result1 = method.invoke(file, false, false); - result2 = method.invoke(file, true, true); - }catch(IllegalAccessException e){ - assert false; - return false; - }catch(IllegalArgumentException e){ - assert false; - return false; - }catch(ExceptionInInitializerError e){ - assert false; - return false; - }catch(InvocationTargetException e){ - Throwable cause = e.getCause(); - if(cause instanceof SecurityException){ - throw (SecurityException) cause; - }else if(cause instanceof RuntimeException){ - throw (RuntimeException) cause; - }else if(cause instanceof Error){ - throw (Error) cause; - }else{ - assert false; - } - return false; - } - - assert result1 instanceof Boolean; - assert result2 instanceof Boolean; - Boolean bresult1 = (Boolean) result1; - Boolean bresult2 = (Boolean) result2; - - return bresult1 && bresult2; - } - - /** - * なるべく自分にだけ読み書き許可を与え - * 自分以外には読み書き許可を与えないように - * ファイル属性を操作する。 - * JRE1.6環境でなければなにもしない。 - * @param file 操作対象ファイル - * @return 成功すればtrue - * @throws SecurityException セキュリティ上の許可が無い場合 - */ - public static boolean setOwnerOnlyAccess(File file) - throws SecurityException{ - boolean readresult = invokeOwnerOnly(METHOD_SETREADABLE, file); - boolean writeresult = invokeOwnerOnly(METHOD_SETWRITABLE, file); - return readresult & writeresult; - } - - /** - * 任意の絶対パスの祖先の内、存在するもっとも近い祖先を返す。 - * @param file 任意の絶対パス - * @return 存在するもっとも近い祖先。一つも存在しなければnull。 - * @throws IllegalArgumentException 引数が絶対パスでない - */ - public static File findExistsAncestor(File file) - throws IllegalArgumentException{ - if(file == null) return null; - if( ! file.isAbsolute() ) throw new IllegalArgumentException(); - if(file.exists()) return file; - File parent = file.getParentFile(); - return findExistsAncestor(parent); - } - - /** - * 任意の絶対パスのルートファイルシステムもしくはドライブレターを返す。 - * @param file 任意の絶対パス - * @return ルートファイルシステムもしくはドライブレター - * @throws IllegalArgumentException 引数が絶対パスでない - */ - public static File findRootFile(File file) - throws IllegalArgumentException{ - if( ! file.isAbsolute() ) throw new IllegalArgumentException(); - File parent = file.getParentFile(); - if(parent == null) return file; - return findRootFile(parent); - } - - /** - * 相対パスの絶対パス化を試みる。 - * @param file 対象パス - * @return 絶対パス。絶対化に失敗した場合は元の引数。 - */ - public static File supplyFullPath(File file){ - if(file.isAbsolute()) return file; - - File absFile; - - try{ - absFile = file.getAbsoluteFile(); - }catch(SecurityException e){ - return file; - } - - return absFile; - } - - /** - * 任意のディレクトリがアクセス可能な状態にあるか判定する。 - * アクセス可能の条件を満たすためには、与えられたパスが - * 存在し、 - * かつディレクトリであり、 - * かつ読み込み可能であり、 - * かつ書き込み可能 - * でなければならない。 - * @param path 任意のディレクトリ - * @return アクセス可能ならtrue - */ - public static boolean isAccessibleDirectory(File path){ - if(path == null) return false; - - if( ! path.exists() ) return false; - if( ! path.isDirectory() ) return false; - if( ! path.canRead() ) return false; - if( ! path.canWrite() ) return false; - - return true; - } - - /** - * クラスがローカルファイルからロードされたのであれば - * そのファイルを返す。 - * @param klass 任意のクラス - * @return ロード元ファイル。見つからなければnull。 - */ - public static File getClassSourceFile(Class klass){ - ProtectionDomain domain; - try{ - domain = klass.getProtectionDomain(); - }catch(SecurityException e){ - return null; - } - - CodeSource src = domain.getCodeSource(); - - URL location = src.getLocation(); - String scheme = location.getProtocol(); - if( ! scheme.equals(SCHEME_FILE) ) return null; - - URI uri; - try{ - uri = location.toURI(); - }catch(URISyntaxException e){ - assert false; - return null; - } - - File file = new File(uri); - - return file; - } - - /** - * すでに存在するJARファイルか判定する。 - * @param file 任意のファイル - * @return すでに存在するJARファイルであればtrue - */ - public static boolean isExistsJarFile(File file){ - if(file == null) return false; - if( ! file.exists() ) return false; - if( ! file.isFile() ) return false; - - String name = file.getName(); - if( ! name.matches("^.+\\.[jJ][aA][rR]$") ) return false; - - // TODO ファイル先頭マジックナンバーのテストも必要? - - return true; - } - - /** - * クラスがローカルJARファイルからロードされたのであれば - * その格納ディレクトリを返す。 - * @param klass 任意のクラス - * @return ロード元JARファイルの格納ディレクトリ。 - * JARが見つからない、もしくはロード元がJARファイルでなければnull。 - */ - public static File getJarDirectory(Class klass){ - File jarFile = getClassSourceFile(klass); - if(jarFile == null) return null; - - if( ! isExistsJarFile(jarFile) ){ - return null; - } - - return jarFile.getParentFile(); - } - - /** - * ホームディレクトリを得る。 - * システムプロパティuser.homeで示されたホームディレクトリを返す。 - * @return ホームディレクトリ。何らかの事情でnullを返す場合もあり。 - */ - public static File getHomeDirectory(){ - String homeProp; - try{ - homeProp = System.getProperty("user.home"); - }catch(SecurityException e){ - return null; - } - - File homeFile = new File(homeProp); - - return homeFile; - } - - /** - * MacOSX環境か否か判定する。 - * @return MacOSX環境ならtrue - */ - public static boolean isMacOSXFs(){ - if(File.separatorChar != '/') return false; - - String osName; - try{ - osName = System.getProperty("os.name"); - }catch(SecurityException e){ - return false; - } - - if(osName == null) return false; - - osName = osName.toLowerCase(ROOT); - - if(osName.startsWith("mac os x")){ - return true; - } - - return false; - } - - /** - * Windows環境か否か判定する。 - * @return Windows環境ならtrue - */ - public static boolean isWindowsOSFs(){ - if(File.separatorChar != '\\') return false; - - String osName; - try{ - osName = System.getProperty("os.name"); - }catch(SecurityException e){ - return false; - } - - if(osName == null) return false; - - osName = osName.toLowerCase(ROOT); - - if(osName.startsWith("windows")){ - return true; - } - - return false; - } - - /** - * アプリケーション設定ディレクトリを返す。 - * 存在の有無、アクセスの可否は関知しない。 - * @return アプリケーション設定ディレクトリ - */ - public static File getAppSetDir(){ - File home = getHomeDirectory(); - if(home == null) return null; - - File result = home; - - if(isMacOSXFs()){ - result = new File(result, "Library"); - result = new File(result, "Application Support"); - } - - // TODO Win環境での%APPDATA%サポート - - return result; - } - - /** - * ファイル名を表示するためのJLabel用HTML文字列を生成する。 - * Windows日本語環境では、バックスラッシュ記号が円通貨記号に置換される。 - * @param file 対象ファイル - * @return HTML文字列断片 - */ - public static String getHtmledFileName(File file){ - String pathName = file.getPath(); - - Locale locale = Locale.getDefault(); - String lang = locale.getLanguage(); - - if( FileUtils.isWindowsOSFs() && lang.equals("ja") ){ - pathName = pathName.replace(File.separator, "¥"); - } - - return "" + pathName + ""; - } - -} +/* + * file utilities + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.Locale; + +/** + * 諸々のファイル操作ユーティリティ。 + * JRE1.6 API へのリフレクションアクセスを含む。 + */ +public final class FileUtils{ + + private static final String SCHEME_FILE = "file"; + + /** JRE1.6のjava.io.File#setReadableに相当。 */ + private static final Method METHOD_SETREADABLE; + /** JRE1.6のjava.io.File#setWritableに相当。 */ + private static final Method METHOD_SETWRITABLE; + /** Locale.ROOT代替品。 */ + private static final Locale ROOT = new Locale("", "", ""); + + static{ + Method method; + int modifiers; + + try{ + method = File.class.getMethod( + "setReadable", Boolean.TYPE, Boolean.TYPE); + modifiers = method.getModifiers(); + if( ! Modifier.isPublic(modifiers) ){ + method = null; + } + }catch(NoSuchMethodException e){ + method = null; + }catch(SecurityException e){ + method = null; + } + METHOD_SETREADABLE = method; + + try{ + method = File.class.getMethod( + "setWritable", Boolean.TYPE, Boolean.TYPE); + modifiers = method.getModifiers(); + if( ! Modifier.isPublic(modifiers) ){ + method = null; + } + }catch(NoSuchMethodException e){ + method = null; + }catch(SecurityException e){ + method = null; + } + METHOD_SETWRITABLE = method; + + assert ! ( isMacOSXFs() && isWindowsOSFs() ); + } + + + /** + * 隠しコンストラクタ。 + */ + private FileUtils(){ + assert false; + throw new AssertionError(); + } + + + /** + * なるべく自分にだけ許可を与え自分以外には許可を与えないように + * ファイル属性を操作する。 + * @param method setReadableかsetWritableのいずれかのメソッド。 + * nullならなにもしない。 + * @param file 操作対象のファイル。 + * @return 成功すればtrue + * @throws SecurityException セキュリティ上の許可が無い場合 + */ + private static boolean invokeOwnerOnly(Method method, File file) + throws SecurityException{ + if(method == null) return false; + if(file == null) throw new NullPointerException(); + + Object result1; + Object result2; + try{ + result1 = method.invoke(file, false, false); + result2 = method.invoke(file, true, true); + }catch(IllegalAccessException e){ + assert false; + return false; + }catch(IllegalArgumentException e){ + assert false; + return false; + }catch(ExceptionInInitializerError e){ + assert false; + return false; + }catch(InvocationTargetException e){ + Throwable cause = e.getCause(); + if(cause instanceof SecurityException){ + throw (SecurityException) cause; + }else if(cause instanceof RuntimeException){ + throw (RuntimeException) cause; + }else if(cause instanceof Error){ + throw (Error) cause; + }else{ + assert false; + } + return false; + } + + assert result1 instanceof Boolean; + assert result2 instanceof Boolean; + Boolean bresult1 = (Boolean) result1; + Boolean bresult2 = (Boolean) result2; + + return bresult1 && bresult2; + } + + /** + * なるべく自分にだけ読み書き許可を与え + * 自分以外には読み書き許可を与えないように + * ファイル属性を操作する。 + * JRE1.6環境でなければなにもしない。 + * @param file 操作対象ファイル + * @return 成功すればtrue + * @throws SecurityException セキュリティ上の許可が無い場合 + */ + public static boolean setOwnerOnlyAccess(File file) + throws SecurityException{ + boolean readresult = invokeOwnerOnly(METHOD_SETREADABLE, file); + boolean writeresult = invokeOwnerOnly(METHOD_SETWRITABLE, file); + return readresult & writeresult; + } + + /** + * 任意の絶対パスの祖先の内、存在するもっとも近い祖先を返す。 + * @param file 任意の絶対パス + * @return 存在するもっとも近い祖先。一つも存在しなければnull。 + * @throws IllegalArgumentException 引数が絶対パスでない + */ + public static File findExistsAncestor(File file) + throws IllegalArgumentException{ + if(file == null) return null; + if( ! file.isAbsolute() ) throw new IllegalArgumentException(); + if(file.exists()) return file; + File parent = file.getParentFile(); + return findExistsAncestor(parent); + } + + /** + * 任意の絶対パスのルートファイルシステムもしくはドライブレターを返す。 + * @param file 任意の絶対パス + * @return ルートファイルシステムもしくはドライブレター + * @throws IllegalArgumentException 引数が絶対パスでない + */ + public static File findRootFile(File file) + throws IllegalArgumentException{ + if( ! file.isAbsolute() ) throw new IllegalArgumentException(); + File parent = file.getParentFile(); + if(parent == null) return file; + return findRootFile(parent); + } + + /** + * 相対パスの絶対パス化を試みる。 + * @param file 対象パス + * @return 絶対パス。絶対化に失敗した場合は元の引数。 + */ + public static File supplyFullPath(File file){ + if(file.isAbsolute()) return file; + + File absFile; + + try{ + absFile = file.getAbsoluteFile(); + }catch(SecurityException e){ + return file; + } + + return absFile; + } + + /** + * 任意のディレクトリがアクセス可能な状態にあるか判定する。 + * アクセス可能の条件を満たすためには、与えられたパスが + * 存在し、 + * かつディレクトリであり、 + * かつ読み込み可能であり、 + * かつ書き込み可能 + * でなければならない。 + * @param path 任意のディレクトリ + * @return アクセス可能ならtrue + */ + public static boolean isAccessibleDirectory(File path){ + if(path == null) return false; + + if( ! path.exists() ) return false; + if( ! path.isDirectory() ) return false; + if( ! path.canRead() ) return false; + if( ! path.canWrite() ) return false; + + return true; + } + + /** + * クラスがローカルファイルからロードされたのであれば + * そのファイルを返す。 + * @param klass 任意のクラス + * @return ロード元ファイル。見つからなければnull。 + */ + public static File getClassSourceFile(Class klass){ + ProtectionDomain domain; + try{ + domain = klass.getProtectionDomain(); + }catch(SecurityException e){ + return null; + } + + CodeSource src = domain.getCodeSource(); + + URL location = src.getLocation(); + String scheme = location.getProtocol(); + if( ! scheme.equals(SCHEME_FILE) ) return null; + + URI uri; + try{ + uri = location.toURI(); + }catch(URISyntaxException e){ + assert false; + return null; + } + + File file = new File(uri); + + return file; + } + + /** + * すでに存在するJARファイルか判定する。 + * @param file 任意のファイル + * @return すでに存在するJARファイルであればtrue + */ + public static boolean isExistsJarFile(File file){ + if(file == null) return false; + if( ! file.exists() ) return false; + if( ! file.isFile() ) return false; + + String name = file.getName(); + if( ! name.matches("^.+\\.[jJ][aA][rR]$") ) return false; + + // TODO ファイル先頭マジックナンバーのテストも必要? + + return true; + } + + /** + * クラスがローカルJARファイルからロードされたのであれば + * その格納ディレクトリを返す。 + * @param klass 任意のクラス + * @return ロード元JARファイルの格納ディレクトリ。 + * JARが見つからない、もしくはロード元がJARファイルでなければnull。 + */ + public static File getJarDirectory(Class klass){ + File jarFile = getClassSourceFile(klass); + if(jarFile == null) return null; + + if( ! isExistsJarFile(jarFile) ){ + return null; + } + + return jarFile.getParentFile(); + } + + /** + * ホームディレクトリを得る。 + * システムプロパティuser.homeで示されたホームディレクトリを返す。 + * @return ホームディレクトリ。何らかの事情でnullを返す場合もあり。 + */ + public static File getHomeDirectory(){ + String homeProp; + try{ + homeProp = System.getProperty("user.home"); + }catch(SecurityException e){ + return null; + } + + File homeFile = new File(homeProp); + + return homeFile; + } + + /** + * MacOSX環境か否か判定する。 + * @return MacOSX環境ならtrue + */ + public static boolean isMacOSXFs(){ + if(File.separatorChar != '/') return false; + + String osName; + try{ + osName = System.getProperty("os.name"); + }catch(SecurityException e){ + return false; + } + + if(osName == null) return false; + + osName = osName.toLowerCase(ROOT); + + if(osName.startsWith("mac os x")){ + return true; + } + + return false; + } + + /** + * Windows環境か否か判定する。 + * @return Windows環境ならtrue + */ + public static boolean isWindowsOSFs(){ + if(File.separatorChar != '\\') return false; + + String osName; + try{ + osName = System.getProperty("os.name"); + }catch(SecurityException e){ + return false; + } + + if(osName == null) return false; + + osName = osName.toLowerCase(ROOT); + + if(osName.startsWith("windows")){ + return true; + } + + return false; + } + + /** + * アプリケーション設定ディレクトリを返す。 + * 存在の有無、アクセスの可否は関知しない。 + * @return アプリケーション設定ディレクトリ + */ + public static File getAppSetDir(){ + File home = getHomeDirectory(); + if(home == null) return null; + + File result = home; + + if(isMacOSXFs()){ + result = new File(result, "Library"); + result = new File(result, "Application Support"); + } + + // TODO Win環境での%APPDATA%サポート + + return result; + } + + /** + * ファイル名を表示するためのJLabel用HTML文字列を生成する。 + * Windows日本語環境では、バックスラッシュ記号が円通貨記号に置換される。 + * @param file 対象ファイル + * @return HTML文字列断片 + */ + public static String getHtmledFileName(File file){ + String pathName = file.getPath(); + + Locale locale = Locale.getDefault(); + String lang = locale.getLanguage(); + + if( FileUtils.isWindowsOSFs() && lang.equals("ja") ){ + pathName = pathName.replace(File.separator, "¥"); + } + + return "" + pathName + ""; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FilterPanel.java b/src/main/java/jp/sourceforge/jindolf/FilterPanel.java index 5351f40..ecf0438 100644 --- a/src/main/java/jp/sourceforge/jindolf/FilterPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/FilterPanel.java @@ -1,526 +1,526 @@ -/* - * Filter panel - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.BitSet; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.SwingConstants; -import javax.swing.border.Border; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.EventListenerList; -import jp.sourceforge.jindolf.corelib.EventFamily; -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * 発言フィルタ GUI。 - */ -@SuppressWarnings("serial") -public class FilterPanel extends JDialog - implements ActionListener, TopicFilter{ - - private static final int COLS = 4; - - private static final String FRAMETITLE = "発言フィルタ - " + Jindolf.TITLE; - - private final JCheckBox checkPublic = new JCheckBox("公開", true); - private final JCheckBox checkWolf = new JCheckBox("狼", true); - private final JCheckBox checkPrivate = new JCheckBox("独り言", true); - private final JCheckBox checkGrave = new JCheckBox("墓下", true); - private final JCheckBox checkExtra = new JCheckBox("Extra", true); - - private final JButton selAllButton = new JButton("全選択"); - private final JButton selNoneButton = new JButton("全解除"); - private final JButton negateButton = new JButton("反転"); - - private final JCheckBox checkRealtime = - new JCheckBox("リアルタイム更新", true); - private final JButton applyButton = new JButton("フィルタ適用"); - - private final Map cbMap = - new HashMap(); - private final List cbList = new LinkedList(); - - private final EventListenerList listeners = new EventListenerList(); - - /** - * 発言フィルタを生成する。 - * @param owner フレームオーナー - */ - public FilterPanel(Frame owner){ - super(owner, FRAMETITLE, false); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - JComponent topicPanel = createTopicPanel(); - JComponent avatarPanel = createAvatarPanel(); - JComponent buttonPanel = createButtonPanel(); - JComponent bottomPanel = createBottomPanel(); - design(topicPanel, avatarPanel, buttonPanel, bottomPanel); - - this.checkPublic.addActionListener(this); - this.checkWolf.addActionListener(this); - this.checkPrivate.addActionListener(this); - this.checkGrave.addActionListener(this); - this.checkExtra.addActionListener(this); - - for(JCheckBox avatarCheckBox : this.cbList){ - avatarCheckBox.addActionListener(this); - } - - this.selAllButton.addActionListener(this); - this.selNoneButton.addActionListener(this); - this.negateButton.addActionListener(this); - - this.checkRealtime.addActionListener(this); - this.applyButton.addActionListener(this); - this.applyButton.setEnabled(false); - - return; - } - - /** - * レイアウトデザインを行う。 - * @param topicPanel システムイベント選択 - * @param avatarPanel キャラ一覧 - * @param buttonPanel ボタン群 - * @param bottomPanel 下段パネル - */ - private void design(JComponent topicPanel, - JComponent avatarPanel, - JComponent buttonPanel, - JComponent bottomPanel ){ - Container content = getContentPane(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - constraints.weightx = 1.0 / 5; - constraints.weighty = 1.0; - constraints.gridheight = 3; - constraints.fill = GridBagConstraints.BOTH; - constraints.anchor = GridBagConstraints.CENTER; - content.add(topicPanel, constraints); - - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.VERTICAL; - constraints.insets = new Insets(3, 0, 3, 0); - content.add(new JSeparator(SwingConstants.VERTICAL), constraints); - - constraints.weightx = 4.0 / 5; - constraints.weighty = 0.0; - constraints.gridheight = 1; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(0, 0, 0, 0); - content.add(buttonPanel, constraints); - - constraints.insets = new Insets(0, 3, 0, 3); - content.add(new JSeparator(), constraints); - - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.insets = new Insets(0, 0, 0, 0); - content.add(avatarPanel, constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(SwingConstants.HORIZONTAL), constraints); - - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - content.add(bottomPanel, constraints); - - return; - } - - /** - * システムイベントチェックボックス群パネルを作成。 - * @return システムイベントチェックボックス群パネル - */ - private JComponent createTopicPanel(){ - this.checkPublic.setToolTipText("誰にでも見える発言"); - this.checkWolf.setToolTipText("人狼同士にしか見えない発言"); - this.checkPrivate.setToolTipText("本人にしか見えない発言"); - this.checkGrave.setToolTipText("死者同士にしか見えない発言"); - this.checkExtra.setToolTipText("占い先や護衛先などのシステムメッセージ"); - - JPanel topicPanel = new JPanel(); - - Border border = BorderFactory.createEmptyBorder(2, 2, 2, 2); - topicPanel.setBorder(border); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - topicPanel.setLayout(layout); - - constraints.anchor = GridBagConstraints.WEST; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - - constraints.gridwidth = GridBagConstraints.REMAINDER; - topicPanel.add(this.checkPublic, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - topicPanel.add(this.checkWolf, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - topicPanel.add(this.checkPrivate, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - topicPanel.add(this.checkGrave, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - topicPanel.add(this.checkExtra, constraints); - - return topicPanel; - } - - /** - * キャラ一覧チェックボックス群パネルを作成。 - * @return キャラ一覧チェックボックス群パネル - */ - private JComponent createAvatarPanel(){ - JPanel avatarPanel = new JPanel(); - - Border border = BorderFactory.createEmptyBorder(2, 2, 2, 2); - avatarPanel.setBorder(border); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - avatarPanel.setLayout(layout); - - constraints.weightx = 1.0 / COLS; - constraints.weighty = 1.0; - constraints.anchor = GridBagConstraints.WEST; - - int xPos = 0; - for(Avatar avatar : Avatar.getPredefinedAvatarList()){ - JCheckBox checkBox = new JCheckBox(avatar.getName(), true); - checkBox.setToolTipText(avatar.getJobTitle()); - this.cbList.add(checkBox); - if(xPos >= COLS - 1){ - constraints.gridwidth = GridBagConstraints.REMAINDER; - xPos = 0; - }else{ - constraints.gridwidth = 1; - xPos++; - } - avatarPanel.add(checkBox, constraints); - this.cbMap.put(avatar, checkBox); - } - - return avatarPanel; - } - - /** - * ボタン群パネルを生成。 - * @return ボタン群パネル - */ - private JComponent createButtonPanel(){ - this.selAllButton.setToolTipText( - "全キャラクタの発言を表示する"); - this.selNoneButton.setToolTipText( - "全キャラクタの発言をフィルタリングする"); - this.negateButton.setToolTipText( - "(表示⇔フィルタリング)の設定を反転させる"); - - JPanel buttonPanel = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - buttonPanel.setLayout(layout); - - constraints.weightx = 1.0 / 3; - constraints.insets = new Insets(3, 3, 3, 3); - buttonPanel.add(this.selAllButton, constraints); - buttonPanel.add(this.selNoneButton, constraints); - buttonPanel.add(this.negateButton, constraints); - - return buttonPanel; - } - - /** - * 下段パネルを生成する。 - * @return 下段パネル - */ - private JComponent createBottomPanel(){ - JPanel panel = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.fill = GridBagConstraints.NONE; - constraints.insets = new Insets(3, 3, 3, 3); - panel.add(this.checkRealtime, constraints); - panel.add(this.applyButton, constraints); - - return panel; - } - - /** - * リスナを登録する。 - * @param listener リスナ - */ - public void addChangeListener(ChangeListener listener){ - this.listeners.add(ChangeListener.class, listener); - } - - /** - * リスナを削除する。 - * @param listener リスナ - */ - public void removeChangeListener(ChangeListener listener){ - this.listeners.remove(ChangeListener.class, listener); - } - - /** - * 全リスナを取得する。 - * @return リスナの配列 - */ - public ChangeListener[] getChangeListeners(){ - return this.listeners.getListeners(ChangeListener.class); - } - - /** - * 全リスナへフィルタ操作を通知する。 - */ - protected void fireCheckChanged(){ - ChangeEvent changeEvent = new ChangeEvent(this); - for(ChangeListener listener : getChangeListeners()){ - listener.stateChanged(changeEvent); - } - } - - /** - * ボタン状態の初期化。 - */ - public void initButtons(){ - this.checkPublic.setSelected(true); - this.checkWolf.setSelected(true); - this.checkPrivate.setSelected(true); - this.checkGrave.setSelected(true); - this.checkExtra.setSelected(true); - - this.selAllButton.doClick(); - - return; - } - - /** - * チェックボックスまたはボタン操作時にリスナとして呼ばれる。 - * {@inheritDoc} - * @param event イベント - */ - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - - boolean isRealtime = this.checkRealtime.isSelected(); - - if(source == this.selAllButton){ - boolean hasChanged = false; - for(JCheckBox avatarCBox : this.cbList){ - if( ! avatarCBox.isSelected()){ - avatarCBox.setSelected(true); - hasChanged = true; - } - } - if(isRealtime && hasChanged){ - fireCheckChanged(); - } - }else if(source == this.selNoneButton){ - boolean hasChanged = false; - for(JCheckBox avatarCBox : this.cbList){ - if(avatarCBox.isSelected()){ - avatarCBox.setSelected(false); - hasChanged = true; - } - } - if(isRealtime && hasChanged){ - fireCheckChanged(); - } - }else if(source == this.negateButton){ - for(JCheckBox avatarCBox : this.cbList){ - if(avatarCBox.isSelected()){ - avatarCBox.setSelected(false); - }else{ - avatarCBox.setSelected(true); - } - } - if(isRealtime){ - fireCheckChanged(); - } - }else if(source == this.checkRealtime){ - if(isRealtime){ - this.applyButton.setEnabled(false); - fireCheckChanged(); - }else{ - this.applyButton.setEnabled(true); - } - }else if(source == this.applyButton){ - fireCheckChanged(); - }else if(source instanceof JCheckBox){ - if(isRealtime){ - fireCheckChanged(); - } - } - - return; - } - - /** - * {@inheritDoc} - * @param topic {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean isFiltered(Topic topic){ - Talk talk; - if(topic instanceof Talk){ - talk = (Talk) topic; - }else if(topic instanceof SysEvent){ - SysEvent sysEvent = (SysEvent) topic; - if(sysEvent.getEventFamily() == EventFamily.EXTRA){ - if( ! this.checkExtra.isSelected() ){ - return true; - } - } - return false; - }else{ - return false; - } - - JCheckBox cbox; - - TalkType type = talk.getTalkType(); - switch(type){ - case PUBLIC: - cbox = this.checkPublic; - break; - case WOLFONLY: - cbox = this.checkWolf; - break; - case PRIVATE: - cbox = this.checkPrivate; - break; - case GRAVE: - cbox = this.checkGrave; - break; - default: - assert false; - return true; - } - if( ! cbox.isSelected()){ - return true; - } - - Avatar avatar = talk.getAvatar(); - cbox = this.cbMap.get(avatar); - if( cbox != null && ! cbox.isSelected()){ - return true; - } - - return false; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public FilterContext getFilterContext(){ - return new FilterPanelContext(); - } - - /** - * {@inheritDoc} - * @param context {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean isSame(FilterContext context){ - if(context == null) return false; - if( ! (context instanceof FilterPanelContext) ) return false; - FilterPanelContext argContext = (FilterPanelContext) context; - FilterPanelContext thisContext = - (FilterPanelContext) getFilterContext(); - - return thisContext.context.equals(argContext.context); - } - - /** - * カスタム化されたフィルタ状態。 - */ - private final class FilterPanelContext implements FilterContext{ - - private final BitSet context = new BitSet(); - - /** - * コンストラクタ。 - */ - public FilterPanelContext(){ - super(); - - int index = 0; - this.context.set(index++, - FilterPanel.this.checkPublic.isSelected()); - this.context.set(index++, - FilterPanel.this.checkWolf.isSelected()); - this.context.set(index++, - FilterPanel.this.checkPrivate.isSelected()); - this.context.set(index++, - FilterPanel.this.checkGrave.isSelected()); - this.context.set(index++, - FilterPanel.this.checkExtra.isSelected()); - - for(Avatar avatar : Avatar.getPredefinedAvatarList()){ - JCheckBox checkBox = FilterPanel.this.cbMap.get(avatar); - this.context.set(index++, checkBox.isSelected()); - } - - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return this.context.toString(); - } - - } - -} +/* + * Filter panel + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.BitSet; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import jp.sourceforge.jindolf.corelib.EventFamily; +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * 発言フィルタ GUI。 + */ +@SuppressWarnings("serial") +public class FilterPanel extends JDialog + implements ActionListener, TopicFilter{ + + private static final int COLS = 4; + + private static final String FRAMETITLE = "発言フィルタ - " + Jindolf.TITLE; + + private final JCheckBox checkPublic = new JCheckBox("公開", true); + private final JCheckBox checkWolf = new JCheckBox("狼", true); + private final JCheckBox checkPrivate = new JCheckBox("独り言", true); + private final JCheckBox checkGrave = new JCheckBox("墓下", true); + private final JCheckBox checkExtra = new JCheckBox("Extra", true); + + private final JButton selAllButton = new JButton("全選択"); + private final JButton selNoneButton = new JButton("全解除"); + private final JButton negateButton = new JButton("反転"); + + private final JCheckBox checkRealtime = + new JCheckBox("リアルタイム更新", true); + private final JButton applyButton = new JButton("フィルタ適用"); + + private final Map cbMap = + new HashMap(); + private final List cbList = new LinkedList(); + + private final EventListenerList listeners = new EventListenerList(); + + /** + * 発言フィルタを生成する。 + * @param owner フレームオーナー + */ + public FilterPanel(Frame owner){ + super(owner, FRAMETITLE, false); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + JComponent topicPanel = createTopicPanel(); + JComponent avatarPanel = createAvatarPanel(); + JComponent buttonPanel = createButtonPanel(); + JComponent bottomPanel = createBottomPanel(); + design(topicPanel, avatarPanel, buttonPanel, bottomPanel); + + this.checkPublic.addActionListener(this); + this.checkWolf.addActionListener(this); + this.checkPrivate.addActionListener(this); + this.checkGrave.addActionListener(this); + this.checkExtra.addActionListener(this); + + for(JCheckBox avatarCheckBox : this.cbList){ + avatarCheckBox.addActionListener(this); + } + + this.selAllButton.addActionListener(this); + this.selNoneButton.addActionListener(this); + this.negateButton.addActionListener(this); + + this.checkRealtime.addActionListener(this); + this.applyButton.addActionListener(this); + this.applyButton.setEnabled(false); + + return; + } + + /** + * レイアウトデザインを行う。 + * @param topicPanel システムイベント選択 + * @param avatarPanel キャラ一覧 + * @param buttonPanel ボタン群 + * @param bottomPanel 下段パネル + */ + private void design(JComponent topicPanel, + JComponent avatarPanel, + JComponent buttonPanel, + JComponent bottomPanel ){ + Container content = getContentPane(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + constraints.weightx = 1.0 / 5; + constraints.weighty = 1.0; + constraints.gridheight = 3; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.CENTER; + content.add(topicPanel, constraints); + + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.VERTICAL; + constraints.insets = new Insets(3, 0, 3, 0); + content.add(new JSeparator(SwingConstants.VERTICAL), constraints); + + constraints.weightx = 4.0 / 5; + constraints.weighty = 0.0; + constraints.gridheight = 1; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(0, 0, 0, 0); + content.add(buttonPanel, constraints); + + constraints.insets = new Insets(0, 3, 0, 3); + content.add(new JSeparator(), constraints); + + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.insets = new Insets(0, 0, 0, 0); + content.add(avatarPanel, constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(SwingConstants.HORIZONTAL), constraints); + + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + content.add(bottomPanel, constraints); + + return; + } + + /** + * システムイベントチェックボックス群パネルを作成。 + * @return システムイベントチェックボックス群パネル + */ + private JComponent createTopicPanel(){ + this.checkPublic.setToolTipText("誰にでも見える発言"); + this.checkWolf.setToolTipText("人狼同士にしか見えない発言"); + this.checkPrivate.setToolTipText("本人にしか見えない発言"); + this.checkGrave.setToolTipText("死者同士にしか見えない発言"); + this.checkExtra.setToolTipText("占い先や護衛先などのシステムメッセージ"); + + JPanel topicPanel = new JPanel(); + + Border border = BorderFactory.createEmptyBorder(2, 2, 2, 2); + topicPanel.setBorder(border); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + topicPanel.setLayout(layout); + + constraints.anchor = GridBagConstraints.WEST; + constraints.weightx = 1.0; + constraints.weighty = 1.0; + + constraints.gridwidth = GridBagConstraints.REMAINDER; + topicPanel.add(this.checkPublic, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + topicPanel.add(this.checkWolf, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + topicPanel.add(this.checkPrivate, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + topicPanel.add(this.checkGrave, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + topicPanel.add(this.checkExtra, constraints); + + return topicPanel; + } + + /** + * キャラ一覧チェックボックス群パネルを作成。 + * @return キャラ一覧チェックボックス群パネル + */ + private JComponent createAvatarPanel(){ + JPanel avatarPanel = new JPanel(); + + Border border = BorderFactory.createEmptyBorder(2, 2, 2, 2); + avatarPanel.setBorder(border); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + avatarPanel.setLayout(layout); + + constraints.weightx = 1.0 / COLS; + constraints.weighty = 1.0; + constraints.anchor = GridBagConstraints.WEST; + + int xPos = 0; + for(Avatar avatar : Avatar.getPredefinedAvatarList()){ + JCheckBox checkBox = new JCheckBox(avatar.getName(), true); + checkBox.setToolTipText(avatar.getJobTitle()); + this.cbList.add(checkBox); + if(xPos >= COLS - 1){ + constraints.gridwidth = GridBagConstraints.REMAINDER; + xPos = 0; + }else{ + constraints.gridwidth = 1; + xPos++; + } + avatarPanel.add(checkBox, constraints); + this.cbMap.put(avatar, checkBox); + } + + return avatarPanel; + } + + /** + * ボタン群パネルを生成。 + * @return ボタン群パネル + */ + private JComponent createButtonPanel(){ + this.selAllButton.setToolTipText( + "全キャラクタの発言を表示する"); + this.selNoneButton.setToolTipText( + "全キャラクタの発言をフィルタリングする"); + this.negateButton.setToolTipText( + "(表示⇔フィルタリング)の設定を反転させる"); + + JPanel buttonPanel = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + buttonPanel.setLayout(layout); + + constraints.weightx = 1.0 / 3; + constraints.insets = new Insets(3, 3, 3, 3); + buttonPanel.add(this.selAllButton, constraints); + buttonPanel.add(this.selNoneButton, constraints); + buttonPanel.add(this.negateButton, constraints); + + return buttonPanel; + } + + /** + * 下段パネルを生成する。 + * @return 下段パネル + */ + private JComponent createBottomPanel(){ + JPanel panel = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.fill = GridBagConstraints.NONE; + constraints.insets = new Insets(3, 3, 3, 3); + panel.add(this.checkRealtime, constraints); + panel.add(this.applyButton, constraints); + + return panel; + } + + /** + * リスナを登録する。 + * @param listener リスナ + */ + public void addChangeListener(ChangeListener listener){ + this.listeners.add(ChangeListener.class, listener); + } + + /** + * リスナを削除する。 + * @param listener リスナ + */ + public void removeChangeListener(ChangeListener listener){ + this.listeners.remove(ChangeListener.class, listener); + } + + /** + * 全リスナを取得する。 + * @return リスナの配列 + */ + public ChangeListener[] getChangeListeners(){ + return this.listeners.getListeners(ChangeListener.class); + } + + /** + * 全リスナへフィルタ操作を通知する。 + */ + protected void fireCheckChanged(){ + ChangeEvent changeEvent = new ChangeEvent(this); + for(ChangeListener listener : getChangeListeners()){ + listener.stateChanged(changeEvent); + } + } + + /** + * ボタン状態の初期化。 + */ + public void initButtons(){ + this.checkPublic.setSelected(true); + this.checkWolf.setSelected(true); + this.checkPrivate.setSelected(true); + this.checkGrave.setSelected(true); + this.checkExtra.setSelected(true); + + this.selAllButton.doClick(); + + return; + } + + /** + * チェックボックスまたはボタン操作時にリスナとして呼ばれる。 + * {@inheritDoc} + * @param event イベント + */ + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + + boolean isRealtime = this.checkRealtime.isSelected(); + + if(source == this.selAllButton){ + boolean hasChanged = false; + for(JCheckBox avatarCBox : this.cbList){ + if( ! avatarCBox.isSelected()){ + avatarCBox.setSelected(true); + hasChanged = true; + } + } + if(isRealtime && hasChanged){ + fireCheckChanged(); + } + }else if(source == this.selNoneButton){ + boolean hasChanged = false; + for(JCheckBox avatarCBox : this.cbList){ + if(avatarCBox.isSelected()){ + avatarCBox.setSelected(false); + hasChanged = true; + } + } + if(isRealtime && hasChanged){ + fireCheckChanged(); + } + }else if(source == this.negateButton){ + for(JCheckBox avatarCBox : this.cbList){ + if(avatarCBox.isSelected()){ + avatarCBox.setSelected(false); + }else{ + avatarCBox.setSelected(true); + } + } + if(isRealtime){ + fireCheckChanged(); + } + }else if(source == this.checkRealtime){ + if(isRealtime){ + this.applyButton.setEnabled(false); + fireCheckChanged(); + }else{ + this.applyButton.setEnabled(true); + } + }else if(source == this.applyButton){ + fireCheckChanged(); + }else if(source instanceof JCheckBox){ + if(isRealtime){ + fireCheckChanged(); + } + } + + return; + } + + /** + * {@inheritDoc} + * @param topic {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean isFiltered(Topic topic){ + Talk talk; + if(topic instanceof Talk){ + talk = (Talk) topic; + }else if(topic instanceof SysEvent){ + SysEvent sysEvent = (SysEvent) topic; + if(sysEvent.getEventFamily() == EventFamily.EXTRA){ + if( ! this.checkExtra.isSelected() ){ + return true; + } + } + return false; + }else{ + return false; + } + + JCheckBox cbox; + + TalkType type = talk.getTalkType(); + switch(type){ + case PUBLIC: + cbox = this.checkPublic; + break; + case WOLFONLY: + cbox = this.checkWolf; + break; + case PRIVATE: + cbox = this.checkPrivate; + break; + case GRAVE: + cbox = this.checkGrave; + break; + default: + assert false; + return true; + } + if( ! cbox.isSelected()){ + return true; + } + + Avatar avatar = talk.getAvatar(); + cbox = this.cbMap.get(avatar); + if( cbox != null && ! cbox.isSelected()){ + return true; + } + + return false; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public FilterContext getFilterContext(){ + return new FilterPanelContext(); + } + + /** + * {@inheritDoc} + * @param context {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean isSame(FilterContext context){ + if(context == null) return false; + if( ! (context instanceof FilterPanelContext) ) return false; + FilterPanelContext argContext = (FilterPanelContext) context; + FilterPanelContext thisContext = + (FilterPanelContext) getFilterContext(); + + return thisContext.context.equals(argContext.context); + } + + /** + * カスタム化されたフィルタ状態。 + */ + private final class FilterPanelContext implements FilterContext{ + + private final BitSet context = new BitSet(); + + /** + * コンストラクタ。 + */ + public FilterPanelContext(){ + super(); + + int index = 0; + this.context.set(index++, + FilterPanel.this.checkPublic.isSelected()); + this.context.set(index++, + FilterPanel.this.checkWolf.isSelected()); + this.context.set(index++, + FilterPanel.this.checkPrivate.isSelected()); + this.context.set(index++, + FilterPanel.this.checkGrave.isSelected()); + this.context.set(index++, + FilterPanel.this.checkExtra.isSelected()); + + for(Avatar avatar : Avatar.getPredefinedAvatarList()){ + JCheckBox checkBox = FilterPanel.this.cbMap.get(avatar); + this.context.set(index++, checkBox.isSelected()); + } + + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return this.context.toString(); + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FindPanel.java b/src/main/java/jp/sourceforge/jindolf/FindPanel.java index e702e5e..df6e80f 100644 --- a/src/main/java/jp/sourceforge/jindolf/FindPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/FindPanel.java @@ -1,774 +1,774 @@ -/* - * Find panel - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import javax.swing.BorderFactory; -import javax.swing.ComboBoxEditor; -import javax.swing.ComboBoxModel; -import javax.swing.DefaultListCellRenderer; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.border.Border; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.EventListenerList; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; -import javax.swing.text.JTextComponent; -import jp.sourceforge.jindolf.json.JsArray; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * 検索パネルGUI。 - */ -@SuppressWarnings("serial") -public class FindPanel extends JDialog - implements ActionListener, - ItemListener, - ChangeListener, - PropertyChangeListener { - - private static final String HIST_FILE = "searchHistory.json"; - private static final String FRAMETITLE = "発言検索 - " + Jindolf.TITLE; - private static final String LABEL_REENTER = "再入力"; - private static final String LABEL_IGNORE = "無視して検索をキャンセル"; - - private final JComboBox findBox = new JComboBox(); - private final JButton searchButton = new JButton("検索"); - private final JButton clearButton = new JButton("クリア"); - private final JCheckBox capitalSwitch = - new JCheckBox("大文字/小文字を区別する"); - private final JCheckBox regexSwitch = - new JCheckBox("正規表現"); - private final JCheckBox dotallSwitch = - new JCheckBox("正規表現 \".\" を行末記号にもマッチさせる"); - private final JCheckBox multilineSwitch = - new JCheckBox("正規表現 \"^\" や \"$\" を" - +"行末記号の前後に反応させる"); - private final JCheckBox bulkSearchSwitch = - new JCheckBox("全日程を一括検索"); - private final JButton closeButton = new JButton("キャンセル"); - - private final CustomModel model = new CustomModel(); - - private JsObject loadedHistory = null; - - private boolean canceled = false; - private RegexPattern regexPattern = null; - - /** - * 検索パネルを生成する。 - * @param owner 親フレーム。 - */ - public FindPanel(Frame owner){ - super(owner, FRAMETITLE, true); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter(){ - @Override - public void windowClosing(WindowEvent event){ - actionCancel(); - return; - } - }); - - design(); - - this.findBox.setModel(this.model); - this.findBox.setToolTipText("検索文字列を入力してください"); - this.findBox.setEditable(true); - this.findBox.setRenderer(new CustomRenderer()); - this.findBox.setMaximumRowCount(15); - - ComboBoxEditor editor = this.findBox.getEditor(); - modifyComboBoxEditor(editor); - this.findBox.addPropertyChangeListener("UI", this); - - this.searchButton.setToolTipText("発言内容を検索する"); - this.clearButton.setToolTipText("入力をクリアする"); - - this.findBox.addItemListener(this); - this.searchButton.addActionListener(this); - this.clearButton.addActionListener(this); - this.regexSwitch.addChangeListener(this); - this.closeButton.addActionListener(this); - - setRegexPattern(null); - - return; - } - - /** - * デザイン・レイアウトを行う。 - */ - private void design(){ - Container content = getContentPane(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridwidth = 2; - Border border = - BorderFactory - .createTitledBorder("検索文字列を入力してください"); - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(this.findBox, BorderLayout.CENTER); - panel.setBorder(border); - content.add(panel, constraints); - - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.SOUTH; - content.add(this.searchButton, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.WEST; - content.add(this.clearButton, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.WEST; - content.add(this.capitalSwitch, constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.WEST; - content.add(this.regexSwitch, constraints); - - JPanel regexPanel = new JPanel(); - regexPanel.setBorder(BorderFactory.createTitledBorder("")); - regexPanel.setLayout(new GridLayout(2, 1)); - regexPanel.add(this.dotallSwitch); - regexPanel.add(this.multilineSwitch); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.WEST; - content.add(regexPanel, constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - constraints.weightx = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.NONE; - content.add(this.bulkSearchSwitch, constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.EAST; - constraints.fill = GridBagConstraints.NONE; - content.add(this.closeButton, constraints); - - return; - } - - /** - * {@inheritDoc} - * 検索ダイアログを表示・非表示する。 - * @param show 表示フラグ。真なら表示。{@inheritDoc} - */ - @Override - public void setVisible(boolean show){ - super.setVisible(show); - getRootPane().setDefaultButton(this.searchButton); - this.findBox.requestFocusInWindow(); - return; - } - - /** - * ダイアログが閉じられた原因を判定する。 - * @return キャンセルもしくはクローズボタンでダイアログが閉じられたらtrue - */ - public boolean isCanceled(){ - return this.canceled; - } - - /** - * 一括検索が指定されたか否か返す。 - * @return 一括検索が指定されたらtrue - */ - public boolean isBulkSearch(){ - return this.bulkSearchSwitch.isSelected(); - } - - /** - * キャンセルボタン押下処理。 - * このモーダルダイアログを閉じる。 - */ - private void actionCancel(){ - this.canceled = true; - setVisible(false); - dispose(); - return; - } - - /** - * 検索ボタン押下処理。 - * このモーダルダイアログを閉じる。 - */ - private void actionSubmit(){ - Object selected = this.findBox.getSelectedItem(); - if(selected == null){ - this.regexPattern = null; - return; - } - String edit = selected.toString(); - - boolean isRegex = this.regexSwitch.isSelected(); - - int flag = 0x00000000; - if( ! this.capitalSwitch.isSelected() ){ - flag |= RegexPattern.IGNORECASEFLAG; - } - if(this.dotallSwitch.isSelected()) flag |= Pattern.DOTALL; - if(this.multilineSwitch.isSelected()) flag |= Pattern.MULTILINE; - - try{ - this.regexPattern = new RegexPattern(edit, isRegex, flag); - }catch(PatternSyntaxException e){ - this.regexPattern = null; - if(showRegexError(e)){ - return; - } - actionCancel(); - return; - } - - this.model.addHistory(this.regexPattern); - - this.canceled = false; - setVisible(false); - dispose(); - - return; - } - - /** - * 正規表現パターン異常系のダイアログ表示。 - * @param e 正規表現構文エラー - * @return 再入力が押されたらtrue。それ以外はfalse。 - */ - private boolean showRegexError(PatternSyntaxException e){ - String pattern = e.getPattern(); - - String position = ""; - int index = e.getIndex(); - if(0 <= index && index <= pattern.length() - 1){ - char errChar = pattern.charAt(index); - position = "エラーの発生箇所は、おおよそ" - + (index+1) + "文字目 [ " + errChar + " ] " - +"かその前後と推測されます。\n"; - } - - String message = - "入力された検索文字列 [ " + pattern + " ] は" - +"正しい正規表現として認識されませんでした。\n" - +position - +"正規表現の書き方は" - +" [ http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/" - +"java/util/regex/Pattern.html#sum ] " - +"を参照してください。\n" - +"ただの文字列を検索したい場合は" - +"「正規表現」のチェックボックスを外しましょう。\n" - ; - - Object[] buttons = new Object[2]; - buttons[0] = LABEL_REENTER; - buttons[1] = LABEL_IGNORE; - Icon icon = null; - - int optionNo = JOptionPane.showOptionDialog(this, - message, - "不正な正規表現", - JOptionPane.YES_NO_OPTION, - JOptionPane.ERROR_MESSAGE, - icon, - buttons, - LABEL_REENTER); - - if(optionNo == JOptionPane.CLOSED_OPTION) return false; - if(buttons[optionNo].equals(LABEL_REENTER)) return true; - if(buttons[optionNo].equals(LABEL_IGNORE) ) return false; - - return true; - } - - /** - * 現時点での検索パターンを得る。 - * @return 検索パターン - */ - public RegexPattern getRegexPattern(){ - return this.regexPattern; - } - - /** - * 検索パターンを設定する。 - * @param pattern 検索パターン - */ - public final void setRegexPattern(RegexPattern pattern){ - if(pattern == null) this.regexPattern = CustomModel.INITITEM; - else this.regexPattern = pattern; - - String edit = this.regexPattern.getEditSource(); - this.findBox.getEditor().setItem(edit); - - this.regexSwitch.setSelected(this.regexPattern.isRegex()); - - int initflag = this.regexPattern.getRegexFlag(); - this.capitalSwitch.setSelected( - (initflag & RegexPattern.IGNORECASEFLAG) == 0); - this.dotallSwitch.setSelected((initflag & Pattern.DOTALL) != 0); - this.multilineSwitch.setSelected((initflag & Pattern.MULTILINE) != 0); - - maskRegexUI(); - - return; - } - - /** - * {@inheritDoc} - * ボタン操作時にリスナとして呼ばれる。 - * @param event イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if(source == this.closeButton){ - actionCancel(); - }else if(source == this.searchButton){ - actionSubmit(); - }else if(source == this.clearButton){ - this.findBox.getEditor().setItem(""); - this.findBox.requestFocusInWindow(); - } - return; - } - - /** - * {@inheritDoc} - * コンボボックスのアイテム選択リスナ。 - * @param event アイテム選択イベント {@inheritDoc} - */ - @Override - public void itemStateChanged(ItemEvent event){ - int stateChange = event.getStateChange(); - if(stateChange != ItemEvent.SELECTED) return; - - Object item = event.getItem(); - if( ! (item instanceof RegexPattern) ) return; - RegexPattern regex = (RegexPattern) item; - - setRegexPattern(regex); - - return; - } - - /** - * {@inheritDoc} - * チェックボックス操作のリスナ。 - * @param event チェックボックス操作イベント {@inheritDoc} - */ - @Override - public void stateChanged(ChangeEvent event){ - if(event.getSource() != this.regexSwitch) return; - maskRegexUI(); - return; - } - - /** - * 正規表現でしか使わないUIのマスク処理。 - */ - private void maskRegexUI(){ - boolean isRegex = this.regexSwitch.isSelected(); - this.dotallSwitch .setEnabled(isRegex); - this.multilineSwitch.setEnabled(isRegex); - return; - } - - /** - * {@inheritDoc} - * コンボボックスのUI変更通知を受け取るリスナ。 - * @param event UI差し替えイベント {@inheritDoc} - */ - @Override - public void propertyChange(PropertyChangeEvent event){ - if( ! event.getPropertyName().equals("UI") ) return; - if(event.getSource() != this.findBox) return; - - ComboBoxEditor editor = this.findBox.getEditor(); - modifyComboBoxEditor(editor); - - return; - } - - /** - * コンボボックスエディタを修飾する。 - * マージン修飾と等幅フォントをいじる。 - * @param editor エディタ - */ - private void modifyComboBoxEditor(ComboBoxEditor editor){ - if(editor == null) return; - - Component editComp = editor.getEditorComponent(); - if(editComp == null) return; - - if(editComp instanceof JTextComponent){ - JTextComponent textEditor = (JTextComponent) editComp; - textEditor.setComponentPopupMenu(new TextPopup()); - } - - GUIUtils.addMargin(editComp, 1, 4, 1, 4); - - return; - } - - /** - * 検索履歴をロードする。 - */ - public void loadHistory(){ - JsValue value = ConfigFile.loadJson(new File(HIST_FILE)); - if(value == null) return; - - if( ! (value instanceof JsObject) ) return; - JsObject root = (JsObject) value; - - value = root.getValue("history"); - if( ! (value instanceof JsArray) ) return; - JsArray array = (JsArray) value; - - for(JsValue elem : array){ - if( ! (elem instanceof JsObject) ) continue; - JsObject regObj = (JsObject) elem; - RegexPattern regex = RegexPattern.decodeJson(regObj); - if(regex == null) continue; - this.model.addHistory(regex); - } - - this.loadedHistory = root; - - return; - } - - /** - * 検索履歴をセーブする。 - */ - public void saveHistory(){ - AppSetting setting = Jindolf.getAppSetting(); - if( ! setting.useConfigPath() ) return; - File configPath = setting.getConfigPath(); - if(configPath == null) return; - - JsObject root = new JsObject(); - JsArray array = new JsArray(); - root.putValue("history", array); - - List history = this.model.getOriginalHistoryList(); - history = new ArrayList(history); - Collections.reverse(history); - for(RegexPattern regex : history){ - JsObject obj = RegexPattern.encodeJson(regex); - array.add(obj); - } - - if(this.loadedHistory != null){ - if(this.loadedHistory.equals(root)) return; - } - - ConfigFile.saveJson(new File(HIST_FILE), root); - - return; - } - - /** - * コンボボックスの独自レンダラ。 - */ - private static class CustomRenderer extends DefaultListCellRenderer{ - - /** - * コンストラクタ。 - */ - public CustomRenderer(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param list {@inheritDoc} - * @param value {@inheritDoc} - * @param index {@inheritDoc} - * @param isSelected {@inheritDoc} - * @param cellHasFocus {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ){ - if(value instanceof JSeparator){ - return (JSeparator) value; - } - - JLabel superLabel = - (JLabel) super.getListCellRendererComponent(list, - value, - index, - isSelected, - cellHasFocus); - - if(value instanceof RegexPattern){ - RegexPattern regexPattern = (RegexPattern) value; - String text; - if(regexPattern.isRegex()){ - text = "[R] " + regexPattern.getEditSource(); - }else{ - text = regexPattern.getEditSource(); - } - text += regexPattern.getComment(); - - superLabel.setText(text); - } - - GUIUtils.addMargin(superLabel, 1, 4, 1, 4); - - return superLabel; - } - } - - /** - * コンボボックスの独自データモデル。 - */ - private static class CustomModel implements ComboBoxModel{ - - private static final int HISTORY_MAX = 7; - private static final RegexPattern INITITEM = - new RegexPattern( - "", false, RegexPattern.IGNORECASEFLAG | Pattern.DOTALL); - private static final List PREDEF_PATTERN_LIST = - new LinkedList(); - - static{ - PREDEF_PATTERN_LIST.add( - new RegexPattern("【[^】]*】", - true, - Pattern.DOTALL, - " ※ 重要事項") ); - PREDEF_PATTERN_LIST.add( - new RegexPattern("[■●▼★□○▽☆〇◯∇]", - true, - Pattern.DOTALL, - " ※ 議題") ); - PREDEF_PATTERN_LIST.add( - new RegexPattern("Jindolf", - false, - RegexPattern.IGNORECASEFLAG, - " ※ 宣伝") ); - } - - private final List history = - new LinkedList(); - private final JSeparator separator1st = new JSeparator(); - private final JSeparator separator2nd = new JSeparator(); - private Object selected; - private final EventListenerList listenerList = - new EventListenerList(); - - /** - * コンストラクタ。 - */ - public CustomModel(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Object getSelectedItem(){ - return this.selected; - } - - /** - * {@inheritDoc} - * @param item {@inheritDoc} - */ - @Override - public void setSelectedItem(Object item){ - if(item instanceof JSeparator) return; - this.selected = item; - return; - } - - /** - * {@inheritDoc} - * @param index {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Object getElementAt(int index){ - int historySize = this.history.size(); - - if(index == 0){ - return INITITEM; - } - if(index == 1){ - return this.separator1st; - } - if(2 <= index && index <= 1 + historySize){ - return this.history.get(index - 2); - } - if(index == historySize + 2){ - return this.separator2nd; - } - if(historySize + 3 <= index){ - return PREDEF_PATTERN_LIST.get(index - 1 - - 1 - - historySize - - 1 ); - } - - return null; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getSize(){ - int size = 1; - size += 1; // first separator - size += this.history.size(); - size += 1; // second separator - size += PREDEF_PATTERN_LIST.size(); - return size; - } - - /** - * {@inheritDoc} - * @param listener {@inheritDoc} - */ - @Override - public void addListDataListener(ListDataListener listener){ - this.listenerList.add(ListDataListener.class, listener); - return; - } - - /** - * {@inheritDoc} - * @param listener {@inheritDoc} - */ - @Override - public void removeListDataListener(ListDataListener listener){ - this.listenerList.remove(ListDataListener.class, listener); - return; - } - - /** - * 検索履歴ヒストリ追加。 - * @param regexPattern 検索履歴 - */ - public void addHistory(RegexPattern regexPattern){ - if(regexPattern == null) return; - if(regexPattern.equals(INITITEM)) return; - if(PREDEF_PATTERN_LIST.contains(regexPattern)) return; - if(this.history.contains(regexPattern)){ - this.history.remove(regexPattern); - } - - this.history.add(0, regexPattern); - - while(this.history.size() > HISTORY_MAX){ - this.history.remove(HISTORY_MAX); - } - - fire(); - - return; - } - - /** - * プリセットでない検索ヒストリリストを返す。 - * @return 検索ヒストリリスト - */ - public List getOriginalHistoryList(){ - return Collections.unmodifiableList(this.history); - } - - /** - * ヒストリ追加イベント発火。 - */ - private void fire(){ - ListDataEvent event = - new ListDataEvent(this, - ListDataEvent.CONTENTS_CHANGED, - 0, getSize() - 1 ); - ListDataListener[] listeners = - this.listenerList.getListeners(ListDataListener.class); - for(ListDataListener listener : listeners){ - listener.contentsChanged(event); - } - return; - } - } - - // TODO ブックマーク機能との統合 -} +/* + * Find panel + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import javax.swing.BorderFactory; +import javax.swing.ComboBoxEditor; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.border.Border; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.text.JTextComponent; +import jp.sourceforge.jindolf.json.JsArray; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * 検索パネルGUI。 + */ +@SuppressWarnings("serial") +public class FindPanel extends JDialog + implements ActionListener, + ItemListener, + ChangeListener, + PropertyChangeListener { + + private static final String HIST_FILE = "searchHistory.json"; + private static final String FRAMETITLE = "発言検索 - " + Jindolf.TITLE; + private static final String LABEL_REENTER = "再入力"; + private static final String LABEL_IGNORE = "無視して検索をキャンセル"; + + private final JComboBox findBox = new JComboBox(); + private final JButton searchButton = new JButton("検索"); + private final JButton clearButton = new JButton("クリア"); + private final JCheckBox capitalSwitch = + new JCheckBox("大文字/小文字を区別する"); + private final JCheckBox regexSwitch = + new JCheckBox("正規表現"); + private final JCheckBox dotallSwitch = + new JCheckBox("正規表現 \".\" を行末記号にもマッチさせる"); + private final JCheckBox multilineSwitch = + new JCheckBox("正規表現 \"^\" や \"$\" を" + +"行末記号の前後に反応させる"); + private final JCheckBox bulkSearchSwitch = + new JCheckBox("全日程を一括検索"); + private final JButton closeButton = new JButton("キャンセル"); + + private final CustomModel model = new CustomModel(); + + private JsObject loadedHistory = null; + + private boolean canceled = false; + private RegexPattern regexPattern = null; + + /** + * 検索パネルを生成する。 + * @param owner 親フレーム。 + */ + public FindPanel(Frame owner){ + super(owner, FRAMETITLE, true); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter(){ + @Override + public void windowClosing(WindowEvent event){ + actionCancel(); + return; + } + }); + + design(); + + this.findBox.setModel(this.model); + this.findBox.setToolTipText("検索文字列を入力してください"); + this.findBox.setEditable(true); + this.findBox.setRenderer(new CustomRenderer()); + this.findBox.setMaximumRowCount(15); + + ComboBoxEditor editor = this.findBox.getEditor(); + modifyComboBoxEditor(editor); + this.findBox.addPropertyChangeListener("UI", this); + + this.searchButton.setToolTipText("発言内容を検索する"); + this.clearButton.setToolTipText("入力をクリアする"); + + this.findBox.addItemListener(this); + this.searchButton.addActionListener(this); + this.clearButton.addActionListener(this); + this.regexSwitch.addChangeListener(this); + this.closeButton.addActionListener(this); + + setRegexPattern(null); + + return; + } + + /** + * デザイン・レイアウトを行う。 + */ + private void design(){ + Container content = getContentPane(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridwidth = 2; + Border border = + BorderFactory + .createTitledBorder("検索文字列を入力してください"); + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(this.findBox, BorderLayout.CENTER); + panel.setBorder(border); + content.add(panel, constraints); + + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.SOUTH; + content.add(this.searchButton, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.WEST; + content.add(this.clearButton, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.WEST; + content.add(this.capitalSwitch, constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.WEST; + content.add(this.regexSwitch, constraints); + + JPanel regexPanel = new JPanel(); + regexPanel.setBorder(BorderFactory.createTitledBorder("")); + regexPanel.setLayout(new GridLayout(2, 1)); + regexPanel.add(this.dotallSwitch); + regexPanel.add(this.multilineSwitch); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.WEST; + content.add(regexPanel, constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + constraints.weightx = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.WEST; + constraints.fill = GridBagConstraints.NONE; + content.add(this.bulkSearchSwitch, constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.EAST; + constraints.fill = GridBagConstraints.NONE; + content.add(this.closeButton, constraints); + + return; + } + + /** + * {@inheritDoc} + * 検索ダイアログを表示・非表示する。 + * @param show 表示フラグ。真なら表示。{@inheritDoc} + */ + @Override + public void setVisible(boolean show){ + super.setVisible(show); + getRootPane().setDefaultButton(this.searchButton); + this.findBox.requestFocusInWindow(); + return; + } + + /** + * ダイアログが閉じられた原因を判定する。 + * @return キャンセルもしくはクローズボタンでダイアログが閉じられたらtrue + */ + public boolean isCanceled(){ + return this.canceled; + } + + /** + * 一括検索が指定されたか否か返す。 + * @return 一括検索が指定されたらtrue + */ + public boolean isBulkSearch(){ + return this.bulkSearchSwitch.isSelected(); + } + + /** + * キャンセルボタン押下処理。 + * このモーダルダイアログを閉じる。 + */ + private void actionCancel(){ + this.canceled = true; + setVisible(false); + dispose(); + return; + } + + /** + * 検索ボタン押下処理。 + * このモーダルダイアログを閉じる。 + */ + private void actionSubmit(){ + Object selected = this.findBox.getSelectedItem(); + if(selected == null){ + this.regexPattern = null; + return; + } + String edit = selected.toString(); + + boolean isRegex = this.regexSwitch.isSelected(); + + int flag = 0x00000000; + if( ! this.capitalSwitch.isSelected() ){ + flag |= RegexPattern.IGNORECASEFLAG; + } + if(this.dotallSwitch.isSelected()) flag |= Pattern.DOTALL; + if(this.multilineSwitch.isSelected()) flag |= Pattern.MULTILINE; + + try{ + this.regexPattern = new RegexPattern(edit, isRegex, flag); + }catch(PatternSyntaxException e){ + this.regexPattern = null; + if(showRegexError(e)){ + return; + } + actionCancel(); + return; + } + + this.model.addHistory(this.regexPattern); + + this.canceled = false; + setVisible(false); + dispose(); + + return; + } + + /** + * 正規表現パターン異常系のダイアログ表示。 + * @param e 正規表現構文エラー + * @return 再入力が押されたらtrue。それ以外はfalse。 + */ + private boolean showRegexError(PatternSyntaxException e){ + String pattern = e.getPattern(); + + String position = ""; + int index = e.getIndex(); + if(0 <= index && index <= pattern.length() - 1){ + char errChar = pattern.charAt(index); + position = "エラーの発生箇所は、おおよそ" + + (index+1) + "文字目 [ " + errChar + " ] " + +"かその前後と推測されます。\n"; + } + + String message = + "入力された検索文字列 [ " + pattern + " ] は" + +"正しい正規表現として認識されませんでした。\n" + +position + +"正規表現の書き方は" + +" [ http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/" + +"java/util/regex/Pattern.html#sum ] " + +"を参照してください。\n" + +"ただの文字列を検索したい場合は" + +"「正規表現」のチェックボックスを外しましょう。\n" + ; + + Object[] buttons = new Object[2]; + buttons[0] = LABEL_REENTER; + buttons[1] = LABEL_IGNORE; + Icon icon = null; + + int optionNo = JOptionPane.showOptionDialog(this, + message, + "不正な正規表現", + JOptionPane.YES_NO_OPTION, + JOptionPane.ERROR_MESSAGE, + icon, + buttons, + LABEL_REENTER); + + if(optionNo == JOptionPane.CLOSED_OPTION) return false; + if(buttons[optionNo].equals(LABEL_REENTER)) return true; + if(buttons[optionNo].equals(LABEL_IGNORE) ) return false; + + return true; + } + + /** + * 現時点での検索パターンを得る。 + * @return 検索パターン + */ + public RegexPattern getRegexPattern(){ + return this.regexPattern; + } + + /** + * 検索パターンを設定する。 + * @param pattern 検索パターン + */ + public final void setRegexPattern(RegexPattern pattern){ + if(pattern == null) this.regexPattern = CustomModel.INITITEM; + else this.regexPattern = pattern; + + String edit = this.regexPattern.getEditSource(); + this.findBox.getEditor().setItem(edit); + + this.regexSwitch.setSelected(this.regexPattern.isRegex()); + + int initflag = this.regexPattern.getRegexFlag(); + this.capitalSwitch.setSelected( + (initflag & RegexPattern.IGNORECASEFLAG) == 0); + this.dotallSwitch.setSelected((initflag & Pattern.DOTALL) != 0); + this.multilineSwitch.setSelected((initflag & Pattern.MULTILINE) != 0); + + maskRegexUI(); + + return; + } + + /** + * {@inheritDoc} + * ボタン操作時にリスナとして呼ばれる。 + * @param event イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if(source == this.closeButton){ + actionCancel(); + }else if(source == this.searchButton){ + actionSubmit(); + }else if(source == this.clearButton){ + this.findBox.getEditor().setItem(""); + this.findBox.requestFocusInWindow(); + } + return; + } + + /** + * {@inheritDoc} + * コンボボックスのアイテム選択リスナ。 + * @param event アイテム選択イベント {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent event){ + int stateChange = event.getStateChange(); + if(stateChange != ItemEvent.SELECTED) return; + + Object item = event.getItem(); + if( ! (item instanceof RegexPattern) ) return; + RegexPattern regex = (RegexPattern) item; + + setRegexPattern(regex); + + return; + } + + /** + * {@inheritDoc} + * チェックボックス操作のリスナ。 + * @param event チェックボックス操作イベント {@inheritDoc} + */ + @Override + public void stateChanged(ChangeEvent event){ + if(event.getSource() != this.regexSwitch) return; + maskRegexUI(); + return; + } + + /** + * 正規表現でしか使わないUIのマスク処理。 + */ + private void maskRegexUI(){ + boolean isRegex = this.regexSwitch.isSelected(); + this.dotallSwitch .setEnabled(isRegex); + this.multilineSwitch.setEnabled(isRegex); + return; + } + + /** + * {@inheritDoc} + * コンボボックスのUI変更通知を受け取るリスナ。 + * @param event UI差し替えイベント {@inheritDoc} + */ + @Override + public void propertyChange(PropertyChangeEvent event){ + if( ! event.getPropertyName().equals("UI") ) return; + if(event.getSource() != this.findBox) return; + + ComboBoxEditor editor = this.findBox.getEditor(); + modifyComboBoxEditor(editor); + + return; + } + + /** + * コンボボックスエディタを修飾する。 + * マージン修飾と等幅フォントをいじる。 + * @param editor エディタ + */ + private void modifyComboBoxEditor(ComboBoxEditor editor){ + if(editor == null) return; + + Component editComp = editor.getEditorComponent(); + if(editComp == null) return; + + if(editComp instanceof JTextComponent){ + JTextComponent textEditor = (JTextComponent) editComp; + textEditor.setComponentPopupMenu(new TextPopup()); + } + + GUIUtils.addMargin(editComp, 1, 4, 1, 4); + + return; + } + + /** + * 検索履歴をロードする。 + */ + public void loadHistory(){ + JsValue value = ConfigFile.loadJson(new File(HIST_FILE)); + if(value == null) return; + + if( ! (value instanceof JsObject) ) return; + JsObject root = (JsObject) value; + + value = root.getValue("history"); + if( ! (value instanceof JsArray) ) return; + JsArray array = (JsArray) value; + + for(JsValue elem : array){ + if( ! (elem instanceof JsObject) ) continue; + JsObject regObj = (JsObject) elem; + RegexPattern regex = RegexPattern.decodeJson(regObj); + if(regex == null) continue; + this.model.addHistory(regex); + } + + this.loadedHistory = root; + + return; + } + + /** + * 検索履歴をセーブする。 + */ + public void saveHistory(){ + AppSetting setting = Jindolf.getAppSetting(); + if( ! setting.useConfigPath() ) return; + File configPath = setting.getConfigPath(); + if(configPath == null) return; + + JsObject root = new JsObject(); + JsArray array = new JsArray(); + root.putValue("history", array); + + List history = this.model.getOriginalHistoryList(); + history = new ArrayList(history); + Collections.reverse(history); + for(RegexPattern regex : history){ + JsObject obj = RegexPattern.encodeJson(regex); + array.add(obj); + } + + if(this.loadedHistory != null){ + if(this.loadedHistory.equals(root)) return; + } + + ConfigFile.saveJson(new File(HIST_FILE), root); + + return; + } + + /** + * コンボボックスの独自レンダラ。 + */ + private static class CustomRenderer extends DefaultListCellRenderer{ + + /** + * コンストラクタ。 + */ + public CustomRenderer(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param list {@inheritDoc} + * @param value {@inheritDoc} + * @param index {@inheritDoc} + * @param isSelected {@inheritDoc} + * @param cellHasFocus {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus ){ + if(value instanceof JSeparator){ + return (JSeparator) value; + } + + JLabel superLabel = + (JLabel) super.getListCellRendererComponent(list, + value, + index, + isSelected, + cellHasFocus); + + if(value instanceof RegexPattern){ + RegexPattern regexPattern = (RegexPattern) value; + String text; + if(regexPattern.isRegex()){ + text = "[R] " + regexPattern.getEditSource(); + }else{ + text = regexPattern.getEditSource(); + } + text += regexPattern.getComment(); + + superLabel.setText(text); + } + + GUIUtils.addMargin(superLabel, 1, 4, 1, 4); + + return superLabel; + } + } + + /** + * コンボボックスの独自データモデル。 + */ + private static class CustomModel implements ComboBoxModel{ + + private static final int HISTORY_MAX = 7; + private static final RegexPattern INITITEM = + new RegexPattern( + "", false, RegexPattern.IGNORECASEFLAG | Pattern.DOTALL); + private static final List PREDEF_PATTERN_LIST = + new LinkedList(); + + static{ + PREDEF_PATTERN_LIST.add( + new RegexPattern("【[^】]*】", + true, + Pattern.DOTALL, + " ※ 重要事項") ); + PREDEF_PATTERN_LIST.add( + new RegexPattern("[■●▼★□○▽☆〇◯∇]", + true, + Pattern.DOTALL, + " ※ 議題") ); + PREDEF_PATTERN_LIST.add( + new RegexPattern("Jindolf", + false, + RegexPattern.IGNORECASEFLAG, + " ※ 宣伝") ); + } + + private final List history = + new LinkedList(); + private final JSeparator separator1st = new JSeparator(); + private final JSeparator separator2nd = new JSeparator(); + private Object selected; + private final EventListenerList listenerList = + new EventListenerList(); + + /** + * コンストラクタ。 + */ + public CustomModel(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object getSelectedItem(){ + return this.selected; + } + + /** + * {@inheritDoc} + * @param item {@inheritDoc} + */ + @Override + public void setSelectedItem(Object item){ + if(item instanceof JSeparator) return; + this.selected = item; + return; + } + + /** + * {@inheritDoc} + * @param index {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object getElementAt(int index){ + int historySize = this.history.size(); + + if(index == 0){ + return INITITEM; + } + if(index == 1){ + return this.separator1st; + } + if(2 <= index && index <= 1 + historySize){ + return this.history.get(index - 2); + } + if(index == historySize + 2){ + return this.separator2nd; + } + if(historySize + 3 <= index){ + return PREDEF_PATTERN_LIST.get(index - 1 + - 1 + - historySize + - 1 ); + } + + return null; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getSize(){ + int size = 1; + size += 1; // first separator + size += this.history.size(); + size += 1; // second separator + size += PREDEF_PATTERN_LIST.size(); + return size; + } + + /** + * {@inheritDoc} + * @param listener {@inheritDoc} + */ + @Override + public void addListDataListener(ListDataListener listener){ + this.listenerList.add(ListDataListener.class, listener); + return; + } + + /** + * {@inheritDoc} + * @param listener {@inheritDoc} + */ + @Override + public void removeListDataListener(ListDataListener listener){ + this.listenerList.remove(ListDataListener.class, listener); + return; + } + + /** + * 検索履歴ヒストリ追加。 + * @param regexPattern 検索履歴 + */ + public void addHistory(RegexPattern regexPattern){ + if(regexPattern == null) return; + if(regexPattern.equals(INITITEM)) return; + if(PREDEF_PATTERN_LIST.contains(regexPattern)) return; + if(this.history.contains(regexPattern)){ + this.history.remove(regexPattern); + } + + this.history.add(0, regexPattern); + + while(this.history.size() > HISTORY_MAX){ + this.history.remove(HISTORY_MAX); + } + + fire(); + + return; + } + + /** + * プリセットでない検索ヒストリリストを返す。 + * @return 検索ヒストリリスト + */ + public List getOriginalHistoryList(){ + return Collections.unmodifiableList(this.history); + } + + /** + * ヒストリ追加イベント発火。 + */ + private void fire(){ + ListDataEvent event = + new ListDataEvent(this, + ListDataEvent.CONTENTS_CHANGED, + 0, getSize() - 1 ); + ListDataListener[] listeners = + this.listenerList.getListeners(ListDataListener.class); + for(ListDataListener listener : listeners){ + listener.contentsChanged(event); + } + return; + } + } + + // TODO ブックマーク機能との統合 +} diff --git a/src/main/java/jp/sourceforge/jindolf/FontChooser.java b/src/main/java/jp/sourceforge/jindolf/FontChooser.java index 0d46279..8b099ef 100644 --- a/src/main/java/jp/sourceforge/jindolf/FontChooser.java +++ b/src/main/java/jp/sourceforge/jindolf/FontChooser.java @@ -1,640 +1,640 @@ -/* - * font chooser - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.font.FontRenderContext; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.ListSelectionModel; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -/** - * 発言表示フォント選択パネル。 - */ -@SuppressWarnings("serial") -public class FontChooser extends JPanel - implements ListSelectionListener, - ActionListener, - ItemListener{ - - private static final Integer[] POINT_SIZES = { - 8, 10, 12, 16, 18, 24, 32, 36, 48, 72, // TODO これで十分? - }; - private static final CharSequence PREVIEW_CONTENT; - - static{ - CharSequence resourceText; - try{ - resourceText = Jindolf.loadResourceText("resources/preview.txt"); - }catch(IOException e){ - resourceText = "ABC"; - } - PREVIEW_CONTENT = resourceText; - } - - private FontInfo fontInfo; - private FontInfo lastFontInfo; - - private final JList familySelector; - private final JComboBox sizeSelector; - private final JCheckBox isBoldCheck; - private final JCheckBox isItalicCheck; - private final JCheckBox useTextAntiAliaseCheck; - private final JCheckBox useFractionalCheck; - private final JLabel maxBounds; - private final JTextField decodeName; - private final FontPreview preview; - private final JButton resetDefault; - - private boolean maskListener = false; - - /** - * コンストラクタ。 - */ - public FontChooser(){ - this(FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param fontInfo 初期フォント設定 - * @throws NullPointerException 引数がnull - */ - public FontChooser(FontInfo fontInfo) - throws NullPointerException{ - super(); - - if(fontInfo == null) throw new NullPointerException(); - this.fontInfo = fontInfo; - this.lastFontInfo = fontInfo; - - Jindolf.logger().info( - "デフォルトの発言表示フォントに" - + this.fontInfo.getFont() - + "が選択されました" ); - Jindolf.logger().info( - "発言表示のアンチエイリアス指定に" - + this.fontInfo.getFontRenderContext().isAntiAliased() - + "が指定されました" ); - Jindolf.logger().info( - "発言表示のFractional指定に" - + this.fontInfo.getFontRenderContext().usesFractionalMetrics() - + "が指定されました" ); - - this.familySelector = new JList(FontUtils.createFontSet().toArray()); - this.familySelector.setVisibleRowCount(-1); - this.familySelector - .setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - this.sizeSelector = new JComboBox(); - this.sizeSelector.setEditable(true); - this.sizeSelector.setActionCommand(ActionManager.CMD_FONTSIZESEL); - for(Integer size : POINT_SIZES){ - this.sizeSelector.addItem(size); - } - - this.isBoldCheck = new JCheckBox("ボールド"); - this.isItalicCheck = new JCheckBox("イタリック"); - this.useTextAntiAliaseCheck = new JCheckBox("アンチエイリアス"); - this.useFractionalCheck = new JCheckBox("サブピクセル精度"); - - this.maxBounds = new JLabel(); - - this.decodeName = new JTextField(); - this.decodeName.setEditable(false); - this.decodeName.setMargin(new Insets(1, 4, 1, 4)); - this.decodeName.setComponentPopupMenu(new TextPopup()); - Monodizer.monodize(this.decodeName); - - this.preview = new FontPreview(PREVIEW_CONTENT, this.fontInfo); - - this.resetDefault = new JButton("出荷時に戻す"); - this.resetDefault.addActionListener(this); - - design(this); - updateControlls(); - updatePreview(); - - this.familySelector.addListSelectionListener(this); - this.sizeSelector .addActionListener(this); - - this.isBoldCheck .addItemListener(this); - this.isItalicCheck .addItemListener(this); - this.useTextAntiAliaseCheck.addItemListener(this); - this.useFractionalCheck .addItemListener(this); - - return; - } - - /** - * GUIのデザイン、レイアウトを行う。 - * @param content コンテナ - */ - private void design(Container content){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - Border border; - JPanel panel; - - JComponent fontPref = createFontPrefPanel(); - - constraints.insets = new Insets(5, 5, 5, 5); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.BOTH; - content.add(fontPref, constraints); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.BOTH; - border = BorderFactory.createTitledBorder("プレビュー"); - panel = new JPanel(); - panel.add(this.preview); - panel.setBorder(border); - content.add(createPreviewPanel(), constraints); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(createFontDecodePanel(), constraints); - - constraints.insets = new Insets(5, 5, 5, 5); - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(this.maxBounds, constraints); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(this.resetDefault, constraints); - - return; - } - - /** - * フォント設定画面を生成する。 - * @return フォント設定画面 - */ - private JComponent createFontPrefPanel(){ - JPanel result = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - result.setLayout(layout); - - Border border; - - constraints.insets = new Insets(0, 0, 0, 5); - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.gridheight = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.BOTH; - border = BorderFactory.createEmptyBorder(1, 1, 1, 1); - this.familySelector.setBorder(border); - JScrollPane familyScroller = new JScrollPane(this.familySelector); - border = BorderFactory.createTitledBorder("フォントファミリ選択"); - JPanel familyPanel = new JPanel(); - familyPanel.setLayout(new BorderLayout()); - familyPanel.add(familyScroller, BorderLayout.CENTER); - familyPanel.setBorder(border); - result.add(familyPanel, constraints); - - constraints.insets = new Insets(0, 0, 0, 0); - constraints.weightx = 0.0; - constraints.gridheight = 1; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.WEST; - - border = BorderFactory.createTitledBorder("ポイントサイズ指定"); - JPanel panel = new JPanel(); - panel.add(this.sizeSelector); - panel.setBorder(border); - result.add(panel, constraints); - - constraints.anchor = GridBagConstraints.NORTHWEST; - result.add(this.isBoldCheck, constraints); - result.add(this.isItalicCheck, constraints); - result.add(this.useTextAntiAliaseCheck, constraints); - result.add(this.useFractionalCheck, constraints); - - return result; - } - - /** - * プレビュー画面を生成する。 - * @return プレビュー画面 - */ - private JComponent createPreviewPanel(){ - JPanel result = new JPanel(); - - JScrollPane scroller = new JScrollPane(this.preview); - scroller.getVerticalScrollBar().setUnitIncrement(8); - - Border border; - border = BorderFactory.createTitledBorder("プレビュー"); - result.setBorder(border); - result.setLayout(new BorderLayout()); - result.add(scroller, BorderLayout.CENTER); - - return result; - } - - /** - * フォントデコード名表示パネルを生成する。 - * @return フォントデコード名表示パネル - */ - private JComponent createFontDecodePanel(){ - JPanel result = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - result.setLayout(layout); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.NONE; - result.add(new JLabel("Font.deode() 識別名:"), constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - result.add(this.decodeName, constraints); - - return result; - } - - /** - * フォント設定を返す。 - * @return フォント設定 - */ - public FontInfo getFontInfo(){ - return this.fontInfo; - } - - /** - * フォント設定を適用する。 - * @param newInfo 新設定 - * @throws NullPointerException 引数がnull - */ - public void setFontInfo(FontInfo newInfo) throws NullPointerException{ - if(newInfo == null) throw new NullPointerException(); - - FontInfo old = this.fontInfo; - if(old.equals(newInfo)) return; - - this.fontInfo = newInfo; - - updateControlls(); - updatePreview(); - - return; - } - - /** - * 選択されたフォントを返す。 - * @return フォント - */ - private Font getSelectedFont(){ - return this.fontInfo.getFont(); - } - - /** - * 設定されたフォント描画設定を返す。 - * @return 描画設定 - */ - protected FontRenderContext getFontRenderContext(){ - return this.fontInfo.getFontRenderContext(); - } - - /** - * フォント設定に合わせてプレビュー画面を更新する。 - */ - private void updatePreview(){ - this.preview.setFontInfo(this.fontInfo); - return; - } - - /** - * フォント設定に合わせてGUIを更新する。 - */ - private void updateControlls(){ - this.maskListener = true; - - Font currentFont = getSelectedFont(); - FontRenderContext currentContext = getFontRenderContext(); - - String defaultFamily = currentFont.getFamily(); - this.familySelector.setSelectedValue(defaultFamily, true); - - Integer selectedInteger = Integer.valueOf(currentFont.getSize()); - this.sizeSelector.setSelectedItem(selectedInteger); - int sizeItems = this.sizeSelector.getItemCount(); - for(int index = 0; index <= sizeItems - 1; index++){ - Object sizeItem = this.sizeSelector.getItemAt(index); - if(sizeItem.equals(selectedInteger)){ - this.sizeSelector.setSelectedIndex(index); - break; - } - } - - this.isBoldCheck .setSelected(currentFont.isBold()); - this.isItalicCheck.setSelected(currentFont.isItalic()); - - this.useTextAntiAliaseCheck - .setSelected(currentContext.isAntiAliased()); - this.useFractionalCheck - .setSelected(currentContext.usesFractionalMetrics()); - - this.decodeName.setText(FontUtils.getFontDecodeName(currentFont)); - this.decodeName.setCaretPosition(0); - - Rectangle2D r2d = currentFont.getMaxCharBounds(currentContext); - Rectangle rect = r2d.getBounds(); - String boundInfo = "最大文字寸法 : " - + rect.width - + " pixel幅 × " - + rect.height - + " pixel高"; - this.maxBounds.setText(boundInfo); - - this.maskListener = false; - - return; - } - - /** - * {@inheritDoc} - * ダイアログの表示・非表示。 - * ダイアログが閉じられるまで制御を返さない。 - * @param isVisible trueなら表示 {@inheritDoc} - */ - @Override - public void setVisible(boolean isVisible){ - if(isVisible){ - updateControlls(); - updatePreview(); - } - this.lastFontInfo = this.fontInfo; - - super.setVisible(isVisible); - - return; - } - - /** - * {@inheritDoc} - * チェックボックス操作のリスナ。 - * @param event 操作イベント {@inheritDoc} - */ - @Override - public void itemStateChanged(ItemEvent event){ - if(this.maskListener) return; - - Object source = event.getSource(); - - if( source != this.isBoldCheck - && source != this.isItalicCheck - && source != this.useTextAntiAliaseCheck - && source != this.useFractionalCheck ){ - return; - } - - int style = 0 | Font.PLAIN; - if(this.isBoldCheck.isSelected()){ - style = style | Font.BOLD; - } - if(this.isItalicCheck.isSelected()){ - style = style | Font.ITALIC; - } - Font newFont = getSelectedFont(); - if(newFont.getStyle() != style){ - newFont = newFont.deriveFont(style); - } - - AffineTransform tx = getFontRenderContext().getTransform(); - boolean isAntiAliases = this.useTextAntiAliaseCheck.isSelected(); - boolean useFractional = this.useFractionalCheck .isSelected(); - FontRenderContext newContext = - new FontRenderContext(tx, isAntiAliases, useFractional); - - FontInfo newInfo = new FontInfo(newFont, newContext); - setFontInfo(newInfo); - - return; - } - - /** - * フォントサイズ変更処理。 - */ - private void actionFontSizeSelected(){ - Object selected = this.sizeSelector.getSelectedItem(); - if(selected == null) return; - - Integer selectedInteger; - if(selected instanceof Integer){ - selectedInteger = (Integer) selected; - }else{ - try{ - selectedInteger = Integer.valueOf(selected.toString()); - }catch(NumberFormatException e){ - selectedInteger = Integer.valueOf( - this.lastFontInfo.getFont().getSize() - ); - } - } - - if(selectedInteger.intValue() <= 0){ - selectedInteger = - Integer.valueOf(this.lastFontInfo.getFont().getSize()); - } - - float fontSize = selectedInteger.floatValue(); - Font newFont = getSelectedFont().deriveFont(fontSize); - FontInfo newInfo = this.fontInfo.deriveFont(newFont); - setFontInfo(newInfo); - - int sizeItems = this.sizeSelector.getItemCount(); - for(int index = 0; index <= sizeItems - 1; index++){ - Object sizeItem = this.sizeSelector.getItemAt(index); - if(sizeItem.equals(selectedInteger)){ - this.sizeSelector.setSelectedIndex(index); - break; - } - } - - updateControlls(); - updatePreview(); - - return; - } - - /** - * {@inheritDoc} - * ボタン操作及びフォントサイズ指定コンボボックス操作のリスナ。 - * @param event 操作イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - if(this.maskListener) return; - - String cmd = event.getActionCommand(); - if(cmd.equals(ActionManager.CMD_FONTSIZESEL)){ - actionFontSizeSelected(); - } - - Object source = event.getSource(); - if(source == this.resetDefault){ - setFontInfo(FontInfo.DEFAULT_FONTINFO); - } - - return; - } - - /** - * {@inheritDoc} - * フォントファミリリスト選択操作のリスナ。 - * @param event 操作イベント {@inheritDoc} - */ - @Override - public void valueChanged(ListSelectionEvent event){ - if(this.maskListener) return; - - if(event.getSource() != this.familySelector) return; - if(event.getValueIsAdjusting()) return; - - Object selected = this.familySelector.getSelectedValue(); - if(selected == null) return; - - String familyName = selected.toString(); - Font currentFont = getSelectedFont(); - int style = currentFont.getStyle(); - int size = currentFont.getSize(); - - Font newFont = new Font(familyName, style, size); - FontInfo newInfo = this.fontInfo.deriveFont(newFont); - - setFontInfo(newInfo); - - return; - } - - /** - * フォントプレビュー画面用コンポーネント。 - */ - private static class FontPreview extends JComponent{ - - private static final int MARGIN = 5; - - private final GlyphDraw draw; - - private FontInfo fontInfo; - - /** - * コンストラクタ。 - * @param source 文字列 - * @param fontInfo フォント設定 - */ - public FontPreview(CharSequence source, - FontInfo fontInfo ){ - super(); - - this.fontInfo = fontInfo; - this.draw = new GlyphDraw(source, this.fontInfo); - this.draw.setFontInfo(this.fontInfo); - - this.draw.setPos(MARGIN, MARGIN); - - this.draw.setColor(Color.BLACK); - setBackground(Color.WHITE); - - updateBounds(); - - return; - } - - /** - * サイズ更新。 - */ - private void updateBounds(){ - Rectangle bounds = this.draw.setWidth(Integer.MAX_VALUE); - Dimension dimension = new Dimension(bounds.width + MARGIN * 2, - bounds.height + MARGIN * 2 ); - setPreferredSize(dimension); - revalidate(); - repaint(); - return; - } - - /** - * フォント設定の変更。 - * @param newFontInfo フォント設定 - */ - public void setFontInfo(FontInfo newFontInfo){ - this.fontInfo = newFontInfo; - this.draw.setFontInfo(this.fontInfo); - - updateBounds(); - - return; - } - - /** - * {@inheritDoc} - * 文字列の描画。 - * @param g {@inheritDoc} - */ - @Override - public void paintComponent(Graphics g){ - super.paintComponent(g); - Graphics2D g2d = (Graphics2D) g; - this.draw.paint(g2d); - return; - } - } - -} +/* + * font chooser + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +/** + * 発言表示フォント選択パネル。 + */ +@SuppressWarnings("serial") +public class FontChooser extends JPanel + implements ListSelectionListener, + ActionListener, + ItemListener{ + + private static final Integer[] POINT_SIZES = { + 8, 10, 12, 16, 18, 24, 32, 36, 48, 72, // TODO これで十分? + }; + private static final CharSequence PREVIEW_CONTENT; + + static{ + CharSequence resourceText; + try{ + resourceText = Jindolf.loadResourceText("resources/preview.txt"); + }catch(IOException e){ + resourceText = "ABC"; + } + PREVIEW_CONTENT = resourceText; + } + + private FontInfo fontInfo; + private FontInfo lastFontInfo; + + private final JList familySelector; + private final JComboBox sizeSelector; + private final JCheckBox isBoldCheck; + private final JCheckBox isItalicCheck; + private final JCheckBox useTextAntiAliaseCheck; + private final JCheckBox useFractionalCheck; + private final JLabel maxBounds; + private final JTextField decodeName; + private final FontPreview preview; + private final JButton resetDefault; + + private boolean maskListener = false; + + /** + * コンストラクタ。 + */ + public FontChooser(){ + this(FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param fontInfo 初期フォント設定 + * @throws NullPointerException 引数がnull + */ + public FontChooser(FontInfo fontInfo) + throws NullPointerException{ + super(); + + if(fontInfo == null) throw new NullPointerException(); + this.fontInfo = fontInfo; + this.lastFontInfo = fontInfo; + + Jindolf.logger().info( + "デフォルトの発言表示フォントに" + + this.fontInfo.getFont() + + "が選択されました" ); + Jindolf.logger().info( + "発言表示のアンチエイリアス指定に" + + this.fontInfo.getFontRenderContext().isAntiAliased() + + "が指定されました" ); + Jindolf.logger().info( + "発言表示のFractional指定に" + + this.fontInfo.getFontRenderContext().usesFractionalMetrics() + + "が指定されました" ); + + this.familySelector = new JList(FontUtils.createFontSet().toArray()); + this.familySelector.setVisibleRowCount(-1); + this.familySelector + .setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + this.sizeSelector = new JComboBox(); + this.sizeSelector.setEditable(true); + this.sizeSelector.setActionCommand(ActionManager.CMD_FONTSIZESEL); + for(Integer size : POINT_SIZES){ + this.sizeSelector.addItem(size); + } + + this.isBoldCheck = new JCheckBox("ボールド"); + this.isItalicCheck = new JCheckBox("イタリック"); + this.useTextAntiAliaseCheck = new JCheckBox("アンチエイリアス"); + this.useFractionalCheck = new JCheckBox("サブピクセル精度"); + + this.maxBounds = new JLabel(); + + this.decodeName = new JTextField(); + this.decodeName.setEditable(false); + this.decodeName.setMargin(new Insets(1, 4, 1, 4)); + this.decodeName.setComponentPopupMenu(new TextPopup()); + Monodizer.monodize(this.decodeName); + + this.preview = new FontPreview(PREVIEW_CONTENT, this.fontInfo); + + this.resetDefault = new JButton("出荷時に戻す"); + this.resetDefault.addActionListener(this); + + design(this); + updateControlls(); + updatePreview(); + + this.familySelector.addListSelectionListener(this); + this.sizeSelector .addActionListener(this); + + this.isBoldCheck .addItemListener(this); + this.isItalicCheck .addItemListener(this); + this.useTextAntiAliaseCheck.addItemListener(this); + this.useFractionalCheck .addItemListener(this); + + return; + } + + /** + * GUIのデザイン、レイアウトを行う。 + * @param content コンテナ + */ + private void design(Container content){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + Border border; + JPanel panel; + + JComponent fontPref = createFontPrefPanel(); + + constraints.insets = new Insets(5, 5, 5, 5); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.BOTH; + content.add(fontPref, constraints); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.BOTH; + border = BorderFactory.createTitledBorder("プレビュー"); + panel = new JPanel(); + panel.add(this.preview); + panel.setBorder(border); + content.add(createPreviewPanel(), constraints); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(createFontDecodePanel(), constraints); + + constraints.insets = new Insets(5, 5, 5, 5); + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(this.maxBounds, constraints); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(this.resetDefault, constraints); + + return; + } + + /** + * フォント設定画面を生成する。 + * @return フォント設定画面 + */ + private JComponent createFontPrefPanel(){ + JPanel result = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + result.setLayout(layout); + + Border border; + + constraints.insets = new Insets(0, 0, 0, 5); + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.gridheight = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.BOTH; + border = BorderFactory.createEmptyBorder(1, 1, 1, 1); + this.familySelector.setBorder(border); + JScrollPane familyScroller = new JScrollPane(this.familySelector); + border = BorderFactory.createTitledBorder("フォントファミリ選択"); + JPanel familyPanel = new JPanel(); + familyPanel.setLayout(new BorderLayout()); + familyPanel.add(familyScroller, BorderLayout.CENTER); + familyPanel.setBorder(border); + result.add(familyPanel, constraints); + + constraints.insets = new Insets(0, 0, 0, 0); + constraints.weightx = 0.0; + constraints.gridheight = 1; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.WEST; + + border = BorderFactory.createTitledBorder("ポイントサイズ指定"); + JPanel panel = new JPanel(); + panel.add(this.sizeSelector); + panel.setBorder(border); + result.add(panel, constraints); + + constraints.anchor = GridBagConstraints.NORTHWEST; + result.add(this.isBoldCheck, constraints); + result.add(this.isItalicCheck, constraints); + result.add(this.useTextAntiAliaseCheck, constraints); + result.add(this.useFractionalCheck, constraints); + + return result; + } + + /** + * プレビュー画面を生成する。 + * @return プレビュー画面 + */ + private JComponent createPreviewPanel(){ + JPanel result = new JPanel(); + + JScrollPane scroller = new JScrollPane(this.preview); + scroller.getVerticalScrollBar().setUnitIncrement(8); + + Border border; + border = BorderFactory.createTitledBorder("プレビュー"); + result.setBorder(border); + result.setLayout(new BorderLayout()); + result.add(scroller, BorderLayout.CENTER); + + return result; + } + + /** + * フォントデコード名表示パネルを生成する。 + * @return フォントデコード名表示パネル + */ + private JComponent createFontDecodePanel(){ + JPanel result = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + result.setLayout(layout); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.NONE; + result.add(new JLabel("Font.deode() 識別名:"), constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + result.add(this.decodeName, constraints); + + return result; + } + + /** + * フォント設定を返す。 + * @return フォント設定 + */ + public FontInfo getFontInfo(){ + return this.fontInfo; + } + + /** + * フォント設定を適用する。 + * @param newInfo 新設定 + * @throws NullPointerException 引数がnull + */ + public void setFontInfo(FontInfo newInfo) throws NullPointerException{ + if(newInfo == null) throw new NullPointerException(); + + FontInfo old = this.fontInfo; + if(old.equals(newInfo)) return; + + this.fontInfo = newInfo; + + updateControlls(); + updatePreview(); + + return; + } + + /** + * 選択されたフォントを返す。 + * @return フォント + */ + private Font getSelectedFont(){ + return this.fontInfo.getFont(); + } + + /** + * 設定されたフォント描画設定を返す。 + * @return 描画設定 + */ + protected FontRenderContext getFontRenderContext(){ + return this.fontInfo.getFontRenderContext(); + } + + /** + * フォント設定に合わせてプレビュー画面を更新する。 + */ + private void updatePreview(){ + this.preview.setFontInfo(this.fontInfo); + return; + } + + /** + * フォント設定に合わせてGUIを更新する。 + */ + private void updateControlls(){ + this.maskListener = true; + + Font currentFont = getSelectedFont(); + FontRenderContext currentContext = getFontRenderContext(); + + String defaultFamily = currentFont.getFamily(); + this.familySelector.setSelectedValue(defaultFamily, true); + + Integer selectedInteger = Integer.valueOf(currentFont.getSize()); + this.sizeSelector.setSelectedItem(selectedInteger); + int sizeItems = this.sizeSelector.getItemCount(); + for(int index = 0; index <= sizeItems - 1; index++){ + Object sizeItem = this.sizeSelector.getItemAt(index); + if(sizeItem.equals(selectedInteger)){ + this.sizeSelector.setSelectedIndex(index); + break; + } + } + + this.isBoldCheck .setSelected(currentFont.isBold()); + this.isItalicCheck.setSelected(currentFont.isItalic()); + + this.useTextAntiAliaseCheck + .setSelected(currentContext.isAntiAliased()); + this.useFractionalCheck + .setSelected(currentContext.usesFractionalMetrics()); + + this.decodeName.setText(FontUtils.getFontDecodeName(currentFont)); + this.decodeName.setCaretPosition(0); + + Rectangle2D r2d = currentFont.getMaxCharBounds(currentContext); + Rectangle rect = r2d.getBounds(); + String boundInfo = "最大文字寸法 : " + + rect.width + + " pixel幅 × " + + rect.height + + " pixel高"; + this.maxBounds.setText(boundInfo); + + this.maskListener = false; + + return; + } + + /** + * {@inheritDoc} + * ダイアログの表示・非表示。 + * ダイアログが閉じられるまで制御を返さない。 + * @param isVisible trueなら表示 {@inheritDoc} + */ + @Override + public void setVisible(boolean isVisible){ + if(isVisible){ + updateControlls(); + updatePreview(); + } + this.lastFontInfo = this.fontInfo; + + super.setVisible(isVisible); + + return; + } + + /** + * {@inheritDoc} + * チェックボックス操作のリスナ。 + * @param event 操作イベント {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent event){ + if(this.maskListener) return; + + Object source = event.getSource(); + + if( source != this.isBoldCheck + && source != this.isItalicCheck + && source != this.useTextAntiAliaseCheck + && source != this.useFractionalCheck ){ + return; + } + + int style = 0 | Font.PLAIN; + if(this.isBoldCheck.isSelected()){ + style = style | Font.BOLD; + } + if(this.isItalicCheck.isSelected()){ + style = style | Font.ITALIC; + } + Font newFont = getSelectedFont(); + if(newFont.getStyle() != style){ + newFont = newFont.deriveFont(style); + } + + AffineTransform tx = getFontRenderContext().getTransform(); + boolean isAntiAliases = this.useTextAntiAliaseCheck.isSelected(); + boolean useFractional = this.useFractionalCheck .isSelected(); + FontRenderContext newContext = + new FontRenderContext(tx, isAntiAliases, useFractional); + + FontInfo newInfo = new FontInfo(newFont, newContext); + setFontInfo(newInfo); + + return; + } + + /** + * フォントサイズ変更処理。 + */ + private void actionFontSizeSelected(){ + Object selected = this.sizeSelector.getSelectedItem(); + if(selected == null) return; + + Integer selectedInteger; + if(selected instanceof Integer){ + selectedInteger = (Integer) selected; + }else{ + try{ + selectedInteger = Integer.valueOf(selected.toString()); + }catch(NumberFormatException e){ + selectedInteger = Integer.valueOf( + this.lastFontInfo.getFont().getSize() + ); + } + } + + if(selectedInteger.intValue() <= 0){ + selectedInteger = + Integer.valueOf(this.lastFontInfo.getFont().getSize()); + } + + float fontSize = selectedInteger.floatValue(); + Font newFont = getSelectedFont().deriveFont(fontSize); + FontInfo newInfo = this.fontInfo.deriveFont(newFont); + setFontInfo(newInfo); + + int sizeItems = this.sizeSelector.getItemCount(); + for(int index = 0; index <= sizeItems - 1; index++){ + Object sizeItem = this.sizeSelector.getItemAt(index); + if(sizeItem.equals(selectedInteger)){ + this.sizeSelector.setSelectedIndex(index); + break; + } + } + + updateControlls(); + updatePreview(); + + return; + } + + /** + * {@inheritDoc} + * ボタン操作及びフォントサイズ指定コンボボックス操作のリスナ。 + * @param event 操作イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + if(this.maskListener) return; + + String cmd = event.getActionCommand(); + if(cmd.equals(ActionManager.CMD_FONTSIZESEL)){ + actionFontSizeSelected(); + } + + Object source = event.getSource(); + if(source == this.resetDefault){ + setFontInfo(FontInfo.DEFAULT_FONTINFO); + } + + return; + } + + /** + * {@inheritDoc} + * フォントファミリリスト選択操作のリスナ。 + * @param event 操作イベント {@inheritDoc} + */ + @Override + public void valueChanged(ListSelectionEvent event){ + if(this.maskListener) return; + + if(event.getSource() != this.familySelector) return; + if(event.getValueIsAdjusting()) return; + + Object selected = this.familySelector.getSelectedValue(); + if(selected == null) return; + + String familyName = selected.toString(); + Font currentFont = getSelectedFont(); + int style = currentFont.getStyle(); + int size = currentFont.getSize(); + + Font newFont = new Font(familyName, style, size); + FontInfo newInfo = this.fontInfo.deriveFont(newFont); + + setFontInfo(newInfo); + + return; + } + + /** + * フォントプレビュー画面用コンポーネント。 + */ + private static class FontPreview extends JComponent{ + + private static final int MARGIN = 5; + + private final GlyphDraw draw; + + private FontInfo fontInfo; + + /** + * コンストラクタ。 + * @param source 文字列 + * @param fontInfo フォント設定 + */ + public FontPreview(CharSequence source, + FontInfo fontInfo ){ + super(); + + this.fontInfo = fontInfo; + this.draw = new GlyphDraw(source, this.fontInfo); + this.draw.setFontInfo(this.fontInfo); + + this.draw.setPos(MARGIN, MARGIN); + + this.draw.setColor(Color.BLACK); + setBackground(Color.WHITE); + + updateBounds(); + + return; + } + + /** + * サイズ更新。 + */ + private void updateBounds(){ + Rectangle bounds = this.draw.setWidth(Integer.MAX_VALUE); + Dimension dimension = new Dimension(bounds.width + MARGIN * 2, + bounds.height + MARGIN * 2 ); + setPreferredSize(dimension); + revalidate(); + repaint(); + return; + } + + /** + * フォント設定の変更。 + * @param newFontInfo フォント設定 + */ + public void setFontInfo(FontInfo newFontInfo){ + this.fontInfo = newFontInfo; + this.draw.setFontInfo(this.fontInfo); + + updateBounds(); + + return; + } + + /** + * {@inheritDoc} + * 文字列の描画。 + * @param g {@inheritDoc} + */ + @Override + public void paintComponent(Graphics g){ + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + this.draw.paint(g2d); + return; + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FontInfo.java b/src/main/java/jp/sourceforge/jindolf/FontInfo.java index c3ad341..a93c67b 100644 --- a/src/main/java/jp/sourceforge/jindolf/FontInfo.java +++ b/src/main/java/jp/sourceforge/jindolf/FontInfo.java @@ -1,263 +1,263 @@ -/* - * font information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Font; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphVector; -import java.awt.geom.AffineTransform; -import java.text.CharacterIterator; -import jp.sourceforge.jindolf.json.JsBoolean; -import jp.sourceforge.jindolf.json.JsNumber; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsPair; -import jp.sourceforge.jindolf.json.JsString; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * フォント描画に関する各種設定。 - */ -public class FontInfo{ - - /** デフォルトのフォント設定。 */ - public static final FontInfo DEFAULT_FONTINFO = new FontInfo(); - - private static final String HASH_FAMILY = "family"; - private static final String HASH_SIZE = "size"; - private static final String HASH_ISBOLD = "isBold"; - private static final String HASH_ISITALIC = "isItalic"; - private static final String HASH_USEAA = "useAntiAlias"; - private static final String HASH_FRACTIONAL = "useFractional"; - - - private Font font; - private FontRenderContext context; - - - /** - * コンストラクタ。 - * デフォルトフォントとそれに適した描画属性が指定される。 - */ - public FontInfo(){ - this(FontUtils.createDefaultSpeechFont()); - return; - } - - /** - * コンストラクタ。 - * 描画設定はフォント属性に応じて自動的に調整される。 - * @param font フォント - * @throws NullPointerException 引数がnull - */ - public FontInfo(Font font) - throws NullPointerException{ - this(font, createBestContext(font)); - return; - } - - /** - * コンストラクタ。 - * @param font フォント - * @param context 描画設定 - * @throws NullPointerException 引数がnull - */ - public FontInfo(Font font, FontRenderContext context) - throws NullPointerException{ - super(); - if(font == null || context == null) throw new NullPointerException(); - this.font = font; - this.context = context; - return; - } - - - /** - * フォントに応じた最適な描画設定を生成する。 - * @param font フォント - * @return 描画設定 - */ - public static FontRenderContext createBestContext(Font font){ - FontRenderContext result; - - AffineTransform identity = ImtblAffineTx.IDENTITY; - if(FontUtils.guessBitmapFont(font)){ - result = new FontRenderContext(identity, false, false); - }else{ - result = new FontRenderContext(identity, true, true); - } - - return result; - } - - /** - * フォント設定をJSON形式にエンコードする。 - * @param fontInfo フォント設定 - * @return JSON Object - */ - public static JsObject buildJson(FontInfo fontInfo){ - Font font = fontInfo.getFont(); - FontRenderContext frc = fontInfo.getFontRenderContext(); - JsPair type = new JsPair(HASH_FAMILY, - FontUtils.getRootFamilyName(font) ); - JsPair size = new JsPair(HASH_SIZE, font.getSize()); - JsPair bold = new JsPair(HASH_ISBOLD, font.isBold()); - JsPair italic = new JsPair(HASH_ISITALIC, font.isItalic()); - JsPair host = new JsPair(HASH_USEAA, frc.isAntiAliased()); - JsPair port = - new JsPair(HASH_FRACTIONAL, frc.usesFractionalMetrics()); - - JsObject result = new JsObject(); - result.putPair(type); - result.putPair(size); - result.putPair(bold); - result.putPair(italic); - result.putPair(host); - result.putPair(port); - - return result; - } - - /** - * JSONからのフォント設定復元。 - * @param obj JSON Object - * @return フォント設定 - */ - public static FontInfo decodeJson(JsObject obj){ - JsValue value; - - Font newFont = FontUtils.createDefaultSpeechFont(); - FontRenderContext newFrc = createBestContext(newFont); - int style = newFont.getStyle(); - - value = obj.getValue(HASH_FAMILY); - if(value instanceof JsString){ - JsString string = (JsString) value; - Font decoded = Font.decode(string.toRawString()); - if(decoded != null){ - newFont = decoded; - } - } - - int size = newFont.getSize(); - value = obj.getValue(HASH_SIZE); - if(value instanceof JsNumber){ - JsNumber number = (JsNumber) value; - size = number.intValue(); - } - - boolean isBold = newFont.isBold(); - value = obj.getValue(HASH_ISBOLD); - if(value instanceof JsBoolean){ - JsBoolean bool = (JsBoolean) value; - isBold = bool.booleanValue(); - } - if(isBold) style |= Font.BOLD; - - boolean isItalic = newFont.isItalic(); - value = obj.getValue(HASH_ISITALIC); - if(value instanceof JsBoolean){ - JsBoolean bool = (JsBoolean) value; - isItalic = bool.booleanValue(); - } - if(isItalic) style |= Font.ITALIC; - - boolean isAntiAlias = newFrc.isAntiAliased(); - value = obj.getValue(HASH_USEAA); - if(value instanceof JsBoolean){ - JsBoolean bool = (JsBoolean) value; - isAntiAlias = bool.booleanValue(); - } - - boolean useFractional = newFrc.usesFractionalMetrics(); - value = obj.getValue(HASH_FRACTIONAL); - if(value instanceof JsBoolean){ - JsBoolean bool = (JsBoolean) value; - useFractional = bool.booleanValue(); - } - - newFont = newFont.deriveFont(style, (float)size); - - newFrc = new FontRenderContext(ImtblAffineTx.IDENTITY, - isAntiAlias, useFractional); - - FontInfo result = new FontInfo(newFont, newFrc); - - return result; - } - - /** - * フォントを返す。 - * @return フォント - */ - public Font getFont(){ - return this.font; - } - - /** - * 描画属性を返す。 - * @return 描画属性 - */ - public FontRenderContext getFontRenderContext(){ - return this.context; - } - - /** - * フォントのみ異なる設定を派生させる。 - * @param newFont 新フォント - * @return 新設定 - */ - public FontInfo deriveFont(Font newFont){ - return new FontInfo(newFont, this.context); - } - - /** - * 描画属性のみ異なる設定を派生させる。 - * @param newContext 新描画設定 - * @return 新設定 - */ - public FontInfo deriveRenderContext(FontRenderContext newContext){ - return new FontInfo(this.font, newContext); - } - - /** - * 文字列からグリフ集合を生成する。 - * @param iterator 文字列 - * @return グリフ集合 - */ - public GlyphVector createGlyphVector(CharacterIterator iterator){ - GlyphVector glyph = - this.font.createGlyphVector(this.context, iterator); - return glyph; - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if( ! (obj instanceof FontInfo) ) return false; - FontInfo target = (FontInfo) obj; - - if( ! (this.font .equals(target.font)) ) return false; - if( ! (this.context.equals(target.context)) ) return false; - - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.font.hashCode() ^ this.context.hashCode(); - } - -} +/* + * font information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.text.CharacterIterator; +import jp.sourceforge.jindolf.json.JsBoolean; +import jp.sourceforge.jindolf.json.JsNumber; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsPair; +import jp.sourceforge.jindolf.json.JsString; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * フォント描画に関する各種設定。 + */ +public class FontInfo{ + + /** デフォルトのフォント設定。 */ + public static final FontInfo DEFAULT_FONTINFO = new FontInfo(); + + private static final String HASH_FAMILY = "family"; + private static final String HASH_SIZE = "size"; + private static final String HASH_ISBOLD = "isBold"; + private static final String HASH_ISITALIC = "isItalic"; + private static final String HASH_USEAA = "useAntiAlias"; + private static final String HASH_FRACTIONAL = "useFractional"; + + + private Font font; + private FontRenderContext context; + + + /** + * コンストラクタ。 + * デフォルトフォントとそれに適した描画属性が指定される。 + */ + public FontInfo(){ + this(FontUtils.createDefaultSpeechFont()); + return; + } + + /** + * コンストラクタ。 + * 描画設定はフォント属性に応じて自動的に調整される。 + * @param font フォント + * @throws NullPointerException 引数がnull + */ + public FontInfo(Font font) + throws NullPointerException{ + this(font, createBestContext(font)); + return; + } + + /** + * コンストラクタ。 + * @param font フォント + * @param context 描画設定 + * @throws NullPointerException 引数がnull + */ + public FontInfo(Font font, FontRenderContext context) + throws NullPointerException{ + super(); + if(font == null || context == null) throw new NullPointerException(); + this.font = font; + this.context = context; + return; + } + + + /** + * フォントに応じた最適な描画設定を生成する。 + * @param font フォント + * @return 描画設定 + */ + public static FontRenderContext createBestContext(Font font){ + FontRenderContext result; + + AffineTransform identity = ImtblAffineTx.IDENTITY; + if(FontUtils.guessBitmapFont(font)){ + result = new FontRenderContext(identity, false, false); + }else{ + result = new FontRenderContext(identity, true, true); + } + + return result; + } + + /** + * フォント設定をJSON形式にエンコードする。 + * @param fontInfo フォント設定 + * @return JSON Object + */ + public static JsObject buildJson(FontInfo fontInfo){ + Font font = fontInfo.getFont(); + FontRenderContext frc = fontInfo.getFontRenderContext(); + JsPair type = new JsPair(HASH_FAMILY, + FontUtils.getRootFamilyName(font) ); + JsPair size = new JsPair(HASH_SIZE, font.getSize()); + JsPair bold = new JsPair(HASH_ISBOLD, font.isBold()); + JsPair italic = new JsPair(HASH_ISITALIC, font.isItalic()); + JsPair host = new JsPair(HASH_USEAA, frc.isAntiAliased()); + JsPair port = + new JsPair(HASH_FRACTIONAL, frc.usesFractionalMetrics()); + + JsObject result = new JsObject(); + result.putPair(type); + result.putPair(size); + result.putPair(bold); + result.putPair(italic); + result.putPair(host); + result.putPair(port); + + return result; + } + + /** + * JSONからのフォント設定復元。 + * @param obj JSON Object + * @return フォント設定 + */ + public static FontInfo decodeJson(JsObject obj){ + JsValue value; + + Font newFont = FontUtils.createDefaultSpeechFont(); + FontRenderContext newFrc = createBestContext(newFont); + int style = newFont.getStyle(); + + value = obj.getValue(HASH_FAMILY); + if(value instanceof JsString){ + JsString string = (JsString) value; + Font decoded = Font.decode(string.toRawString()); + if(decoded != null){ + newFont = decoded; + } + } + + int size = newFont.getSize(); + value = obj.getValue(HASH_SIZE); + if(value instanceof JsNumber){ + JsNumber number = (JsNumber) value; + size = number.intValue(); + } + + boolean isBold = newFont.isBold(); + value = obj.getValue(HASH_ISBOLD); + if(value instanceof JsBoolean){ + JsBoolean bool = (JsBoolean) value; + isBold = bool.booleanValue(); + } + if(isBold) style |= Font.BOLD; + + boolean isItalic = newFont.isItalic(); + value = obj.getValue(HASH_ISITALIC); + if(value instanceof JsBoolean){ + JsBoolean bool = (JsBoolean) value; + isItalic = bool.booleanValue(); + } + if(isItalic) style |= Font.ITALIC; + + boolean isAntiAlias = newFrc.isAntiAliased(); + value = obj.getValue(HASH_USEAA); + if(value instanceof JsBoolean){ + JsBoolean bool = (JsBoolean) value; + isAntiAlias = bool.booleanValue(); + } + + boolean useFractional = newFrc.usesFractionalMetrics(); + value = obj.getValue(HASH_FRACTIONAL); + if(value instanceof JsBoolean){ + JsBoolean bool = (JsBoolean) value; + useFractional = bool.booleanValue(); + } + + newFont = newFont.deriveFont(style, (float)size); + + newFrc = new FontRenderContext(ImtblAffineTx.IDENTITY, + isAntiAlias, useFractional); + + FontInfo result = new FontInfo(newFont, newFrc); + + return result; + } + + /** + * フォントを返す。 + * @return フォント + */ + public Font getFont(){ + return this.font; + } + + /** + * 描画属性を返す。 + * @return 描画属性 + */ + public FontRenderContext getFontRenderContext(){ + return this.context; + } + + /** + * フォントのみ異なる設定を派生させる。 + * @param newFont 新フォント + * @return 新設定 + */ + public FontInfo deriveFont(Font newFont){ + return new FontInfo(newFont, this.context); + } + + /** + * 描画属性のみ異なる設定を派生させる。 + * @param newContext 新描画設定 + * @return 新設定 + */ + public FontInfo deriveRenderContext(FontRenderContext newContext){ + return new FontInfo(this.font, newContext); + } + + /** + * 文字列からグリフ集合を生成する。 + * @param iterator 文字列 + * @return グリフ集合 + */ + public GlyphVector createGlyphVector(CharacterIterator iterator){ + GlyphVector glyph = + this.font.createGlyphVector(this.context, iterator); + return glyph; + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if( ! (obj instanceof FontInfo) ) return false; + FontInfo target = (FontInfo) obj; + + if( ! (this.font .equals(target.font)) ) return false; + if( ! (this.context.equals(target.context)) ) return false; + + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.font.hashCode() ^ this.context.hashCode(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/FontUtils.java b/src/main/java/jp/sourceforge/jindolf/FontUtils.java index 0f337b3..5f9c8fc 100644 --- a/src/main/java/jp/sourceforge/jindolf/FontUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/FontUtils.java @@ -1,160 +1,160 @@ -/* - * font utilities - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Font; -import java.awt.GraphicsEnvironment; -import java.util.Collections; -import java.util.Locale; -import java.util.SortedSet; -import java.util.TreeSet; - -/** - * フォントユーティリティ。 - */ -public final class FontUtils{ - - /** Font.DIALOG代替品。 */ - public static final String FAMILY_DIALOG = "Dialog"; - /** Locale.ROOT代替品。 */ - private static final Locale ROOT = new Locale("", "", ""); - - private static final String[] INIT_FAMILY_NAMES = { - "Hiragino Kaku Gothic Pro", // for MacOS X - "Hiragino Kaku Gothic Std", - "Osaka", - "MS PGothic", // for WinXP - "MS Gothic", - // TODO X11用のおすすめは? - }; - - /** JIS0208:1990 チェック用。 */ - private static final String JPCHECK_CODE = "9Aあゑアアヴヰ┼ЖΩ峠凜熙"; - - - /** - * 隠れコンストラクタ。 - */ - private FontUtils(){ - assert false; - } - - - /** - * システムに存在する有効なファミリ名か判定する。 - * @param family フォントファミリ名。 - * @return 存在する有効なファミリ名ならtrue - */ - public static boolean isValidFamilyName(String family){ - int style = 0x00 | Font.PLAIN; - int size = 1; - Font dummyFont = new Font(family, style, size); - - String dummyFamily = getRootFamilyName(dummyFont); - String dummyLocalFamily = dummyFont.getFamily(); - if(dummyFamily .equals(family)) return true; - if(dummyLocalFamily.equals(family)) return true; - - return false; - } - - /** - * 発言用のデフォルトフォントを生成する。 - * 適当なファミリが見つからなかったら"Dialog"が選択される。 - * @return デフォルトフォント - */ - public static Font createDefaultSpeechFont(){ - String defaultFamilyName = FAMILY_DIALOG; - for(String familyName : INIT_FAMILY_NAMES){ - if(isValidFamilyName(familyName)){ - defaultFamilyName = familyName; - break; - } - } - - int style = 0x00 | Font.PLAIN; - int size = 16; - Font result = new Font(defaultFamilyName, style, size); - - return result; - } - - /** - * ソートされたフォントファミリ一覧表を生成する。 - * JISX0208:1990相当が表示できないファミリは弾かれる。 - * 結構実行時間がかかるかも。乱用禁物。 - * @return フォント一覧 - */ - public static SortedSet createFontSet(){ - GraphicsEnvironment ge = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - - SortedSet result = new TreeSet(); - for(Font font : ge.getAllFonts()){ - if(font.canDisplayUpTo(JPCHECK_CODE) >= 0) continue; - String familyName = font.getFamily(); - result.add(familyName.intern()); - } - - return Collections.unmodifiableSortedSet(result); - } - - /** - * ビットマップフォントか否か見当をつける。 - * ビットマップフォントにはアンチエイリアスやサブピクセルを使わないほうが - * 見栄えがいいような気がする。 - * @param font 判定対象フォント - * @return ビットマップフォントらしかったらtrue - */ - public static boolean guessBitmapFont(Font font){ - String familyName = getRootFamilyName(font); - if( font.getSize() < 24 - && familyName.startsWith("MS") - && ( familyName.contains("Gothic") - || familyName.contains("Mincho") ) ){ - return true; - } - return false; - } - - /** - * Font#decode()用の名前を返す。 - * @param font フォント - * @return Font#decode()用の名前 - */ - public static String getFontDecodeName(Font font){ - StringBuilder result = new StringBuilder(); - - StringBuilder style = new StringBuilder(); - if(font.isBold()) style.append("BOLD"); - if(font.isItalic()) style.append("ITALIC"); - if(style.length() <= 0) style.append("PLAIN"); - - result.append(getRootFamilyName(font)); - result.append('-').append(style); - result.append('-').append(font.getSize()); - - if( result.indexOf("\u0020") >= 0 - || result.indexOf("\u3000") >= 0 ){ - result.insert(0, '"').append('"'); - } - - return result.toString(); - } - - /** - * ロケール中立なフォントファミリ名を返す。 - * JRE1.5対策 - * @param font フォント - * @return ファミリ名 - */ - public static String getRootFamilyName(Font font){ - return font.getFamily(ROOT); - } - -} +/* + * font utilities + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.util.Collections; +import java.util.Locale; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * フォントユーティリティ。 + */ +public final class FontUtils{ + + /** Font.DIALOG代替品。 */ + public static final String FAMILY_DIALOG = "Dialog"; + /** Locale.ROOT代替品。 */ + private static final Locale ROOT = new Locale("", "", ""); + + private static final String[] INIT_FAMILY_NAMES = { + "Hiragino Kaku Gothic Pro", // for MacOS X + "Hiragino Kaku Gothic Std", + "Osaka", + "MS PGothic", // for WinXP + "MS Gothic", + // TODO X11用のおすすめは? + }; + + /** JIS0208:1990 チェック用。 */ + private static final String JPCHECK_CODE = "9Aあゑアアヴヰ┼ЖΩ峠凜熙"; + + + /** + * 隠れコンストラクタ。 + */ + private FontUtils(){ + assert false; + } + + + /** + * システムに存在する有効なファミリ名か判定する。 + * @param family フォントファミリ名。 + * @return 存在する有効なファミリ名ならtrue + */ + public static boolean isValidFamilyName(String family){ + int style = 0x00 | Font.PLAIN; + int size = 1; + Font dummyFont = new Font(family, style, size); + + String dummyFamily = getRootFamilyName(dummyFont); + String dummyLocalFamily = dummyFont.getFamily(); + if(dummyFamily .equals(family)) return true; + if(dummyLocalFamily.equals(family)) return true; + + return false; + } + + /** + * 発言用のデフォルトフォントを生成する。 + * 適当なファミリが見つからなかったら"Dialog"が選択される。 + * @return デフォルトフォント + */ + public static Font createDefaultSpeechFont(){ + String defaultFamilyName = FAMILY_DIALOG; + for(String familyName : INIT_FAMILY_NAMES){ + if(isValidFamilyName(familyName)){ + defaultFamilyName = familyName; + break; + } + } + + int style = 0x00 | Font.PLAIN; + int size = 16; + Font result = new Font(defaultFamilyName, style, size); + + return result; + } + + /** + * ソートされたフォントファミリ一覧表を生成する。 + * JISX0208:1990相当が表示できないファミリは弾かれる。 + * 結構実行時間がかかるかも。乱用禁物。 + * @return フォント一覧 + */ + public static SortedSet createFontSet(){ + GraphicsEnvironment ge = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + + SortedSet result = new TreeSet(); + for(Font font : ge.getAllFonts()){ + if(font.canDisplayUpTo(JPCHECK_CODE) >= 0) continue; + String familyName = font.getFamily(); + result.add(familyName.intern()); + } + + return Collections.unmodifiableSortedSet(result); + } + + /** + * ビットマップフォントか否か見当をつける。 + * ビットマップフォントにはアンチエイリアスやサブピクセルを使わないほうが + * 見栄えがいいような気がする。 + * @param font 判定対象フォント + * @return ビットマップフォントらしかったらtrue + */ + public static boolean guessBitmapFont(Font font){ + String familyName = getRootFamilyName(font); + if( font.getSize() < 24 + && familyName.startsWith("MS") + && ( familyName.contains("Gothic") + || familyName.contains("Mincho") ) ){ + return true; + } + return false; + } + + /** + * Font#decode()用の名前を返す。 + * @param font フォント + * @return Font#decode()用の名前 + */ + public static String getFontDecodeName(Font font){ + StringBuilder result = new StringBuilder(); + + StringBuilder style = new StringBuilder(); + if(font.isBold()) style.append("BOLD"); + if(font.isItalic()) style.append("ITALIC"); + if(style.length() <= 0) style.append("PLAIN"); + + result.append(getRootFamilyName(font)); + result.append('-').append(style); + result.append('-').append(font.getSize()); + + if( result.indexOf("\u0020") >= 0 + || result.indexOf("\u3000") >= 0 ){ + result.insert(0, '"').append('"'); + } + + return result.toString(); + } + + /** + * ロケール中立なフォントファミリ名を返す。 + * JRE1.5対策 + * @param font フォント + * @return ファミリ名 + */ + public static String getRootFamilyName(Font font){ + return font.getFamily(ROOT); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/GUIUtils.java b/src/main/java/jp/sourceforge/jindolf/GUIUtils.java index 687b6ba..398b725 100644 --- a/src/main/java/jp/sourceforge/jindolf/GUIUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/GUIUtils.java @@ -1,399 +1,399 @@ -/* - * GUI utilities - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.AWTEvent; -import java.awt.Component; -import java.awt.Dialog; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Toolkit; -import java.awt.Window; -import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ColorConvertOp; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import javax.imageio.ImageIO; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JComponent; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.border.Border; - -/** - * GUI関連のユーティリティクラス。 - */ -public final class GUIUtils{ - - private static final String RES_LOGOICON = - "resources/image/logo.png"; - private static final String RES_WINDOWICON = - "resources/image/winicon.png"; - private static final String RES_WWWICON = - "resources/image/www.png"; - private static final String RES_NOIMAGE = - "resources/image/noimage.png"; - private static BufferedImage logoImage; - private static Icon logoIcon; - private static BufferedImage windowIconImage; - private static Icon wwwIcon; - private static BufferedImage noImage; - - private static final RenderingHints HINTS_QUALITY; - private static final RenderingHints HINTS_SPEEDY; - - private static final BufferedImageOp OP_MONOIMG; - - private static final Runnable TASK_NOTHING = new Runnable(){ - /** 何もしない。 */ - public void run(){} - }; - - static{ - HINTS_QUALITY = new RenderingHints(null); - HINTS_SPEEDY = new RenderingHints(null); - - HINTS_QUALITY.put(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - HINTS_SPEEDY.put(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_OFF); - - HINTS_QUALITY.put(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY); - HINTS_SPEEDY.put(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_SPEED); - - HINTS_QUALITY.put(RenderingHints.KEY_DITHERING, - RenderingHints.VALUE_DITHER_ENABLE); - HINTS_SPEEDY.put(RenderingHints.KEY_DITHERING, - RenderingHints.VALUE_DITHER_DISABLE); - - HINTS_QUALITY.put(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - HINTS_SPEEDY.put(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); - - HINTS_QUALITY.put(RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_ON); - HINTS_SPEEDY.put(RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_OFF); - - HINTS_QUALITY.put(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_BICUBIC); - HINTS_SPEEDY.put(RenderingHints.KEY_INTERPOLATION, - RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - - HINTS_QUALITY.put(RenderingHints.KEY_ALPHA_INTERPOLATION, - RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - HINTS_SPEEDY.put(RenderingHints.KEY_ALPHA_INTERPOLATION, - RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); - - HINTS_QUALITY.put(RenderingHints.KEY_COLOR_RENDERING, - RenderingHints.VALUE_COLOR_RENDER_QUALITY); - HINTS_SPEEDY.put(RenderingHints.KEY_COLOR_RENDERING, - RenderingHints.VALUE_COLOR_RENDER_SPEED); - - HINTS_QUALITY.put(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_PURE); - HINTS_SPEEDY.put(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_NORMALIZE); - } - - static{ - ColorSpace mono = ColorSpace.getInstance(ColorSpace.CS_GRAY); - OP_MONOIMG = new ColorConvertOp(mono, null); - } - - - /** - * 隠れコンストラクタ。 - */ - private GUIUtils(){ - assert false; - throw new AssertionError(); - } - - - /** - * 描画品質優先の描画ヒントを返す。 - * @return 描画ヒント - */ - public static RenderingHints getQualityHints(){ - return HINTS_QUALITY; - } - - /** - * リソース名からイメージを取得する。 - * @param resource リソース名 - * @return イメージ - * @throws java.io.IOException 入力エラー - */ - public static BufferedImage loadImageFromResource(String resource) - throws IOException{ - BufferedImage result; - - URL url = Jindolf.getResource(resource); - result = ImageIO.read(url); - - return result; - } - - /** - * ロゴイメージを得る。 - * @return ロゴイメージ - */ - public static BufferedImage getLogoImage(){ - if(logoImage != null){ - return logoImage; - } - - BufferedImage image; - try{ - image = loadImageFromResource(RES_LOGOICON); - }catch(IOException e){ - Jindolf.logger().warn("ロゴイメージの取得に失敗しました", e); - image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); - // TODO デカく "狼" とでも描くか? - } - - logoImage = image; - - return logoImage; - } - - /** - * 各種ウィンドウのアイコンイメージを得る。 - * @return アイコンイメージ - */ - public static BufferedImage getWindowIconImage(){ - if(windowIconImage != null){ - return windowIconImage; - } - - BufferedImage image; - try{ - image = loadImageFromResource(RES_WINDOWICON); - }catch(IOException e){ - Jindolf.logger().warn("アイコンイメージの取得に失敗しました", e); - image = getLogoImage(); - } - - windowIconImage = image; - - return windowIconImage; - } - - /** - * ロゴアイコンを得る。 - * @return ロゴアイコン - */ - public static Icon getLogoIcon(){ - if(logoIcon != null){ - return logoIcon; - } - - Icon icon = new ImageIcon(getLogoImage()); - - logoIcon = icon; - - return logoIcon; - } - - /** - * WWWアイコンを得る。 - * @return WWWアイコン - */ - public static Icon getWWWIcon(){ - if(wwwIcon != null){ - return wwwIcon; - } - - URL url = Jindolf.getResource(RES_WWWICON); - wwwIcon = new ImageIcon(url); - - return wwwIcon; - } - - /** - * NoImageイメージを得る。 - * @return NoImageイメージ - */ - public static BufferedImage getNoImage(){ - if(noImage != null){ - return noImage; - } - - URL url = Jindolf.getResource(RES_NOIMAGE); - try{ - noImage = ImageIO.read(url); - }catch(IOException e){ - assert false; - noImage = getLogoImage(); - } - - return noImage; - } - - /** - * AWTディスパッチイベント処理を促す。 - */ - public static void dispatchEmptyAWTEvent(){ - if(SwingUtilities.isEventDispatchThread()){ - return; - } - - try{ - SwingUtilities.invokeAndWait(TASK_NOTHING); - }catch(InterruptedException e){ - // IGNORE - }catch(InvocationTargetException e){ - // IGNORE - } - - return; - } - - /** - * 矩形と点座標の相対関係を判定する。 - * ・矩形に点座標が含まれればSwingContants.CENTER - * ・矩形の上辺より上に点座標が位置すればSwingContants.NORTH - * ・矩形の下辺より下に点座標が位置すればSwingContants.SOUTH - * ・矩形の上辺と下辺内に収まるが右辺からはみ出すときはSwingContants.EAST - * ・矩形の上辺と下辺内に収まるが左辺からはみ出すときはSwingContants.WEST - * @param rect 矩形 - * @param pt 点座標 - * @return 判定結果 - */ - public static int getDirection(Rectangle rect, Point pt){ - if(pt.y < rect.y){ - return SwingConstants.NORTH; - } - if(rect.y + rect.height <= pt.y){ - return SwingConstants.SOUTH; - } - if(pt.x < rect.x){ - return SwingConstants.EAST; - } - if(rect.x + rect.width <= pt.x){ - return SwingConstants.WEST; - } - return SwingConstants.CENTER; - } - - /** - * ウィンドウ属性を設定する。 - * @param window ウィンドウ - * @param isResizable リサイズ可ならtrue - * @param isDynamic リサイズに伴う再描画ならtrue - * @param isAutoLocation 自動位置決め機構を使うならtrue - */ - public static void modifyWindowAttributes(Window window, - boolean isResizable, - boolean isDynamic, - boolean isAutoLocation){ - Toolkit kit = window.getToolkit(); - kit.setDynamicLayout(isDynamic); - - window.setLocationByPlatform(isAutoLocation); - - if(window instanceof Frame){ - Frame frame = (Frame) window; - frame.setIconImage(getWindowIconImage()); - frame.setResizable(isResizable); - }else if(window instanceof Dialog){ - Dialog dialog = (Dialog) window; - dialog.setResizable(isResizable); - } - - return; - } - - /** - * コンポーネントの既存ボーダー内側にマージンをもうける。 - * @param comp 対象コンポーネント - * @param top 上マージン - * @param left 左マージン - * @param bottom 下マージン - * @param right 右マージン - */ - public static void addMargin(Component comp, - int top, int left, int bottom, int right){ - if( ! (comp instanceof JComponent) ) return; - JComponent jcomp = (JComponent) comp; - - Border outer = jcomp.getBorder(); - Border inner = - BorderFactory.createEmptyBorder(top, left, bottom, right); - - Border border; - if(outer == null){ - border = inner; - }else{ - border = BorderFactory.createCompoundBorder(outer, inner); - } - - jcomp.setBorder(border); - - return; - } - - /** - * 独自ロガーにエラーや例外を吐く、 - * カスタム化されたイベントキューに差し替える。 - */ - public static void replaceEventQueue(){ - Toolkit kit = Toolkit.getDefaultToolkit(); - EventQueue oldQueue = kit.getSystemEventQueue(); - EventQueue newQueue = new EventQueue(){ - private static final String FATALMSG = - "イベントディスパッチ中に異常が起きました。"; - @Override - protected void dispatchEvent(AWTEvent event){ - try{ - super.dispatchEvent(event); - }catch(RuntimeException e){ - Jindolf.logger().fatal(FATALMSG, e); - throw e; - }catch(Exception e){ - Jindolf.logger().fatal(FATALMSG, e); - }catch(Error e){ - Jindolf.logger().fatal(FATALMSG, e); - throw e; - } - // TODO Toolkit#beep()もするべきか - // TODO モーダルダイアログを出すべきか - // TODO 標準エラー出力抑止オプションを用意すべきか - // TODO セキュリティバイパス - return; - } - }; - oldQueue.push(newQueue); - return; - } - - /** - * 任意のイメージを多階調モノクロ化する。 - * 寸法は変わらない。 - * @param image イメージ - * @return モノクロ化イメージ - */ - public static BufferedImage createMonoImage(BufferedImage image){ - BufferedImage result; - result = OP_MONOIMG.filter(image, null); - return result; - } - -} +/* + * GUI utilities + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Dialog; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorConvertOp; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; + +/** + * GUI関連のユーティリティクラス。 + */ +public final class GUIUtils{ + + private static final String RES_LOGOICON = + "resources/image/logo.png"; + private static final String RES_WINDOWICON = + "resources/image/winicon.png"; + private static final String RES_WWWICON = + "resources/image/www.png"; + private static final String RES_NOIMAGE = + "resources/image/noimage.png"; + private static BufferedImage logoImage; + private static Icon logoIcon; + private static BufferedImage windowIconImage; + private static Icon wwwIcon; + private static BufferedImage noImage; + + private static final RenderingHints HINTS_QUALITY; + private static final RenderingHints HINTS_SPEEDY; + + private static final BufferedImageOp OP_MONOIMG; + + private static final Runnable TASK_NOTHING = new Runnable(){ + /** 何もしない。 */ + public void run(){} + }; + + static{ + HINTS_QUALITY = new RenderingHints(null); + HINTS_SPEEDY = new RenderingHints(null); + + HINTS_QUALITY.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + HINTS_SPEEDY.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + + HINTS_QUALITY.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + HINTS_SPEEDY.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_SPEED); + + HINTS_QUALITY.put(RenderingHints.KEY_DITHERING, + RenderingHints.VALUE_DITHER_ENABLE); + HINTS_SPEEDY.put(RenderingHints.KEY_DITHERING, + RenderingHints.VALUE_DITHER_DISABLE); + + HINTS_QUALITY.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + HINTS_SPEEDY.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + + HINTS_QUALITY.put(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + HINTS_SPEEDY.put(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + + HINTS_QUALITY.put(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BICUBIC); + HINTS_SPEEDY.put(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + + HINTS_QUALITY.put(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + HINTS_SPEEDY.put(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); + + HINTS_QUALITY.put(RenderingHints.KEY_COLOR_RENDERING, + RenderingHints.VALUE_COLOR_RENDER_QUALITY); + HINTS_SPEEDY.put(RenderingHints.KEY_COLOR_RENDERING, + RenderingHints.VALUE_COLOR_RENDER_SPEED); + + HINTS_QUALITY.put(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_PURE); + HINTS_SPEEDY.put(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_NORMALIZE); + } + + static{ + ColorSpace mono = ColorSpace.getInstance(ColorSpace.CS_GRAY); + OP_MONOIMG = new ColorConvertOp(mono, null); + } + + + /** + * 隠れコンストラクタ。 + */ + private GUIUtils(){ + assert false; + throw new AssertionError(); + } + + + /** + * 描画品質優先の描画ヒントを返す。 + * @return 描画ヒント + */ + public static RenderingHints getQualityHints(){ + return HINTS_QUALITY; + } + + /** + * リソース名からイメージを取得する。 + * @param resource リソース名 + * @return イメージ + * @throws java.io.IOException 入力エラー + */ + public static BufferedImage loadImageFromResource(String resource) + throws IOException{ + BufferedImage result; + + URL url = Jindolf.getResource(resource); + result = ImageIO.read(url); + + return result; + } + + /** + * ロゴイメージを得る。 + * @return ロゴイメージ + */ + public static BufferedImage getLogoImage(){ + if(logoImage != null){ + return logoImage; + } + + BufferedImage image; + try{ + image = loadImageFromResource(RES_LOGOICON); + }catch(IOException e){ + Jindolf.logger().warn("ロゴイメージの取得に失敗しました", e); + image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); + // TODO デカく "狼" とでも描くか? + } + + logoImage = image; + + return logoImage; + } + + /** + * 各種ウィンドウのアイコンイメージを得る。 + * @return アイコンイメージ + */ + public static BufferedImage getWindowIconImage(){ + if(windowIconImage != null){ + return windowIconImage; + } + + BufferedImage image; + try{ + image = loadImageFromResource(RES_WINDOWICON); + }catch(IOException e){ + Jindolf.logger().warn("アイコンイメージの取得に失敗しました", e); + image = getLogoImage(); + } + + windowIconImage = image; + + return windowIconImage; + } + + /** + * ロゴアイコンを得る。 + * @return ロゴアイコン + */ + public static Icon getLogoIcon(){ + if(logoIcon != null){ + return logoIcon; + } + + Icon icon = new ImageIcon(getLogoImage()); + + logoIcon = icon; + + return logoIcon; + } + + /** + * WWWアイコンを得る。 + * @return WWWアイコン + */ + public static Icon getWWWIcon(){ + if(wwwIcon != null){ + return wwwIcon; + } + + URL url = Jindolf.getResource(RES_WWWICON); + wwwIcon = new ImageIcon(url); + + return wwwIcon; + } + + /** + * NoImageイメージを得る。 + * @return NoImageイメージ + */ + public static BufferedImage getNoImage(){ + if(noImage != null){ + return noImage; + } + + URL url = Jindolf.getResource(RES_NOIMAGE); + try{ + noImage = ImageIO.read(url); + }catch(IOException e){ + assert false; + noImage = getLogoImage(); + } + + return noImage; + } + + /** + * AWTディスパッチイベント処理を促す。 + */ + public static void dispatchEmptyAWTEvent(){ + if(SwingUtilities.isEventDispatchThread()){ + return; + } + + try{ + SwingUtilities.invokeAndWait(TASK_NOTHING); + }catch(InterruptedException e){ + // IGNORE + }catch(InvocationTargetException e){ + // IGNORE + } + + return; + } + + /** + * 矩形と点座標の相対関係を判定する。 + * ・矩形に点座標が含まれればSwingContants.CENTER + * ・矩形の上辺より上に点座標が位置すればSwingContants.NORTH + * ・矩形の下辺より下に点座標が位置すればSwingContants.SOUTH + * ・矩形の上辺と下辺内に収まるが右辺からはみ出すときはSwingContants.EAST + * ・矩形の上辺と下辺内に収まるが左辺からはみ出すときはSwingContants.WEST + * @param rect 矩形 + * @param pt 点座標 + * @return 判定結果 + */ + public static int getDirection(Rectangle rect, Point pt){ + if(pt.y < rect.y){ + return SwingConstants.NORTH; + } + if(rect.y + rect.height <= pt.y){ + return SwingConstants.SOUTH; + } + if(pt.x < rect.x){ + return SwingConstants.EAST; + } + if(rect.x + rect.width <= pt.x){ + return SwingConstants.WEST; + } + return SwingConstants.CENTER; + } + + /** + * ウィンドウ属性を設定する。 + * @param window ウィンドウ + * @param isResizable リサイズ可ならtrue + * @param isDynamic リサイズに伴う再描画ならtrue + * @param isAutoLocation 自動位置決め機構を使うならtrue + */ + public static void modifyWindowAttributes(Window window, + boolean isResizable, + boolean isDynamic, + boolean isAutoLocation){ + Toolkit kit = window.getToolkit(); + kit.setDynamicLayout(isDynamic); + + window.setLocationByPlatform(isAutoLocation); + + if(window instanceof Frame){ + Frame frame = (Frame) window; + frame.setIconImage(getWindowIconImage()); + frame.setResizable(isResizable); + }else if(window instanceof Dialog){ + Dialog dialog = (Dialog) window; + dialog.setResizable(isResizable); + } + + return; + } + + /** + * コンポーネントの既存ボーダー内側にマージンをもうける。 + * @param comp 対象コンポーネント + * @param top 上マージン + * @param left 左マージン + * @param bottom 下マージン + * @param right 右マージン + */ + public static void addMargin(Component comp, + int top, int left, int bottom, int right){ + if( ! (comp instanceof JComponent) ) return; + JComponent jcomp = (JComponent) comp; + + Border outer = jcomp.getBorder(); + Border inner = + BorderFactory.createEmptyBorder(top, left, bottom, right); + + Border border; + if(outer == null){ + border = inner; + }else{ + border = BorderFactory.createCompoundBorder(outer, inner); + } + + jcomp.setBorder(border); + + return; + } + + /** + * 独自ロガーにエラーや例外を吐く、 + * カスタム化されたイベントキューに差し替える。 + */ + public static void replaceEventQueue(){ + Toolkit kit = Toolkit.getDefaultToolkit(); + EventQueue oldQueue = kit.getSystemEventQueue(); + EventQueue newQueue = new EventQueue(){ + private static final String FATALMSG = + "イベントディスパッチ中に異常が起きました。"; + @Override + protected void dispatchEvent(AWTEvent event){ + try{ + super.dispatchEvent(event); + }catch(RuntimeException e){ + Jindolf.logger().fatal(FATALMSG, e); + throw e; + }catch(Exception e){ + Jindolf.logger().fatal(FATALMSG, e); + }catch(Error e){ + Jindolf.logger().fatal(FATALMSG, e); + throw e; + } + // TODO Toolkit#beep()もするべきか + // TODO モーダルダイアログを出すべきか + // TODO 標準エラー出力抑止オプションを用意すべきか + // TODO セキュリティバイパス + return; + } + }; + oldQueue.push(newQueue); + return; + } + + /** + * 任意のイメージを多階調モノクロ化する。 + * 寸法は変わらない。 + * @param image イメージ + * @return モノクロ化イメージ + */ + public static BufferedImage createMonoImage(BufferedImage image){ + BufferedImage result; + result = OP_MONOIMG.filter(image, null); + return result; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/GameSummary.java b/src/main/java/jp/sourceforge/jindolf/GameSummary.java index 83a5340..fb00cda 100644 --- a/src/main/java/jp/sourceforge/jindolf/GameSummary.java +++ b/src/main/java/jp/sourceforge/jindolf/GameSummary.java @@ -1,1085 +1,1085 @@ -/* - * Summarize game information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.text.DateFormat; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import jp.sourceforge.jindolf.corelib.Destiny; -import jp.sourceforge.jindolf.corelib.GameRole; -import jp.sourceforge.jindolf.corelib.SysEventType; -import jp.sourceforge.jindolf.corelib.Team; -import jp.sourceforge.jindolf.corelib.VillageState; - -/** - * 決着の付いたゲームのサマリを集計。 - */ -public class GameSummary{ - - /** キャスティング表示用Comparator。 */ - public static final Comparator COMPARATOR_CASTING = - new CastingComparator(); - - - private final Map playerMap = - new HashMap(); - private final List playerList = - new LinkedList(); - private final Map> eventMap = - new EnumMap>(SysEventType.class); - - private final Village village; - - // 勝者 - private Team winner; - - // 占い先集計 - private int ctScryVillage = 0; - private int ctScryHamster = 0; - private int ctScryMadman = 0; - private int ctScryWolf = 0; - - // 護衛先集計 - private int ctGuardVillage = 0; - private int ctGuardHamster = 0; - private int ctGuardMadman = 0; - private int ctGuardWolf = 0; - private int ctGuardVillageGJ = 0; - private int ctGuardHamsterGJ = 0; - private int ctGuardMadmanGJ = 0; - private int ctGuardFakeGJ = 0; - - // 発言時刻範囲 - private long talk1stTimeMs = -1; - private long talkLastTimeMs = -1; - - - /** - * コンストラクタ。 - * @param village 村 - */ - public GameSummary(Village village){ - super(); - - VillageState state = village.getState(); - if( state != VillageState.EPILOGUE - && state != VillageState.GAMEOVER){ - throw new IllegalStateException(); - } - - this.village = village; - - summarize(); - - return; - } - - - /** - * プレイヤーのリストから役職バランス文字列を得る。 - * ex) "村村占霊狂狼" - * @param players プレイヤーのリスト - * @return 役職バランス文字列 - */ - public static String getRoleBalanceSequence(List players){ - List roleList = new LinkedList(); - for(Player player : players){ - GameRole role = player.getRole(); - roleList.add(role); - } - Collections.sort(roleList, GameRole.getPowerBalanceComparator()); - - StringBuilder result = new StringBuilder(); - for(GameRole role : roleList){ - char ch = role.getShortName(); - result.append(ch); - } - - return result.toString(); - } - - /** - * サマライズ処理。 - */ - private void summarize(){ - buildEventMap(); - - summarizeTime(); - summarizeWinner(); - summarizePlayers(); - - for(Period period : this.village.getPeriodList()){ - summarizePeriod(period); - } - - summarizeJudge(); - summarizeGuard(); - - return; - } - - /** - * SysEventの種別ごとに集計する。 - */ - private void buildEventMap(){ - for(SysEventType type : SysEventType.values()){ - List eventList = new LinkedList(); - this.eventMap.put(type, eventList); - } - - for(Period period : this.village.getPeriodList()){ - for(Topic topic : period.getTopicList()){ - if( ! (topic instanceof SysEvent) ) continue; - SysEvent event = (SysEvent) topic; - SysEventType type = event.getSysEventType(); - List eventList = this.eventMap.get(type); - eventList.add(event); - } - } - - return; - } - - /** - * 勝者集計。 - */ - private void summarizeWinner(){ - List eventList; - - eventList = this.eventMap.get(SysEventType.WINVILLAGE); - if( ! eventList.isEmpty() ){ - this.winner = Team.VILLAGE; - } - - eventList = this.eventMap.get(SysEventType.WINWOLF); - if( ! eventList.isEmpty() ){ - this.winner = Team.WOLF; - } - - eventList = this.eventMap.get(SysEventType.WINHAMSTER); - if( ! eventList.isEmpty() ){ - this.winner = Team.HAMSTER; - } - - if(this.winner == null) assert false; - - return; - } - - /** - * 参加者集計。 - */ - private void summarizePlayers(){ - List eventList; - - List avatarList; - List roleList; - List integerList; - List textList; - - eventList = this.eventMap.get(SysEventType.ONSTAGE); - for(SysEvent event : eventList){ - avatarList = event.getAvatarList(); - integerList = event.getIntegerList(); - Avatar onstageAvatar = avatarList.get(0); - Player onstagePlayer = registPlayer(onstageAvatar); - onstagePlayer.setEntryNo(integerList.get(0)); - } - - eventList = this.eventMap.get(SysEventType.PLAYERLIST); - assert eventList.size() == 1; - SysEvent event = eventList.get(0); - - avatarList = event.getAvatarList(); - roleList = event.getRoleList(); - integerList = event.getIntegerList(); - textList = event.getCharSequenceList(); - int avatarNum = avatarList.size(); - for(int idx = 0; idx < avatarNum; idx++){ - Avatar avatar = avatarList.get(idx); - GameRole role = roleList.get(idx); - CharSequence urlText = textList.get(idx * 2); - CharSequence idName = textList.get(idx * 2 + 1); - int liveOrDead = integerList.get(idx); - - Player player = registPlayer(avatar); - player.setRole(role); - player.setUrlText(urlText.toString()); - player.setIdName(idName.toString()); - if(liveOrDead != 0){ // 生存 - player.setObitDay(-1); - player.setDestiny(Destiny.ALIVE); - } - - this.playerList.add(player); - } - - return; - } - - /** - * Periodのサマライズ。 - * @param period Period - */ - private void summarizePeriod(Period period){ - int day = period.getDay(); - for(Topic topic : period.getTopicList()){ - if(topic instanceof SysEvent){ - SysEvent sysEvent = (SysEvent) topic; - summarizeDestiny(day, sysEvent); - } - } - - return; - } - - /** - * 各プレイヤー運命のサマライズ。 - * @param day 日 - * @param sysEvent システムイベント - */ - private void summarizeDestiny(int day, SysEvent sysEvent){ - List avatarList = sysEvent.getAvatarList(); - List integerList = sysEvent.getIntegerList(); - - int avatarTotal = avatarList.size(); - Avatar lastAvatar = null; - if(avatarTotal > 0) lastAvatar = avatarList.get(avatarTotal - 1); - - SysEventType eventType = sysEvent.getSysEventType(); - switch(eventType){ - case EXECUTION: // G国のみ - if(integerList.get(avatarTotal - 1) > 0) break; // 処刑無し - Player executedPl = registPlayer(lastAvatar); - executedPl.setDestiny(Destiny.EXECUTED); - executedPl.setObitDay(day); - break; - case SUDDENDEATH: - Avatar suddenDeathAvatar = avatarList.get(0); - Player suddenDeathPlayer = registPlayer(suddenDeathAvatar); - suddenDeathPlayer.setDestiny(Destiny.SUDDENDEATH); - suddenDeathPlayer.setObitDay(day); - break; - case COUNTING: // G国COUNTING2は運命に関係なし - if(avatarTotal % 2 == 0) break; // 処刑無し - Player executedPlayer = registPlayer(lastAvatar); - executedPlayer.setDestiny(Destiny.EXECUTED); - executedPlayer.setObitDay(day); - break; - case MURDERED: - for(Avatar avatar : avatarList){ - Player player = registPlayer(avatar); - player.setDestiny(Destiny.EATEN); - player.setObitDay(day); - } - // TODO E国ハム溶け処理は後回し - break; - default: - break; - } - - return; - } - - /** - * 会話時刻のサマライズ。 - */ - private void summarizeTime(){ - for(Period period : this.village.getPeriodList()){ - for(Topic topic : period.getTopicList()){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - - long epoch = talk.getTimeFromID(); - - if(this.talk1stTimeMs < 0) this.talk1stTimeMs = epoch; - if(this.talkLastTimeMs < 0) this.talkLastTimeMs = epoch; - - if(epoch < this.talk1stTimeMs ) this.talk1stTimeMs = epoch; - if(epoch > this.talkLastTimeMs) this.talkLastTimeMs = epoch; - } - } - - return; - } - - /** - * 占い師の活動を集計する。 - */ - private void summarizeJudge(){ - List eventList = this.eventMap.get(SysEventType.JUDGE); - - for(SysEvent event : eventList){ - List avatarList = event.getAvatarList(); - Avatar avatar = avatarList.get(1); - Player seered = getPlayer(avatar); - GameRole role = seered.getRole(); - switch(role){ - case WOLF: this.ctScryWolf++; break; - case MADMAN: this.ctScryMadman++; break; - case HAMSTER: this.ctScryHamster++; break; - default: this.ctScryVillage++; break; - } - } - - return; - } - - /** - * 占い師の活動を文字列化する。 - * @return 占い師の活動 - */ - public CharSequence dumpSeerActivity(){ - StringBuilder result = new StringBuilder(); - - if(this.ctScryVillage > 0){ - result.append("村陣営を"); - result.append(this.ctScryVillage); - result.append("回"); - } - - if(this.ctScryHamster > 0){ - if(result.length() > 0) result.append('、'); - result.append("ハムスターを"); - result.append(this.ctScryHamster); - result.append("回"); - } - - if(this.ctScryMadman > 0){ - if(result.length() > 0) result.append('、'); - result.append("狂人を"); - result.append(this.ctScryMadman); - result.append("回"); - } - - if(this.ctScryWolf > 0){ - if(result.length() > 0) result.append('、'); - result.append("人狼を"); - result.append(this.ctScryWolf); - result.append("回"); - } - - if(result.length() <= 0) result.append("誰も占わなかった。"); - else result.append("占った。"); - - CharSequence seq = WolfBBS.escapeWikiSyntax(result); - - return seq; - } - - /** - * 狩人の活動を集計する。 - */ - private void summarizeGuard(){ - List eventList; - - eventList = this.eventMap.get(SysEventType.GUARD); - for(SysEvent event : eventList){ - List avatarList = event.getAvatarList(); - Avatar avatar = avatarList.get(1); - Player guarded = getPlayer(avatar); - GameRole guardedRole = guarded.getRole(); - switch(guardedRole){ - case WOLF: this.ctGuardWolf++; break; - case MADMAN: this.ctGuardMadman++; break; - case HAMSTER: this.ctGuardHamster++; break; - default: this.ctGuardVillage++; break; - } - } - - for(Period period : this.village.getPeriodList()){ - summarizeGjPeriod(period); - } - - return; - } - - /** - * 狩人GJの日ごとの集計。 - * @param period 日 - */ - private void summarizeGjPeriod(Period period){ - if(period.getDay() <= 2) return; - - boolean hasAssaultTried = period.hasAssaultTried(); - boolean hunterAlive = false; - int wolfNum = 0; - - Set voters = period.getVoterSet(); - for(Avatar avatar : voters){ - Player player = getPlayer(avatar); - switch(player.getRole()){ - case HUNTER: hunterAlive = true; break; - case WOLF: wolfNum++; break; - default: break; - } - } - - Avatar executed = period.getExecutedAvatar(); - if(executed != null){ - Player player = getPlayer(executed); - switch(player.getRole()){ - case HUNTER: hunterAlive = false; break; - case WOLF: wolfNum--; break; - default: break; - } - } - - if( ! hunterAlive || wolfNum <= 0) return; - - SysEvent sysEvent; - - sysEvent = period.getTypedSysEvent(SysEventType.NOMURDER); - if(sysEvent == null) return; - - sysEvent = period.getTypedSysEvent(SysEventType.GUARD); - if(sysEvent == null) return; - - if(hasAssaultTried){ - Avatar guarded = sysEvent.getAvatarList().get(1); - Player guardedPlayer = getPlayer(guarded); - GameRole guardedRole = guardedPlayer.getRole(); - switch(guardedRole){ - case MADMAN: this.ctGuardMadmanGJ++; break; - case HAMSTER: this.ctGuardHamsterGJ++; break; - default: this.ctGuardVillageGJ++; break; - } - }else{ - this.ctGuardFakeGJ++; // 偽装GJ - } - - return; - } - - /** - * 狩人の活動を文字列化する。 - * @return 狩人の活動 - */ - public CharSequence dumpHunterActivity(){ - StringBuilder result = new StringBuilder(); - - String atLeast; - if(this.ctGuardFakeGJ > 0) atLeast = "少なくとも"; - else atLeast = ""; - - if(this.ctGuardVillage > 0){ - result.append(atLeast); - result.append("村陣営を"); - result.append(this.ctGuardVillage); - result.append("回護衛し"); - if(this.ctGuardVillageGJ > 0){ - result.append("GJを"); - result.append(this.ctGuardVillageGJ); - result.append("回出した。"); - }else{ - result.append("た。"); - } - } - - if(this.ctGuardHamster > 0){ - result.append(atLeast); - result.append("ハムスターを"); - result.append(this.ctGuardHamster); - result.append("回護衛し"); - if(this.ctGuardHamsterGJ > 0){ - result.append("GJを"); - result.append(this.ctGuardHamsterGJ); - result.append("回出した。"); - }else{ - result.append("た。"); - } - } - - if(this.ctGuardMadman > 0){ - result.append(atLeast); - result.append("狂人を"); - result.append(this.ctGuardMadman); - result.append("回護衛し"); - if(this.ctGuardMadmanGJ > 0){ - result.append("GJを"); - result.append(this.ctGuardMadmanGJ); - result.append("回出した。"); - }else{ - result.append("た。"); - } - } - - if(this.ctGuardWolf > 0){ - result.append(atLeast); - result.append("人狼を"); - result.append(this.ctGuardWolf); - result.append("回護衛した。"); - } - - if(this.ctGuardFakeGJ > 0){ - result.append("護衛先は不明ながら偽装GJが"); - result.append(this.ctGuardFakeGJ); - result.append("回あった。"); - } - - if(result.length() <= 0) result.append("誰も護衛できなかった"); - - CharSequence seq = WolfBBS.escapeWikiSyntax(result); - - return seq; - } - - /** - * 処刑概観を文字列化する。 - * @return 文字列化した処刑概観 - */ - public CharSequence dumpExecutionInfo(){ - StringBuilder result = new StringBuilder(); - - int exeWolf = 0; - int exeMad = 0; - int exeVillage = 0; - for(Player player : this.playerList){ - Destiny destiny = player.getDestiny(); - if(destiny != Destiny.EXECUTED) continue; - GameRole role = player.getRole(); - switch(role){ - case WOLF: exeWolf++; break; - case MADMAN: exeMad++; break; - default: exeVillage++; break; - } - } - - if(exeVillage > 0){ - result.append("▼村陣営×").append(exeVillage).append("回"); - } - if(exeMad > 0){ - if(result.length() > 0) result.append("、"); - result.append("▼狂×").append(exeMad).append("回"); - } - if(exeWolf > 0){ - if(result.length() > 0) result.append("、"); - result.append("▼狼×").append(exeWolf).append("回"); - } - if(result.length() <= 0) result.append("なし"); - - CharSequence seq = WolfBBS.escapeWikiSyntax(result); - - return seq; - } - - /** - * 襲撃概観を文字列化する。 - * @return 文字列化した襲撃概観 - */ - public CharSequence dumpAssaultInfo(){ - StringBuilder result = new StringBuilder(); - - int eatMad = 0; - int eatVillage = 0; - for(Player player : this.playerList){ - if(player.getAvatar() == Avatar.AVATAR_GERD){ - result.append("▲ゲルト"); - continue; - } - Destiny destiny = player.getDestiny(); - if(destiny != Destiny.EATEN) continue; - GameRole role = player.getRole(); - switch(role){ - case MADMAN: eatMad++; break; - default: eatVillage++; break; - } - } - - if(eatVillage > 0){ - if(result.length() > 0) result.append("、"); - result.append("▲村陣営×").append(eatVillage).append("回"); - } - if(eatMad > 0){ - if(result.length() > 0) result.append("、"); - result.append("▲狂×").append(eatMad).append("回"); - } - - if(result.length() <= 0) result.append("襲撃なし"); - - CharSequence seq = WolfBBS.escapeWikiSyntax(result); - - return seq; - } - - /** - * まとめサイト用投票Boxを生成する。 - * @return 投票BoxのWikiテキスト - */ - public CharSequence dumpVoteBox(){ - StringBuilder wikiText = new StringBuilder(); - - for(Player player : getCastingPlayerList()){ - Avatar avatar = player.getAvatar(); - if(avatar == Avatar.AVATAR_GERD) continue; - GameRole role = player.getRole(); - CharSequence fullName = avatar.getFullName(); - CharSequence roleName = role.getRoleName(); - StringBuilder line = new StringBuilder(); - line.append("[").append(roleName).append("] ").append(fullName); - if(wikiText.length() > 0) wikiText.append(','); - wikiText.append(WolfBBS.escapeWikiSyntax(line)); - wikiText.append("[0]"); - } - - wikiText.insert(0, "#vote(").append(")\n"); - - return wikiText; - } - - /** - * まとめサイト用キャスト表を生成する。 - * @param iconSet 顔アイコンセット - * @return キャスト表のWikiテキスト - */ - public CharSequence dumpCastingBoard(FaceIconSet iconSet){ - StringBuilder wikiText = new StringBuilder(); - - String vName = this.village.getVillageFullName(); - String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION; - String author = iconSet.getAuthor() + "氏" - +" [ "+iconSet.getUrlText()+" ]"; - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("// ↓キャスト表開始\n"); - wikiText.append("// Village : " + vName + "\n"); - wikiText.append("// Generator : " + generator + "\n"); - wikiText.append("// アイコン作者 : " + author + '\n'); - wikiText.append("// ※アイコン画像の著作財産権保持者" - +"および画像サーバ運営者から\n"); - wikiText.append("// 新しい意向が示された場合、" - +"そちらを最優先で尊重してください。\n"); - wikiText.append(WolfBBS.COMMENTLINE); - - wikiText.append("|配役") - .append("|参加者") - .append("|役職") - .append("|運命") - .append("|その活躍") - .append("|h") - .append('\n'); - wikiText.append(WolfBBS.COMMENTLINE); - - for(Player player : getCastingPlayerList()){ - Avatar avatar = player.getAvatar(); - GameRole role = player.getRole(); - Destiny destiny = player.getDestiny(); - int obitDay = player.getObitDay(); - String name = player.getIdName(); - String urlText = player.getUrlText(); - if(urlText == null) urlText = ""; - urlText = urlText.replace("~", "%7e"); - urlText = urlText.replace(" ", "%20"); - try{ - URL url = new URL(urlText); - URI uri = url.toURI(); - urlText = uri.toASCIIString(); - }catch(MalformedURLException e){ - // NOTHING - }catch(URISyntaxException e){ - // NOTHING - } - // PukiWikiではURL内の&のエスケープは不要? - - wikiText.append("// ========== "); - wikiText.append(name + " acts as [" + avatar.getName() + "]"); - wikiText.append(" ==========\n"); - - String teamColor = "BGCOLOR(" - + WolfBBS.getTeamWikiColor(role) - + "):"; - - String avatarIcon = iconSet.getAvatarIconWiki(avatar); - - wikiText.append('|').append(teamColor); - wikiText.append(avatarIcon).append("&br;"); - - wikiText.append("[[").append(avatar.getName()).append("]]"); - - wikiText.append('|').append(teamColor); - wikiText.append("[[").append(WolfBBS.escapeWikiBracket(name)); - if(urlText != null && urlText.length() > 0){ - wikiText.append('>').append(urlText); - } - wikiText.append("]]"); - - wikiText.append('|').append(teamColor); - wikiText.append(WolfBBS.getRoleIconWiki(role)); - wikiText.append("&br;"); - wikiText.append("[["); - wikiText.append(role.getRoleName()); - wikiText.append("]]"); - - String destinyColor = WolfBBS.getDestinyColorWiki(destiny); - wikiText.append('|'); - wikiText.append("BGCOLOR(").append(destinyColor).append("):"); - if(destiny == Destiny.ALIVE){ - wikiText.append("最後まで&br;生存"); - }else{ - wikiText.append(obitDay).append("日目").append("&br;"); - wikiText.append(destiny.getMessage()); - } - - wikiText.append('|'); - wikiText.append(avatar.getJobTitle()).append('。'); - - if(avatar == Avatar.AVATAR_GERD){ - wikiText.append("寝てばかりいた。"); - }else if(role == GameRole.HUNTER){ - CharSequence report = dumpHunterActivity(); - wikiText.append(report); - }else if(role == GameRole.SEER){ - CharSequence report = dumpSeerActivity(); - wikiText.append(report); - } - - wikiText.append("|\n"); - - } - - wikiText.append("|>|>|>|>|"); - wikiText.append("RIGHT:"); - wikiText.append("顔アイコン提供 : [["); - wikiText.append(WolfBBS.escapeWikiBracket(iconSet.getAuthor())); - wikiText.append(">" + iconSet.getUrlText()); - wikiText.append("]]氏"); - wikiText.append("|\n"); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("// ↑キャスト表ここまで\n"); - wikiText.append(WolfBBS.COMMENTLINE); - - return wikiText; - } - - /** - * 村詳細情報を出力する。 - * @return 村詳細情報 - */ - public CharSequence dumpVillageWiki(){ - StringBuilder wikiText = new StringBuilder(); - - DateFormat dform = - DateFormat.getDateTimeInstance(DateFormat.FULL, - DateFormat.FULL); - - String vName = this.village.getVillageFullName(); - String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION; - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("// ↓村詳細開始\n"); - wikiText.append("// Village : " + vName + "\n"); - wikiText.append("// Generator : " + generator + "\n"); - - wikiText.append("* 村の詳細\n"); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 勝者\n"); - Team winnerTeam = getWinnerTeam(); - String wonTeam = winnerTeam.getTeamName(); - wikiText.append(wonTeam).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- エントリー開始時刻\n"); - Date date = get1stTalkDate(); - String talk1st = dform.format(date); - wikiText.append(talk1st).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 参加人数\n"); - int avatarNum = countAvatarNum(); - String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = " - + avatarNum + "名"; - wikiText.append(WolfBBS.escapeWikiSyntax(totalMember)) - .append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 役職内訳\n"); - StringBuilder roleMsg = new StringBuilder(); - for(GameRole role : GameRole.values()){ - List players = getRoledPlayerList(role); - String roleName = role.getRoleName(); - if(players.size() <= 0) continue; - if(roleMsg.length() > 0) roleMsg.append('、'); - roleMsg.append(roleName) - .append(" × ") - .append(players.size()) - .append("名"); - } - wikiText.append(WolfBBS.escapeWikiSyntax(roleMsg)).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 処刑内訳\n"); - wikiText.append(dumpExecutionInfo()).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 襲撃内訳\n"); - wikiText.append(dumpAssaultInfo()).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 突然死\n"); - wikiText.append(countSuddenDeath()).append("名").append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 人口推移\n"); - for(int day = 1; day < this.village.getPeriodSize(); day++){ - List players = getSurvivorList(day); - CharSequence roleSeq = - GameSummary.getRoleBalanceSequence(players); - String daySeq; - Period period = this.village.getPeriod(day); - daySeq = period.getCaption(); - wikiText.append('|') - .append(daySeq) - .append('|') - .append(roleSeq) - .append("|\n"); - } - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 占い師の成績\n"); - wikiText.append(dumpSeerActivity()).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("- 狩人の成績\n"); - wikiText.append(dumpHunterActivity()).append('\n'); - - wikiText.append(WolfBBS.COMMENTLINE); - wikiText.append("// ↑村詳細ここまで\n"); - wikiText.append(WolfBBS.COMMENTLINE); - - return wikiText; - } - - /** - * 最初の発言の時刻を得る。 - * @return 時刻 - */ - public Date get1stTalkDate(){ - return new Date(this.talk1stTimeMs); - } - - /** - * 最後の発言の時刻を得る。 - * @return 時刻 - */ - public Date getLastTalkDate(){ - return new Date(this.talkLastTimeMs); - } - - /** - * 指定した日の生存者一覧を得る。 - * @param day 日 - * @return 生存者一覧 - */ - public List getSurvivorList(int day){ - if(day < 0 || this.village.getPeriodSize() <= day){ - throw new IndexOutOfBoundsException(); - } - - List result = new LinkedList(); - - Period period = this.village.getPeriod(day); - - if( period.isPrologue() - || (period.isProgress() && day == 1) ){ - result.addAll(this.playerList); - return result; - } - - if(period.isEpilogue()){ - for(Player player : this.playerList){ - if(player.getDestiny() == Destiny.ALIVE){ - result.add(player); - } - } - return result; - } - - for(Topic topic : period.getTopicList()){ - if( ! (topic instanceof SysEvent) ) continue; - SysEvent sysEvent = (SysEvent) topic; - if(sysEvent.getSysEventType() == SysEventType.SURVIVOR){ - List avatarList = sysEvent.getAvatarList(); - for(Avatar avatar : avatarList){ - Player player = getPlayer(avatar); - result.add(player); - } - } - } - - return result; - } - - /** - * プレイヤー一覧を得る。 - * 参加エントリー順 - * @return プレイヤーのリスト - */ - public List getPlayerList(){ - List result = Collections.unmodifiableList(this.playerList); - return result; - } - - /** - * キャスティング表用にソートされたプレイヤー一覧を得る。 - * @return プレイヤーのリスト - */ - public List getCastingPlayerList(){ - List sortedPlayers = - new LinkedList(); - sortedPlayers.addAll(this.playerList); - Collections.sort(sortedPlayers, COMPARATOR_CASTING); - return sortedPlayers; - } - - /** - * 指定された役職のプレイヤー一覧を得る。 - * @param role 役職 - * @return 役職に合致するプレイヤーのリスト - */ - public List getRoledPlayerList(GameRole role){ - List result = new LinkedList(); - - for(Player player : this.playerList){ - if(player.getRole() == role){ - result.add(player); - } - } - - return result; - } - - /** - * 勝利陣営を得る。 - * @return 勝利した陣営 - */ - public Team getWinnerTeam(){ - return this.winner; - } - - /** - * 突然死者数を得る。 - * @return 突然死者数 - */ - public int countSuddenDeath(){ - int suddenDeath = 0; - for(Player player : this.playerList){ - if(player.getDestiny() == Destiny.SUDDENDEATH) suddenDeath++; - } - return suddenDeath; - } - - /** - * 参加プレイヤー総数を得る。 - * @return プレイヤー総数 - */ - public int countAvatarNum(){ - int playerNum = this.playerList.size(); - return playerNum; - } - - /** - * AvatarからPlayerを得る。 - * 参加していないAvatarならnullを返す。 - * @param avatar Avatar - * @return Player - */ - public final Player getPlayer(Avatar avatar){ - Player player = this.playerMap.get(avatar); - return player; - } - - /** - * AvatarからPlayerを得る。 - * 無ければ新規に作る。 - * @param avatar Avatar - * @return Player - */ - private Player registPlayer(Avatar avatar){ - Player player = getPlayer(avatar); - if(player == null){ - player = new Player(); - player.setAvatar(avatar); - this.playerMap.put(avatar, player); - } - return player; - } - - /** - * プレイヤーのソート仕様の記述。 - * まとめサイトのキャスト表向け。 - */ - private static final class CastingComparator - implements Comparator { - - /** - * コンストラクタ。 - */ - private CastingComparator(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param p1 {@inheritDoc} - * @param p2 {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compare(Player p1, Player p2){ - if(p1 == p2) return 0; - if(p1 == null) return -1; - if(p2 == null) return +1; - - // ゲルトが最前 - Avatar avatar1 = p1.getAvatar(); - Avatar avatar2 = p2.getAvatar(); - if(avatar1.equals(avatar2)) return 0; - if(avatar1 == Avatar.AVATAR_GERD) return -1; - if(avatar2 == Avatar.AVATAR_GERD) return +1; - - // 生存者は最後 - Destiny dest1 = p1.getDestiny(); - Destiny dest2 = p2.getDestiny(); - if(dest1 != dest2){ - if (dest1 == Destiny.ALIVE) return +1; - else if(dest2 == Destiny.ALIVE) return -1; - } - - // 退場順 - int obitDay1 = p1.getObitDay(); - int obitDay2 = p2.getObitDay(); - if(obitDay1 > obitDay2) return +1; - if(obitDay1 < obitDay2) return -1; - - // 運命順 - int destinyOrder = dest1.compareTo(dest2); - if(destinyOrder != 0) return destinyOrder; - - // エントリー順 - int entryOrder = p1.getEntryNo() - p2.getEntryNo(); - - return entryOrder; - } - } - - // TODO E国ハムスター対応 -} +/* + * Summarize game information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.DateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import jp.sourceforge.jindolf.corelib.Destiny; +import jp.sourceforge.jindolf.corelib.GameRole; +import jp.sourceforge.jindolf.corelib.SysEventType; +import jp.sourceforge.jindolf.corelib.Team; +import jp.sourceforge.jindolf.corelib.VillageState; + +/** + * 決着の付いたゲームのサマリを集計。 + */ +public class GameSummary{ + + /** キャスティング表示用Comparator。 */ + public static final Comparator COMPARATOR_CASTING = + new CastingComparator(); + + + private final Map playerMap = + new HashMap(); + private final List playerList = + new LinkedList(); + private final Map> eventMap = + new EnumMap>(SysEventType.class); + + private final Village village; + + // 勝者 + private Team winner; + + // 占い先集計 + private int ctScryVillage = 0; + private int ctScryHamster = 0; + private int ctScryMadman = 0; + private int ctScryWolf = 0; + + // 護衛先集計 + private int ctGuardVillage = 0; + private int ctGuardHamster = 0; + private int ctGuardMadman = 0; + private int ctGuardWolf = 0; + private int ctGuardVillageGJ = 0; + private int ctGuardHamsterGJ = 0; + private int ctGuardMadmanGJ = 0; + private int ctGuardFakeGJ = 0; + + // 発言時刻範囲 + private long talk1stTimeMs = -1; + private long talkLastTimeMs = -1; + + + /** + * コンストラクタ。 + * @param village 村 + */ + public GameSummary(Village village){ + super(); + + VillageState state = village.getState(); + if( state != VillageState.EPILOGUE + && state != VillageState.GAMEOVER){ + throw new IllegalStateException(); + } + + this.village = village; + + summarize(); + + return; + } + + + /** + * プレイヤーのリストから役職バランス文字列を得る。 + * ex) "村村占霊狂狼" + * @param players プレイヤーのリスト + * @return 役職バランス文字列 + */ + public static String getRoleBalanceSequence(List players){ + List roleList = new LinkedList(); + for(Player player : players){ + GameRole role = player.getRole(); + roleList.add(role); + } + Collections.sort(roleList, GameRole.getPowerBalanceComparator()); + + StringBuilder result = new StringBuilder(); + for(GameRole role : roleList){ + char ch = role.getShortName(); + result.append(ch); + } + + return result.toString(); + } + + /** + * サマライズ処理。 + */ + private void summarize(){ + buildEventMap(); + + summarizeTime(); + summarizeWinner(); + summarizePlayers(); + + for(Period period : this.village.getPeriodList()){ + summarizePeriod(period); + } + + summarizeJudge(); + summarizeGuard(); + + return; + } + + /** + * SysEventの種別ごとに集計する。 + */ + private void buildEventMap(){ + for(SysEventType type : SysEventType.values()){ + List eventList = new LinkedList(); + this.eventMap.put(type, eventList); + } + + for(Period period : this.village.getPeriodList()){ + for(Topic topic : period.getTopicList()){ + if( ! (topic instanceof SysEvent) ) continue; + SysEvent event = (SysEvent) topic; + SysEventType type = event.getSysEventType(); + List eventList = this.eventMap.get(type); + eventList.add(event); + } + } + + return; + } + + /** + * 勝者集計。 + */ + private void summarizeWinner(){ + List eventList; + + eventList = this.eventMap.get(SysEventType.WINVILLAGE); + if( ! eventList.isEmpty() ){ + this.winner = Team.VILLAGE; + } + + eventList = this.eventMap.get(SysEventType.WINWOLF); + if( ! eventList.isEmpty() ){ + this.winner = Team.WOLF; + } + + eventList = this.eventMap.get(SysEventType.WINHAMSTER); + if( ! eventList.isEmpty() ){ + this.winner = Team.HAMSTER; + } + + if(this.winner == null) assert false; + + return; + } + + /** + * 参加者集計。 + */ + private void summarizePlayers(){ + List eventList; + + List avatarList; + List roleList; + List integerList; + List textList; + + eventList = this.eventMap.get(SysEventType.ONSTAGE); + for(SysEvent event : eventList){ + avatarList = event.getAvatarList(); + integerList = event.getIntegerList(); + Avatar onstageAvatar = avatarList.get(0); + Player onstagePlayer = registPlayer(onstageAvatar); + onstagePlayer.setEntryNo(integerList.get(0)); + } + + eventList = this.eventMap.get(SysEventType.PLAYERLIST); + assert eventList.size() == 1; + SysEvent event = eventList.get(0); + + avatarList = event.getAvatarList(); + roleList = event.getRoleList(); + integerList = event.getIntegerList(); + textList = event.getCharSequenceList(); + int avatarNum = avatarList.size(); + for(int idx = 0; idx < avatarNum; idx++){ + Avatar avatar = avatarList.get(idx); + GameRole role = roleList.get(idx); + CharSequence urlText = textList.get(idx * 2); + CharSequence idName = textList.get(idx * 2 + 1); + int liveOrDead = integerList.get(idx); + + Player player = registPlayer(avatar); + player.setRole(role); + player.setUrlText(urlText.toString()); + player.setIdName(idName.toString()); + if(liveOrDead != 0){ // 生存 + player.setObitDay(-1); + player.setDestiny(Destiny.ALIVE); + } + + this.playerList.add(player); + } + + return; + } + + /** + * Periodのサマライズ。 + * @param period Period + */ + private void summarizePeriod(Period period){ + int day = period.getDay(); + for(Topic topic : period.getTopicList()){ + if(topic instanceof SysEvent){ + SysEvent sysEvent = (SysEvent) topic; + summarizeDestiny(day, sysEvent); + } + } + + return; + } + + /** + * 各プレイヤー運命のサマライズ。 + * @param day 日 + * @param sysEvent システムイベント + */ + private void summarizeDestiny(int day, SysEvent sysEvent){ + List avatarList = sysEvent.getAvatarList(); + List integerList = sysEvent.getIntegerList(); + + int avatarTotal = avatarList.size(); + Avatar lastAvatar = null; + if(avatarTotal > 0) lastAvatar = avatarList.get(avatarTotal - 1); + + SysEventType eventType = sysEvent.getSysEventType(); + switch(eventType){ + case EXECUTION: // G国のみ + if(integerList.get(avatarTotal - 1) > 0) break; // 処刑無し + Player executedPl = registPlayer(lastAvatar); + executedPl.setDestiny(Destiny.EXECUTED); + executedPl.setObitDay(day); + break; + case SUDDENDEATH: + Avatar suddenDeathAvatar = avatarList.get(0); + Player suddenDeathPlayer = registPlayer(suddenDeathAvatar); + suddenDeathPlayer.setDestiny(Destiny.SUDDENDEATH); + suddenDeathPlayer.setObitDay(day); + break; + case COUNTING: // G国COUNTING2は運命に関係なし + if(avatarTotal % 2 == 0) break; // 処刑無し + Player executedPlayer = registPlayer(lastAvatar); + executedPlayer.setDestiny(Destiny.EXECUTED); + executedPlayer.setObitDay(day); + break; + case MURDERED: + for(Avatar avatar : avatarList){ + Player player = registPlayer(avatar); + player.setDestiny(Destiny.EATEN); + player.setObitDay(day); + } + // TODO E国ハム溶け処理は後回し + break; + default: + break; + } + + return; + } + + /** + * 会話時刻のサマライズ。 + */ + private void summarizeTime(){ + for(Period period : this.village.getPeriodList()){ + for(Topic topic : period.getTopicList()){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + + long epoch = talk.getTimeFromID(); + + if(this.talk1stTimeMs < 0) this.talk1stTimeMs = epoch; + if(this.talkLastTimeMs < 0) this.talkLastTimeMs = epoch; + + if(epoch < this.talk1stTimeMs ) this.talk1stTimeMs = epoch; + if(epoch > this.talkLastTimeMs) this.talkLastTimeMs = epoch; + } + } + + return; + } + + /** + * 占い師の活動を集計する。 + */ + private void summarizeJudge(){ + List eventList = this.eventMap.get(SysEventType.JUDGE); + + for(SysEvent event : eventList){ + List avatarList = event.getAvatarList(); + Avatar avatar = avatarList.get(1); + Player seered = getPlayer(avatar); + GameRole role = seered.getRole(); + switch(role){ + case WOLF: this.ctScryWolf++; break; + case MADMAN: this.ctScryMadman++; break; + case HAMSTER: this.ctScryHamster++; break; + default: this.ctScryVillage++; break; + } + } + + return; + } + + /** + * 占い師の活動を文字列化する。 + * @return 占い師の活動 + */ + public CharSequence dumpSeerActivity(){ + StringBuilder result = new StringBuilder(); + + if(this.ctScryVillage > 0){ + result.append("村陣営を"); + result.append(this.ctScryVillage); + result.append("回"); + } + + if(this.ctScryHamster > 0){ + if(result.length() > 0) result.append('、'); + result.append("ハムスターを"); + result.append(this.ctScryHamster); + result.append("回"); + } + + if(this.ctScryMadman > 0){ + if(result.length() > 0) result.append('、'); + result.append("狂人を"); + result.append(this.ctScryMadman); + result.append("回"); + } + + if(this.ctScryWolf > 0){ + if(result.length() > 0) result.append('、'); + result.append("人狼を"); + result.append(this.ctScryWolf); + result.append("回"); + } + + if(result.length() <= 0) result.append("誰も占わなかった。"); + else result.append("占った。"); + + CharSequence seq = WolfBBS.escapeWikiSyntax(result); + + return seq; + } + + /** + * 狩人の活動を集計する。 + */ + private void summarizeGuard(){ + List eventList; + + eventList = this.eventMap.get(SysEventType.GUARD); + for(SysEvent event : eventList){ + List avatarList = event.getAvatarList(); + Avatar avatar = avatarList.get(1); + Player guarded = getPlayer(avatar); + GameRole guardedRole = guarded.getRole(); + switch(guardedRole){ + case WOLF: this.ctGuardWolf++; break; + case MADMAN: this.ctGuardMadman++; break; + case HAMSTER: this.ctGuardHamster++; break; + default: this.ctGuardVillage++; break; + } + } + + for(Period period : this.village.getPeriodList()){ + summarizeGjPeriod(period); + } + + return; + } + + /** + * 狩人GJの日ごとの集計。 + * @param period 日 + */ + private void summarizeGjPeriod(Period period){ + if(period.getDay() <= 2) return; + + boolean hasAssaultTried = period.hasAssaultTried(); + boolean hunterAlive = false; + int wolfNum = 0; + + Set voters = period.getVoterSet(); + for(Avatar avatar : voters){ + Player player = getPlayer(avatar); + switch(player.getRole()){ + case HUNTER: hunterAlive = true; break; + case WOLF: wolfNum++; break; + default: break; + } + } + + Avatar executed = period.getExecutedAvatar(); + if(executed != null){ + Player player = getPlayer(executed); + switch(player.getRole()){ + case HUNTER: hunterAlive = false; break; + case WOLF: wolfNum--; break; + default: break; + } + } + + if( ! hunterAlive || wolfNum <= 0) return; + + SysEvent sysEvent; + + sysEvent = period.getTypedSysEvent(SysEventType.NOMURDER); + if(sysEvent == null) return; + + sysEvent = period.getTypedSysEvent(SysEventType.GUARD); + if(sysEvent == null) return; + + if(hasAssaultTried){ + Avatar guarded = sysEvent.getAvatarList().get(1); + Player guardedPlayer = getPlayer(guarded); + GameRole guardedRole = guardedPlayer.getRole(); + switch(guardedRole){ + case MADMAN: this.ctGuardMadmanGJ++; break; + case HAMSTER: this.ctGuardHamsterGJ++; break; + default: this.ctGuardVillageGJ++; break; + } + }else{ + this.ctGuardFakeGJ++; // 偽装GJ + } + + return; + } + + /** + * 狩人の活動を文字列化する。 + * @return 狩人の活動 + */ + public CharSequence dumpHunterActivity(){ + StringBuilder result = new StringBuilder(); + + String atLeast; + if(this.ctGuardFakeGJ > 0) atLeast = "少なくとも"; + else atLeast = ""; + + if(this.ctGuardVillage > 0){ + result.append(atLeast); + result.append("村陣営を"); + result.append(this.ctGuardVillage); + result.append("回護衛し"); + if(this.ctGuardVillageGJ > 0){ + result.append("GJを"); + result.append(this.ctGuardVillageGJ); + result.append("回出した。"); + }else{ + result.append("た。"); + } + } + + if(this.ctGuardHamster > 0){ + result.append(atLeast); + result.append("ハムスターを"); + result.append(this.ctGuardHamster); + result.append("回護衛し"); + if(this.ctGuardHamsterGJ > 0){ + result.append("GJを"); + result.append(this.ctGuardHamsterGJ); + result.append("回出した。"); + }else{ + result.append("た。"); + } + } + + if(this.ctGuardMadman > 0){ + result.append(atLeast); + result.append("狂人を"); + result.append(this.ctGuardMadman); + result.append("回護衛し"); + if(this.ctGuardMadmanGJ > 0){ + result.append("GJを"); + result.append(this.ctGuardMadmanGJ); + result.append("回出した。"); + }else{ + result.append("た。"); + } + } + + if(this.ctGuardWolf > 0){ + result.append(atLeast); + result.append("人狼を"); + result.append(this.ctGuardWolf); + result.append("回護衛した。"); + } + + if(this.ctGuardFakeGJ > 0){ + result.append("護衛先は不明ながら偽装GJが"); + result.append(this.ctGuardFakeGJ); + result.append("回あった。"); + } + + if(result.length() <= 0) result.append("誰も護衛できなかった"); + + CharSequence seq = WolfBBS.escapeWikiSyntax(result); + + return seq; + } + + /** + * 処刑概観を文字列化する。 + * @return 文字列化した処刑概観 + */ + public CharSequence dumpExecutionInfo(){ + StringBuilder result = new StringBuilder(); + + int exeWolf = 0; + int exeMad = 0; + int exeVillage = 0; + for(Player player : this.playerList){ + Destiny destiny = player.getDestiny(); + if(destiny != Destiny.EXECUTED) continue; + GameRole role = player.getRole(); + switch(role){ + case WOLF: exeWolf++; break; + case MADMAN: exeMad++; break; + default: exeVillage++; break; + } + } + + if(exeVillage > 0){ + result.append("▼村陣営×").append(exeVillage).append("回"); + } + if(exeMad > 0){ + if(result.length() > 0) result.append("、"); + result.append("▼狂×").append(exeMad).append("回"); + } + if(exeWolf > 0){ + if(result.length() > 0) result.append("、"); + result.append("▼狼×").append(exeWolf).append("回"); + } + if(result.length() <= 0) result.append("なし"); + + CharSequence seq = WolfBBS.escapeWikiSyntax(result); + + return seq; + } + + /** + * 襲撃概観を文字列化する。 + * @return 文字列化した襲撃概観 + */ + public CharSequence dumpAssaultInfo(){ + StringBuilder result = new StringBuilder(); + + int eatMad = 0; + int eatVillage = 0; + for(Player player : this.playerList){ + if(player.getAvatar() == Avatar.AVATAR_GERD){ + result.append("▲ゲルト"); + continue; + } + Destiny destiny = player.getDestiny(); + if(destiny != Destiny.EATEN) continue; + GameRole role = player.getRole(); + switch(role){ + case MADMAN: eatMad++; break; + default: eatVillage++; break; + } + } + + if(eatVillage > 0){ + if(result.length() > 0) result.append("、"); + result.append("▲村陣営×").append(eatVillage).append("回"); + } + if(eatMad > 0){ + if(result.length() > 0) result.append("、"); + result.append("▲狂×").append(eatMad).append("回"); + } + + if(result.length() <= 0) result.append("襲撃なし"); + + CharSequence seq = WolfBBS.escapeWikiSyntax(result); + + return seq; + } + + /** + * まとめサイト用投票Boxを生成する。 + * @return 投票BoxのWikiテキスト + */ + public CharSequence dumpVoteBox(){ + StringBuilder wikiText = new StringBuilder(); + + for(Player player : getCastingPlayerList()){ + Avatar avatar = player.getAvatar(); + if(avatar == Avatar.AVATAR_GERD) continue; + GameRole role = player.getRole(); + CharSequence fullName = avatar.getFullName(); + CharSequence roleName = role.getRoleName(); + StringBuilder line = new StringBuilder(); + line.append("[").append(roleName).append("] ").append(fullName); + if(wikiText.length() > 0) wikiText.append(','); + wikiText.append(WolfBBS.escapeWikiSyntax(line)); + wikiText.append("[0]"); + } + + wikiText.insert(0, "#vote(").append(")\n"); + + return wikiText; + } + + /** + * まとめサイト用キャスト表を生成する。 + * @param iconSet 顔アイコンセット + * @return キャスト表のWikiテキスト + */ + public CharSequence dumpCastingBoard(FaceIconSet iconSet){ + StringBuilder wikiText = new StringBuilder(); + + String vName = this.village.getVillageFullName(); + String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION; + String author = iconSet.getAuthor() + "氏" + +" [ "+iconSet.getUrlText()+" ]"; + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("// ↓キャスト表開始\n"); + wikiText.append("// Village : " + vName + "\n"); + wikiText.append("// Generator : " + generator + "\n"); + wikiText.append("// アイコン作者 : " + author + '\n'); + wikiText.append("// ※アイコン画像の著作財産権保持者" + +"および画像サーバ運営者から\n"); + wikiText.append("// 新しい意向が示された場合、" + +"そちらを最優先で尊重してください。\n"); + wikiText.append(WolfBBS.COMMENTLINE); + + wikiText.append("|配役") + .append("|参加者") + .append("|役職") + .append("|運命") + .append("|その活躍") + .append("|h") + .append('\n'); + wikiText.append(WolfBBS.COMMENTLINE); + + for(Player player : getCastingPlayerList()){ + Avatar avatar = player.getAvatar(); + GameRole role = player.getRole(); + Destiny destiny = player.getDestiny(); + int obitDay = player.getObitDay(); + String name = player.getIdName(); + String urlText = player.getUrlText(); + if(urlText == null) urlText = ""; + urlText = urlText.replace("~", "%7e"); + urlText = urlText.replace(" ", "%20"); + try{ + URL url = new URL(urlText); + URI uri = url.toURI(); + urlText = uri.toASCIIString(); + }catch(MalformedURLException e){ + // NOTHING + }catch(URISyntaxException e){ + // NOTHING + } + // PukiWikiではURL内の&のエスケープは不要? + + wikiText.append("// ========== "); + wikiText.append(name + " acts as [" + avatar.getName() + "]"); + wikiText.append(" ==========\n"); + + String teamColor = "BGCOLOR(" + + WolfBBS.getTeamWikiColor(role) + + "):"; + + String avatarIcon = iconSet.getAvatarIconWiki(avatar); + + wikiText.append('|').append(teamColor); + wikiText.append(avatarIcon).append("&br;"); + + wikiText.append("[[").append(avatar.getName()).append("]]"); + + wikiText.append('|').append(teamColor); + wikiText.append("[[").append(WolfBBS.escapeWikiBracket(name)); + if(urlText != null && urlText.length() > 0){ + wikiText.append('>').append(urlText); + } + wikiText.append("]]"); + + wikiText.append('|').append(teamColor); + wikiText.append(WolfBBS.getRoleIconWiki(role)); + wikiText.append("&br;"); + wikiText.append("[["); + wikiText.append(role.getRoleName()); + wikiText.append("]]"); + + String destinyColor = WolfBBS.getDestinyColorWiki(destiny); + wikiText.append('|'); + wikiText.append("BGCOLOR(").append(destinyColor).append("):"); + if(destiny == Destiny.ALIVE){ + wikiText.append("最後まで&br;生存"); + }else{ + wikiText.append(obitDay).append("日目").append("&br;"); + wikiText.append(destiny.getMessage()); + } + + wikiText.append('|'); + wikiText.append(avatar.getJobTitle()).append('。'); + + if(avatar == Avatar.AVATAR_GERD){ + wikiText.append("寝てばかりいた。"); + }else if(role == GameRole.HUNTER){ + CharSequence report = dumpHunterActivity(); + wikiText.append(report); + }else if(role == GameRole.SEER){ + CharSequence report = dumpSeerActivity(); + wikiText.append(report); + } + + wikiText.append("|\n"); + + } + + wikiText.append("|>|>|>|>|"); + wikiText.append("RIGHT:"); + wikiText.append("顔アイコン提供 : [["); + wikiText.append(WolfBBS.escapeWikiBracket(iconSet.getAuthor())); + wikiText.append(">" + iconSet.getUrlText()); + wikiText.append("]]氏"); + wikiText.append("|\n"); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("// ↑キャスト表ここまで\n"); + wikiText.append(WolfBBS.COMMENTLINE); + + return wikiText; + } + + /** + * 村詳細情報を出力する。 + * @return 村詳細情報 + */ + public CharSequence dumpVillageWiki(){ + StringBuilder wikiText = new StringBuilder(); + + DateFormat dform = + DateFormat.getDateTimeInstance(DateFormat.FULL, + DateFormat.FULL); + + String vName = this.village.getVillageFullName(); + String generator = Jindolf.TITLE + " Ver." + Jindolf.VERSION; + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("// ↓村詳細開始\n"); + wikiText.append("// Village : " + vName + "\n"); + wikiText.append("// Generator : " + generator + "\n"); + + wikiText.append("* 村の詳細\n"); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 勝者\n"); + Team winnerTeam = getWinnerTeam(); + String wonTeam = winnerTeam.getTeamName(); + wikiText.append(wonTeam).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- エントリー開始時刻\n"); + Date date = get1stTalkDate(); + String talk1st = dform.format(date); + wikiText.append(talk1st).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 参加人数\n"); + int avatarNum = countAvatarNum(); + String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = " + + avatarNum + "名"; + wikiText.append(WolfBBS.escapeWikiSyntax(totalMember)) + .append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 役職内訳\n"); + StringBuilder roleMsg = new StringBuilder(); + for(GameRole role : GameRole.values()){ + List players = getRoledPlayerList(role); + String roleName = role.getRoleName(); + if(players.size() <= 0) continue; + if(roleMsg.length() > 0) roleMsg.append('、'); + roleMsg.append(roleName) + .append(" × ") + .append(players.size()) + .append("名"); + } + wikiText.append(WolfBBS.escapeWikiSyntax(roleMsg)).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 処刑内訳\n"); + wikiText.append(dumpExecutionInfo()).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 襲撃内訳\n"); + wikiText.append(dumpAssaultInfo()).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 突然死\n"); + wikiText.append(countSuddenDeath()).append("名").append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 人口推移\n"); + for(int day = 1; day < this.village.getPeriodSize(); day++){ + List players = getSurvivorList(day); + CharSequence roleSeq = + GameSummary.getRoleBalanceSequence(players); + String daySeq; + Period period = this.village.getPeriod(day); + daySeq = period.getCaption(); + wikiText.append('|') + .append(daySeq) + .append('|') + .append(roleSeq) + .append("|\n"); + } + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 占い師の成績\n"); + wikiText.append(dumpSeerActivity()).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("- 狩人の成績\n"); + wikiText.append(dumpHunterActivity()).append('\n'); + + wikiText.append(WolfBBS.COMMENTLINE); + wikiText.append("// ↑村詳細ここまで\n"); + wikiText.append(WolfBBS.COMMENTLINE); + + return wikiText; + } + + /** + * 最初の発言の時刻を得る。 + * @return 時刻 + */ + public Date get1stTalkDate(){ + return new Date(this.talk1stTimeMs); + } + + /** + * 最後の発言の時刻を得る。 + * @return 時刻 + */ + public Date getLastTalkDate(){ + return new Date(this.talkLastTimeMs); + } + + /** + * 指定した日の生存者一覧を得る。 + * @param day 日 + * @return 生存者一覧 + */ + public List getSurvivorList(int day){ + if(day < 0 || this.village.getPeriodSize() <= day){ + throw new IndexOutOfBoundsException(); + } + + List result = new LinkedList(); + + Period period = this.village.getPeriod(day); + + if( period.isPrologue() + || (period.isProgress() && day == 1) ){ + result.addAll(this.playerList); + return result; + } + + if(period.isEpilogue()){ + for(Player player : this.playerList){ + if(player.getDestiny() == Destiny.ALIVE){ + result.add(player); + } + } + return result; + } + + for(Topic topic : period.getTopicList()){ + if( ! (topic instanceof SysEvent) ) continue; + SysEvent sysEvent = (SysEvent) topic; + if(sysEvent.getSysEventType() == SysEventType.SURVIVOR){ + List avatarList = sysEvent.getAvatarList(); + for(Avatar avatar : avatarList){ + Player player = getPlayer(avatar); + result.add(player); + } + } + } + + return result; + } + + /** + * プレイヤー一覧を得る。 + * 参加エントリー順 + * @return プレイヤーのリスト + */ + public List getPlayerList(){ + List result = Collections.unmodifiableList(this.playerList); + return result; + } + + /** + * キャスティング表用にソートされたプレイヤー一覧を得る。 + * @return プレイヤーのリスト + */ + public List getCastingPlayerList(){ + List sortedPlayers = + new LinkedList(); + sortedPlayers.addAll(this.playerList); + Collections.sort(sortedPlayers, COMPARATOR_CASTING); + return sortedPlayers; + } + + /** + * 指定された役職のプレイヤー一覧を得る。 + * @param role 役職 + * @return 役職に合致するプレイヤーのリスト + */ + public List getRoledPlayerList(GameRole role){ + List result = new LinkedList(); + + for(Player player : this.playerList){ + if(player.getRole() == role){ + result.add(player); + } + } + + return result; + } + + /** + * 勝利陣営を得る。 + * @return 勝利した陣営 + */ + public Team getWinnerTeam(){ + return this.winner; + } + + /** + * 突然死者数を得る。 + * @return 突然死者数 + */ + public int countSuddenDeath(){ + int suddenDeath = 0; + for(Player player : this.playerList){ + if(player.getDestiny() == Destiny.SUDDENDEATH) suddenDeath++; + } + return suddenDeath; + } + + /** + * 参加プレイヤー総数を得る。 + * @return プレイヤー総数 + */ + public int countAvatarNum(){ + int playerNum = this.playerList.size(); + return playerNum; + } + + /** + * AvatarからPlayerを得る。 + * 参加していないAvatarならnullを返す。 + * @param avatar Avatar + * @return Player + */ + public final Player getPlayer(Avatar avatar){ + Player player = this.playerMap.get(avatar); + return player; + } + + /** + * AvatarからPlayerを得る。 + * 無ければ新規に作る。 + * @param avatar Avatar + * @return Player + */ + private Player registPlayer(Avatar avatar){ + Player player = getPlayer(avatar); + if(player == null){ + player = new Player(); + player.setAvatar(avatar); + this.playerMap.put(avatar, player); + } + return player; + } + + /** + * プレイヤーのソート仕様の記述。 + * まとめサイトのキャスト表向け。 + */ + private static final class CastingComparator + implements Comparator { + + /** + * コンストラクタ。 + */ + private CastingComparator(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param p1 {@inheritDoc} + * @param p2 {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compare(Player p1, Player p2){ + if(p1 == p2) return 0; + if(p1 == null) return -1; + if(p2 == null) return +1; + + // ゲルトが最前 + Avatar avatar1 = p1.getAvatar(); + Avatar avatar2 = p2.getAvatar(); + if(avatar1.equals(avatar2)) return 0; + if(avatar1 == Avatar.AVATAR_GERD) return -1; + if(avatar2 == Avatar.AVATAR_GERD) return +1; + + // 生存者は最後 + Destiny dest1 = p1.getDestiny(); + Destiny dest2 = p2.getDestiny(); + if(dest1 != dest2){ + if (dest1 == Destiny.ALIVE) return +1; + else if(dest2 == Destiny.ALIVE) return -1; + } + + // 退場順 + int obitDay1 = p1.getObitDay(); + int obitDay2 = p2.getObitDay(); + if(obitDay1 > obitDay2) return +1; + if(obitDay1 < obitDay2) return -1; + + // 運命順 + int destinyOrder = dest1.compareTo(dest2); + if(destinyOrder != 0) return destinyOrder; + + // エントリー順 + int entryOrder = p1.getEntryNo() - p2.getEntryNo(); + + return entryOrder; + } + } + + // TODO E国ハムスター対応 +} diff --git a/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java b/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java index 4c76cf1..a0933a4 100644 --- a/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java +++ b/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java @@ -1,770 +1,770 @@ -/* - * Text-Glyph Drawing - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.GlyphVector; -import java.awt.geom.Rectangle2D; -import java.io.IOException; -import java.text.CharacterIterator; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.swing.SwingConstants; - -/** - * 複数行の文字列を矩形内に描画する。 - * 制御文字は'\n'のみサポート。 - */ -public class GlyphDraw extends AbstractTextRow implements SwingConstants{ - - private static final Color COLOR_SELECTION = new Color(0xb8cfe5); - private static final Color COLOR_SEARCHHIT = new Color(0xb2b300); - private static final Color COLOR_HOTTARGET = Color.ORANGE; - - private Color foregroundColor = Color.WHITE; - private final CharSequence source; - - private float[] dimArray; - private final List lines = new LinkedList(); - private Collection anchorSet; - private final List matchList = new LinkedList(); - private MatchInfo hotTarget = null; - - private int selectStart = -1; - private int selectLast = -1; - - /** - * コンストラクタ。 - * @param source 文字列 - */ - public GlyphDraw(CharSequence source){ - this(source, FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param source 文字列 - * @param fontInfo フォント設定 - */ - public GlyphDraw(CharSequence source, FontInfo fontInfo){ - super(fontInfo); - - this.source = source; - - GlyphVector gv = createGlyphVector(this.source); - - int sourceLength = gv.getNumGlyphs(); - - this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null); - - return; - } - - /** - * 前景色を得る。 - * @return 前景色 - */ - public Color getColor(){ - return this.foregroundColor; - } - - /** - * 前景色を設定する。 - * @param color 前景色 - */ - public void setColor(Color color){ - this.foregroundColor = color; - return; - } - - /** - * アンカーを設定する。 - * アンカーの位置指定はコンストラクタに与えた文字列に対するものでなければ - * ならない。 - * @param anchorSet アンカーの集合 - */ - public void setAnchorSet(Collection anchorSet){ - this.anchorSet = anchorSet; - return; - } - - /** - * 文字列の占めるピクセル幅を返す。 - * @param fromPos 文字列開始位置 - * @param toPos 文字列終了位置 - * @return ピクセル幅 - */ - public float getSpan(int fromPos, int toPos){ - float from = this.dimArray[fromPos * 2]; - float to = this.dimArray[(toPos+1) * 2]; - float span = to - from; - return span; - } - - /** - * 指定領域の文字列から行情報を生成し内部に登録する。 - * @param from 文字列開始位置 - * @param to 文字列終了位置 - * @return 行情報 - */ - protected GlyphVector createLine(int from, int to){ - GlyphVector line = createGlyphVector(this.source, from, to + 1); - this.lines.add(line); - return line; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - // TODO 最後が \n で終わるダイアログが無限再帰を起こす? - @Override - public Rectangle recalcBounds(){ - float newWidth = (float) getWidth(); - this.lines.clear(); - CharacterIterator iterator; - iterator = new SequenceCharacterIterator(this.source); - int from = iterator.getIndex(); - int to = from; - for(;;){ - char ch = iterator.current(); - - if(ch == CharacterIterator.DONE){ - if(from < to){ - createLine(from, to - 1); - } - break; - } - - if(ch == '\n'){ - createLine(from, to); - to++; - from = to; - iterator.next(); - continue; - } - - float fwidth = getSpan(from, to); - if(fwidth > newWidth){ - if(from < to){ - createLine(from, to - 1); - from = to; - }else{ - createLine(from, to); - to++; - from = to; - iterator.next(); - } - continue; - } - - to++; - iterator.next(); - } - - int totalWidth = 0; - int totalHeight = 0; - for(GlyphVector gv : this.lines){ - Rectangle2D r2d = gv.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - totalWidth = Math.max(totalWidth, rect.width); - totalHeight += rect.height; - } - - this.bounds.width = totalWidth; - this.bounds.height = totalHeight; - - return this.bounds; - } - - /** - * {@inheritDoc} - * @param fontInfo {@inheritDoc} - */ - @Override - public void setFontInfo(FontInfo fontInfo){ - super.setFontInfo(fontInfo); - - GlyphVector gv = createGlyphVector(this.source); - - int sourceLength = gv.getNumGlyphs(); - - this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null); - - recalcBounds(); - - return; - } - - /** - * 指定された点座標が文字列のどこを示すか判定する。 - * @param pt 点座標 - * @return 文字位置。座標が文字列以外を示す場合は-1を返す。 - */ - public int getCharIndex(Point pt){ - if( ! this.bounds.contains(pt) ) return -1; - - int sPos = 0; - int xPos = this.bounds.x; - int yPos = this.bounds.y; - for(GlyphVector gv : this.lines){ - Rectangle2D r2d = gv.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - rect.x = xPos; - rect.y = yPos; - int sourceLength = gv.getNumGlyphs(); - if(rect.contains(pt)){ - for(int pos = 0; pos < sourceLength; pos++){ - float span = getSpan(sPos, sPos+pos); - if(span+xPos > pt.x) return sPos + pos; - } - return -1; - } - yPos += rect.height; - sPos += sourceLength; - } - - return -1; - } - - /** - * {@inheritDoc} - * @param appendable {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public Appendable appendSelected(Appendable appendable) - throws IOException{ - if(this.selectStart < 0 || this.selectLast < 0) return appendable; - CharSequence subsel; - subsel = this.source.subSequence(this.selectStart, - this.selectLast + 1); - appendable.append(subsel); - return appendable; - } - - /** - * {@inheritDoc} - */ - @Override - public void clearSelect(){ - this.selectStart = -1; - this.selectLast = -1; - return; - } - - /** - * 指定した部分文字列を選択された状態にする。 - * @param start 文字列開始位置 - * @param last 文字列終了位置 - */ - public void select(int start, int last){ - if(start < last){ - this.selectStart = start; - this.selectLast = last; - }else{ - this.selectStart = last; - this.selectLast = start; - } - this.selectLast = Math.min(this.source.length() - 1, - this.selectLast ); - return; - } - - /** - * {@inheritDoc} - * @param from {@inheritDoc} - * @param to {@inheritDoc} - */ - @Override - public void drag(Point from, Point to){ - Point fromPt = from; - Point toPt = to; - if(fromPt.y > toPt.y || (fromPt.y == toPt.y && fromPt.x > toPt.x)){ - Point swapPt = fromPt; - fromPt = toPt; - toPt = swapPt; - } - - int fromDirection = GUIUtils.getDirection(this.bounds, fromPt); - int toDirection = GUIUtils.getDirection(this.bounds, toPt); - - if(fromDirection == toDirection){ - if( fromDirection == NORTH - || fromDirection == SOUTH){ - clearSelect(); - return; - } - } - - int fromIndex = -1; - int toIndex = -1; - - if(fromDirection == NORTH){ - fromIndex = 0; - } - if(toDirection == SOUTH){ - toIndex = this.source.length() - 1; - } - - if(fromIndex < 0){ - fromIndex = getCharIndex(fromPt); - } - if(toIndex < 0){ - toIndex = getCharIndex(toPt); - } - - if(fromIndex >= 0 && toIndex >= 0){ - select(fromIndex, toIndex); - return; - } - - int xPos = this.bounds.x; - int yPos = this.bounds.y; - int accumPos = 0; - for(GlyphVector gv : this.lines){ - int glyphStart = accumPos; - int glyphLast = accumPos + gv.getNumGlyphs() - 1; - Rectangle2D r2d = gv.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - rect.x += xPos; - rect.y = yPos; - - if( fromIndex < 0 - && GUIUtils.getDirection(rect, fromPt) == SOUTH){ - yPos += rect.height; - accumPos = glyphLast + 1; - continue; - }else if( toIndex < 0 - && GUIUtils.getDirection(rect, toPt) == NORTH){ - break; - } - - if(fromIndex < 0){ - int dir = GUIUtils.getDirection(rect, fromPt); - if(dir == EAST){ - fromIndex = glyphStart; - }else if(dir == WEST){ - fromIndex = glyphLast+1; - } - } - if(toIndex < 0){ - int dir = GUIUtils.getDirection(rect, toPt); - if(dir == EAST){ - toIndex = glyphStart - 1; - }else if(dir == WEST){ - toIndex = glyphLast; - } - } - - if(fromIndex >= 0 && toIndex >= 0){ - select(fromIndex, toIndex); - return; - } - - yPos += rect.height; - accumPos = glyphLast + 1; - } - - clearSelect(); - return; - } - - /** - * 文字列検索がヒットした箇所のハイライト描画を行う。 - * @param g グラフィックスコンテキスト - */ - private void paintRegexHitted(Graphics2D g){ - if(this.matchList.size() <= 0) return; - - FontMetrics metrics = g.getFontMetrics(); - final int ascent = metrics.getAscent(); - - int xPos = this.bounds.x; - int yPos = this.bounds.y + ascent; - - int accumPos = 0; - - for(GlyphVector line : this.lines){ - int glyphStart = accumPos; - int glyphLast = accumPos + line.getNumGlyphs() - 1; - - for(MatchInfo match : this.matchList){ - int matchStart = match.getStartPos(); - int matchLast = match.getEndPos() - 1; - - if(matchLast < glyphStart) continue; - if(glyphLast < matchStart) break; - - int hilightStart = Math.max(matchStart, glyphStart); - int hilightLast = Math.min(matchLast, glyphLast); - Shape shape; - shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); - Rectangle hilight = shape.getBounds(); - shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); - hilight.add(shape.getBounds()); - - if(match == this.hotTarget){ - g.setColor(COLOR_HOTTARGET); - }else{ - g.setColor(COLOR_SEARCHHIT); - } - - g.fillRect(xPos + hilight.x, - yPos + hilight.y, - hilight.width, - hilight.height ); - } - - Rectangle2D r2d = line.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - - yPos += rect.height; - - accumPos = glyphLast + 1; - } - - return; - } - - /** - * 選択文字列のハイライト描画を行う。 - * @param g グラフィックスコンテキスト - */ - private void paintSelected(Graphics2D g){ - if(this.selectStart < 0 || this.selectLast < 0) return; - - g.setColor(COLOR_SELECTION); - - int xPos = this.bounds.x; - int yPos = this.bounds.y; - - int accumPos = 0; - - for(GlyphVector line : this.lines){ - int glyphStart = accumPos; - int glyphLast = accumPos + line.getNumGlyphs() - 1; - - if(this.selectLast < glyphStart) break; - - Rectangle2D r2d = line.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - - if(glyphLast < this.selectStart){ - yPos += rect.height; - accumPos = glyphLast + 1; - continue; - } - - int hilightStart = Math.max(this.selectStart, glyphStart); - int hilightLast = Math.min(this.selectLast, glyphLast); - Shape shape; - shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); - Rectangle hilight = shape.getBounds(); - shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); - hilight.add(shape.getBounds()); - - g.fillRect(xPos + hilight.x, - yPos, - hilight.width, - hilight.height ); - - yPos += rect.height; - accumPos = glyphLast + 1; - } - - return; - } - - /** - * アンカー文字列のハイライト描画を行う。 - * @param g グラフィックスコンテキスト - */ - private void paintAnchorBack(Graphics2D g){ - if(this.anchorSet == null) return; - if(this.anchorSet.size() <= 0) return; - - FontMetrics metrics = g.getFontMetrics(); - final int ascent = metrics.getAscent(); - - g.setColor(Color.GRAY); - - int xPos = this.bounds.x; - int yPos = this.bounds.y + ascent; - - int accumPos = 0; - - for(GlyphVector line : this.lines){ - int glyphStart = accumPos; - int glyphLast = accumPos + line.getNumGlyphs() - 1; - - for(Anchor anchor : this.anchorSet){ - int anchorStart = anchor.getStartPos(); - int anchorLast = anchor.getEndPos() - 1; - - if(anchorLast < glyphStart) continue; - if(glyphLast < anchorStart) break; - - int hilightStart = Math.max(anchorStart, glyphStart); - int hilightLast = Math.min(anchorLast, glyphLast); - Shape shape; - shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); - Rectangle hilight = shape.getBounds(); - shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); - hilight.add(shape.getBounds()); - - g.fillRect(xPos + hilight.x, - yPos + hilight.y, - hilight.width, - hilight.height ); - } - - Rectangle2D r2d = line.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - - yPos += rect.height; - - accumPos = glyphLast + 1; - } - - return; - } - - /** - * {@inheritDoc} - * @param g {@inheritDoc} - */ - @Override - public void paint(Graphics2D g){ - g.setFont(this.fontInfo.getFont()); - FontMetrics metrics = g.getFontMetrics(); - int ascent = metrics.getAscent(); - - int xPos = this.bounds.x; - int yPos = this.bounds.y + ascent; - - paintAnchorBack(g); - paintRegexHitted(g); - paintSelected(g); - - g.setColor(this.foregroundColor); - for(GlyphVector gv : this.lines){ - g.drawGlyphVector(gv, xPos, yPos); - - Rectangle2D r2d = gv.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - - yPos += rect.height; - } - - return; - } - - /** - * 与えられた座標にアンカー文字列が存在すればAnchorを返す。 - * @param pt 座標 - * @return アンカー - */ - public Anchor getAnchor(Point pt){ - int targetIdx = getCharIndex(pt); - if(targetIdx < 0) return null; - - for(Anchor anchor : this.anchorSet){ - int anchorStart = anchor.getStartPos(); - int anchorEnd = anchor.getEndPos(); - if(anchorStart <= targetIdx && targetIdx <= anchorEnd - 1){ - return anchor; - } - } - - return null; - } - - /** - * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。 - * @param pt 座標 - * @return 検索マッチインデックス - */ - public int getRegexMatchIndex(Point pt){ - int targetIdx = getCharIndex(pt); - if(targetIdx < 0) return -1; - - int index = 0; - for(MatchInfo info : this.matchList){ - int matchStart = info.getStartPos(); - int matchEnd = info.getEndPos(); - if(matchStart <= targetIdx && targetIdx <= matchEnd - 1){ - return index; - } - index++; - } - - return -1; - } - - /** - * 検索文字列パターンを設定する。 - * @param searchRegex パターン - * @return ヒット数 - */ - public int setRegex(Pattern searchRegex){ - clearHotTarget(); - this.matchList.clear(); - if(searchRegex == null) return 0; - - Matcher matcher = searchRegex.matcher(this.source); - while(matcher.find()){ - int startPos = matcher.start(); - int endPos = matcher.end(); - if(startPos >= endPos) break; // 長さ0マッチは無視 - MatchInfo matchInfo = new MatchInfo(startPos, endPos); - this.matchList.add(matchInfo); - } - - return getRegexMatches(); - } - - /** - * 検索ハイライトインデックスを返す。 - * @return 検索ハイライトインデックス。見つからなければ-1。 - */ - public int getHotTargetIndex(){ - return this.matchList.indexOf(this.hotTarget); - } - - /** - * 検索ハイライトを設定する。 - * @param index ハイライトインデックス。負ならハイライト全クリア。 - */ - public void setHotTargetIndex(int index){ - if(index < 0){ - clearHotTarget(); - return; - } - this.hotTarget = this.matchList.get(index); - return; - } - - /** - * 検索一致件数を返す。 - * @return 検索一致件数 - */ - public int getRegexMatches(){ - return this.matchList.size(); - } - - /** - * 特別な検索ハイライト描画をクリアする。 - */ - public void clearHotTarget(){ - this.hotTarget = null; - return; - } - - /** - * 特別な検索ハイライト領域の寸法を返す。 - * @return ハイライト領域寸法 - */ - public Rectangle getHotTargetRectangle(){ - Rectangle result = null; - - if(this.hotTarget == null) return result; - - int xPos = this.bounds.x; - int yPos = this.bounds.y; - - int accumPos = 0; - - int matchStart = this.hotTarget.getStartPos(); - int matchLast = this.hotTarget.getEndPos() - 1; - - for(GlyphVector gv : this.lines){ - int glyphStart = accumPos; - int glyphLast = accumPos + gv.getNumGlyphs() - 1; - - if(matchLast < glyphStart) break; - - if(matchStart <= glyphLast){ - int hilightStart = Math.max(matchStart, glyphStart); - int hilightLast = Math.min(matchLast, glyphLast); - - Shape shape; - shape = gv.getGlyphLogicalBounds(hilightStart - glyphStart); - Rectangle hilight = shape.getBounds(); - shape = gv.getGlyphLogicalBounds(hilightLast - glyphStart); - hilight.add(shape.getBounds()); - - Rectangle temp = new Rectangle(xPos + hilight.x, - yPos, - hilight.width, - hilight.height); - if(result == null){ - result = temp; - }else{ - result.add(temp); - } - } - - Rectangle2D r2d = gv.getLogicalBounds(); - Rectangle rect = r2d.getBounds(); - yPos += rect.height; - - accumPos = glyphLast + 1; - } - - return result; - } - - /** - * 検索ヒット情報。 - */ - private static class MatchInfo{ - - private final int startPos; - private final int endPos; - - /** - * コンストラクタ。 - * @param startPos ヒット開始位置 - * @param endPos ヒット終了位置 - */ - public MatchInfo(int startPos, int endPos){ - super(); - this.startPos = startPos; - this.endPos = endPos; - return; - } - - /** - * ヒット開始位置を取得する。 - * @return ヒット開始位置 - */ - public int getStartPos(){ - return this.startPos; - } - - /** - * ヒット終了位置を取得する。 - * @return ヒット終了位置 - */ - public int getEndPos(){ - return this.endPos; - } - } - -} +/* + * Text-Glyph Drawing + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.text.CharacterIterator; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.SwingConstants; + +/** + * 複数行の文字列を矩形内に描画する。 + * 制御文字は'\n'のみサポート。 + */ +public class GlyphDraw extends AbstractTextRow implements SwingConstants{ + + private static final Color COLOR_SELECTION = new Color(0xb8cfe5); + private static final Color COLOR_SEARCHHIT = new Color(0xb2b300); + private static final Color COLOR_HOTTARGET = Color.ORANGE; + + private Color foregroundColor = Color.WHITE; + private final CharSequence source; + + private float[] dimArray; + private final List lines = new LinkedList(); + private Collection anchorSet; + private final List matchList = new LinkedList(); + private MatchInfo hotTarget = null; + + private int selectStart = -1; + private int selectLast = -1; + + /** + * コンストラクタ。 + * @param source 文字列 + */ + public GlyphDraw(CharSequence source){ + this(source, FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param source 文字列 + * @param fontInfo フォント設定 + */ + public GlyphDraw(CharSequence source, FontInfo fontInfo){ + super(fontInfo); + + this.source = source; + + GlyphVector gv = createGlyphVector(this.source); + + int sourceLength = gv.getNumGlyphs(); + + this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null); + + return; + } + + /** + * 前景色を得る。 + * @return 前景色 + */ + public Color getColor(){ + return this.foregroundColor; + } + + /** + * 前景色を設定する。 + * @param color 前景色 + */ + public void setColor(Color color){ + this.foregroundColor = color; + return; + } + + /** + * アンカーを設定する。 + * アンカーの位置指定はコンストラクタに与えた文字列に対するものでなければ + * ならない。 + * @param anchorSet アンカーの集合 + */ + public void setAnchorSet(Collection anchorSet){ + this.anchorSet = anchorSet; + return; + } + + /** + * 文字列の占めるピクセル幅を返す。 + * @param fromPos 文字列開始位置 + * @param toPos 文字列終了位置 + * @return ピクセル幅 + */ + public float getSpan(int fromPos, int toPos){ + float from = this.dimArray[fromPos * 2]; + float to = this.dimArray[(toPos+1) * 2]; + float span = to - from; + return span; + } + + /** + * 指定領域の文字列から行情報を生成し内部に登録する。 + * @param from 文字列開始位置 + * @param to 文字列終了位置 + * @return 行情報 + */ + protected GlyphVector createLine(int from, int to){ + GlyphVector line = createGlyphVector(this.source, from, to + 1); + this.lines.add(line); + return line; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + // TODO 最後が \n で終わるダイアログが無限再帰を起こす? + @Override + public Rectangle recalcBounds(){ + float newWidth = (float) getWidth(); + this.lines.clear(); + CharacterIterator iterator; + iterator = new SequenceCharacterIterator(this.source); + int from = iterator.getIndex(); + int to = from; + for(;;){ + char ch = iterator.current(); + + if(ch == CharacterIterator.DONE){ + if(from < to){ + createLine(from, to - 1); + } + break; + } + + if(ch == '\n'){ + createLine(from, to); + to++; + from = to; + iterator.next(); + continue; + } + + float fwidth = getSpan(from, to); + if(fwidth > newWidth){ + if(from < to){ + createLine(from, to - 1); + from = to; + }else{ + createLine(from, to); + to++; + from = to; + iterator.next(); + } + continue; + } + + to++; + iterator.next(); + } + + int totalWidth = 0; + int totalHeight = 0; + for(GlyphVector gv : this.lines){ + Rectangle2D r2d = gv.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + totalWidth = Math.max(totalWidth, rect.width); + totalHeight += rect.height; + } + + this.bounds.width = totalWidth; + this.bounds.height = totalHeight; + + return this.bounds; + } + + /** + * {@inheritDoc} + * @param fontInfo {@inheritDoc} + */ + @Override + public void setFontInfo(FontInfo fontInfo){ + super.setFontInfo(fontInfo); + + GlyphVector gv = createGlyphVector(this.source); + + int sourceLength = gv.getNumGlyphs(); + + this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null); + + recalcBounds(); + + return; + } + + /** + * 指定された点座標が文字列のどこを示すか判定する。 + * @param pt 点座標 + * @return 文字位置。座標が文字列以外を示す場合は-1を返す。 + */ + public int getCharIndex(Point pt){ + if( ! this.bounds.contains(pt) ) return -1; + + int sPos = 0; + int xPos = this.bounds.x; + int yPos = this.bounds.y; + for(GlyphVector gv : this.lines){ + Rectangle2D r2d = gv.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + rect.x = xPos; + rect.y = yPos; + int sourceLength = gv.getNumGlyphs(); + if(rect.contains(pt)){ + for(int pos = 0; pos < sourceLength; pos++){ + float span = getSpan(sPos, sPos+pos); + if(span+xPos > pt.x) return sPos + pos; + } + return -1; + } + yPos += rect.height; + sPos += sourceLength; + } + + return -1; + } + + /** + * {@inheritDoc} + * @param appendable {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public Appendable appendSelected(Appendable appendable) + throws IOException{ + if(this.selectStart < 0 || this.selectLast < 0) return appendable; + CharSequence subsel; + subsel = this.source.subSequence(this.selectStart, + this.selectLast + 1); + appendable.append(subsel); + return appendable; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSelect(){ + this.selectStart = -1; + this.selectLast = -1; + return; + } + + /** + * 指定した部分文字列を選択された状態にする。 + * @param start 文字列開始位置 + * @param last 文字列終了位置 + */ + public void select(int start, int last){ + if(start < last){ + this.selectStart = start; + this.selectLast = last; + }else{ + this.selectStart = last; + this.selectLast = start; + } + this.selectLast = Math.min(this.source.length() - 1, + this.selectLast ); + return; + } + + /** + * {@inheritDoc} + * @param from {@inheritDoc} + * @param to {@inheritDoc} + */ + @Override + public void drag(Point from, Point to){ + Point fromPt = from; + Point toPt = to; + if(fromPt.y > toPt.y || (fromPt.y == toPt.y && fromPt.x > toPt.x)){ + Point swapPt = fromPt; + fromPt = toPt; + toPt = swapPt; + } + + int fromDirection = GUIUtils.getDirection(this.bounds, fromPt); + int toDirection = GUIUtils.getDirection(this.bounds, toPt); + + if(fromDirection == toDirection){ + if( fromDirection == NORTH + || fromDirection == SOUTH){ + clearSelect(); + return; + } + } + + int fromIndex = -1; + int toIndex = -1; + + if(fromDirection == NORTH){ + fromIndex = 0; + } + if(toDirection == SOUTH){ + toIndex = this.source.length() - 1; + } + + if(fromIndex < 0){ + fromIndex = getCharIndex(fromPt); + } + if(toIndex < 0){ + toIndex = getCharIndex(toPt); + } + + if(fromIndex >= 0 && toIndex >= 0){ + select(fromIndex, toIndex); + return; + } + + int xPos = this.bounds.x; + int yPos = this.bounds.y; + int accumPos = 0; + for(GlyphVector gv : this.lines){ + int glyphStart = accumPos; + int glyphLast = accumPos + gv.getNumGlyphs() - 1; + Rectangle2D r2d = gv.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + rect.x += xPos; + rect.y = yPos; + + if( fromIndex < 0 + && GUIUtils.getDirection(rect, fromPt) == SOUTH){ + yPos += rect.height; + accumPos = glyphLast + 1; + continue; + }else if( toIndex < 0 + && GUIUtils.getDirection(rect, toPt) == NORTH){ + break; + } + + if(fromIndex < 0){ + int dir = GUIUtils.getDirection(rect, fromPt); + if(dir == EAST){ + fromIndex = glyphStart; + }else if(dir == WEST){ + fromIndex = glyphLast+1; + } + } + if(toIndex < 0){ + int dir = GUIUtils.getDirection(rect, toPt); + if(dir == EAST){ + toIndex = glyphStart - 1; + }else if(dir == WEST){ + toIndex = glyphLast; + } + } + + if(fromIndex >= 0 && toIndex >= 0){ + select(fromIndex, toIndex); + return; + } + + yPos += rect.height; + accumPos = glyphLast + 1; + } + + clearSelect(); + return; + } + + /** + * 文字列検索がヒットした箇所のハイライト描画を行う。 + * @param g グラフィックスコンテキスト + */ + private void paintRegexHitted(Graphics2D g){ + if(this.matchList.size() <= 0) return; + + FontMetrics metrics = g.getFontMetrics(); + final int ascent = metrics.getAscent(); + + int xPos = this.bounds.x; + int yPos = this.bounds.y + ascent; + + int accumPos = 0; + + for(GlyphVector line : this.lines){ + int glyphStart = accumPos; + int glyphLast = accumPos + line.getNumGlyphs() - 1; + + for(MatchInfo match : this.matchList){ + int matchStart = match.getStartPos(); + int matchLast = match.getEndPos() - 1; + + if(matchLast < glyphStart) continue; + if(glyphLast < matchStart) break; + + int hilightStart = Math.max(matchStart, glyphStart); + int hilightLast = Math.min(matchLast, glyphLast); + Shape shape; + shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); + Rectangle hilight = shape.getBounds(); + shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); + hilight.add(shape.getBounds()); + + if(match == this.hotTarget){ + g.setColor(COLOR_HOTTARGET); + }else{ + g.setColor(COLOR_SEARCHHIT); + } + + g.fillRect(xPos + hilight.x, + yPos + hilight.y, + hilight.width, + hilight.height ); + } + + Rectangle2D r2d = line.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + + yPos += rect.height; + + accumPos = glyphLast + 1; + } + + return; + } + + /** + * 選択文字列のハイライト描画を行う。 + * @param g グラフィックスコンテキスト + */ + private void paintSelected(Graphics2D g){ + if(this.selectStart < 0 || this.selectLast < 0) return; + + g.setColor(COLOR_SELECTION); + + int xPos = this.bounds.x; + int yPos = this.bounds.y; + + int accumPos = 0; + + for(GlyphVector line : this.lines){ + int glyphStart = accumPos; + int glyphLast = accumPos + line.getNumGlyphs() - 1; + + if(this.selectLast < glyphStart) break; + + Rectangle2D r2d = line.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + + if(glyphLast < this.selectStart){ + yPos += rect.height; + accumPos = glyphLast + 1; + continue; + } + + int hilightStart = Math.max(this.selectStart, glyphStart); + int hilightLast = Math.min(this.selectLast, glyphLast); + Shape shape; + shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); + Rectangle hilight = shape.getBounds(); + shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); + hilight.add(shape.getBounds()); + + g.fillRect(xPos + hilight.x, + yPos, + hilight.width, + hilight.height ); + + yPos += rect.height; + accumPos = glyphLast + 1; + } + + return; + } + + /** + * アンカー文字列のハイライト描画を行う。 + * @param g グラフィックスコンテキスト + */ + private void paintAnchorBack(Graphics2D g){ + if(this.anchorSet == null) return; + if(this.anchorSet.size() <= 0) return; + + FontMetrics metrics = g.getFontMetrics(); + final int ascent = metrics.getAscent(); + + g.setColor(Color.GRAY); + + int xPos = this.bounds.x; + int yPos = this.bounds.y + ascent; + + int accumPos = 0; + + for(GlyphVector line : this.lines){ + int glyphStart = accumPos; + int glyphLast = accumPos + line.getNumGlyphs() - 1; + + for(Anchor anchor : this.anchorSet){ + int anchorStart = anchor.getStartPos(); + int anchorLast = anchor.getEndPos() - 1; + + if(anchorLast < glyphStart) continue; + if(glyphLast < anchorStart) break; + + int hilightStart = Math.max(anchorStart, glyphStart); + int hilightLast = Math.min(anchorLast, glyphLast); + Shape shape; + shape = line.getGlyphLogicalBounds(hilightStart - glyphStart); + Rectangle hilight = shape.getBounds(); + shape = line.getGlyphLogicalBounds(hilightLast - glyphStart); + hilight.add(shape.getBounds()); + + g.fillRect(xPos + hilight.x, + yPos + hilight.y, + hilight.width, + hilight.height ); + } + + Rectangle2D r2d = line.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + + yPos += rect.height; + + accumPos = glyphLast + 1; + } + + return; + } + + /** + * {@inheritDoc} + * @param g {@inheritDoc} + */ + @Override + public void paint(Graphics2D g){ + g.setFont(this.fontInfo.getFont()); + FontMetrics metrics = g.getFontMetrics(); + int ascent = metrics.getAscent(); + + int xPos = this.bounds.x; + int yPos = this.bounds.y + ascent; + + paintAnchorBack(g); + paintRegexHitted(g); + paintSelected(g); + + g.setColor(this.foregroundColor); + for(GlyphVector gv : this.lines){ + g.drawGlyphVector(gv, xPos, yPos); + + Rectangle2D r2d = gv.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + + yPos += rect.height; + } + + return; + } + + /** + * 与えられた座標にアンカー文字列が存在すればAnchorを返す。 + * @param pt 座標 + * @return アンカー + */ + public Anchor getAnchor(Point pt){ + int targetIdx = getCharIndex(pt); + if(targetIdx < 0) return null; + + for(Anchor anchor : this.anchorSet){ + int anchorStart = anchor.getStartPos(); + int anchorEnd = anchor.getEndPos(); + if(anchorStart <= targetIdx && targetIdx <= anchorEnd - 1){ + return anchor; + } + } + + return null; + } + + /** + * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。 + * @param pt 座標 + * @return 検索マッチインデックス + */ + public int getRegexMatchIndex(Point pt){ + int targetIdx = getCharIndex(pt); + if(targetIdx < 0) return -1; + + int index = 0; + for(MatchInfo info : this.matchList){ + int matchStart = info.getStartPos(); + int matchEnd = info.getEndPos(); + if(matchStart <= targetIdx && targetIdx <= matchEnd - 1){ + return index; + } + index++; + } + + return -1; + } + + /** + * 検索文字列パターンを設定する。 + * @param searchRegex パターン + * @return ヒット数 + */ + public int setRegex(Pattern searchRegex){ + clearHotTarget(); + this.matchList.clear(); + if(searchRegex == null) return 0; + + Matcher matcher = searchRegex.matcher(this.source); + while(matcher.find()){ + int startPos = matcher.start(); + int endPos = matcher.end(); + if(startPos >= endPos) break; // 長さ0マッチは無視 + MatchInfo matchInfo = new MatchInfo(startPos, endPos); + this.matchList.add(matchInfo); + } + + return getRegexMatches(); + } + + /** + * 検索ハイライトインデックスを返す。 + * @return 検索ハイライトインデックス。見つからなければ-1。 + */ + public int getHotTargetIndex(){ + return this.matchList.indexOf(this.hotTarget); + } + + /** + * 検索ハイライトを設定する。 + * @param index ハイライトインデックス。負ならハイライト全クリア。 + */ + public void setHotTargetIndex(int index){ + if(index < 0){ + clearHotTarget(); + return; + } + this.hotTarget = this.matchList.get(index); + return; + } + + /** + * 検索一致件数を返す。 + * @return 検索一致件数 + */ + public int getRegexMatches(){ + return this.matchList.size(); + } + + /** + * 特別な検索ハイライト描画をクリアする。 + */ + public void clearHotTarget(){ + this.hotTarget = null; + return; + } + + /** + * 特別な検索ハイライト領域の寸法を返す。 + * @return ハイライト領域寸法 + */ + public Rectangle getHotTargetRectangle(){ + Rectangle result = null; + + if(this.hotTarget == null) return result; + + int xPos = this.bounds.x; + int yPos = this.bounds.y; + + int accumPos = 0; + + int matchStart = this.hotTarget.getStartPos(); + int matchLast = this.hotTarget.getEndPos() - 1; + + for(GlyphVector gv : this.lines){ + int glyphStart = accumPos; + int glyphLast = accumPos + gv.getNumGlyphs() - 1; + + if(matchLast < glyphStart) break; + + if(matchStart <= glyphLast){ + int hilightStart = Math.max(matchStart, glyphStart); + int hilightLast = Math.min(matchLast, glyphLast); + + Shape shape; + shape = gv.getGlyphLogicalBounds(hilightStart - glyphStart); + Rectangle hilight = shape.getBounds(); + shape = gv.getGlyphLogicalBounds(hilightLast - glyphStart); + hilight.add(shape.getBounds()); + + Rectangle temp = new Rectangle(xPos + hilight.x, + yPos, + hilight.width, + hilight.height); + if(result == null){ + result = temp; + }else{ + result.add(temp); + } + } + + Rectangle2D r2d = gv.getLogicalBounds(); + Rectangle rect = r2d.getBounds(); + yPos += rect.height; + + accumPos = glyphLast + 1; + } + + return result; + } + + /** + * 検索ヒット情報。 + */ + private static class MatchInfo{ + + private final int startPos; + private final int endPos; + + /** + * コンストラクタ。 + * @param startPos ヒット開始位置 + * @param endPos ヒット終了位置 + */ + public MatchInfo(int startPos, int endPos){ + super(); + this.startPos = startPos; + this.endPos = endPos; + return; + } + + /** + * ヒット開始位置を取得する。 + * @return ヒット開始位置 + */ + public int getStartPos(){ + return this.startPos; + } + + /** + * ヒット終了位置を取得する。 + * @return ヒット終了位置 + */ + public int getEndPos(){ + return this.endPos; + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/HelpFrame.java b/src/main/java/jp/sourceforge/jindolf/HelpFrame.java index 93c42b2..772a0e8 100644 --- a/src/main/java/jp/sourceforge/jindolf/HelpFrame.java +++ b/src/main/java/jp/sourceforge/jindolf/HelpFrame.java @@ -1,185 +1,185 @@ -/* - * help frame - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.IOException; -import java.net.URL; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTabbedPane; -import javax.swing.JTextArea; -import javax.swing.border.Border; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; - -/** - * ヘルプ画面。 - */ -@SuppressWarnings("serial") -public class HelpFrame extends JFrame - implements ActionListener, HyperlinkListener{ - - private static final String HELP_HTML = "resources/html/help.html"; - - private final JTabbedPane tabPanel = new JTabbedPane(); - private final JEditorPane htmlView = new JEditorPane(); - private final JTextArea vmInfo = new JTextArea(); - private final JButton closeButton = new JButton("閉じる"); - - /** - * コンストラクタ。 - */ - public HelpFrame(){ - super(Jindolf.TITLE + " ヘルプ"); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - this.htmlView.setEditable(false); - this.htmlView.setContentType("text/html"); - this.htmlView.putClientProperty(JEditorPane.W3C_LENGTH_UNITS, - Boolean.TRUE); - this.htmlView.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, - Boolean.TRUE); - Border border = BorderFactory.createEmptyBorder(0, 0, 0, 0); - this.htmlView.setBorder(border); - this.htmlView.addHyperlinkListener(this); - this.htmlView.setComponentPopupMenu(new TextPopup()); - - this.vmInfo.setEditable(false); - this.vmInfo.setLineWrap(true); - this.vmInfo.setComponentPopupMenu(new TextPopup()); - - this.closeButton.addActionListener(this); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter(){ - @Override - public void windowClosing(WindowEvent event){ - close(); - } - }); - - URL topUrl = Jindolf.getResource(HELP_HTML); - loadURL(topUrl); - - StringBuilder info = new StringBuilder(); - info.append(EnvInfo.getVMInfo()); - AppSetting setting = Jindolf.getAppSetting(); - if(setting.useConfigPath()){ - info.append("設定格納ディレクトリ : " - + setting.getConfigPath().getPath() ); - }else{ - info.append("※ 設定格納ディレクトリは使っていません。"); - } - this.vmInfo.setText(info.toString()); - - design(); - - return; - } - - /** - * デザインを行う。 - */ - private void design(){ - Container content = this.getContentPane(); - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - content.setLayout(layout); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.insets = new Insets(5, 5, 5, 5); - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - - JScrollPane sc = new JScrollPane(this.htmlView); - this.tabPanel.add("ヘルプ", sc); - sc = new JScrollPane(this.vmInfo); - this.tabPanel.add("実行環境", sc); - content.add(this.tabPanel, constraints); - - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - constraints.weightx = 0.0; - constraints.anchor = GridBagConstraints.EAST; - constraints.fill = GridBagConstraints.NONE; - content.add(this.closeButton, constraints); - - return; - } - - /** - * ウィンドウを閉じる。 - */ - private void close(){ - setVisible(false); - return; - } - - /** - * URLの示すHTML文書を表示する。 - * @param url URL - */ - private void loadURL(URL url){ - if(url == null) return; - - try{ - this.htmlView.setPage(url); - }catch(IOException e){ - Jindolf.logger().warn("ヘルプファイルが読み込めません", e); - assert false; - } - - return; - } - - /** - * {@inheritDoc} - * 閉じるボタン押下処理。 - * @param event ボタン押下イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - if(event.getSource() != this.closeButton) return; - close(); - return; - } - - /** - * {@inheritDoc} - * リンククリック処理。 - * @param event リンククリックイベント {@inheritDoc} - */ - @Override - public void hyperlinkUpdate(HyperlinkEvent event){ - if(event.getEventType() != HyperlinkEvent.EventType.ACTIVATED){ - return; - } - - URL url = event.getURL(); - loadURL(url); - - return; - } - -} +/* + * help frame + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.net.URL; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +/** + * ヘルプ画面。 + */ +@SuppressWarnings("serial") +public class HelpFrame extends JFrame + implements ActionListener, HyperlinkListener{ + + private static final String HELP_HTML = "resources/html/help.html"; + + private final JTabbedPane tabPanel = new JTabbedPane(); + private final JEditorPane htmlView = new JEditorPane(); + private final JTextArea vmInfo = new JTextArea(); + private final JButton closeButton = new JButton("閉じる"); + + /** + * コンストラクタ。 + */ + public HelpFrame(){ + super(Jindolf.TITLE + " ヘルプ"); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + this.htmlView.setEditable(false); + this.htmlView.setContentType("text/html"); + this.htmlView.putClientProperty(JEditorPane.W3C_LENGTH_UNITS, + Boolean.TRUE); + this.htmlView.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, + Boolean.TRUE); + Border border = BorderFactory.createEmptyBorder(0, 0, 0, 0); + this.htmlView.setBorder(border); + this.htmlView.addHyperlinkListener(this); + this.htmlView.setComponentPopupMenu(new TextPopup()); + + this.vmInfo.setEditable(false); + this.vmInfo.setLineWrap(true); + this.vmInfo.setComponentPopupMenu(new TextPopup()); + + this.closeButton.addActionListener(this); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter(){ + @Override + public void windowClosing(WindowEvent event){ + close(); + } + }); + + URL topUrl = Jindolf.getResource(HELP_HTML); + loadURL(topUrl); + + StringBuilder info = new StringBuilder(); + info.append(EnvInfo.getVMInfo()); + AppSetting setting = Jindolf.getAppSetting(); + if(setting.useConfigPath()){ + info.append("設定格納ディレクトリ : " + + setting.getConfigPath().getPath() ); + }else{ + info.append("※ 設定格納ディレクトリは使っていません。"); + } + this.vmInfo.setText(info.toString()); + + design(); + + return; + } + + /** + * デザインを行う。 + */ + private void design(){ + Container content = this.getContentPane(); + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + content.setLayout(layout); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.insets = new Insets(5, 5, 5, 5); + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + + JScrollPane sc = new JScrollPane(this.htmlView); + this.tabPanel.add("ヘルプ", sc); + sc = new JScrollPane(this.vmInfo); + this.tabPanel.add("実行環境", sc); + content.add(this.tabPanel, constraints); + + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + constraints.weightx = 0.0; + constraints.anchor = GridBagConstraints.EAST; + constraints.fill = GridBagConstraints.NONE; + content.add(this.closeButton, constraints); + + return; + } + + /** + * ウィンドウを閉じる。 + */ + private void close(){ + setVisible(false); + return; + } + + /** + * URLの示すHTML文書を表示する。 + * @param url URL + */ + private void loadURL(URL url){ + if(url == null) return; + + try{ + this.htmlView.setPage(url); + }catch(IOException e){ + Jindolf.logger().warn("ヘルプファイルが読み込めません", e); + assert false; + } + + return; + } + + /** + * {@inheritDoc} + * 閉じるボタン押下処理。 + * @param event ボタン押下イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + if(event.getSource() != this.closeButton) return; + close(); + return; + } + + /** + * {@inheritDoc} + * リンククリック処理。 + * @param event リンククリックイベント {@inheritDoc} + */ + @Override + public void hyperlinkUpdate(HyperlinkEvent event){ + if(event.getEventType() != HyperlinkEvent.EventType.ACTIVATED){ + return; + } + + URL url = event.getURL(); + loadURL(url); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/HtmlSequence.java b/src/main/java/jp/sourceforge/jindolf/HtmlSequence.java index 8d62782..791586d 100644 --- a/src/main/java/jp/sourceforge/jindolf/HtmlSequence.java +++ b/src/main/java/jp/sourceforge/jindolf/HtmlSequence.java @@ -1,107 +1,107 @@ -/* - * HTML sequence - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.net.URL; -import jp.sourceforge.jindolf.parser.DecodedContent; - -/** - * HTML本文。 - * 任意のDecodedContentをラップする。 - * 由来となるURLと受信時刻を含む。 - */ -// TODO Dateも含めたい -public class HtmlSequence implements CharSequence{ - - private final URL url; - private final long datems; - private final DecodedContent html; - - /** - * コンストラクタ。 - * @param url 由来のURL - * @param datems 受信時刻(エポックミリ秒) - * @param html HTML本文 - * @throws java.lang.NullPointerException 引数がnull - */ - public HtmlSequence(URL url, long datems, DecodedContent html) - throws NullPointerException{ - if(url == null || html == null){ - throw new NullPointerException(); - } - this.url = url; - this.datems = datems; - this.html = html; - return; - } - - /** - * URLを返す。 - * @return URL - */ - public URL getURL(){ - return this.url; - } - - /** - * 受信時刻を返す。 - * 単位はエポック時からのミリ秒。 - * @return 受信時刻 - */ - public long getDateMs(){ - return this.datems; - } - - /** - * HTML文字列を返す。 - * @return HTML文字列 - */ - public DecodedContent getContent(){ - return this.html; - } - - /** - * {@inheritDoc} - * @param index {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char charAt(int index){ - return this.html.charAt(index); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int length(){ - return this.html.length(); - } - - /** - * {@inheritDoc} - * @param start {@inheritDoc} - * @param end {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public CharSequence subSequence(int start, int end){ - return this.html.subSequence(start, end); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return this.html.toString(); - } - -} +/* + * HTML sequence + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.net.URL; +import jp.sourceforge.jindolf.parser.DecodedContent; + +/** + * HTML本文。 + * 任意のDecodedContentをラップする。 + * 由来となるURLと受信時刻を含む。 + */ +// TODO Dateも含めたい +public class HtmlSequence implements CharSequence{ + + private final URL url; + private final long datems; + private final DecodedContent html; + + /** + * コンストラクタ。 + * @param url 由来のURL + * @param datems 受信時刻(エポックミリ秒) + * @param html HTML本文 + * @throws java.lang.NullPointerException 引数がnull + */ + public HtmlSequence(URL url, long datems, DecodedContent html) + throws NullPointerException{ + if(url == null || html == null){ + throw new NullPointerException(); + } + this.url = url; + this.datems = datems; + this.html = html; + return; + } + + /** + * URLを返す。 + * @return URL + */ + public URL getURL(){ + return this.url; + } + + /** + * 受信時刻を返す。 + * 単位はエポック時からのミリ秒。 + * @return 受信時刻 + */ + public long getDateMs(){ + return this.datems; + } + + /** + * HTML文字列を返す。 + * @return HTML文字列 + */ + public DecodedContent getContent(){ + return this.html; + } + + /** + * {@inheritDoc} + * @param index {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char charAt(int index){ + return this.html.charAt(index); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int length(){ + return this.html.length(); + } + + /** + * {@inheritDoc} + * @param start {@inheritDoc} + * @param end {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public CharSequence subSequence(int start, int end){ + return this.html.subSequence(start, end); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return this.html.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/HttpUtils.java b/src/main/java/jp/sourceforge/jindolf/HttpUtils.java index 57993d3..d1ff8ef 100644 --- a/src/main/java/jp/sourceforge/jindolf/HttpUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/HttpUtils.java @@ -1,225 +1,225 @@ -/* - * HTTP utilities - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URLConnection; -import java.text.NumberFormat; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * HTTP関連のユーティリティ群。 - */ -public final class HttpUtils{ - - private static final String TOKEN_REGEX = - "([^\\(\\)<>@,;:\\\"/\\[\\]\\?=\\{\\}\\p{Blank}\\p{Cntrl}]+)"; - private static final String MTYPE_REGEX = - "[\\p{Blank}]*" - + TOKEN_REGEX + "/" + TOKEN_REGEX - + "[\\p{Blank}]*"; - private static final String PARAM_REGEX = - "[\\p{Blank}]*;[\\p{Blank}]*" - + TOKEN_REGEX - + "[\\p{Blank}]*=[\\p{Blank}]*" - + "(" + TOKEN_REGEX + "|" + "(\"[^\\p{Cntrl}\\\"]*\")" + ")"; - private static final Pattern MTYPE_PATTERN = Pattern.compile(MTYPE_REGEX); - private static final Pattern ATTR_PATTERN = Pattern.compile(PARAM_REGEX); - - private static final NumberFormat THROUGHPUT_FORMAT; - private static final NumberFormat SIZE_FORMAT; - - static{ - THROUGHPUT_FORMAT = NumberFormat.getInstance(); - THROUGHPUT_FORMAT.setMaximumFractionDigits(1); - THROUGHPUT_FORMAT.setMinimumFractionDigits(1); - THROUGHPUT_FORMAT.setGroupingUsed(true); - SIZE_FORMAT = NumberFormat.getInstance(); - SIZE_FORMAT.setGroupingUsed(true); - } - - - /** - * 隠れコンストラクタ。 - */ - private HttpUtils(){ - super(); - assert false; - throw new AssertionError(); - } - - - /** - * ネットワークのスループット報告用文字列を生成する。 - * @param size 転送サイズ(バイト数) - * @param nano 所要時間(ナノ秒) - * @return スループット文字列 - */ - public static String throughput(long size, long nano){ - if(size <= 0 || nano <= 0) return ""; - - double sec = ((double)nano) / (1000.0 * 1000.0 * 1000.0); - double rate = ((double)size) / sec; - - String unit = ""; - if(rate >= 1500.0){ - rate /= 1000.0; - unit = "K"; - } - if(rate >= 1500.0){ - rate /= 1000.0; - unit = "M"; - } - - String result = SIZE_FORMAT.format(size) + "Bytes " - + THROUGHPUT_FORMAT.format(rate) + unit - + "Bytes/sec"; - return result; - } - - /** - * HTTPセッションの各種結果を文字列化する。 - * @param conn HTTPコネクション - * @param size 転送サイズ - * @param nano 転送に要したナノ秒 - * @return セッション結果 - */ - public static String formatHttpStat(HttpURLConnection conn, - long size, - long nano ){ - String method = conn.getRequestMethod(); - String url = conn.getURL().toString(); - - String responseCode; - try{ - responseCode = String.valueOf(conn.getResponseCode()); - }catch(IOException e){ - responseCode = "???"; - } - - String responseMessage; - try{ - responseMessage = conn.getResponseMessage(); - }catch(IOException e){ - responseMessage = "???"; - } - - String throughput = throughput(size, nano); - - String message = method - + " " + url - + " [" + responseCode - + " " + responseMessage + "]" - + " " + throughput; - - return message; - } - - /** - * ユーザエージェント名を返す。 - * @return ユーザエージェント名 - */ - public static String getUserAgentName(){ - StringBuilder result = new StringBuilder(); - result.append(Jindolf.TITLE).append("/").append(Jindolf.VERSION); - - StringBuilder rawComment = new StringBuilder(); - if(EnvInfo.OS_NAME != null){ - if(rawComment.length() > 0) rawComment.append("; "); - rawComment.append(EnvInfo.OS_NAME); - } - if(EnvInfo.OS_VERSION != null){ - if(rawComment.length() > 0) rawComment.append("; "); - rawComment.append(EnvInfo.OS_VERSION); - } - if(EnvInfo.OS_ARCH != null){ - if(rawComment.length() > 0) rawComment.append("; "); - rawComment.append(EnvInfo.OS_ARCH); - } - if(EnvInfo.JAVA_VENDOR != null){ - if(rawComment.length() > 0) rawComment.append("; "); - rawComment.append(EnvInfo.JAVA_VENDOR); - } - if(EnvInfo.JAVA_VERSION != null){ - if(rawComment.length() > 0) rawComment.append("; "); - rawComment.append(EnvInfo.JAVA_VERSION); - } - - CharSequence comment = escapeHttpComment(rawComment); - if(comment != null) result.append(" ").append(comment); - - return result.toString(); - } - - /** - * 与えられた文字列からHTTPコメントを生成する。 - * @param comment コメント - * @return HTTPコメント - */ - public static String escapeHttpComment(CharSequence comment){ - if(comment == null) return null; - if(comment.length() <= 0) return null; - - String result = comment.toString(); - result = result.replaceAll("\\(", "\\\\("); - result = result.replaceAll("\\)", "\\\\)"); - result = result.replaceAll("[\\u0000-\\u001f]", "?"); - result = result.replaceAll("[\\u007f-\\uffff]", "?"); - result = "(" + result + ")"; - - return result; - } - - /** - * HTTP応答からCharsetを取得する。 - * @param connection HTTP接続 - * @return Charset文字列 - */ - public static String getHTMLCharset(URLConnection connection){ - String contentType = connection.getContentType(); - if(contentType == null) return null; - return getHTMLCharset(contentType); - } - - /** - * ContentTypeからCharsetを取得する。 - * @param contentType ContentType - * @return Charset文字列 - */ - public static String getHTMLCharset(String contentType){ - Matcher matcher; - boolean matchResult; - int lastPos; - - matcher = MTYPE_PATTERN.matcher(contentType); - matchResult = matcher.lookingAt(); - if(!matchResult) return null; - lastPos = matcher.end(); - String type = matcher.group(1); - String subtype = matcher.group(2); - - if(!type.equalsIgnoreCase("text")) return null; - if(!subtype.equalsIgnoreCase("html")) return null; - - matcher.usePattern(ATTR_PATTERN); - - String charset = null; - for(;;){ - matchResult = matcher.find(lastPos); - if(!matchResult) break; - lastPos = matcher.end(); - String attribute = matcher.group(1); - String value = matcher.group(2); - if(attribute.equalsIgnoreCase("charset")) charset = value; - } - return charset; - } - -} +/* + * HTTP utilities + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URLConnection; +import java.text.NumberFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTTP関連のユーティリティ群。 + */ +public final class HttpUtils{ + + private static final String TOKEN_REGEX = + "([^\\(\\)<>@,;:\\\"/\\[\\]\\?=\\{\\}\\p{Blank}\\p{Cntrl}]+)"; + private static final String MTYPE_REGEX = + "[\\p{Blank}]*" + + TOKEN_REGEX + "/" + TOKEN_REGEX + + "[\\p{Blank}]*"; + private static final String PARAM_REGEX = + "[\\p{Blank}]*;[\\p{Blank}]*" + + TOKEN_REGEX + + "[\\p{Blank}]*=[\\p{Blank}]*" + + "(" + TOKEN_REGEX + "|" + "(\"[^\\p{Cntrl}\\\"]*\")" + ")"; + private static final Pattern MTYPE_PATTERN = Pattern.compile(MTYPE_REGEX); + private static final Pattern ATTR_PATTERN = Pattern.compile(PARAM_REGEX); + + private static final NumberFormat THROUGHPUT_FORMAT; + private static final NumberFormat SIZE_FORMAT; + + static{ + THROUGHPUT_FORMAT = NumberFormat.getInstance(); + THROUGHPUT_FORMAT.setMaximumFractionDigits(1); + THROUGHPUT_FORMAT.setMinimumFractionDigits(1); + THROUGHPUT_FORMAT.setGroupingUsed(true); + SIZE_FORMAT = NumberFormat.getInstance(); + SIZE_FORMAT.setGroupingUsed(true); + } + + + /** + * 隠れコンストラクタ。 + */ + private HttpUtils(){ + super(); + assert false; + throw new AssertionError(); + } + + + /** + * ネットワークのスループット報告用文字列を生成する。 + * @param size 転送サイズ(バイト数) + * @param nano 所要時間(ナノ秒) + * @return スループット文字列 + */ + public static String throughput(long size, long nano){ + if(size <= 0 || nano <= 0) return ""; + + double sec = ((double)nano) / (1000.0 * 1000.0 * 1000.0); + double rate = ((double)size) / sec; + + String unit = ""; + if(rate >= 1500.0){ + rate /= 1000.0; + unit = "K"; + } + if(rate >= 1500.0){ + rate /= 1000.0; + unit = "M"; + } + + String result = SIZE_FORMAT.format(size) + "Bytes " + + THROUGHPUT_FORMAT.format(rate) + unit + + "Bytes/sec"; + return result; + } + + /** + * HTTPセッションの各種結果を文字列化する。 + * @param conn HTTPコネクション + * @param size 転送サイズ + * @param nano 転送に要したナノ秒 + * @return セッション結果 + */ + public static String formatHttpStat(HttpURLConnection conn, + long size, + long nano ){ + String method = conn.getRequestMethod(); + String url = conn.getURL().toString(); + + String responseCode; + try{ + responseCode = String.valueOf(conn.getResponseCode()); + }catch(IOException e){ + responseCode = "???"; + } + + String responseMessage; + try{ + responseMessage = conn.getResponseMessage(); + }catch(IOException e){ + responseMessage = "???"; + } + + String throughput = throughput(size, nano); + + String message = method + + " " + url + + " [" + responseCode + + " " + responseMessage + "]" + + " " + throughput; + + return message; + } + + /** + * ユーザエージェント名を返す。 + * @return ユーザエージェント名 + */ + public static String getUserAgentName(){ + StringBuilder result = new StringBuilder(); + result.append(Jindolf.TITLE).append("/").append(Jindolf.VERSION); + + StringBuilder rawComment = new StringBuilder(); + if(EnvInfo.OS_NAME != null){ + if(rawComment.length() > 0) rawComment.append("; "); + rawComment.append(EnvInfo.OS_NAME); + } + if(EnvInfo.OS_VERSION != null){ + if(rawComment.length() > 0) rawComment.append("; "); + rawComment.append(EnvInfo.OS_VERSION); + } + if(EnvInfo.OS_ARCH != null){ + if(rawComment.length() > 0) rawComment.append("; "); + rawComment.append(EnvInfo.OS_ARCH); + } + if(EnvInfo.JAVA_VENDOR != null){ + if(rawComment.length() > 0) rawComment.append("; "); + rawComment.append(EnvInfo.JAVA_VENDOR); + } + if(EnvInfo.JAVA_VERSION != null){ + if(rawComment.length() > 0) rawComment.append("; "); + rawComment.append(EnvInfo.JAVA_VERSION); + } + + CharSequence comment = escapeHttpComment(rawComment); + if(comment != null) result.append(" ").append(comment); + + return result.toString(); + } + + /** + * 与えられた文字列からHTTPコメントを生成する。 + * @param comment コメント + * @return HTTPコメント + */ + public static String escapeHttpComment(CharSequence comment){ + if(comment == null) return null; + if(comment.length() <= 0) return null; + + String result = comment.toString(); + result = result.replaceAll("\\(", "\\\\("); + result = result.replaceAll("\\)", "\\\\)"); + result = result.replaceAll("[\\u0000-\\u001f]", "?"); + result = result.replaceAll("[\\u007f-\\uffff]", "?"); + result = "(" + result + ")"; + + return result; + } + + /** + * HTTP応答からCharsetを取得する。 + * @param connection HTTP接続 + * @return Charset文字列 + */ + public static String getHTMLCharset(URLConnection connection){ + String contentType = connection.getContentType(); + if(contentType == null) return null; + return getHTMLCharset(contentType); + } + + /** + * ContentTypeからCharsetを取得する。 + * @param contentType ContentType + * @return Charset文字列 + */ + public static String getHTMLCharset(String contentType){ + Matcher matcher; + boolean matchResult; + int lastPos; + + matcher = MTYPE_PATTERN.matcher(contentType); + matchResult = matcher.lookingAt(); + if(!matchResult) return null; + lastPos = matcher.end(); + String type = matcher.group(1); + String subtype = matcher.group(2); + + if(!type.equalsIgnoreCase("text")) return null; + if(!subtype.equalsIgnoreCase("html")) return null; + + matcher.usePattern(ATTR_PATTERN); + + String charset = null; + for(;;){ + matchResult = matcher.find(lastPos); + if(!matchResult) break; + lastPos = matcher.end(); + String attribute = matcher.group(1); + String value = matcher.group(2); + if(attribute.equalsIgnoreCase("charset")) charset = value; + } + return charset; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ImtblAffineTx.java b/src/main/java/jp/sourceforge/jindolf/ImtblAffineTx.java index 8d4f188..cd4551f 100644 --- a/src/main/java/jp/sourceforge/jindolf/ImtblAffineTx.java +++ b/src/main/java/jp/sourceforge/jindolf/ImtblAffineTx.java @@ -1,201 +1,201 @@ -/* - * immutable Affine transformation - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.geom.AffineTransform; - -/** - * 不変のアフィン変換。 - * 恒等変換のみをサポート。 - */ -@SuppressWarnings("serial") -public final class ImtblAffineTx extends AffineTransform{ - - /** 恒等変換のシングルトン。 */ - public static final AffineTransform IDENTITY = new ImtblAffineTx(); - - static{ - assert IDENTITY.isIdentity(); - } - - /** - * 隠しコンストラクタ。 - * 恒等変換のみをサポート。 - */ - private ImtblAffineTx(){ - super(); - return; - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param tx {@inheritDoc} - */ - @Override - public void concatenate(AffineTransform tx){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param tx {@inheritDoc} - */ - @Override - public void preConcatenate(AffineTransform tx){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param theta {@inheritDoc} - */ - @Override - public void rotate(double theta){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param theta {@inheritDoc} - * @param x {@inheritDoc} - * @param y {@inheritDoc} - */ - @Override - public void rotate(double theta, double x, double y){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param sx {@inheritDoc} - * @param sy {@inheritDoc} - */ - @Override - public void scale(double sx, double sy){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - */ - @Override - public void setToIdentity(){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param theta {@inheritDoc} - */ - @Override - public void setToRotation(double theta){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param theta {@inheritDoc} - * @param x {@inheritDoc} - * @param y {@inheritDoc} - */ - @Override - public void setToRotation(double theta, double x, double y){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param sx {@inheritDoc} - * @param sy {@inheritDoc} - */ - @Override - public void setToScale(double sx, double sy){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param shx {@inheritDoc} - * @param shy {@inheritDoc} - */ - @Override - public void setToShear(double shx, double shy){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param tx {@inheritDoc} - * @param ty {@inheritDoc} - */ - @Override - public void setToTranslation(double tx, double ty){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param tx {@inheritDoc} - */ - @Override - public void setTransform(AffineTransform tx){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param m00 {@inheritDoc} - * @param m10 {@inheritDoc} - * @param m01 {@inheritDoc} - * @param m11 {@inheritDoc} - * @param m02 {@inheritDoc} - * @param m12 {@inheritDoc} - */ - @Override - public void setTransform(double m00, double m10, double m01, - double m11, double m02, double m12){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param shx {@inheritDoc} - * @param shy {@inheritDoc} - */ - @Override - public void shear(double shx, double shy){ - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * ※未サポート。 - * @param tx {@inheritDoc} - * @param ty {@inheritDoc} - */ - @Override - public void translate(double tx, double ty){ - throw new UnsupportedOperationException(); - } - - // TODO JRE1.6での穴を埋めるのはどうしよう… -} +/* + * immutable Affine transformation + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.geom.AffineTransform; + +/** + * 不変のアフィン変換。 + * 恒等変換のみをサポート。 + */ +@SuppressWarnings("serial") +public final class ImtblAffineTx extends AffineTransform{ + + /** 恒等変換のシングルトン。 */ + public static final AffineTransform IDENTITY = new ImtblAffineTx(); + + static{ + assert IDENTITY.isIdentity(); + } + + /** + * 隠しコンストラクタ。 + * 恒等変換のみをサポート。 + */ + private ImtblAffineTx(){ + super(); + return; + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param tx {@inheritDoc} + */ + @Override + public void concatenate(AffineTransform tx){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param tx {@inheritDoc} + */ + @Override + public void preConcatenate(AffineTransform tx){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param theta {@inheritDoc} + */ + @Override + public void rotate(double theta){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param theta {@inheritDoc} + * @param x {@inheritDoc} + * @param y {@inheritDoc} + */ + @Override + public void rotate(double theta, double x, double y){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param sx {@inheritDoc} + * @param sy {@inheritDoc} + */ + @Override + public void scale(double sx, double sy){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + */ + @Override + public void setToIdentity(){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param theta {@inheritDoc} + */ + @Override + public void setToRotation(double theta){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param theta {@inheritDoc} + * @param x {@inheritDoc} + * @param y {@inheritDoc} + */ + @Override + public void setToRotation(double theta, double x, double y){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param sx {@inheritDoc} + * @param sy {@inheritDoc} + */ + @Override + public void setToScale(double sx, double sy){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param shx {@inheritDoc} + * @param shy {@inheritDoc} + */ + @Override + public void setToShear(double shx, double shy){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param tx {@inheritDoc} + * @param ty {@inheritDoc} + */ + @Override + public void setToTranslation(double tx, double ty){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param tx {@inheritDoc} + */ + @Override + public void setTransform(AffineTransform tx){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param m00 {@inheritDoc} + * @param m10 {@inheritDoc} + * @param m01 {@inheritDoc} + * @param m11 {@inheritDoc} + * @param m02 {@inheritDoc} + * @param m12 {@inheritDoc} + */ + @Override + public void setTransform(double m00, double m10, double m01, + double m11, double m02, double m12){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param shx {@inheritDoc} + * @param shy {@inheritDoc} + */ + @Override + public void shear(double shx, double shy){ + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * ※未サポート。 + * @param tx {@inheritDoc} + * @param ty {@inheritDoc} + */ + @Override + public void translate(double tx, double ty){ + throw new UnsupportedOperationException(); + } + + // TODO JRE1.6での穴を埋めるのはどうしよう… +} diff --git a/src/main/java/jp/sourceforge/jindolf/InterVMLock.java b/src/main/java/jp/sourceforge/jindolf/InterVMLock.java index 263f1a1..c3324b9 100644 --- a/src/main/java/jp/sourceforge/jindolf/InterVMLock.java +++ b/src/main/java/jp/sourceforge/jindolf/InterVMLock.java @@ -1,238 +1,238 @@ -/* - * inter-VM file locking - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * ロックファイルを用いたVM間ロックオブジェクト。 - * 大昔のNFSではうまく動かないかも。 - * 一度でもロックに成功したロックファイルはVM終了時に消されてしまうので注意。 - */ -public class InterVMLock{ - - /** 所持するロックオブジェクト一覧。 */ - private static final Set ownedLockSet = - Collections.synchronizedSet(new HashSet()); - - static{ - Runtime runtime = Runtime.getRuntime(); - runtime.addShutdownHook(new Thread(){ - @Override public void run(){ clearOwnedLockSet(); } - }); - } - - - private final File lockFile; - private boolean isFileOwner = false; - private FileOutputStream stream = null; - - - /** - * コンストラクタ。 - * この時点ではまだロックファイルの存在は確認されない。 - * @param lockFile ロックファイル - * @throws NullPointerException 引数がnull - */ - public InterVMLock(File lockFile) throws NullPointerException{ - if(lockFile == null) throw new NullPointerException(); - this.lockFile = lockFile; - return; - } - - - /** - * 所持するロックオブジェクト一覧への登録。 - * @param lock 登録するロックオブジェクト - */ - private static void addOwnedLock(InterVMLock lock){ - synchronized(ownedLockSet){ - if( ! lock.isFileOwner() ) return; - ownedLockSet.add(lock); - lock.getLockFile().deleteOnExit(); - } - return; - } - - /** - * 所持するロックオブジェクト一覧からの脱退。 - * @param lock 脱退対象のロックオブジェクト - */ - private static void removeOwnedLock(InterVMLock lock){ - synchronized(ownedLockSet){ - ownedLockSet.remove(lock); - } - return; - } - - /** - * 所持するロックオブジェクトすべてを解放しロックファイルを削除する。 - */ - private static void clearOwnedLockSet(){ - synchronized(ownedLockSet){ - for(InterVMLock lock : ownedLockSet){ - if( ! lock.isFileOwner() ) continue; - lock.release(); - try{ - lock.getLockFile().delete(); - }catch(SecurityException e){ - // NOTHING - } - } - ownedLockSet.clear(); - } - return; - } - - /** - * ロック対象のファイルを返す。 - * @return ロック対象ファイル - */ - public File getLockFile(){ - return this.lockFile; - } - - /** - * ロックファイルがディスク上に存在するか判定する。 - * @return 存在すればtrue - */ - public boolean isExistsFile(){ - if(this.lockFile.exists()){ - return true; - } - return false; - } - - /** - * このオブジェクトがロックファイルの作者であるか判定する。 - * @return 作者ならtrue - */ - public synchronized boolean isFileOwner(){ - return this.isFileOwner; - } - - /** - * ロックファイルのオープン中のストリームを返す。 - * ※ 排他制御目的のリソースなので、 - * 勝手に書き込んだりクローズしたりしないように。 - * @return オープン中のストリーム。オープンしてなければnull - */ - protected synchronized FileOutputStream getOpenedStream(){ - if(isFileOwner()) return this.stream; - return null; - } - - /** - * ロックファイルの強制削除を試みる。 - * @return 強制削除に成功すればtrue - */ - public synchronized boolean forceRemove(){ - if(isFileOwner()) release(); - - if( ! isExistsFile() ) return true; - - try{ - boolean result = this.lockFile.delete(); - if( ! result ) return false; - }catch(SecurityException e){ - return false; - } - - if(isExistsFile()) return false; - - return true; - } - - /** - * ロックファイルを生成する。 - * 生成されるロックファイルはVM終了時に削除されるよう登録される。 - * このメソッド実行中にVM終了が重なると、 - * ロックファイルが正しく削除されない場合がありうる。 - * @return 成功すればtrue - */ - protected synchronized boolean touchLockFile(){ - boolean result = false; - try{ - result = this.lockFile.createNewFile(); - }catch(IOException e){ - // NOTHING - }catch(SecurityException e){ - // NOTHING - } - if(result == false){ - return false; - } - - try{ - this.isFileOwner = true; - this.stream = new FileOutputStream(this.lockFile); - }catch(FileNotFoundException e){ - assert false; - this.isFileOwner = false; - this.stream = null; - try{ - this.lockFile.delete(); - }catch(SecurityException e2){ - // NOTHING - } - return false; - } - - addOwnedLock(this); - - return true; - } - - /** - * ロックを試みる。 - * このメソッドはブロックしない。 - * @return すでにロック済みもしくはロックに成功すればtrue - */ - public synchronized boolean tryLock(){ - if( isFileOwner() ) return true; - - if(isExistsFile()) return false; - if(touchLockFile() != true) return false; - - return true; - } - - /** - * ロックを解除する。 - * 自分が作者であるロックファイルは閉じられ削除される。 - * 削除に失敗しても無視。 - */ - public synchronized void release(){ - if( ! isFileOwner() ) return; - - try{ - this.stream.close(); - }catch(IOException e){ - // NOTHING - }finally{ - this.stream = null; - try{ - this.lockFile.delete(); - }catch(SecurityException e){ - // NOTHING - }finally{ - removeOwnedLock(this); - this.isFileOwner = false; - } - } - - return; - } - -} +/* + * inter-VM file locking + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * ロックファイルを用いたVM間ロックオブジェクト。 + * 大昔のNFSではうまく動かないかも。 + * 一度でもロックに成功したロックファイルはVM終了時に消されてしまうので注意。 + */ +public class InterVMLock{ + + /** 所持するロックオブジェクト一覧。 */ + private static final Set ownedLockSet = + Collections.synchronizedSet(new HashSet()); + + static{ + Runtime runtime = Runtime.getRuntime(); + runtime.addShutdownHook(new Thread(){ + @Override public void run(){ clearOwnedLockSet(); } + }); + } + + + private final File lockFile; + private boolean isFileOwner = false; + private FileOutputStream stream = null; + + + /** + * コンストラクタ。 + * この時点ではまだロックファイルの存在は確認されない。 + * @param lockFile ロックファイル + * @throws NullPointerException 引数がnull + */ + public InterVMLock(File lockFile) throws NullPointerException{ + if(lockFile == null) throw new NullPointerException(); + this.lockFile = lockFile; + return; + } + + + /** + * 所持するロックオブジェクト一覧への登録。 + * @param lock 登録するロックオブジェクト + */ + private static void addOwnedLock(InterVMLock lock){ + synchronized(ownedLockSet){ + if( ! lock.isFileOwner() ) return; + ownedLockSet.add(lock); + lock.getLockFile().deleteOnExit(); + } + return; + } + + /** + * 所持するロックオブジェクト一覧からの脱退。 + * @param lock 脱退対象のロックオブジェクト + */ + private static void removeOwnedLock(InterVMLock lock){ + synchronized(ownedLockSet){ + ownedLockSet.remove(lock); + } + return; + } + + /** + * 所持するロックオブジェクトすべてを解放しロックファイルを削除する。 + */ + private static void clearOwnedLockSet(){ + synchronized(ownedLockSet){ + for(InterVMLock lock : ownedLockSet){ + if( ! lock.isFileOwner() ) continue; + lock.release(); + try{ + lock.getLockFile().delete(); + }catch(SecurityException e){ + // NOTHING + } + } + ownedLockSet.clear(); + } + return; + } + + /** + * ロック対象のファイルを返す。 + * @return ロック対象ファイル + */ + public File getLockFile(){ + return this.lockFile; + } + + /** + * ロックファイルがディスク上に存在するか判定する。 + * @return 存在すればtrue + */ + public boolean isExistsFile(){ + if(this.lockFile.exists()){ + return true; + } + return false; + } + + /** + * このオブジェクトがロックファイルの作者であるか判定する。 + * @return 作者ならtrue + */ + public synchronized boolean isFileOwner(){ + return this.isFileOwner; + } + + /** + * ロックファイルのオープン中のストリームを返す。 + * ※ 排他制御目的のリソースなので、 + * 勝手に書き込んだりクローズしたりしないように。 + * @return オープン中のストリーム。オープンしてなければnull + */ + protected synchronized FileOutputStream getOpenedStream(){ + if(isFileOwner()) return this.stream; + return null; + } + + /** + * ロックファイルの強制削除を試みる。 + * @return 強制削除に成功すればtrue + */ + public synchronized boolean forceRemove(){ + if(isFileOwner()) release(); + + if( ! isExistsFile() ) return true; + + try{ + boolean result = this.lockFile.delete(); + if( ! result ) return false; + }catch(SecurityException e){ + return false; + } + + if(isExistsFile()) return false; + + return true; + } + + /** + * ロックファイルを生成する。 + * 生成されるロックファイルはVM終了時に削除されるよう登録される。 + * このメソッド実行中にVM終了が重なると、 + * ロックファイルが正しく削除されない場合がありうる。 + * @return 成功すればtrue + */ + protected synchronized boolean touchLockFile(){ + boolean result = false; + try{ + result = this.lockFile.createNewFile(); + }catch(IOException e){ + // NOTHING + }catch(SecurityException e){ + // NOTHING + } + if(result == false){ + return false; + } + + try{ + this.isFileOwner = true; + this.stream = new FileOutputStream(this.lockFile); + }catch(FileNotFoundException e){ + assert false; + this.isFileOwner = false; + this.stream = null; + try{ + this.lockFile.delete(); + }catch(SecurityException e2){ + // NOTHING + } + return false; + } + + addOwnedLock(this); + + return true; + } + + /** + * ロックを試みる。 + * このメソッドはブロックしない。 + * @return すでにロック済みもしくはロックに成功すればtrue + */ + public synchronized boolean tryLock(){ + if( isFileOwner() ) return true; + + if(isExistsFile()) return false; + if(touchLockFile() != true) return false; + + return true; + } + + /** + * ロックを解除する。 + * 自分が作者であるロックファイルは閉じられ削除される。 + * 削除に失敗しても無視。 + */ + public synchronized void release(){ + if( ! isFileOwner() ) return; + + try{ + this.stream.close(); + }catch(IOException e){ + // NOTHING + }finally{ + this.stream = null; + try{ + this.lockFile.delete(); + }catch(SecurityException e){ + // NOTHING + }finally{ + removeOwnedLock(this); + this.isFileOwner = false; + } + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Jindolf.java b/src/main/java/jp/sourceforge/jindolf/Jindolf.java index 6d2cd9f..fee2ae0 100644 --- a/src/main/java/jp/sourceforge/jindolf/Jindolf.java +++ b/src/main/java/jp/sourceforge/jindolf/Jindolf.java @@ -1,817 +1,817 @@ -/* - * Jindolf main class - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.GraphicsEnvironment; -import java.awt.Window; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.io.Reader; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.security.Permission; -import java.text.DateFormat; -import java.text.NumberFormat; -import java.util.Date; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Logger; -import java.util.logging.LoggingPermission; -import javax.swing.ImageIcon; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JWindow; -import javax.swing.UIManager; - -/** - * Jindolf スタートアップクラス。 - * - * コンストラクタは無いよ。 - * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。 - */ -public final class Jindolf{ - - /** 実行に最低限必要なJREの版数。 */ - public static final String MINIMUM_JREVER = "1.5"; - - - /** このClass。 */ - public static final Class SELF_KLASS; - /** このPackage。 */ - public static final Package SELF_PACKAGE; - /** ランタイムPackage。 */ - public static final Package JRE_PACKAGE; - /** 実行環境。 */ - public static final Runtime RUNTIME; - /** セキュリティマネージャ。 */ - public static final SecurityManager SEC_MANAGER; - /** クラスローダ。 */ - public static final ClassLoader LOADER; - - - /** クラスロード時のナノカウント。 */ - public static final long NANOCT_LOADED; - /** クラスロード時刻(エポックmsec)。 */ - public static final long EPOCHMS_LOADED; - - - /** タイトル。 */ - public static final String TITLE; - /** バージョン。 */ - public static final String VERSION; - /** 作者名。 */ - public static final String AUTHOR; - /** 著作権表記。 */ - public static final String COPYRIGHT; - /** ライセンス表記。 */ - public static final String LICENSE; - /** 連絡先。 */ - public static final String CONTACT; - /** 初出。 */ - public static final String DEBUT; - /** その他、何でも書きたいこと。 */ - public static final String COMMENT; - /** クレジット。 */ - public static final String ID; - - /** 共通ロガー。 */ - private static final LogWrapper COMMON_LOGGER; - - /** 多重起動防止用セマフォ。 */ - private static final AtomicBoolean INVOKE_FLAG; - - /** スプラッシュロゴ。 */ - private static final String RES_LOGOICON = - "resources/image/logo.png"; - - private static OptionInfo option; - private static AppSetting setting; - - /** バージョン定義リソース。 */ - private static final String RES_VERDEF = "resources/version.properties"; - - static{ - SELF_KLASS = Jindolf.class; - SELF_PACKAGE = SELF_KLASS.getPackage(); - JRE_PACKAGE = java.lang.Object.class.getPackage(); - RUNTIME = Runtime.getRuntime(); - SEC_MANAGER = System.getSecurityManager(); - - ClassLoader thisLoader; - try{ - thisLoader = SELF_KLASS.getClassLoader(); - }catch(SecurityException e){ - thisLoader = null; - } - LOADER = thisLoader; - - if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){ - String jreInstalled; - try{ - jreInstalled = System.getProperty("java.home"); - }catch(SecurityException e){ - jreInstalled = "※インストール位置不明"; - } - String errmsg = - "今このプログラム " + SELF_KLASS.getName() + " は\n" - +"[ " + jreInstalled - +" ]\nにインストールされた" - +" JRE" + JRE_PACKAGE.getSpecificationVersion() - +" の実行環境で実行されようとしました。\n" - +"しかしこのプログラムの実行には" - +" JRE" + MINIMUM_JREVER - + " 以降の実行環境が必要です。\n" - +"おそらく http://www.java.com/ などからの" - +"入手が可能でしょう。"; - - errorDialog("実行系の不備", errmsg); - - RUNTIME.exit(1); - } - - // ここからJRE1.5解禁。 - - NANOCT_LOADED = System.nanoTime(); - EPOCHMS_LOADED = System.currentTimeMillis(); - - Properties verProp = loadVersionDefinition(SELF_KLASS); - TITLE = getPackageInfo(verProp, "pkg-title.", "Unknown"); - VERSION = getPackageInfo(verProp, "pkg-version.", "0"); - AUTHOR = getPackageInfo(verProp, "pkg-author.", "nobody"); - LICENSE = getPackageInfo(verProp, "pkg-license.", "Unknown"); - CONTACT = getPackageInfo(verProp, "pkg-contact.", "Unknown"); - DEBUT = getPackageInfo(verProp, "pkg-debut.", "2008"); - COMMENT = getPackageInfo(verProp, "pkg-comment.", ""); - COPYRIGHT = "Copyright(c)" +"\u0020"+ DEBUT +"\u0020"+ AUTHOR; - ID = TITLE - +"\u0020"+ "Ver." + VERSION - +"\u0020"+ COPYRIGHT - +"\u0020"+ "("+ LICENSE +")"; - - Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName()); - COMMON_LOGGER = new LogWrapper(jre14Logger); - - INVOKE_FLAG = new AtomicBoolean(false); - - new Jindolf(); - } - - - /** - * 隠れコンストラクタ。 - */ - private Jindolf(){ - super(); - assert this.getClass() == SELF_KLASS; - return; - } - - - /** - * 起動オプション情報を返す。 - * @return 起動オプション情報 - */ - public static OptionInfo getOptionInfo(){ - return option; - } - - /** - * アプリ設定を返す。 - * @return アプリ設定 - */ - public static AppSetting getAppSetting(){ - return setting; - } - - /** - * エラーダイアログをビットマップディスプレイに出現させる。 - * メインウィンドウが整備されるまでの間だけ一時的に使う。 - * 努力目標:なるべく昔のJRE環境でも例外無く動くように。 - * @param title タイトル - * @param message メッセージ - */ - private static void errorDialog(String title, String message){ - System.err.println(message); - System.err.flush(); - - if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){ - return; - } - - if( JRE_PACKAGE.isCompatibleWith("1.4") - && GraphicsEnvironment.isHeadless()){ - return; - } - - JOptionPane.showMessageDialog(null, - message, - title, - JOptionPane.ERROR_MESSAGE); - - return; - } - - /** - * リソース上のパッケージ定義プロパティをロードする。 - * MANIFEST.MFが参照できない実行環境での代替品。 - * @param klass パッケージを構成する任意のクラス - * @return プロパティ - */ - private static Properties loadVersionDefinition(Class klass){ - Properties result = new Properties(); - - InputStream istream = klass.getResourceAsStream(RES_VERDEF); - try{ - result.load(istream); - }catch(IOException e){ - return result; - }finally{ - try{ - istream.close(); - }catch(IOException e){ - return result; - } - } - - return result; - } - - /** - * リソース上のプロパティから - * このクラスのパッケージのパッケージ情報を取得する。 - * MANIFEST.MFが参照できない実行環境での代替品。 - * @param prop プロパティ - * @param prefix 接頭辞 - * @param defValue 見つからなかった場合のデフォルト値 - * @return パッケージ情報 - */ - public static String getPackageInfo(Properties prop, - String prefix, - String defValue){ - return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue); - } - - /** - * リソース上のプロパティからパッケージ情報を取得する。 - * MANIFEST.MFが参照できない実行環境での代替品。 - * @param prop プロパティ - * @param pkg 任意のパッケージ - * @param prefix 接頭辞 - * @param defValue デフォルト値 - * @return 見つからなかった場合のパッケージ情報 - */ - public static String getPackageInfo(Properties prop, - Package pkg, - String prefix, - String defValue){ - String propName = prefix + pkg.getName(); - String result = prop.getProperty(propName, defValue); - return result; - } - - /** - * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。 - */ - private static void checkGUIEnvironment(){ - if(GraphicsEnvironment.isHeadless()){ - System.err.println( - TITLE - + " はGUI環境と接続できませんでした"); - - String dispEnv; - try{ - dispEnv = System.getenv("DISPLAY"); - }catch(SecurityException e){ - dispEnv = null; - } - - // for X11 user - if(dispEnv != null){ - System.err.println("環境変数 DISPLAY : " + dispEnv); - } - - RUNTIME.exit(1); - } - - return; - } - - /** - * コンパイル時のエラーを判定する。 - * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。 - */ - private static void checkCompileError(){ - String errmsg = - "ソースコードの文字コードが" - +"正しくコンパイルされていないかも。\n" - +"あなたは今、オリジナル開発元の意図しない文字コード環境で" - +"コンパイルされたプログラムを起動しようとしているよ。\n" - +"ソースコードの入手に際して" - +"どのような文字コード変換が行われたか認識しているかな?\n" - +"コンパイルオプションで正しい文字コードを指定したかな?"; - - if( '狼' != 0x72fc - || ' ' != 0x3000 - || '~' != 0x007e - || '\\' != 0x005c // バックスラッシュ - || 'Â¥' != 0x00a5 // 半角円通貨 - || '~' != 0xff5e - || '�' != 0xfffd // Unicode専用特殊文字 - ){ - JOptionPane.showMessageDialog(null, - errmsg, - "コンパイルの不備", - JOptionPane.ERROR_MESSAGE); - RUNTIME.exit(1); - } - return; - } - - /** - * MANIFEST.MFパッケージ定義エラーの検出。 - * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい? - */ - private static void checkPackageDefinition(){ - String implTitle = SELF_PACKAGE.getImplementationTitle(); - String implVersion = SELF_PACKAGE.getImplementationVersion(); - String implVendor = SELF_PACKAGE.getImplementationVendor(); - - String errmsg = null; - - if( implTitle != null - && ! implTitle.equals(TITLE) ){ - errmsg = "パッケージ定義とタイトルが一致しません。" - +"["+ implTitle +"]≠["+ TITLE +"]"; - }else if( implVersion != null - && ! implVersion.equals(VERSION) ){ - errmsg = "パッケージ定義とバージョン番号が一致しません。" - +"["+ implVersion +"]≠["+ VERSION +"]"; - }else if( implVendor != null - && ! implVendor.equals(AUTHOR) ){ - errmsg = "パッケージ定義とベンダが一致しません。" - +"["+ implVendor +"]≠["+ AUTHOR +"]"; - } - - if(errmsg != null){ - JOptionPane.showMessageDialog(null, - errmsg, - "ビルドエラー", - JOptionPane.ERROR_MESSAGE); - RUNTIME.exit(1); - } - - return; - } - - /** - * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。 - */ - private static void showHelpMessage(){ - System.out.flush(); - System.err.flush(); - - CharSequence helpText = CmdOption.getHelpText(); - System.out.print(helpText); - - System.out.flush(); - System.err.flush(); - - return; - } - - /** - * スプラッシュウィンドウを作成する。 - * JRE1.6以降では呼ばれないはず。 - * @return 未表示のスプラッシュウィンドウ。 - */ - private static Window createSplashWindow(){ - Window splashWindow = new JWindow(); - - URL url = getResource(RES_LOGOICON); - ImageIcon logo = new ImageIcon(url); - JLabel splashLabel = new JLabel(logo); - - splashWindow.add(splashLabel); - splashWindow.pack(); - splashWindow.setLocationRelativeTo(null); // locate center - - return splashWindow; - } - - /** - * ロギング初期化。 - * @param useConsoleLog trueならConsoleHandlerを使う。 - */ - private static void initLogging(boolean useConsoleLog){ - boolean hasPermission = hasLoggingPermission(); - - if( ! hasPermission){ - System.out.println( - "セキュリティ設定により、" - + "ログ設定を変更できませんでした" ); - } - - Logger jre14Logger = COMMON_LOGGER.getJre14Logger(); - - if(hasPermission){ - jre14Logger.setUseParentHandlers(false); - Handler pileHandler = new PileHandler(); - jre14Logger.addHandler(pileHandler); - } - - if(hasPermission && useConsoleLog){ - Handler consoleHandler = new ConsoleHandler(); - jre14Logger.addHandler(consoleHandler); - } - - return; - } - - /** - * ログ操作のアクセス権があるか否か判定する。 - * @return アクセス権があればtrue - */ - public static boolean hasLoggingPermission(){ - if(SEC_MANAGER == null) return true; - - Permission logPermission = new LoggingPermission("control", null); - try{ - SEC_MANAGER.checkPermission(logPermission); - }catch(SecurityException e){ - return false; - } - - return true; - } - - /** - * 起動時の諸々の情報をログ出力する。 - */ - private static void dumpBootInfo(){ - DateFormat dform = DateFormat.getDateTimeInstance(); - NumberFormat nform = NumberFormat.getNumberInstance(); - - logger().info( - ID + " は " - + dform.format(new Date(EPOCHMS_LOADED)) - + " にVM上のクラス " - + SELF_KLASS.getName() + " としてロードされました。 " ); - - logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED)); - - logger().info( - "Max-heap : " - + nform.format(RUNTIME.maxMemory()) + " Byte" - + " Total-heap : " - + nform.format(RUNTIME.totalMemory()) + " Byte"); - - logger().info("\n" + EnvInfo.getVMInfo()); - - if(getAppSetting().useConfigPath()){ - logger().info("設定格納ディレクトリに[ " - + getAppSetting().getConfigPath().getPath() - + " ]が指定されました。"); - }else{ - logger().info("設定格納ディレクトリは使いません。"); - } - - if( JRE_PACKAGE.isCompatibleWith("1.6") - && option.hasOption(CmdOption.OPT_NOSPLASH) ){ - logger().warn( - "JRE1.6以降では、" - +"Jindolfの-nosplashオプションは無効です。" - + "Java実行系の方でスプラッシュ画面の非表示を" - + "指示してください(おそらく空の-splash:オプション)" ); - } - - if(LOADER == null){ - logger().warn( - "セキュリティ設定により、" - +"クラスローダを取得できませんでした"); - } - - return; - } - - /** - * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。 - * どーしてもクラス初期化の順序に依存する障害が発生する場合や - * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。 - * - * @throws java.lang.LinkageError クラス間リンケージエラー。 - * @throws java.lang.ExceptionInInitializerError クラス初期化で異常 - */ - private static void preInitClass() - throws LinkageError, - ExceptionInInitializerError { - Object[] classes = { // Class型 または String型 - "java.lang.Object", - TabBrowser.class, - Discussion.class, - GlyphDraw.class, - java.net.HttpURLConnection.class, - java.text.SimpleDateFormat.class, - Void.class, - }; - - for(Object obj : classes){ - String className; - if(obj instanceof Class){ - className = ((Class)obj).getName(); - }else if(obj instanceof String){ - className = obj.toString(); - }else{ - continue; - } - - try{ - if(LOADER != null){ - Class.forName(className, true, LOADER); - }else{ - Class.forName(className); - } - }catch(ClassNotFoundException e){ - logger().warn("クラスの明示的ロードに失敗しました", e); - continue; - } - } - - return; - } - - /** - * AWTイベントディスパッチスレッド版スタートアップエントリ。 - */ - private static void startGUI(){ - LandsModel model = new LandsModel(); - model.loadLandList(); - - JFrame topFrame = buildMVC(model); - - GUIUtils.modifyWindowAttributes(topFrame, true, false, true); - - topFrame.pack(); - - Dimension initGeometry = - new Dimension(setting.initialFrameWidth(), - setting.initialFrameHeight()); - topFrame.setSize(initGeometry); - - if( setting.initialFrameXpos() <= Integer.MIN_VALUE - || setting.initialFrameYpos() <= Integer.MIN_VALUE ){ - topFrame.setLocationByPlatform(true); - }else{ - topFrame.setLocation(setting.initialFrameXpos(), - setting.initialFrameYpos() ); - } - - topFrame.setVisible(true); - - return; - } - - /** - * モデル・ビュー・コントローラの結合。 - * - * @param model 最上位のデータモデル - * @return アプリケーションのトップフレーム - */ - private static JFrame buildMVC(LandsModel model){ - ActionManager actionManager = new ActionManager(); - TopView topView = new TopView(); - - Controller controller = new Controller(actionManager, topView, model); - - JFrame topFrame = controller.createTopFrame(); - - return topFrame; - } - - /** - * リソースからUTF-8で記述されたテキストデータをロードする。 - * @param resourceName リソース名 - * @return テキスト文字列 - * @throws java.io.IOException 入出力の異常。おそらくビルドミス。 - */ - public static CharSequence loadResourceText(String resourceName) - throws IOException{ - InputStream is; - is = getResourceAsStream(resourceName); - is = new BufferedInputStream(is); - Reader reader = new InputStreamReader(is, "UTF-8"); - LineNumberReader lineReader = new LineNumberReader(reader); - - StringBuilder result = new StringBuilder(); - try{ - for(;;){ - String line = lineReader.readLine(); - if(line == null) break; - if(line.startsWith("#")) continue; - result.append(line).append('\n'); - } - }finally{ - lineReader.close(); - } - - return result; - } - - /** - * クラスローダを介してリソースからの入力を生成する。 - * @param name リソース名 - * @return リソースからの入力 - */ - public static InputStream getResourceAsStream(String name){ - return SELF_KLASS.getResourceAsStream(name); - } - - /** - * クラスローダを介してリソース読み込み用URLを生成する。 - * @param name リソース名 - * @return URL - */ - public static URL getResource(String name){ - return SELF_KLASS.getResource(name); - } - - /** - * 共通ロガーを取得する。 - * @return 共通ロガー - */ - public static LogWrapper logger(){ - return COMMON_LOGGER; - } - - /** - * VMごとプログラムを終了する。 - * ※おそらく随所でシャットダウンフックが起動されるはず。 - * - * @param exitCode 終了コード - * @throws java.lang.SecurityException セキュリティ違反 - */ - public static void exit(int exitCode) throws SecurityException{ - logger().info( - "終了コード[" - + exitCode - + "]でVMごとアプリケーションを終了します。" ); - RUNTIME.runFinalization(); - System.out.flush(); - System.err.flush(); - try{ - RUNTIME.exit(exitCode); - }catch(SecurityException e){ - logger().warn( - "セキュリティ設定により、" - +"VMを終了させることができません。", e); - throw e; - } - return; - } - - /** - * Jindolf のスタートアップエントリ。 - * - * @param args コマンドライン引数 - */ - public static void main(final String[] args){ - // VM内二重起動チェック - boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true); - if(hasInvoked){ - String errmsg = "二度目以降の起動がキャンセルされました。"; - errorDialog("多重起動", errmsg); - - // exitせずに戻るのみ - return; - } - - checkGUIEnvironment(); - - // ここからGUIウィンドウとマウス解禁 - - checkCompileError(); - checkPackageDefinition(); - - try{ - option = OptionInfo.parseOptions(args); - }catch(IllegalArgumentException e){ - String message = e.getLocalizedMessage(); - System.err.println(message); - System.err.println( - "起動オプション一覧は、" - + "起動オプションに「" - + CmdOption.OPT_HELP.toHyphened() - + "」を指定すると確認できます。" ); - Jindolf.RUNTIME.exit(1); - assert false; - return; - } - - if(option.hasOption(CmdOption.OPT_HELP)){ - showHelpMessage(); - RUNTIME.exit(0); - return; - } - - if(option.hasOption(CmdOption.OPT_VERSION)){ - System.out.println(ID); - RUNTIME.exit(0); - return; - } - - // あらゆるSwingコンポーネント操作より前に必要。 - if(option.hasOption(CmdOption.OPT_BOLDMETAL)){ - // もの凄く日本語表示が汚くなるかもよ!注意 - UIManager.put("swing.boldMetal", Boolean.TRUE); - }else{ - UIManager.put("swing.boldMetal", Boolean.FALSE); - } - - // JRE1.5用スプラッシュウィンドウ - Window splashWindow = null; - if( ! JRE_PACKAGE.isCompatibleWith("1.6") - && ! option.hasOption(CmdOption.OPT_NOSPLASH) ){ - splashWindow = createSplashWindow(); - splashWindow.setVisible(true); - Thread.yield(); - } - - setting = new AppSetting(); - setting.applyOptionInfo(option); - - if(option.hasOption(CmdOption.OPT_VMINFO)){ - System.out.println(EnvInfo.getVMInfo()); - } - - initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG)); - // ここからロギング解禁 - // Jindolf.exit()もここから解禁 - - dumpBootInfo(); - - ConfigFile.setupConfigDirectory(); - ConfigFile.setupLockFile(); - // ここから設定格納ディレクトリ解禁 - - setting.loadConfig(); - - RUNTIME.addShutdownHook(new Thread(){ - @Override - public void run(){ - logger().info("シャットダウン処理に入ります…"); - System.out.flush(); - System.err.flush(); - RUNTIME.gc(); - Thread.yield(); - RUNTIME.runFinalization(); // 危険? - Thread.yield(); - return; - } - }); - - preInitClass(); - - GUIUtils.replaceEventQueue(); - - boolean hasError = false; - try{ - EventQueue.invokeAndWait(new Runnable(){ - public void run(){ - startGUI(); - return; - } - }); - }catch(InvocationTargetException e){ - logger().fatal("アプリケーション初期化に失敗しました", e); - e.printStackTrace(System.err); - hasError = true; - }catch(InterruptedException e){ - logger().fatal("アプリケーション初期化に失敗しました", e); - e.printStackTrace(System.err); - hasError = true; - }finally{ - if(splashWindow != null){ - splashWindow.setVisible(false); - splashWindow.dispose(); - splashWindow = null; - } - } - - if(hasError) exit(1); - - return; - } - -} +/* + * Jindolf main class + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.GraphicsEnvironment; +import java.awt.Window; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.security.Permission; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.Date; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Logger; +import java.util.logging.LoggingPermission; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JWindow; +import javax.swing.UIManager; + +/** + * Jindolf スタートアップクラス。 + * + * コンストラクタは無いよ。 + * アプリ開始はstaticメソッド{@link #main(String[])}呼び出しから。 + */ +public final class Jindolf{ + + /** 実行に最低限必要なJREの版数。 */ + public static final String MINIMUM_JREVER = "1.5"; + + + /** このClass。 */ + public static final Class SELF_KLASS; + /** このPackage。 */ + public static final Package SELF_PACKAGE; + /** ランタイムPackage。 */ + public static final Package JRE_PACKAGE; + /** 実行環境。 */ + public static final Runtime RUNTIME; + /** セキュリティマネージャ。 */ + public static final SecurityManager SEC_MANAGER; + /** クラスローダ。 */ + public static final ClassLoader LOADER; + + + /** クラスロード時のナノカウント。 */ + public static final long NANOCT_LOADED; + /** クラスロード時刻(エポックmsec)。 */ + public static final long EPOCHMS_LOADED; + + + /** タイトル。 */ + public static final String TITLE; + /** バージョン。 */ + public static final String VERSION; + /** 作者名。 */ + public static final String AUTHOR; + /** 著作権表記。 */ + public static final String COPYRIGHT; + /** ライセンス表記。 */ + public static final String LICENSE; + /** 連絡先。 */ + public static final String CONTACT; + /** 初出。 */ + public static final String DEBUT; + /** その他、何でも書きたいこと。 */ + public static final String COMMENT; + /** クレジット。 */ + public static final String ID; + + /** 共通ロガー。 */ + private static final LogWrapper COMMON_LOGGER; + + /** 多重起動防止用セマフォ。 */ + private static final AtomicBoolean INVOKE_FLAG; + + /** スプラッシュロゴ。 */ + private static final String RES_LOGOICON = + "resources/image/logo.png"; + + private static OptionInfo option; + private static AppSetting setting; + + /** バージョン定義リソース。 */ + private static final String RES_VERDEF = "resources/version.properties"; + + static{ + SELF_KLASS = Jindolf.class; + SELF_PACKAGE = SELF_KLASS.getPackage(); + JRE_PACKAGE = java.lang.Object.class.getPackage(); + RUNTIME = Runtime.getRuntime(); + SEC_MANAGER = System.getSecurityManager(); + + ClassLoader thisLoader; + try{ + thisLoader = SELF_KLASS.getClassLoader(); + }catch(SecurityException e){ + thisLoader = null; + } + LOADER = thisLoader; + + if( ! JRE_PACKAGE.isCompatibleWith(MINIMUM_JREVER) ){ + String jreInstalled; + try{ + jreInstalled = System.getProperty("java.home"); + }catch(SecurityException e){ + jreInstalled = "※インストール位置不明"; + } + String errmsg = + "今このプログラム " + SELF_KLASS.getName() + " は\n" + +"[ " + jreInstalled + +" ]\nにインストールされた" + +" JRE" + JRE_PACKAGE.getSpecificationVersion() + +" の実行環境で実行されようとしました。\n" + +"しかしこのプログラムの実行には" + +" JRE" + MINIMUM_JREVER + + " 以降の実行環境が必要です。\n" + +"おそらく http://www.java.com/ などからの" + +"入手が可能でしょう。"; + + errorDialog("実行系の不備", errmsg); + + RUNTIME.exit(1); + } + + // ここからJRE1.5解禁。 + + NANOCT_LOADED = System.nanoTime(); + EPOCHMS_LOADED = System.currentTimeMillis(); + + Properties verProp = loadVersionDefinition(SELF_KLASS); + TITLE = getPackageInfo(verProp, "pkg-title.", "Unknown"); + VERSION = getPackageInfo(verProp, "pkg-version.", "0"); + AUTHOR = getPackageInfo(verProp, "pkg-author.", "nobody"); + LICENSE = getPackageInfo(verProp, "pkg-license.", "Unknown"); + CONTACT = getPackageInfo(verProp, "pkg-contact.", "Unknown"); + DEBUT = getPackageInfo(verProp, "pkg-debut.", "2008"); + COMMENT = getPackageInfo(verProp, "pkg-comment.", ""); + COPYRIGHT = "Copyright(c)" +"\u0020"+ DEBUT +"\u0020"+ AUTHOR; + ID = TITLE + +"\u0020"+ "Ver." + VERSION + +"\u0020"+ COPYRIGHT + +"\u0020"+ "("+ LICENSE +")"; + + Logger jre14Logger = Logger.getLogger(SELF_PACKAGE.getName()); + COMMON_LOGGER = new LogWrapper(jre14Logger); + + INVOKE_FLAG = new AtomicBoolean(false); + + new Jindolf(); + } + + + /** + * 隠れコンストラクタ。 + */ + private Jindolf(){ + super(); + assert this.getClass() == SELF_KLASS; + return; + } + + + /** + * 起動オプション情報を返す。 + * @return 起動オプション情報 + */ + public static OptionInfo getOptionInfo(){ + return option; + } + + /** + * アプリ設定を返す。 + * @return アプリ設定 + */ + public static AppSetting getAppSetting(){ + return setting; + } + + /** + * エラーダイアログをビットマップディスプレイに出現させる。 + * メインウィンドウが整備されるまでの間だけ一時的に使う。 + * 努力目標:なるべく昔のJRE環境でも例外無く動くように。 + * @param title タイトル + * @param message メッセージ + */ + private static void errorDialog(String title, String message){ + System.err.println(message); + System.err.flush(); + + if( ! JRE_PACKAGE.isCompatibleWith("1.2") ){ + return; + } + + if( JRE_PACKAGE.isCompatibleWith("1.4") + && GraphicsEnvironment.isHeadless()){ + return; + } + + JOptionPane.showMessageDialog(null, + message, + title, + JOptionPane.ERROR_MESSAGE); + + return; + } + + /** + * リソース上のパッケージ定義プロパティをロードする。 + * MANIFEST.MFが参照できない実行環境での代替品。 + * @param klass パッケージを構成する任意のクラス + * @return プロパティ + */ + private static Properties loadVersionDefinition(Class klass){ + Properties result = new Properties(); + + InputStream istream = klass.getResourceAsStream(RES_VERDEF); + try{ + result.load(istream); + }catch(IOException e){ + return result; + }finally{ + try{ + istream.close(); + }catch(IOException e){ + return result; + } + } + + return result; + } + + /** + * リソース上のプロパティから + * このクラスのパッケージのパッケージ情報を取得する。 + * MANIFEST.MFが参照できない実行環境での代替品。 + * @param prop プロパティ + * @param prefix 接頭辞 + * @param defValue 見つからなかった場合のデフォルト値 + * @return パッケージ情報 + */ + public static String getPackageInfo(Properties prop, + String prefix, + String defValue){ + return getPackageInfo(prop, SELF_PACKAGE, prefix, defValue); + } + + /** + * リソース上のプロパティからパッケージ情報を取得する。 + * MANIFEST.MFが参照できない実行環境での代替品。 + * @param prop プロパティ + * @param pkg 任意のパッケージ + * @param prefix 接頭辞 + * @param defValue デフォルト値 + * @return 見つからなかった場合のパッケージ情報 + */ + public static String getPackageInfo(Properties prop, + Package pkg, + String prefix, + String defValue){ + String propName = prefix + pkg.getName(); + String result = prop.getProperty(propName, defValue); + return result; + } + + /** + * Jindolf の実行が可能なGUI環境でなければ、即時VM実行を終了する。 + */ + private static void checkGUIEnvironment(){ + if(GraphicsEnvironment.isHeadless()){ + System.err.println( + TITLE + + " はGUI環境と接続できませんでした"); + + String dispEnv; + try{ + dispEnv = System.getenv("DISPLAY"); + }catch(SecurityException e){ + dispEnv = null; + } + + // for X11 user + if(dispEnv != null){ + System.err.println("環境変数 DISPLAY : " + dispEnv); + } + + RUNTIME.exit(1); + } + + return; + } + + /** + * コンパイル時のエラーを判定する。 + * ※ 非Unicode系の開発環境を使いたい人は適当に無視してね。 + */ + private static void checkCompileError(){ + String errmsg = + "ソースコードの文字コードが" + +"正しくコンパイルされていないかも。\n" + +"あなたは今、オリジナル開発元の意図しない文字コード環境で" + +"コンパイルされたプログラムを起動しようとしているよ。\n" + +"ソースコードの入手に際して" + +"どのような文字コード変換が行われたか認識しているかな?\n" + +"コンパイルオプションで正しい文字コードを指定したかな?"; + + if( '狼' != 0x72fc + || ' ' != 0x3000 + || '~' != 0x007e + || '\\' != 0x005c // バックスラッシュ + || 'Â¥' != 0x00a5 // 半角円通貨 + || '~' != 0xff5e + || '�' != 0xfffd // Unicode専用特殊文字 + ){ + JOptionPane.showMessageDialog(null, + errmsg, + "コンパイルの不備", + JOptionPane.ERROR_MESSAGE); + RUNTIME.exit(1); + } + return; + } + + /** + * MANIFEST.MFパッケージ定義エラーの検出。 + * ビルド前にMANIFEST自動生成Antタスク「manifest」を忘れてないかい? + */ + private static void checkPackageDefinition(){ + String implTitle = SELF_PACKAGE.getImplementationTitle(); + String implVersion = SELF_PACKAGE.getImplementationVersion(); + String implVendor = SELF_PACKAGE.getImplementationVendor(); + + String errmsg = null; + + if( implTitle != null + && ! implTitle.equals(TITLE) ){ + errmsg = "パッケージ定義とタイトルが一致しません。" + +"["+ implTitle +"]≠["+ TITLE +"]"; + }else if( implVersion != null + && ! implVersion.equals(VERSION) ){ + errmsg = "パッケージ定義とバージョン番号が一致しません。" + +"["+ implVersion +"]≠["+ VERSION +"]"; + }else if( implVendor != null + && ! implVendor.equals(AUTHOR) ){ + errmsg = "パッケージ定義とベンダが一致しません。" + +"["+ implVendor +"]≠["+ AUTHOR +"]"; + } + + if(errmsg != null){ + JOptionPane.showMessageDialog(null, + errmsg, + "ビルドエラー", + JOptionPane.ERROR_MESSAGE); + RUNTIME.exit(1); + } + + return; + } + + /** + * 標準出力端末にヘルプメッセージ(オプションの説明)を表示する。 + */ + private static void showHelpMessage(){ + System.out.flush(); + System.err.flush(); + + CharSequence helpText = CmdOption.getHelpText(); + System.out.print(helpText); + + System.out.flush(); + System.err.flush(); + + return; + } + + /** + * スプラッシュウィンドウを作成する。 + * JRE1.6以降では呼ばれないはず。 + * @return 未表示のスプラッシュウィンドウ。 + */ + private static Window createSplashWindow(){ + Window splashWindow = new JWindow(); + + URL url = getResource(RES_LOGOICON); + ImageIcon logo = new ImageIcon(url); + JLabel splashLabel = new JLabel(logo); + + splashWindow.add(splashLabel); + splashWindow.pack(); + splashWindow.setLocationRelativeTo(null); // locate center + + return splashWindow; + } + + /** + * ロギング初期化。 + * @param useConsoleLog trueならConsoleHandlerを使う。 + */ + private static void initLogging(boolean useConsoleLog){ + boolean hasPermission = hasLoggingPermission(); + + if( ! hasPermission){ + System.out.println( + "セキュリティ設定により、" + + "ログ設定を変更できませんでした" ); + } + + Logger jre14Logger = COMMON_LOGGER.getJre14Logger(); + + if(hasPermission){ + jre14Logger.setUseParentHandlers(false); + Handler pileHandler = new PileHandler(); + jre14Logger.addHandler(pileHandler); + } + + if(hasPermission && useConsoleLog){ + Handler consoleHandler = new ConsoleHandler(); + jre14Logger.addHandler(consoleHandler); + } + + return; + } + + /** + * ログ操作のアクセス権があるか否か判定する。 + * @return アクセス権があればtrue + */ + public static boolean hasLoggingPermission(){ + if(SEC_MANAGER == null) return true; + + Permission logPermission = new LoggingPermission("control", null); + try{ + SEC_MANAGER.checkPermission(logPermission); + }catch(SecurityException e){ + return false; + } + + return true; + } + + /** + * 起動時の諸々の情報をログ出力する。 + */ + private static void dumpBootInfo(){ + DateFormat dform = DateFormat.getDateTimeInstance(); + NumberFormat nform = NumberFormat.getNumberInstance(); + + logger().info( + ID + " は " + + dform.format(new Date(EPOCHMS_LOADED)) + + " にVM上のクラス " + + SELF_KLASS.getName() + " としてロードされました。 " ); + + logger().info("Initial Nano-Count : " + nform.format(NANOCT_LOADED)); + + logger().info( + "Max-heap : " + + nform.format(RUNTIME.maxMemory()) + " Byte" + + " Total-heap : " + + nform.format(RUNTIME.totalMemory()) + " Byte"); + + logger().info("\n" + EnvInfo.getVMInfo()); + + if(getAppSetting().useConfigPath()){ + logger().info("設定格納ディレクトリに[ " + + getAppSetting().getConfigPath().getPath() + + " ]が指定されました。"); + }else{ + logger().info("設定格納ディレクトリは使いません。"); + } + + if( JRE_PACKAGE.isCompatibleWith("1.6") + && option.hasOption(CmdOption.OPT_NOSPLASH) ){ + logger().warn( + "JRE1.6以降では、" + +"Jindolfの-nosplashオプションは無効です。" + + "Java実行系の方でスプラッシュ画面の非表示を" + + "指示してください(おそらく空の-splash:オプション)" ); + } + + if(LOADER == null){ + logger().warn( + "セキュリティ設定により、" + +"クラスローダを取得できませんでした"); + } + + return; + } + + /** + * 任意のクラス群に対して一括ロード/初期化を単一スレッドで順に行う。 + * どーしてもクラス初期化の順序に依存する障害が発生する場合や + * クラス初期化のオーバーヘッドでGUIの操作性が損なわれるときなどにどうぞ。 + * + * @throws java.lang.LinkageError クラス間リンケージエラー。 + * @throws java.lang.ExceptionInInitializerError クラス初期化で異常 + */ + private static void preInitClass() + throws LinkageError, + ExceptionInInitializerError { + Object[] classes = { // Class型 または String型 + "java.lang.Object", + TabBrowser.class, + Discussion.class, + GlyphDraw.class, + java.net.HttpURLConnection.class, + java.text.SimpleDateFormat.class, + Void.class, + }; + + for(Object obj : classes){ + String className; + if(obj instanceof Class){ + className = ((Class)obj).getName(); + }else if(obj instanceof String){ + className = obj.toString(); + }else{ + continue; + } + + try{ + if(LOADER != null){ + Class.forName(className, true, LOADER); + }else{ + Class.forName(className); + } + }catch(ClassNotFoundException e){ + logger().warn("クラスの明示的ロードに失敗しました", e); + continue; + } + } + + return; + } + + /** + * AWTイベントディスパッチスレッド版スタートアップエントリ。 + */ + private static void startGUI(){ + LandsModel model = new LandsModel(); + model.loadLandList(); + + JFrame topFrame = buildMVC(model); + + GUIUtils.modifyWindowAttributes(topFrame, true, false, true); + + topFrame.pack(); + + Dimension initGeometry = + new Dimension(setting.initialFrameWidth(), + setting.initialFrameHeight()); + topFrame.setSize(initGeometry); + + if( setting.initialFrameXpos() <= Integer.MIN_VALUE + || setting.initialFrameYpos() <= Integer.MIN_VALUE ){ + topFrame.setLocationByPlatform(true); + }else{ + topFrame.setLocation(setting.initialFrameXpos(), + setting.initialFrameYpos() ); + } + + topFrame.setVisible(true); + + return; + } + + /** + * モデル・ビュー・コントローラの結合。 + * + * @param model 最上位のデータモデル + * @return アプリケーションのトップフレーム + */ + private static JFrame buildMVC(LandsModel model){ + ActionManager actionManager = new ActionManager(); + TopView topView = new TopView(); + + Controller controller = new Controller(actionManager, topView, model); + + JFrame topFrame = controller.createTopFrame(); + + return topFrame; + } + + /** + * リソースからUTF-8で記述されたテキストデータをロードする。 + * @param resourceName リソース名 + * @return テキスト文字列 + * @throws java.io.IOException 入出力の異常。おそらくビルドミス。 + */ + public static CharSequence loadResourceText(String resourceName) + throws IOException{ + InputStream is; + is = getResourceAsStream(resourceName); + is = new BufferedInputStream(is); + Reader reader = new InputStreamReader(is, "UTF-8"); + LineNumberReader lineReader = new LineNumberReader(reader); + + StringBuilder result = new StringBuilder(); + try{ + for(;;){ + String line = lineReader.readLine(); + if(line == null) break; + if(line.startsWith("#")) continue; + result.append(line).append('\n'); + } + }finally{ + lineReader.close(); + } + + return result; + } + + /** + * クラスローダを介してリソースからの入力を生成する。 + * @param name リソース名 + * @return リソースからの入力 + */ + public static InputStream getResourceAsStream(String name){ + return SELF_KLASS.getResourceAsStream(name); + } + + /** + * クラスローダを介してリソース読み込み用URLを生成する。 + * @param name リソース名 + * @return URL + */ + public static URL getResource(String name){ + return SELF_KLASS.getResource(name); + } + + /** + * 共通ロガーを取得する。 + * @return 共通ロガー + */ + public static LogWrapper logger(){ + return COMMON_LOGGER; + } + + /** + * VMごとプログラムを終了する。 + * ※おそらく随所でシャットダウンフックが起動されるはず。 + * + * @param exitCode 終了コード + * @throws java.lang.SecurityException セキュリティ違反 + */ + public static void exit(int exitCode) throws SecurityException{ + logger().info( + "終了コード[" + + exitCode + + "]でVMごとアプリケーションを終了します。" ); + RUNTIME.runFinalization(); + System.out.flush(); + System.err.flush(); + try{ + RUNTIME.exit(exitCode); + }catch(SecurityException e){ + logger().warn( + "セキュリティ設定により、" + +"VMを終了させることができません。", e); + throw e; + } + return; + } + + /** + * Jindolf のスタートアップエントリ。 + * + * @param args コマンドライン引数 + */ + public static void main(final String[] args){ + // VM内二重起動チェック + boolean hasInvoked = ! INVOKE_FLAG.compareAndSet(false, true); + if(hasInvoked){ + String errmsg = "二度目以降の起動がキャンセルされました。"; + errorDialog("多重起動", errmsg); + + // exitせずに戻るのみ + return; + } + + checkGUIEnvironment(); + + // ここからGUIウィンドウとマウス解禁 + + checkCompileError(); + checkPackageDefinition(); + + try{ + option = OptionInfo.parseOptions(args); + }catch(IllegalArgumentException e){ + String message = e.getLocalizedMessage(); + System.err.println(message); + System.err.println( + "起動オプション一覧は、" + + "起動オプションに「" + + CmdOption.OPT_HELP.toHyphened() + + "」を指定すると確認できます。" ); + Jindolf.RUNTIME.exit(1); + assert false; + return; + } + + if(option.hasOption(CmdOption.OPT_HELP)){ + showHelpMessage(); + RUNTIME.exit(0); + return; + } + + if(option.hasOption(CmdOption.OPT_VERSION)){ + System.out.println(ID); + RUNTIME.exit(0); + return; + } + + // あらゆるSwingコンポーネント操作より前に必要。 + if(option.hasOption(CmdOption.OPT_BOLDMETAL)){ + // もの凄く日本語表示が汚くなるかもよ!注意 + UIManager.put("swing.boldMetal", Boolean.TRUE); + }else{ + UIManager.put("swing.boldMetal", Boolean.FALSE); + } + + // JRE1.5用スプラッシュウィンドウ + Window splashWindow = null; + if( ! JRE_PACKAGE.isCompatibleWith("1.6") + && ! option.hasOption(CmdOption.OPT_NOSPLASH) ){ + splashWindow = createSplashWindow(); + splashWindow.setVisible(true); + Thread.yield(); + } + + setting = new AppSetting(); + setting.applyOptionInfo(option); + + if(option.hasOption(CmdOption.OPT_VMINFO)){ + System.out.println(EnvInfo.getVMInfo()); + } + + initLogging(option.hasOption(CmdOption.OPT_CONSOLELOG)); + // ここからロギング解禁 + // Jindolf.exit()もここから解禁 + + dumpBootInfo(); + + ConfigFile.setupConfigDirectory(); + ConfigFile.setupLockFile(); + // ここから設定格納ディレクトリ解禁 + + setting.loadConfig(); + + RUNTIME.addShutdownHook(new Thread(){ + @Override + public void run(){ + logger().info("シャットダウン処理に入ります…"); + System.out.flush(); + System.err.flush(); + RUNTIME.gc(); + Thread.yield(); + RUNTIME.runFinalization(); // 危険? + Thread.yield(); + return; + } + }); + + preInitClass(); + + GUIUtils.replaceEventQueue(); + + boolean hasError = false; + try{ + EventQueue.invokeAndWait(new Runnable(){ + public void run(){ + startGUI(); + return; + } + }); + }catch(InvocationTargetException e){ + logger().fatal("アプリケーション初期化に失敗しました", e); + e.printStackTrace(System.err); + hasError = true; + }catch(InterruptedException e){ + logger().fatal("アプリケーション初期化に失敗しました", e); + e.printStackTrace(System.err); + hasError = true; + }finally{ + if(splashWindow != null){ + splashWindow.setVisible(false); + splashWindow.dispose(); + splashWindow = null; + } + } + + if(hasError) exit(1); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Land.java b/src/main/java/jp/sourceforge/jindolf/Land.java index e448a8d..c0d4fc1 100644 --- a/src/main/java/jp/sourceforge/jindolf/Land.java +++ b/src/main/java/jp/sourceforge/jindolf/Land.java @@ -1,425 +1,425 @@ -/* - * land - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import jp.sourceforge.jindolf.corelib.LandDef; -import jp.sourceforge.jindolf.corelib.LandState; -import jp.sourceforge.jindolf.corelib.VillageState; -import jp.sourceforge.jindolf.parser.DecodedContent; -import jp.sourceforge.jindolf.parser.HtmlAdapter; -import jp.sourceforge.jindolf.parser.HtmlParseException; -import jp.sourceforge.jindolf.parser.HtmlParser; -import jp.sourceforge.jindolf.parser.PageType; -import jp.sourceforge.jindolf.parser.SeqRange; - -/** - * いわゆる「国」。 - */ -public class Land { - - // 古国ID - private static final String ID_VANILLAWOLF = "wolf"; - - - private final LandDef landDef; - private final ServerAccess serverAccess; - private final HtmlParser parser = new HtmlParser(); - private final VillageListHandler handler = new VillageListHandler(); - - private final List villageList = new LinkedList(); - - - /** - * コンストラクタ。 - * @param landDef 国定義 - * @throws java.lang.IllegalArgumentException 不正な国定義 - */ - public Land(LandDef landDef) throws IllegalArgumentException{ - super(); - - this.landDef = landDef; - - URL url; - try{ - url = this.landDef.getCgiURI().toURL(); - }catch(MalformedURLException e){ - throw new IllegalArgumentException(e); - } - this.serverAccess = new ServerAccess(url, this.landDef.getEncoding()); - - this.parser.setBasicHandler(this.handler); - - return; - } - - - /** - * クエリー文字列から特定キーの値を得る。 - * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd - * @param key キー - * @param allQuery クエリー - * @return 値 - */ - public static String getValueFromCGIQueries(String key, - String allQuery){ - String result = null; - - String[] queries = allQuery.split("\\Q&\\E"); - - for(String pair : queries){ - if(pair == null) continue; - String[] namevalue = pair.split("\\Q=\\E"); - if(namevalue == null) continue; - if(namevalue.length != 2) continue; - String name = namevalue[0]; - String value = namevalue[1]; - if(name == null) continue; - if( name.equals(key) ){ - result = value; - if(result == null) continue; - if(result.length() <= 0) continue; - break; - } - } - - return result; - } - - /** - * AタグのHREF属性値からクエリー部を抽出する。 - * 「{@literal &}」は「{@literal &}」に解釈される。 - * @param hrefValue HREF属性値 - * @return クエリー文字列 - */ - public static String getRawQueryFromHREF(CharSequence hrefValue){ - if(hrefValue == null) return null; - - // HTML 4.01 B.2.2 rule - String pureHREF = hrefValue.toString().replace("&", "&"); - - URI uri; - try{ - uri = new URI(pureHREF); - }catch(URISyntaxException e){ - Jindolf.logger().warn( - "不正なURI[" - + hrefValue - + "]を検出しました"); - return null; - } - - String rawQuery = uri.getRawQuery(); - - return rawQuery; - } - - /** - * AタグのHREF属性値から村IDを得る。 - * @param hrefValue HREF値 - * @return village 村ID - */ - public static String getVillageIDFromHREF(CharSequence hrefValue){ - String rawQuery = getRawQueryFromHREF(hrefValue); - if(rawQuery == null) return null; - - String villageID = getValueFromCGIQueries("vid", rawQuery); - if(villageID == null) return null; - if(villageID.length() <= 0) return null; - - return villageID; - } - - /** - * 国定義を得る。 - * @return 国定義 - */ - public LandDef getLandDef(){ - return this.landDef; - } - - /** - * サーバ接続を返す。 - * @return ServerAccessインスタンス - */ - public ServerAccess getServerAccess(){ - return this.serverAccess; - } - - /** - * 指定されたインデックス位置の村を返す。 - * @param index 0から始まるインデックス値 - * @return 村 - */ - public Village getVillage(int index){ - if(index < 0) return null; - if(index >= getVillageCount()) return null; - - Village result = this.villageList.get(index); - return result; - } - - /** - * 村の総数を返す。 - * @return 村の総数 - */ - public int getVillageCount(){ - int result = this.villageList.size(); - return result; - } - - /** - * 村のリストを返す。 - * @return 村のリスト - */ - // TODO インスタンス変数でいいはず。 - public List getVillageList(){ - return Collections.unmodifiableList(this.villageList); - } - - /** - * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。 - * ※ A,B,D 国の顔アイコンは絶対パスらしい…。 - * @param imageURL 画像URL文字列 - * @return 画像イメージ - */ - public BufferedImage downloadImage(String imageURL){ - ServerAccess server = getServerAccess(); - BufferedImage image; - try{ - image = server.downloadImage(imageURL); - }catch(IOException e){ - Jindolf.logger().warn( - "イメージ[" + imageURL + "]" - + "のダウンロードに失敗しました", - e ); - return null; - } - return image; - } - - /** - * 墓アイコンイメージを取得する。 - * @return 墓アイコンイメージ - */ - public BufferedImage getGraveIconImage(){ - URI uri = getLandDef().getTombFaceIconURI(); - BufferedImage result = downloadImage(uri.toASCIIString()); - return result; - } - - /** - * 墓アイコンイメージ(大)を取得する。 - * @return 墓アイコンイメージ(大) - */ - public BufferedImage getGraveBodyImage(){ - URI uri = getLandDef().getTombBodyIconURI(); - BufferedImage result = downloadImage(uri.toASCIIString()); - return result; - } - - /** - * 村リストを更新する。 - * 元情報は国のトップページと村一覧ページ。 - * 古国の場合は村一覧にアクセスせずトップページのみ。 - * 古国以外に村建てをやめた国はトップページにアクセスしない。 - * 村リストはVillageの実装に従いソートされる。重複する村は排除。 - * @throws java.io.IOException ネットワーク入出力の異常 - */ - public void updateVillageList() throws IOException{ - LandDef thisLand = getLandDef(); - LandState state = thisLand.getLandState(); - boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF); - - ServerAccess server = getServerAccess(); - - // たまに同じ村が複数回出現するので注意! - SortedSet vset = new TreeSet(); - - // トップページ - if(state.equals(LandState.ACTIVE) || isVanillaWolf){ - HtmlSequence html = server.getHTMLTopPage(); - DecodedContent content = html.getContent(); - try{ - this.parser.parseAutomatic(content); - }catch(HtmlParseException e){ - Jindolf.logger().warn("トップページを認識できない", e); - } - List list = this.handler.getVillageList(); - if(list != null){ - vset.addAll(list); - } - } - - // 村一覧ページ - if( ! isVanillaWolf ){ - HtmlSequence html = server.getHTMLLandList(); - DecodedContent content = html.getContent(); - try{ - this.parser.parseAutomatic(content); - }catch(HtmlParseException e){ - Jindolf.logger().warn("村一覧ページを認識できない", e); - } - List list = this.handler.getVillageList(); - if(list != null){ - vset.addAll(list); - } - } - - // TODO 村リスト更新のイベントリスナがあると便利か? - this.villageList.clear(); - this.villageList.addAll(vset); - - this.parser.reset(); - this.handler.reset(); - - return; - } - - /** - * 国の文字列表現を返す。 - * @return 文字列表現 - */ - @Override - public String toString(){ - return getLandDef().getLandName(); - } - - /** - * 村一覧取得用ハンドラ。 - */ - private class VillageListHandler extends HtmlAdapter{ - - private List villageList = null; - - /** - * コンストラクタ。 - */ - public VillageListHandler(){ - super(); - return; - } - - /** - * 村一覧を返す。 - * 再度パースを行うまで呼んではいけない。 - * @return 村一覧 - * @throws IllegalStateException パース前に呼び出された。 - * あるいはパース後すでにリセットされている。 - */ - public List getVillageList() throws IllegalStateException{ - if(this.villageList == null){ - throw new IllegalStateException("パースが必要です。"); - } - - List result = this.villageList; - - return result; - } - - /** - * リセットを行う。 - * 村一覧は空になる。 - */ - public void reset(){ - this.villageList = null; - return; - } - - /** - * {@inheritDoc} - * 村一覧リストが初期化される。 - * @param content {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void startParse(DecodedContent content) - throws HtmlParseException{ - reset(); - this.villageList = new LinkedList(); - return; - } - - /** - * {@inheritDoc} - * 自動判定の結果がトップページでも村一覧ページでもなければ - * 例外を投げる。 - * @param type {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。 - */ - @Override - public void pageType(PageType type) throws HtmlParseException{ - if( type != PageType.VILLAGELIST_PAGE - && type != PageType.TOP_PAGE ){ - throw new HtmlParseException( - "トップページか村一覧ページが必要です。"); - } - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param anchorRange {@inheritDoc} - * @param villageRange {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @param villageState {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void villageRecord(DecodedContent content, - SeqRange anchorRange, - SeqRange villageRange, - int hour, int minute, - VillageState villageState) - throws HtmlParseException{ - LandDef landdef = getLandDef(); - LandState landState = landdef.getLandState(); - - CharSequence href = anchorRange.sliceSequence(content); - String villageID = getVillageIDFromHREF(href); - if( villageID == null - || villageID.length() <= 0 ){ - Jindolf.logger().warn( - "認識できないURL[" + href + "]に遭遇しました。"); - return; - } - - CharSequence fullVillageName = - villageRange.sliceSequence(content); - - // TODO 既に出来ているかもしれないVillageを再度作るのは無駄? - Village village = new Village(Land.this, - villageID, - fullVillageName.toString() ); - - if(landState == LandState.HISTORICAL){ - village.setState(VillageState.GAMEOVER); - }else{ - village.setState(villageState); - } - - this.villageList.add(village); - - return; - } - - } - -} +/* + * land + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import jp.sourceforge.jindolf.corelib.LandDef; +import jp.sourceforge.jindolf.corelib.LandState; +import jp.sourceforge.jindolf.corelib.VillageState; +import jp.sourceforge.jindolf.parser.DecodedContent; +import jp.sourceforge.jindolf.parser.HtmlAdapter; +import jp.sourceforge.jindolf.parser.HtmlParseException; +import jp.sourceforge.jindolf.parser.HtmlParser; +import jp.sourceforge.jindolf.parser.PageType; +import jp.sourceforge.jindolf.parser.SeqRange; + +/** + * いわゆる「国」。 + */ +public class Land { + + // 古国ID + private static final String ID_VANILLAWOLF = "wolf"; + + + private final LandDef landDef; + private final ServerAccess serverAccess; + private final HtmlParser parser = new HtmlParser(); + private final VillageListHandler handler = new VillageListHandler(); + + private final List villageList = new LinkedList(); + + + /** + * コンストラクタ。 + * @param landDef 国定義 + * @throws java.lang.IllegalArgumentException 不正な国定義 + */ + public Land(LandDef landDef) throws IllegalArgumentException{ + super(); + + this.landDef = landDef; + + URL url; + try{ + url = this.landDef.getCgiURI().toURL(); + }catch(MalformedURLException e){ + throw new IllegalArgumentException(e); + } + this.serverAccess = new ServerAccess(url, this.landDef.getEncoding()); + + this.parser.setBasicHandler(this.handler); + + return; + } + + + /** + * クエリー文字列から特定キーの値を得る。 + * クエリーの書式例:「a=b&c=d&e=f」この場合キーcの値はd + * @param key キー + * @param allQuery クエリー + * @return 値 + */ + public static String getValueFromCGIQueries(String key, + String allQuery){ + String result = null; + + String[] queries = allQuery.split("\\Q&\\E"); + + for(String pair : queries){ + if(pair == null) continue; + String[] namevalue = pair.split("\\Q=\\E"); + if(namevalue == null) continue; + if(namevalue.length != 2) continue; + String name = namevalue[0]; + String value = namevalue[1]; + if(name == null) continue; + if( name.equals(key) ){ + result = value; + if(result == null) continue; + if(result.length() <= 0) continue; + break; + } + } + + return result; + } + + /** + * AタグのHREF属性値からクエリー部を抽出する。 + * 「{@literal &}」は「{@literal &}」に解釈される。 + * @param hrefValue HREF属性値 + * @return クエリー文字列 + */ + public static String getRawQueryFromHREF(CharSequence hrefValue){ + if(hrefValue == null) return null; + + // HTML 4.01 B.2.2 rule + String pureHREF = hrefValue.toString().replace("&", "&"); + + URI uri; + try{ + uri = new URI(pureHREF); + }catch(URISyntaxException e){ + Jindolf.logger().warn( + "不正なURI[" + + hrefValue + + "]を検出しました"); + return null; + } + + String rawQuery = uri.getRawQuery(); + + return rawQuery; + } + + /** + * AタグのHREF属性値から村IDを得る。 + * @param hrefValue HREF値 + * @return village 村ID + */ + public static String getVillageIDFromHREF(CharSequence hrefValue){ + String rawQuery = getRawQueryFromHREF(hrefValue); + if(rawQuery == null) return null; + + String villageID = getValueFromCGIQueries("vid", rawQuery); + if(villageID == null) return null; + if(villageID.length() <= 0) return null; + + return villageID; + } + + /** + * 国定義を得る。 + * @return 国定義 + */ + public LandDef getLandDef(){ + return this.landDef; + } + + /** + * サーバ接続を返す。 + * @return ServerAccessインスタンス + */ + public ServerAccess getServerAccess(){ + return this.serverAccess; + } + + /** + * 指定されたインデックス位置の村を返す。 + * @param index 0から始まるインデックス値 + * @return 村 + */ + public Village getVillage(int index){ + if(index < 0) return null; + if(index >= getVillageCount()) return null; + + Village result = this.villageList.get(index); + return result; + } + + /** + * 村の総数を返す。 + * @return 村の総数 + */ + public int getVillageCount(){ + int result = this.villageList.size(); + return result; + } + + /** + * 村のリストを返す。 + * @return 村のリスト + */ + // TODO インスタンス変数でいいはず。 + public List getVillageList(){ + return Collections.unmodifiableList(this.villageList); + } + + /** + * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。 + * ※ A,B,D 国の顔アイコンは絶対パスらしい…。 + * @param imageURL 画像URL文字列 + * @return 画像イメージ + */ + public BufferedImage downloadImage(String imageURL){ + ServerAccess server = getServerAccess(); + BufferedImage image; + try{ + image = server.downloadImage(imageURL); + }catch(IOException e){ + Jindolf.logger().warn( + "イメージ[" + imageURL + "]" + + "のダウンロードに失敗しました", + e ); + return null; + } + return image; + } + + /** + * 墓アイコンイメージを取得する。 + * @return 墓アイコンイメージ + */ + public BufferedImage getGraveIconImage(){ + URI uri = getLandDef().getTombFaceIconURI(); + BufferedImage result = downloadImage(uri.toASCIIString()); + return result; + } + + /** + * 墓アイコンイメージ(大)を取得する。 + * @return 墓アイコンイメージ(大) + */ + public BufferedImage getGraveBodyImage(){ + URI uri = getLandDef().getTombBodyIconURI(); + BufferedImage result = downloadImage(uri.toASCIIString()); + return result; + } + + /** + * 村リストを更新する。 + * 元情報は国のトップページと村一覧ページ。 + * 古国の場合は村一覧にアクセスせずトップページのみ。 + * 古国以外に村建てをやめた国はトップページにアクセスしない。 + * 村リストはVillageの実装に従いソートされる。重複する村は排除。 + * @throws java.io.IOException ネットワーク入出力の異常 + */ + public void updateVillageList() throws IOException{ + LandDef thisLand = getLandDef(); + LandState state = thisLand.getLandState(); + boolean isVanillaWolf = thisLand.getLandId().equals(ID_VANILLAWOLF); + + ServerAccess server = getServerAccess(); + + // たまに同じ村が複数回出現するので注意! + SortedSet vset = new TreeSet(); + + // トップページ + if(state.equals(LandState.ACTIVE) || isVanillaWolf){ + HtmlSequence html = server.getHTMLTopPage(); + DecodedContent content = html.getContent(); + try{ + this.parser.parseAutomatic(content); + }catch(HtmlParseException e){ + Jindolf.logger().warn("トップページを認識できない", e); + } + List list = this.handler.getVillageList(); + if(list != null){ + vset.addAll(list); + } + } + + // 村一覧ページ + if( ! isVanillaWolf ){ + HtmlSequence html = server.getHTMLLandList(); + DecodedContent content = html.getContent(); + try{ + this.parser.parseAutomatic(content); + }catch(HtmlParseException e){ + Jindolf.logger().warn("村一覧ページを認識できない", e); + } + List list = this.handler.getVillageList(); + if(list != null){ + vset.addAll(list); + } + } + + // TODO 村リスト更新のイベントリスナがあると便利か? + this.villageList.clear(); + this.villageList.addAll(vset); + + this.parser.reset(); + this.handler.reset(); + + return; + } + + /** + * 国の文字列表現を返す。 + * @return 文字列表現 + */ + @Override + public String toString(){ + return getLandDef().getLandName(); + } + + /** + * 村一覧取得用ハンドラ。 + */ + private class VillageListHandler extends HtmlAdapter{ + + private List villageList = null; + + /** + * コンストラクタ。 + */ + public VillageListHandler(){ + super(); + return; + } + + /** + * 村一覧を返す。 + * 再度パースを行うまで呼んではいけない。 + * @return 村一覧 + * @throws IllegalStateException パース前に呼び出された。 + * あるいはパース後すでにリセットされている。 + */ + public List getVillageList() throws IllegalStateException{ + if(this.villageList == null){ + throw new IllegalStateException("パースが必要です。"); + } + + List result = this.villageList; + + return result; + } + + /** + * リセットを行う。 + * 村一覧は空になる。 + */ + public void reset(){ + this.villageList = null; + return; + } + + /** + * {@inheritDoc} + * 村一覧リストが初期化される。 + * @param content {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void startParse(DecodedContent content) + throws HtmlParseException{ + reset(); + this.villageList = new LinkedList(); + return; + } + + /** + * {@inheritDoc} + * 自動判定の結果がトップページでも村一覧ページでもなければ + * 例外を投げる。 + * @param type {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。 + */ + @Override + public void pageType(PageType type) throws HtmlParseException{ + if( type != PageType.VILLAGELIST_PAGE + && type != PageType.TOP_PAGE ){ + throw new HtmlParseException( + "トップページか村一覧ページが必要です。"); + } + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param anchorRange {@inheritDoc} + * @param villageRange {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @param villageState {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void villageRecord(DecodedContent content, + SeqRange anchorRange, + SeqRange villageRange, + int hour, int minute, + VillageState villageState) + throws HtmlParseException{ + LandDef landdef = getLandDef(); + LandState landState = landdef.getLandState(); + + CharSequence href = anchorRange.sliceSequence(content); + String villageID = getVillageIDFromHREF(href); + if( villageID == null + || villageID.length() <= 0 ){ + Jindolf.logger().warn( + "認識できないURL[" + href + "]に遭遇しました。"); + return; + } + + CharSequence fullVillageName = + villageRange.sliceSequence(content); + + // TODO 既に出来ているかもしれないVillageを再度作るのは無駄? + Village village = new Village(Land.this, + villageID, + fullVillageName.toString() ); + + if(landState == LandState.HISTORICAL){ + village.setState(VillageState.GAMEOVER); + }else{ + village.setState(villageState); + } + + this.villageList.add(village); + + return; + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/LandInfoPanel.java b/src/main/java/jp/sourceforge/jindolf/LandInfoPanel.java index 7ad22b6..4ab35ca 100644 --- a/src/main/java/jp/sourceforge/jindolf/LandInfoPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/LandInfoPanel.java @@ -1,167 +1,167 @@ -/* - * Land information panel - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.text.DateFormat; -import java.util.Date; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import jp.sourceforge.jindolf.corelib.LandDef; -import jp.sourceforge.jindolf.corelib.LandState; - -/** - * 国情報表示パネル。 - */ -@SuppressWarnings("serial") -public class LandInfoPanel extends JPanel{ - - private final JLabel landName = new JLabel(); - private final JLabel landIdentifier = new JLabel(); - private final WebButton webURL = new WebButton(); - private final JLabel startDate = new JLabel(); - private final JLabel endDate = new JLabel(); - private final JLabel landState = new JLabel(); - private final JLabel locale = new JLabel(); - private final JLabel timezone = new JLabel(); - private final WebButton contact = new WebButton(); - private final JLabel description = new JLabel(); - - - /** - * コンストラクタ。 - */ - public LandInfoPanel(){ - super(); - - Monodizer.monodize(this.landIdentifier); - Monodizer.monodize(this.locale); - - design(); - - return; - } - - - /** - * 国の状態を文字列化する。 - * @param state 国状態 - * @return 文字列化された国状態 - */ - private static String getStatusMark(LandState state){ - String result; - - switch(state){ - case CLOSED: result = "サービス終了"; break; - case HISTORICAL: result = "過去ログ提供のみ"; break; - case ACTIVE: result = "稼動中"; break; - default: - assert false; - result = ""; - break; - } - - return result; - } - - /** - * 一行分レイアウトする。 - * @param item 項目名 - * @param comp コンポーネント - */ - private void layoutRow(String item, JComponent comp){ - GridBagConstraints constraints = new GridBagConstraints(); - constraints.insets = new Insets(2, 2, 2, 2); - - String itemCaption = item + " : "; - JLabel itemLabel = new JLabel(itemCaption); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(itemLabel, constraints); - - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(comp, constraints); - - } - - /** - * レイアウトを行う。 - */ - private void design(){ - GridBagLayout layout = new GridBagLayout(); - setLayout(layout); - - layoutRow("国名", this.landName); - layoutRow("識別名", this.landIdentifier); - layoutRow("Webサイト", this.webURL); - layoutRow("建国", this.startDate); - layoutRow("亡国", this.endDate); - layoutRow("状態", this.landState); - layoutRow("ロケール", this.locale); - layoutRow("時間帯", this.timezone); - layoutRow("連絡先", this.contact); - layoutRow("説明", this.description); - - GridBagConstraints constraints = new GridBagConstraints(); - constraints.fill = GridBagConstraints.BOTH; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.gridheight = GridBagConstraints.REMAINDER; - add(new JPanel(), constraints); // ダミー詰め物 - - return; - } - - /** - * 国情報を更新する。 - * @param land 国 - */ - public void update(Land land){ - LandDef landDef = land.getLandDef(); - - DateFormat dform = - DateFormat.getDateTimeInstance(DateFormat.FULL, - DateFormat.FULL); - - long start = landDef.getStartDateTime(); - String startStr = dform.format(new Date(start)); - if(start < 0){ - startStr = "(不明)"; - } - - long end = landDef.getEndDateTime(); - String endStr = dform.format(new Date(end)); - if(end < 0){ - endStr = "まだまだ"; - } - - String status = getStatusMark(land.getLandDef().getLandState()); - - this.landName .setText(landDef.getLandName()); - this.landIdentifier .setText(landDef.getLandId()); - this.webURL .setURI(land.getLandDef().getWebURI()); - this.startDate .setText(startStr); - this.endDate .setText(endStr); - this.landState .setText(status); - this.locale .setText(landDef.getLocale().toString()); - this.timezone .setText(landDef.getTimeZone().getDisplayName()); - this.contact .setURLText(landDef.getContactInfo()); - this.description .setText(landDef.getDescription()); - - revalidate(); - - return; - } - -} +/* + * Land information panel + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.text.DateFormat; +import java.util.Date; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import jp.sourceforge.jindolf.corelib.LandDef; +import jp.sourceforge.jindolf.corelib.LandState; + +/** + * 国情報表示パネル。 + */ +@SuppressWarnings("serial") +public class LandInfoPanel extends JPanel{ + + private final JLabel landName = new JLabel(); + private final JLabel landIdentifier = new JLabel(); + private final WebButton webURL = new WebButton(); + private final JLabel startDate = new JLabel(); + private final JLabel endDate = new JLabel(); + private final JLabel landState = new JLabel(); + private final JLabel locale = new JLabel(); + private final JLabel timezone = new JLabel(); + private final WebButton contact = new WebButton(); + private final JLabel description = new JLabel(); + + + /** + * コンストラクタ。 + */ + public LandInfoPanel(){ + super(); + + Monodizer.monodize(this.landIdentifier); + Monodizer.monodize(this.locale); + + design(); + + return; + } + + + /** + * 国の状態を文字列化する。 + * @param state 国状態 + * @return 文字列化された国状態 + */ + private static String getStatusMark(LandState state){ + String result; + + switch(state){ + case CLOSED: result = "サービス終了"; break; + case HISTORICAL: result = "過去ログ提供のみ"; break; + case ACTIVE: result = "稼動中"; break; + default: + assert false; + result = ""; + break; + } + + return result; + } + + /** + * 一行分レイアウトする。 + * @param item 項目名 + * @param comp コンポーネント + */ + private void layoutRow(String item, JComponent comp){ + GridBagConstraints constraints = new GridBagConstraints(); + constraints.insets = new Insets(2, 2, 2, 2); + + String itemCaption = item + " : "; + JLabel itemLabel = new JLabel(itemCaption); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(itemLabel, constraints); + + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(comp, constraints); + + } + + /** + * レイアウトを行う。 + */ + private void design(){ + GridBagLayout layout = new GridBagLayout(); + setLayout(layout); + + layoutRow("国名", this.landName); + layoutRow("識別名", this.landIdentifier); + layoutRow("Webサイト", this.webURL); + layoutRow("建国", this.startDate); + layoutRow("亡国", this.endDate); + layoutRow("状態", this.landState); + layoutRow("ロケール", this.locale); + layoutRow("時間帯", this.timezone); + layoutRow("連絡先", this.contact); + layoutRow("説明", this.description); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.gridheight = GridBagConstraints.REMAINDER; + add(new JPanel(), constraints); // ダミー詰め物 + + return; + } + + /** + * 国情報を更新する。 + * @param land 国 + */ + public void update(Land land){ + LandDef landDef = land.getLandDef(); + + DateFormat dform = + DateFormat.getDateTimeInstance(DateFormat.FULL, + DateFormat.FULL); + + long start = landDef.getStartDateTime(); + String startStr = dform.format(new Date(start)); + if(start < 0){ + startStr = "(不明)"; + } + + long end = landDef.getEndDateTime(); + String endStr = dform.format(new Date(end)); + if(end < 0){ + endStr = "まだまだ"; + } + + String status = getStatusMark(land.getLandDef().getLandState()); + + this.landName .setText(landDef.getLandName()); + this.landIdentifier .setText(landDef.getLandId()); + this.webURL .setURI(land.getLandDef().getWebURI()); + this.startDate .setText(startStr); + this.endDate .setText(endStr); + this.landState .setText(status); + this.locale .setText(landDef.getLocale().toString()); + this.timezone .setText(landDef.getTimeZone().getDisplayName()); + this.contact .setURLText(landDef.getContactInfo()); + this.description .setText(landDef.getDescription()); + + revalidate(); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/LandsModel.java b/src/main/java/jp/sourceforge/jindolf/LandsModel.java index f5774b9..d5380f3 100644 --- a/src/main/java/jp/sourceforge/jindolf/LandsModel.java +++ b/src/main/java/jp/sourceforge/jindolf/LandsModel.java @@ -1,455 +1,455 @@ -/* - * model of lands for JTree view - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.swing.event.EventListenerList; -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.ParserConfigurationException; -import jp.sourceforge.jindolf.corelib.LandDef; -import org.xml.sax.SAXException; - -/** - * 国の集合。あらゆるデータモデルの大元。 - * 国一覧と村一覧を管理。 - * JTreeのモデルも兼用。 - */ -public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか? - - private static final String ROOT = "ROOT"; - private static final int SECTION_INTERVAL = 100; - - - private final List landList = new LinkedList(); - private final List unmodList = - Collections.unmodifiableList(this.landList); - private final Map> sectionMap = - new HashMap>(); - private boolean isLandListLoaded = false; - - private final EventListenerList listeners = new EventListenerList(); - - private boolean ascending = false; - - /** - * コンストラクタ。 - * この時点ではまだ国一覧が読み込まれない。 - */ - public LandsModel(){ - super(); - return; - } - - /** - * 指定した国の村一覧を読み込む。 - * @param land 国 - * @throws java.io.IOException ネットワーク入出力の異常 - */ - public void loadVillageList(Land land) throws IOException{ - land.updateVillageList(); - - List sectionList = - VillageSection.getSectionList(land, SECTION_INTERVAL); - this.sectionMap.put(land, sectionList); - - int[] childIndices = new int[sectionList.size()]; - for(int ct = 0; ct < childIndices.length; ct++){ - childIndices[ct] = ct; - } - Object[] children = sectionList.toArray(); - - Object[] path = {ROOT, land}; - TreePath treePath = new TreePath(path); - TreeModelEvent event = new TreeModelEvent(this, - treePath, - childIndices, - children ); - fireTreeStructureChanged(event); - - return; - } - - /** - * 国一覧を読み込む。 - */ - // TODO static にできない? - public void loadLandList(){ - if(this.isLandListLoaded) return; - - this.landList.clear(); - - List landDefList; - try{ - DocumentBuilder builder = XmlUtils.createDocumentBuilder(); - landDefList = LandDef.buildLandDefList(builder); - }catch(IOException e){ - Jindolf.logger().fatal("failed to load land list", e); - return; - }catch(SAXException e){ - Jindolf.logger().fatal("failed to load land list", e); - return; - }catch(URISyntaxException e){ - Jindolf.logger().fatal("failed to load land list", e); - return; - }catch(ParserConfigurationException e){ - Jindolf.logger().fatal("failed to load land list", e); - return; - } - - for(LandDef landDef : landDefList){ - Land land = new Land(landDef); - this.landList.add(land); - } - - this.isLandListLoaded = true; - - fireLandListChanged(); - - return; - } - - /** - * ツリー内容が更新された事をリスナーに通知する。 - */ - private void fireLandListChanged(){ - int size = this.landList.size(); - int[] childIndices = new int[size]; - for(int ct = 0; ct < size; ct++){ - int index = ct; - childIndices[ct] = index; - } - - Object[] children = this.landList.toArray(); - - TreePath treePath = new TreePath(ROOT); - TreeModelEvent event = new TreeModelEvent(this, - treePath, - childIndices, - children ); - fireTreeStructureChanged(event); - - return; - } - - /** - * ツリーの並び順を設定する。 - * 場合によってはTreeModelEventが発生する。 - * @param ascending trueなら昇順 - */ - public void setAscending(boolean ascending){ - if(this.ascending == ascending) return; - - this.ascending = ascending; - fireLandListChanged(); - - return; - } - - /** - * {@inheritDoc} - * @param l {@inheritDoc} - */ - @Override - public void addTreeModelListener(TreeModelListener l){ - this.listeners.add(TreeModelListener.class, l); - return; - } - - /** - * {@inheritDoc} - * @param l {@inheritDoc} - */ - @Override - public void removeTreeModelListener(TreeModelListener l){ - this.listeners.remove(TreeModelListener.class, l); - return; - } - - /** - * 登録中のリスナーのリストを得る。 - * @return リスナーのリスト - */ - private TreeModelListener[] getTreeModelListeners(){ - return this.listeners.getListeners(TreeModelListener.class); - } - - /** - * 全リスナーにイベントを送出する。 - * @param event ツリーイベント - */ - protected void fireTreeStructureChanged(TreeModelEvent event){ - for(TreeModelListener listener : getTreeModelListeners()){ - listener.treeStructureChanged(event); - } - return; - } - - /** - * 国リストを得る。 - * @return 国のリスト - */ - public List getLandList(){ - return this.unmodList; - } - - /** - * {@inheritDoc} - * @param parent {@inheritDoc} - * @param index {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Object getChild(Object parent, int index){ - if(index < 0) return null; - if(index >= getChildCount(parent)) return null; - - if(parent == ROOT){ - List list = getLandList(); - int landIndex = index; - if( ! this.ascending) landIndex = list.size() - index - 1; - Land land = list.get(landIndex); - return land; - } - if(parent instanceof Land){ - Land land = (Land)parent; - List sectionList = this.sectionMap.get(land); - int sectIndex = index; - if( ! this.ascending) sectIndex = sectionList.size() - index - 1; - VillageSection section = sectionList.get(sectIndex); - return section; - } - if(parent instanceof VillageSection){ - VillageSection section = (VillageSection)parent; - int vilIndex = index; - if( ! this.ascending){ - vilIndex = section.getVillageCount() - index - 1; - } - Village village = section.getVillage(vilIndex); - return village; - } - return null; - } - - /** - * {@inheritDoc} - * @param parent {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getChildCount(Object parent){ - if(parent == ROOT){ - return getLandList().size(); - } - if(parent instanceof Land){ - Land land = (Land)parent; - List sectionList = this.sectionMap.get(land); - if(sectionList == null) return 0; - return sectionList.size(); - } - if(parent instanceof VillageSection){ - VillageSection section = (VillageSection)parent; - return section.getVillageCount(); - } - return 0; - } - - /** - * {@inheritDoc} - * @param parent {@inheritDoc} - * @param child {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getIndexOfChild(Object parent, Object child){ - if(child == null) return -1; - if(parent == ROOT){ - List list = getLandList(); - int index = list.indexOf(child); - if( ! this.ascending) index = list.size() - index - 1; - return index; - } - if(parent instanceof Land){ - Land land = (Land)parent; - List sectionList = this.sectionMap.get(land); - int index = sectionList.indexOf(child); - if( ! this.ascending) index = sectionList.size() - index - 1; - return index; - } - if(parent instanceof VillageSection){ - VillageSection section = (VillageSection)parent; - int index = section.getIndexOfVillage(child); - if( ! this.ascending){ - index = section.getVillageCount() - index - 1; - } - return index; - } - return -1; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Object getRoot(){ - return ROOT; - } - - /** - * {@inheritDoc} - * @param node {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean isLeaf(Object node){ - if(node == ROOT) return false; - if(node instanceof Land) return false; - if(node instanceof VillageSection) return false; - if(node instanceof Village) return true; - return true; - } - - /** - * {@inheritDoc} - * ※ たぶん使わないので必ず失敗させている。 - * @param path {@inheritDoc} - * @param newValue {@inheritDoc} - */ - @Override - public void valueForPathChanged(TreePath path, Object newValue){ - throw new UnsupportedOperationException("Not supported yet."); - } - - /** - * 村IDで範囲指定した、村のセクション集合。国-村間の中間ツリー。 - * @see javax.swing.tree.TreeModel - */ - private static final class VillageSection{ - - private final int startID; - private final int endID; - private final String prefix; - - private final List villageList = new LinkedList(); - - - /** - * セクション集合を生成する。 - * @param land 国 - * @param startID 開始村ID - * @param endID 終了村ID - * @throws java.lang.IndexOutOfBoundsException IDの範囲指定が変 - */ - private VillageSection(Land land, int startID, int endID) - throws IndexOutOfBoundsException{ - super(); - - if(startID < 0 || startID > endID){ - throw new IndexOutOfBoundsException(); - } - - this.startID = startID; - this.endID = endID; - this.prefix = land.getLandDef().getLandPrefix(); - - for(Village village : land.getVillageList()){ - int id = village.getVillageIDNum(); - if(startID <= id && id <= endID){ - this.villageList.add(village); - } - } - - return; - } - - - /** - * 与えられた国の全ての村を、指定されたinterval間隔でセクション化する。 - * @param land 国 - * @param interval セクションの間隔 - * @return セクションのリスト - * @throws java.lang.IllegalArgumentException intervalが正でない - */ - private static List getSectionList(Land land, - int interval ) - throws IllegalArgumentException{ - if(interval <= 0){ - throw new IllegalArgumentException(); - } - - List villageList = land.getVillageList(); - Village village1st = villageList.get(0); - Village villageLast = villageList.get(villageList.size() - 1); - - int startID = village1st.getVillageIDNum(); - int endID = villageLast.getVillageIDNum(); - - List result = new LinkedList(); - - int fixedStart = startID / interval * interval; - for(int ct = fixedStart; ct <= endID; ct += interval){ - VillageSection section = - new VillageSection(land, ct, ct + interval - 1); - result.add(section); - } - - return Collections.unmodifiableList(result); - } - - /** - * セクションに含まれる村の総数を返す。 - * @return 村の総数 - */ - private int getVillageCount(){ - return this.villageList.size(); - } - - /** - * セクションに含まれるindex番目の村を返す。 - * @param index インデックス - * @return index番目の村 - */ - private Village getVillage(int index){ - return this.villageList.get(index); - } - - /** - * セクションにおける、指定された子(村)のインデックス位置を返す。 - * @param child 子 - * @return インデックス位置 - */ - private int getIndexOfVillage(Object child){ - return this.villageList.indexOf(child); - } - - /** - * セクションの文字列表記。 - * JTree描画に反映される。 - * @return 文字列表記 - */ - @Override - public String toString(){ - StringBuilder result = new StringBuilder(); - result.append(this.prefix).append(this.startID); - result.append(" ~ "); - result.append(this.prefix).append(this.endID); - return result.toString(); - } - } - -} +/* + * model of lands for JTree view + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.swing.event.EventListenerList; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; +import jp.sourceforge.jindolf.corelib.LandDef; +import org.xml.sax.SAXException; + +/** + * 国の集合。あらゆるデータモデルの大元。 + * 国一覧と村一覧を管理。 + * JTreeのモデルも兼用。 + */ +public class LandsModel implements TreeModel{ // ComboBoxModelも付けるか? + + private static final String ROOT = "ROOT"; + private static final int SECTION_INTERVAL = 100; + + + private final List landList = new LinkedList(); + private final List unmodList = + Collections.unmodifiableList(this.landList); + private final Map> sectionMap = + new HashMap>(); + private boolean isLandListLoaded = false; + + private final EventListenerList listeners = new EventListenerList(); + + private boolean ascending = false; + + /** + * コンストラクタ。 + * この時点ではまだ国一覧が読み込まれない。 + */ + public LandsModel(){ + super(); + return; + } + + /** + * 指定した国の村一覧を読み込む。 + * @param land 国 + * @throws java.io.IOException ネットワーク入出力の異常 + */ + public void loadVillageList(Land land) throws IOException{ + land.updateVillageList(); + + List sectionList = + VillageSection.getSectionList(land, SECTION_INTERVAL); + this.sectionMap.put(land, sectionList); + + int[] childIndices = new int[sectionList.size()]; + for(int ct = 0; ct < childIndices.length; ct++){ + childIndices[ct] = ct; + } + Object[] children = sectionList.toArray(); + + Object[] path = {ROOT, land}; + TreePath treePath = new TreePath(path); + TreeModelEvent event = new TreeModelEvent(this, + treePath, + childIndices, + children ); + fireTreeStructureChanged(event); + + return; + } + + /** + * 国一覧を読み込む。 + */ + // TODO static にできない? + public void loadLandList(){ + if(this.isLandListLoaded) return; + + this.landList.clear(); + + List landDefList; + try{ + DocumentBuilder builder = XmlUtils.createDocumentBuilder(); + landDefList = LandDef.buildLandDefList(builder); + }catch(IOException e){ + Jindolf.logger().fatal("failed to load land list", e); + return; + }catch(SAXException e){ + Jindolf.logger().fatal("failed to load land list", e); + return; + }catch(URISyntaxException e){ + Jindolf.logger().fatal("failed to load land list", e); + return; + }catch(ParserConfigurationException e){ + Jindolf.logger().fatal("failed to load land list", e); + return; + } + + for(LandDef landDef : landDefList){ + Land land = new Land(landDef); + this.landList.add(land); + } + + this.isLandListLoaded = true; + + fireLandListChanged(); + + return; + } + + /** + * ツリー内容が更新された事をリスナーに通知する。 + */ + private void fireLandListChanged(){ + int size = this.landList.size(); + int[] childIndices = new int[size]; + for(int ct = 0; ct < size; ct++){ + int index = ct; + childIndices[ct] = index; + } + + Object[] children = this.landList.toArray(); + + TreePath treePath = new TreePath(ROOT); + TreeModelEvent event = new TreeModelEvent(this, + treePath, + childIndices, + children ); + fireTreeStructureChanged(event); + + return; + } + + /** + * ツリーの並び順を設定する。 + * 場合によってはTreeModelEventが発生する。 + * @param ascending trueなら昇順 + */ + public void setAscending(boolean ascending){ + if(this.ascending == ascending) return; + + this.ascending = ascending; + fireLandListChanged(); + + return; + } + + /** + * {@inheritDoc} + * @param l {@inheritDoc} + */ + @Override + public void addTreeModelListener(TreeModelListener l){ + this.listeners.add(TreeModelListener.class, l); + return; + } + + /** + * {@inheritDoc} + * @param l {@inheritDoc} + */ + @Override + public void removeTreeModelListener(TreeModelListener l){ + this.listeners.remove(TreeModelListener.class, l); + return; + } + + /** + * 登録中のリスナーのリストを得る。 + * @return リスナーのリスト + */ + private TreeModelListener[] getTreeModelListeners(){ + return this.listeners.getListeners(TreeModelListener.class); + } + + /** + * 全リスナーにイベントを送出する。 + * @param event ツリーイベント + */ + protected void fireTreeStructureChanged(TreeModelEvent event){ + for(TreeModelListener listener : getTreeModelListeners()){ + listener.treeStructureChanged(event); + } + return; + } + + /** + * 国リストを得る。 + * @return 国のリスト + */ + public List getLandList(){ + return this.unmodList; + } + + /** + * {@inheritDoc} + * @param parent {@inheritDoc} + * @param index {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object getChild(Object parent, int index){ + if(index < 0) return null; + if(index >= getChildCount(parent)) return null; + + if(parent == ROOT){ + List list = getLandList(); + int landIndex = index; + if( ! this.ascending) landIndex = list.size() - index - 1; + Land land = list.get(landIndex); + return land; + } + if(parent instanceof Land){ + Land land = (Land)parent; + List sectionList = this.sectionMap.get(land); + int sectIndex = index; + if( ! this.ascending) sectIndex = sectionList.size() - index - 1; + VillageSection section = sectionList.get(sectIndex); + return section; + } + if(parent instanceof VillageSection){ + VillageSection section = (VillageSection)parent; + int vilIndex = index; + if( ! this.ascending){ + vilIndex = section.getVillageCount() - index - 1; + } + Village village = section.getVillage(vilIndex); + return village; + } + return null; + } + + /** + * {@inheritDoc} + * @param parent {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getChildCount(Object parent){ + if(parent == ROOT){ + return getLandList().size(); + } + if(parent instanceof Land){ + Land land = (Land)parent; + List sectionList = this.sectionMap.get(land); + if(sectionList == null) return 0; + return sectionList.size(); + } + if(parent instanceof VillageSection){ + VillageSection section = (VillageSection)parent; + return section.getVillageCount(); + } + return 0; + } + + /** + * {@inheritDoc} + * @param parent {@inheritDoc} + * @param child {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getIndexOfChild(Object parent, Object child){ + if(child == null) return -1; + if(parent == ROOT){ + List list = getLandList(); + int index = list.indexOf(child); + if( ! this.ascending) index = list.size() - index - 1; + return index; + } + if(parent instanceof Land){ + Land land = (Land)parent; + List sectionList = this.sectionMap.get(land); + int index = sectionList.indexOf(child); + if( ! this.ascending) index = sectionList.size() - index - 1; + return index; + } + if(parent instanceof VillageSection){ + VillageSection section = (VillageSection)parent; + int index = section.getIndexOfVillage(child); + if( ! this.ascending){ + index = section.getVillageCount() - index - 1; + } + return index; + } + return -1; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object getRoot(){ + return ROOT; + } + + /** + * {@inheritDoc} + * @param node {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean isLeaf(Object node){ + if(node == ROOT) return false; + if(node instanceof Land) return false; + if(node instanceof VillageSection) return false; + if(node instanceof Village) return true; + return true; + } + + /** + * {@inheritDoc} + * ※ たぶん使わないので必ず失敗させている。 + * @param path {@inheritDoc} + * @param newValue {@inheritDoc} + */ + @Override + public void valueForPathChanged(TreePath path, Object newValue){ + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * 村IDで範囲指定した、村のセクション集合。国-村間の中間ツリー。 + * @see javax.swing.tree.TreeModel + */ + private static final class VillageSection{ + + private final int startID; + private final int endID; + private final String prefix; + + private final List villageList = new LinkedList(); + + + /** + * セクション集合を生成する。 + * @param land 国 + * @param startID 開始村ID + * @param endID 終了村ID + * @throws java.lang.IndexOutOfBoundsException IDの範囲指定が変 + */ + private VillageSection(Land land, int startID, int endID) + throws IndexOutOfBoundsException{ + super(); + + if(startID < 0 || startID > endID){ + throw new IndexOutOfBoundsException(); + } + + this.startID = startID; + this.endID = endID; + this.prefix = land.getLandDef().getLandPrefix(); + + for(Village village : land.getVillageList()){ + int id = village.getVillageIDNum(); + if(startID <= id && id <= endID){ + this.villageList.add(village); + } + } + + return; + } + + + /** + * 与えられた国の全ての村を、指定されたinterval間隔でセクション化する。 + * @param land 国 + * @param interval セクションの間隔 + * @return セクションのリスト + * @throws java.lang.IllegalArgumentException intervalが正でない + */ + private static List getSectionList(Land land, + int interval ) + throws IllegalArgumentException{ + if(interval <= 0){ + throw new IllegalArgumentException(); + } + + List villageList = land.getVillageList(); + Village village1st = villageList.get(0); + Village villageLast = villageList.get(villageList.size() - 1); + + int startID = village1st.getVillageIDNum(); + int endID = villageLast.getVillageIDNum(); + + List result = new LinkedList(); + + int fixedStart = startID / interval * interval; + for(int ct = fixedStart; ct <= endID; ct += interval){ + VillageSection section = + new VillageSection(land, ct, ct + interval - 1); + result.add(section); + } + + return Collections.unmodifiableList(result); + } + + /** + * セクションに含まれる村の総数を返す。 + * @return 村の総数 + */ + private int getVillageCount(){ + return this.villageList.size(); + } + + /** + * セクションに含まれるindex番目の村を返す。 + * @param index インデックス + * @return index番目の村 + */ + private Village getVillage(int index){ + return this.villageList.get(index); + } + + /** + * セクションにおける、指定された子(村)のインデックス位置を返す。 + * @param child 子 + * @return インデックス位置 + */ + private int getIndexOfVillage(Object child){ + return this.villageList.indexOf(child); + } + + /** + * セクションの文字列表記。 + * JTree描画に反映される。 + * @return 文字列表記 + */ + @Override + public String toString(){ + StringBuilder result = new StringBuilder(); + result.append(this.prefix).append(this.startID); + result.append(" ~ "); + result.append(this.prefix).append(this.endID); + return result.toString(); + } + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/LandsTree.java b/src/main/java/jp/sourceforge/jindolf/LandsTree.java index e613768..3e3cb7e 100644 --- a/src/main/java/jp/sourceforge/jindolf/LandsTree.java +++ b/src/main/java/jp/sourceforge/jindolf/LandsTree.java @@ -1,248 +1,248 @@ -/* - * Lands tree container - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.EventQueue; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.net.URL; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JToolBar; -import javax.swing.JTree; -import javax.swing.border.Border; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; -import javax.swing.tree.TreeSelectionModel; - -/** - * 国一覧Tree周辺コンポーネント群。 - */ -@SuppressWarnings("serial") -public class LandsTree - extends JPanel - implements ActionListener, TreeSelectionListener{ - - private static final String TIP_ASCEND = "押すと降順に"; - private static final String TIP_DESCEND = "押すと昇順に"; - private static final String TIP_ORDER = "選択中の国の村一覧を読み込み直す"; - private static final ImageIcon ICON_ASCEND; - private static final ImageIcon ICON_DESCEND; - - static{ - URL url; - url = Jindolf.getResource("resources/image/ascend.png"); - ICON_ASCEND = new ImageIcon(url); - url = Jindolf.getResource("resources/image/descend.png"); - ICON_DESCEND = new ImageIcon(url); - } - - private final JButton orderButton = new JButton(); - private final JButton reloadButton = new JButton(); - private final JTree treeView = new JTree(); - - private boolean ascending = false; - - /** - * コンストラクタ。 - */ - public LandsTree(){ - super(); - - design(); - - this.orderButton.setIcon(ICON_DESCEND); - this.orderButton.setToolTipText(TIP_DESCEND); - this.orderButton.setMargin(new Insets(1, 1, 1, 1)); - this.orderButton.setActionCommand(ActionManager.CMD_SWITCHORDER); - this.orderButton.addActionListener(this); - - URL url; - url = Jindolf.getResource("resources/image/reload.png"); - this.reloadButton.setIcon(new ImageIcon(url)); - this.reloadButton.setToolTipText(TIP_ORDER); - this.reloadButton.setMargin(new Insets(1, 1, 1, 1)); - this.reloadButton.setActionCommand(ActionManager.CMD_VILLAGELIST); - - TreeSelectionModel selModel = this.treeView.getSelectionModel(); - selModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - this.treeView.addTreeSelectionListener(this); - - return; - } - - /** - * GUIパーツのデザインを行う。 - */ - private void design(){ - BorderLayout layout = new BorderLayout(); - setLayout(layout); - - JToolBar toolBar = new JToolBar(); - toolBar.add(this.orderButton); - toolBar.add(this.reloadButton); - - JComponent landSelector = createLandSelector(); - - add(toolBar, BorderLayout.NORTH); - add(landSelector, BorderLayout.CENTER); - - return; - } - - /** - * 国村選択ツリーコンポーネントを生成する。 - * @return 国村選択ツリーコンポーネント - */ - private JComponent createLandSelector(){ - this.treeView.setRootVisible(false); - this.treeView.setCellRenderer(new VillageIconRenderer()); - - Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - this.treeView.setBorder(border); - - JScrollPane landSelector = new JScrollPane(this.treeView); - - return landSelector; - } - - /** - * リロードボタンを返す。 - * @return リロードボタン - */ - public JButton getReloadVillageListButton(){ - return this.reloadButton; - } - - /** - * 国村選択ツリービューを返す。 - * @return 国村選択ツリービュー - */ - public JTree getTreeView(){ - return this.treeView; - } - - /** - * 指定した国を展開する。 - * @param land 国 - */ - public void expandLand(Land land){ - TreeModel model = this.treeView.getModel(); - Object root = model.getRoot(); - Object[] path = {root, land}; - TreePath treePath = new TreePath(path); - this.treeView.expandPath(treePath); - return; - } - - /** - * 管理下のLandsModelを返す。 - * @return LandsModel - */ - private LandsModel getLandsModel(){ - TreeModel model = this.treeView.getModel(); - if(model instanceof LandsModel){ - return (LandsModel) model; - } - return null; - } - - /** - * Tree表示順を反転させる。 - * @return 反転後が昇順ならtrue - */ - private boolean toggleTreeOrder(){ - this.ascending = ! this.ascending; - - if(this.ascending){ - this.orderButton.setToolTipText(TIP_ASCEND); - this.orderButton.setIcon(ICON_ASCEND); - }else{ - this.orderButton.setToolTipText(TIP_DESCEND); - this.orderButton.setIcon(ICON_DESCEND); - } - - final TreePath lastPath = this.treeView.getSelectionPath(); - - LandsModel model = getLandsModel(); - if(model != null){ - model.setAscending(this.ascending); - } - - EventQueue.invokeLater(new Runnable(){ - public void run(){ - if(lastPath != null){ - LandsTree.this.treeView.setSelectionPath(lastPath); - LandsTree.this.treeView.scrollPathToVisible(lastPath); - } - return; - } - }); - - return this.ascending; - } - - /** - * {@inheritDoc} - * ボタン押下処理。 - * @param event ボタン押下イベント {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - String cmd = event.getActionCommand(); - if(ActionManager.CMD_SWITCHORDER.equals(cmd)){ - toggleTreeOrder(); - } - return; - } - - /** - * {@inheritDoc} - * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。 - * @param event イベント {@inheritDoc} - */ - @Override - public void valueChanged(TreeSelectionEvent event){ - TreePath path = event.getNewLeadSelectionPath(); - if(path == null){ - this.reloadButton.setEnabled(false); - return; - } - - Object selObj = path.getLastPathComponent(); - - if( selObj instanceof Land ){ - this.reloadButton.setEnabled(true); - }else{ - this.reloadButton.setEnabled(false); - } - - return; - } - - /** - * {@inheritDoc} - */ - @Override - public void updateUI(){ - super.updateUI(); - if(this.treeView != null){ - this.treeView.setCellRenderer(new VillageIconRenderer()); - } - return; - } - -} +/* + * Lands tree container + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.EventQueue; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URL; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToolBar; +import javax.swing.JTree; +import javax.swing.border.Border; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +/** + * 国一覧Tree周辺コンポーネント群。 + */ +@SuppressWarnings("serial") +public class LandsTree + extends JPanel + implements ActionListener, TreeSelectionListener{ + + private static final String TIP_ASCEND = "押すと降順に"; + private static final String TIP_DESCEND = "押すと昇順に"; + private static final String TIP_ORDER = "選択中の国の村一覧を読み込み直す"; + private static final ImageIcon ICON_ASCEND; + private static final ImageIcon ICON_DESCEND; + + static{ + URL url; + url = Jindolf.getResource("resources/image/ascend.png"); + ICON_ASCEND = new ImageIcon(url); + url = Jindolf.getResource("resources/image/descend.png"); + ICON_DESCEND = new ImageIcon(url); + } + + private final JButton orderButton = new JButton(); + private final JButton reloadButton = new JButton(); + private final JTree treeView = new JTree(); + + private boolean ascending = false; + + /** + * コンストラクタ。 + */ + public LandsTree(){ + super(); + + design(); + + this.orderButton.setIcon(ICON_DESCEND); + this.orderButton.setToolTipText(TIP_DESCEND); + this.orderButton.setMargin(new Insets(1, 1, 1, 1)); + this.orderButton.setActionCommand(ActionManager.CMD_SWITCHORDER); + this.orderButton.addActionListener(this); + + URL url; + url = Jindolf.getResource("resources/image/reload.png"); + this.reloadButton.setIcon(new ImageIcon(url)); + this.reloadButton.setToolTipText(TIP_ORDER); + this.reloadButton.setMargin(new Insets(1, 1, 1, 1)); + this.reloadButton.setActionCommand(ActionManager.CMD_VILLAGELIST); + + TreeSelectionModel selModel = this.treeView.getSelectionModel(); + selModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + this.treeView.addTreeSelectionListener(this); + + return; + } + + /** + * GUIパーツのデザインを行う。 + */ + private void design(){ + BorderLayout layout = new BorderLayout(); + setLayout(layout); + + JToolBar toolBar = new JToolBar(); + toolBar.add(this.orderButton); + toolBar.add(this.reloadButton); + + JComponent landSelector = createLandSelector(); + + add(toolBar, BorderLayout.NORTH); + add(landSelector, BorderLayout.CENTER); + + return; + } + + /** + * 国村選択ツリーコンポーネントを生成する。 + * @return 国村選択ツリーコンポーネント + */ + private JComponent createLandSelector(){ + this.treeView.setRootVisible(false); + this.treeView.setCellRenderer(new VillageIconRenderer()); + + Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); + this.treeView.setBorder(border); + + JScrollPane landSelector = new JScrollPane(this.treeView); + + return landSelector; + } + + /** + * リロードボタンを返す。 + * @return リロードボタン + */ + public JButton getReloadVillageListButton(){ + return this.reloadButton; + } + + /** + * 国村選択ツリービューを返す。 + * @return 国村選択ツリービュー + */ + public JTree getTreeView(){ + return this.treeView; + } + + /** + * 指定した国を展開する。 + * @param land 国 + */ + public void expandLand(Land land){ + TreeModel model = this.treeView.getModel(); + Object root = model.getRoot(); + Object[] path = {root, land}; + TreePath treePath = new TreePath(path); + this.treeView.expandPath(treePath); + return; + } + + /** + * 管理下のLandsModelを返す。 + * @return LandsModel + */ + private LandsModel getLandsModel(){ + TreeModel model = this.treeView.getModel(); + if(model instanceof LandsModel){ + return (LandsModel) model; + } + return null; + } + + /** + * Tree表示順を反転させる。 + * @return 反転後が昇順ならtrue + */ + private boolean toggleTreeOrder(){ + this.ascending = ! this.ascending; + + if(this.ascending){ + this.orderButton.setToolTipText(TIP_ASCEND); + this.orderButton.setIcon(ICON_ASCEND); + }else{ + this.orderButton.setToolTipText(TIP_DESCEND); + this.orderButton.setIcon(ICON_DESCEND); + } + + final TreePath lastPath = this.treeView.getSelectionPath(); + + LandsModel model = getLandsModel(); + if(model != null){ + model.setAscending(this.ascending); + } + + EventQueue.invokeLater(new Runnable(){ + public void run(){ + if(lastPath != null){ + LandsTree.this.treeView.setSelectionPath(lastPath); + LandsTree.this.treeView.scrollPathToVisible(lastPath); + } + return; + } + }); + + return this.ascending; + } + + /** + * {@inheritDoc} + * ボタン押下処理。 + * @param event ボタン押下イベント {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + String cmd = event.getActionCommand(); + if(ActionManager.CMD_SWITCHORDER.equals(cmd)){ + toggleTreeOrder(); + } + return; + } + + /** + * {@inheritDoc} + * ツリーリストで何らかの要素(国、村)がクリックされたときの処理。 + * @param event イベント {@inheritDoc} + */ + @Override + public void valueChanged(TreeSelectionEvent event){ + TreePath path = event.getNewLeadSelectionPath(); + if(path == null){ + this.reloadButton.setEnabled(false); + return; + } + + Object selObj = path.getLastPathComponent(); + + if( selObj instanceof Land ){ + this.reloadButton.setEnabled(true); + }else{ + this.reloadButton.setEnabled(false); + } + + return; + } + + /** + * {@inheritDoc} + */ + @Override + public void updateUI(){ + super.updateUI(); + if(this.treeView != null){ + this.treeView.setCellRenderer(new VillageIconRenderer()); + } + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/LogFrame.java b/src/main/java/jp/sourceforge/jindolf/LogFrame.java index c50d311..312e6f8 100644 --- a/src/main/java/jp/sourceforge/jindolf/LogFrame.java +++ b/src/main/java/jp/sourceforge/jindolf/LogFrame.java @@ -1,357 +1,357 @@ -/* - * Log frame - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.util.logging.Formatter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.SimpleFormatter; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JPopupMenu; -import javax.swing.JScrollBar; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTextArea; -import javax.swing.border.Border; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.PlainDocument; - -/** - * ログ表示パネル。 - */ -@SuppressWarnings("serial") -public class LogFrame extends JDialog - implements WindowListener, ActionListener{ - - private static final String FRAMETITLE = "ログ表示 - " + Jindolf.TITLE; - private static final int DOCLIMIT = 100 * 1000; // 単位は文字 - private static final float CHOPRATIO = 0.9f; - private static final int CHOPPEDLEN = (int)(DOCLIMIT * CHOPRATIO); - private static final Document DOC_EMPTY = new PlainDocument(); - - static{ - assert DOCLIMIT > CHOPPEDLEN; - } - - /** - * ログハンドラ。 - */ - private class SwingLogger extends Handler{ - - /** - * ログハンドラの生成。 - */ - public SwingLogger(){ - super(); - Formatter formatter = new SimpleFormatter(); - setFormatter(formatter); - return; - } - - /** - * {@inheritDoc} - * @param record {@inheritDoc} - */ - @Override - public void publish(LogRecord record){ - if( ! isLoggable(record) ){ - return; - } - Formatter formatter = getFormatter(); - String message = formatter.format(record); - try{ - LogFrame.this.document - .insertString(LogFrame.this.document.getLength(), - message, - null ); - }catch(BadLocationException e){ - // IGNORE - } - - int docLength = LogFrame.this.document.getLength(); - if(docLength > DOCLIMIT){ - int offset = docLength - CHOPPEDLEN; - try{ - LogFrame.this.document.remove(0, offset); - }catch(BadLocationException e){ - // IGNORE - } - } - - showLastPos(); - - return; - } - - /** - * {@inheritDoc} - * (何もしない)。 - */ - @Override - public void flush(){ - return; - } - - /** - * {@inheritDoc} - */ - @Override - public void close(){ - setLevel(Level.OFF); - flush(); - return; - } - }; - - private final JTextArea textarea; - private final Document document = new PlainDocument(); - private final JScrollPane scrollPane; - private final JScrollBar vertical; - private final Handler handler; - - private final JButton clearButton = new JButton("クリア"); - private final JButton closeButton = new JButton("閉じる"); - - /** - * ログ表示パネルの生成。 - * @param owner フレームオーナー - */ - public LogFrame(Frame owner){ - super(owner, FRAMETITLE, false); - - if(Jindolf.hasLoggingPermission()){ - this.handler = new SwingLogger(); - }else{ - this.handler = null; - } - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - this.textarea = new JTextArea(); - this.textarea.setEditable(false); - this.textarea.setLineWrap(true); - this.textarea.setDocument(DOC_EMPTY); - Border border = BorderFactory.createEmptyBorder(3, 3, 3, 3); - this.textarea.setBorder(border); - Monodizer.monodize(this.textarea); - JPopupMenu popup = new TextPopup(); - this.textarea.setComponentPopupMenu(popup); - - this.scrollPane = new JScrollPane(this.textarea); - this.vertical = this.scrollPane.getVerticalScrollBar(); - - design(); - - this.clearButton.addActionListener(this); - this.closeButton.addActionListener(this); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(this); - - return; - } - - /** - * デザインを行う。 - */ - private void design(){ - Container content = getContentPane(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - content.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.gridwidth = GridBagConstraints.REMAINDER; - - content.add(this.scrollPane, constraints); - - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(5, 5, 5, 5); - - content.add(new JSeparator(), constraints); - - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.NONE; - constraints.gridwidth = 1; - content.add(this.clearButton, constraints); - - constraints.weightx = 0.0; - constraints.anchor = GridBagConstraints.EAST; - content.add(this.closeButton, constraints); - - return; - } - - /** - * ロギングハンドラを取得する。 - * @return ロギングハンドラ - */ - public Handler getHandler(){ - return this.handler; - } - - /** - * 垂直スクロールバーを末端に設定する。 - */ - private void showLastPos(){ - if( ! isVisible() ) return; - if(this.textarea.getDocument() != this.document) return; - - EventQueue.invokeLater(new Runnable(){ - public void run(){ - LogFrame.this.vertical.setValue(Integer.MAX_VALUE); - return; - } - }); - - return; - } - - /** - * ウィンドウクローズ処理。 - */ - private void close(){ - setVisible(false); - return; - } - - /** - * ログクリア処理。 - */ - private void clear(){ - try{ - this.document.remove(0, this.document.getLength()); - }catch(BadLocationException e){ - assert false; - } - return; - } - - /** - * {@inheritDoc} - * ウィンドウの表示・非表示を設定する。 - * @param visible trueなら表示 {@inheritDoc} - */ - @Override - public void setVisible(boolean visible){ - super.setVisible(visible); - - if(visible){ - this.textarea.setDocument(this.document); - showLastPos(); - }else{ - this.textarea.setDocument(DOC_EMPTY); - } - - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowActivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeactivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowIconified(WindowEvent event){ - this.textarea.setDocument(DOC_EMPTY); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeiconified(WindowEvent event){ - this.textarea.setDocument(this.document); - showLastPos(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowOpened(WindowEvent event){ - this.textarea.setDocument(this.document); - showLastPos(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowClosed(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowClosing(WindowEvent event){ - close(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if(source == this.clearButton){ - clear(); - }else if(source == this.closeButton){ - close(); - } - - return; - } - -} +/* + * Log frame + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPopupMenu; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextArea; +import javax.swing.border.Border; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.PlainDocument; + +/** + * ログ表示パネル。 + */ +@SuppressWarnings("serial") +public class LogFrame extends JDialog + implements WindowListener, ActionListener{ + + private static final String FRAMETITLE = "ログ表示 - " + Jindolf.TITLE; + private static final int DOCLIMIT = 100 * 1000; // 単位は文字 + private static final float CHOPRATIO = 0.9f; + private static final int CHOPPEDLEN = (int)(DOCLIMIT * CHOPRATIO); + private static final Document DOC_EMPTY = new PlainDocument(); + + static{ + assert DOCLIMIT > CHOPPEDLEN; + } + + /** + * ログハンドラ。 + */ + private class SwingLogger extends Handler{ + + /** + * ログハンドラの生成。 + */ + public SwingLogger(){ + super(); + Formatter formatter = new SimpleFormatter(); + setFormatter(formatter); + return; + } + + /** + * {@inheritDoc} + * @param record {@inheritDoc} + */ + @Override + public void publish(LogRecord record){ + if( ! isLoggable(record) ){ + return; + } + Formatter formatter = getFormatter(); + String message = formatter.format(record); + try{ + LogFrame.this.document + .insertString(LogFrame.this.document.getLength(), + message, + null ); + }catch(BadLocationException e){ + // IGNORE + } + + int docLength = LogFrame.this.document.getLength(); + if(docLength > DOCLIMIT){ + int offset = docLength - CHOPPEDLEN; + try{ + LogFrame.this.document.remove(0, offset); + }catch(BadLocationException e){ + // IGNORE + } + } + + showLastPos(); + + return; + } + + /** + * {@inheritDoc} + * (何もしない)。 + */ + @Override + public void flush(){ + return; + } + + /** + * {@inheritDoc} + */ + @Override + public void close(){ + setLevel(Level.OFF); + flush(); + return; + } + }; + + private final JTextArea textarea; + private final Document document = new PlainDocument(); + private final JScrollPane scrollPane; + private final JScrollBar vertical; + private final Handler handler; + + private final JButton clearButton = new JButton("クリア"); + private final JButton closeButton = new JButton("閉じる"); + + /** + * ログ表示パネルの生成。 + * @param owner フレームオーナー + */ + public LogFrame(Frame owner){ + super(owner, FRAMETITLE, false); + + if(Jindolf.hasLoggingPermission()){ + this.handler = new SwingLogger(); + }else{ + this.handler = null; + } + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + this.textarea = new JTextArea(); + this.textarea.setEditable(false); + this.textarea.setLineWrap(true); + this.textarea.setDocument(DOC_EMPTY); + Border border = BorderFactory.createEmptyBorder(3, 3, 3, 3); + this.textarea.setBorder(border); + Monodizer.monodize(this.textarea); + JPopupMenu popup = new TextPopup(); + this.textarea.setComponentPopupMenu(popup); + + this.scrollPane = new JScrollPane(this.textarea); + this.vertical = this.scrollPane.getVerticalScrollBar(); + + design(); + + this.clearButton.addActionListener(this); + this.closeButton.addActionListener(this); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(this); + + return; + } + + /** + * デザインを行う。 + */ + private void design(){ + Container content = getContentPane(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + content.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.gridwidth = GridBagConstraints.REMAINDER; + + content.add(this.scrollPane, constraints); + + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(5, 5, 5, 5); + + content.add(new JSeparator(), constraints); + + constraints.anchor = GridBagConstraints.WEST; + constraints.fill = GridBagConstraints.NONE; + constraints.gridwidth = 1; + content.add(this.clearButton, constraints); + + constraints.weightx = 0.0; + constraints.anchor = GridBagConstraints.EAST; + content.add(this.closeButton, constraints); + + return; + } + + /** + * ロギングハンドラを取得する。 + * @return ロギングハンドラ + */ + public Handler getHandler(){ + return this.handler; + } + + /** + * 垂直スクロールバーを末端に設定する。 + */ + private void showLastPos(){ + if( ! isVisible() ) return; + if(this.textarea.getDocument() != this.document) return; + + EventQueue.invokeLater(new Runnable(){ + public void run(){ + LogFrame.this.vertical.setValue(Integer.MAX_VALUE); + return; + } + }); + + return; + } + + /** + * ウィンドウクローズ処理。 + */ + private void close(){ + setVisible(false); + return; + } + + /** + * ログクリア処理。 + */ + private void clear(){ + try{ + this.document.remove(0, this.document.getLength()); + }catch(BadLocationException e){ + assert false; + } + return; + } + + /** + * {@inheritDoc} + * ウィンドウの表示・非表示を設定する。 + * @param visible trueなら表示 {@inheritDoc} + */ + @Override + public void setVisible(boolean visible){ + super.setVisible(visible); + + if(visible){ + this.textarea.setDocument(this.document); + showLastPos(); + }else{ + this.textarea.setDocument(DOC_EMPTY); + } + + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowActivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeactivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowIconified(WindowEvent event){ + this.textarea.setDocument(DOC_EMPTY); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeiconified(WindowEvent event){ + this.textarea.setDocument(this.document); + showLastPos(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowOpened(WindowEvent event){ + this.textarea.setDocument(this.document); + showLastPos(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowClosed(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowClosing(WindowEvent event){ + close(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if(source == this.clearButton){ + clear(); + }else if(source == this.closeButton){ + close(); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/LogWrapper.java b/src/main/java/jp/sourceforge/jindolf/LogWrapper.java index ef8d11d..51753f0 100644 --- a/src/main/java/jp/sourceforge/jindolf/LogWrapper.java +++ b/src/main/java/jp/sourceforge/jindolf/LogWrapper.java @@ -1,152 +1,152 @@ -/* - * log wrapper - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -/** - * 各種ログAPIへの共通ラッパー。 - * 現時点では java.util.logging のみサポート。 - */ -public class LogWrapper{ - - private final Logger jre14Logger; - - /** - * コンストラクタ。 - * @param logger ラップ対象のjava.util.loggingロガー - */ - public LogWrapper(Logger logger){ - super(); - if(logger == null) throw new NullPointerException(); - this.jre14Logger = logger; - return; - } - - /** - * ラップ対象のjava.util.loggingロガーを取得する。 - * @return ラップ対象のjava.util.loggingロガー - */ - public Logger getJre14Logger(){ - return this.jre14Logger; - } - - /** - * ログレコードにスタックトレース情報を埋め込む。 - * @param record ログレコード - */ - private void fillStackInfo(LogRecord record){ - Thread selfThread = Thread.currentThread(); - StackTraceElement[] stacks = selfThread.getStackTrace(); - - String thisName = this.getClass().getName(); - - boolean foundMySelf = false; - for(StackTraceElement frame : stacks){ - String frameClassName = frame.getClassName(); - - if( ! foundMySelf && frameClassName.equals(thisName) ){ - foundMySelf = true; - continue; - } - - if( foundMySelf && ! frameClassName.equals(thisName) ){ - record.setSourceClassName(frameClassName); - record.setSourceMethodName(frame.getMethodName()); - break; - } - } - - return; - } - - /** - * java.util.loggingロガーへログ出力。 - * @param level ログレベル - * @param msg メッセージ - */ - private void logJre14(Level level, CharSequence msg){ - logJre14(level, msg, null); - return; - } - - /** - * java.util.loggingロガーへログ出力。 - * @param level ログレベル - * @param msg メッセージ - * @param thrown 例外 - */ - private void logJre14(Level level, CharSequence msg, Throwable thrown){ - String message; - if(msg == null) message = null; - else message = msg.toString(); - - LogRecord record = new LogRecord(level, message); - - if(thrown != null){ - record.setThrown(thrown); - } - - fillStackInfo(record); - - this.jre14Logger.log(record); - - return; - } - - /** - * 単純な情報を出力する。 - * @param msg メッセージ - */ - public void info(CharSequence msg){ - logJre14(Level.INFO, msg); - return; - } - - /** - * 警告を出力する。 - * @param msg メッセージ - */ - public void warn(CharSequence msg){ - warn(msg, null); - return; - } - - /** - * 警告を出力する。 - * @param msg メッセージ - * @param thrown 例外 - */ - public void warn(CharSequence msg, Throwable thrown){ - logJre14(Level.WARNING, msg, thrown); - return; - } - - /** - * 致命的な障害情報を出力する。 - * @param msg メッセージ - */ - public void fatal(CharSequence msg){ - fatal(msg, null); - return; - } - - /** - * 致命的な障害情報を出力する。 - * @param msg メッセージ - * @param thrown 例外 - */ - public void fatal(CharSequence msg, Throwable thrown){ - logJre14(Level.SEVERE, msg, thrown); - return; - } - - // TODO Apache log4j サポート -} +/* + * log wrapper + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * 各種ログAPIへの共通ラッパー。 + * 現時点では java.util.logging のみサポート。 + */ +public class LogWrapper{ + + private final Logger jre14Logger; + + /** + * コンストラクタ。 + * @param logger ラップ対象のjava.util.loggingロガー + */ + public LogWrapper(Logger logger){ + super(); + if(logger == null) throw new NullPointerException(); + this.jre14Logger = logger; + return; + } + + /** + * ラップ対象のjava.util.loggingロガーを取得する。 + * @return ラップ対象のjava.util.loggingロガー + */ + public Logger getJre14Logger(){ + return this.jre14Logger; + } + + /** + * ログレコードにスタックトレース情報を埋め込む。 + * @param record ログレコード + */ + private void fillStackInfo(LogRecord record){ + Thread selfThread = Thread.currentThread(); + StackTraceElement[] stacks = selfThread.getStackTrace(); + + String thisName = this.getClass().getName(); + + boolean foundMySelf = false; + for(StackTraceElement frame : stacks){ + String frameClassName = frame.getClassName(); + + if( ! foundMySelf && frameClassName.equals(thisName) ){ + foundMySelf = true; + continue; + } + + if( foundMySelf && ! frameClassName.equals(thisName) ){ + record.setSourceClassName(frameClassName); + record.setSourceMethodName(frame.getMethodName()); + break; + } + } + + return; + } + + /** + * java.util.loggingロガーへログ出力。 + * @param level ログレベル + * @param msg メッセージ + */ + private void logJre14(Level level, CharSequence msg){ + logJre14(level, msg, null); + return; + } + + /** + * java.util.loggingロガーへログ出力。 + * @param level ログレベル + * @param msg メッセージ + * @param thrown 例外 + */ + private void logJre14(Level level, CharSequence msg, Throwable thrown){ + String message; + if(msg == null) message = null; + else message = msg.toString(); + + LogRecord record = new LogRecord(level, message); + + if(thrown != null){ + record.setThrown(thrown); + } + + fillStackInfo(record); + + this.jre14Logger.log(record); + + return; + } + + /** + * 単純な情報を出力する。 + * @param msg メッセージ + */ + public void info(CharSequence msg){ + logJre14(Level.INFO, msg); + return; + } + + /** + * 警告を出力する。 + * @param msg メッセージ + */ + public void warn(CharSequence msg){ + warn(msg, null); + return; + } + + /** + * 警告を出力する。 + * @param msg メッセージ + * @param thrown 例外 + */ + public void warn(CharSequence msg, Throwable thrown){ + logJre14(Level.WARNING, msg, thrown); + return; + } + + /** + * 致命的な障害情報を出力する。 + * @param msg メッセージ + */ + public void fatal(CharSequence msg){ + fatal(msg, null); + return; + } + + /** + * 致命的な障害情報を出力する。 + * @param msg メッセージ + * @param thrown 例外 + */ + public void fatal(CharSequence msg, Throwable thrown){ + logJre14(Level.SEVERE, msg, thrown); + return; + } + + // TODO Apache log4j サポート +} diff --git a/src/main/java/jp/sourceforge/jindolf/Monodizer.java b/src/main/java/jp/sourceforge/jindolf/Monodizer.java index 864e241..c1ff069 100644 --- a/src/main/java/jp/sourceforge/jindolf/Monodizer.java +++ b/src/main/java/jp/sourceforge/jindolf/Monodizer.java @@ -1,176 +1,176 @@ -/* - * font monodizer - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.awt.Font; -import java.awt.font.TextAttribute; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Collections; -import java.util.Map; -import javax.swing.ComboBoxEditor; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.text.JTextComponent; - -/** - * Swingコンポーネントのフォント等幅化。 - * L&F変更にも対処。 - */ -public final class Monodizer implements PropertyChangeListener{ - - /** フォント変更時のプロパティ名。 */ - public static final String PROPNAME_FONT = "font"; - /** L&F変更時のプロパティ名。 */ - public static final String PROPNAME_UI = "UI"; - /** Font.MONOSPACED代替品。 */ - public static final String FAMILY_MONO = "Monospaced"; - - private static final Map TEXTATTR_MONO = - Collections.singletonMap(TextAttribute.FAMILY, FAMILY_MONO); - - private static final Monodizer CHANGER = new Monodizer(); - - - /** - * 隠しコンストラクタ。 - */ - private Monodizer(){ - super(); - return; - } - - - /** - * 等幅フォントか否か判定する。 - * @param font フォント - * @return 等幅フォントならtrue - */ - public static boolean isMonospaced(Font font){ - Map attrMap = font.getAttributes(); - - Object attr = attrMap.get(TextAttribute.FAMILY); - if( ! (attr instanceof String) ) return false; - - String family = (String) attr; - if(family.equals(FAMILY_MONO)) return true; - - return false; - } - - /** - * 任意のフォントの等幅化を行う。 - * 等幅以外の属性は可能な限り保持する。 - * @param font 任意のフォント - * @return 等幅フォント - */ - public static Font deriveMonoFont(Font font){ - Font monofont = font.deriveFont(TEXTATTR_MONO); - return monofont; - } - - /** - * 任意のコンポーネントをフォント等幅化する。 - * L&F変更に対処するためのリスナ組み込みも行われる。 - * @param comp コンポーネント - */ - public static void monodize(JComponent comp){ - Font oldFont = comp.getFont(); - Font newFont = deriveMonoFont(oldFont); - comp.setFont(newFont); - - modifyComponent(comp); - - comp.addPropertyChangeListener(PROPNAME_FONT, CHANGER); - comp.addPropertyChangeListener(PROPNAME_UI, CHANGER); - - comp.revalidate(); - - return; - } - - /** - * コンポーネントに微修正を加える。 - * @param comp コンポーネント - */ - private static void modifyComponent(JComponent comp){ - if(comp instanceof JTextComponent){ - JTextComponent textComp = (JTextComponent) comp; - modifyTextComponent(textComp); - }else if(comp instanceof JComboBox){ - JComboBox combo = (JComboBox) comp; - modifyComboBox(combo); - } - - return; - } - - /** - * テキストコンポーネントへの微修正を行う。 - * @param textComp テキストコンポーネント - */ - private static void modifyTextComponent(JTextComponent textComp){ - if( ! textComp.isEditable() ){ - textComp.setCaretPosition(0); - } - return; - } - - /** - * コンボボックスのエディタを等幅化する。 - * @param comboBox コンボボックス - */ - private static void modifyComboBox(JComboBox comboBox){ - ComboBoxEditor editor = comboBox.getEditor(); - if(editor == null) return; - - Component editComp = editor.getEditorComponent(); - if( ! (editComp instanceof JTextComponent) ) return; - JTextComponent textEditor = (JTextComponent) editComp; - - Font oldFont = textEditor.getFont(); - Font newFont = deriveMonoFont(oldFont); - textEditor.setFont(newFont); - - modifyTextComponent(textEditor); - - return; - } - - /** - * フォント変更イベントの受信。 - * @param event フォント変更イベント - */ - public void propertyChange(PropertyChangeEvent event){ - Object source = event.getSource(); - if( ! (source instanceof JComponent) ) return; - JComponent comp = (JComponent) source; - - String propName = event.getPropertyName(); - - Font newFont; - if(PROPNAME_FONT.equals(propName)){ - Object newValue = event.getNewValue(); - if( ! (newValue instanceof Font) ) return; - newFont = (Font) newValue; - }else if(PROPNAME_UI.equals(propName)){ - newFont = comp.getFont(); - }else{ - return; - } - - Font monoFont = deriveMonoFont(newFont); - comp.setFont(monoFont); // 再帰は起きないはず… - - modifyComponent(comp); - - return; - } - -} +/* + * font monodizer + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.awt.Font; +import java.awt.font.TextAttribute; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.Map; +import javax.swing.ComboBoxEditor; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.text.JTextComponent; + +/** + * Swingコンポーネントのフォント等幅化。 + * L&F変更にも対処。 + */ +public final class Monodizer implements PropertyChangeListener{ + + /** フォント変更時のプロパティ名。 */ + public static final String PROPNAME_FONT = "font"; + /** L&F変更時のプロパティ名。 */ + public static final String PROPNAME_UI = "UI"; + /** Font.MONOSPACED代替品。 */ + public static final String FAMILY_MONO = "Monospaced"; + + private static final Map TEXTATTR_MONO = + Collections.singletonMap(TextAttribute.FAMILY, FAMILY_MONO); + + private static final Monodizer CHANGER = new Monodizer(); + + + /** + * 隠しコンストラクタ。 + */ + private Monodizer(){ + super(); + return; + } + + + /** + * 等幅フォントか否か判定する。 + * @param font フォント + * @return 等幅フォントならtrue + */ + public static boolean isMonospaced(Font font){ + Map attrMap = font.getAttributes(); + + Object attr = attrMap.get(TextAttribute.FAMILY); + if( ! (attr instanceof String) ) return false; + + String family = (String) attr; + if(family.equals(FAMILY_MONO)) return true; + + return false; + } + + /** + * 任意のフォントの等幅化を行う。 + * 等幅以外の属性は可能な限り保持する。 + * @param font 任意のフォント + * @return 等幅フォント + */ + public static Font deriveMonoFont(Font font){ + Font monofont = font.deriveFont(TEXTATTR_MONO); + return monofont; + } + + /** + * 任意のコンポーネントをフォント等幅化する。 + * L&F変更に対処するためのリスナ組み込みも行われる。 + * @param comp コンポーネント + */ + public static void monodize(JComponent comp){ + Font oldFont = comp.getFont(); + Font newFont = deriveMonoFont(oldFont); + comp.setFont(newFont); + + modifyComponent(comp); + + comp.addPropertyChangeListener(PROPNAME_FONT, CHANGER); + comp.addPropertyChangeListener(PROPNAME_UI, CHANGER); + + comp.revalidate(); + + return; + } + + /** + * コンポーネントに微修正を加える。 + * @param comp コンポーネント + */ + private static void modifyComponent(JComponent comp){ + if(comp instanceof JTextComponent){ + JTextComponent textComp = (JTextComponent) comp; + modifyTextComponent(textComp); + }else if(comp instanceof JComboBox){ + JComboBox combo = (JComboBox) comp; + modifyComboBox(combo); + } + + return; + } + + /** + * テキストコンポーネントへの微修正を行う。 + * @param textComp テキストコンポーネント + */ + private static void modifyTextComponent(JTextComponent textComp){ + if( ! textComp.isEditable() ){ + textComp.setCaretPosition(0); + } + return; + } + + /** + * コンボボックスのエディタを等幅化する。 + * @param comboBox コンボボックス + */ + private static void modifyComboBox(JComboBox comboBox){ + ComboBoxEditor editor = comboBox.getEditor(); + if(editor == null) return; + + Component editComp = editor.getEditorComponent(); + if( ! (editComp instanceof JTextComponent) ) return; + JTextComponent textEditor = (JTextComponent) editComp; + + Font oldFont = textEditor.getFont(); + Font newFont = deriveMonoFont(oldFont); + textEditor.setFont(newFont); + + modifyTextComponent(textEditor); + + return; + } + + /** + * フォント変更イベントの受信。 + * @param event フォント変更イベント + */ + public void propertyChange(PropertyChangeEvent event){ + Object source = event.getSource(); + if( ! (source instanceof JComponent) ) return; + JComponent comp = (JComponent) source; + + String propName = event.getPropertyName(); + + Font newFont; + if(PROPNAME_FONT.equals(propName)){ + Object newValue = event.getNewValue(); + if( ! (newValue instanceof Font) ) return; + newFont = (Font) newValue; + }else if(PROPNAME_UI.equals(propName)){ + newFont = comp.getFont(); + }else{ + return; + } + + Font monoFont = deriveMonoFont(newFont); + comp.setFont(monoFont); // 再帰は起きないはず… + + modifyComponent(comp); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/OptionInfo.java b/src/main/java/jp/sourceforge/jindolf/OptionInfo.java index ecaa8d6..1d9c6b3 100644 --- a/src/main/java/jp/sourceforge/jindolf/OptionInfo.java +++ b/src/main/java/jp/sourceforge/jindolf/OptionInfo.java @@ -1,295 +1,295 @@ -/* - * option argument information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.Collections; -import java.util.EnumMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * コマンドラインオプション情報。 - * public static void main()の引数から展開される。 - */ -public class OptionInfo{ - - private static final Pattern PATTERN_GEOMETRY = - Pattern.compile( - "([1-9][0-9]*)x([1-9][0-9]*)" - +"(?:(\\+|\\-)([1-9][0-9]*)(\\+|\\-)([1-9][0-9]*))?" - ); - - - private Integer frameWidth = null; - private Integer frameHeight = null; - private Integer frameXpos = null; - private Integer frameYpos = null; - - private final List invokeArgs = new LinkedList(); - private final List optionList = new LinkedList(); - private final Map boolOptionMap = - new EnumMap(CmdOption.class); - private final Map stringOptionMap = - new EnumMap(CmdOption.class); - - - /** - * コンストラクタ。 - */ - protected OptionInfo(){ - super(); - return; - } - - - /** - * オプション文字列を解析する。 - * @param args main()に渡されるオプション文字列 - * @return 解析済みのオプション情報。 - * @throws IllegalArgumentException 構文エラー - */ - public static OptionInfo parseOptions(String[] args) - throws IllegalArgumentException{ - OptionInfo result = new OptionInfo(); - - result.invokeArgs.clear(); - for(String arg : args){ - if(arg == null) continue; - if(arg.length() <= 0) continue; - result.invokeArgs.add(arg); - } - Iterator iterator = result.invokeArgs.iterator(); - - while(iterator.hasNext()){ - String arg = iterator.next(); - - CmdOption option = CmdOption.parseCmdOption(arg); - if(option == null){ - throw new IllegalArgumentException( - "未定義の起動オプション[" - + arg - + "]が指定されました。"); - } - result.optionList.add(option); - - if(CmdOption.isIndepOption(option)){ - continue; - }else if(CmdOption.isBooleanOption(option)){ - Boolean bool = parseBooleanSwitch(arg, iterator); - result.boolOptionMap.put(option, bool); - continue; - } - - switch(option){ - case OPT_INITFONT: - case OPT_CONFDIR: - checkNextArg(arg, iterator); - result.stringOptionMap.put(option, iterator.next()); - break; - case OPT_GEOMETRY: - checkNextArg(arg, iterator); - String geometry = iterator.next(); - Matcher matcher = PATTERN_GEOMETRY.matcher(geometry); - if( ! matcher.matches() ){ - throw new IllegalArgumentException( - "起動オプション[" - +arg - +"]の引数形式[" - +geometry - +"]が不正です。" ); - } - String width = matcher.group(1); - String height = matcher.group(2); - String xSign = matcher.group(3); - String xPos = matcher.group(4); - String ySign = matcher.group(5); - String yPos = matcher.group(6); - try{ - result.frameWidth = Integer.parseInt(width); - result.frameHeight = Integer.parseInt(height); - if(xPos != null && xPos.length() > 0){ - result.frameXpos = Integer.parseInt(xPos); - if(xSign.equals("-")){ - result.frameXpos = -result.frameXpos; - } - } - if(yPos != null && yPos.length() > 0){ - result.frameYpos = Integer.parseInt(yPos); - if(ySign.equals("-")){ - result.frameYpos = -result.frameYpos; - } - } - }catch(NumberFormatException e){ - assert false; - throw new IllegalArgumentException(e); - } - - break; - default: - assert false; - break; - } - } - - return result; - } - - /** - * 真偽二値をとるオプション解析の下請け。 - * @param option オプション名 - * @param iterator 引数並び - * @return 真偽 - * @throws IllegalArgumentException 構文エラー - */ - private static Boolean parseBooleanSwitch( - String option, Iterator iterator ) - throws IllegalArgumentException{ - Boolean result; - checkNextArg(option, iterator); - String onoff = iterator.next(); - if( onoff.compareToIgnoreCase("on" ) == 0 - || onoff.compareToIgnoreCase("yes" ) == 0 - || onoff.compareToIgnoreCase("true") == 0){ - result = Boolean.TRUE; - }else if( onoff.compareToIgnoreCase("off" ) == 0 - || onoff.compareToIgnoreCase("no" ) == 0 - || onoff.compareToIgnoreCase("false") == 0){ - result = Boolean.FALSE; - }else{ - throw new IllegalArgumentException( - "起動オプション[" - +option - +"]の引数形式[" - +onoff - +"]が不正です。" - +"on, off, yes, no, true, false" - +"のいずれかを指定してください。"); - } - return result; - } - - /** - * 追加引数を持つオプションのチェック。 - * @param option オプション名 - * @param iterator 引数並び - * @throws IllegalArgumentException 構文エラー - */ - private static void checkNextArg(CharSequence option, - Iterator iterator ) - throws IllegalArgumentException{ - if( ! iterator.hasNext() ){ - throw new IllegalArgumentException( - "起動オプション[" - +option - +"]に引数がありません。"); - } - return; - } - - - /** - * 全引数のリストを返す。 - * @return 全引数のリスト - */ - public List getInvokeArgList(){ - return Collections.unmodifiableList(this.invokeArgs); - } - - /** - * オプションが指定されていたか否か判定する。 - * @param option オプション - * @return 指定されていたらtrue - */ - public boolean hasOption(CmdOption option){ - if(this.optionList.contains(option)) return true; - return false; - } - - /** - * 真偽値をとるオプション値を返す。 - * 複数回指定された場合は最後の値。 - * @param option オプション - * @return 真偽値。オプション指定がなかった場合はnull - * @throws IllegalArgumentException 真偽値を取るオプションではない。 - */ - public Boolean getBooleanArg(CmdOption option) - throws IllegalArgumentException{ - if( ! CmdOption.isBooleanOption(option) ){ - throw new IllegalArgumentException(); - } - Boolean result = this.boolOptionMap.get(option); - return result; - } - - /** - * 文字列引数をとるオプション値を返す。 - * 複数回指定された場合は最後の値。 - * @param option オプション - * @return 文字列。オプション指定がなかった場合はnull - */ - public String getStringArg(CmdOption option){ - String result = this.stringOptionMap.get(option); - return result; - } - - /** - * 排他的オプションのいずれかが指定されたか判定する。 - * 後から指定された方が有効となる。 - * @param options 排他的オプション群 - * @return いずれかのオプション。どれも指定されなければnull - */ - public CmdOption getExclusiveOption(CmdOption... options){ - CmdOption result = null; - for(CmdOption option : this.optionList){ - for(CmdOption excOption : options){ - if(option == excOption){ - result = option; - break; - } - } - } - return result; - } - - /** - * 初期のフレーム幅を返す。 - * @return 初期のフレーム幅。オプション指定されてなければnull - */ - public Integer initialFrameWidth(){ - return this.frameWidth; - } - - /** - * 初期のフレーム高を返す。 - * @return 初期のフレーム高。オプション指定されてなければnull - */ - public Integer initialFrameHeight(){ - return this.frameHeight; - } - - /** - * 初期のフレーム位置のX座標を返す。 - * @return 初期のフレーム位置のX座標。オプション指定されてなければnull - */ - public Integer initialFrameXpos(){ - return this.frameXpos; - } - - /** - * 初期のフレーム位置のY座標を返す。 - * @return 初期のフレーム位置のY座標。オプション指定されてなければnull - */ - public Integer initialFrameYpos(){ - return this.frameYpos; - } - -} +/* + * option argument information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * コマンドラインオプション情報。 + * public static void main()の引数から展開される。 + */ +public class OptionInfo{ + + private static final Pattern PATTERN_GEOMETRY = + Pattern.compile( + "([1-9][0-9]*)x([1-9][0-9]*)" + +"(?:(\\+|\\-)([1-9][0-9]*)(\\+|\\-)([1-9][0-9]*))?" + ); + + + private Integer frameWidth = null; + private Integer frameHeight = null; + private Integer frameXpos = null; + private Integer frameYpos = null; + + private final List invokeArgs = new LinkedList(); + private final List optionList = new LinkedList(); + private final Map boolOptionMap = + new EnumMap(CmdOption.class); + private final Map stringOptionMap = + new EnumMap(CmdOption.class); + + + /** + * コンストラクタ。 + */ + protected OptionInfo(){ + super(); + return; + } + + + /** + * オプション文字列を解析する。 + * @param args main()に渡されるオプション文字列 + * @return 解析済みのオプション情報。 + * @throws IllegalArgumentException 構文エラー + */ + public static OptionInfo parseOptions(String[] args) + throws IllegalArgumentException{ + OptionInfo result = new OptionInfo(); + + result.invokeArgs.clear(); + for(String arg : args){ + if(arg == null) continue; + if(arg.length() <= 0) continue; + result.invokeArgs.add(arg); + } + Iterator iterator = result.invokeArgs.iterator(); + + while(iterator.hasNext()){ + String arg = iterator.next(); + + CmdOption option = CmdOption.parseCmdOption(arg); + if(option == null){ + throw new IllegalArgumentException( + "未定義の起動オプション[" + + arg + + "]が指定されました。"); + } + result.optionList.add(option); + + if(CmdOption.isIndepOption(option)){ + continue; + }else if(CmdOption.isBooleanOption(option)){ + Boolean bool = parseBooleanSwitch(arg, iterator); + result.boolOptionMap.put(option, bool); + continue; + } + + switch(option){ + case OPT_INITFONT: + case OPT_CONFDIR: + checkNextArg(arg, iterator); + result.stringOptionMap.put(option, iterator.next()); + break; + case OPT_GEOMETRY: + checkNextArg(arg, iterator); + String geometry = iterator.next(); + Matcher matcher = PATTERN_GEOMETRY.matcher(geometry); + if( ! matcher.matches() ){ + throw new IllegalArgumentException( + "起動オプション[" + +arg + +"]の引数形式[" + +geometry + +"]が不正です。" ); + } + String width = matcher.group(1); + String height = matcher.group(2); + String xSign = matcher.group(3); + String xPos = matcher.group(4); + String ySign = matcher.group(5); + String yPos = matcher.group(6); + try{ + result.frameWidth = Integer.parseInt(width); + result.frameHeight = Integer.parseInt(height); + if(xPos != null && xPos.length() > 0){ + result.frameXpos = Integer.parseInt(xPos); + if(xSign.equals("-")){ + result.frameXpos = -result.frameXpos; + } + } + if(yPos != null && yPos.length() > 0){ + result.frameYpos = Integer.parseInt(yPos); + if(ySign.equals("-")){ + result.frameYpos = -result.frameYpos; + } + } + }catch(NumberFormatException e){ + assert false; + throw new IllegalArgumentException(e); + } + + break; + default: + assert false; + break; + } + } + + return result; + } + + /** + * 真偽二値をとるオプション解析の下請け。 + * @param option オプション名 + * @param iterator 引数並び + * @return 真偽 + * @throws IllegalArgumentException 構文エラー + */ + private static Boolean parseBooleanSwitch( + String option, Iterator iterator ) + throws IllegalArgumentException{ + Boolean result; + checkNextArg(option, iterator); + String onoff = iterator.next(); + if( onoff.compareToIgnoreCase("on" ) == 0 + || onoff.compareToIgnoreCase("yes" ) == 0 + || onoff.compareToIgnoreCase("true") == 0){ + result = Boolean.TRUE; + }else if( onoff.compareToIgnoreCase("off" ) == 0 + || onoff.compareToIgnoreCase("no" ) == 0 + || onoff.compareToIgnoreCase("false") == 0){ + result = Boolean.FALSE; + }else{ + throw new IllegalArgumentException( + "起動オプション[" + +option + +"]の引数形式[" + +onoff + +"]が不正です。" + +"on, off, yes, no, true, false" + +"のいずれかを指定してください。"); + } + return result; + } + + /** + * 追加引数を持つオプションのチェック。 + * @param option オプション名 + * @param iterator 引数並び + * @throws IllegalArgumentException 構文エラー + */ + private static void checkNextArg(CharSequence option, + Iterator iterator ) + throws IllegalArgumentException{ + if( ! iterator.hasNext() ){ + throw new IllegalArgumentException( + "起動オプション[" + +option + +"]に引数がありません。"); + } + return; + } + + + /** + * 全引数のリストを返す。 + * @return 全引数のリスト + */ + public List getInvokeArgList(){ + return Collections.unmodifiableList(this.invokeArgs); + } + + /** + * オプションが指定されていたか否か判定する。 + * @param option オプション + * @return 指定されていたらtrue + */ + public boolean hasOption(CmdOption option){ + if(this.optionList.contains(option)) return true; + return false; + } + + /** + * 真偽値をとるオプション値を返す。 + * 複数回指定された場合は最後の値。 + * @param option オプション + * @return 真偽値。オプション指定がなかった場合はnull + * @throws IllegalArgumentException 真偽値を取るオプションではない。 + */ + public Boolean getBooleanArg(CmdOption option) + throws IllegalArgumentException{ + if( ! CmdOption.isBooleanOption(option) ){ + throw new IllegalArgumentException(); + } + Boolean result = this.boolOptionMap.get(option); + return result; + } + + /** + * 文字列引数をとるオプション値を返す。 + * 複数回指定された場合は最後の値。 + * @param option オプション + * @return 文字列。オプション指定がなかった場合はnull + */ + public String getStringArg(CmdOption option){ + String result = this.stringOptionMap.get(option); + return result; + } + + /** + * 排他的オプションのいずれかが指定されたか判定する。 + * 後から指定された方が有効となる。 + * @param options 排他的オプション群 + * @return いずれかのオプション。どれも指定されなければnull + */ + public CmdOption getExclusiveOption(CmdOption... options){ + CmdOption result = null; + for(CmdOption option : this.optionList){ + for(CmdOption excOption : options){ + if(option == excOption){ + result = option; + break; + } + } + } + return result; + } + + /** + * 初期のフレーム幅を返す。 + * @return 初期のフレーム幅。オプション指定されてなければnull + */ + public Integer initialFrameWidth(){ + return this.frameWidth; + } + + /** + * 初期のフレーム高を返す。 + * @return 初期のフレーム高。オプション指定されてなければnull + */ + public Integer initialFrameHeight(){ + return this.frameHeight; + } + + /** + * 初期のフレーム位置のX座標を返す。 + * @return 初期のフレーム位置のX座標。オプション指定されてなければnull + */ + public Integer initialFrameXpos(){ + return this.frameXpos; + } + + /** + * 初期のフレーム位置のY座標を返す。 + * @return 初期のフレーム位置のY座標。オプション指定されてなければnull + */ + public Integer initialFrameYpos(){ + return this.frameYpos; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/OptionPanel.java b/src/main/java/jp/sourceforge/jindolf/OptionPanel.java index 6282cc8..42f5d21 100644 --- a/src/main/java/jp/sourceforge/jindolf/OptionPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/OptionPanel.java @@ -1,239 +1,239 @@ -/* - * option panel - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JSeparator; -import javax.swing.JTabbedPane; - -/** - * オプション設定パネル。 - */ -@SuppressWarnings("serial") -public class OptionPanel - extends JDialog - implements ActionListener, WindowListener{ - - private static final String FRAMETITLE = - "オプション設定 - " + Jindolf.TITLE; - - private final JTabbedPane tabPane = new JTabbedPane(); - - private final FontChooser fontChooser; - private final ProxyChooser proxyChooser; - private final DialogPrefPanel dialogPrefPanel; - - private final JButton okButton = new JButton("OK"); - private final JButton cancelButton = new JButton("キャンセル"); - - private boolean isCanceled = false; - - /** - * コンストラクタ。 - * @param owner フレームオーナ - */ - public OptionPanel(Frame owner){ - super(owner, FRAMETITLE, true); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(this); - - this.fontChooser = new FontChooser(FontInfo.DEFAULT_FONTINFO); - this.proxyChooser = new ProxyChooser(); - this.dialogPrefPanel = new DialogPrefPanel(); - - this.tabPane.add("フォント", this.fontChooser); - this.tabPane.add("プロクシ", this.proxyChooser); - this.tabPane.add("発言表示", this.dialogPrefPanel); - - design(getContentPane()); - - this.okButton .addActionListener(this); - this.cancelButton.addActionListener(this); - - return; - } - - /** - * レイアウトを行う。 - * @param content コンテナ - */ - private void design(Container content){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.BOTH; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.insets = new Insets(5, 5, 5, 5); - content.add(this.tabPane, constraints); - - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - content.add(new JSeparator(), constraints); - - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.EAST; - content.add(this.okButton, constraints); - - constraints.weightx = 0.0; - content.add(this.cancelButton, constraints); - - return; - } - - /** - * FontChooserを返す。 - * @return FontChooser - */ - public FontChooser getFontChooser(){ - return this.fontChooser; - } - - /** - * ProxyChooserを返す。 - * @return ProxyChooser - */ - public ProxyChooser getProxyChooser(){ - return this.proxyChooser; - } - - /** - * DialogPrefPanelを返す。 - * @return DialogPrefPanel - */ - public DialogPrefPanel getDialogPrefPanel(){ - return this.dialogPrefPanel; - } - - /** - * ダイアログが閉じられた原因が「キャンセル」か否か判定する。 - * ウィンドウクローズ操作は「キャンセル」扱い。 - * @return 「キャンセル」ならtrue - */ - public boolean isCanceled(){ - return this.isCanceled; - } - - /** - * OKボタン押下処理。 - * ダイアログを閉じる。 - */ - private void actionOk(){ - this.isCanceled = false; - setVisible(false); - dispose(); - return; - } - - /** - * キャンセルボタン押下処理。 - * ダイアログを閉じる。 - */ - private void actionCancel(){ - this.isCanceled = true; - setVisible(false); - dispose(); - return; - } - - /** - * ボタン押下イベント受信。 - * @param event イベント - */ - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if (source == this.okButton ) actionOk(); - else if(source == this.cancelButton) actionCancel(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowOpened(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * ダイアログを閉じる。 - * キャンセルボタン押下時と同じ。 - * @param event {@inheritDoc} - */ - @Override - public void windowClosing(WindowEvent event){ - actionCancel(); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowClosed(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowActivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeactivated(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowIconified(WindowEvent event){ - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void windowDeiconified(WindowEvent event){ - return; - } - -} +/* + * option panel + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JSeparator; +import javax.swing.JTabbedPane; + +/** + * オプション設定パネル。 + */ +@SuppressWarnings("serial") +public class OptionPanel + extends JDialog + implements ActionListener, WindowListener{ + + private static final String FRAMETITLE = + "オプション設定 - " + Jindolf.TITLE; + + private final JTabbedPane tabPane = new JTabbedPane(); + + private final FontChooser fontChooser; + private final ProxyChooser proxyChooser; + private final DialogPrefPanel dialogPrefPanel; + + private final JButton okButton = new JButton("OK"); + private final JButton cancelButton = new JButton("キャンセル"); + + private boolean isCanceled = false; + + /** + * コンストラクタ。 + * @param owner フレームオーナ + */ + public OptionPanel(Frame owner){ + super(owner, FRAMETITLE, true); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(this); + + this.fontChooser = new FontChooser(FontInfo.DEFAULT_FONTINFO); + this.proxyChooser = new ProxyChooser(); + this.dialogPrefPanel = new DialogPrefPanel(); + + this.tabPane.add("フォント", this.fontChooser); + this.tabPane.add("プロクシ", this.proxyChooser); + this.tabPane.add("発言表示", this.dialogPrefPanel); + + design(getContentPane()); + + this.okButton .addActionListener(this); + this.cancelButton.addActionListener(this); + + return; + } + + /** + * レイアウトを行う。 + * @param content コンテナ + */ + private void design(Container content){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + constraints.insets = new Insets(5, 5, 5, 5); + content.add(this.tabPane, constraints); + + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + content.add(new JSeparator(), constraints); + + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + content.add(this.okButton, constraints); + + constraints.weightx = 0.0; + content.add(this.cancelButton, constraints); + + return; + } + + /** + * FontChooserを返す。 + * @return FontChooser + */ + public FontChooser getFontChooser(){ + return this.fontChooser; + } + + /** + * ProxyChooserを返す。 + * @return ProxyChooser + */ + public ProxyChooser getProxyChooser(){ + return this.proxyChooser; + } + + /** + * DialogPrefPanelを返す。 + * @return DialogPrefPanel + */ + public DialogPrefPanel getDialogPrefPanel(){ + return this.dialogPrefPanel; + } + + /** + * ダイアログが閉じられた原因が「キャンセル」か否か判定する。 + * ウィンドウクローズ操作は「キャンセル」扱い。 + * @return 「キャンセル」ならtrue + */ + public boolean isCanceled(){ + return this.isCanceled; + } + + /** + * OKボタン押下処理。 + * ダイアログを閉じる。 + */ + private void actionOk(){ + this.isCanceled = false; + setVisible(false); + dispose(); + return; + } + + /** + * キャンセルボタン押下処理。 + * ダイアログを閉じる。 + */ + private void actionCancel(){ + this.isCanceled = true; + setVisible(false); + dispose(); + return; + } + + /** + * ボタン押下イベント受信。 + * @param event イベント + */ + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if (source == this.okButton ) actionOk(); + else if(source == this.cancelButton) actionCancel(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowOpened(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * ダイアログを閉じる。 + * キャンセルボタン押下時と同じ。 + * @param event {@inheritDoc} + */ + @Override + public void windowClosing(WindowEvent event){ + actionCancel(); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowClosed(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowActivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeactivated(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowIconified(WindowEvent event){ + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void windowDeiconified(WindowEvent event){ + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Period.java b/src/main/java/jp/sourceforge/jindolf/Period.java index 9f8e97a..9bce811 100644 --- a/src/main/java/jp/sourceforge/jindolf/Period.java +++ b/src/main/java/jp/sourceforge/jindolf/Period.java @@ -1,1242 +1,1242 @@ -/* - * daily period in village - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import jp.sourceforge.jindolf.corelib.EventFamily; -import jp.sourceforge.jindolf.corelib.GameRole; -import jp.sourceforge.jindolf.corelib.LandDef; -import jp.sourceforge.jindolf.corelib.PeriodType; -import jp.sourceforge.jindolf.corelib.SysEventType; -import jp.sourceforge.jindolf.corelib.TalkType; -import jp.sourceforge.jindolf.corelib.Team; -import jp.sourceforge.jindolf.corelib.VillageState; -import jp.sourceforge.jindolf.parser.DecodedContent; -import jp.sourceforge.jindolf.parser.EntityConverter; -import jp.sourceforge.jindolf.parser.HtmlAdapter; -import jp.sourceforge.jindolf.parser.HtmlParseException; -import jp.sourceforge.jindolf.parser.HtmlParser; -import jp.sourceforge.jindolf.parser.PageType; -import jp.sourceforge.jindolf.parser.SeqRange; - -/** - * いわゆる「日」。 - * 村の進行の一区切り。プロローグやエピローグも含まれる。 - * - * 将来、24時間更新でなくなる可能性の考慮が必要。 - * 人気のないプロローグなどで、 - * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。 - */ -public class Period{ - // TODO Comparable も implement する? - - private static final HtmlParser PARSER = new HtmlParser(); - private static final PeriodHandler HANDLER = - new PeriodHandler(); - - static{ - PARSER.setBasicHandler (HANDLER); - PARSER.setSysEventHandler(HANDLER); - PARSER.setTalkHandler (HANDLER); - } - - private final Village homeVillage; - private final PeriodType periodType; - private final int day; - private int limitHour; - private int limitMinute; - // TODO 更新月日も入れるべきか。 - private String loginName; - private boolean isFullOpen = false; - - private final List topicList = new LinkedList(); - private final List unmodList = - Collections.unmodifiableList(this.topicList); - - - /** - * この Period が進行中の村の最新日で、 - * 今まさに次々と発言が蓄積されているときは - * true になる。 - * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。 - */ - private boolean isHot; - - - /** - * Periodを生成する。 - * この段階では発言データのロードは行われない。 - * デフォルトで非Hot状態。 - * @param homeVillage 所属するVillage - * @param periodType Period種別 - * @param day Period通番 - * @throws java.lang.NullPointerException 引数にnullが渡された場合。 - */ - public Period(Village homeVillage, - PeriodType periodType, - int day) - throws NullPointerException{ - this(homeVillage, periodType, day, false); - return; - } - - /** - * Periodを生成する。 - * この段階では発言データのロードは行われない。 - * @param homeVillage 所属するVillage - * @param periodType Period種別 - * @param day Period通番 - * @param isHot Hotか否か - * @throws java.lang.NullPointerException 引数にnullが渡された場合。 - */ - private Period(Village homeVillage, - PeriodType periodType, - int day, - boolean isHot) - throws NullPointerException{ - if( homeVillage == null - || periodType == null ) throw new NullPointerException(); - if(day < 0){ - throw new IllegalArgumentException("Period day is too small !"); - } - switch(periodType){ - case PROLOGUE: - assert day == 0; - break; - case PROGRESS: - case EPILOGUE: - assert day > 0; - break; - default: - assert false; - break; - } - - this.homeVillage = homeVillage; - this.periodType = periodType; - this.day = day; - - unload(); - - this.isHot = isHot; - - return; - } - - - /** - * Periodを更新する。Topicのリストが更新される。 - * @param period 日 - * @param force trueなら強制再読み込み。 - * falseならまだ読み込んで無い時のみ読み込み。 - * @throws IOException ネットワーク入力エラー - */ - public static void parsePeriod(Period period, boolean force) - throws IOException{ - if( ! force && period.hasLoaded() ) return; - - Village village = period.getVillage(); - Land land = village.getParentLand(); - ServerAccess server = land.getServerAccess(); - - if(village.getState() != VillageState.PROGRESS){ - period.isFullOpen = true; - }else if(period.getType() != PeriodType.PROGRESS){ - period.isFullOpen = true; - }else{ - period.isFullOpen = false; - } - - HtmlSequence html = server.getHTMLPeriod(period); - - period.topicList.clear(); - - boolean wasHot = period.isHot(); - - HANDLER.setPeriod(period); - DecodedContent content = html.getContent(); - try{ - PARSER.parseAutomatic(content); - }catch(HtmlParseException e){ - Jindolf.logger().warn("発言抽出に失敗", e); - } - - if(wasHot && ! period.isHot() ){ - parsePeriod(period, true); - return; - } - - return; - } - - /** - * 所属する村を返す。 - * @return 村 - */ - public Village getVillage(){ - return this.homeVillage; - } - - /** - * Period種別を返す。 - * @return 種別 - */ - public PeriodType getType(){ - return this.periodType; - } - - /** - * Period通番を返す。 - * プロローグは常に0番。 - * n日目のゲーム進行日はn番 - * エピローグは最後のゲーム進行日+1番 - * @return Period通番 - */ - public int getDay(){ - return this.day; - } - - /** - * 更新時刻の文字表記を返す。 - * @return 更新時刻の文字表記 - */ - public String getLimit(){ - StringBuilder result = new StringBuilder(); - - if(this.limitHour < 10) result.append('0'); - result.append(this.limitHour).append(':'); - - if(this.limitMinute < 10) result.append('0'); - result.append(this.limitMinute); - - return result.toString(); - } - - /** - * Hotか否か返す。 - * @return Hotか否か - */ - public boolean isHot(){ - return this.isHot; - } - - /** - * Hotか否か設定する。 - * @param isHotArg Hot指定 - */ - public void setHot(boolean isHotArg){ - this.isHot = isHotArg; - } - - /** - * プロローグか否か判定する。 - * @return プロローグならtrue - */ - public boolean isPrologue(){ - if(getType() == PeriodType.PROLOGUE) return true; - return false; - } - - /** - * エピローグか否か判定する。 - * @return エピローグならtrue - */ - public boolean isEpilogue(){ - if(getType() == PeriodType.EPILOGUE) return true; - return false; - } - - /** - * 進行日か否か判定する。 - * @return 進行日ならtrue - */ - public boolean isProgress(){ - if(getType() == PeriodType.PROGRESS) return true; - return false; - } - - /** - * このPeriodにアクセスするためのクエリーを生成する。 - * @return CGIに渡すクエリー - */ - public String getCGIQuery(){ - StringBuilder result = new StringBuilder(); - - Village village = getVillage(); - result.append(village.getCGIQuery()); - - if(isHot()){ - result.append("&mes=all"); // 全表示指定 - return result.toString(); - } - - Land land = village.getParentLand(); - LandDef ldef = land.getLandDef(); - - if(ldef.getLandId().equals("wolfg")){ - result.append("&meslog="); - String dnum = "000" + (getDay() - 1); - dnum = dnum.substring(dnum.length() - 3); - switch(getType()){ - case PROLOGUE: - result.append("000_ready"); - break; - case PROGRESS: - result.append(dnum).append("_progress"); - break; - case EPILOGUE: - result.append(dnum).append("_party"); - break; - default: - assert false; - return null; - } - }else{ - result.append("&meslog=").append(village.getVillageID()); - switch(getType()){ - case PROLOGUE: - result.append("_ready_0"); - break; - case PROGRESS: - result.append("_progress_").append(getDay() - 1); - break; - case EPILOGUE: - result.append("_party_").append(getDay() - 1); - break; - default: - assert false; - return null; - } - } - - - result.append("&mes=all"); - - return result.toString(); - } - - /** - * Periodに含まれるTopicのリストを返す。 - * このリストは上書き操作不能。 - * @return Topicのリスト - */ - public List getTopicList(){ - return this.unmodList; - } - - /** - * Periodに含まれるTopicの総数を返す。 - * @return Topic総数 - */ - public int getTopics(){ - return this.topicList.size(); - } - - /** - * Topicを追加する。 - * @param topic Topic - * @throws java.lang.NullPointerException nullが渡された場合。 - */ - protected void addTopic(Topic topic) throws NullPointerException{ - if(topic == null) throw new NullPointerException(); - this.topicList.add(topic); - return; - } - - /** - * Periodのキャプション文字列を返す。 - * 主な用途はタブ画面の耳のラベルなど。 - * @return キャプション文字列 - */ - public String getCaption(){ - String result; - - switch(getType()){ - case PROLOGUE: - result = "プロローグ"; - break; - case PROGRESS: - result = getDay() + "日目"; - break; - case EPILOGUE: - result = "エピローグ"; - break; - default: - assert false; - result = null; - break; - } - - return result; - } - - /** - * このPeriodをダウンロードしたときのログイン名を返す。 - * @return ログイン名。ログアウト中はnull。 - */ - public String getLoginName(){ - return this.loginName; - } - - /** - * 公開発言番号にマッチする発言を返す。 - * @param talkNo 公開発言番号 - * @return 発言。見つからなければnull - */ - public Talk getNumberedTalk(int talkNo){ - if(talkNo <= 0) throw new IllegalArgumentException(); - - for(Topic topic : this.topicList){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - if(talkNo == talk.getTalkNo()) return talk; - } - - return null; - } - - /** - * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。 - * @return 隠れた要素がありうるならfalse - */ - public boolean isFullOpen(){ - return this.isFullOpen; - } - - /** - * ロード済みか否かチェックする。 - * @return ロード済みならtrue - */ - public boolean hasLoaded(){ - return getTopics() > 0; - } - - /** - * 発言データをアンロードする。 - */ - public void unload(){ - this.limitHour = 0; - this.limitMinute = 0; - this.loginName = null; - this.isFullOpen = false; - - this.isHot = false; - - this.topicList.clear(); - - return; - } - - /** - * 襲撃メッセージの有無を判定する。 - * 決着が付くまで非狼陣営には見えない。 - * 偽装GJでは狼にも見えない。 - * @return 襲撃メッセージがあればtrue - */ - public boolean hasAssaultTried(){ - for(Topic topic : this.topicList){ - if(topic instanceof Talk){ - Talk talk = (Talk) topic; - if(talk.getTalkCount() <= 0) return true; - }else if(topic instanceof SysEvent){ - SysEvent sysEvent = (SysEvent) topic; - SysEventType type = sysEvent.getSysEventType(); - if(type == SysEventType.ASSAULT) return true; - } - } - - return false; - } - - /** - * 処刑されたAvatarを返す。 - * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull - */ - public Avatar getExecutedAvatar(){ - Avatar result = null; - - for(Topic topic : getTopicList()){ - if( ! (topic instanceof SysEvent) ) continue; - SysEvent event = (SysEvent) topic; - result = event.getExecutedAvatar(); - if(result != null) break; - } - - return result; - } - - /** - * 投票に参加したAvatarの集合を返す。 - * @return 投票に参加したAvatarのSet - */ - public Set getVoterSet(){ - Set result = new HashSet(); - - for(Topic topic : getTopicList()){ - if( ! (topic instanceof SysEvent) ) continue; - SysEvent event = (SysEvent) topic; - result = event.getVoterSet(result); - } - - return result; - } - - /** - * 任意のタイプのシステムイベントを返す。 - * 複数存在する場合、返すのは最初の一つだけ。 - * @param type イベントタイプ - * @return システムイベント - */ - public SysEvent getTypedSysEvent(SysEventType type){ - for(Topic topic : getTopicList()){ - if( ! (topic instanceof SysEvent) ) continue; - SysEvent event = (SysEvent) topic; - if(event.getSysEventType() == type) return event; - } - - return null; - } - - /** - * Periodパース用ハンドラ。 - */ - private static class PeriodHandler extends HtmlAdapter{ - - private static final int TALKTYPE_NUM = TalkType.values().length; - - private final EntityConverter converter = - new EntityConverter(); - - private final Map countMap = - new HashMap(); - - private Period period = null; - - private TalkType talkType; - private Avatar avatar; - private int talkNo; - private String anchorId; - private int talkHour; - private int talkMinute; - private DecodedContent talkContent = null; - - private EventFamily eventFamily; - private SysEventType sysEventType; - private DecodedContent eventContent = null; - private final List avatarList = new LinkedList(); - private final List roleList = new LinkedList(); - private final List integerList = new LinkedList(); - private final List charseqList = - new LinkedList(); - - /** - * コンストラクタ。 - */ - public PeriodHandler(){ - super(); - return; - } - - /** - * パース結果を格納するPeriodを設定する。 - * @param period Period - */ - public void setPeriod(Period period){ - this.period = period; - return; - } - - /** - * 文字列断片からAvatarを得る。 - * 村に未登録のAvatarであればついでに登録される。 - * @param content 文字列 - * @param range 文字列内のAvatarフルネームを示す領域 - * @return Avatar - */ - private Avatar toAvatar(DecodedContent content, SeqRange range){ - Village village = this.period.getVillage(); - String fullName = this.converter - .convert(content, range) - .toString(); - Avatar result = village.getAvatar(fullName); - if(result == null){ - result = new Avatar(fullName); - village.addAvatar(result); - } - - return result; - } - - /** - * Avatar別、会話種ごとに発言回数をカウントする。 - * 1から始まる。 - * @param targetAvatar 対象Avatar - * @param targetType 対象会話種 - * @return カウント数 - */ - private int countUp(Avatar targetAvatar, TalkType targetType){ - int[] countArray = this.countMap.get(targetAvatar); - if(countArray == null){ - countArray = new int[TALKTYPE_NUM]; - this.countMap.put(targetAvatar, countArray); - } - int count = ++countArray[targetType.ordinal()]; - return count; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void startParse(DecodedContent content) - throws HtmlParseException{ - this.period.loginName = null; - this.period.topicList.clear(); - this.countMap.clear(); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param loginRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void loginName(DecodedContent content, SeqRange loginRange) - throws HtmlParseException{ - DecodedContent loginName = - this.converter.convert(content, loginRange); - - this.period.loginName = loginName.toString(); - - return; - } - - /** - * {@inheritDoc} - * @param type {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void pageType(PageType type) throws HtmlParseException{ - if(type != PageType.PERIOD_PAGE){ - throw new HtmlParseException( - "意図しないページを読み込もうとしました。"); - } - return; - } - - /** - * {@inheritDoc} - * @param month {@inheritDoc} - * @param day {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void commitTime(int month, int day, int hour, int minute) - throws HtmlParseException{ - this.period.limitHour = hour; - this.period.limitMinute = minute; - return; - } - - /** - * {@inheritDoc} - * 自分へのリンクが無いかチェックする。 - * 自分へのリンクが見つかればこのPeriodを非Hotにする。 - * 自分へのリンクがあるということは、 - * 今読んでるHTMLは別のPeriodのために書かれたものということ。 - * 考えられる原因は、HotだったPeriodがゲーム進行に従い - * Hotでなくなったこと。 - * @param content {@inheritDoc} - * @param anchorRange {@inheritDoc} - * @param periodType {@inheritDoc} - * @param day {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void periodLink(DecodedContent content, - SeqRange anchorRange, - PeriodType periodType, - int day) - throws HtmlParseException{ - - if(this.period.getType() != periodType) return; - - if( periodType == PeriodType.PROGRESS - && this.period.getDay() != day ){ - return; - } - - if( ! anchorRange.isValid() ) return; - - this.period.setHot(false); - - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void startTalk() throws HtmlParseException{ - this.talkType = null; - this.avatar = null; - this.talkNo = -1; - this.anchorId = null; - this.talkHour = -1; - this.talkMinute = -1; - this.talkContent = new DecodedContent(100 + 1); - - return; - } - - /** - * {@inheritDoc} - * @param type {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkType(TalkType type) - throws HtmlParseException{ - this.talkType = type; - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkAvatar(DecodedContent content, SeqRange avatarRange) - throws HtmlParseException{ - this.avatar = toAvatar(content, avatarRange); - return; - } - - /** - * {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkTime(int hour, int minute) - throws HtmlParseException{ - this.talkHour = hour; - this.talkMinute = minute; - return; - } - - /** - * {@inheritDoc} - * @param tno {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkNo(int tno) throws HtmlParseException{ - this.talkNo = tno; - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param idRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkId(DecodedContent content, SeqRange idRange) - throws HtmlParseException{ - this.anchorId = content.subSequence(idRange.getStartPos(), - idRange.getEndPos() ) - .toString(); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param textRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkText(DecodedContent content, SeqRange textRange) - throws HtmlParseException{ - this.converter.append(this.talkContent, content, textRange); - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void talkBreak() - throws HtmlParseException{ - this.talkContent.append('\n'); - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void endTalk() throws HtmlParseException{ - Talk talk = new Talk(this.period, - this.talkType, - this.avatar, - this.talkNo, - this.anchorId, - this.talkHour, this.talkMinute, - this.talkContent ); - - int count = countUp(this.avatar, this.talkType); - talk.setCount(count); - - this.period.addTopic(talk); - - this.talkType = null; - this.avatar = null; - this.talkNo = -1; - this.anchorId = null; - this.talkHour = -1; - this.talkMinute = -1; - this.talkContent = null; - - return; - } - - /** - * {@inheritDoc} - * @param family {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void startSysEvent(EventFamily family) - throws HtmlParseException{ - this.eventFamily = family; - this.sysEventType = null; - this.eventContent = new DecodedContent(); - this.avatarList.clear(); - this.roleList.clear(); - this.integerList.clear(); - this.charseqList.clear(); - return; - } - - /** - * {@inheritDoc} - * @param type {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventType(SysEventType type) - throws HtmlParseException{ - this.sysEventType = type; - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param contentRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventContent(DecodedContent content, - SeqRange contentRange) - throws HtmlParseException{ - this.converter.append(this.eventContent, content, contentRange); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param anchorRange {@inheritDoc} - * @param contentRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventContentAnchor(DecodedContent content, - SeqRange anchorRange, - SeqRange contentRange) - throws HtmlParseException{ - this.converter.append(this.eventContent, content, contentRange); - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventContentBreak() throws HtmlParseException{ - this.eventContent.append('\n'); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param entryNo {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventOnStage(DecodedContent content, - int entryNo, - SeqRange avatarRange) - throws HtmlParseException{ - Avatar newAvatar = toAvatar(content, avatarRange); - this.integerList.add(entryNo); - this.avatarList.add(newAvatar); - return; - } - - /** - * {@inheritDoc} - * @param role {@inheritDoc} - * @param num {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventOpenRole(GameRole role, int num) - throws HtmlParseException{ - this.roleList.add(role); - this.integerList.add(num); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventMurdered(DecodedContent content, - SeqRange avatarRange) - throws HtmlParseException{ - Avatar murdered = toAvatar(content, avatarRange); - this.avatarList.add(murdered); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventSurvivor(DecodedContent content, - SeqRange avatarRange) - throws HtmlParseException{ - Avatar survivor = toAvatar(content, avatarRange); - this.avatarList.add(survivor); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param voteByRange {@inheritDoc} - * @param voteToRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventCounting(DecodedContent content, - SeqRange voteByRange, - SeqRange voteToRange) - throws HtmlParseException{ - if(voteByRange.isValid()){ - Avatar voteBy = toAvatar(content, voteByRange); - this.avatarList.add(voteBy); - } - Avatar voteTo = toAvatar(content, voteToRange); - this.avatarList.add(voteTo); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param voteByRange {@inheritDoc} - * @param voteToRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventCounting2(DecodedContent content, - SeqRange voteByRange, - SeqRange voteToRange) - throws HtmlParseException{ - sysEventCounting(content, voteByRange, voteToRange); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventSuddenDeath(DecodedContent content, - SeqRange avatarRange) - throws HtmlParseException{ - Avatar suddenDeath = toAvatar(content, avatarRange); - this.avatarList.add(suddenDeath); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @param anchorRange {@inheritDoc} - * @param loginRange {@inheritDoc} - * @param isLiving {@inheritDoc} - * @param role {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventPlayerList(DecodedContent content, - SeqRange avatarRange, - SeqRange anchorRange, - SeqRange loginRange, - boolean isLiving, - GameRole role ) - throws HtmlParseException{ - Avatar who = toAvatar(content, avatarRange); - - CharSequence anchor; - if(anchorRange.isValid()){ - anchor = this.converter.convert(content, anchorRange); - }else{ - anchor = ""; - } - CharSequence account = this.converter - .convert(content, loginRange); - - Integer liveOrDead; - if(isLiving) liveOrDead = Integer.valueOf(1); - else liveOrDead = Integer.valueOf(0); - - this.avatarList.add(who); - this.charseqList.add(anchor); - this.charseqList.add(account); - this.integerList.add(liveOrDead); - this.roleList.add(role); - - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @param votes {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventExecution(DecodedContent content, - SeqRange avatarRange, - int votes ) - throws HtmlParseException{ - Avatar who = toAvatar(content, avatarRange); - - this.avatarList.add(who); - this.integerList.add(votes); - - return; - } - - /** - * {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @param minLimit {@inheritDoc} - * @param maxLimit {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventAskEntry(int hour, int minute, - int minLimit, int maxLimit) - throws HtmlParseException{ - this.integerList.add(hour * 60 + minute); - this.integerList.add(minLimit); - this.integerList.add(maxLimit); - return; - } - - /** - * {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventAskCommit(int hour, int minute) - throws HtmlParseException{ - this.integerList.add(hour * 60 + minute); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param avatarRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventNoComment(DecodedContent content, - SeqRange avatarRange) - throws HtmlParseException{ - Avatar noComAvatar = toAvatar(content, avatarRange); - this.avatarList.add(noComAvatar); - return; - } - - /** - * {@inheritDoc} - * @param winner {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventStayEpilogue(Team winner, int hour, int minute) - throws HtmlParseException{ - GameRole role = null; - - switch(winner){ - case VILLAGE: role = GameRole.INNOCENT; break; - case WOLF: role = GameRole.WOLF; break; - case HAMSTER: role = GameRole.HAMSTER; break; - default: assert false; break; - } - - this.roleList.add(role); - this.integerList.add(hour * 60 + minute); - - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param guardByRange {@inheritDoc} - * @param guardToRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventGuard(DecodedContent content, - SeqRange guardByRange, - SeqRange guardToRange) - throws HtmlParseException{ - Avatar guardBy = toAvatar(content, guardByRange); - Avatar guardTo = toAvatar(content, guardToRange); - this.avatarList.add(guardBy); - this.avatarList.add(guardTo); - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param judgeByRange {@inheritDoc} - * @param judgeToRange {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void sysEventJudge(DecodedContent content, - SeqRange judgeByRange, - SeqRange judgeToRange) - throws HtmlParseException{ - Avatar judgeBy = toAvatar(content, judgeByRange); - Avatar judgeTo = toAvatar(content, judgeToRange); - this.avatarList.add(judgeBy); - this.avatarList.add(judgeTo); - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void endSysEvent() throws HtmlParseException{ - SysEvent event = new SysEvent(); - event.setEventFamily(this.eventFamily); - event.setSysEventType(this.sysEventType); - event.setContent(this.eventContent); - event.addAvatarList(this.avatarList); - event.addRoleList(this.roleList); - event.addIntegerList(this.integerList); - event.addCharSequenceList(this.charseqList); - - this.period.addTopic(event); - - if( this.sysEventType == SysEventType.MURDERED - || this.sysEventType == SysEventType.NOMURDER ){ - for(Topic topic : this.period.topicList){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - if(talk.getTalkType() != TalkType.WOLFONLY) continue; - if( ! StringUtils - .isTerminated(talk.getDialog(), - "!\u0020今日がお前の命日だ!") ){ - continue; - } - talk.setCount(-1); - this.countMap.clear(); - } - } - - this.eventFamily = null; - this.sysEventType = null; - this.eventContent = null; - this.avatarList.clear(); - this.roleList.clear(); - this.integerList.clear(); - this.charseqList.clear(); - - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void endParse() throws HtmlParseException{ - return; - } - - // TODO 村名のチェックは不要か? - } - -} +/* + * daily period in village + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import jp.sourceforge.jindolf.corelib.EventFamily; +import jp.sourceforge.jindolf.corelib.GameRole; +import jp.sourceforge.jindolf.corelib.LandDef; +import jp.sourceforge.jindolf.corelib.PeriodType; +import jp.sourceforge.jindolf.corelib.SysEventType; +import jp.sourceforge.jindolf.corelib.TalkType; +import jp.sourceforge.jindolf.corelib.Team; +import jp.sourceforge.jindolf.corelib.VillageState; +import jp.sourceforge.jindolf.parser.DecodedContent; +import jp.sourceforge.jindolf.parser.EntityConverter; +import jp.sourceforge.jindolf.parser.HtmlAdapter; +import jp.sourceforge.jindolf.parser.HtmlParseException; +import jp.sourceforge.jindolf.parser.HtmlParser; +import jp.sourceforge.jindolf.parser.PageType; +import jp.sourceforge.jindolf.parser.SeqRange; + +/** + * いわゆる「日」。 + * 村の進行の一区切り。プロローグやエピローグも含まれる。 + * + * 将来、24時間更新でなくなる可能性の考慮が必要。 + * 人気のないプロローグなどで、 + * 24時間以上の期間を持つPeriodが生成される可能性の考慮が必要。 + */ +public class Period{ + // TODO Comparable も implement する? + + private static final HtmlParser PARSER = new HtmlParser(); + private static final PeriodHandler HANDLER = + new PeriodHandler(); + + static{ + PARSER.setBasicHandler (HANDLER); + PARSER.setSysEventHandler(HANDLER); + PARSER.setTalkHandler (HANDLER); + } + + private final Village homeVillage; + private final PeriodType periodType; + private final int day; + private int limitHour; + private int limitMinute; + // TODO 更新月日も入れるべきか。 + private String loginName; + private boolean isFullOpen = false; + + private final List topicList = new LinkedList(); + private final List unmodList = + Collections.unmodifiableList(this.topicList); + + + /** + * この Period が進行中の村の最新日で、 + * 今まさに次々と発言が蓄積されているときは + * true になる。 + * ※重要: Hot な Period は meslog クエリーを使ってダウンロードできない。 + */ + private boolean isHot; + + + /** + * Periodを生成する。 + * この段階では発言データのロードは行われない。 + * デフォルトで非Hot状態。 + * @param homeVillage 所属するVillage + * @param periodType Period種別 + * @param day Period通番 + * @throws java.lang.NullPointerException 引数にnullが渡された場合。 + */ + public Period(Village homeVillage, + PeriodType periodType, + int day) + throws NullPointerException{ + this(homeVillage, periodType, day, false); + return; + } + + /** + * Periodを生成する。 + * この段階では発言データのロードは行われない。 + * @param homeVillage 所属するVillage + * @param periodType Period種別 + * @param day Period通番 + * @param isHot Hotか否か + * @throws java.lang.NullPointerException 引数にnullが渡された場合。 + */ + private Period(Village homeVillage, + PeriodType periodType, + int day, + boolean isHot) + throws NullPointerException{ + if( homeVillage == null + || periodType == null ) throw new NullPointerException(); + if(day < 0){ + throw new IllegalArgumentException("Period day is too small !"); + } + switch(periodType){ + case PROLOGUE: + assert day == 0; + break; + case PROGRESS: + case EPILOGUE: + assert day > 0; + break; + default: + assert false; + break; + } + + this.homeVillage = homeVillage; + this.periodType = periodType; + this.day = day; + + unload(); + + this.isHot = isHot; + + return; + } + + + /** + * Periodを更新する。Topicのリストが更新される。 + * @param period 日 + * @param force trueなら強制再読み込み。 + * falseならまだ読み込んで無い時のみ読み込み。 + * @throws IOException ネットワーク入力エラー + */ + public static void parsePeriod(Period period, boolean force) + throws IOException{ + if( ! force && period.hasLoaded() ) return; + + Village village = period.getVillage(); + Land land = village.getParentLand(); + ServerAccess server = land.getServerAccess(); + + if(village.getState() != VillageState.PROGRESS){ + period.isFullOpen = true; + }else if(period.getType() != PeriodType.PROGRESS){ + period.isFullOpen = true; + }else{ + period.isFullOpen = false; + } + + HtmlSequence html = server.getHTMLPeriod(period); + + period.topicList.clear(); + + boolean wasHot = period.isHot(); + + HANDLER.setPeriod(period); + DecodedContent content = html.getContent(); + try{ + PARSER.parseAutomatic(content); + }catch(HtmlParseException e){ + Jindolf.logger().warn("発言抽出に失敗", e); + } + + if(wasHot && ! period.isHot() ){ + parsePeriod(period, true); + return; + } + + return; + } + + /** + * 所属する村を返す。 + * @return 村 + */ + public Village getVillage(){ + return this.homeVillage; + } + + /** + * Period種別を返す。 + * @return 種別 + */ + public PeriodType getType(){ + return this.periodType; + } + + /** + * Period通番を返す。 + * プロローグは常に0番。 + * n日目のゲーム進行日はn番 + * エピローグは最後のゲーム進行日+1番 + * @return Period通番 + */ + public int getDay(){ + return this.day; + } + + /** + * 更新時刻の文字表記を返す。 + * @return 更新時刻の文字表記 + */ + public String getLimit(){ + StringBuilder result = new StringBuilder(); + + if(this.limitHour < 10) result.append('0'); + result.append(this.limitHour).append(':'); + + if(this.limitMinute < 10) result.append('0'); + result.append(this.limitMinute); + + return result.toString(); + } + + /** + * Hotか否か返す。 + * @return Hotか否か + */ + public boolean isHot(){ + return this.isHot; + } + + /** + * Hotか否か設定する。 + * @param isHotArg Hot指定 + */ + public void setHot(boolean isHotArg){ + this.isHot = isHotArg; + } + + /** + * プロローグか否か判定する。 + * @return プロローグならtrue + */ + public boolean isPrologue(){ + if(getType() == PeriodType.PROLOGUE) return true; + return false; + } + + /** + * エピローグか否か判定する。 + * @return エピローグならtrue + */ + public boolean isEpilogue(){ + if(getType() == PeriodType.EPILOGUE) return true; + return false; + } + + /** + * 進行日か否か判定する。 + * @return 進行日ならtrue + */ + public boolean isProgress(){ + if(getType() == PeriodType.PROGRESS) return true; + return false; + } + + /** + * このPeriodにアクセスするためのクエリーを生成する。 + * @return CGIに渡すクエリー + */ + public String getCGIQuery(){ + StringBuilder result = new StringBuilder(); + + Village village = getVillage(); + result.append(village.getCGIQuery()); + + if(isHot()){ + result.append("&mes=all"); // 全表示指定 + return result.toString(); + } + + Land land = village.getParentLand(); + LandDef ldef = land.getLandDef(); + + if(ldef.getLandId().equals("wolfg")){ + result.append("&meslog="); + String dnum = "000" + (getDay() - 1); + dnum = dnum.substring(dnum.length() - 3); + switch(getType()){ + case PROLOGUE: + result.append("000_ready"); + break; + case PROGRESS: + result.append(dnum).append("_progress"); + break; + case EPILOGUE: + result.append(dnum).append("_party"); + break; + default: + assert false; + return null; + } + }else{ + result.append("&meslog=").append(village.getVillageID()); + switch(getType()){ + case PROLOGUE: + result.append("_ready_0"); + break; + case PROGRESS: + result.append("_progress_").append(getDay() - 1); + break; + case EPILOGUE: + result.append("_party_").append(getDay() - 1); + break; + default: + assert false; + return null; + } + } + + + result.append("&mes=all"); + + return result.toString(); + } + + /** + * Periodに含まれるTopicのリストを返す。 + * このリストは上書き操作不能。 + * @return Topicのリスト + */ + public List getTopicList(){ + return this.unmodList; + } + + /** + * Periodに含まれるTopicの総数を返す。 + * @return Topic総数 + */ + public int getTopics(){ + return this.topicList.size(); + } + + /** + * Topicを追加する。 + * @param topic Topic + * @throws java.lang.NullPointerException nullが渡された場合。 + */ + protected void addTopic(Topic topic) throws NullPointerException{ + if(topic == null) throw new NullPointerException(); + this.topicList.add(topic); + return; + } + + /** + * Periodのキャプション文字列を返す。 + * 主な用途はタブ画面の耳のラベルなど。 + * @return キャプション文字列 + */ + public String getCaption(){ + String result; + + switch(getType()){ + case PROLOGUE: + result = "プロローグ"; + break; + case PROGRESS: + result = getDay() + "日目"; + break; + case EPILOGUE: + result = "エピローグ"; + break; + default: + assert false; + result = null; + break; + } + + return result; + } + + /** + * このPeriodをダウンロードしたときのログイン名を返す。 + * @return ログイン名。ログアウト中はnull。 + */ + public String getLoginName(){ + return this.loginName; + } + + /** + * 公開発言番号にマッチする発言を返す。 + * @param talkNo 公開発言番号 + * @return 発言。見つからなければnull + */ + public Talk getNumberedTalk(int talkNo){ + if(talkNo <= 0) throw new IllegalArgumentException(); + + for(Topic topic : this.topicList){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + if(talkNo == talk.getTalkNo()) return talk; + } + + return null; + } + + /** + * このPeriodの内容にゲーム進行上隠された部分がある可能性を判定する。 + * @return 隠れた要素がありうるならfalse + */ + public boolean isFullOpen(){ + return this.isFullOpen; + } + + /** + * ロード済みか否かチェックする。 + * @return ロード済みならtrue + */ + public boolean hasLoaded(){ + return getTopics() > 0; + } + + /** + * 発言データをアンロードする。 + */ + public void unload(){ + this.limitHour = 0; + this.limitMinute = 0; + this.loginName = null; + this.isFullOpen = false; + + this.isHot = false; + + this.topicList.clear(); + + return; + } + + /** + * 襲撃メッセージの有無を判定する。 + * 決着が付くまで非狼陣営には見えない。 + * 偽装GJでは狼にも見えない。 + * @return 襲撃メッセージがあればtrue + */ + public boolean hasAssaultTried(){ + for(Topic topic : this.topicList){ + if(topic instanceof Talk){ + Talk talk = (Talk) topic; + if(talk.getTalkCount() <= 0) return true; + }else if(topic instanceof SysEvent){ + SysEvent sysEvent = (SysEvent) topic; + SysEventType type = sysEvent.getSysEventType(); + if(type == SysEventType.ASSAULT) return true; + } + } + + return false; + } + + /** + * 処刑されたAvatarを返す。 + * @return 処刑されたAvatar。突然死などなんらかの理由でいない場合はnull + */ + public Avatar getExecutedAvatar(){ + Avatar result = null; + + for(Topic topic : getTopicList()){ + if( ! (topic instanceof SysEvent) ) continue; + SysEvent event = (SysEvent) topic; + result = event.getExecutedAvatar(); + if(result != null) break; + } + + return result; + } + + /** + * 投票に参加したAvatarの集合を返す。 + * @return 投票に参加したAvatarのSet + */ + public Set getVoterSet(){ + Set result = new HashSet(); + + for(Topic topic : getTopicList()){ + if( ! (topic instanceof SysEvent) ) continue; + SysEvent event = (SysEvent) topic; + result = event.getVoterSet(result); + } + + return result; + } + + /** + * 任意のタイプのシステムイベントを返す。 + * 複数存在する場合、返すのは最初の一つだけ。 + * @param type イベントタイプ + * @return システムイベント + */ + public SysEvent getTypedSysEvent(SysEventType type){ + for(Topic topic : getTopicList()){ + if( ! (topic instanceof SysEvent) ) continue; + SysEvent event = (SysEvent) topic; + if(event.getSysEventType() == type) return event; + } + + return null; + } + + /** + * Periodパース用ハンドラ。 + */ + private static class PeriodHandler extends HtmlAdapter{ + + private static final int TALKTYPE_NUM = TalkType.values().length; + + private final EntityConverter converter = + new EntityConverter(); + + private final Map countMap = + new HashMap(); + + private Period period = null; + + private TalkType talkType; + private Avatar avatar; + private int talkNo; + private String anchorId; + private int talkHour; + private int talkMinute; + private DecodedContent talkContent = null; + + private EventFamily eventFamily; + private SysEventType sysEventType; + private DecodedContent eventContent = null; + private final List avatarList = new LinkedList(); + private final List roleList = new LinkedList(); + private final List integerList = new LinkedList(); + private final List charseqList = + new LinkedList(); + + /** + * コンストラクタ。 + */ + public PeriodHandler(){ + super(); + return; + } + + /** + * パース結果を格納するPeriodを設定する。 + * @param period Period + */ + public void setPeriod(Period period){ + this.period = period; + return; + } + + /** + * 文字列断片からAvatarを得る。 + * 村に未登録のAvatarであればついでに登録される。 + * @param content 文字列 + * @param range 文字列内のAvatarフルネームを示す領域 + * @return Avatar + */ + private Avatar toAvatar(DecodedContent content, SeqRange range){ + Village village = this.period.getVillage(); + String fullName = this.converter + .convert(content, range) + .toString(); + Avatar result = village.getAvatar(fullName); + if(result == null){ + result = new Avatar(fullName); + village.addAvatar(result); + } + + return result; + } + + /** + * Avatar別、会話種ごとに発言回数をカウントする。 + * 1から始まる。 + * @param targetAvatar 対象Avatar + * @param targetType 対象会話種 + * @return カウント数 + */ + private int countUp(Avatar targetAvatar, TalkType targetType){ + int[] countArray = this.countMap.get(targetAvatar); + if(countArray == null){ + countArray = new int[TALKTYPE_NUM]; + this.countMap.put(targetAvatar, countArray); + } + int count = ++countArray[targetType.ordinal()]; + return count; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void startParse(DecodedContent content) + throws HtmlParseException{ + this.period.loginName = null; + this.period.topicList.clear(); + this.countMap.clear(); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param loginRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void loginName(DecodedContent content, SeqRange loginRange) + throws HtmlParseException{ + DecodedContent loginName = + this.converter.convert(content, loginRange); + + this.period.loginName = loginName.toString(); + + return; + } + + /** + * {@inheritDoc} + * @param type {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void pageType(PageType type) throws HtmlParseException{ + if(type != PageType.PERIOD_PAGE){ + throw new HtmlParseException( + "意図しないページを読み込もうとしました。"); + } + return; + } + + /** + * {@inheritDoc} + * @param month {@inheritDoc} + * @param day {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void commitTime(int month, int day, int hour, int minute) + throws HtmlParseException{ + this.period.limitHour = hour; + this.period.limitMinute = minute; + return; + } + + /** + * {@inheritDoc} + * 自分へのリンクが無いかチェックする。 + * 自分へのリンクが見つかればこのPeriodを非Hotにする。 + * 自分へのリンクがあるということは、 + * 今読んでるHTMLは別のPeriodのために書かれたものということ。 + * 考えられる原因は、HotだったPeriodがゲーム進行に従い + * Hotでなくなったこと。 + * @param content {@inheritDoc} + * @param anchorRange {@inheritDoc} + * @param periodType {@inheritDoc} + * @param day {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void periodLink(DecodedContent content, + SeqRange anchorRange, + PeriodType periodType, + int day) + throws HtmlParseException{ + + if(this.period.getType() != periodType) return; + + if( periodType == PeriodType.PROGRESS + && this.period.getDay() != day ){ + return; + } + + if( ! anchorRange.isValid() ) return; + + this.period.setHot(false); + + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void startTalk() throws HtmlParseException{ + this.talkType = null; + this.avatar = null; + this.talkNo = -1; + this.anchorId = null; + this.talkHour = -1; + this.talkMinute = -1; + this.talkContent = new DecodedContent(100 + 1); + + return; + } + + /** + * {@inheritDoc} + * @param type {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkType(TalkType type) + throws HtmlParseException{ + this.talkType = type; + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkAvatar(DecodedContent content, SeqRange avatarRange) + throws HtmlParseException{ + this.avatar = toAvatar(content, avatarRange); + return; + } + + /** + * {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkTime(int hour, int minute) + throws HtmlParseException{ + this.talkHour = hour; + this.talkMinute = minute; + return; + } + + /** + * {@inheritDoc} + * @param tno {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkNo(int tno) throws HtmlParseException{ + this.talkNo = tno; + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param idRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkId(DecodedContent content, SeqRange idRange) + throws HtmlParseException{ + this.anchorId = content.subSequence(idRange.getStartPos(), + idRange.getEndPos() ) + .toString(); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param textRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkText(DecodedContent content, SeqRange textRange) + throws HtmlParseException{ + this.converter.append(this.talkContent, content, textRange); + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void talkBreak() + throws HtmlParseException{ + this.talkContent.append('\n'); + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void endTalk() throws HtmlParseException{ + Talk talk = new Talk(this.period, + this.talkType, + this.avatar, + this.talkNo, + this.anchorId, + this.talkHour, this.talkMinute, + this.talkContent ); + + int count = countUp(this.avatar, this.talkType); + talk.setCount(count); + + this.period.addTopic(talk); + + this.talkType = null; + this.avatar = null; + this.talkNo = -1; + this.anchorId = null; + this.talkHour = -1; + this.talkMinute = -1; + this.talkContent = null; + + return; + } + + /** + * {@inheritDoc} + * @param family {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void startSysEvent(EventFamily family) + throws HtmlParseException{ + this.eventFamily = family; + this.sysEventType = null; + this.eventContent = new DecodedContent(); + this.avatarList.clear(); + this.roleList.clear(); + this.integerList.clear(); + this.charseqList.clear(); + return; + } + + /** + * {@inheritDoc} + * @param type {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventType(SysEventType type) + throws HtmlParseException{ + this.sysEventType = type; + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param contentRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventContent(DecodedContent content, + SeqRange contentRange) + throws HtmlParseException{ + this.converter.append(this.eventContent, content, contentRange); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param anchorRange {@inheritDoc} + * @param contentRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventContentAnchor(DecodedContent content, + SeqRange anchorRange, + SeqRange contentRange) + throws HtmlParseException{ + this.converter.append(this.eventContent, content, contentRange); + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventContentBreak() throws HtmlParseException{ + this.eventContent.append('\n'); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param entryNo {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventOnStage(DecodedContent content, + int entryNo, + SeqRange avatarRange) + throws HtmlParseException{ + Avatar newAvatar = toAvatar(content, avatarRange); + this.integerList.add(entryNo); + this.avatarList.add(newAvatar); + return; + } + + /** + * {@inheritDoc} + * @param role {@inheritDoc} + * @param num {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventOpenRole(GameRole role, int num) + throws HtmlParseException{ + this.roleList.add(role); + this.integerList.add(num); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventMurdered(DecodedContent content, + SeqRange avatarRange) + throws HtmlParseException{ + Avatar murdered = toAvatar(content, avatarRange); + this.avatarList.add(murdered); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventSurvivor(DecodedContent content, + SeqRange avatarRange) + throws HtmlParseException{ + Avatar survivor = toAvatar(content, avatarRange); + this.avatarList.add(survivor); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param voteByRange {@inheritDoc} + * @param voteToRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventCounting(DecodedContent content, + SeqRange voteByRange, + SeqRange voteToRange) + throws HtmlParseException{ + if(voteByRange.isValid()){ + Avatar voteBy = toAvatar(content, voteByRange); + this.avatarList.add(voteBy); + } + Avatar voteTo = toAvatar(content, voteToRange); + this.avatarList.add(voteTo); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param voteByRange {@inheritDoc} + * @param voteToRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventCounting2(DecodedContent content, + SeqRange voteByRange, + SeqRange voteToRange) + throws HtmlParseException{ + sysEventCounting(content, voteByRange, voteToRange); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventSuddenDeath(DecodedContent content, + SeqRange avatarRange) + throws HtmlParseException{ + Avatar suddenDeath = toAvatar(content, avatarRange); + this.avatarList.add(suddenDeath); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @param anchorRange {@inheritDoc} + * @param loginRange {@inheritDoc} + * @param isLiving {@inheritDoc} + * @param role {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventPlayerList(DecodedContent content, + SeqRange avatarRange, + SeqRange anchorRange, + SeqRange loginRange, + boolean isLiving, + GameRole role ) + throws HtmlParseException{ + Avatar who = toAvatar(content, avatarRange); + + CharSequence anchor; + if(anchorRange.isValid()){ + anchor = this.converter.convert(content, anchorRange); + }else{ + anchor = ""; + } + CharSequence account = this.converter + .convert(content, loginRange); + + Integer liveOrDead; + if(isLiving) liveOrDead = Integer.valueOf(1); + else liveOrDead = Integer.valueOf(0); + + this.avatarList.add(who); + this.charseqList.add(anchor); + this.charseqList.add(account); + this.integerList.add(liveOrDead); + this.roleList.add(role); + + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @param votes {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventExecution(DecodedContent content, + SeqRange avatarRange, + int votes ) + throws HtmlParseException{ + Avatar who = toAvatar(content, avatarRange); + + this.avatarList.add(who); + this.integerList.add(votes); + + return; + } + + /** + * {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @param minLimit {@inheritDoc} + * @param maxLimit {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventAskEntry(int hour, int minute, + int minLimit, int maxLimit) + throws HtmlParseException{ + this.integerList.add(hour * 60 + minute); + this.integerList.add(minLimit); + this.integerList.add(maxLimit); + return; + } + + /** + * {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventAskCommit(int hour, int minute) + throws HtmlParseException{ + this.integerList.add(hour * 60 + minute); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param avatarRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventNoComment(DecodedContent content, + SeqRange avatarRange) + throws HtmlParseException{ + Avatar noComAvatar = toAvatar(content, avatarRange); + this.avatarList.add(noComAvatar); + return; + } + + /** + * {@inheritDoc} + * @param winner {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventStayEpilogue(Team winner, int hour, int minute) + throws HtmlParseException{ + GameRole role = null; + + switch(winner){ + case VILLAGE: role = GameRole.INNOCENT; break; + case WOLF: role = GameRole.WOLF; break; + case HAMSTER: role = GameRole.HAMSTER; break; + default: assert false; break; + } + + this.roleList.add(role); + this.integerList.add(hour * 60 + minute); + + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param guardByRange {@inheritDoc} + * @param guardToRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventGuard(DecodedContent content, + SeqRange guardByRange, + SeqRange guardToRange) + throws HtmlParseException{ + Avatar guardBy = toAvatar(content, guardByRange); + Avatar guardTo = toAvatar(content, guardToRange); + this.avatarList.add(guardBy); + this.avatarList.add(guardTo); + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param judgeByRange {@inheritDoc} + * @param judgeToRange {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void sysEventJudge(DecodedContent content, + SeqRange judgeByRange, + SeqRange judgeToRange) + throws HtmlParseException{ + Avatar judgeBy = toAvatar(content, judgeByRange); + Avatar judgeTo = toAvatar(content, judgeToRange); + this.avatarList.add(judgeBy); + this.avatarList.add(judgeTo); + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void endSysEvent() throws HtmlParseException{ + SysEvent event = new SysEvent(); + event.setEventFamily(this.eventFamily); + event.setSysEventType(this.sysEventType); + event.setContent(this.eventContent); + event.addAvatarList(this.avatarList); + event.addRoleList(this.roleList); + event.addIntegerList(this.integerList); + event.addCharSequenceList(this.charseqList); + + this.period.addTopic(event); + + if( this.sysEventType == SysEventType.MURDERED + || this.sysEventType == SysEventType.NOMURDER ){ + for(Topic topic : this.period.topicList){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + if(talk.getTalkType() != TalkType.WOLFONLY) continue; + if( ! StringUtils + .isTerminated(talk.getDialog(), + "!\u0020今日がお前の命日だ!") ){ + continue; + } + talk.setCount(-1); + this.countMap.clear(); + } + } + + this.eventFamily = null; + this.sysEventType = null; + this.eventContent = null; + this.avatarList.clear(); + this.roleList.clear(); + this.integerList.clear(); + this.charseqList.clear(); + + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void endParse() throws HtmlParseException{ + return; + } + + // TODO 村名のチェックは不要か? + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/PeriodView.java b/src/main/java/jp/sourceforge/jindolf/PeriodView.java index 3a9a7e8..47b2545 100644 --- a/src/main/java/jp/sourceforge/jindolf/PeriodView.java +++ b/src/main/java/jp/sourceforge/jindolf/PeriodView.java @@ -1,382 +1,382 @@ -/* - * period viewer - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.Rectangle; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.DefaultComboBoxModel; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollBar; -import javax.swing.JScrollPane; -import javax.swing.JViewport; -import javax.swing.ScrollPaneConstants; -import javax.swing.border.Border; -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * 発言ブラウザを内包するPeriodビューワ。 - */ -@SuppressWarnings("serial") -public class PeriodView extends JPanel implements ItemListener{ - - private static final Color COLOR_SELECT = new Color(0xffff80); - private static final Color COLOR_NORMALBG = Color.BLACK; - private static final Color COLOR_SIMPLEBG = Color.WHITE; - - private Period period; - - private final Discussion discussion; - private final JScrollPane scroller = new JScrollPane(); - private final JLabel caption = new JLabel(); - private final JLabel limit = new JLabel(); - private final JComboBox talkSelector = new JComboBox(); - private final DefaultComboBoxModel model = new DefaultComboBoxModel(); - - private DialogPref dialogPref = new DialogPref(); - - /** - * 発言ブラウザを内包するPeriodビューワを生成する。 - * @param period 日 - */ - public PeriodView(Period period){ - super(); - - this.period = period; - - this.talkSelector.setEditable(false); - this.talkSelector.setMaximumRowCount(20); - this.talkSelector.setModel(this.model); - this.talkSelector.setRenderer(new AnchorRenderer()); - this.talkSelector.addItemListener(this); - - this.discussion = new Discussion(); - Border border = - BorderFactory.createMatteBorder(15, 15, 15, 15, Color.BLACK); - this.discussion.setBorder(border); - this.discussion.setPeriod(this.period); - - JViewport viewPort = this.scroller.getViewport(); - viewPort.setBackground(Color.BLACK); - viewPort.setView(this.discussion); - - this.scroller.setHorizontalScrollBarPolicy( - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - this.scroller.setVerticalScrollBarPolicy( - ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - design(); - - setColorDesign(); - - return; - } - - /** - * デザインを行う。 - */ - private void design(){ - LayoutManager layout; - - JPanel topPanel = new JPanel(); - layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - topPanel.setLayout(layout); - constraints.insets = new Insets(1, 3, 1, 3); - constraints.weightx = 1.0; - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.NONE; - topPanel.add(this.caption, constraints); - constraints.weightx = 0.0; - constraints.anchor = GridBagConstraints.EAST; - topPanel.add(this.limit, constraints); - constraints.weightx = 0.0; - topPanel.add(this.talkSelector, constraints); - - layout = new BorderLayout(); - setLayout(layout); - add(topPanel, BorderLayout.NORTH); - add(this.scroller, BorderLayout.CENTER); - - return; - } - - /** - * 配色を設定する。 - */ - private void setColorDesign(){ - Color bgColor; - - if(this.dialogPref.isSimpleMode()){ - bgColor = COLOR_SIMPLEBG; - }else{ - bgColor = COLOR_NORMALBG; - } - - JViewport viewPort = this.scroller.getViewport(); - viewPort.setBackground(bgColor); - - Border border = - BorderFactory.createMatteBorder(15, 15, 15, 15, bgColor); - this.discussion.setBorder(border); - - repaint(); - - return; - } - - /** - * Periodを更新する。 - * 古いPeriodの表示内容は消える。 - * 新しいPeriodの表示内容はまだ反映されない。 - * @param period 新しいPeriod - */ - public void setPeriod(Period period){ - this.discussion.setPeriod(period); - - this.period = period; - - updateTopPanel(); - - return; - } - - /** - * 現在のPeriodを返す。 - * @return 現在のPeriod - */ - public Period getPeriod(){ - return this.discussion.getPeriod(); - } - - /** - * 上部のGUI(村名、発言一覧)を、Periodの状態に合わせて更新する。 - */ - private void updateTopPanel(){ - if(this.period == null){ - this.caption.setText(""); - this.limit.setText(""); - this.model.removeAllElements(); - return; - } - - Village village = this.period.getVillage(); - String villageName = village.getVillageName(); - - String dayCaption = this.period.getCaption(); - String limitCaption = this.period.getLimit(); - String account = this.period.getLoginName(); - - String loginout; - if(this.period.isFullOpen()){ - loginout = ""; - }else if(account != null){ - loginout = " (ログイン中)"; - }else{ - loginout = " (ログアウト中)"; - } - - String info = villageName + "村 " + dayCaption + loginout; - this.caption.setText(info); - this.limit.setText("更新時刻 " + limitCaption); - - this.model.removeAllElements(); - this.model.addElement(" "); - List topicList = this.period.getTopicList(); - for(Topic topic : topicList){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - if(talk.getTalkCount() <= 0) continue; - this.model.addElement(talk); - } - - return; - } - - /** - * フィルタを適用してPeriodの内容を出力する。 - */ - public void showTopics(){ - Period newPeriod = this.discussion.getPeriod(); - setPeriod(newPeriod); - return; - } - - /** - * フォント描画設定を変更する。 - * @param fontInfo フォント設定 - */ - // TODO スクロール位置の復元 - public void setFontInfo(FontInfo fontInfo){ - this.discussion.setFontInfo(fontInfo); - - revalidate(); - repaint(); - - return; - } - - /** - * 発言表示設定を更新する。 - * @param pref 表示設定 - */ - public void setDialogPref(DialogPref pref){ - this.dialogPref = pref; - this.discussion.setDialogPref(this.dialogPref); - - setColorDesign(); - revalidate(); - repaint(); - - return; - } - - /** - * ビューポート内の発言ブラウザを返す。 - * @return 内部ブラウザ - */ - public Discussion getDiscussion(){ - return this.discussion; - } - - /** - * スクロール位置を返す。 - * @return スクロール位置 - */ - public int getVerticalPosition(){ - JScrollBar vt = this.scroller.getVerticalScrollBar(); - int pos = vt.getValue(); - return pos; - } - - /** - * スクロール位置を設定する。 - * @param pos スクロール位置 - */ - public void setVerticalPosition(int pos){ - JScrollBar vt = this.scroller.getVerticalScrollBar(); - vt.setValue(pos); - return; - } - - /** - * {@inheritDoc} - * コンボボックス操作のリスナ。 - * @param event コンボボックス操作イベント {@inheritDoc} - */ - @Override - public void itemStateChanged(ItemEvent event){ - if(event.getStateChange() != ItemEvent.SELECTED) return; - - Object selected = this.talkSelector.getSelectedItem(); - if( ! (selected instanceof Talk) ) return; - Talk talk = (Talk) selected; - - scrollToTalk(talk); - - return; - } - - /** - * 任意の発言が表示されるようスクロールする。 - * @param talk 発言 - */ - public void scrollToTalk(Talk talk){ - if(talk == null) return; - if(talk.getPeriod() != this.period) return; - - Rectangle rect = this.discussion.getTalkBounds(talk); - if(rect == null) return; - - Rectangle showRect = new Rectangle(rect); - showRect.y -= 15; - showRect.height = this.scroller.getHeight(); - this.discussion.scrollRectToVisible(showRect); - - return; - } - - /** - * Talkをアイテムに持つコンボボックス用のセル描画。 - */ - private static class AnchorRenderer extends DefaultListCellRenderer{ - - /** - * コンストラクタ。 - */ - public AnchorRenderer(){ - super(); - return; - } - - /** - * {@inheritDoc} - * Talkのアンカー表記と発言者名を描画する。 - * @param list {@inheritDoc} - * @param value {@inheritDoc} - * @param index {@inheritDoc} - * @param isSelected {@inheritDoc} - * @param cellHasFocus {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Component getListCellRendererComponent( - JList list, - Object value, - int index, - boolean isSelected, - boolean cellHasFocus ){ - Talk talk = null; - Object newValue = value; - if(value instanceof Talk){ - talk = (Talk) value; - newValue = new StringBuilder() - .append(talk.getAnchorNotation()) - .append(' ') - .append(talk.getAvatar().getName()) - .toString(); - } - - Component superResult = - super.getListCellRendererComponent(list, - newValue, - index, - isSelected, - cellHasFocus); - - if(talk != null){ - Color bgColor = null; - if(isSelected){ - bgColor = COLOR_SELECT; - }else{ - TalkType type = talk.getTalkType(); - bgColor = TalkDraw.getTypedColor(type); - } - superResult.setForeground(Color.BLACK); - superResult.setBackground(bgColor); - } - - return superResult; - } - } - - // TODO フィルタ中の発言をプルダウン選択したらどうあるべきか? -} +/* + * period viewer + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JViewport; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.Border; +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * 発言ブラウザを内包するPeriodビューワ。 + */ +@SuppressWarnings("serial") +public class PeriodView extends JPanel implements ItemListener{ + + private static final Color COLOR_SELECT = new Color(0xffff80); + private static final Color COLOR_NORMALBG = Color.BLACK; + private static final Color COLOR_SIMPLEBG = Color.WHITE; + + private Period period; + + private final Discussion discussion; + private final JScrollPane scroller = new JScrollPane(); + private final JLabel caption = new JLabel(); + private final JLabel limit = new JLabel(); + private final JComboBox talkSelector = new JComboBox(); + private final DefaultComboBoxModel model = new DefaultComboBoxModel(); + + private DialogPref dialogPref = new DialogPref(); + + /** + * 発言ブラウザを内包するPeriodビューワを生成する。 + * @param period 日 + */ + public PeriodView(Period period){ + super(); + + this.period = period; + + this.talkSelector.setEditable(false); + this.talkSelector.setMaximumRowCount(20); + this.talkSelector.setModel(this.model); + this.talkSelector.setRenderer(new AnchorRenderer()); + this.talkSelector.addItemListener(this); + + this.discussion = new Discussion(); + Border border = + BorderFactory.createMatteBorder(15, 15, 15, 15, Color.BLACK); + this.discussion.setBorder(border); + this.discussion.setPeriod(this.period); + + JViewport viewPort = this.scroller.getViewport(); + viewPort.setBackground(Color.BLACK); + viewPort.setView(this.discussion); + + this.scroller.setHorizontalScrollBarPolicy( + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + this.scroller.setVerticalScrollBarPolicy( + ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + design(); + + setColorDesign(); + + return; + } + + /** + * デザインを行う。 + */ + private void design(){ + LayoutManager layout; + + JPanel topPanel = new JPanel(); + layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + topPanel.setLayout(layout); + constraints.insets = new Insets(1, 3, 1, 3); + constraints.weightx = 1.0; + constraints.anchor = GridBagConstraints.WEST; + constraints.fill = GridBagConstraints.NONE; + topPanel.add(this.caption, constraints); + constraints.weightx = 0.0; + constraints.anchor = GridBagConstraints.EAST; + topPanel.add(this.limit, constraints); + constraints.weightx = 0.0; + topPanel.add(this.talkSelector, constraints); + + layout = new BorderLayout(); + setLayout(layout); + add(topPanel, BorderLayout.NORTH); + add(this.scroller, BorderLayout.CENTER); + + return; + } + + /** + * 配色を設定する。 + */ + private void setColorDesign(){ + Color bgColor; + + if(this.dialogPref.isSimpleMode()){ + bgColor = COLOR_SIMPLEBG; + }else{ + bgColor = COLOR_NORMALBG; + } + + JViewport viewPort = this.scroller.getViewport(); + viewPort.setBackground(bgColor); + + Border border = + BorderFactory.createMatteBorder(15, 15, 15, 15, bgColor); + this.discussion.setBorder(border); + + repaint(); + + return; + } + + /** + * Periodを更新する。 + * 古いPeriodの表示内容は消える。 + * 新しいPeriodの表示内容はまだ反映されない。 + * @param period 新しいPeriod + */ + public void setPeriod(Period period){ + this.discussion.setPeriod(period); + + this.period = period; + + updateTopPanel(); + + return; + } + + /** + * 現在のPeriodを返す。 + * @return 現在のPeriod + */ + public Period getPeriod(){ + return this.discussion.getPeriod(); + } + + /** + * 上部のGUI(村名、発言一覧)を、Periodの状態に合わせて更新する。 + */ + private void updateTopPanel(){ + if(this.period == null){ + this.caption.setText(""); + this.limit.setText(""); + this.model.removeAllElements(); + return; + } + + Village village = this.period.getVillage(); + String villageName = village.getVillageName(); + + String dayCaption = this.period.getCaption(); + String limitCaption = this.period.getLimit(); + String account = this.period.getLoginName(); + + String loginout; + if(this.period.isFullOpen()){ + loginout = ""; + }else if(account != null){ + loginout = " (ログイン中)"; + }else{ + loginout = " (ログアウト中)"; + } + + String info = villageName + "村 " + dayCaption + loginout; + this.caption.setText(info); + this.limit.setText("更新時刻 " + limitCaption); + + this.model.removeAllElements(); + this.model.addElement(" "); + List topicList = this.period.getTopicList(); + for(Topic topic : topicList){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + if(talk.getTalkCount() <= 0) continue; + this.model.addElement(talk); + } + + return; + } + + /** + * フィルタを適用してPeriodの内容を出力する。 + */ + public void showTopics(){ + Period newPeriod = this.discussion.getPeriod(); + setPeriod(newPeriod); + return; + } + + /** + * フォント描画設定を変更する。 + * @param fontInfo フォント設定 + */ + // TODO スクロール位置の復元 + public void setFontInfo(FontInfo fontInfo){ + this.discussion.setFontInfo(fontInfo); + + revalidate(); + repaint(); + + return; + } + + /** + * 発言表示設定を更新する。 + * @param pref 表示設定 + */ + public void setDialogPref(DialogPref pref){ + this.dialogPref = pref; + this.discussion.setDialogPref(this.dialogPref); + + setColorDesign(); + revalidate(); + repaint(); + + return; + } + + /** + * ビューポート内の発言ブラウザを返す。 + * @return 内部ブラウザ + */ + public Discussion getDiscussion(){ + return this.discussion; + } + + /** + * スクロール位置を返す。 + * @return スクロール位置 + */ + public int getVerticalPosition(){ + JScrollBar vt = this.scroller.getVerticalScrollBar(); + int pos = vt.getValue(); + return pos; + } + + /** + * スクロール位置を設定する。 + * @param pos スクロール位置 + */ + public void setVerticalPosition(int pos){ + JScrollBar vt = this.scroller.getVerticalScrollBar(); + vt.setValue(pos); + return; + } + + /** + * {@inheritDoc} + * コンボボックス操作のリスナ。 + * @param event コンボボックス操作イベント {@inheritDoc} + */ + @Override + public void itemStateChanged(ItemEvent event){ + if(event.getStateChange() != ItemEvent.SELECTED) return; + + Object selected = this.talkSelector.getSelectedItem(); + if( ! (selected instanceof Talk) ) return; + Talk talk = (Talk) selected; + + scrollToTalk(talk); + + return; + } + + /** + * 任意の発言が表示されるようスクロールする。 + * @param talk 発言 + */ + public void scrollToTalk(Talk talk){ + if(talk == null) return; + if(talk.getPeriod() != this.period) return; + + Rectangle rect = this.discussion.getTalkBounds(talk); + if(rect == null) return; + + Rectangle showRect = new Rectangle(rect); + showRect.y -= 15; + showRect.height = this.scroller.getHeight(); + this.discussion.scrollRectToVisible(showRect); + + return; + } + + /** + * Talkをアイテムに持つコンボボックス用のセル描画。 + */ + private static class AnchorRenderer extends DefaultListCellRenderer{ + + /** + * コンストラクタ。 + */ + public AnchorRenderer(){ + super(); + return; + } + + /** + * {@inheritDoc} + * Talkのアンカー表記と発言者名を描画する。 + * @param list {@inheritDoc} + * @param value {@inheritDoc} + * @param index {@inheritDoc} + * @param isSelected {@inheritDoc} + * @param cellHasFocus {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus ){ + Talk talk = null; + Object newValue = value; + if(value instanceof Talk){ + talk = (Talk) value; + newValue = new StringBuilder() + .append(talk.getAnchorNotation()) + .append(' ') + .append(talk.getAvatar().getName()) + .toString(); + } + + Component superResult = + super.getListCellRendererComponent(list, + newValue, + index, + isSelected, + cellHasFocus); + + if(talk != null){ + Color bgColor = null; + if(isSelected){ + bgColor = COLOR_SELECT; + }else{ + TalkType type = talk.getTalkType(); + bgColor = TalkDraw.getTypedColor(type); + } + superResult.setForeground(Color.BLACK); + superResult.setBackground(bgColor); + } + + return superResult; + } + } + + // TODO フィルタ中の発言をプルダウン選択したらどうあるべきか? +} diff --git a/src/main/java/jp/sourceforge/jindolf/PileHandler.java b/src/main/java/jp/sourceforge/jindolf/PileHandler.java index 27eff50..9524b98 100644 --- a/src/main/java/jp/sourceforge/jindolf/PileHandler.java +++ b/src/main/java/jp/sourceforge/jindolf/PileHandler.java @@ -1,84 +1,84 @@ -/* - * Dummy logging handler - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.LinkedList; -import java.util.List; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -/** - * なにもしないロギングハンドラ。 - * あとからなにがロギングされたのか一括して出力することができる。 - */ -public class PileHandler extends Handler{ - - private final List logList = new LinkedList(); - - /** - * ロギングハンドラを生成する。 - */ - public PileHandler(){ - super(); - return; - } - - /** - * {@inheritDoc} - * ログを内部に溜め込む。 - * @param record {@inheritDoc} - */ - @Override - public void publish(LogRecord record){ - if( ! isLoggable(record) ){ - return; - } - this.logList.add(record); - return; - } - - /** - * {@inheritDoc} - * (何もしない)。 - */ - @Override - public void flush(){ - return; - } - - /** - * {@inheritDoc} - */ - @Override - public void close(){ - setLevel(Level.OFF); - flush(); - return; - } - - /** - * 他のハンドラへ蓄積したログをまとめて出力する。 - * @param handler 他のハンドラ - */ - public void delegate(Handler handler){ - if(handler == this) return; - - close(); - - for(LogRecord record : this.logList){ - handler.publish(record); - } - - handler.flush(); - this.logList.clear(); - - return; - } - -} +/* + * Dummy logging handler + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * なにもしないロギングハンドラ。 + * あとからなにがロギングされたのか一括して出力することができる。 + */ +public class PileHandler extends Handler{ + + private final List logList = new LinkedList(); + + /** + * ロギングハンドラを生成する。 + */ + public PileHandler(){ + super(); + return; + } + + /** + * {@inheritDoc} + * ログを内部に溜め込む。 + * @param record {@inheritDoc} + */ + @Override + public void publish(LogRecord record){ + if( ! isLoggable(record) ){ + return; + } + this.logList.add(record); + return; + } + + /** + * {@inheritDoc} + * (何もしない)。 + */ + @Override + public void flush(){ + return; + } + + /** + * {@inheritDoc} + */ + @Override + public void close(){ + setLevel(Level.OFF); + flush(); + return; + } + + /** + * 他のハンドラへ蓄積したログをまとめて出力する。 + * @param handler 他のハンドラ + */ + public void delegate(Handler handler){ + if(handler == this) return; + + close(); + + for(LogRecord record : this.logList){ + handler.publish(record); + } + + handler.flush(); + this.logList.clear(); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Player.java b/src/main/java/jp/sourceforge/jindolf/Player.java index 9dc1c79..9b1380d 100644 --- a/src/main/java/jp/sourceforge/jindolf/Player.java +++ b/src/main/java/jp/sourceforge/jindolf/Player.java @@ -1,185 +1,185 @@ -/* - * Player - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import jp.sourceforge.jindolf.corelib.Destiny; -import jp.sourceforge.jindolf.corelib.GameRole; - -/** - * プレイヤーに関する情報の集約。 - */ -public class Player{ - - private Avatar avatar; - private GameRole role; - private Destiny destiny; - private int obitDay = -1; - private String idName; - private String urlText; - private int entryNo = -1; - - /** - * コンストラクタ。 - */ - public Player(){ - super(); - return; - } - - /** - * Avatar名を返す。 - * @return Avatar名 - */ - @Override - public String toString(){ - if(this.avatar == null) return "?"; - return this.avatar.toString(); - } - - /** - * Avatarを取得する。 - * @return Avatar - */ - public Avatar getAvatar(){ - return avatar; - } - - /** - * Avatarをセットする。 - * @param avatar Avatar - */ - public void setAvatar(Avatar avatar){ - this.avatar = avatar; - return; - } - - /** - * 役割を取得する。 - * @return 役割 - */ - public GameRole getRole(){ - return role; - } - - /** - * 役割を設定する。 - * @param role 役割 - */ - public void setRole(GameRole role){ - this.role = role; - return; - } - - /** - * 運命を取得する。 - * @return 運命 - */ - public Destiny getDestiny(){ - return destiny; - } - - /** - * 運命を設定する。 - * @param destiny 運命 - */ - public void setDestiny(Destiny destiny){ - this.destiny = destiny; - return; - } - - /** - * 命日を取得する。 - * @return プロローグを0とする命日。死んでなければ負。 - */ - public int getObitDay(){ - return obitDay; - } - - /** - * 命日を設定する。 - * @param obitDay プロローグを0とする命日。死んでなければ負。 - */ - public void setObitDay(int obitDay){ - this.obitDay = obitDay; - return; - } - - /** - * プレイヤーIDを取得する。 - * @return プレイヤーID - */ - public String getIdName(){ - return idName; - } - - /** - * プレイヤーIDを設定する。 - * @param idName プレイヤーID - */ - public void setIdName(String idName){ - this.idName = idName; - return; - } - - /** - * URL文字列を取得する。 - * 必ずしもURLを満たす文字列ではないかもしれない。 - * @return URL文字列 - */ - public String getUrlText(){ - return urlText; - } - - /** - * URL文字列を設定する。 - * @param urlText URL文字列 - */ - public void setUrlText(String urlText){ - this.urlText = urlText; - return; - } - - /** - * エントリーNo.を取得する。 - * @return エントリーNo. - */ - public int getEntryNo(){ - return entryNo; - } - - /** - * エントリーNo.を設定する。 - * @param entryNo エントリーNo. - */ - public void setEntryNo(int entryNo){ - this.entryNo = entryNo; - return; - } - - /** - * プレイヤーの運命を文字列化する。 - * @return 文字列化した運命 - */ - public String getDestinyMessage(){ - StringBuilder destinyMessage = new StringBuilder(); - - switch(this.destiny){ - case ALIVE: - assert this.obitDay < 0; - destinyMessage.append("最後まで生存"); - break; - default: - assert this.obitDay >= 0; - destinyMessage.append(this.obitDay).append("日目に"); - destinyMessage.append(this.destiny.getMessage()); - } - - return destinyMessage.toString(); - } - -} +/* + * Player + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import jp.sourceforge.jindolf.corelib.Destiny; +import jp.sourceforge.jindolf.corelib.GameRole; + +/** + * プレイヤーに関する情報の集約。 + */ +public class Player{ + + private Avatar avatar; + private GameRole role; + private Destiny destiny; + private int obitDay = -1; + private String idName; + private String urlText; + private int entryNo = -1; + + /** + * コンストラクタ。 + */ + public Player(){ + super(); + return; + } + + /** + * Avatar名を返す。 + * @return Avatar名 + */ + @Override + public String toString(){ + if(this.avatar == null) return "?"; + return this.avatar.toString(); + } + + /** + * Avatarを取得する。 + * @return Avatar + */ + public Avatar getAvatar(){ + return avatar; + } + + /** + * Avatarをセットする。 + * @param avatar Avatar + */ + public void setAvatar(Avatar avatar){ + this.avatar = avatar; + return; + } + + /** + * 役割を取得する。 + * @return 役割 + */ + public GameRole getRole(){ + return role; + } + + /** + * 役割を設定する。 + * @param role 役割 + */ + public void setRole(GameRole role){ + this.role = role; + return; + } + + /** + * 運命を取得する。 + * @return 運命 + */ + public Destiny getDestiny(){ + return destiny; + } + + /** + * 運命を設定する。 + * @param destiny 運命 + */ + public void setDestiny(Destiny destiny){ + this.destiny = destiny; + return; + } + + /** + * 命日を取得する。 + * @return プロローグを0とする命日。死んでなければ負。 + */ + public int getObitDay(){ + return obitDay; + } + + /** + * 命日を設定する。 + * @param obitDay プロローグを0とする命日。死んでなければ負。 + */ + public void setObitDay(int obitDay){ + this.obitDay = obitDay; + return; + } + + /** + * プレイヤーIDを取得する。 + * @return プレイヤーID + */ + public String getIdName(){ + return idName; + } + + /** + * プレイヤーIDを設定する。 + * @param idName プレイヤーID + */ + public void setIdName(String idName){ + this.idName = idName; + return; + } + + /** + * URL文字列を取得する。 + * 必ずしもURLを満たす文字列ではないかもしれない。 + * @return URL文字列 + */ + public String getUrlText(){ + return urlText; + } + + /** + * URL文字列を設定する。 + * @param urlText URL文字列 + */ + public void setUrlText(String urlText){ + this.urlText = urlText; + return; + } + + /** + * エントリーNo.を取得する。 + * @return エントリーNo. + */ + public int getEntryNo(){ + return entryNo; + } + + /** + * エントリーNo.を設定する。 + * @param entryNo エントリーNo. + */ + public void setEntryNo(int entryNo){ + this.entryNo = entryNo; + return; + } + + /** + * プレイヤーの運命を文字列化する。 + * @return 文字列化した運命 + */ + public String getDestinyMessage(){ + StringBuilder destinyMessage = new StringBuilder(); + + switch(this.destiny){ + case ALIVE: + assert this.obitDay < 0; + destinyMessage.append("最後まで生存"); + break; + default: + assert this.obitDay >= 0; + destinyMessage.append(this.obitDay).append("日目に"); + destinyMessage.append(this.destiny.getMessage()); + } + + return destinyMessage.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ProxyChooser.java b/src/main/java/jp/sourceforge/jindolf/ProxyChooser.java index 740eff4..18e1cc3 100644 --- a/src/main/java/jp/sourceforge/jindolf/ProxyChooser.java +++ b/src/main/java/jp/sourceforge/jindolf/ProxyChooser.java @@ -1,309 +1,309 @@ -/* - * proxy chooser - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.awt.Container; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.net.InetSocketAddress; -import java.net.Proxy; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.ButtonModel; -import javax.swing.ComboBoxEditor; -import javax.swing.ComboBoxModel; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JTextField; -import javax.swing.border.Border; - -/** - * プロクシサーバ選択画面。 - * @see RFC2616 - * @see RFC1928 - */ -@SuppressWarnings("serial") -public class ProxyChooser extends JPanel implements ItemListener{ - - private final JRadioButton isDirect = - new JRadioButton("直接接続"); - private final JRadioButton isHttp = - new JRadioButton("HTTP-Proxy (RFC2616)"); - private final JRadioButton isSocks = - new JRadioButton("SOCKS (RFC1928)"); - private final ButtonGroup buttonGroup = new ButtonGroup(); - - private final JTextField hostname = new JTextField(); - private final JComboBox port = new JComboBox(); - private final JComponent serverInfo = buildServerPanel(); - - - /** - * コンストラクタ。 - */ - public ProxyChooser(){ - this(ProxyInfo.DEFAULT); - return; - } - - /** - * コンストラクタ。 - * @param proxyInfo プロクシ設定 - */ - public ProxyChooser(ProxyInfo proxyInfo){ - super(); - - this.buttonGroup.add(this.isDirect); - this.buttonGroup.add(this.isHttp); - this.buttonGroup.add(this.isSocks); - this.isDirect.addItemListener(this); - this.isHttp .addItemListener(this); - this.isSocks .addItemListener(this); - - this.hostname.setComponentPopupMenu(new TextPopup()); - Monodizer.monodize(this.hostname); - GUIUtils.addMargin(this.hostname, 1, 4, 1, 4); - - this.port.setModel(buildPortRecommender()); - this.port.setEditable(true); - ComboBoxEditor editor = this.port.getEditor(); - Component comp = editor.getEditorComponent(); - GUIUtils.addMargin(comp, 1, 4, 1, 4); - if(comp instanceof JComponent){ - ((JComponent)comp).setComponentPopupMenu(new TextPopup()); - } - Monodizer.monodize(this.port); - - design(this); - - setProxyInfo(proxyInfo); - - return; - } - - - /** - * ポート番号選択肢を生成する。 - * @return ポート番号選択肢 - */ - private static ComboBoxModel buildPortRecommender(){ - DefaultComboBoxModel model = new DefaultComboBoxModel(); - model.addElement("80"); - model.addElement("1080"); - model.addElement("3128"); - model.addElement("8000"); - model.addElement("8080"); - model.addElement("10080"); - return model; - } - - /** - * レイアウトを行う。 - * @param content コンテナ - */ - private void design(Container content){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - content.setLayout(layout); - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.BOTH; - constraints.anchor = GridBagConstraints.NORTHWEST; - - content.add(this.isDirect, constraints); - content.add(this.isHttp, constraints); - content.add(this.isSocks, constraints); - content.add(this.serverInfo, constraints); - - constraints.weighty = 1.0; - content.add(new JPanel(), constraints); - - return; - } - - /** - * サーバ情報パネルを生成する。 - * @return サーバ情報パネル - */ - private JComponent buildServerPanel(){ - JPanel panel = new JPanel(); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.weightx = 0.0; - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - panel.add(new JLabel("アドレス:"), constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.NORTHWEST; - panel.add(this.hostname, constraints); - - constraints.weightx = 0.0; - constraints.gridwidth = 1; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - panel.add(new JLabel("ポート:"), constraints); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.NORTHWEST; - panel.add(this.port, constraints); - - String warn = - "※ このプロクシサーバは本当に信頼できますか?
" - + "あなたが人狼BBSにログインしている間、
" - + "あなたのパスワードは平文状態のまま
" - + "このプロクシサーバ上を何度も通過します。"; - panel.add(new JLabel(warn), constraints); - - Border border = BorderFactory.createTitledBorder("プロクシサーバ情報"); - panel.setBorder(border); - - return panel; - } - - /** - * プロクシの種別を返す。 - * @return プロクシ種別 - */ - protected Proxy.Type getType(){ - if(this.isDirect.isSelected()) return Proxy.Type.DIRECT; - if(this.isHttp .isSelected()) return Proxy.Type.HTTP; - if(this.isSocks .isSelected()) return Proxy.Type.SOCKS; - - return Proxy.Type.DIRECT; - } - - /** - * サーバ名を返す。 - * プロクシのホスト名として妥当なものか否かは検証されない。 - * @return サーバ名 - */ - protected String getHostName(){ - String hostText = this.hostname.getText(); - hostText = hostText.trim(); - return hostText; - } - - /** - * ポート番号を返す。 - * 番号の体をなしていなければゼロを返す。 - * @return ポート番号 - */ - protected int getPort(){ - Object portItem = this.port.getEditor().getItem(); - String portText = portItem.toString(); - portText = portText.trim(); - - int result; - try{ - result = Integer.parseInt(portText); - }catch(NumberFormatException e){ - return 0; - } - - if(result < 0) result = 0; - if(65535 < result) result = 65535; - - return result; - } - - /** - * サーバへのソケットアドレスを生成する。 - * @return ソケットアドレス - */ - protected InetSocketAddress getInetSocketAddress(){ - return InetSocketAddress.createUnresolved(getHostName(), getPort()); - } - - /** - * プロクシ設定を返す。 - * @return プロクシ設定 - */ - public ProxyInfo getProxyInfo(){ - Proxy.Type type = getType(); - return new ProxyInfo(type, getInetSocketAddress()); - } - - /** - * プロクシ設定を設定する。 - * UIに反映される。 - * @param proxyInfo プロクシ設定。nullなら直接接続と解釈される。 - */ - public final void setProxyInfo(ProxyInfo proxyInfo){ - Proxy.Type type; - InetSocketAddress addr; - if(proxyInfo == null){ - type = Proxy.Type.DIRECT; - addr = ProxyInfo.IP4SOCKET_NOBODY; - }else{ - type = proxyInfo.getType(); - addr = proxyInfo.address(); - } - - ButtonModel model; - switch(type){ - case DIRECT: model = this.isDirect.getModel(); break; - case HTTP: model = this.isHttp .getModel(); break; - case SOCKS: model = this.isSocks .getModel(); break; - default: model = this.isDirect.getModel(); break; - } - this.buttonGroup.setSelected(model, true); - - this.hostname.setText(addr.getHostName()); - this.port.getEditor() - .setItem(Integer.valueOf(addr.getPort())); - - return; - } - - /** - * プロクシ種別ボタン操作の受信。 - * @param event ボタン操作イベント - */ - public void itemStateChanged(ItemEvent event){ - Object source = event.getSource(); - - Proxy.Type type; - if (source == this.isDirect) type = Proxy.Type.DIRECT; - else if(source == this.isHttp) type = Proxy.Type.HTTP; - else if(source == this.isSocks) type = Proxy.Type.SOCKS; - else return; - - if(type == Proxy.Type.DIRECT){ - this.hostname.setEnabled(false); - this.port .setEnabled(false); - }else{ - this.hostname.setEnabled(true); - this.port .setEnabled(true); - } - - return; - } - -} +/* + * proxy chooser + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.net.InetSocketAddress; +import java.net.Proxy; +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; +import javax.swing.ComboBoxEditor; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.border.Border; + +/** + * プロクシサーバ選択画面。 + * @see RFC2616 + * @see RFC1928 + */ +@SuppressWarnings("serial") +public class ProxyChooser extends JPanel implements ItemListener{ + + private final JRadioButton isDirect = + new JRadioButton("直接接続"); + private final JRadioButton isHttp = + new JRadioButton("HTTP-Proxy (RFC2616)"); + private final JRadioButton isSocks = + new JRadioButton("SOCKS (RFC1928)"); + private final ButtonGroup buttonGroup = new ButtonGroup(); + + private final JTextField hostname = new JTextField(); + private final JComboBox port = new JComboBox(); + private final JComponent serverInfo = buildServerPanel(); + + + /** + * コンストラクタ。 + */ + public ProxyChooser(){ + this(ProxyInfo.DEFAULT); + return; + } + + /** + * コンストラクタ。 + * @param proxyInfo プロクシ設定 + */ + public ProxyChooser(ProxyInfo proxyInfo){ + super(); + + this.buttonGroup.add(this.isDirect); + this.buttonGroup.add(this.isHttp); + this.buttonGroup.add(this.isSocks); + this.isDirect.addItemListener(this); + this.isHttp .addItemListener(this); + this.isSocks .addItemListener(this); + + this.hostname.setComponentPopupMenu(new TextPopup()); + Monodizer.monodize(this.hostname); + GUIUtils.addMargin(this.hostname, 1, 4, 1, 4); + + this.port.setModel(buildPortRecommender()); + this.port.setEditable(true); + ComboBoxEditor editor = this.port.getEditor(); + Component comp = editor.getEditorComponent(); + GUIUtils.addMargin(comp, 1, 4, 1, 4); + if(comp instanceof JComponent){ + ((JComponent)comp).setComponentPopupMenu(new TextPopup()); + } + Monodizer.monodize(this.port); + + design(this); + + setProxyInfo(proxyInfo); + + return; + } + + + /** + * ポート番号選択肢を生成する。 + * @return ポート番号選択肢 + */ + private static ComboBoxModel buildPortRecommender(){ + DefaultComboBoxModel model = new DefaultComboBoxModel(); + model.addElement("80"); + model.addElement("1080"); + model.addElement("3128"); + model.addElement("8000"); + model.addElement("8080"); + model.addElement("10080"); + return model; + } + + /** + * レイアウトを行う。 + * @param content コンテナ + */ + private void design(Container content){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + content.setLayout(layout); + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + + content.add(this.isDirect, constraints); + content.add(this.isHttp, constraints); + content.add(this.isSocks, constraints); + content.add(this.serverInfo, constraints); + + constraints.weighty = 1.0; + content.add(new JPanel(), constraints); + + return; + } + + /** + * サーバ情報パネルを生成する。 + * @return サーバ情報パネル + */ + private JComponent buildServerPanel(){ + JPanel panel = new JPanel(); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.weightx = 0.0; + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + panel.add(new JLabel("アドレス:"), constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.NORTHWEST; + panel.add(this.hostname, constraints); + + constraints.weightx = 0.0; + constraints.gridwidth = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + panel.add(new JLabel("ポート:"), constraints); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.NORTHWEST; + panel.add(this.port, constraints); + + String warn = + "※ このプロクシサーバは本当に信頼できますか?
" + + "あなたが人狼BBSにログインしている間、
" + + "あなたのパスワードは平文状態のまま
" + + "このプロクシサーバ上を何度も通過します。"; + panel.add(new JLabel(warn), constraints); + + Border border = BorderFactory.createTitledBorder("プロクシサーバ情報"); + panel.setBorder(border); + + return panel; + } + + /** + * プロクシの種別を返す。 + * @return プロクシ種別 + */ + protected Proxy.Type getType(){ + if(this.isDirect.isSelected()) return Proxy.Type.DIRECT; + if(this.isHttp .isSelected()) return Proxy.Type.HTTP; + if(this.isSocks .isSelected()) return Proxy.Type.SOCKS; + + return Proxy.Type.DIRECT; + } + + /** + * サーバ名を返す。 + * プロクシのホスト名として妥当なものか否かは検証されない。 + * @return サーバ名 + */ + protected String getHostName(){ + String hostText = this.hostname.getText(); + hostText = hostText.trim(); + return hostText; + } + + /** + * ポート番号を返す。 + * 番号の体をなしていなければゼロを返す。 + * @return ポート番号 + */ + protected int getPort(){ + Object portItem = this.port.getEditor().getItem(); + String portText = portItem.toString(); + portText = portText.trim(); + + int result; + try{ + result = Integer.parseInt(portText); + }catch(NumberFormatException e){ + return 0; + } + + if(result < 0) result = 0; + if(65535 < result) result = 65535; + + return result; + } + + /** + * サーバへのソケットアドレスを生成する。 + * @return ソケットアドレス + */ + protected InetSocketAddress getInetSocketAddress(){ + return InetSocketAddress.createUnresolved(getHostName(), getPort()); + } + + /** + * プロクシ設定を返す。 + * @return プロクシ設定 + */ + public ProxyInfo getProxyInfo(){ + Proxy.Type type = getType(); + return new ProxyInfo(type, getInetSocketAddress()); + } + + /** + * プロクシ設定を設定する。 + * UIに反映される。 + * @param proxyInfo プロクシ設定。nullなら直接接続と解釈される。 + */ + public final void setProxyInfo(ProxyInfo proxyInfo){ + Proxy.Type type; + InetSocketAddress addr; + if(proxyInfo == null){ + type = Proxy.Type.DIRECT; + addr = ProxyInfo.IP4SOCKET_NOBODY; + }else{ + type = proxyInfo.getType(); + addr = proxyInfo.address(); + } + + ButtonModel model; + switch(type){ + case DIRECT: model = this.isDirect.getModel(); break; + case HTTP: model = this.isHttp .getModel(); break; + case SOCKS: model = this.isSocks .getModel(); break; + default: model = this.isDirect.getModel(); break; + } + this.buttonGroup.setSelected(model, true); + + this.hostname.setText(addr.getHostName()); + this.port.getEditor() + .setItem(Integer.valueOf(addr.getPort())); + + return; + } + + /** + * プロクシ種別ボタン操作の受信。 + * @param event ボタン操作イベント + */ + public void itemStateChanged(ItemEvent event){ + Object source = event.getSource(); + + Proxy.Type type; + if (source == this.isDirect) type = Proxy.Type.DIRECT; + else if(source == this.isHttp) type = Proxy.Type.HTTP; + else if(source == this.isSocks) type = Proxy.Type.SOCKS; + else return; + + if(type == Proxy.Type.DIRECT){ + this.hostname.setEnabled(false); + this.port .setEnabled(false); + }else{ + this.hostname.setEnabled(true); + this.port .setEnabled(true); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ProxyInfo.java b/src/main/java/jp/sourceforge/jindolf/ProxyInfo.java index 45124b2..16ad769 100644 --- a/src/main/java/jp/sourceforge/jindolf/ProxyInfo.java +++ b/src/main/java/jp/sourceforge/jindolf/ProxyInfo.java @@ -1,235 +1,235 @@ -/* - * proxy information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.SocketAddress; -import jp.sourceforge.jindolf.json.JsNumber; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsPair; -import jp.sourceforge.jindolf.json.JsString; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * プロクシ情報。 - * SOCKSも範疇に入る。 - * IP4,IP6以外のアドレスは未サポート。 - */ -public class ProxyInfo{ - - /** - * アドレス「0.0.0.0」、ポート番号0のソケット端点。 - */ - public static final InetSocketAddress IP4SOCKET_NOBODY = - InetSocketAddress.createUnresolved("0.0.0.0", 0); - - /** デフォルトのプロクシ(直接接続)。 */ - public static final ProxyInfo DEFAULT = new ProxyInfo(); - - private static final String HASH_TYPE = "type"; - private static final String HASH_HOST = "host"; - private static final String HASH_PORT = "port"; - - - private final Proxy proxy; - private final InetSocketAddress inetAddr; - - - /** - * コンストラクタ。 - * 直接接続プロクシが暗黙に指定される。 - */ - public ProxyInfo(){ - this(Proxy.NO_PROXY); - return; - } - - /** - * コンストラクタ。 - * @param proxy プロクシ - */ - public ProxyInfo(Proxy proxy){ - - this.proxy = proxy; - - if(this.proxy.type() == Proxy.Type.DIRECT){ - this.inetAddr = IP4SOCKET_NOBODY; - }else{ - SocketAddress addr = this.proxy.address(); - if( ! (addr instanceof InetSocketAddress) ){ - throw new IllegalArgumentException(); - } - this.inetAddr = (InetSocketAddress) addr; - } - - return; - } - - /** - * コンストラクタ。 - * @param type プロクシの種別 - * @param hostName ホスト名 - * @param port ポート番号 - */ - public ProxyInfo(Proxy.Type type, String hostName, int port){ - this(type, InetSocketAddress.createUnresolved(hostName, port)); - return; - } - - /** - * コンストラクタ。 - * @param type プロクシの種別 - * @param inetAddr 端点 - */ - public ProxyInfo(Proxy.Type type, InetSocketAddress inetAddr){ - super(); - - if(type == null || inetAddr == null){ - throw new NullPointerException(); - } - - if(type == Proxy.Type.DIRECT){ - this.proxy = Proxy.NO_PROXY; - }else{ - this.proxy = new Proxy(type, inetAddr); - } - - this.inetAddr = inetAddr; - - return; - } - - - /** - * プロクシ設定をJSON形式にエンコードする。 - * @param proxyInfo プロクシ設定 - * @return JSON object - */ - public static JsObject buildJson(ProxyInfo proxyInfo){ - JsPair type = new JsPair(HASH_TYPE, proxyInfo.getType().name()); - JsPair host = new JsPair(HASH_HOST, proxyInfo.getHostName()); - JsPair port = new JsPair(HASH_PORT, proxyInfo.getPort()); - - JsObject result = new JsObject(); - result.putPair(type); - result.putPair(host); - result.putPair(port); - - return result; - } - - /** - * JSONからのプロクシ設定復元。 - * @param obj JSON object - * @return 復元されたプロクシ設定。 - */ - public static ProxyInfo decodeJson(JsObject obj){ - JsValue value; - - Proxy.Type type = Proxy.Type.DIRECT; - value = obj.getValue(HASH_TYPE); - if(value instanceof JsString){ - JsString string = (JsString) value; - try{ - type = Enum.valueOf(Proxy.Type.class, string.toRawString()); - }catch(IllegalArgumentException e){ - // NOTHING - } - } - - String host = "0.0.0.0"; - value = obj.getValue(HASH_HOST); - if(value instanceof JsString){ - JsString string = (JsString) value; - host = string.toRawString(); - } - - int port = 0; - value = obj.getValue(HASH_PORT); - if(value instanceof JsNumber){ - JsNumber number = (JsNumber) value; - port = number.intValue(); - } - - return new ProxyInfo(type, host, port); - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(getClass() != obj.getClass()) return false; - ProxyInfo target = (ProxyInfo) obj; - - boolean result; - - result = this.proxy.equals(target.proxy); - if(result){ - result = this.inetAddr.equals(target.inetAddr); - } - - return result; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.proxy.hashCode() ^ this.inetAddr.hashCode(); - } - - /** - * プロクシを返す。 - * 直結プロクシだった場合、ホスト名とポート番号には何が入っているか不明。 - * @return プロクシ - */ - public Proxy getProxy(){ - return this.proxy; - } - - /** - * プロクシ種別を返す。 - * @return プロクシ種別 - */ - public Proxy.Type getType(){ - return this.proxy.type(); - } - - /** - * ソケット端点を返す。 - * @return ソケット端点 - */ - public InetSocketAddress address(){ - return this.inetAddr; - } - - /** - * ホスト名を返す。 - * @return ホスト名 - */ - public String getHostName(){ - return this.inetAddr.getHostName(); - } - - /** - * ポート番号を返す。 - * @return ポート番号 - */ - public int getPort(){ - return this.inetAddr.getPort(); - } - - // TODO 認証情報のサポート -} +/* + * proxy information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import jp.sourceforge.jindolf.json.JsNumber; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsPair; +import jp.sourceforge.jindolf.json.JsString; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * プロクシ情報。 + * SOCKSも範疇に入る。 + * IP4,IP6以外のアドレスは未サポート。 + */ +public class ProxyInfo{ + + /** + * アドレス「0.0.0.0」、ポート番号0のソケット端点。 + */ + public static final InetSocketAddress IP4SOCKET_NOBODY = + InetSocketAddress.createUnresolved("0.0.0.0", 0); + + /** デフォルトのプロクシ(直接接続)。 */ + public static final ProxyInfo DEFAULT = new ProxyInfo(); + + private static final String HASH_TYPE = "type"; + private static final String HASH_HOST = "host"; + private static final String HASH_PORT = "port"; + + + private final Proxy proxy; + private final InetSocketAddress inetAddr; + + + /** + * コンストラクタ。 + * 直接接続プロクシが暗黙に指定される。 + */ + public ProxyInfo(){ + this(Proxy.NO_PROXY); + return; + } + + /** + * コンストラクタ。 + * @param proxy プロクシ + */ + public ProxyInfo(Proxy proxy){ + + this.proxy = proxy; + + if(this.proxy.type() == Proxy.Type.DIRECT){ + this.inetAddr = IP4SOCKET_NOBODY; + }else{ + SocketAddress addr = this.proxy.address(); + if( ! (addr instanceof InetSocketAddress) ){ + throw new IllegalArgumentException(); + } + this.inetAddr = (InetSocketAddress) addr; + } + + return; + } + + /** + * コンストラクタ。 + * @param type プロクシの種別 + * @param hostName ホスト名 + * @param port ポート番号 + */ + public ProxyInfo(Proxy.Type type, String hostName, int port){ + this(type, InetSocketAddress.createUnresolved(hostName, port)); + return; + } + + /** + * コンストラクタ。 + * @param type プロクシの種別 + * @param inetAddr 端点 + */ + public ProxyInfo(Proxy.Type type, InetSocketAddress inetAddr){ + super(); + + if(type == null || inetAddr == null){ + throw new NullPointerException(); + } + + if(type == Proxy.Type.DIRECT){ + this.proxy = Proxy.NO_PROXY; + }else{ + this.proxy = new Proxy(type, inetAddr); + } + + this.inetAddr = inetAddr; + + return; + } + + + /** + * プロクシ設定をJSON形式にエンコードする。 + * @param proxyInfo プロクシ設定 + * @return JSON object + */ + public static JsObject buildJson(ProxyInfo proxyInfo){ + JsPair type = new JsPair(HASH_TYPE, proxyInfo.getType().name()); + JsPair host = new JsPair(HASH_HOST, proxyInfo.getHostName()); + JsPair port = new JsPair(HASH_PORT, proxyInfo.getPort()); + + JsObject result = new JsObject(); + result.putPair(type); + result.putPair(host); + result.putPair(port); + + return result; + } + + /** + * JSONからのプロクシ設定復元。 + * @param obj JSON object + * @return 復元されたプロクシ設定。 + */ + public static ProxyInfo decodeJson(JsObject obj){ + JsValue value; + + Proxy.Type type = Proxy.Type.DIRECT; + value = obj.getValue(HASH_TYPE); + if(value instanceof JsString){ + JsString string = (JsString) value; + try{ + type = Enum.valueOf(Proxy.Type.class, string.toRawString()); + }catch(IllegalArgumentException e){ + // NOTHING + } + } + + String host = "0.0.0.0"; + value = obj.getValue(HASH_HOST); + if(value instanceof JsString){ + JsString string = (JsString) value; + host = string.toRawString(); + } + + int port = 0; + value = obj.getValue(HASH_PORT); + if(value instanceof JsNumber){ + JsNumber number = (JsNumber) value; + port = number.intValue(); + } + + return new ProxyInfo(type, host, port); + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(getClass() != obj.getClass()) return false; + ProxyInfo target = (ProxyInfo) obj; + + boolean result; + + result = this.proxy.equals(target.proxy); + if(result){ + result = this.inetAddr.equals(target.inetAddr); + } + + return result; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.proxy.hashCode() ^ this.inetAddr.hashCode(); + } + + /** + * プロクシを返す。 + * 直結プロクシだった場合、ホスト名とポート番号には何が入っているか不明。 + * @return プロクシ + */ + public Proxy getProxy(){ + return this.proxy; + } + + /** + * プロクシ種別を返す。 + * @return プロクシ種別 + */ + public Proxy.Type getType(){ + return this.proxy.type(); + } + + /** + * ソケット端点を返す。 + * @return ソケット端点 + */ + public InetSocketAddress address(){ + return this.inetAddr; + } + + /** + * ホスト名を返す。 + * @return ホスト名 + */ + public String getHostName(){ + return this.inetAddr.getHostName(); + } + + /** + * ポート番号を返す。 + * @return ポート番号 + */ + public int getPort(){ + return this.inetAddr.getPort(); + } + + // TODO 認証情報のサポート +} diff --git a/src/main/java/jp/sourceforge/jindolf/RegexPattern.java b/src/main/java/jp/sourceforge/jindolf/RegexPattern.java index e88bd8a..14587f5 100644 --- a/src/main/java/jp/sourceforge/jindolf/RegexPattern.java +++ b/src/main/java/jp/sourceforge/jindolf/RegexPattern.java @@ -1,303 +1,303 @@ -/* - * Regex pattern - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import jp.sourceforge.jindolf.json.JsBoolean; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsPair; -import jp.sourceforge.jindolf.json.JsString; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * 正規表現。 - */ -public class RegexPattern{ - - /** 英字大小無視指定フラグ。 */ - public static final int IGNORECASEFLAG = - 0x00000000 | Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE; - private static final String REGEX_DELIM = "[\\s\u3000]+"; // 空白(全角含) - private static final String REGEX_CHAR = ".?+*\\$(|)[]{}^-&"; - - - private final String editSource; - private final boolean isRegex; - private final Pattern pattern; - private final String comment; - - - /** - * コンストラクタ。 - * - * @param editSource リテラル文字列または正規表現 - * @param isRegex 指定文字列が正規表現ならtrue。リテラルならfalse - * @param flag 正規表現フラグ - * @param comment コメント - * @throws java.util.regex.PatternSyntaxException 正規表現がおかしい - */ - public RegexPattern(String editSource, - boolean isRegex, - int flag, - String comment) - throws PatternSyntaxException{ - super(); - if(editSource == null) throw new NullPointerException(); - - this.isRegex = isRegex; - if(comment != null) this.comment = comment; - else this.comment = ""; - - String regexExpr; - if(this.isRegex){ - this.editSource = editSource; - regexExpr = this.editSource; - }else{ - String newSource = ""; - regexExpr = ""; - - String[] tokens = editSource.split(REGEX_DELIM); - for(String token : tokens){ - if(token == null || token.length() <= 0) continue; - - if(newSource.length() <= 0) newSource = token; - else newSource += " " + token; - - String quoted = "(?:" + quote(token) + ")"; - if(regexExpr.length() <= 0) regexExpr = quoted; - else regexExpr += "|" + quoted; - } - - this.editSource = newSource; - } - - this.pattern = Pattern.compile(regexExpr, flag); - - return; - } - - /** - * コンストラクタ。 - * - * @param editSource リテラル文字列または正規表現 - * @param isRegex 指定文字列が正規表現ならtrue。リテラルならfalse - * @param flag 正規表現フラグ - * @throws java.util.regex.PatternSyntaxException 正規表現がおかしい - */ - public RegexPattern(String editSource, - boolean isRegex, - int flag ) - throws PatternSyntaxException{ - this(editSource, isRegex, flag, " "); - return; - } - - - /** - * 正規表現とまぎらわしい字を含むか判定する。 - * @param seq 文字列 - * @return 紛らわしい字を含むならtrue - */ - public static boolean hasRegexChar(CharSequence seq){ - int length = seq.length(); - for(int pt = 0; pt < length; pt++){ - char ch = seq.charAt(pt); - if(REGEX_CHAR.indexOf(ch) >= 0) return true; - } - return false; - } - - /** - * 任意の文字列を必要に応じて正規表現シーケンス化する。 - * @param text 文字列 - * @return 引数と同じ内容の正規表現。必要がなければ引数そのまま - */ - public static String quote(String text){ - if(hasRegexChar(text)){ - return Pattern.quote(text); - } - return text; - } - - /** - * JSON形式に変換する。 - * @param regex 正規表現 - * @return JSON Object - */ - public static JsObject encodeJson(RegexPattern regex){ - JsObject result = new JsObject(); - - int regexFlag = regex.getRegexFlag(); - boolean flagDotall = (regexFlag & Pattern.DOTALL) != 0; - boolean flagMultiline = (regexFlag & Pattern.MULTILINE) != 0; - boolean flagIgnoreCase = (regexFlag & IGNORECASEFLAG) != 0; - - JsPair source = new JsPair("source", regex.getEditSource()); - JsPair isRegex = new JsPair("isRegex", regex.isRegex()); - JsPair dotall = new JsPair("dotall", flagDotall); - JsPair multiline = new JsPair("multiline", flagMultiline); - JsPair ignorecase = new JsPair("ignorecase", flagIgnoreCase); - JsPair comment = new JsPair("comment", regex.getComment()); - - result.putPair(source); - result.putPair(isRegex); - result.putPair(dotall); - result.putPair(multiline); - result.putPair(ignorecase); - result.putPair(comment); - - return result; - } - - /** - * JSON形式から復元する。 - * @param object JSON Object - * @return 正規表現 - */ - public static RegexPattern decodeJson(JsObject object){ - JsValue value; - - String source; - value = object.getValue("source"); - if(value instanceof JsString){ - source = ((JsString)value).toRawString(); - }else{ - source = ""; - } - - boolean isRegex; - value = object.getValue("isRegex"); - if(value instanceof JsBoolean){ - isRegex = ((JsBoolean)value).booleanValue(); - }else{ - isRegex = false; - } - - int regexFlag = 0x00000000; - value = object.getValue("dotall"); - if(value instanceof JsBoolean){ - if(((JsBoolean)value).isTrue()){ - regexFlag |= Pattern.DOTALL; - } - } - value = object.getValue("multiline"); - if(value instanceof JsBoolean){ - if(((JsBoolean)value).isTrue()){ - regexFlag |= Pattern.MULTILINE; - } - } - value = object.getValue("ignorecase"); - if(value instanceof JsBoolean){ - if(((JsBoolean)value).isTrue()){ - regexFlag |= IGNORECASEFLAG; - } - } - - String comment; - value = object.getValue("comment"); - if(value instanceof JsString){ - comment = ((JsString)value).toRawString(); - }else{ - comment = ""; - } - - RegexPattern result = - new RegexPattern(source, isRegex, regexFlag, comment); - - return result; - } - - - /** - * 元の入力文字列を返す。 - * @return 入力文字列 - */ - public String getEditSource(){ - return this.editSource; - } - - /** - * コメントを返す。 - * @return コメント - */ - public String getComment(){ - return this.comment; - } - - /** - * 元の入力文字列が正規表現か否か返す。 - * @return 正規表現ならtrue - */ - public boolean isRegex(){ - return this.isRegex; - } - - /** - * 正規表現フラグを返す。 - * @return 正規表現フラグ。 - * @see java.util.regex.Pattern#flags() - */ - public int getRegexFlag(){ - return this.pattern.flags(); - } - - /** - * コンパイルされた正規表現形式を返す。 - * @return コンパイルされた正規表現形式 - */ - public Pattern getPattern(){ - return this.pattern; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return this.editSource; - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null){ - return false; - } - if( ! (obj instanceof RegexPattern) ){ - return false; - } - RegexPattern other = (RegexPattern) obj; - - String thisPattern = this.pattern.pattern(); - String otherPattern = other.pattern.pattern(); - - if( ! thisPattern.equals(otherPattern) ) return false; - - if(this.pattern.flags() != other.pattern.flags()) return false; - - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - int hash = this.pattern.pattern().hashCode(); - hash ^= this.pattern.flags(); - return hash; - } - -} +/* + * Regex pattern + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import jp.sourceforge.jindolf.json.JsBoolean; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsPair; +import jp.sourceforge.jindolf.json.JsString; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * 正規表現。 + */ +public class RegexPattern{ + + /** 英字大小無視指定フラグ。 */ + public static final int IGNORECASEFLAG = + 0x00000000 | Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE; + private static final String REGEX_DELIM = "[\\s\u3000]+"; // 空白(全角含) + private static final String REGEX_CHAR = ".?+*\\$(|)[]{}^-&"; + + + private final String editSource; + private final boolean isRegex; + private final Pattern pattern; + private final String comment; + + + /** + * コンストラクタ。 + * + * @param editSource リテラル文字列または正規表現 + * @param isRegex 指定文字列が正規表現ならtrue。リテラルならfalse + * @param flag 正規表現フラグ + * @param comment コメント + * @throws java.util.regex.PatternSyntaxException 正規表現がおかしい + */ + public RegexPattern(String editSource, + boolean isRegex, + int flag, + String comment) + throws PatternSyntaxException{ + super(); + if(editSource == null) throw new NullPointerException(); + + this.isRegex = isRegex; + if(comment != null) this.comment = comment; + else this.comment = ""; + + String regexExpr; + if(this.isRegex){ + this.editSource = editSource; + regexExpr = this.editSource; + }else{ + String newSource = ""; + regexExpr = ""; + + String[] tokens = editSource.split(REGEX_DELIM); + for(String token : tokens){ + if(token == null || token.length() <= 0) continue; + + if(newSource.length() <= 0) newSource = token; + else newSource += " " + token; + + String quoted = "(?:" + quote(token) + ")"; + if(regexExpr.length() <= 0) regexExpr = quoted; + else regexExpr += "|" + quoted; + } + + this.editSource = newSource; + } + + this.pattern = Pattern.compile(regexExpr, flag); + + return; + } + + /** + * コンストラクタ。 + * + * @param editSource リテラル文字列または正規表現 + * @param isRegex 指定文字列が正規表現ならtrue。リテラルならfalse + * @param flag 正規表現フラグ + * @throws java.util.regex.PatternSyntaxException 正規表現がおかしい + */ + public RegexPattern(String editSource, + boolean isRegex, + int flag ) + throws PatternSyntaxException{ + this(editSource, isRegex, flag, " "); + return; + } + + + /** + * 正規表現とまぎらわしい字を含むか判定する。 + * @param seq 文字列 + * @return 紛らわしい字を含むならtrue + */ + public static boolean hasRegexChar(CharSequence seq){ + int length = seq.length(); + for(int pt = 0; pt < length; pt++){ + char ch = seq.charAt(pt); + if(REGEX_CHAR.indexOf(ch) >= 0) return true; + } + return false; + } + + /** + * 任意の文字列を必要に応じて正規表現シーケンス化する。 + * @param text 文字列 + * @return 引数と同じ内容の正規表現。必要がなければ引数そのまま + */ + public static String quote(String text){ + if(hasRegexChar(text)){ + return Pattern.quote(text); + } + return text; + } + + /** + * JSON形式に変換する。 + * @param regex 正規表現 + * @return JSON Object + */ + public static JsObject encodeJson(RegexPattern regex){ + JsObject result = new JsObject(); + + int regexFlag = regex.getRegexFlag(); + boolean flagDotall = (regexFlag & Pattern.DOTALL) != 0; + boolean flagMultiline = (regexFlag & Pattern.MULTILINE) != 0; + boolean flagIgnoreCase = (regexFlag & IGNORECASEFLAG) != 0; + + JsPair source = new JsPair("source", regex.getEditSource()); + JsPair isRegex = new JsPair("isRegex", regex.isRegex()); + JsPair dotall = new JsPair("dotall", flagDotall); + JsPair multiline = new JsPair("multiline", flagMultiline); + JsPair ignorecase = new JsPair("ignorecase", flagIgnoreCase); + JsPair comment = new JsPair("comment", regex.getComment()); + + result.putPair(source); + result.putPair(isRegex); + result.putPair(dotall); + result.putPair(multiline); + result.putPair(ignorecase); + result.putPair(comment); + + return result; + } + + /** + * JSON形式から復元する。 + * @param object JSON Object + * @return 正規表現 + */ + public static RegexPattern decodeJson(JsObject object){ + JsValue value; + + String source; + value = object.getValue("source"); + if(value instanceof JsString){ + source = ((JsString)value).toRawString(); + }else{ + source = ""; + } + + boolean isRegex; + value = object.getValue("isRegex"); + if(value instanceof JsBoolean){ + isRegex = ((JsBoolean)value).booleanValue(); + }else{ + isRegex = false; + } + + int regexFlag = 0x00000000; + value = object.getValue("dotall"); + if(value instanceof JsBoolean){ + if(((JsBoolean)value).isTrue()){ + regexFlag |= Pattern.DOTALL; + } + } + value = object.getValue("multiline"); + if(value instanceof JsBoolean){ + if(((JsBoolean)value).isTrue()){ + regexFlag |= Pattern.MULTILINE; + } + } + value = object.getValue("ignorecase"); + if(value instanceof JsBoolean){ + if(((JsBoolean)value).isTrue()){ + regexFlag |= IGNORECASEFLAG; + } + } + + String comment; + value = object.getValue("comment"); + if(value instanceof JsString){ + comment = ((JsString)value).toRawString(); + }else{ + comment = ""; + } + + RegexPattern result = + new RegexPattern(source, isRegex, regexFlag, comment); + + return result; + } + + + /** + * 元の入力文字列を返す。 + * @return 入力文字列 + */ + public String getEditSource(){ + return this.editSource; + } + + /** + * コメントを返す。 + * @return コメント + */ + public String getComment(){ + return this.comment; + } + + /** + * 元の入力文字列が正規表現か否か返す。 + * @return 正規表現ならtrue + */ + public boolean isRegex(){ + return this.isRegex; + } + + /** + * 正規表現フラグを返す。 + * @return 正規表現フラグ。 + * @see java.util.regex.Pattern#flags() + */ + public int getRegexFlag(){ + return this.pattern.flags(); + } + + /** + * コンパイルされた正規表現形式を返す。 + * @return コンパイルされた正規表現形式 + */ + public Pattern getPattern(){ + return this.pattern; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return this.editSource; + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null){ + return false; + } + if( ! (obj instanceof RegexPattern) ){ + return false; + } + RegexPattern other = (RegexPattern) obj; + + String thisPattern = this.pattern.pattern(); + String otherPattern = other.pattern.pattern(); + + if( ! thisPattern.equals(otherPattern) ) return false; + + if(this.pattern.flags() != other.pattern.flags()) return false; + + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + int hash = this.pattern.pattern().hashCode(); + hash ^= this.pattern.flags(); + return hash; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Selectable.java b/src/main/java/jp/sourceforge/jindolf/Selectable.java index 3d37e75..32e3bd9 100644 --- a/src/main/java/jp/sourceforge/jindolf/Selectable.java +++ b/src/main/java/jp/sourceforge/jindolf/Selectable.java @@ -1,39 +1,39 @@ -/* - * 文字列選択インタフェース - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Point; -import java.io.IOException; - -/** - * ドラッグ操作で文字列選択が可能な「何か」。 - */ -public interface Selectable{ - - /** - * ドラッグ処理を行う。 - * @param fromPt ドラッグ開始位置 - * @param toPt 現在のドラッグ位置 - */ - void drag(Point fromPt, Point toPt); - - /** - * 受け取った文字列に選択文字列を追加する。 - * @param appendable 追加対象文字列 - * @return 引数と同じインスタンス - * @throws java.io.IOException ※ 出ないはず - */ - Appendable appendSelected(Appendable appendable) - throws IOException; - - /** - * 選択範囲の解除。 - */ - void clearSelect(); - -} +/* + * 文字列選択インタフェース + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Point; +import java.io.IOException; + +/** + * ドラッグ操作で文字列選択が可能な「何か」。 + */ +public interface Selectable{ + + /** + * ドラッグ処理を行う。 + * @param fromPt ドラッグ開始位置 + * @param toPt 現在のドラッグ位置 + */ + void drag(Point fromPt, Point toPt); + + /** + * 受け取った文字列に選択文字列を追加する。 + * @param appendable 追加対象文字列 + * @return 引数と同じインスタンス + * @throws java.io.IOException ※ 出ないはず + */ + Appendable appendSelected(Appendable appendable) + throws IOException; + + /** + * 選択範囲の解除。 + */ + void clearSelect(); + +} diff --git a/src/main/java/jp/sourceforge/jindolf/SequenceCharacterIterator.java b/src/main/java/jp/sourceforge/jindolf/SequenceCharacterIterator.java index 00ab16e..eff102b 100644 --- a/src/main/java/jp/sourceforge/jindolf/SequenceCharacterIterator.java +++ b/src/main/java/jp/sourceforge/jindolf/SequenceCharacterIterator.java @@ -1,184 +1,184 @@ -/* - * CharSequence CharacterIterator - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.text.CharacterIterator; - -/** - * CharSequenceをソースとするCharacterIterator。 - */ -public class SequenceCharacterIterator - implements CharacterIterator, - Cloneable { - - private CharSequence source; - private final int cursorBegin; - private final int cursorEnd; - private final int cursorLength; - private int cursorPos; - - /** - * コンストラクタ。 - * @param source ソース文字列 - * @param cursorBegin カーソル開始位置 - * @param cursorEnd カーソル終了位置 - */ - public SequenceCharacterIterator(CharSequence source, - int cursorBegin, int cursorEnd){ - super(); - - if(cursorBegin > cursorEnd){ - throw new IllegalArgumentException(); - } - if(cursorBegin < 0 || source.length() < cursorEnd){ - throw new IndexOutOfBoundsException(); - } - - this.source = source; - this.cursorBegin = cursorBegin; - this.cursorEnd = cursorEnd; - this.cursorLength = this.cursorEnd - this.cursorBegin; - this.cursorPos = this.cursorBegin; - - return; - } - - /** - * コンストラクタ。 - * @param source ソース文字列 - */ - public SequenceCharacterIterator(CharSequence source){ - super(); - this.source = source; - this.cursorBegin = 0; - this.cursorEnd = source.length(); - this.cursorLength = this.cursorEnd - this.cursorBegin; - this.cursorPos = this.cursorBegin; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char first(){ - this.cursorPos = this.cursorBegin; - return current(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char last(){ - this.cursorPos = this.cursorEnd - 1; - return current(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char current(){ - if(this.cursorLength <= 0 || this.cursorPos < this.cursorBegin){ - this.cursorPos = this.cursorBegin; - return CharacterIterator.DONE; - } - if(this.cursorPos >= this.cursorEnd){ - this.cursorPos = this.cursorEnd; - return CharacterIterator.DONE; - } - return this.source.charAt(this.cursorPos); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char next(){ - this.cursorPos++; - return current(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public char previous(){ - this.cursorPos--; - return current(); - } - - /** - * {@inheritDoc} - * @param newPos {@inheritDoc} - * @return {@inheritDoc} - * @throws java.lang.IllegalArgumentException {@inheritDoc} - */ - @Override - public char setIndex(int newPos) throws IllegalArgumentException{ - if(newPos < this.cursorBegin || this.cursorEnd < newPos){ - throw new IllegalArgumentException(); - } - this.cursorPos = newPos; - return current(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getBeginIndex(){ - return this.cursorBegin; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getEndIndex(){ - return this.cursorEnd; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getIndex(){ - return this.cursorPos; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Object clone(){ - Object result; - try{ - result = super.clone(); - }catch(CloneNotSupportedException e){ - assert false; - return null; - } - - SequenceCharacterIterator seq = (SequenceCharacterIterator) result; - seq.source = this.source.toString(); - - return seq; - } - -} +/* + * CharSequence CharacterIterator + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.text.CharacterIterator; + +/** + * CharSequenceをソースとするCharacterIterator。 + */ +public class SequenceCharacterIterator + implements CharacterIterator, + Cloneable { + + private CharSequence source; + private final int cursorBegin; + private final int cursorEnd; + private final int cursorLength; + private int cursorPos; + + /** + * コンストラクタ。 + * @param source ソース文字列 + * @param cursorBegin カーソル開始位置 + * @param cursorEnd カーソル終了位置 + */ + public SequenceCharacterIterator(CharSequence source, + int cursorBegin, int cursorEnd){ + super(); + + if(cursorBegin > cursorEnd){ + throw new IllegalArgumentException(); + } + if(cursorBegin < 0 || source.length() < cursorEnd){ + throw new IndexOutOfBoundsException(); + } + + this.source = source; + this.cursorBegin = cursorBegin; + this.cursorEnd = cursorEnd; + this.cursorLength = this.cursorEnd - this.cursorBegin; + this.cursorPos = this.cursorBegin; + + return; + } + + /** + * コンストラクタ。 + * @param source ソース文字列 + */ + public SequenceCharacterIterator(CharSequence source){ + super(); + this.source = source; + this.cursorBegin = 0; + this.cursorEnd = source.length(); + this.cursorLength = this.cursorEnd - this.cursorBegin; + this.cursorPos = this.cursorBegin; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char first(){ + this.cursorPos = this.cursorBegin; + return current(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char last(){ + this.cursorPos = this.cursorEnd - 1; + return current(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char current(){ + if(this.cursorLength <= 0 || this.cursorPos < this.cursorBegin){ + this.cursorPos = this.cursorBegin; + return CharacterIterator.DONE; + } + if(this.cursorPos >= this.cursorEnd){ + this.cursorPos = this.cursorEnd; + return CharacterIterator.DONE; + } + return this.source.charAt(this.cursorPos); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char next(){ + this.cursorPos++; + return current(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public char previous(){ + this.cursorPos--; + return current(); + } + + /** + * {@inheritDoc} + * @param newPos {@inheritDoc} + * @return {@inheritDoc} + * @throws java.lang.IllegalArgumentException {@inheritDoc} + */ + @Override + public char setIndex(int newPos) throws IllegalArgumentException{ + if(newPos < this.cursorBegin || this.cursorEnd < newPos){ + throw new IllegalArgumentException(); + } + this.cursorPos = newPos; + return current(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getBeginIndex(){ + return this.cursorBegin; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getEndIndex(){ + return this.cursorEnd; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getIndex(){ + return this.cursorPos; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Object clone(){ + Object result; + try{ + result = super.clone(); + }catch(CloneNotSupportedException e){ + assert false; + return null; + } + + SequenceCharacterIterator seq = (SequenceCharacterIterator) result; + seq.source = this.source.toString(); + + return seq; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/ServerAccess.java b/src/main/java/jp/sourceforge/jindolf/ServerAccess.java index 302617c..2b6c4cc 100644 --- a/src/main/java/jp/sourceforge/jindolf/ServerAccess.java +++ b/src/main/java/jp/sourceforge/jindolf/ServerAccess.java @@ -1,620 +1,620 @@ -/* - * manage HTTP access - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.ref.SoftReference; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.imageio.ImageIO; -import jp.sourceforge.jindolf.parser.ContentBuilder; -import jp.sourceforge.jindolf.parser.ContentBuilderSJ; -import jp.sourceforge.jindolf.parser.ContentBuilderUCS2; -import jp.sourceforge.jindolf.parser.DecodeException; -import jp.sourceforge.jindolf.parser.DecodedContent; -import jp.sourceforge.jindolf.parser.SjisDecoder; -import jp.sourceforge.jindolf.parser.StreamDecoder; - -/** - * 国ごとの人狼BBSサーバとの通信を一手に引き受ける。 - */ -public class ServerAccess{ - - private static final String USER_AGENT = HttpUtils.getUserAgentName(); - private static final String JINRO_CGI = "./index.rb"; - private static final - Map> IMAGE_CACHE; - - static{ - Map> cache = - new HashMap>(); - IMAGE_CACHE = Collections.synchronizedMap(cache); - } - - - private final URL baseURL; - private final Charset charset; - private Proxy proxy = Proxy.NO_PROXY; - private long lastServerMs; - private long lastLocalMs; - private long lastSystemMs; - private AccountCookie cookieAuth = null; - private String encodedUserID = null; - - - /** - * 人狼BBSサーバとの接続管理を生成する。 - * この時点ではまだ通信は行われない。 - * @param baseURL 国別のベースURL - * @param charset 国のCharset - */ - public ServerAccess(URL baseURL, Charset charset){ - this.baseURL = baseURL; - this.charset = charset; - return; - } - - - /** - * 画像キャッシュを検索する。 - * @param key キー - * @return キャッシュされた画像。キャッシュされていなければnull。 - */ - private static BufferedImage getImageCache(String key){ - if(key == null) return null; - - BufferedImage image; - - synchronized(IMAGE_CACHE){ - SoftReference ref = IMAGE_CACHE.get(key); - if(ref == null) return null; - - Object referent = ref.get(); - if(referent == null){ - IMAGE_CACHE.remove(key); - return null; - } - - image = (BufferedImage) referent; - } - - return image; - } - - /** - * 画像キャッシュに登録する。 - * @param key キー - * @param image キャッシュしたい画像。 - */ - private static void putImageCache(String key, BufferedImage image){ - if(key == null || image == null) return; - - synchronized(IMAGE_CACHE){ - if(getImageCache(key) != null) return; - SoftReference ref = - new SoftReference(image); - IMAGE_CACHE.put(key, ref); - } - - return; - } - - /** - * 与えられた文字列に対し「application/x-www-form-urlencoded」符号化を行う。 - * この符号化はHTTPのPOSTメソッドで必要になる。 - * この処理は、一般的なPC用Webブラウザにおける、 - * Shift_JISで書かれたHTML文書のFORMタグに伴う - * submit処理を模倣する。 - * @param formData 元の文字列 - * @return 符号化された文字列 - */ - public static String formEncode(String formData){ - if(formData == null){ - return null; - } - String result; - try{ - result = URLEncoder.encode(formData, "US-ASCII"); - }catch(UnsupportedEncodingException e){ - assert false; - result = null; - } - return result; - } - - /** - * 配列版formEncode。 - * @param formData 元の文字列 - * @return 符号化された文字列 - */ - public static String formEncode(char[] formData){ - return formEncode(new String(formData)); - } - - /** - * HTTP-Proxyを返す。 - * @return HTTP-Proxy - */ - public Proxy getProxy(){ - return this.proxy; - } - - /** - * HTTP-Proxyを設定する。 - * @param proxy HTTP-Proxy。nullならProxyなしと解釈される。 - */ - public void setProxy(Proxy proxy){ - if(proxy == null) this.proxy = Proxy.NO_PROXY; - else this.proxy = proxy; - return; - } - - /** - * 国のベースURLを返す。 - * @return ベースURL - */ - public URL getBaseURL(){ - return this.baseURL; - } - - /** - * 与えられたクエリーとCGIのURLから新たにURLを合成する。 - * @param query クエリー - * @return 新たなURL - */ - protected URL getQueryURL(String query){ - if(query.length() >= 1 && query.charAt(0) != '?'){ - return null; - } - - URL result; - try{ - result = new URL(getBaseURL(), JINRO_CGI + query); - }catch(MalformedURLException e){ - assert false; - return null; - } - return result; - } - - /** - * 「Shift_JIS」でエンコーディングされた入力ストリームから文字列を生成する。 - * @param istream 入力ストリーム - * @return 文字列 - * @throws java.io.IOException 入出力エラー(おそらくネットワーク関連) - */ - public DecodedContent downloadHTMLStream(InputStream istream) - throws IOException{ - StreamDecoder decoder; - ContentBuilder builder; - if(this.charset.name().equalsIgnoreCase("Shift_JIS")){ - decoder = new SjisDecoder(); - builder = new ContentBuilderSJ(200 * 1024); - }else if(this.charset.name().equalsIgnoreCase("UTF-8")){ - decoder = new StreamDecoder(this.charset.newDecoder()); - builder = new ContentBuilderUCS2(200 * 1024); - }else{ - assert false; - return null; - } - decoder.setDecodeHandler(builder); - - // TODO デコーダをインスタンス変数にできないか。 - // TODO DecodedContentのキャッシュ管理。 - - try{ - decoder.decode(istream); - }catch(DecodeException e){ - return null; - } - - return builder.getContent(); - } - - /** - * 与えられたクエリーを用いてHTMLデータを取得する。 - * @param query HTTP-GET クエリー - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - protected HtmlSequence downloadHTML(String query) - throws IOException{ - URL url = getQueryURL(query); - HtmlSequence result = downloadHTML(url); - return result; - } - - /** - * 与えられたURLを用いてHTMLデータを取得する。 - * @param url URL - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - protected HtmlSequence downloadHTML(URL url) - throws IOException{ - HttpURLConnection connection = - (HttpURLConnection) url.openConnection(this.proxy); - connection.setRequestProperty("Accept", "*/*"); - connection.setRequestProperty("User-Agent", USER_AGENT); - connection.setUseCaches(false); - connection.setInstanceFollowRedirects(false); - connection.setDoInput(true); - connection.setRequestMethod("GET"); - - AccountCookie cookie = this.cookieAuth; - if(cookie != null){ - if(shouldAccept(url, cookie)){ - connection.setRequestProperty( - "Cookie", - "login=" + cookie.getLoginData()); - }else{ - clearAuthentication(); - } - } - - connection.connect(); - - long datems = updateLastAccess(connection); - - int responseCode = connection.getResponseCode(); - if(responseCode != HttpURLConnection.HTTP_OK){ // 200 - String logMessage = "発言のダウンロードに失敗しました。"; - logMessage += HttpUtils.formatHttpStat(connection, 0, 0); - Jindolf.logger().warn(logMessage); - return null; - } - - String cs = HttpUtils.getHTMLCharset(connection); - if(!cs.equalsIgnoreCase(this.charset.name())){ - return null; - } - - InputStream stream = TallyInputStream.getInputStream(connection); - DecodedContent html = downloadHTMLStream(stream); - - stream.close(); - connection.disconnect(); - - HtmlSequence hseq = new HtmlSequence(url, datems, html); - - return hseq; - } - - /** - * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。 - * @param url 画像URL文字列 - * @return 画像イメージ - * @throws java.io.IOException ネットワークエラー - */ - public BufferedImage downloadImage(String url) throws IOException{ - URL absolute; - try{ - URL base = getBaseURL(); - absolute = new URL(base, url); - }catch(MalformedURLException e){ - assert false; - return null; - } - - BufferedImage image; - image = getImageCache(absolute.toString()); - if(image != null) return image; - - HttpURLConnection connection = - (HttpURLConnection) absolute.openConnection(this.proxy); - connection.setRequestProperty("Accept", "*/*"); - connection.setRequestProperty("User-Agent", USER_AGENT); - connection.setUseCaches(true); - connection.setInstanceFollowRedirects(true); - connection.setDoInput(true); - connection.setRequestMethod("GET"); - - connection.connect(); - - int responseCode = connection.getResponseCode(); - if(responseCode != HttpURLConnection.HTTP_OK){ - String logMessage = "イメージのダウンロードに失敗しました。"; - logMessage += HttpUtils.formatHttpStat(connection, 0, 0); - Jindolf.logger().warn(logMessage); - return null; - } - - InputStream stream = TallyInputStream.getInputStream(connection); - image = ImageIO.read(stream); - stream.close(); - - connection.disconnect(); - - putImageCache(absolute.toString(), image); - - return image; - } - - /** - * 指定された認証情報をPOSTする。 - * @param authData 認証情報 - * @return 認証情報が受け入れられたらtrue - * @throws java.io.IOException ネットワークエラー - */ - protected boolean postAuthData(String authData) throws IOException{ - URL url = getQueryURL(""); - HttpURLConnection connection = - (HttpURLConnection) url.openConnection(this.proxy); - connection.setRequestProperty("Accept", "*/*"); - connection.setRequestProperty("User-Agent", USER_AGENT); - connection.setUseCaches(false); - connection.setInstanceFollowRedirects(false); - connection.setDoInput(true); - connection.setDoOutput(true); - connection.setRequestMethod("POST"); - - byte[] authBytes = authData.getBytes(); - - OutputStream os = TallyOutputStream.getOutputStream(connection); - os.write(authBytes); - os.flush(); - os.close(); - - updateLastAccess(connection); - - int responseCode = connection.getResponseCode(); - if(responseCode != HttpURLConnection.HTTP_MOVED_TEMP){ // 302 - String logMessage = "認証情報の送信に失敗しました。"; - Jindolf.logger().warn(logMessage); - connection.disconnect(); - return false; - } - - connection.disconnect(); - - AccountCookie loginCookie = AccountCookie.createCookie(connection); - if(loginCookie == null){ - return false; - } - - setAuthentication(loginCookie); - - Jindolf.logger().info("正しく認証が行われました。"); - - return true; - } - - /** - * トップページのHTMLデータを取得する。 - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - public HtmlSequence getHTMLTopPage() throws IOException{ - return downloadHTML(""); - } - - /** - * 国に含まれる村一覧HTMLデータを取得する。 - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - public HtmlSequence getHTMLLandList() throws IOException{ - return downloadHTML("?cmd=log"); - } - - /** - * 指定された村のPeriod一覧のHTMLデータを取得する。 - * 現在ゲーム進行中の村にも可能。 - * ※ 古国では使えないよ! - * @param village 村 - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - public HtmlSequence getHTMLBoneHead(Village village) throws IOException{ - String villageID = village.getVillageID(); - return downloadHTML("?vid=" + villageID + "&meslog="); - } - - /** - * 指定された村の最新PeriodのHTMLデータをロードする。 - * 既にGAMEOVERの村ではPeriod一覧のHTMLデータとなる。 - * @param village 村 - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - public HtmlSequence getHTMLVillage(Village village) throws IOException{ - URL url = getVillageURL(village); - return downloadHTML(url); - } - - /** - * 指定された村の最新PeriodのHTMLデータのURLを取得する。 - * @param village 村 - * @return URL - */ - public URL getVillageURL(Village village){ - String villageID = village.getVillageID(); - URL url = getQueryURL("?vid=" + villageID); - return url; - } - - /** - * 指定されたPeriodのHTMLデータをロードする。 - * @param period Period - * @return HTMLデータ - * @throws java.io.IOException ネットワークエラー - */ - public HtmlSequence getHTMLPeriod(Period period) throws IOException{ - URL url = getPeriodURL(period); - return downloadHTML(url); - } - - /** - * 指定されたPeriodのHTMLデータのURLを取得する。 - * @param period 日 - * @return URL - */ - public URL getPeriodURL(Period period){ - String query = period.getCGIQuery(); - URL url = getQueryURL(query); - return url; - } - - /** - * 最終アクセス時刻を更新する。 - * @param connection HTTP接続 - * @return リソース送信時刻 - */ - public long updateLastAccess(HttpURLConnection connection){ - this.lastServerMs = connection.getDate(); - this.lastLocalMs = System.currentTimeMillis(); - this.lastSystemMs = System.nanoTime() / (1000 * 1000); - return this.lastServerMs; - } - - /** - * 指定したURLに対しCookieを送っても良いか否か判定する。 - * 判別材料は Cookie の寿命とパス指定のみ。 - * @param url URL - * @param cookie Cookie - * @return 送ってもよければtrue - */ - private static boolean shouldAccept(URL url, AccountCookie cookie){ - if(cookie.hasExpired()){ - return false; - } - - String urlPath = url.getPath(); - String cookiePath = cookie.getPathURI().getPath(); - - if( ! urlPath.startsWith(cookiePath) ){ - return false; - } - - return true; - } - - /** - * 現在ログイン中か否か判別する。 - * @return ログイン中ならtrue - */ - // TODO interval call - public boolean hasLoggedIn(){ - AccountCookie cookie = this.cookieAuth; - if(cookie == null){ - return false; - } - if(cookie.hasExpired()){ - clearAuthentication(); - return false; - } - return true; - } - - /** - * 与えられたユーザIDとパスワードでログイン処理を行う。 - * @param userID ユーザID - * @param password パスワード - * @return ログインに成功すればtrue - * @throws java.io.IOException ネットワークエラー - */ - public final boolean login(String userID, char[] password) - throws IOException{ - if(hasLoggedIn()){ - return true; - } - - String id = formEncode(userID); - if(id == null || id.length() <= 0){ - return false; - } - - String pw = formEncode(password); - if(pw == null || pw.length() <= 0){ - return false; - } - - this.encodedUserID = id; - - String redirect = formEncode("&#bottom"); // TODO ほんとに必要? - - StringBuilder postData = new StringBuilder(); - postData.append("cmd=login"); - postData.append('&').append("cgi_param=").append(redirect); - postData.append('&').append("user_id=").append(id); - postData.append('&').append("password=").append(pw); - - boolean result; - try{ - result = postAuthData(postData.toString()); - }catch(IOException e){ - clearAuthentication(); - throw e; - } - - return result; - } - - /** - * ログアウト処理を行う。 - * @throws java.io.IOException ネットワーク入出力エラー - */ - // TODO シャットダウンフックでログアウトさせようかな… - public void logout() throws IOException{ - if(!hasLoggedIn()){ - return; - } - if(this.encodedUserID == null){ - clearAuthentication(); - return; - } - - String redirect = formEncode("&#bottom"); // TODO 必要? - - StringBuilder postData = new StringBuilder(); - postData.append("cmd=logout"); - postData.append('&').append("cgi_param=").append(redirect); - postData.append('&').append("user_id=").append(this.encodedUserID); - - try{ - postAuthData(postData.toString()); - }finally{ - clearAuthentication(); - } - - return; - } - - /** - * 認証情報クリア。 - */ - // TODO タイマーでExpire date の時刻にクリアしたい。 - protected void clearAuthentication(){ - this.cookieAuth = null; - this.encodedUserID = null; - return; - } - - /** - * 認証情報のセット。 - * @param cookie 認証Cookie - */ - private void setAuthentication(AccountCookie cookie){ - this.cookieAuth = cookie; - return; - } - - // TODO JRE1.6対応するときに HttpCookie, CookieManager 利用へ移行したい。 - -} +/* + * manage HTTP access + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.ref.SoftReference; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.imageio.ImageIO; +import jp.sourceforge.jindolf.parser.ContentBuilder; +import jp.sourceforge.jindolf.parser.ContentBuilderSJ; +import jp.sourceforge.jindolf.parser.ContentBuilderUCS2; +import jp.sourceforge.jindolf.parser.DecodeException; +import jp.sourceforge.jindolf.parser.DecodedContent; +import jp.sourceforge.jindolf.parser.SjisDecoder; +import jp.sourceforge.jindolf.parser.StreamDecoder; + +/** + * 国ごとの人狼BBSサーバとの通信を一手に引き受ける。 + */ +public class ServerAccess{ + + private static final String USER_AGENT = HttpUtils.getUserAgentName(); + private static final String JINRO_CGI = "./index.rb"; + private static final + Map> IMAGE_CACHE; + + static{ + Map> cache = + new HashMap>(); + IMAGE_CACHE = Collections.synchronizedMap(cache); + } + + + private final URL baseURL; + private final Charset charset; + private Proxy proxy = Proxy.NO_PROXY; + private long lastServerMs; + private long lastLocalMs; + private long lastSystemMs; + private AccountCookie cookieAuth = null; + private String encodedUserID = null; + + + /** + * 人狼BBSサーバとの接続管理を生成する。 + * この時点ではまだ通信は行われない。 + * @param baseURL 国別のベースURL + * @param charset 国のCharset + */ + public ServerAccess(URL baseURL, Charset charset){ + this.baseURL = baseURL; + this.charset = charset; + return; + } + + + /** + * 画像キャッシュを検索する。 + * @param key キー + * @return キャッシュされた画像。キャッシュされていなければnull。 + */ + private static BufferedImage getImageCache(String key){ + if(key == null) return null; + + BufferedImage image; + + synchronized(IMAGE_CACHE){ + SoftReference ref = IMAGE_CACHE.get(key); + if(ref == null) return null; + + Object referent = ref.get(); + if(referent == null){ + IMAGE_CACHE.remove(key); + return null; + } + + image = (BufferedImage) referent; + } + + return image; + } + + /** + * 画像キャッシュに登録する。 + * @param key キー + * @param image キャッシュしたい画像。 + */ + private static void putImageCache(String key, BufferedImage image){ + if(key == null || image == null) return; + + synchronized(IMAGE_CACHE){ + if(getImageCache(key) != null) return; + SoftReference ref = + new SoftReference(image); + IMAGE_CACHE.put(key, ref); + } + + return; + } + + /** + * 与えられた文字列に対し「application/x-www-form-urlencoded」符号化を行う。 + * この符号化はHTTPのPOSTメソッドで必要になる。 + * この処理は、一般的なPC用Webブラウザにおける、 + * Shift_JISで書かれたHTML文書のFORMタグに伴う + * submit処理を模倣する。 + * @param formData 元の文字列 + * @return 符号化された文字列 + */ + public static String formEncode(String formData){ + if(formData == null){ + return null; + } + String result; + try{ + result = URLEncoder.encode(formData, "US-ASCII"); + }catch(UnsupportedEncodingException e){ + assert false; + result = null; + } + return result; + } + + /** + * 配列版formEncode。 + * @param formData 元の文字列 + * @return 符号化された文字列 + */ + public static String formEncode(char[] formData){ + return formEncode(new String(formData)); + } + + /** + * HTTP-Proxyを返す。 + * @return HTTP-Proxy + */ + public Proxy getProxy(){ + return this.proxy; + } + + /** + * HTTP-Proxyを設定する。 + * @param proxy HTTP-Proxy。nullならProxyなしと解釈される。 + */ + public void setProxy(Proxy proxy){ + if(proxy == null) this.proxy = Proxy.NO_PROXY; + else this.proxy = proxy; + return; + } + + /** + * 国のベースURLを返す。 + * @return ベースURL + */ + public URL getBaseURL(){ + return this.baseURL; + } + + /** + * 与えられたクエリーとCGIのURLから新たにURLを合成する。 + * @param query クエリー + * @return 新たなURL + */ + protected URL getQueryURL(String query){ + if(query.length() >= 1 && query.charAt(0) != '?'){ + return null; + } + + URL result; + try{ + result = new URL(getBaseURL(), JINRO_CGI + query); + }catch(MalformedURLException e){ + assert false; + return null; + } + return result; + } + + /** + * 「Shift_JIS」でエンコーディングされた入力ストリームから文字列を生成する。 + * @param istream 入力ストリーム + * @return 文字列 + * @throws java.io.IOException 入出力エラー(おそらくネットワーク関連) + */ + public DecodedContent downloadHTMLStream(InputStream istream) + throws IOException{ + StreamDecoder decoder; + ContentBuilder builder; + if(this.charset.name().equalsIgnoreCase("Shift_JIS")){ + decoder = new SjisDecoder(); + builder = new ContentBuilderSJ(200 * 1024); + }else if(this.charset.name().equalsIgnoreCase("UTF-8")){ + decoder = new StreamDecoder(this.charset.newDecoder()); + builder = new ContentBuilderUCS2(200 * 1024); + }else{ + assert false; + return null; + } + decoder.setDecodeHandler(builder); + + // TODO デコーダをインスタンス変数にできないか。 + // TODO DecodedContentのキャッシュ管理。 + + try{ + decoder.decode(istream); + }catch(DecodeException e){ + return null; + } + + return builder.getContent(); + } + + /** + * 与えられたクエリーを用いてHTMLデータを取得する。 + * @param query HTTP-GET クエリー + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + protected HtmlSequence downloadHTML(String query) + throws IOException{ + URL url = getQueryURL(query); + HtmlSequence result = downloadHTML(url); + return result; + } + + /** + * 与えられたURLを用いてHTMLデータを取得する。 + * @param url URL + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + protected HtmlSequence downloadHTML(URL url) + throws IOException{ + HttpURLConnection connection = + (HttpURLConnection) url.openConnection(this.proxy); + connection.setRequestProperty("Accept", "*/*"); + connection.setRequestProperty("User-Agent", USER_AGENT); + connection.setUseCaches(false); + connection.setInstanceFollowRedirects(false); + connection.setDoInput(true); + connection.setRequestMethod("GET"); + + AccountCookie cookie = this.cookieAuth; + if(cookie != null){ + if(shouldAccept(url, cookie)){ + connection.setRequestProperty( + "Cookie", + "login=" + cookie.getLoginData()); + }else{ + clearAuthentication(); + } + } + + connection.connect(); + + long datems = updateLastAccess(connection); + + int responseCode = connection.getResponseCode(); + if(responseCode != HttpURLConnection.HTTP_OK){ // 200 + String logMessage = "発言のダウンロードに失敗しました。"; + logMessage += HttpUtils.formatHttpStat(connection, 0, 0); + Jindolf.logger().warn(logMessage); + return null; + } + + String cs = HttpUtils.getHTMLCharset(connection); + if(!cs.equalsIgnoreCase(this.charset.name())){ + return null; + } + + InputStream stream = TallyInputStream.getInputStream(connection); + DecodedContent html = downloadHTMLStream(stream); + + stream.close(); + connection.disconnect(); + + HtmlSequence hseq = new HtmlSequence(url, datems, html); + + return hseq; + } + + /** + * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。 + * @param url 画像URL文字列 + * @return 画像イメージ + * @throws java.io.IOException ネットワークエラー + */ + public BufferedImage downloadImage(String url) throws IOException{ + URL absolute; + try{ + URL base = getBaseURL(); + absolute = new URL(base, url); + }catch(MalformedURLException e){ + assert false; + return null; + } + + BufferedImage image; + image = getImageCache(absolute.toString()); + if(image != null) return image; + + HttpURLConnection connection = + (HttpURLConnection) absolute.openConnection(this.proxy); + connection.setRequestProperty("Accept", "*/*"); + connection.setRequestProperty("User-Agent", USER_AGENT); + connection.setUseCaches(true); + connection.setInstanceFollowRedirects(true); + connection.setDoInput(true); + connection.setRequestMethod("GET"); + + connection.connect(); + + int responseCode = connection.getResponseCode(); + if(responseCode != HttpURLConnection.HTTP_OK){ + String logMessage = "イメージのダウンロードに失敗しました。"; + logMessage += HttpUtils.formatHttpStat(connection, 0, 0); + Jindolf.logger().warn(logMessage); + return null; + } + + InputStream stream = TallyInputStream.getInputStream(connection); + image = ImageIO.read(stream); + stream.close(); + + connection.disconnect(); + + putImageCache(absolute.toString(), image); + + return image; + } + + /** + * 指定された認証情報をPOSTする。 + * @param authData 認証情報 + * @return 認証情報が受け入れられたらtrue + * @throws java.io.IOException ネットワークエラー + */ + protected boolean postAuthData(String authData) throws IOException{ + URL url = getQueryURL(""); + HttpURLConnection connection = + (HttpURLConnection) url.openConnection(this.proxy); + connection.setRequestProperty("Accept", "*/*"); + connection.setRequestProperty("User-Agent", USER_AGENT); + connection.setUseCaches(false); + connection.setInstanceFollowRedirects(false); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + + byte[] authBytes = authData.getBytes(); + + OutputStream os = TallyOutputStream.getOutputStream(connection); + os.write(authBytes); + os.flush(); + os.close(); + + updateLastAccess(connection); + + int responseCode = connection.getResponseCode(); + if(responseCode != HttpURLConnection.HTTP_MOVED_TEMP){ // 302 + String logMessage = "認証情報の送信に失敗しました。"; + Jindolf.logger().warn(logMessage); + connection.disconnect(); + return false; + } + + connection.disconnect(); + + AccountCookie loginCookie = AccountCookie.createCookie(connection); + if(loginCookie == null){ + return false; + } + + setAuthentication(loginCookie); + + Jindolf.logger().info("正しく認証が行われました。"); + + return true; + } + + /** + * トップページのHTMLデータを取得する。 + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + public HtmlSequence getHTMLTopPage() throws IOException{ + return downloadHTML(""); + } + + /** + * 国に含まれる村一覧HTMLデータを取得する。 + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + public HtmlSequence getHTMLLandList() throws IOException{ + return downloadHTML("?cmd=log"); + } + + /** + * 指定された村のPeriod一覧のHTMLデータを取得する。 + * 現在ゲーム進行中の村にも可能。 + * ※ 古国では使えないよ! + * @param village 村 + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + public HtmlSequence getHTMLBoneHead(Village village) throws IOException{ + String villageID = village.getVillageID(); + return downloadHTML("?vid=" + villageID + "&meslog="); + } + + /** + * 指定された村の最新PeriodのHTMLデータをロードする。 + * 既にGAMEOVERの村ではPeriod一覧のHTMLデータとなる。 + * @param village 村 + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + public HtmlSequence getHTMLVillage(Village village) throws IOException{ + URL url = getVillageURL(village); + return downloadHTML(url); + } + + /** + * 指定された村の最新PeriodのHTMLデータのURLを取得する。 + * @param village 村 + * @return URL + */ + public URL getVillageURL(Village village){ + String villageID = village.getVillageID(); + URL url = getQueryURL("?vid=" + villageID); + return url; + } + + /** + * 指定されたPeriodのHTMLデータをロードする。 + * @param period Period + * @return HTMLデータ + * @throws java.io.IOException ネットワークエラー + */ + public HtmlSequence getHTMLPeriod(Period period) throws IOException{ + URL url = getPeriodURL(period); + return downloadHTML(url); + } + + /** + * 指定されたPeriodのHTMLデータのURLを取得する。 + * @param period 日 + * @return URL + */ + public URL getPeriodURL(Period period){ + String query = period.getCGIQuery(); + URL url = getQueryURL(query); + return url; + } + + /** + * 最終アクセス時刻を更新する。 + * @param connection HTTP接続 + * @return リソース送信時刻 + */ + public long updateLastAccess(HttpURLConnection connection){ + this.lastServerMs = connection.getDate(); + this.lastLocalMs = System.currentTimeMillis(); + this.lastSystemMs = System.nanoTime() / (1000 * 1000); + return this.lastServerMs; + } + + /** + * 指定したURLに対しCookieを送っても良いか否か判定する。 + * 判別材料は Cookie の寿命とパス指定のみ。 + * @param url URL + * @param cookie Cookie + * @return 送ってもよければtrue + */ + private static boolean shouldAccept(URL url, AccountCookie cookie){ + if(cookie.hasExpired()){ + return false; + } + + String urlPath = url.getPath(); + String cookiePath = cookie.getPathURI().getPath(); + + if( ! urlPath.startsWith(cookiePath) ){ + return false; + } + + return true; + } + + /** + * 現在ログイン中か否か判別する。 + * @return ログイン中ならtrue + */ + // TODO interval call + public boolean hasLoggedIn(){ + AccountCookie cookie = this.cookieAuth; + if(cookie == null){ + return false; + } + if(cookie.hasExpired()){ + clearAuthentication(); + return false; + } + return true; + } + + /** + * 与えられたユーザIDとパスワードでログイン処理を行う。 + * @param userID ユーザID + * @param password パスワード + * @return ログインに成功すればtrue + * @throws java.io.IOException ネットワークエラー + */ + public final boolean login(String userID, char[] password) + throws IOException{ + if(hasLoggedIn()){ + return true; + } + + String id = formEncode(userID); + if(id == null || id.length() <= 0){ + return false; + } + + String pw = formEncode(password); + if(pw == null || pw.length() <= 0){ + return false; + } + + this.encodedUserID = id; + + String redirect = formEncode("&#bottom"); // TODO ほんとに必要? + + StringBuilder postData = new StringBuilder(); + postData.append("cmd=login"); + postData.append('&').append("cgi_param=").append(redirect); + postData.append('&').append("user_id=").append(id); + postData.append('&').append("password=").append(pw); + + boolean result; + try{ + result = postAuthData(postData.toString()); + }catch(IOException e){ + clearAuthentication(); + throw e; + } + + return result; + } + + /** + * ログアウト処理を行う。 + * @throws java.io.IOException ネットワーク入出力エラー + */ + // TODO シャットダウンフックでログアウトさせようかな… + public void logout() throws IOException{ + if(!hasLoggedIn()){ + return; + } + if(this.encodedUserID == null){ + clearAuthentication(); + return; + } + + String redirect = formEncode("&#bottom"); // TODO 必要? + + StringBuilder postData = new StringBuilder(); + postData.append("cmd=logout"); + postData.append('&').append("cgi_param=").append(redirect); + postData.append('&').append("user_id=").append(this.encodedUserID); + + try{ + postAuthData(postData.toString()); + }finally{ + clearAuthentication(); + } + + return; + } + + /** + * 認証情報クリア。 + */ + // TODO タイマーでExpire date の時刻にクリアしたい。 + protected void clearAuthentication(){ + this.cookieAuth = null; + this.encodedUserID = null; + return; + } + + /** + * 認証情報のセット。 + * @param cookie 認証Cookie + */ + private void setAuthentication(AccountCookie cookie){ + this.cookieAuth = cookie; + return; + } + + // TODO JRE1.6対応するときに HttpCookie, CookieManager 利用へ移行したい。 + +} diff --git a/src/main/java/jp/sourceforge/jindolf/StringUtils.java b/src/main/java/jp/sourceforge/jindolf/StringUtils.java index 0715d47..307dee3 100644 --- a/src/main/java/jp/sourceforge/jindolf/StringUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/StringUtils.java @@ -1,183 +1,183 @@ -/* - * string utilities - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.regex.Matcher; - -/** - * 文字列ユーティリティクラス。 - */ -public final class StringUtils{ - - private static final int SUPLEN = 5; - - - /** - * ダミーコンストラクタ。 - */ - private StringUtils(){ - super(); - assert false; - throw new AssertionError(); - } - - - /** - * 正規表現にマッチした領域を数値化する。 - * @param seq 文字列 - * @param matcher Matcher - * @param groupIndex 前方指定グループ番号 - * @return 数値 - * @throws IndexOutOfBoundsException 不正なグループ番号 - */ - public static int parseInt(CharSequence seq, - Matcher matcher, - int groupIndex ) - throws IndexOutOfBoundsException { - return parseInt(seq, - matcher.start(groupIndex), - matcher.end(groupIndex) ); - } - - /** - * 文字列を数値化する。 - * @param seq 文字列 - * @return 数値 - */ - public static int parseInt(CharSequence seq){ - return parseInt(seq, 0, seq.length()); - } - - /** - * 部分文字列を数値化する。 - * @param seq 文字列 - * @param startPos 範囲開始位置 - * @param endPos 範囲終了位置 - * @return パースした数値 - * @throws IndexOutOfBoundsException 不正な位置指定 - */ - public static int parseInt(CharSequence seq, int startPos, int endPos) - throws IndexOutOfBoundsException{ - int result = 0; - - for(int pos = startPos; pos < endPos; pos++){ - char ch = seq.charAt(pos); - int digit = Character.digit(ch, 10); - if(digit < 0) break; - result *= 10; - result += digit; - } - - return result; - } - - /** - * 長い文字列を三点リーダで省略する。 - * 「abcdefg」→「abc…efg」 - * @param str 文字列 - * @return 省略した文字列 - */ - public static CharSequence suppressString(CharSequence str){ - String result = str.toString(); - result = result.replaceAll("[\u0020\\t\\n\\r\u3000]", ""); - if(result.length() <= SUPLEN * 2) return result; - int len = result.length(); - String head = result.substring(0, SUPLEN); - String tail = result.substring(len - SUPLEN, len); - result = head + "…" + tail; - return result; - } - - /** - * ある文字列の末尾が別の文字列に一致するか判定する。 - * @see String#endsWith(String) - * @param target 判定対象 - * @param term 末尾文字 - * @return 一致すればtrue - * @throws java.lang.NullPointerException 引数がnull - */ - public static boolean isTerminated(CharSequence target, - CharSequence term) - throws NullPointerException{ - if(target == null || term == null) throw new NullPointerException(); - - int targetLength = target.length(); - int termLength = term .length(); - - int offset = targetLength - termLength; - if(offset < 0) return false; - - for(int pos = 0; pos < termLength; pos++){ - char targetch = target.charAt(offset + pos); - char termch = term .charAt(0 + pos); - if(targetch != termch) return false; - } - - return true; - } - - /** - * サブシーケンス同士を比較する。 - * @see CharSequence#subSequence(int,int) - * @param seq1 サブシーケンス1 - * @param start1 開始インデックス1 - * @param end1 終了インデックス1 - * @param seq2 サブシーケンス2 - * @param start2 開始インデックス2 - * @param end2 終了インデックス2 - * @return サブシーケンス1の方が小さければ負、大きければ正、等しければ0 - * @throws IndexOutOfBoundsException 不正なインデックス指定 - */ - public static int compareSubSequence( - CharSequence seq1, int start1, int end1, - CharSequence seq2, int start2, int end2 ) - throws IndexOutOfBoundsException{ - int pos1 = start1; - int pos2 = start2; - - for(;;){ - if(pos1 >= end1) break; - if(pos2 >= end2) break; - char ch1 = seq1.charAt(pos1); - char ch2 = seq2.charAt(pos2); - int diff = ch1 - ch2; - if(diff != 0) return diff; - pos1++; - pos2++; - } - - int length1 = end1 - start1; - int length2 = end2 - start2; - - if(length1 == length2) return 0; - if(length1 < length2) return -1; - else return +1; - } - - /** - * 文字シーケンスとサブシーケンスを比較する。 - * @see CharSequence#subSequence(int,int) - * @param seq1 文字シーケンス - * @param seq2 サブシーケンス - * @param start2 開始インデックス - * @param end2 終了インデックス - * @return 文字シーケンスの方が小さければ負、大きければ正、等しければ0 - * @throws IndexOutOfBoundsException 不正なインデックス指定 - */ - public static int compareSubSequence( - CharSequence seq1, - CharSequence seq2, int start2, int end2 ) - throws IndexOutOfBoundsException{ - int result = compareSubSequence(seq1, 0, seq1.length(), - seq2, start2, end2 ); - return result; - } - - // TODO 文字エンコーダ・デコーダ処理の一本化。 - // TODO 文字エンコーダ・デコーダのカスタム化。「~」対策など。 -} +/* + * string utilities + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.regex.Matcher; + +/** + * 文字列ユーティリティクラス。 + */ +public final class StringUtils{ + + private static final int SUPLEN = 5; + + + /** + * ダミーコンストラクタ。 + */ + private StringUtils(){ + super(); + assert false; + throw new AssertionError(); + } + + + /** + * 正規表現にマッチした領域を数値化する。 + * @param seq 文字列 + * @param matcher Matcher + * @param groupIndex 前方指定グループ番号 + * @return 数値 + * @throws IndexOutOfBoundsException 不正なグループ番号 + */ + public static int parseInt(CharSequence seq, + Matcher matcher, + int groupIndex ) + throws IndexOutOfBoundsException { + return parseInt(seq, + matcher.start(groupIndex), + matcher.end(groupIndex) ); + } + + /** + * 文字列を数値化する。 + * @param seq 文字列 + * @return 数値 + */ + public static int parseInt(CharSequence seq){ + return parseInt(seq, 0, seq.length()); + } + + /** + * 部分文字列を数値化する。 + * @param seq 文字列 + * @param startPos 範囲開始位置 + * @param endPos 範囲終了位置 + * @return パースした数値 + * @throws IndexOutOfBoundsException 不正な位置指定 + */ + public static int parseInt(CharSequence seq, int startPos, int endPos) + throws IndexOutOfBoundsException{ + int result = 0; + + for(int pos = startPos; pos < endPos; pos++){ + char ch = seq.charAt(pos); + int digit = Character.digit(ch, 10); + if(digit < 0) break; + result *= 10; + result += digit; + } + + return result; + } + + /** + * 長い文字列を三点リーダで省略する。 + * 「abcdefg」→「abc…efg」 + * @param str 文字列 + * @return 省略した文字列 + */ + public static CharSequence suppressString(CharSequence str){ + String result = str.toString(); + result = result.replaceAll("[\u0020\\t\\n\\r\u3000]", ""); + if(result.length() <= SUPLEN * 2) return result; + int len = result.length(); + String head = result.substring(0, SUPLEN); + String tail = result.substring(len - SUPLEN, len); + result = head + "…" + tail; + return result; + } + + /** + * ある文字列の末尾が別の文字列に一致するか判定する。 + * @see String#endsWith(String) + * @param target 判定対象 + * @param term 末尾文字 + * @return 一致すればtrue + * @throws java.lang.NullPointerException 引数がnull + */ + public static boolean isTerminated(CharSequence target, + CharSequence term) + throws NullPointerException{ + if(target == null || term == null) throw new NullPointerException(); + + int targetLength = target.length(); + int termLength = term .length(); + + int offset = targetLength - termLength; + if(offset < 0) return false; + + for(int pos = 0; pos < termLength; pos++){ + char targetch = target.charAt(offset + pos); + char termch = term .charAt(0 + pos); + if(targetch != termch) return false; + } + + return true; + } + + /** + * サブシーケンス同士を比較する。 + * @see CharSequence#subSequence(int,int) + * @param seq1 サブシーケンス1 + * @param start1 開始インデックス1 + * @param end1 終了インデックス1 + * @param seq2 サブシーケンス2 + * @param start2 開始インデックス2 + * @param end2 終了インデックス2 + * @return サブシーケンス1の方が小さければ負、大きければ正、等しければ0 + * @throws IndexOutOfBoundsException 不正なインデックス指定 + */ + public static int compareSubSequence( + CharSequence seq1, int start1, int end1, + CharSequence seq2, int start2, int end2 ) + throws IndexOutOfBoundsException{ + int pos1 = start1; + int pos2 = start2; + + for(;;){ + if(pos1 >= end1) break; + if(pos2 >= end2) break; + char ch1 = seq1.charAt(pos1); + char ch2 = seq2.charAt(pos2); + int diff = ch1 - ch2; + if(diff != 0) return diff; + pos1++; + pos2++; + } + + int length1 = end1 - start1; + int length2 = end2 - start2; + + if(length1 == length2) return 0; + if(length1 < length2) return -1; + else return +1; + } + + /** + * 文字シーケンスとサブシーケンスを比較する。 + * @see CharSequence#subSequence(int,int) + * @param seq1 文字シーケンス + * @param seq2 サブシーケンス + * @param start2 開始インデックス + * @param end2 終了インデックス + * @return 文字シーケンスの方が小さければ負、大きければ正、等しければ0 + * @throws IndexOutOfBoundsException 不正なインデックス指定 + */ + public static int compareSubSequence( + CharSequence seq1, + CharSequence seq2, int start2, int end2 ) + throws IndexOutOfBoundsException{ + int result = compareSubSequence(seq1, 0, seq1.length(), + seq2, start2, end2 ); + return result; + } + + // TODO 文字エンコーダ・デコーダ処理の一本化。 + // TODO 文字エンコーダ・デコーダのカスタム化。「~」対策など。 +} diff --git a/src/main/java/jp/sourceforge/jindolf/SysEvent.java b/src/main/java/jp/sourceforge/jindolf/SysEvent.java index 00f82c1..8faa8fc 100644 --- a/src/main/java/jp/sourceforge/jindolf/SysEvent.java +++ b/src/main/java/jp/sourceforge/jindolf/SysEvent.java @@ -1,247 +1,247 @@ -/* - * system event in game - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import jp.sourceforge.jindolf.corelib.EventFamily; -import jp.sourceforge.jindolf.corelib.GameRole; -import jp.sourceforge.jindolf.corelib.SysEventType; -import jp.sourceforge.jindolf.parser.DecodedContent; - -/** - * 人狼BBSシステムが生成する各種メッセージ。 - * Topicの具体化。 - */ -public class SysEvent implements Topic{ - // TODO 狼の襲撃先表示は Talk か SysEvent どちらにしよう... - - private EventFamily eventFamily; - private SysEventType sysEventType; - private DecodedContent content; - - private final List avatarList = new LinkedList(); - private final List roleList = new LinkedList(); - private final List integerList = new LinkedList(); - private final List charseqList = - new LinkedList(); - - /** - * コンストラクタ。 - */ - public SysEvent(){ - super(); - return; - } - - /** - * イベントファミリを取得する。 - * @return イベントファミリ - */ - public EventFamily getEventFamily(){ - return this.eventFamily; - } - - /** - * イベントファミリを設定する。 - * @param eventFamily イベントファミリ - * @throws NullPointerException 引数がnull - */ - public void setEventFamily(EventFamily eventFamily) - throws NullPointerException{ - this.eventFamily = eventFamily; - return; - } - - /** - * イベント種別を取得する。 - * @return イベント種別 - */ - public SysEventType getSysEventType(){ - return this.sysEventType; - } - - /** - * イベント種別を設定する。 - * @param type イベント種別 - * @throws NullPointerException 引数がnull - */ - public void setSysEventType(SysEventType type) - throws NullPointerException{ - if(type == null) throw new NullPointerException(); - this.sysEventType = type; - return; - } - - /** - * イベントメッセージを取得する。 - * @return イベントメッセージ - */ - public DecodedContent getContent(){ - return this.content; - } - - /** - * イベントメッセージを設定する。 - * @param content イベントメッセージ - * @throws NullPointerException 引数がnull - */ - public void setContent(DecodedContent content) - throws NullPointerException{ - if(content == null) throw new NullPointerException(); - this.content = content; - return; - } - - /** - * Avatarリストを取得する。 - * @return Avatarリスト - */ - public List getAvatarList(){ - List result = Collections.unmodifiableList(this.avatarList); - return result; - } - - /** - * Roleリストを取得する。 - * @return Roleリスト - */ - public List getRoleList(){ - List result = Collections.unmodifiableList(this.roleList); - return result; - } - - /** - * Integerリストを取得する。 - * @return Integerリスト - */ - public List getIntegerList(){ - List result = Collections.unmodifiableList(this.integerList); - return result; - } - - /** - * CharSequenceリストを取得する。 - * @return CharSequenceリスト - */ - public List getCharSequenceList(){ - List result = - Collections.unmodifiableList(this.charseqList); - return result; - } - - /** - * Avatar一覧を追加する。 - * @param list Avatar一覧 - */ - public void addAvatarList(List list){ - this.avatarList.addAll(list); - return; - } - - /** - * 役職一覧を追加する。 - * @param list 役職一覧 - */ - public void addRoleList(List list){ - this.roleList.addAll(list); - return; - } - - /** - * 数値一覧を追加する。 - * @param list 数値一覧 - */ - public void addIntegerList(List list){ - this.integerList.addAll(list); - return; - } - - /** - * 文字列一覧を追加する。 - * @param list 文字列一覧 - */ - public void addCharSequenceList(List list){ - this.charseqList.addAll(list); - return; - } - - /** - * システムイベントを解析し、処刑されたAvatarを返す。 - * G国運用中の時点で、処刑者が出るのはCOUNTINGとEXECUTIONのみ。 - * @return 処刑されたAvatar。いなければnull - */ - public Avatar getExecutedAvatar(){ - Avatar result = null; - - int avatarNum; - Avatar lastAvatar; - - switch(this.sysEventType){ - case COUNTING: - if(this.avatarList.isEmpty()) return null; - avatarNum = this.avatarList.size(); - if(avatarNum % 2 != 0){ - lastAvatar = this.avatarList.get(avatarNum - 1); - result = lastAvatar; - } - break; - case EXECUTION: - if(this.avatarList.isEmpty()) return null; - avatarNum = this.avatarList.size(); - List intList = getIntegerList(); - int intNum = intList.size(); - assert intNum > 0; - if(avatarNum != intNum || intList.get(intNum - 1) <= 0){ - lastAvatar = this.avatarList.get(avatarNum - 1); - result = lastAvatar; - } - break; - case COUNTING2: - // NOTHING - break; - default: - break; - } - - return result; - } - - /** - * 投票に参加したAvatarの集合を返す。 - * G国運用中の時点で、投票者が出るのはCOUNTINGとCOUNTING2のみ。 - * @param set 結果格納先。nullなら自動的に確保される。 - * @return 投票に参加したAvatarのSet - */ - public Set getVoterSet(Set set){ - Set result; - if(set == null) result = new HashSet(); - else result = set; - - if( this.sysEventType != SysEventType.COUNTING - && this.sysEventType != SysEventType.COUNTING2 ){ - return result; - } - - int size = this.avatarList.size(); - assert size >= 2; - int limit = size - 1; - if(size % 2 != 0) limit--; - - for(int idx = 0; idx <= limit; idx += 2){ - Avatar avatar = this.avatarList.get(idx); - result.add(avatar); - } - - return result; - } - -} +/* + * system event in game + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import jp.sourceforge.jindolf.corelib.EventFamily; +import jp.sourceforge.jindolf.corelib.GameRole; +import jp.sourceforge.jindolf.corelib.SysEventType; +import jp.sourceforge.jindolf.parser.DecodedContent; + +/** + * 人狼BBSシステムが生成する各種メッセージ。 + * Topicの具体化。 + */ +public class SysEvent implements Topic{ + // TODO 狼の襲撃先表示は Talk か SysEvent どちらにしよう... + + private EventFamily eventFamily; + private SysEventType sysEventType; + private DecodedContent content; + + private final List avatarList = new LinkedList(); + private final List roleList = new LinkedList(); + private final List integerList = new LinkedList(); + private final List charseqList = + new LinkedList(); + + /** + * コンストラクタ。 + */ + public SysEvent(){ + super(); + return; + } + + /** + * イベントファミリを取得する。 + * @return イベントファミリ + */ + public EventFamily getEventFamily(){ + return this.eventFamily; + } + + /** + * イベントファミリを設定する。 + * @param eventFamily イベントファミリ + * @throws NullPointerException 引数がnull + */ + public void setEventFamily(EventFamily eventFamily) + throws NullPointerException{ + this.eventFamily = eventFamily; + return; + } + + /** + * イベント種別を取得する。 + * @return イベント種別 + */ + public SysEventType getSysEventType(){ + return this.sysEventType; + } + + /** + * イベント種別を設定する。 + * @param type イベント種別 + * @throws NullPointerException 引数がnull + */ + public void setSysEventType(SysEventType type) + throws NullPointerException{ + if(type == null) throw new NullPointerException(); + this.sysEventType = type; + return; + } + + /** + * イベントメッセージを取得する。 + * @return イベントメッセージ + */ + public DecodedContent getContent(){ + return this.content; + } + + /** + * イベントメッセージを設定する。 + * @param content イベントメッセージ + * @throws NullPointerException 引数がnull + */ + public void setContent(DecodedContent content) + throws NullPointerException{ + if(content == null) throw new NullPointerException(); + this.content = content; + return; + } + + /** + * Avatarリストを取得する。 + * @return Avatarリスト + */ + public List getAvatarList(){ + List result = Collections.unmodifiableList(this.avatarList); + return result; + } + + /** + * Roleリストを取得する。 + * @return Roleリスト + */ + public List getRoleList(){ + List result = Collections.unmodifiableList(this.roleList); + return result; + } + + /** + * Integerリストを取得する。 + * @return Integerリスト + */ + public List getIntegerList(){ + List result = Collections.unmodifiableList(this.integerList); + return result; + } + + /** + * CharSequenceリストを取得する。 + * @return CharSequenceリスト + */ + public List getCharSequenceList(){ + List result = + Collections.unmodifiableList(this.charseqList); + return result; + } + + /** + * Avatar一覧を追加する。 + * @param list Avatar一覧 + */ + public void addAvatarList(List list){ + this.avatarList.addAll(list); + return; + } + + /** + * 役職一覧を追加する。 + * @param list 役職一覧 + */ + public void addRoleList(List list){ + this.roleList.addAll(list); + return; + } + + /** + * 数値一覧を追加する。 + * @param list 数値一覧 + */ + public void addIntegerList(List list){ + this.integerList.addAll(list); + return; + } + + /** + * 文字列一覧を追加する。 + * @param list 文字列一覧 + */ + public void addCharSequenceList(List list){ + this.charseqList.addAll(list); + return; + } + + /** + * システムイベントを解析し、処刑されたAvatarを返す。 + * G国運用中の時点で、処刑者が出るのはCOUNTINGとEXECUTIONのみ。 + * @return 処刑されたAvatar。いなければnull + */ + public Avatar getExecutedAvatar(){ + Avatar result = null; + + int avatarNum; + Avatar lastAvatar; + + switch(this.sysEventType){ + case COUNTING: + if(this.avatarList.isEmpty()) return null; + avatarNum = this.avatarList.size(); + if(avatarNum % 2 != 0){ + lastAvatar = this.avatarList.get(avatarNum - 1); + result = lastAvatar; + } + break; + case EXECUTION: + if(this.avatarList.isEmpty()) return null; + avatarNum = this.avatarList.size(); + List intList = getIntegerList(); + int intNum = intList.size(); + assert intNum > 0; + if(avatarNum != intNum || intList.get(intNum - 1) <= 0){ + lastAvatar = this.avatarList.get(avatarNum - 1); + result = lastAvatar; + } + break; + case COUNTING2: + // NOTHING + break; + default: + break; + } + + return result; + } + + /** + * 投票に参加したAvatarの集合を返す。 + * G国運用中の時点で、投票者が出るのはCOUNTINGとCOUNTING2のみ。 + * @param set 結果格納先。nullなら自動的に確保される。 + * @return 投票に参加したAvatarのSet + */ + public Set getVoterSet(Set set){ + Set result; + if(set == null) result = new HashSet(); + else result = set; + + if( this.sysEventType != SysEventType.COUNTING + && this.sysEventType != SysEventType.COUNTING2 ){ + return result; + } + + int size = this.avatarList.size(); + assert size >= 2; + int limit = size - 1; + if(size % 2 != 0) limit--; + + for(int idx = 0; idx <= limit; idx += 2){ + Avatar avatar = this.avatarList.get(idx); + result.add(avatar); + } + + return result; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/SysEventDraw.java b/src/main/java/jp/sourceforge/jindolf/SysEventDraw.java index 639baf6..daa0e80 100644 --- a/src/main/java/jp/sourceforge/jindolf/SysEventDraw.java +++ b/src/main/java/jp/sourceforge/jindolf/SysEventDraw.java @@ -1,226 +1,226 @@ -/* - * system event drawing - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.io.IOException; -import jp.sourceforge.jindolf.parser.DecodedContent; - -/** - * システムイベントメッセージの描画。 - */ -public class SysEventDraw extends AbstractTextRow{ - - /** announceメッセージの色。 */ - public static final Color COLOR_ANNOUNCE = new Color(0xffffff); - /** orderメッセージの色。 */ - public static final Color COLOR_ORDER = new Color(0xf04040); - /** extraメッセージの色。 */ - public static final Color COLOR_EXTRA = new Color(0x808080); - - private static final Color COLOR_SIMPLEFG = Color.BLACK; - - private static final int INSET = 10; - private static final int UNDER_MARGIN = 15; - - private final SysEvent sysEvent; - private final GlyphDraw sysMessage; - - private DialogPref dialogPref; - private Color fgColor; - - /** - * コンストラクタ。 - * @param sysEvent システムイベント - */ - public SysEventDraw(SysEvent sysEvent){ - this(sysEvent, new DialogPref(), FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param sysEvent システムイベント - * @param pref 発言表示設定 - * @param fontInfo フォント設定 - */ - public SysEventDraw(SysEvent sysEvent, - DialogPref pref, - FontInfo fontInfo){ - super(fontInfo); - this.sysEvent = sysEvent; - - DecodedContent content = this.sysEvent.getContent(); - CharSequence rawContent = content.getRawContent(); - - this.sysMessage = new GlyphDraw(rawContent, this.fontInfo); - - this.dialogPref = pref; - - setColorDesign(); - - return; - } - - /** - * 配色を設定する。 - */ - private void setColorDesign(){ - if(this.dialogPref.isSimpleMode()){ - this.fgColor = COLOR_SIMPLEFG; - }else{ - this.fgColor = getEventColor(); - } - - this.sysMessage.setColor(this.fgColor); - - return; - } - - /** - * システムイベントの取得。 - * @return システムイベント - */ - public SysEvent getSysEvent(){ - return this.sysEvent; - } - - /** - * イベント種別に応じた前景色を返す。 - * @return イベント種別前景色 - */ - private Color getEventColor(){ - Color result; - switch(this.sysEvent.getEventFamily()){ - case ANNOUNCE: - result = COLOR_ANNOUNCE; - break; - case ORDER: - result = COLOR_ORDER; - break; - case EXTRA: - result = COLOR_EXTRA; - break; - default: - assert false; - result = null; - } - return result; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Rectangle recalcBounds(){ - int newWidth = getWidth(); - - Rectangle child; - child = this.sysMessage.setWidth(newWidth - INSET - INSET); - this.bounds.width = newWidth; - this.bounds.height = child.height + INSET + INSET + UNDER_MARGIN; - return this.bounds; - } - - /** - * {@inheritDoc} - * @param fontInfo {@inheritDoc} - */ - @Override - public void setFontInfo(FontInfo fontInfo){ - super.setFontInfo(fontInfo); - this.sysMessage.setFontInfo(this.fontInfo); - recalcBounds(); - return; - } - - /** - * 発言設定を更新する。 - * @param pref 発言設定 - */ - public void setDialogPref(DialogPref pref){ - this.dialogPref = pref; - - setColorDesign(); - recalcBounds(); - - return; - } - - /** - * {@inheritDoc} - * @param from {@inheritDoc} - * @param to {@inheritDoc} - */ - @Override - public void drag(Point from, Point to){ - this.sysMessage.drag(from, to); - return; - } - - /** - * {@inheritDoc} - * @param appendable {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public Appendable appendSelected(Appendable appendable) - throws IOException{ - this.sysMessage.appendSelected(appendable); - return appendable; - } - - /** - * {@inheritDoc} - */ - @Override - public void clearSelect(){ - this.sysMessage.clearSelect(); - return; - } - - /** - * {@inheritDoc} - * @param xPos {@inheritDoc} - * @param yPos {@inheritDoc} - */ - @Override - public void setPos(int xPos, int yPos){ - super.setPos(xPos, yPos); - this.sysMessage.setPos(this.bounds.x + INSET, this.bounds.y + INSET); - return; - } - - /** - * {@inheritDoc} - * @param g {@inheritDoc} - */ - @Override - public void paint(Graphics2D g){ - g.setColor(this.fgColor); - - if(this.dialogPref.isSimpleMode()){ - g.drawLine(this.bounds.x, this.bounds.y, - this.bounds.x + this.bounds.width, this.bounds.y ); - }else{ - g.drawRect(this.bounds.x, - this.bounds.y, - this.bounds.width - 1, - this.bounds.height - UNDER_MARGIN); - } - - this.sysMessage.paint(g); - - return; - } -} +/* + * system event drawing + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; +import jp.sourceforge.jindolf.parser.DecodedContent; + +/** + * システムイベントメッセージの描画。 + */ +public class SysEventDraw extends AbstractTextRow{ + + /** announceメッセージの色。 */ + public static final Color COLOR_ANNOUNCE = new Color(0xffffff); + /** orderメッセージの色。 */ + public static final Color COLOR_ORDER = new Color(0xf04040); + /** extraメッセージの色。 */ + public static final Color COLOR_EXTRA = new Color(0x808080); + + private static final Color COLOR_SIMPLEFG = Color.BLACK; + + private static final int INSET = 10; + private static final int UNDER_MARGIN = 15; + + private final SysEvent sysEvent; + private final GlyphDraw sysMessage; + + private DialogPref dialogPref; + private Color fgColor; + + /** + * コンストラクタ。 + * @param sysEvent システムイベント + */ + public SysEventDraw(SysEvent sysEvent){ + this(sysEvent, new DialogPref(), FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param sysEvent システムイベント + * @param pref 発言表示設定 + * @param fontInfo フォント設定 + */ + public SysEventDraw(SysEvent sysEvent, + DialogPref pref, + FontInfo fontInfo){ + super(fontInfo); + this.sysEvent = sysEvent; + + DecodedContent content = this.sysEvent.getContent(); + CharSequence rawContent = content.getRawContent(); + + this.sysMessage = new GlyphDraw(rawContent, this.fontInfo); + + this.dialogPref = pref; + + setColorDesign(); + + return; + } + + /** + * 配色を設定する。 + */ + private void setColorDesign(){ + if(this.dialogPref.isSimpleMode()){ + this.fgColor = COLOR_SIMPLEFG; + }else{ + this.fgColor = getEventColor(); + } + + this.sysMessage.setColor(this.fgColor); + + return; + } + + /** + * システムイベントの取得。 + * @return システムイベント + */ + public SysEvent getSysEvent(){ + return this.sysEvent; + } + + /** + * イベント種別に応じた前景色を返す。 + * @return イベント種別前景色 + */ + private Color getEventColor(){ + Color result; + switch(this.sysEvent.getEventFamily()){ + case ANNOUNCE: + result = COLOR_ANNOUNCE; + break; + case ORDER: + result = COLOR_ORDER; + break; + case EXTRA: + result = COLOR_EXTRA; + break; + default: + assert false; + result = null; + } + return result; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Rectangle recalcBounds(){ + int newWidth = getWidth(); + + Rectangle child; + child = this.sysMessage.setWidth(newWidth - INSET - INSET); + this.bounds.width = newWidth; + this.bounds.height = child.height + INSET + INSET + UNDER_MARGIN; + return this.bounds; + } + + /** + * {@inheritDoc} + * @param fontInfo {@inheritDoc} + */ + @Override + public void setFontInfo(FontInfo fontInfo){ + super.setFontInfo(fontInfo); + this.sysMessage.setFontInfo(this.fontInfo); + recalcBounds(); + return; + } + + /** + * 発言設定を更新する。 + * @param pref 発言設定 + */ + public void setDialogPref(DialogPref pref){ + this.dialogPref = pref; + + setColorDesign(); + recalcBounds(); + + return; + } + + /** + * {@inheritDoc} + * @param from {@inheritDoc} + * @param to {@inheritDoc} + */ + @Override + public void drag(Point from, Point to){ + this.sysMessage.drag(from, to); + return; + } + + /** + * {@inheritDoc} + * @param appendable {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public Appendable appendSelected(Appendable appendable) + throws IOException{ + this.sysMessage.appendSelected(appendable); + return appendable; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSelect(){ + this.sysMessage.clearSelect(); + return; + } + + /** + * {@inheritDoc} + * @param xPos {@inheritDoc} + * @param yPos {@inheritDoc} + */ + @Override + public void setPos(int xPos, int yPos){ + super.setPos(xPos, yPos); + this.sysMessage.setPos(this.bounds.x + INSET, this.bounds.y + INSET); + return; + } + + /** + * {@inheritDoc} + * @param g {@inheritDoc} + */ + @Override + public void paint(Graphics2D g){ + g.setColor(this.fgColor); + + if(this.dialogPref.isSimpleMode()){ + g.drawLine(this.bounds.x, this.bounds.y, + this.bounds.x + this.bounds.width, this.bounds.y ); + }else{ + g.drawRect(this.bounds.x, + this.bounds.y, + this.bounds.width - 1, + this.bounds.height - UNDER_MARGIN); + } + + this.sysMessage.paint(g); + + return; + } +} diff --git a/src/main/java/jp/sourceforge/jindolf/TabBrowser.java b/src/main/java/jp/sourceforge/jindolf/TabBrowser.java index 7d12539..06e4d36 100644 --- a/src/main/java/jp/sourceforge/jindolf/TabBrowser.java +++ b/src/main/java/jp/sourceforge/jindolf/TabBrowser.java @@ -1,399 +1,399 @@ -/* - * period viewer with tab access - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.awt.event.ActionListener; -import java.util.EventListener; -import java.util.LinkedList; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.SwingConstants; -import javax.swing.border.Border; -import javax.swing.event.EventListenerList; - -/** - * タブを用いて村情報と各Periodを閲覧するためのコンポーネント。 - */ -@SuppressWarnings("serial") -public class TabBrowser extends JTabbedPane{ - - private Village village; - - private final VillageInfoPanel villageInfo = new VillageInfoPanel(); - - private FontInfo fontInfo; - private DialogPref dialogPref; - - private final EventListenerList thisListenerList = - new EventListenerList(); - - /** - * 村が指定されていない状態のタブパネルを生成する。 - */ - public TabBrowser(){ - super(); - - setTabPlacement(SwingConstants.TOP); - // Mac Aqua L&F ignore WRAP_TAB_LAYOUT - setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); - - Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - this.villageInfo.setBorder(border); - - addTab("村情報", new JScrollPane(this.villageInfo)); - - setVillage(null); - - return; - } - - /** - * 村情報閲覧用のコンポーネントを更新する。 - */ - private void updateVillageInfo(){ - Village target = getVillage(); - this.villageInfo.updateVillage(target); - return; - } - - /** - * 村情報表示タブを選択表示する。 - */ - private void selectVillageInfoTab(){ - setSelectedIndex(0); - return; - } - - /** - * 設定された村を返す。 - * @return 設定された村 - */ - public Village getVillage(){ - return this.village; - } - - /** - * 新規に村を設定する。 - * @param village 新しい村 - */ - public final void setVillage(Village village){ - if(village == null){ - if(this.village != null){ - this.village.unloadPeriods(); - } - this.village = null; - selectVillageInfoTab(); - modifyTabCount(0); - updateVillageInfo(); - return; - }else if(village != this.village){ - selectVillageInfoTab(); - } - - if(this.village != null){ - this.village.unloadPeriods(); - } - this.village = village; - - updateVillageInfo(); - - int periodNum = this.village.getPeriodSize(); - modifyTabCount(periodNum); - - for(int periodDays = 0; periodDays < periodNum; periodDays++){ - Period period = this.village.getPeriod(periodDays); - int tabIndex = periodDaysToTabIndex(periodDays); - PeriodView periodView = getPeriodView(tabIndex); - if(periodView == null){ - periodView = new PeriodView(period); - periodView.setFontInfo(this.fontInfo); - periodView.setDialogPref(this.dialogPref); - setComponentAt(tabIndex, periodView); - Discussion discussion = periodView.getDiscussion(); - for(ActionListener listener : getActionListeners()){ - discussion.addActionListener(listener); - } - for(AnchorHitListener listener : getAnchorHitListeners()){ - discussion.addAnchorHitListener(listener); - } - } - String caption = period.getCaption(); - setTitleAt(tabIndex, caption); - if(period == periodView.getPeriod()) continue; - periodView.setPeriod(period); - } - - return; - } - - /** - * 指定した数のPeriodが収まるよう必要十分なタブ数を用意する。 - * @param periods Periodの数 - */ - private void modifyTabCount(int periods){ // TODO 0でも大丈夫? - int maxPeriodDays = periods - 1; - - for(;;){ // 短ければタブ追加 - int maxTabIndex = getTabCount() - 1; - if(tabIndexToPeriodDays(maxTabIndex) >= maxPeriodDays) break; - String title = ""; - Component component = new JPanel(); - addTab(title, component); - } - - for(;;){ // 長ければ余分なタブ削除 - int maxTabIndex = getTabCount() - 1; - if(tabIndexToPeriodDays(maxTabIndex) <= maxPeriodDays) break; - remove(maxTabIndex); - } - - return; - } - - /** - * Period日付指定からタブインデックス値への変換。 - * @param days Period日付指定 - * @return タブインデックス - */ - public int periodDaysToTabIndex(int days){ - int tabIndex = days+1; - if(tabIndex >= getTabCount()) return -1; - return tabIndex; - } - - /** - * タブインデックス値からPeriod日付指定への変換。 - * @param tabIndex タブインデックス - * @return Period日付指定 - */ - private int tabIndexToPeriodDays(int tabIndex){ - if(tabIndex >= getTabCount()) return - 1; - int days = tabIndex - 1; - return days; - } - - /** - * PeriodView一覧を得る。 - * @return PeriodView の List - */ - public List getPeriodViewList(){ - List result = new LinkedList(); - - int tabCount = getTabCount(); - for(int tabIndex = 0; tabIndex <= tabCount - 1; tabIndex++){ - Component component = getComponent(tabIndex); - if(component == null) continue; - if( ! (component instanceof PeriodView) ) continue; - PeriodView periodView = (PeriodView) component; - result.add(periodView); - } - - return result; - } - - /** - * 現在タブ選択中のDiscussionを返す。 - * Periodに関係ないタブが選択されていたらnullを返す。 - * @return 現在選択中のDiscussion - */ - public Discussion currentDiscussion(){ - int tabIndex = getSelectedIndex(); - Discussion result = getDiscussion(tabIndex); - return result; - } - - /** - * 現在タブ選択中のPeriodViewを返す。 - * Periodに関係ないタブが選択されていたらnullを返す。 - * @return 現在選択中のPeriodView - */ - public PeriodView currentPeriodView(){ - int tabIndex = getSelectedIndex(); - PeriodView result = getPeriodView(tabIndex); - return result; - } - - /** - * 指定したタブインデックスに関連付けられたPeriodViewを返す。 - * Periodに関係ないタブが指定されたらnullを返す。 - * @param tabIndex タブインデックス - * @return 指定されたPeriodView - */ - public PeriodView getPeriodView(int tabIndex){ - if(tabIndexToPeriodDays(tabIndex) < 0) return null; - if(tabIndex >= getTabCount()) return null; - Component component = getComponentAt(tabIndex); - if(component == null) return null; - - if( ! (component instanceof PeriodView) ) return null; - PeriodView periodView = (PeriodView) component; - - return periodView; - } - - /** - * 指定したタブインデックスに関連付けられたDiscussionを返す。 - * Periodに関係ないタブが指定されたらnullを返す。 - * @param tabIndex タブインデックス - * @return 指定されたDiscussion - */ - private Discussion getDiscussion(int tabIndex){ - PeriodView periodView = getPeriodView(tabIndex); - if(periodView == null) return null; - - Discussion result = periodView.getDiscussion(); - return result; - } - - /** - * フォント描画設定を変更する。 - * @param fontInfo フォント - */ - public void setFontInfo(FontInfo fontInfo){ - this.fontInfo = fontInfo; - - for(int tabIndex = 0; tabIndex <= getTabCount() - 1; tabIndex++){ - PeriodView periodView = getPeriodView(tabIndex); - if(periodView == null) continue; - periodView.setFontInfo(this.fontInfo); - } - - return; - } - - /** - * 発言表示設定を変更する。 - * @param dialogPref 発言表示設定 - */ - public void setDialogPref(DialogPref dialogPref){ - this.dialogPref = dialogPref; - - for(int tabIndex = 0; tabIndex <= getTabCount() - 1; tabIndex++){ - PeriodView periodView = getPeriodView(tabIndex); - if(periodView == null) continue; - periodView.setDialogPref(this.dialogPref); - } - - return; - } - - /** - * ActionListenerを追加する。 - * @param listener リスナー - */ - public void addActionListener(ActionListener listener){ - this.thisListenerList.add(ActionListener.class, listener); - - if(this.village == null) return; - int periodNum = this.village.getPeriodSize(); - for(int periodDays = 0; periodDays < periodNum; periodDays++){ - int tabIndex = periodDaysToTabIndex(periodDays); - Discussion discussion = getDiscussion(tabIndex); - if(discussion == null) continue; - discussion.addActionListener(listener); - } - - return; - } - - /** - * ActionListenerを削除する。 - * @param listener リスナー - */ - public void removeActionListener(ActionListener listener){ - this.thisListenerList.remove(ActionListener.class, listener); - - if(this.village == null) return; - int periodNum = this.village.getPeriodSize(); - for(int periodDays = 0; periodDays < periodNum; periodDays++){ - int tabIndex = periodDaysToTabIndex(periodDays); - Discussion discussion = getDiscussion(tabIndex); - if(discussion == null) continue; - discussion.removeActionListener(listener); - } - - return; - } - - /** - * ActionListenerを列挙する。 - * @return すべてのActionListener - */ - public ActionListener[] getActionListeners(){ - return this.thisListenerList.getListeners(ActionListener.class); - } - - /** - * AnchorHitListenerを追加する。 - * @param listener リスナー - */ - public void addAnchorHitListener(AnchorHitListener listener){ - this.thisListenerList.add(AnchorHitListener.class, listener); - - if(this.village == null) return; - int periodNum = this.village.getPeriodSize(); - for(int periodDays = 0; periodDays < periodNum; periodDays++){ - int tabIndex = periodDaysToTabIndex(periodDays); - Discussion discussion = getDiscussion(tabIndex); - if(discussion == null) continue; - discussion.addAnchorHitListener(listener); - } - - return; - } - - /** - * AnchorHitListenerを削除する。 - * @param listener リスナー - */ - public void removeAnchorHitListener(AnchorHitListener listener){ - this.thisListenerList.remove(AnchorHitListener.class, listener); - - if(this.village == null) return; - int periodNum = this.village.getPeriodSize(); - for(int periodDays = 0; periodDays < periodNum; periodDays++){ - int tabIndex = periodDaysToTabIndex(periodDays); - Discussion discussion = getDiscussion(tabIndex); - if(discussion == null) continue; - discussion.removeAnchorHitListener(listener); - } - - return; - } - - /** - * AnchorHitListenerを列挙する。 - * @return すべてのAnchorHitListener - */ - public AnchorHitListener[] getAnchorHitListeners(){ - return this.thisListenerList.getListeners(AnchorHitListener.class); - } - - /** - * {@inheritDoc} - * @param {@inheritDoc} - * @param listenerType {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public T[] getListeners(Class listenerType){ - T[] result; - result = this.thisListenerList.getListeners(listenerType); - - if(result.length <= 0){ - result = super.getListeners(listenerType); - } - - return result; - } - -} +/* + * period viewer with tab access + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.awt.event.ActionListener; +import java.util.EventListener; +import java.util.LinkedList; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.event.EventListenerList; + +/** + * タブを用いて村情報と各Periodを閲覧するためのコンポーネント。 + */ +@SuppressWarnings("serial") +public class TabBrowser extends JTabbedPane{ + + private Village village; + + private final VillageInfoPanel villageInfo = new VillageInfoPanel(); + + private FontInfo fontInfo; + private DialogPref dialogPref; + + private final EventListenerList thisListenerList = + new EventListenerList(); + + /** + * 村が指定されていない状態のタブパネルを生成する。 + */ + public TabBrowser(){ + super(); + + setTabPlacement(SwingConstants.TOP); + // Mac Aqua L&F ignore WRAP_TAB_LAYOUT + setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); + + Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); + this.villageInfo.setBorder(border); + + addTab("村情報", new JScrollPane(this.villageInfo)); + + setVillage(null); + + return; + } + + /** + * 村情報閲覧用のコンポーネントを更新する。 + */ + private void updateVillageInfo(){ + Village target = getVillage(); + this.villageInfo.updateVillage(target); + return; + } + + /** + * 村情報表示タブを選択表示する。 + */ + private void selectVillageInfoTab(){ + setSelectedIndex(0); + return; + } + + /** + * 設定された村を返す。 + * @return 設定された村 + */ + public Village getVillage(){ + return this.village; + } + + /** + * 新規に村を設定する。 + * @param village 新しい村 + */ + public final void setVillage(Village village){ + if(village == null){ + if(this.village != null){ + this.village.unloadPeriods(); + } + this.village = null; + selectVillageInfoTab(); + modifyTabCount(0); + updateVillageInfo(); + return; + }else if(village != this.village){ + selectVillageInfoTab(); + } + + if(this.village != null){ + this.village.unloadPeriods(); + } + this.village = village; + + updateVillageInfo(); + + int periodNum = this.village.getPeriodSize(); + modifyTabCount(periodNum); + + for(int periodDays = 0; periodDays < periodNum; periodDays++){ + Period period = this.village.getPeriod(periodDays); + int tabIndex = periodDaysToTabIndex(periodDays); + PeriodView periodView = getPeriodView(tabIndex); + if(periodView == null){ + periodView = new PeriodView(period); + periodView.setFontInfo(this.fontInfo); + periodView.setDialogPref(this.dialogPref); + setComponentAt(tabIndex, periodView); + Discussion discussion = periodView.getDiscussion(); + for(ActionListener listener : getActionListeners()){ + discussion.addActionListener(listener); + } + for(AnchorHitListener listener : getAnchorHitListeners()){ + discussion.addAnchorHitListener(listener); + } + } + String caption = period.getCaption(); + setTitleAt(tabIndex, caption); + if(period == periodView.getPeriod()) continue; + periodView.setPeriod(period); + } + + return; + } + + /** + * 指定した数のPeriodが収まるよう必要十分なタブ数を用意する。 + * @param periods Periodの数 + */ + private void modifyTabCount(int periods){ // TODO 0でも大丈夫? + int maxPeriodDays = periods - 1; + + for(;;){ // 短ければタブ追加 + int maxTabIndex = getTabCount() - 1; + if(tabIndexToPeriodDays(maxTabIndex) >= maxPeriodDays) break; + String title = ""; + Component component = new JPanel(); + addTab(title, component); + } + + for(;;){ // 長ければ余分なタブ削除 + int maxTabIndex = getTabCount() - 1; + if(tabIndexToPeriodDays(maxTabIndex) <= maxPeriodDays) break; + remove(maxTabIndex); + } + + return; + } + + /** + * Period日付指定からタブインデックス値への変換。 + * @param days Period日付指定 + * @return タブインデックス + */ + public int periodDaysToTabIndex(int days){ + int tabIndex = days+1; + if(tabIndex >= getTabCount()) return -1; + return tabIndex; + } + + /** + * タブインデックス値からPeriod日付指定への変換。 + * @param tabIndex タブインデックス + * @return Period日付指定 + */ + private int tabIndexToPeriodDays(int tabIndex){ + if(tabIndex >= getTabCount()) return - 1; + int days = tabIndex - 1; + return days; + } + + /** + * PeriodView一覧を得る。 + * @return PeriodView の List + */ + public List getPeriodViewList(){ + List result = new LinkedList(); + + int tabCount = getTabCount(); + for(int tabIndex = 0; tabIndex <= tabCount - 1; tabIndex++){ + Component component = getComponent(tabIndex); + if(component == null) continue; + if( ! (component instanceof PeriodView) ) continue; + PeriodView periodView = (PeriodView) component; + result.add(periodView); + } + + return result; + } + + /** + * 現在タブ選択中のDiscussionを返す。 + * Periodに関係ないタブが選択されていたらnullを返す。 + * @return 現在選択中のDiscussion + */ + public Discussion currentDiscussion(){ + int tabIndex = getSelectedIndex(); + Discussion result = getDiscussion(tabIndex); + return result; + } + + /** + * 現在タブ選択中のPeriodViewを返す。 + * Periodに関係ないタブが選択されていたらnullを返す。 + * @return 現在選択中のPeriodView + */ + public PeriodView currentPeriodView(){ + int tabIndex = getSelectedIndex(); + PeriodView result = getPeriodView(tabIndex); + return result; + } + + /** + * 指定したタブインデックスに関連付けられたPeriodViewを返す。 + * Periodに関係ないタブが指定されたらnullを返す。 + * @param tabIndex タブインデックス + * @return 指定されたPeriodView + */ + public PeriodView getPeriodView(int tabIndex){ + if(tabIndexToPeriodDays(tabIndex) < 0) return null; + if(tabIndex >= getTabCount()) return null; + Component component = getComponentAt(tabIndex); + if(component == null) return null; + + if( ! (component instanceof PeriodView) ) return null; + PeriodView periodView = (PeriodView) component; + + return periodView; + } + + /** + * 指定したタブインデックスに関連付けられたDiscussionを返す。 + * Periodに関係ないタブが指定されたらnullを返す。 + * @param tabIndex タブインデックス + * @return 指定されたDiscussion + */ + private Discussion getDiscussion(int tabIndex){ + PeriodView periodView = getPeriodView(tabIndex); + if(periodView == null) return null; + + Discussion result = periodView.getDiscussion(); + return result; + } + + /** + * フォント描画設定を変更する。 + * @param fontInfo フォント + */ + public void setFontInfo(FontInfo fontInfo){ + this.fontInfo = fontInfo; + + for(int tabIndex = 0; tabIndex <= getTabCount() - 1; tabIndex++){ + PeriodView periodView = getPeriodView(tabIndex); + if(periodView == null) continue; + periodView.setFontInfo(this.fontInfo); + } + + return; + } + + /** + * 発言表示設定を変更する。 + * @param dialogPref 発言表示設定 + */ + public void setDialogPref(DialogPref dialogPref){ + this.dialogPref = dialogPref; + + for(int tabIndex = 0; tabIndex <= getTabCount() - 1; tabIndex++){ + PeriodView periodView = getPeriodView(tabIndex); + if(periodView == null) continue; + periodView.setDialogPref(this.dialogPref); + } + + return; + } + + /** + * ActionListenerを追加する。 + * @param listener リスナー + */ + public void addActionListener(ActionListener listener){ + this.thisListenerList.add(ActionListener.class, listener); + + if(this.village == null) return; + int periodNum = this.village.getPeriodSize(); + for(int periodDays = 0; periodDays < periodNum; periodDays++){ + int tabIndex = periodDaysToTabIndex(periodDays); + Discussion discussion = getDiscussion(tabIndex); + if(discussion == null) continue; + discussion.addActionListener(listener); + } + + return; + } + + /** + * ActionListenerを削除する。 + * @param listener リスナー + */ + public void removeActionListener(ActionListener listener){ + this.thisListenerList.remove(ActionListener.class, listener); + + if(this.village == null) return; + int periodNum = this.village.getPeriodSize(); + for(int periodDays = 0; periodDays < periodNum; periodDays++){ + int tabIndex = periodDaysToTabIndex(periodDays); + Discussion discussion = getDiscussion(tabIndex); + if(discussion == null) continue; + discussion.removeActionListener(listener); + } + + return; + } + + /** + * ActionListenerを列挙する。 + * @return すべてのActionListener + */ + public ActionListener[] getActionListeners(){ + return this.thisListenerList.getListeners(ActionListener.class); + } + + /** + * AnchorHitListenerを追加する。 + * @param listener リスナー + */ + public void addAnchorHitListener(AnchorHitListener listener){ + this.thisListenerList.add(AnchorHitListener.class, listener); + + if(this.village == null) return; + int periodNum = this.village.getPeriodSize(); + for(int periodDays = 0; periodDays < periodNum; periodDays++){ + int tabIndex = periodDaysToTabIndex(periodDays); + Discussion discussion = getDiscussion(tabIndex); + if(discussion == null) continue; + discussion.addAnchorHitListener(listener); + } + + return; + } + + /** + * AnchorHitListenerを削除する。 + * @param listener リスナー + */ + public void removeAnchorHitListener(AnchorHitListener listener){ + this.thisListenerList.remove(AnchorHitListener.class, listener); + + if(this.village == null) return; + int periodNum = this.village.getPeriodSize(); + for(int periodDays = 0; periodDays < periodNum; periodDays++){ + int tabIndex = periodDaysToTabIndex(periodDays); + Discussion discussion = getDiscussion(tabIndex); + if(discussion == null) continue; + discussion.removeAnchorHitListener(listener); + } + + return; + } + + /** + * AnchorHitListenerを列挙する。 + * @return すべてのAnchorHitListener + */ + public AnchorHitListener[] getAnchorHitListeners(){ + return this.thisListenerList.getListeners(AnchorHitListener.class); + } + + /** + * {@inheritDoc} + * @param {@inheritDoc} + * @param listenerType {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public T[] getListeners(Class listenerType){ + T[] result; + result = this.thisListenerList.getListeners(listenerType); + + if(result.length <= 0){ + result = super.getListeners(listenerType); + } + + return result; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Talk.java b/src/main/java/jp/sourceforge/jindolf/Talk.java index 4eec88d..2d01cf3 100644 --- a/src/main/java/jp/sourceforge/jindolf/Talk.java +++ b/src/main/java/jp/sourceforge/jindolf/Talk.java @@ -1,259 +1,259 @@ -/* - * dialogs in game - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * プレイヤーの発言。 - */ -public class Talk implements Topic{ - - private final Period homePeriod; - private final TalkType talkType; - private final Avatar avatar; - private final int talkNo; - private final String messageID; - private final int hour; - private final int minute; - private final CharSequence dialog; - private final int charNum; - private int count = -1; - - - /** - * Talkの生成。 - * @param homePeriod 発言元Period - * @param talkType 発言種別 - * @param avatar Avatar - * @param talkNo 公開発言番号。公開発言でないなら0以下の値を指定。 - * @param messageID メッセージID - * @param hour 発言時 - * @param minute 発言分 - * @param dialog 会話データ - */ - public Talk(Period homePeriod, - TalkType talkType, - Avatar avatar, - int talkNo, - String messageID, - int hour, int minute, - CharSequence dialog ){ - if( homePeriod == null - || talkType == null - || avatar == null - || messageID == null - || dialog == null ) throw new NullPointerException(); - if(hour < 0 || 23 < hour ) throw new IllegalArgumentException(); - if(minute < 0 || 59 < minute) throw new IllegalArgumentException(); - if(talkType != TalkType.PUBLIC){ - if(0 < talkNo) throw new IllegalArgumentException(); - } - - this.homePeriod = homePeriod; - this.talkType = talkType; - this.avatar = avatar; - this.talkNo = talkNo; - this.messageID = messageID; - this.hour = hour; - this.minute = minute; - this.dialog = dialog; - - this.charNum = this.dialog.length(); - - return; - } - - - /** - * 会話種別から色名への変換を行う。 - * @param type 会話種別 - * @return 色名 - */ - public static String encodeColorName(TalkType type){ - String result; - - switch(type){ - case PUBLIC: result = "白"; break; - case PRIVATE: result = "灰"; break; - case WOLFONLY: result = "赤"; break; - case GRAVE: result = "青"; break; - default: assert false; return null; - } - - return result; - } - - /** - * 発言が交わされたPeriodを返す。 - * @return Period - */ - public Period getPeriod(){ - return this.homePeriod; - } - - /** - * 発言種別を得る。 - * @return 種別 - */ - public TalkType getTalkType(){ - return this.talkType; - } - - /** - * 墓下発言か否か判定する。 - * @return 墓下発言ならtrue - */ - public boolean isGrave(){ - return this.talkType == TalkType.GRAVE; - } - - /** - * 発言種別ごとにその日(Period)の累積発言回数を返す。 - * 1から始まる。 - * @return 累積発言回数。 - */ - public int getTalkCount(){ - return this.count; - } - - /** - * 発言文字数を返す。 - * 改行(\n)は1文字。 - * @return 文字数 - */ - public int getTotalChars(){ - return this.charNum; - } - - /** - * 発言元Avatarを得る。 - * @return 発言元Avatar - */ - public Avatar getAvatar(){ - return this.avatar; - } - - /** - * 公開発言番号を取得する。 - * 公開発言番号が割り振られてなければ0以下の値を返す。 - * @return 公開発言番号 - */ - public int getTalkNo(){ - return this.talkNo; - } - - /** - * 公開発言番号の有無を返す。 - * @return 公開発言番号が割り当てられているならtrueを返す。 - */ - public boolean hasTalkNo(){ - if(0 < this.talkNo) return true; - return false; - } - - /** - * メッセージIDを取得する。 - * @return メッセージID - */ - public String getMessageID(){ - return this.messageID; - } - - /** - * メッセージIDからエポック秒(ms)に変換する。 - * @return GMT 1970-01-01 00:00:00 からのエポック秒(ms) - */ - public long getTimeFromID(){ - String epoch = this.messageID.replace("mes", ""); - long result = Long.parseLong(epoch) * 1000; - return result; - } - - /** - * 発言時を取得する。 - * @return 発言時 - */ - public int getHour(){ - return this.hour; - } - - /** - * 発言分を取得する。 - * @return 発言分 - */ - public int getMinute(){ - return this.minute; - } - - /** - * 会話データを取得する。 - * @return 会話データ - */ - public CharSequence getDialog(){ - return this.dialog; - } - - /** - * 発言種別ごとの発言回数を設定する。 - * @param count 発言回数 - */ - public void setCount(int count){ - this.count = count; - return; - } - - /** - * この会話を識別するためのアンカー文字列を生成する。 - * 例えば「3d09:56」など。 - * @return アンカー文字列 - */ - public String getAnchorNotation(){ - int day = this.homePeriod.getDay(); - - String hstr = "0"+this.hour; - hstr = hstr.substring(hstr.length() - 2); - String mstr = "0"+this.minute; - mstr = mstr.substring(mstr.length() - 2); - - return day + "d" + hstr + ":" + mstr; - } - - /** - * この会話を識別するためのG国用アンカー文字列を発言番号から生成する。 - * 例えば「>>172」など。 - * @return アンカー文字列。発言番号がなければ空文字列。 - */ - public String getAnchorNotation_G(){ - if( ! hasTalkNo() ) return ""; - return ">>" + this.talkNo; - } - - /** - * {@inheritDoc} - * 会話のString表現を返す。 - * 実体参照やHTMLタグも含まれる。 - * @return 会話のString表現 - */ - @Override - public String toString(){ - StringBuilder result = new StringBuilder(); - - result.append(this.avatar.getFullName()); - - if (this.talkType == TalkType.PUBLIC) result.append(" says "); - else if(this.talkType == TalkType.PRIVATE) result.append(" think "); - else if(this.talkType == TalkType.WOLFONLY) result.append(" howl "); - else if(this.talkType == TalkType.GRAVE) result.append(" groan "); - - result.append(this.dialog); - - return result.toString(); - } - -} +/* + * dialogs in game + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * プレイヤーの発言。 + */ +public class Talk implements Topic{ + + private final Period homePeriod; + private final TalkType talkType; + private final Avatar avatar; + private final int talkNo; + private final String messageID; + private final int hour; + private final int minute; + private final CharSequence dialog; + private final int charNum; + private int count = -1; + + + /** + * Talkの生成。 + * @param homePeriod 発言元Period + * @param talkType 発言種別 + * @param avatar Avatar + * @param talkNo 公開発言番号。公開発言でないなら0以下の値を指定。 + * @param messageID メッセージID + * @param hour 発言時 + * @param minute 発言分 + * @param dialog 会話データ + */ + public Talk(Period homePeriod, + TalkType talkType, + Avatar avatar, + int talkNo, + String messageID, + int hour, int minute, + CharSequence dialog ){ + if( homePeriod == null + || talkType == null + || avatar == null + || messageID == null + || dialog == null ) throw new NullPointerException(); + if(hour < 0 || 23 < hour ) throw new IllegalArgumentException(); + if(minute < 0 || 59 < minute) throw new IllegalArgumentException(); + if(talkType != TalkType.PUBLIC){ + if(0 < talkNo) throw new IllegalArgumentException(); + } + + this.homePeriod = homePeriod; + this.talkType = talkType; + this.avatar = avatar; + this.talkNo = talkNo; + this.messageID = messageID; + this.hour = hour; + this.minute = minute; + this.dialog = dialog; + + this.charNum = this.dialog.length(); + + return; + } + + + /** + * 会話種別から色名への変換を行う。 + * @param type 会話種別 + * @return 色名 + */ + public static String encodeColorName(TalkType type){ + String result; + + switch(type){ + case PUBLIC: result = "白"; break; + case PRIVATE: result = "灰"; break; + case WOLFONLY: result = "赤"; break; + case GRAVE: result = "青"; break; + default: assert false; return null; + } + + return result; + } + + /** + * 発言が交わされたPeriodを返す。 + * @return Period + */ + public Period getPeriod(){ + return this.homePeriod; + } + + /** + * 発言種別を得る。 + * @return 種別 + */ + public TalkType getTalkType(){ + return this.talkType; + } + + /** + * 墓下発言か否か判定する。 + * @return 墓下発言ならtrue + */ + public boolean isGrave(){ + return this.talkType == TalkType.GRAVE; + } + + /** + * 発言種別ごとにその日(Period)の累積発言回数を返す。 + * 1から始まる。 + * @return 累積発言回数。 + */ + public int getTalkCount(){ + return this.count; + } + + /** + * 発言文字数を返す。 + * 改行(\n)は1文字。 + * @return 文字数 + */ + public int getTotalChars(){ + return this.charNum; + } + + /** + * 発言元Avatarを得る。 + * @return 発言元Avatar + */ + public Avatar getAvatar(){ + return this.avatar; + } + + /** + * 公開発言番号を取得する。 + * 公開発言番号が割り振られてなければ0以下の値を返す。 + * @return 公開発言番号 + */ + public int getTalkNo(){ + return this.talkNo; + } + + /** + * 公開発言番号の有無を返す。 + * @return 公開発言番号が割り当てられているならtrueを返す。 + */ + public boolean hasTalkNo(){ + if(0 < this.talkNo) return true; + return false; + } + + /** + * メッセージIDを取得する。 + * @return メッセージID + */ + public String getMessageID(){ + return this.messageID; + } + + /** + * メッセージIDからエポック秒(ms)に変換する。 + * @return GMT 1970-01-01 00:00:00 からのエポック秒(ms) + */ + public long getTimeFromID(){ + String epoch = this.messageID.replace("mes", ""); + long result = Long.parseLong(epoch) * 1000; + return result; + } + + /** + * 発言時を取得する。 + * @return 発言時 + */ + public int getHour(){ + return this.hour; + } + + /** + * 発言分を取得する。 + * @return 発言分 + */ + public int getMinute(){ + return this.minute; + } + + /** + * 会話データを取得する。 + * @return 会話データ + */ + public CharSequence getDialog(){ + return this.dialog; + } + + /** + * 発言種別ごとの発言回数を設定する。 + * @param count 発言回数 + */ + public void setCount(int count){ + this.count = count; + return; + } + + /** + * この会話を識別するためのアンカー文字列を生成する。 + * 例えば「3d09:56」など。 + * @return アンカー文字列 + */ + public String getAnchorNotation(){ + int day = this.homePeriod.getDay(); + + String hstr = "0"+this.hour; + hstr = hstr.substring(hstr.length() - 2); + String mstr = "0"+this.minute; + mstr = mstr.substring(mstr.length() - 2); + + return day + "d" + hstr + ":" + mstr; + } + + /** + * この会話を識別するためのG国用アンカー文字列を発言番号から生成する。 + * 例えば「>>172」など。 + * @return アンカー文字列。発言番号がなければ空文字列。 + */ + public String getAnchorNotation_G(){ + if( ! hasTalkNo() ) return ""; + return ">>" + this.talkNo; + } + + /** + * {@inheritDoc} + * 会話のString表現を返す。 + * 実体参照やHTMLタグも含まれる。 + * @return 会話のString表現 + */ + @Override + public String toString(){ + StringBuilder result = new StringBuilder(); + + result.append(this.avatar.getFullName()); + + if (this.talkType == TalkType.PUBLIC) result.append(" says "); + else if(this.talkType == TalkType.PRIVATE) result.append(" think "); + else if(this.talkType == TalkType.WOLFONLY) result.append(" howl "); + else if(this.talkType == TalkType.GRAVE) result.append(" groan "); + + result.append(this.dialog); + + return result.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TalkDraw.java b/src/main/java/jp/sourceforge/jindolf/TalkDraw.java index 65ca023..1c66c90 100644 --- a/src/main/java/jp/sourceforge/jindolf/TalkDraw.java +++ b/src/main/java/jp/sourceforge/jindolf/TalkDraw.java @@ -1,723 +1,723 @@ -/* - * 会話部描画 - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.text.DateFormat; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Pattern; -import jp.sourceforge.jindolf.corelib.TalkType; - -/** - * 会話部の描画。 - * 会話部描画領域は、キャプション部と発言部から構成される。 - */ -public class TalkDraw extends AbstractTextRow{ - - /** 通常会話の色。 */ - public static final Color COLOR_PUBLIC = new Color(0xffffff); - /** 狼間ささやきの色。 */ - public static final Color COLOR_WOLFONLY = new Color(0xff7777); - /** 灰色発言の色。 */ - public static final Color COLOR_PRIVATE = new Color(0x939393); - /** 墓下発言の色。 */ - public static final Color COLOR_GRAVE = new Color(0x9fb7cf); - - private static final Color COLOR_CAPTIONFG = Color.WHITE; - private static final Color COLOR_DIALOGFG = Color.BLACK; - - private static final Color COLOR_SIMPLEFG = Color.BLACK; - private static final Color COLOR_SIMPLEBG = Color.WHITE; - - private static final int BALOONTIP_WIDTH = 16; - private static final int BALOONTIP_HEIGHT = 8; - private static final int UPPER_MARGIN = 5; - private static final int UNDER_MARGIN = 10; - private static final int OFFSET_ANCHOR = 36; - private static final int CAPTION_DIALOG_GAP = 3; - - private static final Color COLOR_TRANS = new Color(0, 0, 0, 0); - private static final int BALOON_R = 10; - private static final BufferedImage BALOON_PUBLIC; - private static final BufferedImage BALOON_WOLFONLY; - private static final BufferedImage BALOON_GRAVE; - private static final BufferedImage BALOON_PRIVATE; - private static final BufferedImage SQUARE_PUBLIC; - private static final BufferedImage SQUARE_WOLFONLY; - private static final BufferedImage SQUARE_GRAVE; - private static final BufferedImage SQUARE_PRIVATE; - - private static final float ANCHOR_FONT_RATIO = 0.9f; - - static{ - BALOON_PUBLIC = createWedgeImage(COLOR_PUBLIC); - BALOON_WOLFONLY = createBubbleImage(COLOR_WOLFONLY); - BALOON_PRIVATE = createBubbleImage(COLOR_PRIVATE); - BALOON_GRAVE = createBubbleImage(COLOR_GRAVE); - SQUARE_PUBLIC = createSquareImage(COLOR_PUBLIC); - SQUARE_WOLFONLY = createSquareImage(COLOR_WOLFONLY); - SQUARE_GRAVE = createSquareImage(COLOR_GRAVE); - SQUARE_PRIVATE = createSquareImage(COLOR_PRIVATE); - } - - - private final Talk talk; - private Anchor showingAnchor; - - private final GlyphDraw caption; - private BufferedImage faceImage; - private final GlyphDraw dialog; - private final List anchorTalks = new LinkedList(); - private Point imageOrigin; - private Point dialogOrigin; - private Point tipOrigin; - private int baloonWidth; - private int baloonHeight; - - private FontInfo anchorFontInfo; - private DialogPref dialogPref; - - - /** - * コンストラクタ。 - * @param talk 一発言 - */ - public TalkDraw(Talk talk){ - this(talk, new DialogPref(), FontInfo.DEFAULT_FONTINFO); - return; - } - - /** - * コンストラクタ。 - * @param talk 一発言 - * @param dialogPref 発言表示設定 - * @param fontInfo フォント設定 - */ - public TalkDraw(Talk talk, DialogPref dialogPref, FontInfo fontInfo){ - super(fontInfo); - - this.talk = talk; - this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo); - this.dialogPref = dialogPref; - - this.faceImage = getFaceImage(); - this.caption = new GlyphDraw(getCaptionString(), this.fontInfo); - this.dialog = new GlyphDraw(this.talk.getDialog(), this.fontInfo); - - setColorDesign(); - - Period period = this.talk.getPeriod(); - List anchorList = Anchor.getAnchorList(this.talk.getDialog(), - period.getDay() ); - this.dialog.setAnchorSet(anchorList); - - return; - } - - - /** - * 指定した色で描画したクサビイメージを取得する。 - * @param color 色 - * @return クサビイメージ - */ - private static BufferedImage createWedgeImage(Color color){ - BufferedImage image; - image = new BufferedImage(BALOONTIP_WIDTH, - BALOONTIP_HEIGHT, - BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = image.createGraphics(); - RenderingHints renderHints = GUIUtils.getQualityHints(); - g2.addRenderingHints(renderHints); - g2.setColor(COLOR_TRANS); - g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); - g2.setColor(color); - Polygon poly = new Polygon(); - poly.addPoint(8, 8); - poly.addPoint(16, 8); - poly.addPoint(16, 0); - g2.fillPolygon(poly); - return image; - } - - /** - * 指定した色で描画した泡イメージを取得する。 - * @param color 色 - * @return 泡イメージ - */ - private static BufferedImage createBubbleImage(Color color){ - BufferedImage image; - image = new BufferedImage(BALOONTIP_WIDTH, - BALOONTIP_HEIGHT, - BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = image.createGraphics(); - RenderingHints renderHints = GUIUtils.getQualityHints(); - g2.addRenderingHints(renderHints); - g2.setColor(COLOR_TRANS); - g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); - g2.setColor(color); - g2.fillOval(2, 4, 4, 4); - g2.fillOval(8, 2, 6, 6); - return image; - } - - /** - * 指定した色で描画した長方形イメージを返す。 - * @param color 色 - * @return 長方形イメージ - */ - private static BufferedImage createSquareImage(Color color){ - BufferedImage image; - image = new BufferedImage(BALOONTIP_WIDTH, - BALOONTIP_HEIGHT, - BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = image.createGraphics(); - RenderingHints renderHints = GUIUtils.getQualityHints(); - g2.addRenderingHints(renderHints); - g2.setColor(color); - g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); - return image; - } - - /** - * 会話表示用フォントからアンカー表示用フォントを派生させる。 - * @param font 派生元フォント - * @return 派生先フォント - */ - private static Font deriveAnchorFont(Font font){ - float fontSize = font.getSize2D(); - float newSize = fontSize * ANCHOR_FONT_RATIO; - return font.deriveFont(newSize); - } - - /** - * 会話表示用フォント設定からアンカー表示用フォント設定を派生させる。 - * @param info 派生元フォント設定 - * @return 派生先フォント設定 - */ - private static FontInfo deriveAnchorFontInfo(FontInfo info){ - Font newFont = deriveAnchorFont(info.getFont()); - FontInfo result = info.deriveFont(newFont); - return result; - } - - /** - * 発言種別毎の色を返す。 - * @param type 発言種別 - * @return 色 - */ - public static Color getTypedColor(TalkType type){ - Color result; - - switch(type){ - case PUBLIC: result = TalkDraw.COLOR_PUBLIC; break; - case WOLFONLY: result = TalkDraw.COLOR_WOLFONLY; break; - case GRAVE: result = TalkDraw.COLOR_GRAVE; break; - case PRIVATE: result = TalkDraw.COLOR_PRIVATE; break; - default: return null; - } - - return result; - } - - /** - * 配色を設定する。 - */ - private void setColorDesign(){ - if(this.dialogPref.isSimpleMode()){ - this.caption.setColor(COLOR_SIMPLEFG); - }else{ - this.caption.setColor(COLOR_CAPTIONFG); - } - - this.dialog.setColor(COLOR_DIALOGFG); - - return; - } - - /** - * Talk取得。 - * @return Talkインスタンス - */ - public Talk getTalk(){ - return this.talk; - } - - /** - * 顔イメージを返す。 - * @return 顔イメージ - */ - private BufferedImage getFaceImage(){ - Village village = this.talk.getPeriod().getVillage(); - Avatar avatar = this.talk.getAvatar(); - - boolean useBodyImage = this.dialogPref.useBodyImage(); - boolean useMonoImage = this.dialogPref.useMonoImage(); - - BufferedImage image; - if(this.talk.isGrave()){ - if(useMonoImage){ - if(useBodyImage){ - image = village.getAvatarBodyMonoImage(avatar); - }else{ - image = village.getAvatarFaceMonoImage(avatar); - } - }else{ - if(useBodyImage){ - image = village.getGraveBodyImage(); - }else{ - image = village.getGraveImage(); - } - } - }else{ - if(useBodyImage){ - image = village.getAvatarBodyImage(avatar); - }else{ - image = village.getAvatarFaceImage(avatar); - } - } - - return image; - } - - /** - * キャプション文字列を取得する。 - * @return キャプション文字列 - */ - private CharSequence getCaptionString(){ - StringBuilder result = new StringBuilder(); - - Avatar avatar = this.talk.getAvatar(); - - if(this.talk.hasTalkNo()){ - result.append(this.talk.getAnchorNotation_G()).append(' '); - } - result.append(avatar.getFullName()).append(' '); - result.append(this.talk.getAnchorNotation()); - result.append('\n'); - - DateFormat dform = - DateFormat.getDateTimeInstance(DateFormat.MEDIUM, - DateFormat.MEDIUM); - long epoch = this.talk.getTimeFromID(); - String decoded = dform.format(epoch); - result.append(decoded); - - int count = this.talk.getTalkCount(); - if(count > 0){ - TalkType type = this.talk.getTalkType(); - result.append(" (").append(Talk.encodeColorName(type)); - result.append('#').append(count).append(')'); - } - - int charNum = this.talk.getTotalChars(); - if(charNum > 0){ - result.append(' ').append(charNum).append('字'); - } - - return result; - } - - /** - * 会話部背景色を返す。 - * @return 会話部背景色 - */ - protected Color getTalkBgColor(){ - if(this.dialogPref.isSimpleMode()) return COLOR_SIMPLEBG; - - TalkType type = this.talk.getTalkType(); - Color result = getTypedColor(type); - - return result; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Rectangle recalcBounds(){ - int newWidth = getWidth(); - - int imageWidth = 0; - int imageHeight = 0; - if( ! this.dialogPref.isSimpleMode()){ - imageWidth = this.faceImage.getWidth(null); - imageHeight = this.faceImage.getHeight(null); - } - - int tipWidth = BALOON_WOLFONLY.getWidth(); - - int modWidth; - int minWidth = imageWidth + tipWidth + BALOON_R * 2; - if(newWidth < minWidth) modWidth = minWidth; - else modWidth = newWidth; - - this.caption.setWidth(modWidth); - int captionWidth = this.caption.getWidth(); - int captionHeight = this.caption.getHeight() + CAPTION_DIALOG_GAP; - - this.dialog.setWidth(modWidth - minWidth); - int dialogWidth = this.dialog.getWidth(); - int dialogHeight = this.dialog.getHeight(); - - if(this.dialogPref.alignBaloonWidth()){ - this.baloonWidth = (modWidth - minWidth) + BALOON_R * 2; - }else{ - this.baloonWidth = dialogWidth + BALOON_R * 2; - } - this.baloonHeight = dialogHeight + BALOON_R * 2; - - int imageAndDialogWidth = imageWidth + tipWidth + this.baloonWidth; - - int totalWidth = Math.max(captionWidth, imageAndDialogWidth); - - int totalHeight = captionHeight; - totalHeight += Math.max(imageHeight, this.baloonHeight); - - int imageYpos = captionHeight; - int dialogYpos = captionHeight; - int tipYpos = captionHeight; - if(imageHeight < this.baloonHeight){ - imageYpos += (this.baloonHeight - imageHeight) / 2; - tipYpos += (this.baloonHeight - BALOON_WOLFONLY.getHeight()) / 2; - dialogYpos += BALOON_R; - }else{ - dialogYpos += (imageHeight - this.baloonHeight) / 2 + BALOON_R; - tipYpos += (imageHeight - BALOON_WOLFONLY.getHeight()) / 2; - } - - this.imageOrigin = new Point(0, imageYpos); - this.caption.setPos(this.bounds.x + 0, this.bounds.y + 0); - this.dialogOrigin = - new Point(imageWidth+tipWidth+BALOON_R, dialogYpos); - this.dialog.setPos(this.bounds.x + imageWidth+tipWidth+BALOON_R, - this.bounds.y + dialogYpos); - this.tipOrigin = new Point(imageWidth, tipYpos); - - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.setWidth(modWidth - OFFSET_ANCHOR); - totalHeight += anchorDraw.getHeight(); - } - - if( this.dialogPref.isSimpleMode() - || this.dialogPref.alignBaloonWidth() ){ - this.bounds.width = newWidth; - }else{ - this.bounds.width = totalWidth; - } - this.bounds.height = UPPER_MARGIN + totalHeight + UNDER_MARGIN; - - return this.bounds; - } - - /** - * {@inheritDoc} - * @param xPos {@inheritDoc} - * @param yPos {@inheritDoc} - */ - @Override - public void setPos(int xPos, int yPos){ - super.setPos(xPos, yPos); - this.caption.setPos(this.bounds.x, this.bounds.y + UPPER_MARGIN); - this.dialog.setPos(this.bounds.x + this.dialogOrigin.x, - this.bounds.y + this.dialogOrigin.y - + UPPER_MARGIN); - return; - } - - /** - * アイコンイメージとフキダシを繋ぐ補助イメージを返す。 - * @return 補助イメージ - */ - private BufferedImage getTipImage(){ - BufferedImage tip; - - TalkType type = this.talk.getTalkType(); - - if(this.dialogPref.isSimpleMode()){ - switch(type){ - case PUBLIC: tip = SQUARE_PUBLIC; break; - case WOLFONLY: tip = SQUARE_WOLFONLY; break; - case GRAVE: tip = SQUARE_GRAVE; break; - case PRIVATE: tip = SQUARE_PRIVATE; break; - default: - assert false; - tip = null; - break; - } - }else{ - switch(type){ - case PUBLIC: tip = BALOON_PUBLIC; break; - case WOLFONLY: tip = BALOON_WOLFONLY; break; - case GRAVE: tip = BALOON_GRAVE; break; - case PRIVATE: tip = BALOON_PRIVATE; break; - default: - assert false; - tip = null; - break; - } - } - - return tip; - } - - /** - * {@inheritDoc} - * @param g {@inheritDoc} - */ - @Override - public void paint(Graphics2D g){ - final int xPos = this.bounds.x; - final int yPos = this.bounds.y + UPPER_MARGIN; - - this.caption.paint(g); - - if(this.dialogPref.isSimpleMode() ){ - g.drawLine(xPos, this.bounds.y, - xPos + this.bounds.width, this.bounds.y ); - }else{ - g.drawImage(this.faceImage, - xPos + this.imageOrigin.x, - yPos + this.imageOrigin.y, - null ); - } - - BufferedImage tip = getTipImage(); - g.drawImage(tip, - xPos + this.tipOrigin.x, - yPos + this.tipOrigin.y, - null ); - - g.setColor(getTalkBgColor()); - g.fillRoundRect( - xPos + this.dialogOrigin.x - BALOON_R, - yPos + this.dialogOrigin.y - BALOON_R, - this.baloonWidth, - this.baloonHeight, - BALOON_R, - BALOON_R ); - - this.dialog.paint(g); - - int anchorX = xPos + OFFSET_ANCHOR; - int anchorY = yPos + this.dialogOrigin.y + baloonHeight; - - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.setPos(anchorX, anchorY); - anchorDraw.paint(g); - anchorY += anchorDraw.getHeight(); - } - - return; - } - - /** - * {@inheritDoc} - * @param fontInfo {@inheritDoc} - */ - @Override - public void setFontInfo(FontInfo fontInfo){ - super.setFontInfo(fontInfo); - - this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo); - - this.caption.setFontInfo(this.fontInfo); - this.dialog .setFontInfo(this.fontInfo); - - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.setFontInfo(this.anchorFontInfo); - } - - recalcBounds(); - - return; - } - - /** - * 発言設定を更新する。 - * @param pref 発言設定 - */ - public void setDialogPref(DialogPref pref){ - this.dialogPref = pref; - this.faceImage = getFaceImage(); - - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.setDialogPref(this.dialogPref); - } - - setColorDesign(); - recalcBounds(); - - return; - } - - /** - * {@inheritDoc} - * @param from {@inheritDoc} - * @param to {@inheritDoc} - */ - @Override - public void drag(Point from, Point to){ - this.caption.drag(from, to); - this.dialog.drag(from, to); - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.drag(from, to); - } - return; - } - - /** - * {@inheritDoc} - * @param appendable {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public Appendable appendSelected(Appendable appendable) - throws IOException{ - this.caption.appendSelected(appendable); - this.dialog .appendSelected(appendable); - - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.appendSelected(appendable); - } - - return appendable; - } - - /** - * {@inheritDoc} - */ - @Override - public void clearSelect(){ - this.caption.clearSelect(); - this.dialog.clearSelect(); - for(AnchorDraw anchorDraw : this.anchorTalks){ - anchorDraw.clearSelect(); - } - return; - } - - /** - * 与えられた座標にアンカー文字列が存在すればAnchorを返す。 - * @param pt 座標 - * @return アンカー - */ - public Anchor getAnchor(Point pt){ - Anchor result = this.dialog.getAnchor(pt); - return result; - } - - /** - * アンカーを展開表示する。 - * アンカーにnullを指定すればアンカー表示は非表示となる。 - * @param anchor アンカー - * @param talkList アンカーの示す一連のTalk - */ - public void showAnchorTalks(Anchor anchor, List talkList){ - if(anchor == null || this.showingAnchor == anchor){ - this.showingAnchor = null; - this.anchorTalks.clear(); - recalcBounds(); - return; - } - - this.showingAnchor = anchor; - - this.anchorTalks.clear(); - for(Talk anchorTalk : talkList){ - AnchorDraw anchorDraw = - new AnchorDraw(anchorTalk, - this.dialogPref, - this.anchorFontInfo ); - this.anchorTalks.add(anchorDraw); - } - - recalcBounds(); - - return; - } - - /** - * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。 - * @param pt 座標 - * @return 検索マッチインデックス - */ - public int getRegexMatchIndex(Point pt){ - int index = this.dialog.getRegexMatchIndex(pt); - return index; - } - - /** - * 検索文字列パターンを設定する。 - * @param searchRegex パターン - * @return ヒット数 - */ - public int setRegex(Pattern searchRegex){ - int total = 0; - - total += this.dialog.setRegex(searchRegex); -/* - for(AnchorDraw anchorDraw : this.anchorTalks){ - total += anchorDraw.setRegex(searchRegex); - } -*/ // TODO よくわからんので保留 - return total; - } - - /** - * 検索ハイライトインデックスを返す。 - * @return 検索ハイライトインデックス。見つからなければ-1。 - */ - public int getHotTargetIndex(){ - return this.dialog.getHotTargetIndex(); - } - - /** - * 検索ハイライトを設定する。 - * @param index ハイライトインデックス。負ならハイライト全クリア。 - */ - public void setHotTargetIndex(int index){ - this.dialog.setHotTargetIndex(index); - return; - } - - /** - * 検索一致件数を返す。 - * @return 検索一致件数 - */ - public int getRegexMatches(){ - return this.dialog.getRegexMatches(); - } - - /** - * 特別な検索ハイライト描画をクリアする。 - */ - public void clearHotTarget(){ - this.dialog.clearHotTarget(); - return; - } - - /** - * 特別な検索ハイライト領域の寸法を返す。 - * @return ハイライト領域寸法 - */ - public Rectangle getHotTargetRectangle(){ - return this.dialog.getHotTargetRectangle(); - } - -} +/* + * 会話部描画 + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.DateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; +import jp.sourceforge.jindolf.corelib.TalkType; + +/** + * 会話部の描画。 + * 会話部描画領域は、キャプション部と発言部から構成される。 + */ +public class TalkDraw extends AbstractTextRow{ + + /** 通常会話の色。 */ + public static final Color COLOR_PUBLIC = new Color(0xffffff); + /** 狼間ささやきの色。 */ + public static final Color COLOR_WOLFONLY = new Color(0xff7777); + /** 灰色発言の色。 */ + public static final Color COLOR_PRIVATE = new Color(0x939393); + /** 墓下発言の色。 */ + public static final Color COLOR_GRAVE = new Color(0x9fb7cf); + + private static final Color COLOR_CAPTIONFG = Color.WHITE; + private static final Color COLOR_DIALOGFG = Color.BLACK; + + private static final Color COLOR_SIMPLEFG = Color.BLACK; + private static final Color COLOR_SIMPLEBG = Color.WHITE; + + private static final int BALOONTIP_WIDTH = 16; + private static final int BALOONTIP_HEIGHT = 8; + private static final int UPPER_MARGIN = 5; + private static final int UNDER_MARGIN = 10; + private static final int OFFSET_ANCHOR = 36; + private static final int CAPTION_DIALOG_GAP = 3; + + private static final Color COLOR_TRANS = new Color(0, 0, 0, 0); + private static final int BALOON_R = 10; + private static final BufferedImage BALOON_PUBLIC; + private static final BufferedImage BALOON_WOLFONLY; + private static final BufferedImage BALOON_GRAVE; + private static final BufferedImage BALOON_PRIVATE; + private static final BufferedImage SQUARE_PUBLIC; + private static final BufferedImage SQUARE_WOLFONLY; + private static final BufferedImage SQUARE_GRAVE; + private static final BufferedImage SQUARE_PRIVATE; + + private static final float ANCHOR_FONT_RATIO = 0.9f; + + static{ + BALOON_PUBLIC = createWedgeImage(COLOR_PUBLIC); + BALOON_WOLFONLY = createBubbleImage(COLOR_WOLFONLY); + BALOON_PRIVATE = createBubbleImage(COLOR_PRIVATE); + BALOON_GRAVE = createBubbleImage(COLOR_GRAVE); + SQUARE_PUBLIC = createSquareImage(COLOR_PUBLIC); + SQUARE_WOLFONLY = createSquareImage(COLOR_WOLFONLY); + SQUARE_GRAVE = createSquareImage(COLOR_GRAVE); + SQUARE_PRIVATE = createSquareImage(COLOR_PRIVATE); + } + + + private final Talk talk; + private Anchor showingAnchor; + + private final GlyphDraw caption; + private BufferedImage faceImage; + private final GlyphDraw dialog; + private final List anchorTalks = new LinkedList(); + private Point imageOrigin; + private Point dialogOrigin; + private Point tipOrigin; + private int baloonWidth; + private int baloonHeight; + + private FontInfo anchorFontInfo; + private DialogPref dialogPref; + + + /** + * コンストラクタ。 + * @param talk 一発言 + */ + public TalkDraw(Talk talk){ + this(talk, new DialogPref(), FontInfo.DEFAULT_FONTINFO); + return; + } + + /** + * コンストラクタ。 + * @param talk 一発言 + * @param dialogPref 発言表示設定 + * @param fontInfo フォント設定 + */ + public TalkDraw(Talk talk, DialogPref dialogPref, FontInfo fontInfo){ + super(fontInfo); + + this.talk = talk; + this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo); + this.dialogPref = dialogPref; + + this.faceImage = getFaceImage(); + this.caption = new GlyphDraw(getCaptionString(), this.fontInfo); + this.dialog = new GlyphDraw(this.talk.getDialog(), this.fontInfo); + + setColorDesign(); + + Period period = this.talk.getPeriod(); + List anchorList = Anchor.getAnchorList(this.talk.getDialog(), + period.getDay() ); + this.dialog.setAnchorSet(anchorList); + + return; + } + + + /** + * 指定した色で描画したクサビイメージを取得する。 + * @param color 色 + * @return クサビイメージ + */ + private static BufferedImage createWedgeImage(Color color){ + BufferedImage image; + image = new BufferedImage(BALOONTIP_WIDTH, + BALOONTIP_HEIGHT, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = image.createGraphics(); + RenderingHints renderHints = GUIUtils.getQualityHints(); + g2.addRenderingHints(renderHints); + g2.setColor(COLOR_TRANS); + g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); + g2.setColor(color); + Polygon poly = new Polygon(); + poly.addPoint(8, 8); + poly.addPoint(16, 8); + poly.addPoint(16, 0); + g2.fillPolygon(poly); + return image; + } + + /** + * 指定した色で描画した泡イメージを取得する。 + * @param color 色 + * @return 泡イメージ + */ + private static BufferedImage createBubbleImage(Color color){ + BufferedImage image; + image = new BufferedImage(BALOONTIP_WIDTH, + BALOONTIP_HEIGHT, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = image.createGraphics(); + RenderingHints renderHints = GUIUtils.getQualityHints(); + g2.addRenderingHints(renderHints); + g2.setColor(COLOR_TRANS); + g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); + g2.setColor(color); + g2.fillOval(2, 4, 4, 4); + g2.fillOval(8, 2, 6, 6); + return image; + } + + /** + * 指定した色で描画した長方形イメージを返す。 + * @param color 色 + * @return 長方形イメージ + */ + private static BufferedImage createSquareImage(Color color){ + BufferedImage image; + image = new BufferedImage(BALOONTIP_WIDTH, + BALOONTIP_HEIGHT, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = image.createGraphics(); + RenderingHints renderHints = GUIUtils.getQualityHints(); + g2.addRenderingHints(renderHints); + g2.setColor(color); + g2.fillRect(0, 0, BALOONTIP_WIDTH, BALOONTIP_HEIGHT); + return image; + } + + /** + * 会話表示用フォントからアンカー表示用フォントを派生させる。 + * @param font 派生元フォント + * @return 派生先フォント + */ + private static Font deriveAnchorFont(Font font){ + float fontSize = font.getSize2D(); + float newSize = fontSize * ANCHOR_FONT_RATIO; + return font.deriveFont(newSize); + } + + /** + * 会話表示用フォント設定からアンカー表示用フォント設定を派生させる。 + * @param info 派生元フォント設定 + * @return 派生先フォント設定 + */ + private static FontInfo deriveAnchorFontInfo(FontInfo info){ + Font newFont = deriveAnchorFont(info.getFont()); + FontInfo result = info.deriveFont(newFont); + return result; + } + + /** + * 発言種別毎の色を返す。 + * @param type 発言種別 + * @return 色 + */ + public static Color getTypedColor(TalkType type){ + Color result; + + switch(type){ + case PUBLIC: result = TalkDraw.COLOR_PUBLIC; break; + case WOLFONLY: result = TalkDraw.COLOR_WOLFONLY; break; + case GRAVE: result = TalkDraw.COLOR_GRAVE; break; + case PRIVATE: result = TalkDraw.COLOR_PRIVATE; break; + default: return null; + } + + return result; + } + + /** + * 配色を設定する。 + */ + private void setColorDesign(){ + if(this.dialogPref.isSimpleMode()){ + this.caption.setColor(COLOR_SIMPLEFG); + }else{ + this.caption.setColor(COLOR_CAPTIONFG); + } + + this.dialog.setColor(COLOR_DIALOGFG); + + return; + } + + /** + * Talk取得。 + * @return Talkインスタンス + */ + public Talk getTalk(){ + return this.talk; + } + + /** + * 顔イメージを返す。 + * @return 顔イメージ + */ + private BufferedImage getFaceImage(){ + Village village = this.talk.getPeriod().getVillage(); + Avatar avatar = this.talk.getAvatar(); + + boolean useBodyImage = this.dialogPref.useBodyImage(); + boolean useMonoImage = this.dialogPref.useMonoImage(); + + BufferedImage image; + if(this.talk.isGrave()){ + if(useMonoImage){ + if(useBodyImage){ + image = village.getAvatarBodyMonoImage(avatar); + }else{ + image = village.getAvatarFaceMonoImage(avatar); + } + }else{ + if(useBodyImage){ + image = village.getGraveBodyImage(); + }else{ + image = village.getGraveImage(); + } + } + }else{ + if(useBodyImage){ + image = village.getAvatarBodyImage(avatar); + }else{ + image = village.getAvatarFaceImage(avatar); + } + } + + return image; + } + + /** + * キャプション文字列を取得する。 + * @return キャプション文字列 + */ + private CharSequence getCaptionString(){ + StringBuilder result = new StringBuilder(); + + Avatar avatar = this.talk.getAvatar(); + + if(this.talk.hasTalkNo()){ + result.append(this.talk.getAnchorNotation_G()).append(' '); + } + result.append(avatar.getFullName()).append(' '); + result.append(this.talk.getAnchorNotation()); + result.append('\n'); + + DateFormat dform = + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, + DateFormat.MEDIUM); + long epoch = this.talk.getTimeFromID(); + String decoded = dform.format(epoch); + result.append(decoded); + + int count = this.talk.getTalkCount(); + if(count > 0){ + TalkType type = this.talk.getTalkType(); + result.append(" (").append(Talk.encodeColorName(type)); + result.append('#').append(count).append(')'); + } + + int charNum = this.talk.getTotalChars(); + if(charNum > 0){ + result.append(' ').append(charNum).append('字'); + } + + return result; + } + + /** + * 会話部背景色を返す。 + * @return 会話部背景色 + */ + protected Color getTalkBgColor(){ + if(this.dialogPref.isSimpleMode()) return COLOR_SIMPLEBG; + + TalkType type = this.talk.getTalkType(); + Color result = getTypedColor(type); + + return result; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Rectangle recalcBounds(){ + int newWidth = getWidth(); + + int imageWidth = 0; + int imageHeight = 0; + if( ! this.dialogPref.isSimpleMode()){ + imageWidth = this.faceImage.getWidth(null); + imageHeight = this.faceImage.getHeight(null); + } + + int tipWidth = BALOON_WOLFONLY.getWidth(); + + int modWidth; + int minWidth = imageWidth + tipWidth + BALOON_R * 2; + if(newWidth < minWidth) modWidth = minWidth; + else modWidth = newWidth; + + this.caption.setWidth(modWidth); + int captionWidth = this.caption.getWidth(); + int captionHeight = this.caption.getHeight() + CAPTION_DIALOG_GAP; + + this.dialog.setWidth(modWidth - minWidth); + int dialogWidth = this.dialog.getWidth(); + int dialogHeight = this.dialog.getHeight(); + + if(this.dialogPref.alignBaloonWidth()){ + this.baloonWidth = (modWidth - minWidth) + BALOON_R * 2; + }else{ + this.baloonWidth = dialogWidth + BALOON_R * 2; + } + this.baloonHeight = dialogHeight + BALOON_R * 2; + + int imageAndDialogWidth = imageWidth + tipWidth + this.baloonWidth; + + int totalWidth = Math.max(captionWidth, imageAndDialogWidth); + + int totalHeight = captionHeight; + totalHeight += Math.max(imageHeight, this.baloonHeight); + + int imageYpos = captionHeight; + int dialogYpos = captionHeight; + int tipYpos = captionHeight; + if(imageHeight < this.baloonHeight){ + imageYpos += (this.baloonHeight - imageHeight) / 2; + tipYpos += (this.baloonHeight - BALOON_WOLFONLY.getHeight()) / 2; + dialogYpos += BALOON_R; + }else{ + dialogYpos += (imageHeight - this.baloonHeight) / 2 + BALOON_R; + tipYpos += (imageHeight - BALOON_WOLFONLY.getHeight()) / 2; + } + + this.imageOrigin = new Point(0, imageYpos); + this.caption.setPos(this.bounds.x + 0, this.bounds.y + 0); + this.dialogOrigin = + new Point(imageWidth+tipWidth+BALOON_R, dialogYpos); + this.dialog.setPos(this.bounds.x + imageWidth+tipWidth+BALOON_R, + this.bounds.y + dialogYpos); + this.tipOrigin = new Point(imageWidth, tipYpos); + + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.setWidth(modWidth - OFFSET_ANCHOR); + totalHeight += anchorDraw.getHeight(); + } + + if( this.dialogPref.isSimpleMode() + || this.dialogPref.alignBaloonWidth() ){ + this.bounds.width = newWidth; + }else{ + this.bounds.width = totalWidth; + } + this.bounds.height = UPPER_MARGIN + totalHeight + UNDER_MARGIN; + + return this.bounds; + } + + /** + * {@inheritDoc} + * @param xPos {@inheritDoc} + * @param yPos {@inheritDoc} + */ + @Override + public void setPos(int xPos, int yPos){ + super.setPos(xPos, yPos); + this.caption.setPos(this.bounds.x, this.bounds.y + UPPER_MARGIN); + this.dialog.setPos(this.bounds.x + this.dialogOrigin.x, + this.bounds.y + this.dialogOrigin.y + + UPPER_MARGIN); + return; + } + + /** + * アイコンイメージとフキダシを繋ぐ補助イメージを返す。 + * @return 補助イメージ + */ + private BufferedImage getTipImage(){ + BufferedImage tip; + + TalkType type = this.talk.getTalkType(); + + if(this.dialogPref.isSimpleMode()){ + switch(type){ + case PUBLIC: tip = SQUARE_PUBLIC; break; + case WOLFONLY: tip = SQUARE_WOLFONLY; break; + case GRAVE: tip = SQUARE_GRAVE; break; + case PRIVATE: tip = SQUARE_PRIVATE; break; + default: + assert false; + tip = null; + break; + } + }else{ + switch(type){ + case PUBLIC: tip = BALOON_PUBLIC; break; + case WOLFONLY: tip = BALOON_WOLFONLY; break; + case GRAVE: tip = BALOON_GRAVE; break; + case PRIVATE: tip = BALOON_PRIVATE; break; + default: + assert false; + tip = null; + break; + } + } + + return tip; + } + + /** + * {@inheritDoc} + * @param g {@inheritDoc} + */ + @Override + public void paint(Graphics2D g){ + final int xPos = this.bounds.x; + final int yPos = this.bounds.y + UPPER_MARGIN; + + this.caption.paint(g); + + if(this.dialogPref.isSimpleMode() ){ + g.drawLine(xPos, this.bounds.y, + xPos + this.bounds.width, this.bounds.y ); + }else{ + g.drawImage(this.faceImage, + xPos + this.imageOrigin.x, + yPos + this.imageOrigin.y, + null ); + } + + BufferedImage tip = getTipImage(); + g.drawImage(tip, + xPos + this.tipOrigin.x, + yPos + this.tipOrigin.y, + null ); + + g.setColor(getTalkBgColor()); + g.fillRoundRect( + xPos + this.dialogOrigin.x - BALOON_R, + yPos + this.dialogOrigin.y - BALOON_R, + this.baloonWidth, + this.baloonHeight, + BALOON_R, + BALOON_R ); + + this.dialog.paint(g); + + int anchorX = xPos + OFFSET_ANCHOR; + int anchorY = yPos + this.dialogOrigin.y + baloonHeight; + + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.setPos(anchorX, anchorY); + anchorDraw.paint(g); + anchorY += anchorDraw.getHeight(); + } + + return; + } + + /** + * {@inheritDoc} + * @param fontInfo {@inheritDoc} + */ + @Override + public void setFontInfo(FontInfo fontInfo){ + super.setFontInfo(fontInfo); + + this.anchorFontInfo = deriveAnchorFontInfo(this.fontInfo); + + this.caption.setFontInfo(this.fontInfo); + this.dialog .setFontInfo(this.fontInfo); + + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.setFontInfo(this.anchorFontInfo); + } + + recalcBounds(); + + return; + } + + /** + * 発言設定を更新する。 + * @param pref 発言設定 + */ + public void setDialogPref(DialogPref pref){ + this.dialogPref = pref; + this.faceImage = getFaceImage(); + + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.setDialogPref(this.dialogPref); + } + + setColorDesign(); + recalcBounds(); + + return; + } + + /** + * {@inheritDoc} + * @param from {@inheritDoc} + * @param to {@inheritDoc} + */ + @Override + public void drag(Point from, Point to){ + this.caption.drag(from, to); + this.dialog.drag(from, to); + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.drag(from, to); + } + return; + } + + /** + * {@inheritDoc} + * @param appendable {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public Appendable appendSelected(Appendable appendable) + throws IOException{ + this.caption.appendSelected(appendable); + this.dialog .appendSelected(appendable); + + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.appendSelected(appendable); + } + + return appendable; + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSelect(){ + this.caption.clearSelect(); + this.dialog.clearSelect(); + for(AnchorDraw anchorDraw : this.anchorTalks){ + anchorDraw.clearSelect(); + } + return; + } + + /** + * 与えられた座標にアンカー文字列が存在すればAnchorを返す。 + * @param pt 座標 + * @return アンカー + */ + public Anchor getAnchor(Point pt){ + Anchor result = this.dialog.getAnchor(pt); + return result; + } + + /** + * アンカーを展開表示する。 + * アンカーにnullを指定すればアンカー表示は非表示となる。 + * @param anchor アンカー + * @param talkList アンカーの示す一連のTalk + */ + public void showAnchorTalks(Anchor anchor, List talkList){ + if(anchor == null || this.showingAnchor == anchor){ + this.showingAnchor = null; + this.anchorTalks.clear(); + recalcBounds(); + return; + } + + this.showingAnchor = anchor; + + this.anchorTalks.clear(); + for(Talk anchorTalk : talkList){ + AnchorDraw anchorDraw = + new AnchorDraw(anchorTalk, + this.dialogPref, + this.anchorFontInfo ); + this.anchorTalks.add(anchorDraw); + } + + recalcBounds(); + + return; + } + + /** + * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。 + * @param pt 座標 + * @return 検索マッチインデックス + */ + public int getRegexMatchIndex(Point pt){ + int index = this.dialog.getRegexMatchIndex(pt); + return index; + } + + /** + * 検索文字列パターンを設定する。 + * @param searchRegex パターン + * @return ヒット数 + */ + public int setRegex(Pattern searchRegex){ + int total = 0; + + total += this.dialog.setRegex(searchRegex); +/* + for(AnchorDraw anchorDraw : this.anchorTalks){ + total += anchorDraw.setRegex(searchRegex); + } +*/ // TODO よくわからんので保留 + return total; + } + + /** + * 検索ハイライトインデックスを返す。 + * @return 検索ハイライトインデックス。見つからなければ-1。 + */ + public int getHotTargetIndex(){ + return this.dialog.getHotTargetIndex(); + } + + /** + * 検索ハイライトを設定する。 + * @param index ハイライトインデックス。負ならハイライト全クリア。 + */ + public void setHotTargetIndex(int index){ + this.dialog.setHotTargetIndex(index); + return; + } + + /** + * 検索一致件数を返す。 + * @return 検索一致件数 + */ + public int getRegexMatches(){ + return this.dialog.getRegexMatches(); + } + + /** + * 特別な検索ハイライト描画をクリアする。 + */ + public void clearHotTarget(){ + this.dialog.clearHotTarget(); + return; + } + + /** + * 特別な検索ハイライト領域の寸法を返す。 + * @return ハイライト領域寸法 + */ + public Rectangle getHotTargetRectangle(){ + return this.dialog.getHotTargetRectangle(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TalkEditor.java b/src/main/java/jp/sourceforge/jindolf/TalkEditor.java index 4bee555..2a1280f 100644 --- a/src/main/java/jp/sourceforge/jindolf/TalkEditor.java +++ b/src/main/java/jp/sourceforge/jindolf/TalkEditor.java @@ -1,549 +1,549 @@ -/* - * 原稿作成支援エディタ - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.Rectangle; -import java.awt.event.FocusListener; -import javax.swing.BorderFactory; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.border.Border; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.NavigationFilter; -import javax.swing.text.PlainDocument; -import javax.swing.text.Segment; - -/** - * 原稿作成支援エディタ。 - * 文字数行数管理などを行う。 - * 200文字もしくは10行まで入力可能。 - * ※ 10回目に出現する改行文字は許される。 - * ※ 2010-11-27以降、G国では5行制限が10行に緩和された。 - */ -@SuppressWarnings("serial") -public class TalkEditor - extends JPanel - implements DocumentListener { - - private static final int MAX_CHARS = 200; - private static final int MAX_LINES = 10; - - private static final Color COLOR_ACTIVATED = Color.GRAY; - - - private final PlainDocument document = new PlainDocument(); - - private int sequenceNumber; - - private boolean isActive = false; - - private final JLabel seqCount = new JLabel(); - private final JLabel talkStat = new JLabel(); - private final TextEditor textEditor = new TextEditor(); - - private Font textFont; - - - /** - * コンストラクタ。 - * 通し番号は0が指定される。 - */ - public TalkEditor(){ - this(0); - return; - } - - /** - * コンストラクタ。 - * @param seqNumber 通し番号 - */ - private TalkEditor(int seqNumber){ - super(); - - setOpaque(true); - - this.document.addDocumentListener(this); - - this.seqCount.setForeground(Color.WHITE); - this.talkStat.setForeground(Color.WHITE); - this.seqCount.setOpaque(false); - this.talkStat.setOpaque(false); - - this.textEditor.setMargin(new Insets(3, 3, 3, 3)); - this.textEditor.setDocument(this.document); - - JPopupMenu popup = new TextPopup(); - this.textEditor.setComponentPopupMenu(popup); - - this.textFont = this.textEditor.getFont(); - - setSequenceNumber(seqNumber); - updateStat(); - setActive(false); - - design(); - - return; - } - - - /** - * 指定された文字列の指定された位置から、 - * 最大何文字まで1発言におさめる事ができるか判定する。 - * @param source 検査対象 - * @param start 検査開始位置 - * @return 1発言に納めていい長さ。 - */ - public static int choplimit(CharSequence source, int start){ - int length = source.length(); - if(start >= length) return 0; - - int chars = 0; - int lines = 0; - - for(int pos = start; pos < length; pos++){ - chars++; - if(chars >= MAX_CHARS) break; - char ch = source.charAt(pos); - if(ch == '\n'){ - lines++; - if(lines >= MAX_LINES) break; - } - } - - return chars; - } - - /** - * レイアウトを行う。 - */ - private void design(){ - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - setLayout(layout); - - constraints.gridx = 0; - constraints.gridy = 0; - constraints.gridwidth = 1; - constraints.gridheight = 1; - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.insets = new Insets(0, 0, 1, 3); - add(this.seqCount, constraints); - - constraints.gridx = 1; - constraints.gridy = 0; - constraints.gridwidth = 1; - constraints.gridheight = 2; - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.insets = new Insets(0, 0, 0, 0); - JComponent decorated = - BalloonBorder.decorateTransparentBorder(this.textEditor); - add(decorated, constraints); - - constraints.gridx = 0; - constraints.gridy = 1; - constraints.gridwidth = 1; - constraints.gridheight = 1; - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.insets = new Insets(0, 0, 0, 3); - add(this.talkStat, constraints); - - Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - setBorder(border); - - return; - } - - /** - * テキスト編集用フォントを指定する。 - * @param textFont フォント - */ - public void setTextFont(Font textFont){ - this.textFont = textFont; - this.textEditor.setFont(this.textFont); - this.textEditor.repaint(); - revalidate(); - return; - } - - /** - * テキスト編集用フォントを取得する。 - * @return フォント - */ - public Font getTextFont(){ - return this.textFont; - } - - /** - * テキストコンポーネントにNavigationFilterを設定する。 - * @param navigator ナビゲーションフィルタ - */ - public void setNavigationFilter(NavigationFilter navigator){ - this.textEditor.setNavigationFilter(navigator); - return; - } - - /** - * 通し番号を取得する。 - * @return 通し番号 - */ - public int getSequenceNumber(){ - return this.sequenceNumber; - } - - /** - * 通し番号を設定する。 - * @param seqNumber 通し番号 - */ - public void setSequenceNumber(int seqNumber){ - this.sequenceNumber = seqNumber; - String seqText = "=== #" + this.sequenceNumber + " ==="; - this.seqCount.setText(seqText); - return; - } - - /** - * Documentを取得する。 - * @return 管理中のDocument - */ - public Document getDocument(){ - return this.document; - } - - /** - * 現在のエディタの文字列内容を取得する。 - * @return 文字列内容 - */ - public String getText(){ - int length = this.document.getLength(); - String result = ""; - try{ - result = this.document.getText(0, length); - }catch(BadLocationException e){ - assert false; - } - return result; - } - - /** - * 現在のエディタの文字列長を取得する。 - * @return 文字列長 - */ - public int getTextLength(){ - return this.document.getLength(); - } - - /** - * 現在の行数を取得する。 - * ※ 改行文字の総数より一つ多い場合もある。 - * @return 行数 - */ - private int getTextLines(){ - int lines = 0; - - Segment segment = new Segment(); - segment.setPartialReturn(true); - - boolean hasLineContents = false; - int pos = 0; - int remain = getTextLength(); - while(remain > 0){ - try{ - this.document.getText(pos, remain, segment); - }catch(BadLocationException e){ - assert false; - } - - for(;;){ - char ch = segment.current(); - if(ch == Segment.DONE) break; - - if(ch == '\n'){ - if( ! hasLineContents ){ - lines++; - } - hasLineContents = false; - }else if( ! hasLineContents ){ - hasLineContents = true; - lines++; - } - - segment.next(); - } - - pos += segment.count; - remain -= segment.count; - } - - return lines; - } - - /** - * エディタ先頭に文字列を挿入する。 - * @param text 挿入文字列 - */ - public void appendHead(CharSequence text){ - if(text == null) return; - if(text.length() <= 0) return; - - try{ - this.document.insertString(0, text.toString(), null); - }catch(BadLocationException e){ - assert false; - } - - return; - } - - /** - * エディタ末尾に文字列を追加する。 - * @param text 追加文字列 - */ - public void appendTail(CharSequence text){ - if(text == null) return; - if(text.length() <= 0) return; - - int offset = getTextLength(); - try{ - this.document.insertString(offset, text.toString(), null); - }catch(BadLocationException e){ - assert false; - } - - return; - } - - /** - * エディタ先頭から文字列を削除する。 - * @param chopLength 削除文字数 - */ - public void chopHead(int chopLength){ - if(chopLength <= 0) return; - - int modLength; - int textLength = getTextLength(); - if(chopLength > textLength) modLength = textLength; - else modLength = chopLength; - - try{ - this.document.remove(0, modLength); - }catch(BadLocationException e){ - assert false; - } - - return; - } - - /** - * テキストを空にする。 - */ - public void clearText(){ - int textLength = this.document.getLength(); - try{ - this.document.remove(0, textLength); - }catch(BadLocationException e){ - assert false; - } - return; - } - - /** - * アクティブ状態を指定する。 - * アクティブ状態の場合、背景色が変わる。 - * @param isActiveArg trueならアクティブ状態 - */ - public void setActive(boolean isActiveArg){ - this.isActive = isActiveArg; - - if(this.isActive){ - setOpaque(true); - setBackground(COLOR_ACTIVATED); - Dimension size = getSize(); - Rectangle bounds = new Rectangle(size); - scrollRectToVisible(bounds); - this.textEditor.scrollCaretToVisible(); - }else{ - setOpaque(false); - } - - repaint(); - - return; - } - - /** - * アクティブ状態を取得する。 - * @return アクティブ状態ならtrue - */ - public boolean isActive(){ - return this.isActive; - } - - /** - * エディタが現在IME操作中か判定する。 - * @return IME操作中ならtrue - */ - public boolean onIMEoperation(){ - boolean result = this.textEditor.onIMEoperation(); - return result; - } - - /** - * エディタの現在のカーソル位置を取得する。 - * @return 0から始まるカーソル位置 - */ - public int getCaretPosition(){ - int caretPos = this.textEditor.getCaretPosition(); - return caretPos; - } - - /** - * エディタのカーソル位置を設定する。 - * @param pos 0から始まるカーソル位置 - * @throws java.lang.IllegalArgumentException 範囲外のカーソル位置指定 - */ - public void setCaretPosition(int pos) throws IllegalArgumentException{ - this.textEditor.setCaretPosition(pos); - return; - } - - /** - * 集計情報表示(文字数、行数)を更新する。 - */ - private void updateStat(){ - if(onIMEoperation()) return; - - int charTotal = getTextLength(); - int lineNumber = getTextLines(); - - StringBuilder statistics = new StringBuilder(); - statistics.append(charTotal) - .append("字 ") - .append(lineNumber) - .append("行"); - this.talkStat.setText(statistics.toString()); - - return; - } - - /** - * このテキストエディタに収まらない末尾文章を切り出す。 - * @return 最小の末尾文書。余裕があり切り出す必要がなければnullを返す。 - */ - public String chopRest(){ - String text = getText(); - int textLength = getTextLength(); - int choppedlen = choplimit(text, 0); - - int restLength = textLength - choppedlen; - if(restLength <= 0) return null; - - String rest = null; - try{ - rest = this.document.getText(choppedlen, restLength); - this.document.remove(choppedlen, restLength); - }catch(BadLocationException e){ - assert false; - } - - return rest; - } - - /** - * テキストエディタにフォーカスを設定する。 - * @return 絶対失敗する場合はfalse - */ - public boolean requestEditorFocus(){ - boolean result = this.textEditor.requestFocusInWindow(); - return result; - } - - /** - * テキストエディタがフォーカスを保持しているか判定する。 - * @return フォーカスを保持していればtrue - */ - public boolean hasEditorFocus(){ - boolean result = this.textEditor.hasFocus(); - return result; - } - - /** - * 子エディタのフォーカス監視リスナを登録する。 - * @param listener FocusListener - */ - public void addTextFocusListener(FocusListener listener){ - this.textEditor.addFocusListener(listener); - return; - } - - /** - * 子エディタからフォーカス監視リスナを外す。 - * @param listener FocusListener - */ - public void removeTextFocusListener(FocusListener listener){ - this.textEditor.removeFocusListener(listener); - return; - } - - /** - * {@inheritDoc} - * 集計情報を更新する。 - * @param event {@inheritDoc} - */ - // TODO いつ呼ばれるのか不明 - @Override - public void changedUpdate(DocumentEvent event){ - updateStat(); - return; - } - - /** - * {@inheritDoc} - * 集計情報を更新する。 - * @param event {@inheritDoc} - */ - @Override - public void insertUpdate(DocumentEvent event){ - updateStat(); - return; - } - - /** - * {@inheritDoc} - * 集計情報を更新する。 - * @param event {@inheritDoc} - */ - @Override - public void removeUpdate(DocumentEvent event){ - updateStat(); - return; - } - -} +/* + * 原稿作成支援エディタ + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.FocusListener; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.border.Border; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.NavigationFilter; +import javax.swing.text.PlainDocument; +import javax.swing.text.Segment; + +/** + * 原稿作成支援エディタ。 + * 文字数行数管理などを行う。 + * 200文字もしくは10行まで入力可能。 + * ※ 10回目に出現する改行文字は許される。 + * ※ 2010-11-27以降、G国では5行制限が10行に緩和された。 + */ +@SuppressWarnings("serial") +public class TalkEditor + extends JPanel + implements DocumentListener { + + private static final int MAX_CHARS = 200; + private static final int MAX_LINES = 10; + + private static final Color COLOR_ACTIVATED = Color.GRAY; + + + private final PlainDocument document = new PlainDocument(); + + private int sequenceNumber; + + private boolean isActive = false; + + private final JLabel seqCount = new JLabel(); + private final JLabel talkStat = new JLabel(); + private final TextEditor textEditor = new TextEditor(); + + private Font textFont; + + + /** + * コンストラクタ。 + * 通し番号は0が指定される。 + */ + public TalkEditor(){ + this(0); + return; + } + + /** + * コンストラクタ。 + * @param seqNumber 通し番号 + */ + private TalkEditor(int seqNumber){ + super(); + + setOpaque(true); + + this.document.addDocumentListener(this); + + this.seqCount.setForeground(Color.WHITE); + this.talkStat.setForeground(Color.WHITE); + this.seqCount.setOpaque(false); + this.talkStat.setOpaque(false); + + this.textEditor.setMargin(new Insets(3, 3, 3, 3)); + this.textEditor.setDocument(this.document); + + JPopupMenu popup = new TextPopup(); + this.textEditor.setComponentPopupMenu(popup); + + this.textFont = this.textEditor.getFont(); + + setSequenceNumber(seqNumber); + updateStat(); + setActive(false); + + design(); + + return; + } + + + /** + * 指定された文字列の指定された位置から、 + * 最大何文字まで1発言におさめる事ができるか判定する。 + * @param source 検査対象 + * @param start 検査開始位置 + * @return 1発言に納めていい長さ。 + */ + public static int choplimit(CharSequence source, int start){ + int length = source.length(); + if(start >= length) return 0; + + int chars = 0; + int lines = 0; + + for(int pos = start; pos < length; pos++){ + chars++; + if(chars >= MAX_CHARS) break; + char ch = source.charAt(pos); + if(ch == '\n'){ + lines++; + if(lines >= MAX_LINES) break; + } + } + + return chars; + } + + /** + * レイアウトを行う。 + */ + private void design(){ + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + setLayout(layout); + + constraints.gridx = 0; + constraints.gridy = 0; + constraints.gridwidth = 1; + constraints.gridheight = 1; + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + constraints.insets = new Insets(0, 0, 1, 3); + add(this.seqCount, constraints); + + constraints.gridx = 1; + constraints.gridy = 0; + constraints.gridwidth = 1; + constraints.gridheight = 2; + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.NORTHWEST; + constraints.insets = new Insets(0, 0, 0, 0); + JComponent decorated = + BalloonBorder.decorateTransparentBorder(this.textEditor); + add(decorated, constraints); + + constraints.gridx = 0; + constraints.gridy = 1; + constraints.gridwidth = 1; + constraints.gridheight = 1; + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.insets = new Insets(0, 0, 0, 3); + add(this.talkStat, constraints); + + Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); + setBorder(border); + + return; + } + + /** + * テキスト編集用フォントを指定する。 + * @param textFont フォント + */ + public void setTextFont(Font textFont){ + this.textFont = textFont; + this.textEditor.setFont(this.textFont); + this.textEditor.repaint(); + revalidate(); + return; + } + + /** + * テキスト編集用フォントを取得する。 + * @return フォント + */ + public Font getTextFont(){ + return this.textFont; + } + + /** + * テキストコンポーネントにNavigationFilterを設定する。 + * @param navigator ナビゲーションフィルタ + */ + public void setNavigationFilter(NavigationFilter navigator){ + this.textEditor.setNavigationFilter(navigator); + return; + } + + /** + * 通し番号を取得する。 + * @return 通し番号 + */ + public int getSequenceNumber(){ + return this.sequenceNumber; + } + + /** + * 通し番号を設定する。 + * @param seqNumber 通し番号 + */ + public void setSequenceNumber(int seqNumber){ + this.sequenceNumber = seqNumber; + String seqText = "=== #" + this.sequenceNumber + " ==="; + this.seqCount.setText(seqText); + return; + } + + /** + * Documentを取得する。 + * @return 管理中のDocument + */ + public Document getDocument(){ + return this.document; + } + + /** + * 現在のエディタの文字列内容を取得する。 + * @return 文字列内容 + */ + public String getText(){ + int length = this.document.getLength(); + String result = ""; + try{ + result = this.document.getText(0, length); + }catch(BadLocationException e){ + assert false; + } + return result; + } + + /** + * 現在のエディタの文字列長を取得する。 + * @return 文字列長 + */ + public int getTextLength(){ + return this.document.getLength(); + } + + /** + * 現在の行数を取得する。 + * ※ 改行文字の総数より一つ多い場合もある。 + * @return 行数 + */ + private int getTextLines(){ + int lines = 0; + + Segment segment = new Segment(); + segment.setPartialReturn(true); + + boolean hasLineContents = false; + int pos = 0; + int remain = getTextLength(); + while(remain > 0){ + try{ + this.document.getText(pos, remain, segment); + }catch(BadLocationException e){ + assert false; + } + + for(;;){ + char ch = segment.current(); + if(ch == Segment.DONE) break; + + if(ch == '\n'){ + if( ! hasLineContents ){ + lines++; + } + hasLineContents = false; + }else if( ! hasLineContents ){ + hasLineContents = true; + lines++; + } + + segment.next(); + } + + pos += segment.count; + remain -= segment.count; + } + + return lines; + } + + /** + * エディタ先頭に文字列を挿入する。 + * @param text 挿入文字列 + */ + public void appendHead(CharSequence text){ + if(text == null) return; + if(text.length() <= 0) return; + + try{ + this.document.insertString(0, text.toString(), null); + }catch(BadLocationException e){ + assert false; + } + + return; + } + + /** + * エディタ末尾に文字列を追加する。 + * @param text 追加文字列 + */ + public void appendTail(CharSequence text){ + if(text == null) return; + if(text.length() <= 0) return; + + int offset = getTextLength(); + try{ + this.document.insertString(offset, text.toString(), null); + }catch(BadLocationException e){ + assert false; + } + + return; + } + + /** + * エディタ先頭から文字列を削除する。 + * @param chopLength 削除文字数 + */ + public void chopHead(int chopLength){ + if(chopLength <= 0) return; + + int modLength; + int textLength = getTextLength(); + if(chopLength > textLength) modLength = textLength; + else modLength = chopLength; + + try{ + this.document.remove(0, modLength); + }catch(BadLocationException e){ + assert false; + } + + return; + } + + /** + * テキストを空にする。 + */ + public void clearText(){ + int textLength = this.document.getLength(); + try{ + this.document.remove(0, textLength); + }catch(BadLocationException e){ + assert false; + } + return; + } + + /** + * アクティブ状態を指定する。 + * アクティブ状態の場合、背景色が変わる。 + * @param isActiveArg trueならアクティブ状態 + */ + public void setActive(boolean isActiveArg){ + this.isActive = isActiveArg; + + if(this.isActive){ + setOpaque(true); + setBackground(COLOR_ACTIVATED); + Dimension size = getSize(); + Rectangle bounds = new Rectangle(size); + scrollRectToVisible(bounds); + this.textEditor.scrollCaretToVisible(); + }else{ + setOpaque(false); + } + + repaint(); + + return; + } + + /** + * アクティブ状態を取得する。 + * @return アクティブ状態ならtrue + */ + public boolean isActive(){ + return this.isActive; + } + + /** + * エディタが現在IME操作中か判定する。 + * @return IME操作中ならtrue + */ + public boolean onIMEoperation(){ + boolean result = this.textEditor.onIMEoperation(); + return result; + } + + /** + * エディタの現在のカーソル位置を取得する。 + * @return 0から始まるカーソル位置 + */ + public int getCaretPosition(){ + int caretPos = this.textEditor.getCaretPosition(); + return caretPos; + } + + /** + * エディタのカーソル位置を設定する。 + * @param pos 0から始まるカーソル位置 + * @throws java.lang.IllegalArgumentException 範囲外のカーソル位置指定 + */ + public void setCaretPosition(int pos) throws IllegalArgumentException{ + this.textEditor.setCaretPosition(pos); + return; + } + + /** + * 集計情報表示(文字数、行数)を更新する。 + */ + private void updateStat(){ + if(onIMEoperation()) return; + + int charTotal = getTextLength(); + int lineNumber = getTextLines(); + + StringBuilder statistics = new StringBuilder(); + statistics.append(charTotal) + .append("字 ") + .append(lineNumber) + .append("行"); + this.talkStat.setText(statistics.toString()); + + return; + } + + /** + * このテキストエディタに収まらない末尾文章を切り出す。 + * @return 最小の末尾文書。余裕があり切り出す必要がなければnullを返す。 + */ + public String chopRest(){ + String text = getText(); + int textLength = getTextLength(); + int choppedlen = choplimit(text, 0); + + int restLength = textLength - choppedlen; + if(restLength <= 0) return null; + + String rest = null; + try{ + rest = this.document.getText(choppedlen, restLength); + this.document.remove(choppedlen, restLength); + }catch(BadLocationException e){ + assert false; + } + + return rest; + } + + /** + * テキストエディタにフォーカスを設定する。 + * @return 絶対失敗する場合はfalse + */ + public boolean requestEditorFocus(){ + boolean result = this.textEditor.requestFocusInWindow(); + return result; + } + + /** + * テキストエディタがフォーカスを保持しているか判定する。 + * @return フォーカスを保持していればtrue + */ + public boolean hasEditorFocus(){ + boolean result = this.textEditor.hasFocus(); + return result; + } + + /** + * 子エディタのフォーカス監視リスナを登録する。 + * @param listener FocusListener + */ + public void addTextFocusListener(FocusListener listener){ + this.textEditor.addFocusListener(listener); + return; + } + + /** + * 子エディタからフォーカス監視リスナを外す。 + * @param listener FocusListener + */ + public void removeTextFocusListener(FocusListener listener){ + this.textEditor.removeFocusListener(listener); + return; + } + + /** + * {@inheritDoc} + * 集計情報を更新する。 + * @param event {@inheritDoc} + */ + // TODO いつ呼ばれるのか不明 + @Override + public void changedUpdate(DocumentEvent event){ + updateStat(); + return; + } + + /** + * {@inheritDoc} + * 集計情報を更新する。 + * @param event {@inheritDoc} + */ + @Override + public void insertUpdate(DocumentEvent event){ + updateStat(); + return; + } + + /** + * {@inheritDoc} + * 集計情報を更新する。 + * @param event {@inheritDoc} + */ + @Override + public void removeUpdate(DocumentEvent event){ + updateStat(); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TalkPreview.java b/src/main/java/jp/sourceforge/jindolf/TalkPreview.java index f645b52..448b05f 100644 --- a/src/main/java/jp/sourceforge/jindolf/TalkPreview.java +++ b/src/main/java/jp/sourceforge/jindolf/TalkPreview.java @@ -1,506 +1,506 @@ -/* - * 発言エディットパネル - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Container; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JViewport; -import javax.swing.border.BevelBorder; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.text.JTextComponent; -import jp.sourceforge.jindolf.json.JsArray; -import jp.sourceforge.jindolf.json.JsObject; -import jp.sourceforge.jindolf.json.JsString; -import jp.sourceforge.jindolf.json.JsValue; - -/** - * 発言エディットパネル。 - */ -@SuppressWarnings("serial") -public class TalkPreview extends JFrame - implements ActionListener, ChangeListener{ - - private static final String FRAMETITLE = "発言エディタ - " + Jindolf.TITLE; - private static final Color COLOR_EDITORBACK = Color.BLACK; - private static final String DRAFT_FILE = "draft.json"; - - private final JTextComponent freeMemo = new TextEditor(); - - private final EditArray editArray = new EditArray(); - - private final JButton cutButton = new JButton("カット"); - private final JButton copyButton = new JButton("コピー"); - private final JButton clearButton = new JButton("クリア"); - private final JButton cutAllButton = new JButton("全カット"); - private final JButton copyAllButton = new JButton("全コピー"); - private final JButton clearAllButton = new JButton("全クリア"); - private final JButton closeButton = new JButton("閉じる"); - private final TitledBorder numberBorder = - BorderFactory.createTitledBorder(""); - private final JComponent singleGroup = buildSingleGroup(); - private final JComponent multiGroup = buildMultiGroup(); - private final JLabel letsBrowser = - new JLabel("投稿はWebブラウザからどうぞ"); - - private JsObject loadedDraft = null; - - /** - * コンストラクタ。 - */ - public TalkPreview(){ - super(FRAMETITLE); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - setDefaultCloseOperation(HIDE_ON_CLOSE); - - this.cutButton .addActionListener(this); - this.copyButton .addActionListener(this); - this.clearButton .addActionListener(this); - this.cutAllButton .addActionListener(this); - this.copyAllButton .addActionListener(this); - this.clearAllButton .addActionListener(this); - this.closeButton .addActionListener(this); - - this.editArray.addChangeListener(this); - - Container content = getContentPane(); - design(content); - - setBorderNumber(1); - - return; - } - - /** - * レイアウトを行う。 - * @param content コンテナ - */ - private void design(Container content){ - JComponent freeNotePanel = buildFreeNotePanel(); - - JScrollPane scrollPane = new JScrollPane(); - scrollPane.setHorizontalScrollBarPolicy( - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER - ); - JViewport viewPort = new JViewport(); - viewPort.setBackground(COLOR_EDITORBACK); - viewPort.setView(this.editArray); - scrollPane.setViewport(viewPort); - - LayoutManager layout; - Border border; - - JComponent editPanel = new JPanel(); - layout = new BorderLayout(); - editPanel.setLayout(layout); - editPanel.add(scrollPane, BorderLayout.CENTER); - JComponent buttonPanel = buildButtonPanel(); - editPanel.add(buttonPanel, BorderLayout.EAST); - border = BorderFactory.createTitledBorder("発言編集"); - editPanel.setBorder(border); - - JSplitPane split = new JSplitPane(); - split.setOrientation(JSplitPane.HORIZONTAL_SPLIT); - split.setContinuousLayout(false); - split.setDividerSize(10); - split.setDividerLocation(200); - split.setOneTouchExpandable(true); - split.setLeftComponent(freeNotePanel); - split.setRightComponent(editPanel); - - Border inside = BorderFactory.createBevelBorder(BevelBorder.LOWERED); - Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2); - border = BorderFactory.createCompoundBorder(inside, outside); - this.letsBrowser.setBorder(border); - - layout = new BorderLayout(); - content.setLayout(layout); - content.add(split, BorderLayout.CENTER); - content.add(this.letsBrowser, BorderLayout.SOUTH); - - return; - } - - /** - * ボタン群を生成する。 - * @return ボタン群 - */ - private JComponent buildButtonPanel(){ - JPanel panel = new JPanel(); - - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.WEST; - constraints.gridx = 1; - constraints.gridy = GridBagConstraints.RELATIVE; - constraints.gridwidth = 1; - constraints.gridheight = 1; - - - constraints.insets = new Insets(3, 3, 3, 3); - panel.add(this.singleGroup, constraints); - - constraints.insets = new Insets(10, 3, 3, 3); - panel.add(this.multiGroup, constraints); - - constraints.weighty = 1.0; - constraints.anchor = GridBagConstraints.SOUTH; - constraints.insets = new Insets(3, 3, 10, 3); - panel.add(this.closeButton, constraints); - - return panel; - } - - /** - * アクティブ発言操作ボタン群を生成する。 - * @return ボタン群 - */ - private JComponent buildSingleGroup(){ - JComponent panel = new JPanel(); - - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridx = 1; - constraints.gridy = GridBagConstraints.RELATIVE; - constraints.gridwidth = 1; - constraints.gridheight = 1; - constraints.insets = new Insets(3, 3, 3, 3); - - panel.add(this.cutButton, constraints); - panel.add(this.copyButton, constraints); - panel.add(this.clearButton, constraints); - - panel.setBorder(this.numberBorder); - - return panel; - } - - /** - * 全発言操作ボタン群を生成する。 - * @return ボタン群 - */ - private JComponent buildMultiGroup(){ - JComponent panel = new JPanel(); - - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridx = 1; - constraints.gridy = GridBagConstraints.RELATIVE; - constraints.gridwidth = 1; - constraints.gridheight = 1; - constraints.insets = new Insets(3, 3, 3, 3); - - panel.add(this.cutAllButton, constraints); - panel.add(this.copyAllButton, constraints); - panel.add(this.clearAllButton, constraints); - - Border border = BorderFactory.createTitledBorder("全発言を"); - panel.setBorder(border); - - return panel; - } - - /** - * フリーノート部を生成する。 - * @return フリーノート部 - */ - private JComponent buildFreeNotePanel(){ - Insets margin = new Insets(3, 3, 3, 3); - this.freeMemo.setMargin(margin); - JPopupMenu popup = new TextPopup(); - this.freeMemo.setComponentPopupMenu(popup); - - JScrollPane scrollPane = new JScrollPane(); - scrollPane.setHorizontalScrollBarPolicy( - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER - ); - JViewport viewPort = new JViewport(); - viewPort.setView(this.freeMemo); - scrollPane.setViewport(viewPort); - - JComponent panel = new JPanel(); - - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - panel.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.insets = new Insets(1, 1, 1, 1); - panel.add(scrollPane, constraints); - - Border border = BorderFactory.createTitledBorder("フリーメモ"); - panel.setBorder(border); - - return panel; - } - - /** - * アクティブ発言の通し番号表示を更新。 - * @param num 通し番号 - */ - private void setBorderNumber(int num){ - String title = "発言#"+num+" を"; - this.numberBorder.setTitle(title); - this.singleGroup.revalidate(); - this.singleGroup.repaint(); - return; - } - - /** - * テキスト編集用フォントを指定する。 - * 描画属性は無視される。 - * @param fontInfo フォント設定 - */ - public void setFontInfo(FontInfo fontInfo){ - setTextFont(fontInfo.getFont()); - return; - } - - /** - * テキスト編集用フォントを指定する。 - * @param textFont フォント - */ - public void setTextFont(Font textFont){ - this.freeMemo.setFont(textFont); - this.editArray.setTextFont(textFont); - return; - } - - /** - * テキスト編集用フォントを取得する。 - * @return フォント - */ - public Font getTextFont(){ - return this.editArray.getTextFont(); - } - - /** - * 発言クリア操作の確認ダイアログを表示する。 - * @return OKなら0, Cancelなら2 - */ - private int warnClear(){ - int result = JOptionPane.showConfirmDialog( - this, - "本当に発言をクリアしてもよいですか?", - "発言クリア確認", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE ); - return result; - } - - /** - * 原稿のロード。 - */ - public void loadDraft(){ - JsValue value = ConfigFile.loadJson(new File(DRAFT_FILE)); - if(value == null) return; - - if( ! (value instanceof JsObject) ) return; - JsObject root = (JsObject) value; - - value = root.getValue("freeMemo"); - if(value instanceof JsString){ - JsString memo = (JsString) value; - this.freeMemo.setText(memo.toRawString()); - } - - value = root.getValue("drafts"); - if( ! (value instanceof JsArray) ) return; - JsArray array = (JsArray) value; - - StringBuilder draftAll = new StringBuilder(); - for(JsValue elem : array){ - if( ! (elem instanceof JsString) ) continue; - JsString draft = (JsString) elem; - draftAll.append(draft.toRawString()); - } - this.editArray.clearAllEditor(); - this.editArray.setAllText(draftAll); - - this.loadedDraft = root; - - return; - } - - /** - * 原稿のセーブ。 - */ - public void saveDraft(){ - AppSetting setting = Jindolf.getAppSetting(); - if( ! setting.useConfigPath() ) return; - File configPath = setting.getConfigPath(); - if(configPath == null) return; - - JsObject root = new JsObject(); - JsString memo = new JsString(this.freeMemo.getText()); - root.putValue("freeMemo", memo); - - JsArray array = new JsArray(); - JsString text = new JsString(this.editArray.getAllText()); - array.add(text); - root.putValue("drafts", array); - - if(this.loadedDraft != null){ - if(this.loadedDraft.equals(root)) return; - } - - ConfigFile.saveJson(new File(DRAFT_FILE), root); - - return; - } - - /** - * アクティブな発言をカットしクリップボードへコピーする。 - */ - private void actionCutActive(){ - actionCopyActive(); - actionClearActive(false); - return; - } - - /** - * アクティブな発言をクリップボードにコピーする。 - */ - private void actionCopyActive(){ - TalkEditor activeEditor = this.editArray.getActiveEditor(); - if(activeEditor == null) return; - - CharSequence text = activeEditor.getText(); - ClipboardAction.copyToClipboard(text); - - return; - } - - /** - * アクティブな発言をクリアする。 - * @param confirm trueなら確認ダイアログを出す - */ - private void actionClearActive(boolean confirm){ - if(confirm && warnClear() != 0 ) return; - - TalkEditor activeEditor = this.editArray.getActiveEditor(); - if(activeEditor == null) return; - - activeEditor.clearText(); - - return; - } - - /** - * 全発言をカットしクリップボードへコピーする。 - */ - private void actionCutAll(){ - actionCopyAll(); - actionClearAll(false); - return; - } - - /** - * 全発言をクリップボードにコピーする。 - */ - private void actionCopyAll(){ - CharSequence text = this.editArray.getAllText(); - ClipboardAction.copyToClipboard(text); - return; - } - - /** - * 全発言をクリアする。 - * @param confirm trueなら確認ダイアログを出す - */ - private void actionClearAll(boolean confirm){ - if(confirm && warnClear() != 0 ) return; - this.editArray.clearAllEditor(); - return; - } - - /** - * 上位ウィンドウをクローズする。 - */ - private void actionClose(){ - setVisible(false); - return; - } - - /** - * {@inheritDoc} - * 各種ボタン操作の処理。 - * @param event {@inheritDoc} - */ - @Override - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if (source == this.cutButton) actionCutActive(); - else if(source == this.copyButton) actionCopyActive(); - else if(source == this.clearButton) actionClearActive(true); - else if(source == this.cutAllButton) actionCutAll(); - else if(source == this.copyAllButton) actionCopyAll(); - else if(source == this.clearAllButton) actionClearAll(true); - else if(source == this.closeButton) actionClose(); - return; - } - - /** - * アクティブなエディタが変更された時の処理。 - * @param event イベント情報 - */ - public void stateChanged(ChangeEvent event){ - TalkEditor activeEditor = this.editArray.getActiveEditor(); - int seqNo = activeEditor.getSequenceNumber(); - setBorderNumber(seqNo); - return; - } - - // TODO アンドゥ・リドゥ機能 - // TODO バルーンの雰囲気を選択できるようにしたい。(白、赤、青、灰) - // TODO アンカーの表記揺れの指摘 -} +/* + * 発言エディットパネル + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JViewport; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.text.JTextComponent; +import jp.sourceforge.jindolf.json.JsArray; +import jp.sourceforge.jindolf.json.JsObject; +import jp.sourceforge.jindolf.json.JsString; +import jp.sourceforge.jindolf.json.JsValue; + +/** + * 発言エディットパネル。 + */ +@SuppressWarnings("serial") +public class TalkPreview extends JFrame + implements ActionListener, ChangeListener{ + + private static final String FRAMETITLE = "発言エディタ - " + Jindolf.TITLE; + private static final Color COLOR_EDITORBACK = Color.BLACK; + private static final String DRAFT_FILE = "draft.json"; + + private final JTextComponent freeMemo = new TextEditor(); + + private final EditArray editArray = new EditArray(); + + private final JButton cutButton = new JButton("カット"); + private final JButton copyButton = new JButton("コピー"); + private final JButton clearButton = new JButton("クリア"); + private final JButton cutAllButton = new JButton("全カット"); + private final JButton copyAllButton = new JButton("全コピー"); + private final JButton clearAllButton = new JButton("全クリア"); + private final JButton closeButton = new JButton("閉じる"); + private final TitledBorder numberBorder = + BorderFactory.createTitledBorder(""); + private final JComponent singleGroup = buildSingleGroup(); + private final JComponent multiGroup = buildMultiGroup(); + private final JLabel letsBrowser = + new JLabel("投稿はWebブラウザからどうぞ"); + + private JsObject loadedDraft = null; + + /** + * コンストラクタ。 + */ + public TalkPreview(){ + super(FRAMETITLE); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + setDefaultCloseOperation(HIDE_ON_CLOSE); + + this.cutButton .addActionListener(this); + this.copyButton .addActionListener(this); + this.clearButton .addActionListener(this); + this.cutAllButton .addActionListener(this); + this.copyAllButton .addActionListener(this); + this.clearAllButton .addActionListener(this); + this.closeButton .addActionListener(this); + + this.editArray.addChangeListener(this); + + Container content = getContentPane(); + design(content); + + setBorderNumber(1); + + return; + } + + /** + * レイアウトを行う。 + * @param content コンテナ + */ + private void design(Container content){ + JComponent freeNotePanel = buildFreeNotePanel(); + + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setHorizontalScrollBarPolicy( + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + ); + JViewport viewPort = new JViewport(); + viewPort.setBackground(COLOR_EDITORBACK); + viewPort.setView(this.editArray); + scrollPane.setViewport(viewPort); + + LayoutManager layout; + Border border; + + JComponent editPanel = new JPanel(); + layout = new BorderLayout(); + editPanel.setLayout(layout); + editPanel.add(scrollPane, BorderLayout.CENTER); + JComponent buttonPanel = buildButtonPanel(); + editPanel.add(buttonPanel, BorderLayout.EAST); + border = BorderFactory.createTitledBorder("発言編集"); + editPanel.setBorder(border); + + JSplitPane split = new JSplitPane(); + split.setOrientation(JSplitPane.HORIZONTAL_SPLIT); + split.setContinuousLayout(false); + split.setDividerSize(10); + split.setDividerLocation(200); + split.setOneTouchExpandable(true); + split.setLeftComponent(freeNotePanel); + split.setRightComponent(editPanel); + + Border inside = BorderFactory.createBevelBorder(BevelBorder.LOWERED); + Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2); + border = BorderFactory.createCompoundBorder(inside, outside); + this.letsBrowser.setBorder(border); + + layout = new BorderLayout(); + content.setLayout(layout); + content.add(split, BorderLayout.CENTER); + content.add(this.letsBrowser, BorderLayout.SOUTH); + + return; + } + + /** + * ボタン群を生成する。 + * @return ボタン群 + */ + private JComponent buildButtonPanel(){ + JPanel panel = new JPanel(); + + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.WEST; + constraints.gridx = 1; + constraints.gridy = GridBagConstraints.RELATIVE; + constraints.gridwidth = 1; + constraints.gridheight = 1; + + + constraints.insets = new Insets(3, 3, 3, 3); + panel.add(this.singleGroup, constraints); + + constraints.insets = new Insets(10, 3, 3, 3); + panel.add(this.multiGroup, constraints); + + constraints.weighty = 1.0; + constraints.anchor = GridBagConstraints.SOUTH; + constraints.insets = new Insets(3, 3, 10, 3); + panel.add(this.closeButton, constraints); + + return panel; + } + + /** + * アクティブ発言操作ボタン群を生成する。 + * @return ボタン群 + */ + private JComponent buildSingleGroup(){ + JComponent panel = new JPanel(); + + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 1; + constraints.gridy = GridBagConstraints.RELATIVE; + constraints.gridwidth = 1; + constraints.gridheight = 1; + constraints.insets = new Insets(3, 3, 3, 3); + + panel.add(this.cutButton, constraints); + panel.add(this.copyButton, constraints); + panel.add(this.clearButton, constraints); + + panel.setBorder(this.numberBorder); + + return panel; + } + + /** + * 全発言操作ボタン群を生成する。 + * @return ボタン群 + */ + private JComponent buildMultiGroup(){ + JComponent panel = new JPanel(); + + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 1; + constraints.gridy = GridBagConstraints.RELATIVE; + constraints.gridwidth = 1; + constraints.gridheight = 1; + constraints.insets = new Insets(3, 3, 3, 3); + + panel.add(this.cutAllButton, constraints); + panel.add(this.copyAllButton, constraints); + panel.add(this.clearAllButton, constraints); + + Border border = BorderFactory.createTitledBorder("全発言を"); + panel.setBorder(border); + + return panel; + } + + /** + * フリーノート部を生成する。 + * @return フリーノート部 + */ + private JComponent buildFreeNotePanel(){ + Insets margin = new Insets(3, 3, 3, 3); + this.freeMemo.setMargin(margin); + JPopupMenu popup = new TextPopup(); + this.freeMemo.setComponentPopupMenu(popup); + + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setHorizontalScrollBarPolicy( + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + ); + JViewport viewPort = new JViewport(); + viewPort.setView(this.freeMemo); + scrollPane.setViewport(viewPort); + + JComponent panel = new JPanel(); + + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + panel.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.insets = new Insets(1, 1, 1, 1); + panel.add(scrollPane, constraints); + + Border border = BorderFactory.createTitledBorder("フリーメモ"); + panel.setBorder(border); + + return panel; + } + + /** + * アクティブ発言の通し番号表示を更新。 + * @param num 通し番号 + */ + private void setBorderNumber(int num){ + String title = "発言#"+num+" を"; + this.numberBorder.setTitle(title); + this.singleGroup.revalidate(); + this.singleGroup.repaint(); + return; + } + + /** + * テキスト編集用フォントを指定する。 + * 描画属性は無視される。 + * @param fontInfo フォント設定 + */ + public void setFontInfo(FontInfo fontInfo){ + setTextFont(fontInfo.getFont()); + return; + } + + /** + * テキスト編集用フォントを指定する。 + * @param textFont フォント + */ + public void setTextFont(Font textFont){ + this.freeMemo.setFont(textFont); + this.editArray.setTextFont(textFont); + return; + } + + /** + * テキスト編集用フォントを取得する。 + * @return フォント + */ + public Font getTextFont(){ + return this.editArray.getTextFont(); + } + + /** + * 発言クリア操作の確認ダイアログを表示する。 + * @return OKなら0, Cancelなら2 + */ + private int warnClear(){ + int result = JOptionPane.showConfirmDialog( + this, + "本当に発言をクリアしてもよいですか?", + "発言クリア確認", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE ); + return result; + } + + /** + * 原稿のロード。 + */ + public void loadDraft(){ + JsValue value = ConfigFile.loadJson(new File(DRAFT_FILE)); + if(value == null) return; + + if( ! (value instanceof JsObject) ) return; + JsObject root = (JsObject) value; + + value = root.getValue("freeMemo"); + if(value instanceof JsString){ + JsString memo = (JsString) value; + this.freeMemo.setText(memo.toRawString()); + } + + value = root.getValue("drafts"); + if( ! (value instanceof JsArray) ) return; + JsArray array = (JsArray) value; + + StringBuilder draftAll = new StringBuilder(); + for(JsValue elem : array){ + if( ! (elem instanceof JsString) ) continue; + JsString draft = (JsString) elem; + draftAll.append(draft.toRawString()); + } + this.editArray.clearAllEditor(); + this.editArray.setAllText(draftAll); + + this.loadedDraft = root; + + return; + } + + /** + * 原稿のセーブ。 + */ + public void saveDraft(){ + AppSetting setting = Jindolf.getAppSetting(); + if( ! setting.useConfigPath() ) return; + File configPath = setting.getConfigPath(); + if(configPath == null) return; + + JsObject root = new JsObject(); + JsString memo = new JsString(this.freeMemo.getText()); + root.putValue("freeMemo", memo); + + JsArray array = new JsArray(); + JsString text = new JsString(this.editArray.getAllText()); + array.add(text); + root.putValue("drafts", array); + + if(this.loadedDraft != null){ + if(this.loadedDraft.equals(root)) return; + } + + ConfigFile.saveJson(new File(DRAFT_FILE), root); + + return; + } + + /** + * アクティブな発言をカットしクリップボードへコピーする。 + */ + private void actionCutActive(){ + actionCopyActive(); + actionClearActive(false); + return; + } + + /** + * アクティブな発言をクリップボードにコピーする。 + */ + private void actionCopyActive(){ + TalkEditor activeEditor = this.editArray.getActiveEditor(); + if(activeEditor == null) return; + + CharSequence text = activeEditor.getText(); + ClipboardAction.copyToClipboard(text); + + return; + } + + /** + * アクティブな発言をクリアする。 + * @param confirm trueなら確認ダイアログを出す + */ + private void actionClearActive(boolean confirm){ + if(confirm && warnClear() != 0 ) return; + + TalkEditor activeEditor = this.editArray.getActiveEditor(); + if(activeEditor == null) return; + + activeEditor.clearText(); + + return; + } + + /** + * 全発言をカットしクリップボードへコピーする。 + */ + private void actionCutAll(){ + actionCopyAll(); + actionClearAll(false); + return; + } + + /** + * 全発言をクリップボードにコピーする。 + */ + private void actionCopyAll(){ + CharSequence text = this.editArray.getAllText(); + ClipboardAction.copyToClipboard(text); + return; + } + + /** + * 全発言をクリアする。 + * @param confirm trueなら確認ダイアログを出す + */ + private void actionClearAll(boolean confirm){ + if(confirm && warnClear() != 0 ) return; + this.editArray.clearAllEditor(); + return; + } + + /** + * 上位ウィンドウをクローズする。 + */ + private void actionClose(){ + setVisible(false); + return; + } + + /** + * {@inheritDoc} + * 各種ボタン操作の処理。 + * @param event {@inheritDoc} + */ + @Override + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if (source == this.cutButton) actionCutActive(); + else if(source == this.copyButton) actionCopyActive(); + else if(source == this.clearButton) actionClearActive(true); + else if(source == this.cutAllButton) actionCutAll(); + else if(source == this.copyAllButton) actionCopyAll(); + else if(source == this.clearAllButton) actionClearAll(true); + else if(source == this.closeButton) actionClose(); + return; + } + + /** + * アクティブなエディタが変更された時の処理。 + * @param event イベント情報 + */ + public void stateChanged(ChangeEvent event){ + TalkEditor activeEditor = this.editArray.getActiveEditor(); + int seqNo = activeEditor.getSequenceNumber(); + setBorderNumber(seqNo); + return; + } + + // TODO アンドゥ・リドゥ機能 + // TODO バルーンの雰囲気を選択できるようにしたい。(白、赤、青、灰) + // TODO アンカーの表記揺れの指摘 +} diff --git a/src/main/java/jp/sourceforge/jindolf/TallyInputStream.java b/src/main/java/jp/sourceforge/jindolf/TallyInputStream.java index 9bfd2b8..4ebeae1 100644 --- a/src/main/java/jp/sourceforge/jindolf/TallyInputStream.java +++ b/src/main/java/jp/sourceforge/jindolf/TallyInputStream.java @@ -1,176 +1,176 @@ -/* - * InputStream associated with HttpURLConnection with counter - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; - -/** - * 読み込みバイト数を記録するHTTPコネクション由来のInputStream。 - * バッファリングも行う。 - */ -public class TallyInputStream extends InputStream{ - - private static final int BUFSIZE = 2 * 1024; - - - private final HttpURLConnection conn; - private final InputStream in; - private long counter; - private long nanoLap; - private boolean hasClosed; - - - /** - * コンストラクタ。 - * @param conn HTTPコネクション - * @throws java.io.IOException 入出力エラー - */ - protected TallyInputStream(HttpURLConnection conn) throws IOException{ - super(); - - this.conn = conn; - this.counter = 0; - this.nanoLap = 0; - - InputStream is; - is = this.conn.getInputStream(); - is = new BufferedInputStream(is, BUFSIZE); - this.in = is; - - this.hasClosed = false; - - return; - } - - - /** - * HTTPコネクションから入力ストリームを得る。 - * @param conn HTTPコネクション - * @return 入力ストリーム - * @throws java.io.IOException 入出力エラー - */ - public static InputStream getInputStream(HttpURLConnection conn) - throws IOException{ - return new TallyInputStream(conn); - } - - /** - * 読み込みバイト数を返す。 - * @return 読み込みバイト数。 - */ - protected long getCount(){ - return this.counter; - } - - /** - * {@inheritDoc} - * - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public int available() throws IOException{ - int bytes = this.in.available(); - return bytes; - } - - /** - * {@inheritDoc} - * 今までに読み込んだバイト数のスループットをログ出力する。 - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void close() throws IOException{ - if(this.hasClosed) return; - - this.in.close(); - - long size = getCount(); - long span = System.nanoTime() - this.nanoLap; - - String message = HttpUtils.formatHttpStat(this.conn, size, span); - Jindolf.logger().info(message); - - this.hasClosed = true; - - return; - } - - /** - * {@inheritDoc} - * - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public int read() throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - int byteData = this.in.read(); - if(byteData >= 0) this.counter++; - - return byteData; - } - - /** - * {@inheritDoc} - * - * @param buf {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public int read(byte[] buf) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - int count = this.in.read(buf); - if(count >= 0) this.counter += count; - - return count; - } - - /** - * {@inheritDoc} - * - * @param buf {@inheritDoc} - * @param off {@inheritDoc} - * @param len {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public int read(byte[] buf, int off, int len) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - int count = this.in.read(buf, off, len); - if(count >= 0) this.counter += count; - - return count; - } - - /** - * {@inheritDoc} - * - * @param n {@inheritDoc} - * @return {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public long skip(long n) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - long skipped = this.in.skip(n); - this.counter += skipped; - - return skipped; - } - -} +/* + * InputStream associated with HttpURLConnection with counter + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +/** + * 読み込みバイト数を記録するHTTPコネクション由来のInputStream。 + * バッファリングも行う。 + */ +public class TallyInputStream extends InputStream{ + + private static final int BUFSIZE = 2 * 1024; + + + private final HttpURLConnection conn; + private final InputStream in; + private long counter; + private long nanoLap; + private boolean hasClosed; + + + /** + * コンストラクタ。 + * @param conn HTTPコネクション + * @throws java.io.IOException 入出力エラー + */ + protected TallyInputStream(HttpURLConnection conn) throws IOException{ + super(); + + this.conn = conn; + this.counter = 0; + this.nanoLap = 0; + + InputStream is; + is = this.conn.getInputStream(); + is = new BufferedInputStream(is, BUFSIZE); + this.in = is; + + this.hasClosed = false; + + return; + } + + + /** + * HTTPコネクションから入力ストリームを得る。 + * @param conn HTTPコネクション + * @return 入力ストリーム + * @throws java.io.IOException 入出力エラー + */ + public static InputStream getInputStream(HttpURLConnection conn) + throws IOException{ + return new TallyInputStream(conn); + } + + /** + * 読み込みバイト数を返す。 + * @return 読み込みバイト数。 + */ + protected long getCount(){ + return this.counter; + } + + /** + * {@inheritDoc} + * + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public int available() throws IOException{ + int bytes = this.in.available(); + return bytes; + } + + /** + * {@inheritDoc} + * 今までに読み込んだバイト数のスループットをログ出力する。 + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void close() throws IOException{ + if(this.hasClosed) return; + + this.in.close(); + + long size = getCount(); + long span = System.nanoTime() - this.nanoLap; + + String message = HttpUtils.formatHttpStat(this.conn, size, span); + Jindolf.logger().info(message); + + this.hasClosed = true; + + return; + } + + /** + * {@inheritDoc} + * + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public int read() throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + int byteData = this.in.read(); + if(byteData >= 0) this.counter++; + + return byteData; + } + + /** + * {@inheritDoc} + * + * @param buf {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public int read(byte[] buf) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + int count = this.in.read(buf); + if(count >= 0) this.counter += count; + + return count; + } + + /** + * {@inheritDoc} + * + * @param buf {@inheritDoc} + * @param off {@inheritDoc} + * @param len {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public int read(byte[] buf, int off, int len) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + int count = this.in.read(buf, off, len); + if(count >= 0) this.counter += count; + + return count; + } + + /** + * {@inheritDoc} + * + * @param n {@inheritDoc} + * @return {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public long skip(long n) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + long skipped = this.in.skip(n); + this.counter += skipped; + + return skipped; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TallyOutputStream.java b/src/main/java/jp/sourceforge/jindolf/TallyOutputStream.java index 4aa45c5..d17ce6a 100644 --- a/src/main/java/jp/sourceforge/jindolf/TallyOutputStream.java +++ b/src/main/java/jp/sourceforge/jindolf/TallyOutputStream.java @@ -1,145 +1,145 @@ -/* - * OutputStream associated with HttpURLConnection with counter - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; - -/** - * 書き込みバイト数をログ出力するHTTPコネクション由来のOutputStream。 - */ -public class TallyOutputStream extends OutputStream{ - - private static final int BUFSIZE = 512; - - - private final HttpURLConnection conn; - private final OutputStream out; - private long counter; - private long nanoLap; - - - /** - * コンストラクタ。 - * @param conn HTTPコネクション - * @throws java.io.IOException 入出力エラー - */ - protected TallyOutputStream(HttpURLConnection conn) - throws IOException{ - super(); - - this.conn = conn; - this.counter = 0; - this.nanoLap = 0; - - OutputStream os; - os = this.conn.getOutputStream(); - os = new BufferedOutputStream(os, BUFSIZE); - this.out = os; - - return; - } - - - /** - * HTTPコネクションから出力ストリームを得る。 - * @param conn HTTPコネクション - * @return 出力ストリーム - * @throws java.io.IOException 入出力エラー - */ - public static OutputStream getOutputStream(HttpURLConnection conn) - throws IOException{ - return new TallyOutputStream(conn); - } - - /** - * 書き込みバイト数を返す。 - * @return 書き込みバイト数。 - */ - protected long getCount(){ - return this.counter; - } - - /** - * {@inheritDoc} - * 今までに書き込んだバイト数のスループットをログ出力する。 - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void close() throws IOException{ - this.out.close(); - - long size = getCount(); - long span = System.nanoTime() - this.nanoLap; - - String message = HttpUtils.formatHttpStat(this.conn, size, span); - Jindolf.logger().info(message); - - return; - } - - /** - * {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void flush() throws IOException{ - this.out.flush(); - return; - } - - /** - * {@inheritDoc} - * @param b {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void write(byte[] b) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - this.out.write(b); - this.counter += b.length; - - return; - } - - /** - * {@inheritDoc} - * @param b {@inheritDoc} - * @param off {@inheritDoc} - * @param len {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void write(byte[] b, int off, int len) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - this.out.write(b, off, len); - this.counter += len; - - return; - } - - /** - * {@inheritDoc} - * @param b {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public void write(int b) throws IOException{ - if(this.counter <= 0) this.nanoLap = System.nanoTime(); - - this.out.write(b); - this.counter++; - - return; - } - -} +/* + * OutputStream associated with HttpURLConnection with counter + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; + +/** + * 書き込みバイト数をログ出力するHTTPコネクション由来のOutputStream。 + */ +public class TallyOutputStream extends OutputStream{ + + private static final int BUFSIZE = 512; + + + private final HttpURLConnection conn; + private final OutputStream out; + private long counter; + private long nanoLap; + + + /** + * コンストラクタ。 + * @param conn HTTPコネクション + * @throws java.io.IOException 入出力エラー + */ + protected TallyOutputStream(HttpURLConnection conn) + throws IOException{ + super(); + + this.conn = conn; + this.counter = 0; + this.nanoLap = 0; + + OutputStream os; + os = this.conn.getOutputStream(); + os = new BufferedOutputStream(os, BUFSIZE); + this.out = os; + + return; + } + + + /** + * HTTPコネクションから出力ストリームを得る。 + * @param conn HTTPコネクション + * @return 出力ストリーム + * @throws java.io.IOException 入出力エラー + */ + public static OutputStream getOutputStream(HttpURLConnection conn) + throws IOException{ + return new TallyOutputStream(conn); + } + + /** + * 書き込みバイト数を返す。 + * @return 書き込みバイト数。 + */ + protected long getCount(){ + return this.counter; + } + + /** + * {@inheritDoc} + * 今までに書き込んだバイト数のスループットをログ出力する。 + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void close() throws IOException{ + this.out.close(); + + long size = getCount(); + long span = System.nanoTime() - this.nanoLap; + + String message = HttpUtils.formatHttpStat(this.conn, size, span); + Jindolf.logger().info(message); + + return; + } + + /** + * {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void flush() throws IOException{ + this.out.flush(); + return; + } + + /** + * {@inheritDoc} + * @param b {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void write(byte[] b) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + this.out.write(b); + this.counter += b.length; + + return; + } + + /** + * {@inheritDoc} + * @param b {@inheritDoc} + * @param off {@inheritDoc} + * @param len {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void write(byte[] b, int off, int len) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + this.out.write(b, off, len); + this.counter += len; + + return; + } + + /** + * {@inheritDoc} + * @param b {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public void write(int b) throws IOException{ + if(this.counter <= 0) this.nanoLap = System.nanoTime(); + + this.out.write(b); + this.counter++; + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TextEditor.java b/src/main/java/jp/sourceforge/jindolf/TextEditor.java index 2378b43..28ba523 100644 --- a/src/main/java/jp/sourceforge/jindolf/TextEditor.java +++ b/src/main/java/jp/sourceforge/jindolf/TextEditor.java @@ -1,284 +1,284 @@ -/* - * 原稿作成支援用テキストコンポーネント - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Rectangle; -import java.awt.event.InputMethodEvent; -import java.awt.event.InputMethodListener; -import java.nio.CharBuffer; -import java.text.AttributedCharacterIterator; -import javax.swing.JTextArea; -import javax.swing.text.AbstractDocument; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.DocumentFilter; -import javax.swing.text.DocumentFilter.FilterBypass; -import javax.swing.text.PlainDocument; - -/** - * 原稿作成支援用テキストコンポーネント。 - */ -@SuppressWarnings("serial") -public class TextEditor extends JTextArea - implements InputMethodListener { - - private static final int MAX_DOCUMENT = 10 * 1000; - - private final DocumentFilter documentFilter = new CustomFilter(); - - private boolean onIMEoperation = false; - - /** - * コンストラクタ。 - */ - public TextEditor(){ - super(); - - setLineWrap(true); - setWrapStyleWord(false); - - Document document = new PlainDocument(); - setDocument(document); - - addInputMethodListener(this); - - return; - } - - /** - * エディタが現在IME操作中か判定する。 - * @return IME操作中ならtrue - */ - public boolean onIMEoperation(){ - return this.onIMEoperation; - } - - /** - * 現在のカーソルが表示されるようスクロールエリアを操作する。 - */ - public void scrollCaretToVisible(){ - int caretPosition = getCaretPosition(); - - Rectangle caretBounds; - try{ - caretBounds = modelToView(caretPosition); - }catch(BadLocationException e){ - assert false; - return; - } - - scrollRectToVisible(caretBounds); - - return; - } - - /** - * {@inheritDoc} - * Document変更をフックしてフィルタを仕込む。 - * @param document {@inheritDoc} - */ - @Override - public final void setDocument(Document document){ - Document oldDocument = getDocument(); - if(oldDocument instanceof AbstractDocument){ - AbstractDocument abstractDocument = - (AbstractDocument) oldDocument; - abstractDocument.setDocumentFilter(null); - } - - super.setDocument(document); - - if(document instanceof AbstractDocument){ - AbstractDocument abstractDocument = (AbstractDocument) document; - abstractDocument.setDocumentFilter(this.documentFilter); - } - - return; - } - - /** - * {@inheritDoc} - * このエディタ中の指定領域が表示されるようスクロールエリアを操作する。 - * キーボードフォーカスを保持しないときは無視。 - * @param rect {@inheritDoc} - */ - @Override - public void scrollRectToVisible(Rectangle rect){ - if( ! hasFocus() ) return; - super.scrollRectToVisible(rect); - return; - } - - /** - * {@inheritDoc} - * @param event {@inheritDoc} - */ - @Override - public void caretPositionChanged(InputMethodEvent event){ - // NOTHING - return; - } - - /** - * {@inheritDoc} - * このテキストエディタで現在IMEの変換中か否か判定する処理を含む。 - * @param event {@inheritDoc} - */ - @Override - public void inputMethodTextChanged(InputMethodEvent event){ - int committed = event.getCommittedCharacterCount(); - AttributedCharacterIterator aci = event.getText(); - if(aci == null){ - this.onIMEoperation = false; - return; - } - int begin = aci.getBeginIndex(); - int end = aci.getEndIndex(); - int span = end - begin; - - if(committed >= span) this.onIMEoperation = false; - else this.onIMEoperation = true; - - return; - } - - /** - * 入力文字列に制限を加えるDocumentFilter。 - * \n,\f 以外の制御文字はタブも含め入力禁止。 - * U+FFFF はjava.textパッケージで特別扱いなのでこれも入力禁止。 - * ※ ただしIME操作中は制限なし。 - */ - private class CustomFilter extends DocumentFilter{ - - /** - * コンストラクタ。 - */ - public CustomFilter(){ - super(); - return; - } - - /** - * 入力禁止文字の判定。 - * @param ch 検査対象文字 - * @return 入力禁止ならfalse。ただしIME操作中は必ずtrue。 - */ - private boolean isValid(char ch){ - if(onIMEoperation()) return true; - - if(ch == '\n') return true; -// if(ch == '\f') return true; - - if(ch == '\uffff') return false; - if(Character.isISOControl(ch)) return false; - -// if( ! CodeX0208.isValid(ch) ) return false; - if(Character.isHighSurrogate(ch)) return false; - if(Character.isLowSurrogate(ch) ) return false; - - return true; - } - - /** - * 与えられた文字列から入力禁止文字を除いた文字列に変換する。 - * @param input 検査対象文字列 - * @return 除去済み文字列 - */ - private String filter(CharSequence input){ - if(onIMEoperation()) return input.toString(); - - int length = input.length(); - CharBuffer buf = CharBuffer.allocate(length); - - for(int pos = 0; pos < length; pos++){ - char ch = input.charAt(pos); - if(ch == '\u2211') ch = '\u03a3'; // Σ変換 - if(ch == '\u00ac') ch = '\uffe2'; // ¬変換 -// if(ch == 0x005c ) ch = '\u00a5'; // バックスラッシュから円へ - if(isValid(ch)) buf.append(ch); - } - - buf.flip(); - return buf.toString(); - } - - /** - * {@inheritDoc} - * @param fb {@inheritDoc} - * @param offset {@inheritDoc} - * @param text {@inheritDoc} - * @param attrs {@inheritDoc} - * @throws javax.swing.text.BadLocationException {@inheritDoc} - */ - @Override - public void insertString(FilterBypass fb, - int offset, - String text, - AttributeSet attrs) - throws BadLocationException{ - String filtered = filter(text); - - if( ! onIMEoperation() ){ - Document document = fb.getDocument(); - int docLength = document.getLength(); - int rest = MAX_DOCUMENT - docLength; - if(rest < 0){ - return; - }else if(rest < filtered.length()){ - filtered = filtered.substring(0, rest); - } - } - - fb.insertString(offset, filtered, attrs); - - return; - } - - /** - * {@inheritDoc} - * @param fb {@inheritDoc} - * @param offset {@inheritDoc} - * @param length {@inheritDoc} - * @param text {@inheritDoc} - * @param attrs {@inheritDoc} - * @throws javax.swing.text.BadLocationException {@inheritDoc} - */ - @Override - public void replace(FilterBypass fb, - int offset, - int length, - String text, - AttributeSet attrs) - throws BadLocationException{ - String filtered = filter(text); - - if( ! onIMEoperation() ){ - Document document = fb.getDocument(); - int docLength = document.getLength(); - docLength -= length; - int rest = MAX_DOCUMENT - docLength; - if(rest < 0){ - return; - }else if(rest < filtered.length()){ - filtered = filtered.substring(0, rest); - } - } - - fb.replace(offset, length, filtered, attrs); - - return; - } - } - - // TODO 禁則チェック。20文字を超える長大なブレーク禁止文字列の出現の監視。 - // TODO 連続したホワイトスペースに対する警告。 - // TODO 先頭もしくは末尾のホワイトスペース出現に対する警告。 - // TODO 改行記号の表示 - // TODO 改発言記号の導入 -} +/* + * 原稿作成支援用テキストコンポーネント + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Rectangle; +import java.awt.event.InputMethodEvent; +import java.awt.event.InputMethodListener; +import java.nio.CharBuffer; +import java.text.AttributedCharacterIterator; +import javax.swing.JTextArea; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.DocumentFilter; +import javax.swing.text.DocumentFilter.FilterBypass; +import javax.swing.text.PlainDocument; + +/** + * 原稿作成支援用テキストコンポーネント。 + */ +@SuppressWarnings("serial") +public class TextEditor extends JTextArea + implements InputMethodListener { + + private static final int MAX_DOCUMENT = 10 * 1000; + + private final DocumentFilter documentFilter = new CustomFilter(); + + private boolean onIMEoperation = false; + + /** + * コンストラクタ。 + */ + public TextEditor(){ + super(); + + setLineWrap(true); + setWrapStyleWord(false); + + Document document = new PlainDocument(); + setDocument(document); + + addInputMethodListener(this); + + return; + } + + /** + * エディタが現在IME操作中か判定する。 + * @return IME操作中ならtrue + */ + public boolean onIMEoperation(){ + return this.onIMEoperation; + } + + /** + * 現在のカーソルが表示されるようスクロールエリアを操作する。 + */ + public void scrollCaretToVisible(){ + int caretPosition = getCaretPosition(); + + Rectangle caretBounds; + try{ + caretBounds = modelToView(caretPosition); + }catch(BadLocationException e){ + assert false; + return; + } + + scrollRectToVisible(caretBounds); + + return; + } + + /** + * {@inheritDoc} + * Document変更をフックしてフィルタを仕込む。 + * @param document {@inheritDoc} + */ + @Override + public final void setDocument(Document document){ + Document oldDocument = getDocument(); + if(oldDocument instanceof AbstractDocument){ + AbstractDocument abstractDocument = + (AbstractDocument) oldDocument; + abstractDocument.setDocumentFilter(null); + } + + super.setDocument(document); + + if(document instanceof AbstractDocument){ + AbstractDocument abstractDocument = (AbstractDocument) document; + abstractDocument.setDocumentFilter(this.documentFilter); + } + + return; + } + + /** + * {@inheritDoc} + * このエディタ中の指定領域が表示されるようスクロールエリアを操作する。 + * キーボードフォーカスを保持しないときは無視。 + * @param rect {@inheritDoc} + */ + @Override + public void scrollRectToVisible(Rectangle rect){ + if( ! hasFocus() ) return; + super.scrollRectToVisible(rect); + return; + } + + /** + * {@inheritDoc} + * @param event {@inheritDoc} + */ + @Override + public void caretPositionChanged(InputMethodEvent event){ + // NOTHING + return; + } + + /** + * {@inheritDoc} + * このテキストエディタで現在IMEの変換中か否か判定する処理を含む。 + * @param event {@inheritDoc} + */ + @Override + public void inputMethodTextChanged(InputMethodEvent event){ + int committed = event.getCommittedCharacterCount(); + AttributedCharacterIterator aci = event.getText(); + if(aci == null){ + this.onIMEoperation = false; + return; + } + int begin = aci.getBeginIndex(); + int end = aci.getEndIndex(); + int span = end - begin; + + if(committed >= span) this.onIMEoperation = false; + else this.onIMEoperation = true; + + return; + } + + /** + * 入力文字列に制限を加えるDocumentFilter。 + * \n,\f 以外の制御文字はタブも含め入力禁止。 + * U+FFFF はjava.textパッケージで特別扱いなのでこれも入力禁止。 + * ※ ただしIME操作中は制限なし。 + */ + private class CustomFilter extends DocumentFilter{ + + /** + * コンストラクタ。 + */ + public CustomFilter(){ + super(); + return; + } + + /** + * 入力禁止文字の判定。 + * @param ch 検査対象文字 + * @return 入力禁止ならfalse。ただしIME操作中は必ずtrue。 + */ + private boolean isValid(char ch){ + if(onIMEoperation()) return true; + + if(ch == '\n') return true; +// if(ch == '\f') return true; + + if(ch == '\uffff') return false; + if(Character.isISOControl(ch)) return false; + +// if( ! CodeX0208.isValid(ch) ) return false; + if(Character.isHighSurrogate(ch)) return false; + if(Character.isLowSurrogate(ch) ) return false; + + return true; + } + + /** + * 与えられた文字列から入力禁止文字を除いた文字列に変換する。 + * @param input 検査対象文字列 + * @return 除去済み文字列 + */ + private String filter(CharSequence input){ + if(onIMEoperation()) return input.toString(); + + int length = input.length(); + CharBuffer buf = CharBuffer.allocate(length); + + for(int pos = 0; pos < length; pos++){ + char ch = input.charAt(pos); + if(ch == '\u2211') ch = '\u03a3'; // Σ変換 + if(ch == '\u00ac') ch = '\uffe2'; // ¬変換 +// if(ch == 0x005c ) ch = '\u00a5'; // バックスラッシュから円へ + if(isValid(ch)) buf.append(ch); + } + + buf.flip(); + return buf.toString(); + } + + /** + * {@inheritDoc} + * @param fb {@inheritDoc} + * @param offset {@inheritDoc} + * @param text {@inheritDoc} + * @param attrs {@inheritDoc} + * @throws javax.swing.text.BadLocationException {@inheritDoc} + */ + @Override + public void insertString(FilterBypass fb, + int offset, + String text, + AttributeSet attrs) + throws BadLocationException{ + String filtered = filter(text); + + if( ! onIMEoperation() ){ + Document document = fb.getDocument(); + int docLength = document.getLength(); + int rest = MAX_DOCUMENT - docLength; + if(rest < 0){ + return; + }else if(rest < filtered.length()){ + filtered = filtered.substring(0, rest); + } + } + + fb.insertString(offset, filtered, attrs); + + return; + } + + /** + * {@inheritDoc} + * @param fb {@inheritDoc} + * @param offset {@inheritDoc} + * @param length {@inheritDoc} + * @param text {@inheritDoc} + * @param attrs {@inheritDoc} + * @throws javax.swing.text.BadLocationException {@inheritDoc} + */ + @Override + public void replace(FilterBypass fb, + int offset, + int length, + String text, + AttributeSet attrs) + throws BadLocationException{ + String filtered = filter(text); + + if( ! onIMEoperation() ){ + Document document = fb.getDocument(); + int docLength = document.getLength(); + docLength -= length; + int rest = MAX_DOCUMENT - docLength; + if(rest < 0){ + return; + }else if(rest < filtered.length()){ + filtered = filtered.substring(0, rest); + } + } + + fb.replace(offset, length, filtered, attrs); + + return; + } + } + + // TODO 禁則チェック。20文字を超える長大なブレーク禁止文字列の出現の監視。 + // TODO 連続したホワイトスペースに対する警告。 + // TODO 先頭もしくは末尾のホワイトスペース出現に対する警告。 + // TODO 改行記号の表示 + // TODO 改発言記号の導入 +} diff --git a/src/main/java/jp/sourceforge/jindolf/TextPopup.java b/src/main/java/jp/sourceforge/jindolf/TextPopup.java index 28d338d..c912e29 100644 --- a/src/main/java/jp/sourceforge/jindolf/TextPopup.java +++ b/src/main/java/jp/sourceforge/jindolf/TextPopup.java @@ -1,153 +1,153 @@ -/* - * テキストコンポーネント用ポップアップメニュー - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import javax.swing.Action; -import javax.swing.JPopupMenu; -import javax.swing.text.Document; -import javax.swing.text.JTextComponent; - -/** - * テキストコンポーネント用ポップアップメニュー。 - * 各種クリップボード操作(カット、コピー、ペースト、etc.)を備える。 - */ -@SuppressWarnings("serial") -public class TextPopup extends JPopupMenu implements PropertyChangeListener{ - - /** プロパティ変更イベントキー。 */ - private static final String PROPERTY_UI = "UI"; - - private final Action cutAction = ClipboardAction.cutAction(); - private final Action copyAction = ClipboardAction.copyAction(); - private final Action pasteAction = ClipboardAction.pasteAction(); - private final Action selallAction = ClipboardAction.selectallAction(); - - /** - * コンストラクタ。 - */ - public TextPopup(){ - super(); - - buildMenu(); - - return; - } - - /** - * メニューの構成を作る。 - */ - private void buildMenu(){ - add(this.cutAction); - add(this.copyAction); - add(this.pasteAction); - - addSeparator(); - - add(this.selallAction); - - return; - } - - /** - * テキストコンポーネントに選択中文字列があるか判定する。 - * @param textComp テキストコンポーネント - * @return 選択中文字列があればtrue - */ - protected boolean hasSelectedContent(JTextComponent textComp){ - int selStart = textComp.getSelectionStart(); - int selEnd = textComp.getSelectionEnd(); - - boolean result; - if(selStart == selEnd) result = false; - else result = true; - - return result; - } - - /** - * テキストコンポーネントに文字列があるか判定する。 - * @param textComp テキストコンポーネント - * @return 文字列があればtrue - */ - protected boolean hasContent(JTextComponent textComp){ - Document document = textComp.getDocument(); - int docLength = document.getLength(); - if(docLength <= 0) return false; - return true; - } - - /** - * {@inheritDoc} - * ついでにL&F変更監視機構を仕込む。 - * @param invoker {@inheritDoc} - */ - @Override - public void setInvoker(Component invoker){ - Component old = getInvoker(); - if(old != null){ - old.removePropertyChangeListener(this); - } - - super.setInvoker(invoker); - - if(invoker != null){ - invoker.addPropertyChangeListener(PROPERTY_UI, this); - } - - return; - } - - /** - * {@inheritDoc} - * 文字列選択状況によって一部のポップアップメニューをマスクする。 - * @param invoker {@inheritDoc} - * @param x {@inheritDoc} - * @param y {@inheritDoc} - */ - @Override - public void show(Component invoker, int x, int y){ - if( ! (invoker instanceof JTextComponent) ) return; - JTextComponent textComp = (JTextComponent) invoker; - - boolean textSelected = hasSelectedContent(textComp); - this.cutAction .setEnabled(textSelected); - this.copyAction.setEnabled(textSelected); - - if( ! textComp.isEditable() ){ - this.cutAction .setEnabled(false); - this.pasteAction .setEnabled(false); - } - - if(hasContent(textComp)){ - this.selallAction.setEnabled(true); - }else{ - this.selallAction.setEnabled(false); - } - - super.show(invoker, x, y); - - return; - } - - /** - * {@inheritDoc} - * ポップアップ呼び出し元を監視してL&Fを変更する。 - * @param event {@inheritDoc} - */ - @Override - public void propertyChange(PropertyChangeEvent event){ - String propertyName = event.getPropertyName(); - if(PROPERTY_UI.equals(propertyName)) updateUI(); - return; - } - - // TODO アクセス権チェックによるポップアップメニュー変更 -} +/* + * テキストコンポーネント用ポップアップメニュー + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.Action; +import javax.swing.JPopupMenu; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; + +/** + * テキストコンポーネント用ポップアップメニュー。 + * 各種クリップボード操作(カット、コピー、ペースト、etc.)を備える。 + */ +@SuppressWarnings("serial") +public class TextPopup extends JPopupMenu implements PropertyChangeListener{ + + /** プロパティ変更イベントキー。 */ + private static final String PROPERTY_UI = "UI"; + + private final Action cutAction = ClipboardAction.cutAction(); + private final Action copyAction = ClipboardAction.copyAction(); + private final Action pasteAction = ClipboardAction.pasteAction(); + private final Action selallAction = ClipboardAction.selectallAction(); + + /** + * コンストラクタ。 + */ + public TextPopup(){ + super(); + + buildMenu(); + + return; + } + + /** + * メニューの構成を作る。 + */ + private void buildMenu(){ + add(this.cutAction); + add(this.copyAction); + add(this.pasteAction); + + addSeparator(); + + add(this.selallAction); + + return; + } + + /** + * テキストコンポーネントに選択中文字列があるか判定する。 + * @param textComp テキストコンポーネント + * @return 選択中文字列があればtrue + */ + protected boolean hasSelectedContent(JTextComponent textComp){ + int selStart = textComp.getSelectionStart(); + int selEnd = textComp.getSelectionEnd(); + + boolean result; + if(selStart == selEnd) result = false; + else result = true; + + return result; + } + + /** + * テキストコンポーネントに文字列があるか判定する。 + * @param textComp テキストコンポーネント + * @return 文字列があればtrue + */ + protected boolean hasContent(JTextComponent textComp){ + Document document = textComp.getDocument(); + int docLength = document.getLength(); + if(docLength <= 0) return false; + return true; + } + + /** + * {@inheritDoc} + * ついでにL&F変更監視機構を仕込む。 + * @param invoker {@inheritDoc} + */ + @Override + public void setInvoker(Component invoker){ + Component old = getInvoker(); + if(old != null){ + old.removePropertyChangeListener(this); + } + + super.setInvoker(invoker); + + if(invoker != null){ + invoker.addPropertyChangeListener(PROPERTY_UI, this); + } + + return; + } + + /** + * {@inheritDoc} + * 文字列選択状況によって一部のポップアップメニューをマスクする。 + * @param invoker {@inheritDoc} + * @param x {@inheritDoc} + * @param y {@inheritDoc} + */ + @Override + public void show(Component invoker, int x, int y){ + if( ! (invoker instanceof JTextComponent) ) return; + JTextComponent textComp = (JTextComponent) invoker; + + boolean textSelected = hasSelectedContent(textComp); + this.cutAction .setEnabled(textSelected); + this.copyAction.setEnabled(textSelected); + + if( ! textComp.isEditable() ){ + this.cutAction .setEnabled(false); + this.pasteAction .setEnabled(false); + } + + if(hasContent(textComp)){ + this.selallAction.setEnabled(true); + }else{ + this.selallAction.setEnabled(false); + } + + super.show(invoker, x, y); + + return; + } + + /** + * {@inheritDoc} + * ポップアップ呼び出し元を監視してL&Fを変更する。 + * @param event {@inheritDoc} + */ + @Override + public void propertyChange(PropertyChangeEvent event){ + String propertyName = event.getPropertyName(); + if(PROPERTY_UI.equals(propertyName)) updateUI(); + return; + } + + // TODO アクセス権チェックによるポップアップメニュー変更 +} diff --git a/src/main/java/jp/sourceforge/jindolf/TextRow.java b/src/main/java/jp/sourceforge/jindolf/TextRow.java index 246ea3a..281103c 100644 --- a/src/main/java/jp/sourceforge/jindolf/TextRow.java +++ b/src/main/java/jp/sourceforge/jindolf/TextRow.java @@ -1,81 +1,81 @@ -/* - * 矩形領域テキスト描画基本インタフェース - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Graphics2D; -import java.awt.Rectangle; - -/** - * 矩形に複数行のテキストを配置・描画する「なにものか」を表すインタフェース。 - * この場合、「行」とは左から右へ水平方向にグリフを並べたもの。 - */ -public interface TextRow extends Selectable{ - - /** - * 新しい幅を指定し、寸法の再計算、内部の再レイアウトを促す。 - * @param newWidth 新しいピクセル幅 - * @return 新しい寸法 - */ - Rectangle setWidth(int newWidth); - - /** - * 現在の設定で寸法の再計算、内部の再レイアウトを促す。 - * @return 新しい寸法 - */ - Rectangle recalcBounds(); - - /** - * 描画領域の寸法を返す。 - * @return 描画領域の寸法 - */ - Rectangle getBounds(); - - /** - * 描画開始位置の指定。 - * @param xPos 描画開始位置のx座標 - * @param yPos 描画開始位置のy座標 - */ - void setPos(int xPos, int yPos); - - /** - * 描画領域の寸法幅を返す。 - * @return 描画領域の寸法幅 - */ - int getWidth(); - - /** - * 描画領域の寸法高を返す。 - * @return 描画領域の寸法高 - */ - int getHeight(); - - /** - * フォント設定を変更する。 - * @param fontInfo フォント設定 - */ - void setFontInfo(FontInfo fontInfo); - - /** - * 描画対象か否か判定する。 - * @return 描画対象ならtrue - */ - boolean isVisible(); - - /** - * 描画対象か否か設定する。 - * @param visible 描画対象ならtrue - */ - void setVisible(boolean visible); - - /** - * 描画を行う。 - * @param g グラフィックコンテキスト - */ - void paint(Graphics2D g); - -} +/* + * 矩形領域テキスト描画基本インタフェース + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Graphics2D; +import java.awt.Rectangle; + +/** + * 矩形に複数行のテキストを配置・描画する「なにものか」を表すインタフェース。 + * この場合、「行」とは左から右へ水平方向にグリフを並べたもの。 + */ +public interface TextRow extends Selectable{ + + /** + * 新しい幅を指定し、寸法の再計算、内部の再レイアウトを促す。 + * @param newWidth 新しいピクセル幅 + * @return 新しい寸法 + */ + Rectangle setWidth(int newWidth); + + /** + * 現在の設定で寸法の再計算、内部の再レイアウトを促す。 + * @return 新しい寸法 + */ + Rectangle recalcBounds(); + + /** + * 描画領域の寸法を返す。 + * @return 描画領域の寸法 + */ + Rectangle getBounds(); + + /** + * 描画開始位置の指定。 + * @param xPos 描画開始位置のx座標 + * @param yPos 描画開始位置のy座標 + */ + void setPos(int xPos, int yPos); + + /** + * 描画領域の寸法幅を返す。 + * @return 描画領域の寸法幅 + */ + int getWidth(); + + /** + * 描画領域の寸法高を返す。 + * @return 描画領域の寸法高 + */ + int getHeight(); + + /** + * フォント設定を変更する。 + * @param fontInfo フォント設定 + */ + void setFontInfo(FontInfo fontInfo); + + /** + * 描画対象か否か判定する。 + * @return 描画対象ならtrue + */ + boolean isVisible(); + + /** + * 描画対象か否か設定する。 + * @param visible 描画対象ならtrue + */ + void setVisible(boolean visible); + + /** + * 描画を行う。 + * @param g グラフィックコンテキスト + */ + void paint(Graphics2D g); + +} diff --git a/src/main/java/jp/sourceforge/jindolf/TopView.java b/src/main/java/jp/sourceforge/jindolf/TopView.java index 6b53c3f..6c6d5f8 100644 --- a/src/main/java/jp/sourceforge/jindolf/TopView.java +++ b/src/main/java/jp/sourceforge/jindolf/TopView.java @@ -1,300 +1,300 @@ -/* - * Top view - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.BorderLayout; -import java.awt.CardLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import javax.swing.BorderFactory; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTextField; -import javax.swing.JToolBar; -import javax.swing.JTree; -import javax.swing.SwingConstants; -import javax.swing.border.BevelBorder; -import javax.swing.border.Border; -import javax.swing.border.CompoundBorder; - -/** - * 最上位ビュー。 - * メインアプリウィンドウのコンポーネントの親コンテナ。 - */ -@SuppressWarnings("serial") -public class TopView extends JPanel{ - - private static final String INITCARD = "INITCARD"; - private static final String LANDCARD = "LANDINFO"; - private static final String BROWSECARD = "BROWSER"; - - private final JComponent cards; - private final CardLayout cardLayout = new CardLayout(); - - private final LandsTree landsTreeView = new LandsTree(); - - private final LandInfoPanel landInfo = new LandInfoPanel(); - - private final JTextField sysMessage = new JTextField(); - private final JProgressBar progressBar = new JProgressBar(); - - private final TabBrowser tabBrowser = new TabBrowser(); - - private JComponent browsePanel; - - /** - * トップビューを生成する。 - */ - public TopView(){ - super(); - - this.cards = createCards(); - JComponent split = createSplitPane(this.landsTreeView, this.cards); - JComponent statusBar = createStatusBar(); - - BorderLayout layout = new BorderLayout(); - setLayout(layout); - add(split, BorderLayout.CENTER); - add(statusBar, BorderLayout.SOUTH); - - return; - } - - /** - * カードパネルを生成する。 - * @return カードパネル - */ - private JComponent createCards(){ - this.browsePanel = createBrowsePanel(); - - JPanel panel = new JPanel(); - panel.setLayout(this.cardLayout); - panel.add(INITCARD, createInitCard()); - panel.add(LANDCARD, createLandInfoCard()); - panel.add(BROWSECARD, this.browsePanel); - - return panel; - } - - /** - * 初期パネルを生成。 - * @return 初期パネル - */ - private JComponent createInitCard(){ - JLabel initMessage = new JLabel("← 村を選択してください"); - - StringBuilder acct = new StringBuilder(); - acct.append("※ 参加中の村がある人は

"); - acct.append("メニューの「アカウント管理」から

"); - acct.append("ログインしてください"); - acct.insert(0, "
").append("
"); - acct.insert(0, "") .append(""); - acct.insert(0, "") .append(""); - JLabel acctMessage = new JLabel(acct.toString()); - - StringBuilder warn = new StringBuilder(); - warn.append("※ たまにはWebブラウザでアクセスして、"); - warn.append("

"); - warn.append("運営の動向を確かめようね!"); - warn.insert(0, "
").append("
"); - warn.insert(0, "") .append(""); - warn.insert(0, "") .append(""); - JLabel warnMessage = new JLabel(warn.toString()); - - JPanel panel = new JPanel(); - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - panel.setLayout(layout); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.anchor = GridBagConstraints.CENTER; - constraints.gridx = GridBagConstraints.REMAINDER; - panel.add(initMessage, constraints); - panel.add(acctMessage, constraints); - panel.add(warnMessage, constraints); - - JScrollPane scrollPane = new JScrollPane(panel); - - return scrollPane; - } - - /** - * 国別情報を生成。 - * @return 国別情報 - */ - private JComponent createLandInfoCard(){ - this.landInfo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - JScrollPane scrollPane = new JScrollPane(this.landInfo); - return scrollPane; - } - - /** - * 内部ブラウザを生成。 - * @return 内部ブラウザ - */ - private JComponent createBrowsePanel(){ - JPanel panel = new JPanel(); - BorderLayout layout = new BorderLayout(); - panel.setLayout(layout); - - panel.add(this.tabBrowser, BorderLayout.CENTER); - - return panel; - } - - /** - * ブラウザ用ツールバーをセットする。 - * @param toolbar ツールバー - */ - public void setBrowseToolBar(JToolBar toolbar){ - this.browsePanel.add(toolbar, BorderLayout.NORTH); - return; - } - - /** - * SplitPaneを生成。 - * @param left 左コンポーネント - * @param right 右コンポーネント - * @return SplitPane - */ - private JComponent createSplitPane(JComponent left, JComponent right){ - JSplitPane split = new JSplitPane(); - split.setLeftComponent(left); - split.setRightComponent(right); - split.setOrientation(JSplitPane.HORIZONTAL_SPLIT); - split.setContinuousLayout(false); - split.setOneTouchExpandable(true); - split.setDividerLocation(200); - - return split; - } - - /** - * ステータスバーを生成する。 - * @return ステータスバー - */ - private JComponent createStatusBar(){ - this.sysMessage.setText( - Jindolf.TITLE + " " + Jindolf.VERSION - + " を使ってくれてありがとう!" ); - this.sysMessage.setEditable(false); - Border inside = BorderFactory.createBevelBorder(BevelBorder.LOWERED); - Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2); - Border border = new CompoundBorder(inside, outside); - this.sysMessage.setBorder(border); - - this.progressBar.setIndeterminate(false); - this.progressBar.setOrientation(SwingConstants.HORIZONTAL); - this.progressBar.setMinimum(0); - this.progressBar.setMaximum(99); - this.progressBar.setValue(0); - - JPanel statusBar = new JPanel(); - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - statusBar.setLayout(layout); - - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - statusBar.add(this.sysMessage, constraints); - - constraints.weightx = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.insets = new Insets(2, 2, 2, 2); - statusBar.add(this.progressBar, constraints); - - return statusBar; - } - - /** - * 国村選択ツリービューを返す。 - * @return 国村選択ツリービュー - */ - public JTree getTreeView(){ - return this.landsTreeView.getTreeView(); - } - - /** - * タブビューを返す。 - * @return タブビュー - */ - public TabBrowser getTabBrowser(){ - return this.tabBrowser; - } - - /** - * 村一覧ビューを返す。 - * @return 村一番ビュー - */ - public LandsTree getLandsTree(){ - return this.landsTreeView; - } - - /** - * プログレスバーとカーソルの設定を行う。 - * @param busy trueならプログレスバーのアニメ開始&WAITカーソル。 - * falseなら停止&通常カーソル。 - */ - public void setBusy(boolean busy){ - this.progressBar.setIndeterminate(busy); - return; - } - - /** - * ステータスバーの更新。 - * @param message 更新文字列 - */ - public void updateSysMessage(String message){ - if(message == null) return; - String text; - if(message.length() <= 0) text = " "; - else text = message; - this.sysMessage.setText(text); // Thread safe - GUIUtils.dispatchEmptyAWTEvent(); - return; - } - - /** - * 初期パネルを表示する。 - */ - public void showInitPanel(){ - this.cardLayout.show(this.cards, INITCARD); - return; - } - - /** - * 村情報を表示する。 - * @param village 村 - */ - public void showVillageInfo(Village village){ - this.tabBrowser.setVillage(village); - this.cardLayout.show(this.cards, BROWSECARD); - this.tabBrowser.repaint(); - this.tabBrowser.revalidate(); - - return; - } - - /** - * 国情報を表示する。 - * @param land 国 - */ - public void showLandInfo(Land land){ - this.landInfo.update(land); - this.cardLayout.show(this.cards, LANDCARD); - return; - } - - // TODO setEnabled()を全子フレームにも波及させるべきか -} +/* + * Top view + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.JToolBar; +import javax.swing.JTree; +import javax.swing.SwingConstants; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; + +/** + * 最上位ビュー。 + * メインアプリウィンドウのコンポーネントの親コンテナ。 + */ +@SuppressWarnings("serial") +public class TopView extends JPanel{ + + private static final String INITCARD = "INITCARD"; + private static final String LANDCARD = "LANDINFO"; + private static final String BROWSECARD = "BROWSER"; + + private final JComponent cards; + private final CardLayout cardLayout = new CardLayout(); + + private final LandsTree landsTreeView = new LandsTree(); + + private final LandInfoPanel landInfo = new LandInfoPanel(); + + private final JTextField sysMessage = new JTextField(); + private final JProgressBar progressBar = new JProgressBar(); + + private final TabBrowser tabBrowser = new TabBrowser(); + + private JComponent browsePanel; + + /** + * トップビューを生成する。 + */ + public TopView(){ + super(); + + this.cards = createCards(); + JComponent split = createSplitPane(this.landsTreeView, this.cards); + JComponent statusBar = createStatusBar(); + + BorderLayout layout = new BorderLayout(); + setLayout(layout); + add(split, BorderLayout.CENTER); + add(statusBar, BorderLayout.SOUTH); + + return; + } + + /** + * カードパネルを生成する。 + * @return カードパネル + */ + private JComponent createCards(){ + this.browsePanel = createBrowsePanel(); + + JPanel panel = new JPanel(); + panel.setLayout(this.cardLayout); + panel.add(INITCARD, createInitCard()); + panel.add(LANDCARD, createLandInfoCard()); + panel.add(BROWSECARD, this.browsePanel); + + return panel; + } + + /** + * 初期パネルを生成。 + * @return 初期パネル + */ + private JComponent createInitCard(){ + JLabel initMessage = new JLabel("← 村を選択してください"); + + StringBuilder acct = new StringBuilder(); + acct.append("※ 参加中の村がある人は

"); + acct.append("メニューの「アカウント管理」から

"); + acct.append("ログインしてください"); + acct.insert(0, "
").append("
"); + acct.insert(0, "") .append(""); + acct.insert(0, "") .append(""); + JLabel acctMessage = new JLabel(acct.toString()); + + StringBuilder warn = new StringBuilder(); + warn.append("※ たまにはWebブラウザでアクセスして、"); + warn.append("

"); + warn.append("運営の動向を確かめようね!"); + warn.insert(0, "
").append("
"); + warn.insert(0, "") .append(""); + warn.insert(0, "") .append(""); + JLabel warnMessage = new JLabel(warn.toString()); + + JPanel panel = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + panel.setLayout(layout); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.anchor = GridBagConstraints.CENTER; + constraints.gridx = GridBagConstraints.REMAINDER; + panel.add(initMessage, constraints); + panel.add(acctMessage, constraints); + panel.add(warnMessage, constraints); + + JScrollPane scrollPane = new JScrollPane(panel); + + return scrollPane; + } + + /** + * 国別情報を生成。 + * @return 国別情報 + */ + private JComponent createLandInfoCard(){ + this.landInfo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + JScrollPane scrollPane = new JScrollPane(this.landInfo); + return scrollPane; + } + + /** + * 内部ブラウザを生成。 + * @return 内部ブラウザ + */ + private JComponent createBrowsePanel(){ + JPanel panel = new JPanel(); + BorderLayout layout = new BorderLayout(); + panel.setLayout(layout); + + panel.add(this.tabBrowser, BorderLayout.CENTER); + + return panel; + } + + /** + * ブラウザ用ツールバーをセットする。 + * @param toolbar ツールバー + */ + public void setBrowseToolBar(JToolBar toolbar){ + this.browsePanel.add(toolbar, BorderLayout.NORTH); + return; + } + + /** + * SplitPaneを生成。 + * @param left 左コンポーネント + * @param right 右コンポーネント + * @return SplitPane + */ + private JComponent createSplitPane(JComponent left, JComponent right){ + JSplitPane split = new JSplitPane(); + split.setLeftComponent(left); + split.setRightComponent(right); + split.setOrientation(JSplitPane.HORIZONTAL_SPLIT); + split.setContinuousLayout(false); + split.setOneTouchExpandable(true); + split.setDividerLocation(200); + + return split; + } + + /** + * ステータスバーを生成する。 + * @return ステータスバー + */ + private JComponent createStatusBar(){ + this.sysMessage.setText( + Jindolf.TITLE + " " + Jindolf.VERSION + + " を使ってくれてありがとう!" ); + this.sysMessage.setEditable(false); + Border inside = BorderFactory.createBevelBorder(BevelBorder.LOWERED); + Border outside = BorderFactory.createEmptyBorder(2, 5, 2, 2); + Border border = new CompoundBorder(inside, outside); + this.sysMessage.setBorder(border); + + this.progressBar.setIndeterminate(false); + this.progressBar.setOrientation(SwingConstants.HORIZONTAL); + this.progressBar.setMinimum(0); + this.progressBar.setMaximum(99); + this.progressBar.setValue(0); + + JPanel statusBar = new JPanel(); + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + statusBar.setLayout(layout); + + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + statusBar.add(this.sysMessage, constraints); + + constraints.weightx = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.insets = new Insets(2, 2, 2, 2); + statusBar.add(this.progressBar, constraints); + + return statusBar; + } + + /** + * 国村選択ツリービューを返す。 + * @return 国村選択ツリービュー + */ + public JTree getTreeView(){ + return this.landsTreeView.getTreeView(); + } + + /** + * タブビューを返す。 + * @return タブビュー + */ + public TabBrowser getTabBrowser(){ + return this.tabBrowser; + } + + /** + * 村一覧ビューを返す。 + * @return 村一番ビュー + */ + public LandsTree getLandsTree(){ + return this.landsTreeView; + } + + /** + * プログレスバーとカーソルの設定を行う。 + * @param busy trueならプログレスバーのアニメ開始&WAITカーソル。 + * falseなら停止&通常カーソル。 + */ + public void setBusy(boolean busy){ + this.progressBar.setIndeterminate(busy); + return; + } + + /** + * ステータスバーの更新。 + * @param message 更新文字列 + */ + public void updateSysMessage(String message){ + if(message == null) return; + String text; + if(message.length() <= 0) text = " "; + else text = message; + this.sysMessage.setText(text); // Thread safe + GUIUtils.dispatchEmptyAWTEvent(); + return; + } + + /** + * 初期パネルを表示する。 + */ + public void showInitPanel(){ + this.cardLayout.show(this.cards, INITCARD); + return; + } + + /** + * 村情報を表示する。 + * @param village 村 + */ + public void showVillageInfo(Village village){ + this.tabBrowser.setVillage(village); + this.cardLayout.show(this.cards, BROWSECARD); + this.tabBrowser.repaint(); + this.tabBrowser.revalidate(); + + return; + } + + /** + * 国情報を表示する。 + * @param land 国 + */ + public void showLandInfo(Land land){ + this.landInfo.update(land); + this.cardLayout.show(this.cards, LANDCARD); + return; + } + + // TODO setEnabled()を全子フレームにも波及させるべきか +} diff --git a/src/main/java/jp/sourceforge/jindolf/Topic.java b/src/main/java/jp/sourceforge/jindolf/Topic.java index c2dfc6b..9278190 100644 --- a/src/main/java/jp/sourceforge/jindolf/Topic.java +++ b/src/main/java/jp/sourceforge/jindolf/Topic.java @@ -1,15 +1,15 @@ -/* - * topic in game - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -/** - * プレイヤーの発言及びゲームシステムからのメッセージのスーパーインタフェース。 - */ -public interface Topic{ - // TODO 脱HTML化した現在、このインタフェースの存在意義は? -} +/* + * topic in game + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +/** + * プレイヤーの発言及びゲームシステムからのメッセージのスーパーインタフェース。 + */ +public interface Topic{ + // TODO 脱HTML化した現在、このインタフェースの存在意義は? +} diff --git a/src/main/java/jp/sourceforge/jindolf/TopicFilter.java b/src/main/java/jp/sourceforge/jindolf/TopicFilter.java index 8b396ba..e3ba864 100644 --- a/src/main/java/jp/sourceforge/jindolf/TopicFilter.java +++ b/src/main/java/jp/sourceforge/jindolf/TopicFilter.java @@ -1,41 +1,41 @@ -/* - * Topic filter - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -/** - * 発言Topicのフィルタリングを行うインタフェース。 - */ -public interface TopicFilter { - - /** - * フィルタの状態を表すインタフェース。 - */ - interface FilterContext{} - - /** - * 与えられたTopicをフィルタリングする。 - * @param topic Topic - * @return フィルタリングするならtrue - */ - boolean isFiltered(Topic topic); - - /** - * フィルタの内部状態を表すインスタンスを取得する。 - * @return フィルタの内部状態 - */ - FilterContext getFilterContext(); - - /** - * 以前得られたフィルタ内部状態と同じフィルタリング条件を - * 現在も保っているか判別する。 - * @param context フィルタの内部状態 - * @return 同じ状態ならtrue - */ - boolean isSame(FilterContext context); - -} +/* + * Topic filter + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +/** + * 発言Topicのフィルタリングを行うインタフェース。 + */ +public interface TopicFilter { + + /** + * フィルタの状態を表すインタフェース。 + */ + interface FilterContext{} + + /** + * 与えられたTopicをフィルタリングする。 + * @param topic Topic + * @return フィルタリングするならtrue + */ + boolean isFiltered(Topic topic); + + /** + * フィルタの内部状態を表すインスタンスを取得する。 + * @return フィルタの内部状態 + */ + FilterContext getFilterContext(); + + /** + * 以前得られたフィルタ内部状態と同じフィルタリング条件を + * 現在も保っているか判別する。 + * @param context フィルタの内部状態 + * @return 同じ状態ならtrue + */ + boolean isSame(FilterContext context); + +} diff --git a/src/main/java/jp/sourceforge/jindolf/UriExporter.java b/src/main/java/jp/sourceforge/jindolf/UriExporter.java index ea47b2f..6ca0c6c 100644 --- a/src/main/java/jp/sourceforge/jindolf/UriExporter.java +++ b/src/main/java/jp/sourceforge/jindolf/UriExporter.java @@ -1,111 +1,111 @@ -/* - * URI expoter - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; - -/** - * URIのエクスポートを行う。 - * エクスポートに使うMIMEは「text/uri-list」と「text/plain」。 - */ -public class UriExporter implements Transferable{ - - private static final String[] MIMES = { - "text/uri-list", - "text/plain", - }; - private static final List URI_FLAVORS = - new ArrayList(MIMES.length); - - static{ - String stringClassName = "class=" + String.class.getName(); - for(String mime : MIMES){ - String newMime = mime + "; " + stringClassName; - DataFlavor dataFlavor; - try{ - dataFlavor = new DataFlavor(newMime); - }catch(ClassNotFoundException e){ - assert false; - throw new ExceptionInInitializerError(e); - } - URI_FLAVORS.add(dataFlavor); - } - } - - private final URI uri; - - /** - * コンストラクタ。 - * @param uri URI - */ - public UriExporter(URI uri){ - super(); - this.uri = uri; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public DataFlavor[] getTransferDataFlavors(){ - DataFlavor[] result = new DataFlavor[URI_FLAVORS.size()]; - int index = 0; - for(DataFlavor dataFlavor : URI_FLAVORS){ - try{ - result[index++] = (DataFlavor)( dataFlavor.clone() ); - }catch(CloneNotSupportedException e){ - assert false; - throw new AssertionError(e); - } - } - return result; - } - - /** - * {@inheritDoc} - * @param flavor {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean isDataFlavorSupported(DataFlavor flavor){ - for(DataFlavor dataFlavor : URI_FLAVORS){ - if(dataFlavor.equals(flavor)) return true; - } - return false; - } - - /** - * {@inheritDoc} - * エクスポートするURI文字列を返す。 - * @param flavor {@inheritDoc} - * @return {@inheritDoc} - * @throws java.awt.datatransfer.UnsupportedFlavorException {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public Object getTransferData(DataFlavor flavor) - throws UnsupportedFlavorException, - IOException { - if( ! isDataFlavorSupported(flavor) ){ - throw new UnsupportedFlavorException(flavor); - } - - String result = this.uri.toASCIIString(); - - return result; - } - -} +/* + * URI expoter + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * URIのエクスポートを行う。 + * エクスポートに使うMIMEは「text/uri-list」と「text/plain」。 + */ +public class UriExporter implements Transferable{ + + private static final String[] MIMES = { + "text/uri-list", + "text/plain", + }; + private static final List URI_FLAVORS = + new ArrayList(MIMES.length); + + static{ + String stringClassName = "class=" + String.class.getName(); + for(String mime : MIMES){ + String newMime = mime + "; " + stringClassName; + DataFlavor dataFlavor; + try{ + dataFlavor = new DataFlavor(newMime); + }catch(ClassNotFoundException e){ + assert false; + throw new ExceptionInInitializerError(e); + } + URI_FLAVORS.add(dataFlavor); + } + } + + private final URI uri; + + /** + * コンストラクタ。 + * @param uri URI + */ + public UriExporter(URI uri){ + super(); + this.uri = uri; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public DataFlavor[] getTransferDataFlavors(){ + DataFlavor[] result = new DataFlavor[URI_FLAVORS.size()]; + int index = 0; + for(DataFlavor dataFlavor : URI_FLAVORS){ + try{ + result[index++] = (DataFlavor)( dataFlavor.clone() ); + }catch(CloneNotSupportedException e){ + assert false; + throw new AssertionError(e); + } + } + return result; + } + + /** + * {@inheritDoc} + * @param flavor {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean isDataFlavorSupported(DataFlavor flavor){ + for(DataFlavor dataFlavor : URI_FLAVORS){ + if(dataFlavor.equals(flavor)) return true; + } + return false; + } + + /** + * {@inheritDoc} + * エクスポートするURI文字列を返す。 + * @param flavor {@inheritDoc} + * @return {@inheritDoc} + * @throws java.awt.datatransfer.UnsupportedFlavorException {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, + IOException { + if( ! isDataFlavorSupported(flavor) ){ + throw new UnsupportedFlavorException(flavor); + } + + String result = this.uri.toASCIIString(); + + return result; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/Village.java b/src/main/java/jp/sourceforge/jindolf/Village.java index 0e75352..a212a5b 100644 --- a/src/main/java/jp/sourceforge/jindolf/Village.java +++ b/src/main/java/jp/sourceforge/jindolf/Village.java @@ -1,875 +1,875 @@ -/* - * Village - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import jp.sourceforge.jindolf.corelib.LandDef; -import jp.sourceforge.jindolf.corelib.LandState; -import jp.sourceforge.jindolf.corelib.PeriodType; -import jp.sourceforge.jindolf.corelib.VillageState; -import jp.sourceforge.jindolf.parser.DecodedContent; -import jp.sourceforge.jindolf.parser.HtmlAdapter; -import jp.sourceforge.jindolf.parser.HtmlParseException; -import jp.sourceforge.jindolf.parser.HtmlParser; -import jp.sourceforge.jindolf.parser.PageType; -import jp.sourceforge.jindolf.parser.SeqRange; - -/** - * いわゆる「村」。 - */ -public class Village implements Comparable { - - /** - * 村同士を比較するためのComparator。 - */ - private static class VillageComparator implements Comparator { - - /** - * コンストラクタ。 - */ - public VillageComparator(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param v1 {@inheritDoc} - * @param v2 {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compare(Village v1, Village v2){ - int v1Num; - if(v1 == null) v1Num = Integer.MIN_VALUE; - else v1Num = v1.getVillageIDNum(); - - int v2Num; - if(v2 == null) v2Num = Integer.MIN_VALUE; - else v2Num = v2.getVillageIDNum(); - - return v1Num - v2Num; - } - - } - - private static final Comparator VILLAGE_COMPARATOR = - new VillageComparator(); - - private static final HtmlParser PARSER = new HtmlParser(); - private static final VillageHeadHandler HANDLER = - new VillageHeadHandler(); - - static{ - PARSER.setBasicHandler (HANDLER); - PARSER.setSysEventHandler(HANDLER); - PARSER.setTalkHandler (HANDLER); - } - - - private final Land parentLand; - private final String villageID; - private final int villageIDNum; - private final String villageName; - - private final boolean isValid; - - private int limitMonth; - private int limitDay; - private int limitHour; - private int limitMinute; - - private VillageState state = VillageState.UNKNOWN; - - private final LinkedList periodList = new LinkedList(); - private final List unmodList = - Collections.unmodifiableList(this.periodList); - - private final Map avatarMap = - new HashMap(); - - private final Map faceImageMap = - new HashMap(); - private final Map bodyImageMap = - new HashMap(); - private final Map faceMonoImageMap = - new HashMap(); - private final Map bodyMonoImageMap = - new HashMap(); - - - /** - * Villageを生成する。 - * @param parentLand Villageの所属する国 - * @param villageID 村のID - * @param villageName 村の名前 - */ - public Village(Land parentLand, String villageID, String villageName) { - this.parentLand = parentLand; - this.villageID = villageID.intern(); - this.villageIDNum = Integer.parseInt(this.villageID); - this.villageName = villageName.intern(); - - this.isValid = this.parentLand.getLandDef() - .isValidVillageId(this.villageIDNum); - - return; - } - - - /** - * 村同士を比較するためのComparatorを返す。 - * @return Comparatorインスタンス - */ - public static Comparator comparator(){ - return VILLAGE_COMPARATOR; - } - - /** - * 人狼BBSサーバからPeriod一覧情報が含まれたHTMLを取得し、 - * Periodリストを更新する。 - * @param village 村 - * @throws java.io.IOException ネットワーク入出力の異常 - */ - public static synchronized void updateVillage(Village village) - throws IOException{ - Land land = village.getParentLand(); - LandDef landDef = land.getLandDef(); - LandState landState = landDef.getLandState(); - ServerAccess server = land.getServerAccess(); - - HtmlSequence html; - if(landState == LandState.ACTIVE){ - html = server.getHTMLBoneHead(village); - }else{ - html = server.getHTMLVillage(village); - } - - DecodedContent content = html.getContent(); - HANDLER.setVillage(village); - try{ - PARSER.parseAutomatic(content); - }catch(HtmlParseException e){ - Jindolf.logger().warn("村の状態が不明", e); - } - - return; - } - - /** - * 所属する国を返す。 - * @return 村の所属する国(Land) - */ - public Land getParentLand(){ - return this.parentLand; - } - - /** - * 村のID文字列を返す。 - * @return 村ID - */ - public String getVillageID(){ - return this.villageID; - } - - /** - * 村のID数値を返す。 - * @return 村ID - */ - public int getVillageIDNum(){ - return this.villageIDNum; - } - - /** - * 村の名前を返す。 - * @return 村の名前 - */ - public String getVillageName(){ - return this.parentLand.getLandDef().getLandPrefix() + getVillageID(); - } - - /** - * 村の長い名前を返す。 - * @return 村の長い名前 - */ - public String getVillageFullName(){ - return this.villageName; - } - - /** - * 村の状態を返す。 - * @return 村の状態 - */ - public VillageState getState(){ - return this.state; - } - - /** - * 村の状態を設定する。 - * @param state 村の状態 - */ - public void setState(VillageState state){ - this.state = state; - return; - } - - /** - * プロローグを返す。 - * @return プロローグ - */ - public Period getPrologue(){ - for(Period period : this.periodList){ - if(period.isPrologue()) return period; - } - return null; - } - - /** - * エピローグを返す。 - * @return エピローグ - */ - public Period getEpilogue(){ - for(Period period : this.periodList){ - if(period.isEpilogue()) return period; - } - return null; - } - - /** - * 指定された日付の進行日を返す。 - * @param day 日付 - * @return Period - */ - public Period getProgress(int day){ - for(Period period : this.periodList){ - if( period.isProgress() - && period.getDay() == day ) return period; - } - return null; - } - - /** - * PROGRESS状態のPeriodの総数を返す。 - * @return PROGRESS状態のPeriod総数 - */ - public int getProgressDays(){ - int result = 0; - for(Period period : this.periodList){ - if(period.isProgress()) result++; - } - return result; - } - - /** - * 指定されたPeriodインデックスのPeriodを返す。 - * プロローグやエピローグへのアクセスも可能。 - * @param day Periodインデックス - * @return Period - */ - public Period getPeriod(int day){ - return this.periodList.get(day); - } - - /** - * 指定されたアンカーの対象のPeriodを返す。 - * @param anchor アンカー - * @return Period - */ - public Period getPeriod(Anchor anchor){ - Period anchorPeriod; - - if(anchor.isEpilogueDay()){ - anchorPeriod = getEpilogue(); - return anchorPeriod; - } - - int anchorDay = anchor.getDay(); - anchorPeriod = getPeriod(anchorDay); - - return anchorPeriod; - } - - /** - * Period総数を返す。 - * @return Period総数 - */ - public int getPeriodSize(){ - return this.periodList.size(); - } - - /** - * Periodへのリストを返す。 - * @return Periodのリスト。 - */ - public List getPeriodList(){ - return this.unmodList; - } - - /** - * 指定した名前で村に登録されているAvatarを返す。 - * @param fullName Avatarの名前 - * @return Avatar - */ - public Avatar getAvatar(String fullName){ - // TODO CharSequenceにできない? - Avatar avatar; - - avatar = Avatar.getPredefinedAvatar(fullName); - if( avatar != null ){ - preloadAvatarFace(avatar); - return avatar; - } - - avatar = this.avatarMap.get(fullName); - if( avatar != null ){ - preloadAvatarFace(avatar); - return avatar; - } - - return null; - } - - /** - * Avatarの顔画像を事前にロードする。 - * @param avatar Avatar - */ - private void preloadAvatarFace(Avatar avatar){ - if(this.faceImageMap.get(avatar) != null) return; - - Land land = getParentLand(); - LandDef landDef = land.getLandDef(); - - String template = landDef.getFaceURITemplate(); - int serialNo = avatar.getIdNum(); - String uri = MessageFormat.format(template, serialNo); - - BufferedImage image = land.downloadImage(uri); - if(image == null) image = GUIUtils.getNoImage(); - - this.faceImageMap.put(avatar, image); - - return; - } - - /** - * Avatarを村に登録する。 - * @param avatar Avatar - */ - // 未知のAvatar出現時の処理が不完全 - public void addAvatar(Avatar avatar){ - if(avatar == null) return; - String fullName = avatar.getFullName(); - this.avatarMap.put(fullName, avatar); - - preloadAvatarFace(avatar); - - return; - } - - /** - * 村に登録されたAvatarの顔イメージを返す。 - * @param avatar Avatar - * @return 顔イメージ - */ - // TODO 失敗したらプロローグを強制読み込みして再トライしたい - public BufferedImage getAvatarFaceImage(Avatar avatar){ - return this.faceImageMap.get(avatar); - } - - /** - * 村に登録されたAvatarの全身像イメージを返す。 - * @param avatar Avatar - * @return 全身イメージ - */ - public BufferedImage getAvatarBodyImage(Avatar avatar){ - BufferedImage result; - result = this.bodyImageMap.get(avatar); - if(result != null) return result; - - Land land = getParentLand(); - LandDef landDef = land.getLandDef(); - - String template = landDef.getBodyURITemplate(); - int serialNo = avatar.getIdNum(); - String uri = MessageFormat.format(template, serialNo); - - result = land.downloadImage(uri); - if(result == null) result = GUIUtils.getNoImage(); - - this.bodyImageMap.put(avatar, result); - - return result; - } - - /** - * 村に登録されたAvatarのモノクロ顔イメージを返す。 - * @param avatar Avatar - * @return 顔イメージ - */ - public BufferedImage getAvatarFaceMonoImage(Avatar avatar){ - BufferedImage result; - result = this.faceMonoImageMap.get(avatar); - if(result == null){ - result = getAvatarFaceImage(avatar); - result = GUIUtils.createMonoImage(result); - this.faceMonoImageMap.put(avatar, result); - } - return result; - } - - /** - * 村に登録されたAvatarの全身像イメージを返す。 - * @param avatar Avatar - * @return 全身イメージ - */ - public BufferedImage getAvatarBodyMonoImage(Avatar avatar){ - BufferedImage result; - result = this.bodyMonoImageMap.get(avatar); - if(result == null){ - result = getAvatarBodyImage(avatar); - result = GUIUtils.createMonoImage(result); - this.bodyMonoImageMap.put(avatar, result); - } - return result; - } - - /** - * 国に登録された墓イメージを返す。 - * @return 墓イメージ - */ - public BufferedImage getGraveImage(){ - BufferedImage result = getParentLand().getGraveIconImage(); - return result; - } - - /** - * 国に登録された墓イメージ(大)を返す。 - * @return 墓イメージ(大) - */ - public BufferedImage getGraveBodyImage(){ - BufferedImage result = getParentLand().getGraveBodyImage(); - return result; - } - - /** - * 村にアクセスするためのCGIクエリーを返す。 - * @return CGIクエリー - */ - public CharSequence getCGIQuery(){ - StringBuilder result = new StringBuilder(); - result.append("?vid=").append(getVillageID()); - return result; - } - - /** - * 次回更新月を返す。 - * @return 更新月(1-12) - */ - public int getLimitMonth(){ - return this.limitMonth; - } - - /** - * 次回更新日を返す。 - * @return 更新日(1-31) - */ - public int getLimitDay(){ - return this.limitDay; - } - - /** - * 次回更新時を返す。 - * @return 更新時(0-23) - */ - public int getLimitHour(){ - return this.limitHour; - } - - /** - * 次回更新分を返す。 - * @return 更新分(0-59) - */ - public int getLimitMinute(){ - return this.limitMinute; - } - - /** - * 有効な村か否か判定する。 - * @return 無効な村ならfalse - */ - public boolean isValid(){ - return this.isValid; - } - - /** - * Periodリストの指定したインデックスにPeriodを上書きする。 - * リストのサイズと同じインデックスを指定する事が許される。 - * その場合の動作はList.addと同じ。 - * @param index Periodリストのインデックス。 - * @param period 上書きするPeriod - * @throws java.lang.IndexOutOfBoundsException インデックスの指定がおかしい - */ - private void setPeriod(int index, Period period) - throws IndexOutOfBoundsException{ - int listSize = this.periodList.size(); - if(index == listSize){ - this.periodList.add(period); - }else if(index < listSize){ - this.periodList.set(index, period); - }else{ - throw new IndexOutOfBoundsException(); - } - return; - } - - /** - * アンカーに一致する会話(Talk)のリストを取得する。 - * @param anchor アンカー - * @return Talkのリスト - * @throws java.io.IOException おそらくネットワークエラー - */ - public List getTalkListFromAnchor(Anchor anchor) - throws IOException{ - List result = new LinkedList(); - - /* G国アンカー対応 */ - if(anchor.hasTalkNo()){ - // 事前に全Periodがロードされているのが前提 - for(Period period : this.periodList){ - Talk talk = period.getNumberedTalk(anchor.getTalkNo()); - if(talk == null) continue; - result.add(talk); - } - return result; - } - - Period anchorPeriod = getPeriod(anchor); - if(anchorPeriod == null) return result; - - Period.parsePeriod(anchorPeriod, false); - - for(Topic topic : anchorPeriod.getTopicList()){ - if( ! (topic instanceof Talk) ) continue; - Talk talk = (Talk) topic; - if(talk.getHour() != anchor.getHour() ) continue; - if(talk.getMinute() != anchor.getMinute()) continue; - result.add(talk); - } - return result; - } - - /** - * 全Periodの発言データをアンロードする。 - */ - public void unloadPeriods(){ - for(Period period : this.periodList){ - period.unload(); - } - return; - } - - /** - * {@inheritDoc} - * 二つの村を順序付ける。 - * @param village {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(Village village){ - int cmpResult = VILLAGE_COMPARATOR.compare(this, village); - return cmpResult; - } - - /** - * {@inheritDoc} - * 二つの村が等しいか調べる。 - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if( ! (obj instanceof Village) ) return false; - Village village = (Village) obj; - - if( getParentLand() != village.getParentLand() ) return false; - - int cmpResult = compareTo(village); - if(cmpResult == 0) return true; - return false; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - int homeHash = getParentLand().hashCode(); - int vidHash = getVillageID().hashCode(); - int result = homeHash ^ vidHash; - return result; - } - - /** - * {@inheritDoc} - * 村の文字列表現を返す。 - * 村の名前と等しい。 - * @return 村の名前 - */ - @Override - public String toString(){ - return getVillageFullName(); - } - - /** - * Period一覧取得用ハンドラ。 - */ - private static class VillageHeadHandler extends HtmlAdapter{ - - private Village village = null; - - private boolean hasPrologue; - private boolean hasProgress; - private boolean hasEpilogue; - private boolean hasDone; - private int maxProgress; - - /** - * コンストラクタ。 - */ - public VillageHeadHandler(){ - super(); - return; - } - - /** - * 更新対象の村を設定する。 - * @param village 村 - */ - public void setVillage(Village village){ - this.village = village; - return; - } - - /** - * リセットを行う。 - */ - public void reset(){ - this.hasPrologue = false; - this.hasProgress = false; - this.hasEpilogue = false; - this.hasDone = false; - this.maxProgress = 0; - return; - } - - /** - * パース結果から村の状態を算出する。 - * @return 村の状態 - */ - public VillageState getVillageState(){ - if(this.hasDone){ - return VillageState.GAMEOVER; - }else if(this.hasEpilogue){ - return VillageState.EPILOGUE; - }else if(this.hasProgress){ - return VillageState.PROGRESS; - }else if(this.hasPrologue){ - return VillageState.PROLOGUE; - } - - return VillageState.UNKNOWN; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void startParse(DecodedContent content) - throws HtmlParseException{ - reset(); - return; - } - - /** - * {@inheritDoc} - * 自動判定の結果が日ページでなければ例外を投げる。 - * @param type {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。 - */ - @Override - public void pageType(PageType type) throws HtmlParseException{ - if(type != PageType.PERIOD_PAGE){ - throw new HtmlParseException( - "日ページが必要です。"); - } - return; - } - - /** - * {@inheritDoc} - * @param month {@inheritDoc} - * @param day {@inheritDoc} - * @param hour {@inheritDoc} - * @param minute {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void commitTime(int month, int day, - int hour, int minute) - throws HtmlParseException{ - this.village.limitMonth = month; - this.village.limitDay = day; - this.village.limitHour = hour; - this.village.limitMinute = minute; - - return; - } - - /** - * {@inheritDoc} - * @param content {@inheritDoc} - * @param anchorRange {@inheritDoc} - * @param periodType {@inheritDoc} - * @param day {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void periodLink(DecodedContent content, - SeqRange anchorRange, - PeriodType periodType, - int day) - throws HtmlParseException{ - if(periodType == null){ - this.hasDone = true; - return; - } - - switch(periodType){ - case PROLOGUE: - this.hasPrologue = true; - break; - case PROGRESS: - this.hasProgress = true; - this.maxProgress = day; - break; - case EPILOGUE: - this.hasEpilogue = true; - break; - default: - assert false; - break; - } - - return; - } - - /** - * {@inheritDoc} - * @throws HtmlParseException {@inheritDoc} - */ - @Override - public void endParse() throws HtmlParseException{ - Land land = this.village.getParentLand(); - LandDef landDef = land.getLandDef(); - LandState landState = landDef.getLandState(); - - VillageState villageState = getVillageState(); - if(villageState == VillageState.UNKNOWN){ - this.village.setState(villageState); - this.village.periodList.clear(); - Jindolf.logger().warn("村の状況を読み取れません"); - return; - } - - if(landState == LandState.ACTIVE){ - this.village.setState(villageState); - }else{ - this.village.setState(VillageState.GAMEOVER); - } - - modifyPeriodList(); - - return; - } - - /** - * 抽出したリンク情報に伴いPeriodリストを更新する。 - * まだPeriodデータのロードは行われない。 - * ゲーム進行中の村で更新時刻をまたいで更新が行われた場合、 - * 既存のPeriodリストが伸張する場合がある。 - */ - private void modifyPeriodList(){ - Period lastPeriod = null; - - if(this.hasPrologue){ - Period prologue = this.village.getPrologue(); - if(prologue == null){ - lastPeriod = new Period(this.village, - PeriodType.PROLOGUE, 0); - this.village.setPeriod(0, lastPeriod); - }else{ - lastPeriod = prologue; - } - } - - if(this.hasProgress){ - for(int day = 1; day <= this.maxProgress; day++){ - Period progress = this.village.getProgress(day); - if(progress == null){ - lastPeriod = new Period(this.village, - PeriodType.PROGRESS, day); - this.village.setPeriod(day, lastPeriod); - }else{ - lastPeriod = progress; - } - } - } - - if(this.hasEpilogue){ - Period epilogue = this.village.getEpilogue(); - if(epilogue == null){ - lastPeriod = new Period(this.village, - PeriodType.EPILOGUE, - this.maxProgress +1); - this.village.setPeriod(this.maxProgress +1, lastPeriod); - }else{ - lastPeriod = epilogue; - } - } - - assert this.village.getPeriodSize() > 0; - assert lastPeriod != null; - - // 念のためチョップ。 - // リロードで村が縮むわけないじゃん。みんな大げさだなあ - while(this.village.periodList.getLast() != lastPeriod){ - this.village.periodList.removeLast(); - } - - if(this.village.getState() != VillageState.GAMEOVER){ - lastPeriod.setHot(true); - } - - return; - } - - } - -} +/* + * Village + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import jp.sourceforge.jindolf.corelib.LandDef; +import jp.sourceforge.jindolf.corelib.LandState; +import jp.sourceforge.jindolf.corelib.PeriodType; +import jp.sourceforge.jindolf.corelib.VillageState; +import jp.sourceforge.jindolf.parser.DecodedContent; +import jp.sourceforge.jindolf.parser.HtmlAdapter; +import jp.sourceforge.jindolf.parser.HtmlParseException; +import jp.sourceforge.jindolf.parser.HtmlParser; +import jp.sourceforge.jindolf.parser.PageType; +import jp.sourceforge.jindolf.parser.SeqRange; + +/** + * いわゆる「村」。 + */ +public class Village implements Comparable { + + /** + * 村同士を比較するためのComparator。 + */ + private static class VillageComparator implements Comparator { + + /** + * コンストラクタ。 + */ + public VillageComparator(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param v1 {@inheritDoc} + * @param v2 {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compare(Village v1, Village v2){ + int v1Num; + if(v1 == null) v1Num = Integer.MIN_VALUE; + else v1Num = v1.getVillageIDNum(); + + int v2Num; + if(v2 == null) v2Num = Integer.MIN_VALUE; + else v2Num = v2.getVillageIDNum(); + + return v1Num - v2Num; + } + + } + + private static final Comparator VILLAGE_COMPARATOR = + new VillageComparator(); + + private static final HtmlParser PARSER = new HtmlParser(); + private static final VillageHeadHandler HANDLER = + new VillageHeadHandler(); + + static{ + PARSER.setBasicHandler (HANDLER); + PARSER.setSysEventHandler(HANDLER); + PARSER.setTalkHandler (HANDLER); + } + + + private final Land parentLand; + private final String villageID; + private final int villageIDNum; + private final String villageName; + + private final boolean isValid; + + private int limitMonth; + private int limitDay; + private int limitHour; + private int limitMinute; + + private VillageState state = VillageState.UNKNOWN; + + private final LinkedList periodList = new LinkedList(); + private final List unmodList = + Collections.unmodifiableList(this.periodList); + + private final Map avatarMap = + new HashMap(); + + private final Map faceImageMap = + new HashMap(); + private final Map bodyImageMap = + new HashMap(); + private final Map faceMonoImageMap = + new HashMap(); + private final Map bodyMonoImageMap = + new HashMap(); + + + /** + * Villageを生成する。 + * @param parentLand Villageの所属する国 + * @param villageID 村のID + * @param villageName 村の名前 + */ + public Village(Land parentLand, String villageID, String villageName) { + this.parentLand = parentLand; + this.villageID = villageID.intern(); + this.villageIDNum = Integer.parseInt(this.villageID); + this.villageName = villageName.intern(); + + this.isValid = this.parentLand.getLandDef() + .isValidVillageId(this.villageIDNum); + + return; + } + + + /** + * 村同士を比較するためのComparatorを返す。 + * @return Comparatorインスタンス + */ + public static Comparator comparator(){ + return VILLAGE_COMPARATOR; + } + + /** + * 人狼BBSサーバからPeriod一覧情報が含まれたHTMLを取得し、 + * Periodリストを更新する。 + * @param village 村 + * @throws java.io.IOException ネットワーク入出力の異常 + */ + public static synchronized void updateVillage(Village village) + throws IOException{ + Land land = village.getParentLand(); + LandDef landDef = land.getLandDef(); + LandState landState = landDef.getLandState(); + ServerAccess server = land.getServerAccess(); + + HtmlSequence html; + if(landState == LandState.ACTIVE){ + html = server.getHTMLBoneHead(village); + }else{ + html = server.getHTMLVillage(village); + } + + DecodedContent content = html.getContent(); + HANDLER.setVillage(village); + try{ + PARSER.parseAutomatic(content); + }catch(HtmlParseException e){ + Jindolf.logger().warn("村の状態が不明", e); + } + + return; + } + + /** + * 所属する国を返す。 + * @return 村の所属する国(Land) + */ + public Land getParentLand(){ + return this.parentLand; + } + + /** + * 村のID文字列を返す。 + * @return 村ID + */ + public String getVillageID(){ + return this.villageID; + } + + /** + * 村のID数値を返す。 + * @return 村ID + */ + public int getVillageIDNum(){ + return this.villageIDNum; + } + + /** + * 村の名前を返す。 + * @return 村の名前 + */ + public String getVillageName(){ + return this.parentLand.getLandDef().getLandPrefix() + getVillageID(); + } + + /** + * 村の長い名前を返す。 + * @return 村の長い名前 + */ + public String getVillageFullName(){ + return this.villageName; + } + + /** + * 村の状態を返す。 + * @return 村の状態 + */ + public VillageState getState(){ + return this.state; + } + + /** + * 村の状態を設定する。 + * @param state 村の状態 + */ + public void setState(VillageState state){ + this.state = state; + return; + } + + /** + * プロローグを返す。 + * @return プロローグ + */ + public Period getPrologue(){ + for(Period period : this.periodList){ + if(period.isPrologue()) return period; + } + return null; + } + + /** + * エピローグを返す。 + * @return エピローグ + */ + public Period getEpilogue(){ + for(Period period : this.periodList){ + if(period.isEpilogue()) return period; + } + return null; + } + + /** + * 指定された日付の進行日を返す。 + * @param day 日付 + * @return Period + */ + public Period getProgress(int day){ + for(Period period : this.periodList){ + if( period.isProgress() + && period.getDay() == day ) return period; + } + return null; + } + + /** + * PROGRESS状態のPeriodの総数を返す。 + * @return PROGRESS状態のPeriod総数 + */ + public int getProgressDays(){ + int result = 0; + for(Period period : this.periodList){ + if(period.isProgress()) result++; + } + return result; + } + + /** + * 指定されたPeriodインデックスのPeriodを返す。 + * プロローグやエピローグへのアクセスも可能。 + * @param day Periodインデックス + * @return Period + */ + public Period getPeriod(int day){ + return this.periodList.get(day); + } + + /** + * 指定されたアンカーの対象のPeriodを返す。 + * @param anchor アンカー + * @return Period + */ + public Period getPeriod(Anchor anchor){ + Period anchorPeriod; + + if(anchor.isEpilogueDay()){ + anchorPeriod = getEpilogue(); + return anchorPeriod; + } + + int anchorDay = anchor.getDay(); + anchorPeriod = getPeriod(anchorDay); + + return anchorPeriod; + } + + /** + * Period総数を返す。 + * @return Period総数 + */ + public int getPeriodSize(){ + return this.periodList.size(); + } + + /** + * Periodへのリストを返す。 + * @return Periodのリスト。 + */ + public List getPeriodList(){ + return this.unmodList; + } + + /** + * 指定した名前で村に登録されているAvatarを返す。 + * @param fullName Avatarの名前 + * @return Avatar + */ + public Avatar getAvatar(String fullName){ + // TODO CharSequenceにできない? + Avatar avatar; + + avatar = Avatar.getPredefinedAvatar(fullName); + if( avatar != null ){ + preloadAvatarFace(avatar); + return avatar; + } + + avatar = this.avatarMap.get(fullName); + if( avatar != null ){ + preloadAvatarFace(avatar); + return avatar; + } + + return null; + } + + /** + * Avatarの顔画像を事前にロードする。 + * @param avatar Avatar + */ + private void preloadAvatarFace(Avatar avatar){ + if(this.faceImageMap.get(avatar) != null) return; + + Land land = getParentLand(); + LandDef landDef = land.getLandDef(); + + String template = landDef.getFaceURITemplate(); + int serialNo = avatar.getIdNum(); + String uri = MessageFormat.format(template, serialNo); + + BufferedImage image = land.downloadImage(uri); + if(image == null) image = GUIUtils.getNoImage(); + + this.faceImageMap.put(avatar, image); + + return; + } + + /** + * Avatarを村に登録する。 + * @param avatar Avatar + */ + // 未知のAvatar出現時の処理が不完全 + public void addAvatar(Avatar avatar){ + if(avatar == null) return; + String fullName = avatar.getFullName(); + this.avatarMap.put(fullName, avatar); + + preloadAvatarFace(avatar); + + return; + } + + /** + * 村に登録されたAvatarの顔イメージを返す。 + * @param avatar Avatar + * @return 顔イメージ + */ + // TODO 失敗したらプロローグを強制読み込みして再トライしたい + public BufferedImage getAvatarFaceImage(Avatar avatar){ + return this.faceImageMap.get(avatar); + } + + /** + * 村に登録されたAvatarの全身像イメージを返す。 + * @param avatar Avatar + * @return 全身イメージ + */ + public BufferedImage getAvatarBodyImage(Avatar avatar){ + BufferedImage result; + result = this.bodyImageMap.get(avatar); + if(result != null) return result; + + Land land = getParentLand(); + LandDef landDef = land.getLandDef(); + + String template = landDef.getBodyURITemplate(); + int serialNo = avatar.getIdNum(); + String uri = MessageFormat.format(template, serialNo); + + result = land.downloadImage(uri); + if(result == null) result = GUIUtils.getNoImage(); + + this.bodyImageMap.put(avatar, result); + + return result; + } + + /** + * 村に登録されたAvatarのモノクロ顔イメージを返す。 + * @param avatar Avatar + * @return 顔イメージ + */ + public BufferedImage getAvatarFaceMonoImage(Avatar avatar){ + BufferedImage result; + result = this.faceMonoImageMap.get(avatar); + if(result == null){ + result = getAvatarFaceImage(avatar); + result = GUIUtils.createMonoImage(result); + this.faceMonoImageMap.put(avatar, result); + } + return result; + } + + /** + * 村に登録されたAvatarの全身像イメージを返す。 + * @param avatar Avatar + * @return 全身イメージ + */ + public BufferedImage getAvatarBodyMonoImage(Avatar avatar){ + BufferedImage result; + result = this.bodyMonoImageMap.get(avatar); + if(result == null){ + result = getAvatarBodyImage(avatar); + result = GUIUtils.createMonoImage(result); + this.bodyMonoImageMap.put(avatar, result); + } + return result; + } + + /** + * 国に登録された墓イメージを返す。 + * @return 墓イメージ + */ + public BufferedImage getGraveImage(){ + BufferedImage result = getParentLand().getGraveIconImage(); + return result; + } + + /** + * 国に登録された墓イメージ(大)を返す。 + * @return 墓イメージ(大) + */ + public BufferedImage getGraveBodyImage(){ + BufferedImage result = getParentLand().getGraveBodyImage(); + return result; + } + + /** + * 村にアクセスするためのCGIクエリーを返す。 + * @return CGIクエリー + */ + public CharSequence getCGIQuery(){ + StringBuilder result = new StringBuilder(); + result.append("?vid=").append(getVillageID()); + return result; + } + + /** + * 次回更新月を返す。 + * @return 更新月(1-12) + */ + public int getLimitMonth(){ + return this.limitMonth; + } + + /** + * 次回更新日を返す。 + * @return 更新日(1-31) + */ + public int getLimitDay(){ + return this.limitDay; + } + + /** + * 次回更新時を返す。 + * @return 更新時(0-23) + */ + public int getLimitHour(){ + return this.limitHour; + } + + /** + * 次回更新分を返す。 + * @return 更新分(0-59) + */ + public int getLimitMinute(){ + return this.limitMinute; + } + + /** + * 有効な村か否か判定する。 + * @return 無効な村ならfalse + */ + public boolean isValid(){ + return this.isValid; + } + + /** + * Periodリストの指定したインデックスにPeriodを上書きする。 + * リストのサイズと同じインデックスを指定する事が許される。 + * その場合の動作はList.addと同じ。 + * @param index Periodリストのインデックス。 + * @param period 上書きするPeriod + * @throws java.lang.IndexOutOfBoundsException インデックスの指定がおかしい + */ + private void setPeriod(int index, Period period) + throws IndexOutOfBoundsException{ + int listSize = this.periodList.size(); + if(index == listSize){ + this.periodList.add(period); + }else if(index < listSize){ + this.periodList.set(index, period); + }else{ + throw new IndexOutOfBoundsException(); + } + return; + } + + /** + * アンカーに一致する会話(Talk)のリストを取得する。 + * @param anchor アンカー + * @return Talkのリスト + * @throws java.io.IOException おそらくネットワークエラー + */ + public List getTalkListFromAnchor(Anchor anchor) + throws IOException{ + List result = new LinkedList(); + + /* G国アンカー対応 */ + if(anchor.hasTalkNo()){ + // 事前に全Periodがロードされているのが前提 + for(Period period : this.periodList){ + Talk talk = period.getNumberedTalk(anchor.getTalkNo()); + if(talk == null) continue; + result.add(talk); + } + return result; + } + + Period anchorPeriod = getPeriod(anchor); + if(anchorPeriod == null) return result; + + Period.parsePeriod(anchorPeriod, false); + + for(Topic topic : anchorPeriod.getTopicList()){ + if( ! (topic instanceof Talk) ) continue; + Talk talk = (Talk) topic; + if(talk.getHour() != anchor.getHour() ) continue; + if(talk.getMinute() != anchor.getMinute()) continue; + result.add(talk); + } + return result; + } + + /** + * 全Periodの発言データをアンロードする。 + */ + public void unloadPeriods(){ + for(Period period : this.periodList){ + period.unload(); + } + return; + } + + /** + * {@inheritDoc} + * 二つの村を順序付ける。 + * @param village {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(Village village){ + int cmpResult = VILLAGE_COMPARATOR.compare(this, village); + return cmpResult; + } + + /** + * {@inheritDoc} + * 二つの村が等しいか調べる。 + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if( ! (obj instanceof Village) ) return false; + Village village = (Village) obj; + + if( getParentLand() != village.getParentLand() ) return false; + + int cmpResult = compareTo(village); + if(cmpResult == 0) return true; + return false; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + int homeHash = getParentLand().hashCode(); + int vidHash = getVillageID().hashCode(); + int result = homeHash ^ vidHash; + return result; + } + + /** + * {@inheritDoc} + * 村の文字列表現を返す。 + * 村の名前と等しい。 + * @return 村の名前 + */ + @Override + public String toString(){ + return getVillageFullName(); + } + + /** + * Period一覧取得用ハンドラ。 + */ + private static class VillageHeadHandler extends HtmlAdapter{ + + private Village village = null; + + private boolean hasPrologue; + private boolean hasProgress; + private boolean hasEpilogue; + private boolean hasDone; + private int maxProgress; + + /** + * コンストラクタ。 + */ + public VillageHeadHandler(){ + super(); + return; + } + + /** + * 更新対象の村を設定する。 + * @param village 村 + */ + public void setVillage(Village village){ + this.village = village; + return; + } + + /** + * リセットを行う。 + */ + public void reset(){ + this.hasPrologue = false; + this.hasProgress = false; + this.hasEpilogue = false; + this.hasDone = false; + this.maxProgress = 0; + return; + } + + /** + * パース結果から村の状態を算出する。 + * @return 村の状態 + */ + public VillageState getVillageState(){ + if(this.hasDone){ + return VillageState.GAMEOVER; + }else if(this.hasEpilogue){ + return VillageState.EPILOGUE; + }else if(this.hasProgress){ + return VillageState.PROGRESS; + }else if(this.hasPrologue){ + return VillageState.PROLOGUE; + } + + return VillageState.UNKNOWN; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void startParse(DecodedContent content) + throws HtmlParseException{ + reset(); + return; + } + + /** + * {@inheritDoc} + * 自動判定の結果が日ページでなければ例外を投げる。 + * @param type {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。 + */ + @Override + public void pageType(PageType type) throws HtmlParseException{ + if(type != PageType.PERIOD_PAGE){ + throw new HtmlParseException( + "日ページが必要です。"); + } + return; + } + + /** + * {@inheritDoc} + * @param month {@inheritDoc} + * @param day {@inheritDoc} + * @param hour {@inheritDoc} + * @param minute {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void commitTime(int month, int day, + int hour, int minute) + throws HtmlParseException{ + this.village.limitMonth = month; + this.village.limitDay = day; + this.village.limitHour = hour; + this.village.limitMinute = minute; + + return; + } + + /** + * {@inheritDoc} + * @param content {@inheritDoc} + * @param anchorRange {@inheritDoc} + * @param periodType {@inheritDoc} + * @param day {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void periodLink(DecodedContent content, + SeqRange anchorRange, + PeriodType periodType, + int day) + throws HtmlParseException{ + if(periodType == null){ + this.hasDone = true; + return; + } + + switch(periodType){ + case PROLOGUE: + this.hasPrologue = true; + break; + case PROGRESS: + this.hasProgress = true; + this.maxProgress = day; + break; + case EPILOGUE: + this.hasEpilogue = true; + break; + default: + assert false; + break; + } + + return; + } + + /** + * {@inheritDoc} + * @throws HtmlParseException {@inheritDoc} + */ + @Override + public void endParse() throws HtmlParseException{ + Land land = this.village.getParentLand(); + LandDef landDef = land.getLandDef(); + LandState landState = landDef.getLandState(); + + VillageState villageState = getVillageState(); + if(villageState == VillageState.UNKNOWN){ + this.village.setState(villageState); + this.village.periodList.clear(); + Jindolf.logger().warn("村の状況を読み取れません"); + return; + } + + if(landState == LandState.ACTIVE){ + this.village.setState(villageState); + }else{ + this.village.setState(VillageState.GAMEOVER); + } + + modifyPeriodList(); + + return; + } + + /** + * 抽出したリンク情報に伴いPeriodリストを更新する。 + * まだPeriodデータのロードは行われない。 + * ゲーム進行中の村で更新時刻をまたいで更新が行われた場合、 + * 既存のPeriodリストが伸張する場合がある。 + */ + private void modifyPeriodList(){ + Period lastPeriod = null; + + if(this.hasPrologue){ + Period prologue = this.village.getPrologue(); + if(prologue == null){ + lastPeriod = new Period(this.village, + PeriodType.PROLOGUE, 0); + this.village.setPeriod(0, lastPeriod); + }else{ + lastPeriod = prologue; + } + } + + if(this.hasProgress){ + for(int day = 1; day <= this.maxProgress; day++){ + Period progress = this.village.getProgress(day); + if(progress == null){ + lastPeriod = new Period(this.village, + PeriodType.PROGRESS, day); + this.village.setPeriod(day, lastPeriod); + }else{ + lastPeriod = progress; + } + } + } + + if(this.hasEpilogue){ + Period epilogue = this.village.getEpilogue(); + if(epilogue == null){ + lastPeriod = new Period(this.village, + PeriodType.EPILOGUE, + this.maxProgress +1); + this.village.setPeriod(this.maxProgress +1, lastPeriod); + }else{ + lastPeriod = epilogue; + } + } + + assert this.village.getPeriodSize() > 0; + assert lastPeriod != null; + + // 念のためチョップ。 + // リロードで村が縮むわけないじゃん。みんな大げさだなあ + while(this.village.periodList.getLast() != lastPeriod){ + this.village.periodList.removeLast(); + } + + if(this.village.getState() != VillageState.GAMEOVER){ + lastPeriod.setHot(true); + } + + return; + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/VillageDigest.java b/src/main/java/jp/sourceforge/jindolf/VillageDigest.java index 71f50cc..1c6c17e 100644 --- a/src/main/java/jp/sourceforge/jindolf/VillageDigest.java +++ b/src/main/java/jp/sourceforge/jindolf/VillageDigest.java @@ -1,838 +1,838 @@ -/* - * Village digest GUI - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Image; -import java.awt.Insets; -import java.awt.LayoutManager; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.text.DateFormat; -import java.util.Date; -import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.DefaultComboBoxModel; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.JTextArea; -import javax.swing.JViewport; -import javax.swing.border.Border; -import jp.sourceforge.jindolf.corelib.GameRole; -import jp.sourceforge.jindolf.corelib.Team; - -/** - * 決着のついた村のサマリを表示する。 - */ -@SuppressWarnings("serial") -public class VillageDigest - extends JDialog - implements ActionListener, - ItemListener { - - private static final String FRAMETITLE = - "村のダイジェスト - " + Jindolf.TITLE; - private static final String ITEMDELIM = " : "; - - - private final JComponent summaryPanel = buildSummaryPanel(); - - private final JLabel faceLabel = new JLabel(); - private final ImageIcon faceIcon = new ImageIcon(); - private final JComboBox playerBox = new JComboBox(); - private final DefaultComboBoxModel playerListModel = - new DefaultComboBoxModel(); - private final JButton prevPlayer = new JButton("↑"); - private final JButton nextPlayer = new JButton("↓"); - private final JLabel roleLabel = new JLabel(); - private final JLabel destinyLabel = new JLabel(); - private final JLabel specialSkillLabel = new JLabel(); - private final JLabel entryLabel = new JLabel(); - private final JLabel idLabel = new JLabel(); - private final WebButton urlLine = new WebButton(); - private final JComponent playerPanel = buildPlayerPanel(); - - private final JComboBox iconSetBox = new JComboBox(); - private final DefaultComboBoxModel iconSetListModel = - new DefaultComboBoxModel(); - private final JLabel authorLabel = new JLabel(); - private final JLabel authorUrlLabel = new JLabel(); - private final WebButton iconCatalog = new WebButton(); - private final JButton genCastTableButton = - new JButton("キャスト表Wiki生成"); - private final JButton copyClipButton = - new JButton("クリップボードにコピー"); - private final JTextArea templateArea = new JTextArea(); - private final JButton voteButton = new JButton("投票Wiki生成"); - private final JButton vlgWikiButton = new JButton("村詳細Wiki生成"); - private final JComponent clipboardPanel = buildClipboardPanel(); - - private final JButton closeButton = new JButton("閉じる"); - - private Village village; - - private GameSummary gameSummary; - - - /** - * コンストラクタ。 - * @param owner 親フレーム - */ - public VillageDigest(Frame owner){ - super(owner, FRAMETITLE, true); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter(){ - @Override - public void windowClosing(WindowEvent event){ - actionClose(); - return; - } - }); - - this.faceLabel.setIcon(this.faceIcon); - - this.playerBox.setModel(this.playerListModel); - this.playerBox.addItemListener(this); - - this.prevPlayer.setMargin(new Insets(1, 1, 1, 1)); - this.prevPlayer.addActionListener(this); - this.prevPlayer.setToolTipText("前のプレイヤー"); - - this.nextPlayer.setMargin(new Insets(1, 1, 1, 1)); - this.nextPlayer.addActionListener(this); - this.nextPlayer.setToolTipText("次のプレイヤー"); - - this.iconSetBox.setModel(this.iconSetListModel); - this.iconSetBox.addItemListener(this); - for(FaceIconSet iconSet : WolfBBS.getFaceIconSetList()){ - this.iconSetListModel.addElement(iconSet); - } - - this.iconCatalog.setURLText( - "http://wolfbbs.jp/" - +"%A4%DE%A4%C8%A4%E1%A5%B5%A5%A4%A5%C8%A4%C7" - +"%CD%F8%CD%D1%B2%C4%C7%BD%A4%CA%A5%A2%A5%A4" - +"%A5%B3%A5%F3%B2%E8%C1%FC.html"); - this.iconCatalog.setCaption("顔アイコン見本ページ"); - - this.templateArea.setEditable(true); - this.templateArea.setLineWrap(true); - Monodizer.monodize(this.templateArea); - JPopupMenu popup = new TextPopup(); - this.templateArea.setComponentPopupMenu(popup); - - this.genCastTableButton.addActionListener(this); - this.voteButton.addActionListener(this); - this.vlgWikiButton.addActionListener(this); - this.copyClipButton.addActionListener(this); - - this.closeButton.addActionListener(this); - - Monodizer.monodize(this.idLabel); - Monodizer.monodize(this.authorUrlLabel); - - Container content = getContentPane(); - design(content); - - return; - } - - - /** - * キャプション付き項目をコンテナに追加。 - * @param container コンテナ - * @param caption 項目キャプション名 - * @param delimiter デリミタ文字 - * @param item 項目アイテム - */ - private static void addCaptionedItem(Container container, - CharSequence caption, - CharSequence delimiter, - Object item ){ - LayoutManager layout = container.getLayout(); - if( ! (layout instanceof GridBagLayout) ){ - throw new IllegalArgumentException(); - } - - JLabel captionLabel = new JLabel(caption.toString()); - JLabel delimiterLabel = new JLabel(delimiter.toString()); - JComponent itemComp; - if(item instanceof JComponent){ - itemComp = (JComponent) item; - }else{ - itemComp = new JLabel(item.toString()); - } - - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.gridwidth = 1; - constraints.anchor = GridBagConstraints.NORTHEAST; - container.add(captionLabel, constraints); - container.add(delimiterLabel, constraints); - - constraints.weightx = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.anchor = GridBagConstraints.NORTHWEST; - container.add(itemComp, constraints); - - return; - } - - /** - * キャプション付き項目をコンテナに追加。 - * @param container コンテナ - * @param caption 項目キャプション名 - * @param item 項目アイテム - */ - private static void addCaptionedItem(Container container, - CharSequence caption, - Object item ){ - addCaptionedItem(container, caption, ITEMDELIM, item); - return; - } - - /** - * レイアウトの最後に詰め物をする。 - * @param container コンテナ - */ - private static void addFatPad(Container container){ - LayoutManager layout = container.getLayout(); - if( ! (layout instanceof GridBagLayout) ){ - throw new IllegalArgumentException(); - } - - JComponent pad = new JPanel(); - - GridBagConstraints constraints = new GridBagConstraints(); - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.gridwidth = GridBagConstraints.REMAINDER; - container.add(pad, constraints); - - return; - } - - /** - * GridBagLayoutでレイアウトする空コンポーネントを生成する。 - * @return 空コンポーネント - */ - private static JComponent createGridBagComponent(){ - JComponent result = new JPanel(); - LayoutManager layout = new GridBagLayout(); - result.setLayout(layout); - return result; - } - - /** - * 村サマリ画面の生成。 - * @return 村サマリ画面 - */ - private JComponent buildSummaryPanel(){ - JComponent result = createGridBagComponent(); - - Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - result.setBorder(border); - - return result; - } - - /** - * プレイヤーサマリ画面の生成。 - * @return プレイヤーサマリ画面 - */ - private JComponent buildPlayerPanel(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.insets = new Insets(2, 2, 2, 2); - result.add(this.faceLabel, constraints); - - result.add(new JLabel(ITEMDELIM), constraints); - - constraints.anchor = GridBagConstraints.NORTHWEST; - result.add(this.playerBox, constraints); - result.add(this.prevPlayer, constraints); - constraints.gridwidth = GridBagConstraints.REMAINDER; - result.add(this.nextPlayer, constraints); - - addCaptionedItem(result, "役職", this.roleLabel); - addCaptionedItem(result, "運命", this.destinyLabel); - addCaptionedItem(result, "特殊技能", this.specialSkillLabel); - addCaptionedItem(result, "エントリ#", this.entryLabel); - addCaptionedItem(result, "ID", this.idLabel); - addCaptionedItem(result, "URL", this.urlLine); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.gridwidth = GridBagConstraints.REMAINDER; - result.add(new JPanel(), constraints); - - Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - result.setBorder(border); - - return result; - } - - /** - * キャスト表生成画面を生成する。 - * @return キャスト表生成画面 - */ - private JComponent buildCastPanel(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.insets = new Insets(2, 2, 2, 2); - result.add(this.iconCatalog, constraints); - - addCaptionedItem(result, "顔アイコンセットを選択", this.iconSetBox); - addCaptionedItem(result, "作者", this.authorLabel); - addCaptionedItem(result, "URL", this.authorUrlLabel); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.insets = new Insets(2, 2, 2, 2); - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - result.add(this.genCastTableButton, constraints); - - Border border = BorderFactory.createTitledBorder("キャスト表Wiki生成"); - result.setBorder(border); - - return result; - } - - /** - * 投票Box生成画面を生成する。 - * @return 投票Box生成画面 - */ - private JComponent buildVotePanel(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.insets = new Insets(2, 2, 2, 2); - result.add(this.voteButton, constraints); - - Border border = BorderFactory.createTitledBorder("投票Wiki生成"); - result.setBorder(border); - - return result; - } - - /** - * 村詳細Wiki生成画面を生成する。 - * @return 村詳細Wiki生成画面 - */ - private JComponent buildVillageWikiPanel(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.insets = new Insets(2, 2, 2, 2); - result.add(this.vlgWikiButton, constraints); - - Border border = BorderFactory.createTitledBorder("村詳細Wiki生成"); - result.setBorder(border); - - return result; - } - - /** - * Wikiテキスト領域GUIの生成。 - * @return Wikiテキスト領域GUI - */ - private JComponent buildClipText(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - Border border; - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.NORTHEAST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - result.add(this.copyClipButton, constraints); - - border = BorderFactory.createEmptyBorder(2, 2, 2, 2); - this.templateArea.setBorder(border); - JScrollPane scroller = new JScrollPane(); - JViewport viewPort = scroller.getViewport(); - viewPort.setView(this.templateArea); - scroller.setHorizontalScrollBarPolicy( - JScrollPane.HORIZONTAL_SCROLLBAR_NEVER - ); - scroller.setMinimumSize(new Dimension(10, 50)); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.gridwidth = GridBagConstraints.REMAINDER; - result.add(scroller, constraints); - - border = BorderFactory.createTitledBorder("PukiWikiテキスト"); - result.setBorder(border); - - return result; - } - - /** - * テンプレート生成画面を生成する。 - * @return テンプレート生成画面 - */ - private JComponent buildClipboardPanel(){ - JComponent result = createGridBagComponent(); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.insets = new Insets(3, 3, 3, 3); - - constraints.weightx = 1.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - - JComponent castPanel = buildCastPanel(); - result.add(castPanel, constraints); - - JComponent vlgWikiPanel = buildVillageWikiPanel(); - result.add(vlgWikiPanel, constraints); - - JComponent votePanel = buildVotePanel(); - result.add(votePanel, constraints); - - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.CENTER; - result.add(new JLabel("↓↓↓"), constraints); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.NORTHWEST; - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.gridwidth = GridBagConstraints.REMAINDER; - JComponent clipText = buildClipText(); - result.add(clipText, constraints); - - return result; - } - - /** - * 画面レイアウトを行う。 - * @param container コンテナ - */ - private void design(Container container){ - LayoutManager layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - - container.setLayout(layout); - - JScrollPane scroller1 = new JScrollPane(); - scroller1.getVerticalScrollBar().setUnitIncrement(15); - scroller1.getHorizontalScrollBar().setUnitIncrement(15); - JViewport viewPort1 = scroller1.getViewport(); - viewPort1.setView(this.summaryPanel); - - JScrollPane scroller2 = new JScrollPane(); - scroller2.getVerticalScrollBar().setUnitIncrement(15); - scroller2.getHorizontalScrollBar().setUnitIncrement(15); - JViewport viewPort2 = scroller2.getViewport(); - viewPort2.setView(this.playerPanel); - - JTabbedPane tabComp = new JTabbedPane(); - tabComp.add("村詳細", scroller1); - tabComp.add("プレイヤー詳細", scroller2); - tabComp.add("まとめサイト用Wiki生成", this.clipboardPanel); - - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - container.add(tabComp, constraints); - - constraints.insets = new Insets(3, 3, 3, 3); - constraints.weightx = 0.0; - constraints.weighty = 0.0; - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.SOUTHEAST; - container.add(this.closeButton, constraints); - - return; - } - - /** - * このモーダルダイアログを閉じる。 - */ - private void actionClose(){ - setVisible(false); - dispose(); - return; - } - - /** - * 村を設定する。 - * @param village 村 - */ - public void setVillage(Village village){ - clear(); - - this.village = village; - if(village == null) return; - - this.gameSummary = new GameSummary(this.village); - - updateSummary(); - - for(Player player : this.gameSummary.getPlayerList()){ - Avatar avatar = player.getAvatar(); - this.playerListModel.addElement(avatar); - } - - if(this.playerListModel.getSize() >= 2){ // 強制イベント発生 - Object player2nd = this.playerListModel.getElementAt(1); - this.playerListModel.setSelectedItem(player2nd); - Object player1st = this.playerListModel.getElementAt(0); - this.playerListModel.setSelectedItem(player1st); - } - - return; - } - - /** - * 村詳細画面の更新。 - */ - private void updateSummary(){ - String villageName = this.village.getVillageFullName(); - - Team winnerTeam = this.gameSummary.getWinnerTeam(); - String wonTeam = winnerTeam.getTeamName(); - - int avatarNum = this.gameSummary.countAvatarNum(); - String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = " - + avatarNum + "名"; - - JComponent roleDetail = createGridBagComponent(); - for(GameRole role : GameRole.values()){ - List players = this.gameSummary.getRoledPlayerList(role); - if(players.size() <= 0) continue; - String roleName = role.getRoleName(); - addCaptionedItem(roleDetail, roleName, " × ", players.size()); - } - - String suddenDeath = this.gameSummary.countSuddenDeath() + "名"; - - DateFormat dform = - DateFormat.getDateTimeInstance(DateFormat.FULL, - DateFormat.FULL); - Date date; - date = this.gameSummary.get1stTalkDate(); - String talk1st = dform.format(date); - date = this.gameSummary.getLastTalkDate(); - String talkLast = dform.format(date); - - int limitHour = this.village.getLimitHour(); - int limitMinute = this.village.getLimitMinute(); - StringBuilder limit = new StringBuilder(); - if(limitHour < 10) limit.append('0'); - limit.append(limitHour).append(':'); - if(limitMinute < 10) limit.append('0'); - limit.append(limitMinute); - - JComponent transition = createGridBagComponent(); - for(int day = 1; day < this.village.getPeriodSize(); day++){ - List players = this.gameSummary.getSurvivorList(day); - CharSequence roleSeq = - GameSummary.getRoleBalanceSequence(players); - String daySeq; - Period period = this.village.getPeriod(day); - daySeq = period.getCaption(); - addCaptionedItem(transition, daySeq, roleSeq); - } - - StringBuilder schedule = new StringBuilder(); - int progressDays = this.village.getProgressDays(); - schedule.append("プロローグ + ") - .append(progressDays) - .append("日 + エピローグ"); - - CharSequence exeInfo = this.gameSummary.dumpExecutionInfo(); - CharSequence eatInfo = this.gameSummary.dumpAssaultInfo(); - CharSequence scoreSeer = this.gameSummary.dumpSeerActivity(); - CharSequence scoreHunter = this.gameSummary.dumpHunterActivity(); - - this.summaryPanel.removeAll(); - - addCaptionedItem(this.summaryPanel, "村名", villageName); - addCaptionedItem(this.summaryPanel, "勝者", wonTeam); - addCaptionedItem(this.summaryPanel, "所要日数", schedule); - addCaptionedItem(this.summaryPanel, "更新時刻", limit); - addCaptionedItem(this.summaryPanel, "発言開始", talk1st); - addCaptionedItem(this.summaryPanel, "最終発言", talkLast); - addCaptionedItem(this.summaryPanel, "参加人数", totalMember); - addCaptionedItem(this.summaryPanel, "役職内訳", roleDetail); - addCaptionedItem(this.summaryPanel, "処刑内訳", exeInfo); - addCaptionedItem(this.summaryPanel, "襲撃内訳", eatInfo); - addCaptionedItem(this.summaryPanel, "突然死", suddenDeath); - addCaptionedItem(this.summaryPanel, "人口推移", transition); - addCaptionedItem(this.summaryPanel, "占成績", scoreSeer); - addCaptionedItem(this.summaryPanel, "狩成績", scoreHunter); - - addFatPad(this.summaryPanel); - - return; - } - - /** - * アクションイベントの振り分け。 - * @param event アクションイベント - */ - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - - if(source == this.closeButton){ - actionClose(); - }else if(source == this.copyClipButton){ - actionCopyToClipboard(); - }else if(source == this.genCastTableButton){ - actionGenCastTable(); - }else if(source == this.voteButton){ - actionGenVoteBox(); - }else if(source == this.vlgWikiButton){ - actionGenVillageWiki(); - }else if(source == this.prevPlayer){ - int index = this.playerBox.getSelectedIndex(); - if(index <= 0) return; - index--; - this.playerBox.setSelectedIndex(index); - }else if(source == this.nextPlayer){ - int index = this.playerBox.getSelectedIndex(); - int playerNum = this.playerBox.getItemCount(); - if(index >= playerNum - 1) return; - index++; - this.playerBox.setSelectedIndex(index); - } - - return; - } - - /** - * キャスト表Wikiデータの生成を行う。 - */ - private void actionGenCastTable(){ - Object selected = this.iconSetListModel.getSelectedItem(); - if( ! (selected instanceof FaceIconSet) ) return; - FaceIconSet iconSet = (FaceIconSet) selected; - - CharSequence wikiText = this.gameSummary.dumpCastingBoard(iconSet); - - putWikiText(wikiText); - - return; - } - - /** - * 投票Boxを生成する。 - */ - private void actionGenVoteBox(){ - CharSequence wikiText = this.gameSummary.dumpVoteBox(); - putWikiText(wikiText); - return; - } - - /** - * 村詳細Wikiを生成する。 - */ - private void actionGenVillageWiki(){ - CharSequence wikiText = this.gameSummary.dumpVillageWiki(); - putWikiText(wikiText); - return; - } - - /** - * Wikiテキストをテキストボックスに出力する。 - * スクロール位置は一番上に。 - * @param wikiText Wikiテキスト - */ - private void putWikiText(CharSequence wikiText){ - this.templateArea.setText(wikiText.toString()); - // 最上部へスクロールアップ - EventQueue.invokeLater(new Runnable(){ - public void run(){ - templateArea.scrollRectToVisible(new Rectangle()); - } - }); - // TODO あらかじめテキストを全選択させておきたい - return; - } - - /** - * Wiki文字列をクリップボードへコピーする。 - */ - private void actionCopyToClipboard(){ - CharSequence text = this.templateArea.getText(); - ClipboardAction.copyToClipboard(text); - return; - } - - /** - * プレイヤーの選択操作。 - * @param avatar 選択されたAvatar - */ - private void selectPlayer(Avatar avatar){ - if(avatar == this.playerBox.getItemAt(0)){ - this.prevPlayer.setEnabled(false); - }else{ - this.prevPlayer.setEnabled(true); - } - - int playerNum = this.playerBox.getItemCount(); - if(avatar == this.playerBox.getItemAt(playerNum - 1)){ - this.nextPlayer.setEnabled(false); - }else{ - this.nextPlayer.setEnabled(true); - } - - Image image = village.getAvatarFaceImage(avatar); - this.faceIcon.setImage(image); - this.faceLabel.setIcon(null); // なぜかこれが必要 - this.faceLabel.setIcon(this.faceIcon); - - Player player = this.gameSummary.getPlayer(avatar); - - GameRole role = player.getRole(); - this.roleLabel.setText(role.getRoleName()); - - String destinyMessage = player.getDestinyMessage(); - this.destinyLabel.setText(destinyMessage); - - CharSequence specialSkill = ""; - switch(role){ - case SEER: - specialSkill = this.gameSummary.dumpSeerActivity(); - break; - case HUNTER: - specialSkill = this.gameSummary.dumpHunterActivity(); - break; - default: - break; - } - this.specialSkillLabel.setText(specialSkill.toString()); - - this.entryLabel.setText(String.valueOf(player.getEntryNo())); - - String userId = player.getIdName(); - this.idLabel.setText(userId); - - String urlText = player.getUrlText(); - String caption = urlText; - - if(urlText == null || urlText.length() <= 0){ - urlText = WolfBBS.encodeURLFromId(userId); - caption = "もしかして " + urlText; - } - - this.urlLine.setURLText(urlText); - this.urlLine.setCaption(caption); - - return; - } - - /** - * 顔アイコンセットの選択操作。 - * @param iconSet 顔アイコンセット - */ - private void selectIconSet(FaceIconSet iconSet){ - String author = iconSet.getAuthor(); - String urlText = iconSet.getUrlText(); - this.authorLabel .setText(author + "氏"); - this.authorUrlLabel.setText(urlText); - return; - } - - /** - * コンボボックス操作の受信。 - * @param event コンボボックス操作イベント - */ - public void itemStateChanged(ItemEvent event){ - int state = event.getStateChange(); - if(state != ItemEvent.SELECTED) return; - - Object source = event.getSource(); - Object item = event.getItem(); - if(item == null) return; - - if(source == this.playerBox){ - if( ! (item instanceof Avatar) ) return; - Avatar avatar = (Avatar) item; - selectPlayer(avatar); - }else if(source == this.iconSetBox){ - if( ! (item instanceof FaceIconSet) ) return; - FaceIconSet iconSet = (FaceIconSet) item; - selectIconSet(iconSet); - } - - return; - } - - /** - * 表示内容をクリアする。 - */ - private void clear(){ - this.templateArea.setText(""); - this.playerListModel.removeAllElements(); - return; - } - - // TODO ハムスター対応 -} +/* + * Village digest GUI + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.text.DateFormat; +import java.util.Date; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JViewport; +import javax.swing.border.Border; +import jp.sourceforge.jindolf.corelib.GameRole; +import jp.sourceforge.jindolf.corelib.Team; + +/** + * 決着のついた村のサマリを表示する。 + */ +@SuppressWarnings("serial") +public class VillageDigest + extends JDialog + implements ActionListener, + ItemListener { + + private static final String FRAMETITLE = + "村のダイジェスト - " + Jindolf.TITLE; + private static final String ITEMDELIM = " : "; + + + private final JComponent summaryPanel = buildSummaryPanel(); + + private final JLabel faceLabel = new JLabel(); + private final ImageIcon faceIcon = new ImageIcon(); + private final JComboBox playerBox = new JComboBox(); + private final DefaultComboBoxModel playerListModel = + new DefaultComboBoxModel(); + private final JButton prevPlayer = new JButton("↑"); + private final JButton nextPlayer = new JButton("↓"); + private final JLabel roleLabel = new JLabel(); + private final JLabel destinyLabel = new JLabel(); + private final JLabel specialSkillLabel = new JLabel(); + private final JLabel entryLabel = new JLabel(); + private final JLabel idLabel = new JLabel(); + private final WebButton urlLine = new WebButton(); + private final JComponent playerPanel = buildPlayerPanel(); + + private final JComboBox iconSetBox = new JComboBox(); + private final DefaultComboBoxModel iconSetListModel = + new DefaultComboBoxModel(); + private final JLabel authorLabel = new JLabel(); + private final JLabel authorUrlLabel = new JLabel(); + private final WebButton iconCatalog = new WebButton(); + private final JButton genCastTableButton = + new JButton("キャスト表Wiki生成"); + private final JButton copyClipButton = + new JButton("クリップボードにコピー"); + private final JTextArea templateArea = new JTextArea(); + private final JButton voteButton = new JButton("投票Wiki生成"); + private final JButton vlgWikiButton = new JButton("村詳細Wiki生成"); + private final JComponent clipboardPanel = buildClipboardPanel(); + + private final JButton closeButton = new JButton("閉じる"); + + private Village village; + + private GameSummary gameSummary; + + + /** + * コンストラクタ。 + * @param owner 親フレーム + */ + public VillageDigest(Frame owner){ + super(owner, FRAMETITLE, true); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter(){ + @Override + public void windowClosing(WindowEvent event){ + actionClose(); + return; + } + }); + + this.faceLabel.setIcon(this.faceIcon); + + this.playerBox.setModel(this.playerListModel); + this.playerBox.addItemListener(this); + + this.prevPlayer.setMargin(new Insets(1, 1, 1, 1)); + this.prevPlayer.addActionListener(this); + this.prevPlayer.setToolTipText("前のプレイヤー"); + + this.nextPlayer.setMargin(new Insets(1, 1, 1, 1)); + this.nextPlayer.addActionListener(this); + this.nextPlayer.setToolTipText("次のプレイヤー"); + + this.iconSetBox.setModel(this.iconSetListModel); + this.iconSetBox.addItemListener(this); + for(FaceIconSet iconSet : WolfBBS.getFaceIconSetList()){ + this.iconSetListModel.addElement(iconSet); + } + + this.iconCatalog.setURLText( + "http://wolfbbs.jp/" + +"%A4%DE%A4%C8%A4%E1%A5%B5%A5%A4%A5%C8%A4%C7" + +"%CD%F8%CD%D1%B2%C4%C7%BD%A4%CA%A5%A2%A5%A4" + +"%A5%B3%A5%F3%B2%E8%C1%FC.html"); + this.iconCatalog.setCaption("顔アイコン見本ページ"); + + this.templateArea.setEditable(true); + this.templateArea.setLineWrap(true); + Monodizer.monodize(this.templateArea); + JPopupMenu popup = new TextPopup(); + this.templateArea.setComponentPopupMenu(popup); + + this.genCastTableButton.addActionListener(this); + this.voteButton.addActionListener(this); + this.vlgWikiButton.addActionListener(this); + this.copyClipButton.addActionListener(this); + + this.closeButton.addActionListener(this); + + Monodizer.monodize(this.idLabel); + Monodizer.monodize(this.authorUrlLabel); + + Container content = getContentPane(); + design(content); + + return; + } + + + /** + * キャプション付き項目をコンテナに追加。 + * @param container コンテナ + * @param caption 項目キャプション名 + * @param delimiter デリミタ文字 + * @param item 項目アイテム + */ + private static void addCaptionedItem(Container container, + CharSequence caption, + CharSequence delimiter, + Object item ){ + LayoutManager layout = container.getLayout(); + if( ! (layout instanceof GridBagLayout) ){ + throw new IllegalArgumentException(); + } + + JLabel captionLabel = new JLabel(caption.toString()); + JLabel delimiterLabel = new JLabel(delimiter.toString()); + JComponent itemComp; + if(item instanceof JComponent){ + itemComp = (JComponent) item; + }else{ + itemComp = new JLabel(item.toString()); + } + + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.gridwidth = 1; + constraints.anchor = GridBagConstraints.NORTHEAST; + container.add(captionLabel, constraints); + container.add(delimiterLabel, constraints); + + constraints.weightx = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.anchor = GridBagConstraints.NORTHWEST; + container.add(itemComp, constraints); + + return; + } + + /** + * キャプション付き項目をコンテナに追加。 + * @param container コンテナ + * @param caption 項目キャプション名 + * @param item 項目アイテム + */ + private static void addCaptionedItem(Container container, + CharSequence caption, + Object item ){ + addCaptionedItem(container, caption, ITEMDELIM, item); + return; + } + + /** + * レイアウトの最後に詰め物をする。 + * @param container コンテナ + */ + private static void addFatPad(Container container){ + LayoutManager layout = container.getLayout(); + if( ! (layout instanceof GridBagLayout) ){ + throw new IllegalArgumentException(); + } + + JComponent pad = new JPanel(); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.gridwidth = GridBagConstraints.REMAINDER; + container.add(pad, constraints); + + return; + } + + /** + * GridBagLayoutでレイアウトする空コンポーネントを生成する。 + * @return 空コンポーネント + */ + private static JComponent createGridBagComponent(){ + JComponent result = new JPanel(); + LayoutManager layout = new GridBagLayout(); + result.setLayout(layout); + return result; + } + + /** + * 村サマリ画面の生成。 + * @return 村サマリ画面 + */ + private JComponent buildSummaryPanel(){ + JComponent result = createGridBagComponent(); + + Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); + result.setBorder(border); + + return result; + } + + /** + * プレイヤーサマリ画面の生成。 + * @return プレイヤーサマリ画面 + */ + private JComponent buildPlayerPanel(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.insets = new Insets(2, 2, 2, 2); + result.add(this.faceLabel, constraints); + + result.add(new JLabel(ITEMDELIM), constraints); + + constraints.anchor = GridBagConstraints.NORTHWEST; + result.add(this.playerBox, constraints); + result.add(this.prevPlayer, constraints); + constraints.gridwidth = GridBagConstraints.REMAINDER; + result.add(this.nextPlayer, constraints); + + addCaptionedItem(result, "役職", this.roleLabel); + addCaptionedItem(result, "運命", this.destinyLabel); + addCaptionedItem(result, "特殊技能", this.specialSkillLabel); + addCaptionedItem(result, "エントリ#", this.entryLabel); + addCaptionedItem(result, "ID", this.idLabel); + addCaptionedItem(result, "URL", this.urlLine); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.gridwidth = GridBagConstraints.REMAINDER; + result.add(new JPanel(), constraints); + + Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5); + result.setBorder(border); + + return result; + } + + /** + * キャスト表生成画面を生成する。 + * @return キャスト表生成画面 + */ + private JComponent buildCastPanel(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.insets = new Insets(2, 2, 2, 2); + result.add(this.iconCatalog, constraints); + + addCaptionedItem(result, "顔アイコンセットを選択", this.iconSetBox); + addCaptionedItem(result, "作者", this.authorLabel); + addCaptionedItem(result, "URL", this.authorUrlLabel); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.insets = new Insets(2, 2, 2, 2); + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + result.add(this.genCastTableButton, constraints); + + Border border = BorderFactory.createTitledBorder("キャスト表Wiki生成"); + result.setBorder(border); + + return result; + } + + /** + * 投票Box生成画面を生成する。 + * @return 投票Box生成画面 + */ + private JComponent buildVotePanel(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.insets = new Insets(2, 2, 2, 2); + result.add(this.voteButton, constraints); + + Border border = BorderFactory.createTitledBorder("投票Wiki生成"); + result.setBorder(border); + + return result; + } + + /** + * 村詳細Wiki生成画面を生成する。 + * @return 村詳細Wiki生成画面 + */ + private JComponent buildVillageWikiPanel(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.insets = new Insets(2, 2, 2, 2); + result.add(this.vlgWikiButton, constraints); + + Border border = BorderFactory.createTitledBorder("村詳細Wiki生成"); + result.setBorder(border); + + return result; + } + + /** + * Wikiテキスト領域GUIの生成。 + * @return Wikiテキスト領域GUI + */ + private JComponent buildClipText(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + Border border; + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHEAST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + result.add(this.copyClipButton, constraints); + + border = BorderFactory.createEmptyBorder(2, 2, 2, 2); + this.templateArea.setBorder(border); + JScrollPane scroller = new JScrollPane(); + JViewport viewPort = scroller.getViewport(); + viewPort.setView(this.templateArea); + scroller.setHorizontalScrollBarPolicy( + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + ); + scroller.setMinimumSize(new Dimension(10, 50)); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.gridwidth = GridBagConstraints.REMAINDER; + result.add(scroller, constraints); + + border = BorderFactory.createTitledBorder("PukiWikiテキスト"); + result.setBorder(border); + + return result; + } + + /** + * テンプレート生成画面を生成する。 + * @return テンプレート生成画面 + */ + private JComponent buildClipboardPanel(){ + JComponent result = createGridBagComponent(); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.insets = new Insets(3, 3, 3, 3); + + constraints.weightx = 1.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.NORTHWEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + + JComponent castPanel = buildCastPanel(); + result.add(castPanel, constraints); + + JComponent vlgWikiPanel = buildVillageWikiPanel(); + result.add(vlgWikiPanel, constraints); + + JComponent votePanel = buildVotePanel(); + result.add(votePanel, constraints); + + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.CENTER; + result.add(new JLabel("↓↓↓"), constraints); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.NORTHWEST; + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.gridwidth = GridBagConstraints.REMAINDER; + JComponent clipText = buildClipText(); + result.add(clipText, constraints); + + return result; + } + + /** + * 画面レイアウトを行う。 + * @param container コンテナ + */ + private void design(Container container){ + LayoutManager layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + + container.setLayout(layout); + + JScrollPane scroller1 = new JScrollPane(); + scroller1.getVerticalScrollBar().setUnitIncrement(15); + scroller1.getHorizontalScrollBar().setUnitIncrement(15); + JViewport viewPort1 = scroller1.getViewport(); + viewPort1.setView(this.summaryPanel); + + JScrollPane scroller2 = new JScrollPane(); + scroller2.getVerticalScrollBar().setUnitIncrement(15); + scroller2.getHorizontalScrollBar().setUnitIncrement(15); + JViewport viewPort2 = scroller2.getViewport(); + viewPort2.setView(this.playerPanel); + + JTabbedPane tabComp = new JTabbedPane(); + tabComp.add("村詳細", scroller1); + tabComp.add("プレイヤー詳細", scroller2); + tabComp.add("まとめサイト用Wiki生成", this.clipboardPanel); + + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + container.add(tabComp, constraints); + + constraints.insets = new Insets(3, 3, 3, 3); + constraints.weightx = 0.0; + constraints.weighty = 0.0; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.SOUTHEAST; + container.add(this.closeButton, constraints); + + return; + } + + /** + * このモーダルダイアログを閉じる。 + */ + private void actionClose(){ + setVisible(false); + dispose(); + return; + } + + /** + * 村を設定する。 + * @param village 村 + */ + public void setVillage(Village village){ + clear(); + + this.village = village; + if(village == null) return; + + this.gameSummary = new GameSummary(this.village); + + updateSummary(); + + for(Player player : this.gameSummary.getPlayerList()){ + Avatar avatar = player.getAvatar(); + this.playerListModel.addElement(avatar); + } + + if(this.playerListModel.getSize() >= 2){ // 強制イベント発生 + Object player2nd = this.playerListModel.getElementAt(1); + this.playerListModel.setSelectedItem(player2nd); + Object player1st = this.playerListModel.getElementAt(0); + this.playerListModel.setSelectedItem(player1st); + } + + return; + } + + /** + * 村詳細画面の更新。 + */ + private void updateSummary(){ + String villageName = this.village.getVillageFullName(); + + Team winnerTeam = this.gameSummary.getWinnerTeam(); + String wonTeam = winnerTeam.getTeamName(); + + int avatarNum = this.gameSummary.countAvatarNum(); + String totalMember = "ゲルト + " + (avatarNum - 1) + "名 = " + + avatarNum + "名"; + + JComponent roleDetail = createGridBagComponent(); + for(GameRole role : GameRole.values()){ + List players = this.gameSummary.getRoledPlayerList(role); + if(players.size() <= 0) continue; + String roleName = role.getRoleName(); + addCaptionedItem(roleDetail, roleName, " × ", players.size()); + } + + String suddenDeath = this.gameSummary.countSuddenDeath() + "名"; + + DateFormat dform = + DateFormat.getDateTimeInstance(DateFormat.FULL, + DateFormat.FULL); + Date date; + date = this.gameSummary.get1stTalkDate(); + String talk1st = dform.format(date); + date = this.gameSummary.getLastTalkDate(); + String talkLast = dform.format(date); + + int limitHour = this.village.getLimitHour(); + int limitMinute = this.village.getLimitMinute(); + StringBuilder limit = new StringBuilder(); + if(limitHour < 10) limit.append('0'); + limit.append(limitHour).append(':'); + if(limitMinute < 10) limit.append('0'); + limit.append(limitMinute); + + JComponent transition = createGridBagComponent(); + for(int day = 1; day < this.village.getPeriodSize(); day++){ + List players = this.gameSummary.getSurvivorList(day); + CharSequence roleSeq = + GameSummary.getRoleBalanceSequence(players); + String daySeq; + Period period = this.village.getPeriod(day); + daySeq = period.getCaption(); + addCaptionedItem(transition, daySeq, roleSeq); + } + + StringBuilder schedule = new StringBuilder(); + int progressDays = this.village.getProgressDays(); + schedule.append("プロローグ + ") + .append(progressDays) + .append("日 + エピローグ"); + + CharSequence exeInfo = this.gameSummary.dumpExecutionInfo(); + CharSequence eatInfo = this.gameSummary.dumpAssaultInfo(); + CharSequence scoreSeer = this.gameSummary.dumpSeerActivity(); + CharSequence scoreHunter = this.gameSummary.dumpHunterActivity(); + + this.summaryPanel.removeAll(); + + addCaptionedItem(this.summaryPanel, "村名", villageName); + addCaptionedItem(this.summaryPanel, "勝者", wonTeam); + addCaptionedItem(this.summaryPanel, "所要日数", schedule); + addCaptionedItem(this.summaryPanel, "更新時刻", limit); + addCaptionedItem(this.summaryPanel, "発言開始", talk1st); + addCaptionedItem(this.summaryPanel, "最終発言", talkLast); + addCaptionedItem(this.summaryPanel, "参加人数", totalMember); + addCaptionedItem(this.summaryPanel, "役職内訳", roleDetail); + addCaptionedItem(this.summaryPanel, "処刑内訳", exeInfo); + addCaptionedItem(this.summaryPanel, "襲撃内訳", eatInfo); + addCaptionedItem(this.summaryPanel, "突然死", suddenDeath); + addCaptionedItem(this.summaryPanel, "人口推移", transition); + addCaptionedItem(this.summaryPanel, "占成績", scoreSeer); + addCaptionedItem(this.summaryPanel, "狩成績", scoreHunter); + + addFatPad(this.summaryPanel); + + return; + } + + /** + * アクションイベントの振り分け。 + * @param event アクションイベント + */ + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + + if(source == this.closeButton){ + actionClose(); + }else if(source == this.copyClipButton){ + actionCopyToClipboard(); + }else if(source == this.genCastTableButton){ + actionGenCastTable(); + }else if(source == this.voteButton){ + actionGenVoteBox(); + }else if(source == this.vlgWikiButton){ + actionGenVillageWiki(); + }else if(source == this.prevPlayer){ + int index = this.playerBox.getSelectedIndex(); + if(index <= 0) return; + index--; + this.playerBox.setSelectedIndex(index); + }else if(source == this.nextPlayer){ + int index = this.playerBox.getSelectedIndex(); + int playerNum = this.playerBox.getItemCount(); + if(index >= playerNum - 1) return; + index++; + this.playerBox.setSelectedIndex(index); + } + + return; + } + + /** + * キャスト表Wikiデータの生成を行う。 + */ + private void actionGenCastTable(){ + Object selected = this.iconSetListModel.getSelectedItem(); + if( ! (selected instanceof FaceIconSet) ) return; + FaceIconSet iconSet = (FaceIconSet) selected; + + CharSequence wikiText = this.gameSummary.dumpCastingBoard(iconSet); + + putWikiText(wikiText); + + return; + } + + /** + * 投票Boxを生成する。 + */ + private void actionGenVoteBox(){ + CharSequence wikiText = this.gameSummary.dumpVoteBox(); + putWikiText(wikiText); + return; + } + + /** + * 村詳細Wikiを生成する。 + */ + private void actionGenVillageWiki(){ + CharSequence wikiText = this.gameSummary.dumpVillageWiki(); + putWikiText(wikiText); + return; + } + + /** + * Wikiテキストをテキストボックスに出力する。 + * スクロール位置は一番上に。 + * @param wikiText Wikiテキスト + */ + private void putWikiText(CharSequence wikiText){ + this.templateArea.setText(wikiText.toString()); + // 最上部へスクロールアップ + EventQueue.invokeLater(new Runnable(){ + public void run(){ + templateArea.scrollRectToVisible(new Rectangle()); + } + }); + // TODO あらかじめテキストを全選択させておきたい + return; + } + + /** + * Wiki文字列をクリップボードへコピーする。 + */ + private void actionCopyToClipboard(){ + CharSequence text = this.templateArea.getText(); + ClipboardAction.copyToClipboard(text); + return; + } + + /** + * プレイヤーの選択操作。 + * @param avatar 選択されたAvatar + */ + private void selectPlayer(Avatar avatar){ + if(avatar == this.playerBox.getItemAt(0)){ + this.prevPlayer.setEnabled(false); + }else{ + this.prevPlayer.setEnabled(true); + } + + int playerNum = this.playerBox.getItemCount(); + if(avatar == this.playerBox.getItemAt(playerNum - 1)){ + this.nextPlayer.setEnabled(false); + }else{ + this.nextPlayer.setEnabled(true); + } + + Image image = village.getAvatarFaceImage(avatar); + this.faceIcon.setImage(image); + this.faceLabel.setIcon(null); // なぜかこれが必要 + this.faceLabel.setIcon(this.faceIcon); + + Player player = this.gameSummary.getPlayer(avatar); + + GameRole role = player.getRole(); + this.roleLabel.setText(role.getRoleName()); + + String destinyMessage = player.getDestinyMessage(); + this.destinyLabel.setText(destinyMessage); + + CharSequence specialSkill = ""; + switch(role){ + case SEER: + specialSkill = this.gameSummary.dumpSeerActivity(); + break; + case HUNTER: + specialSkill = this.gameSummary.dumpHunterActivity(); + break; + default: + break; + } + this.specialSkillLabel.setText(specialSkill.toString()); + + this.entryLabel.setText(String.valueOf(player.getEntryNo())); + + String userId = player.getIdName(); + this.idLabel.setText(userId); + + String urlText = player.getUrlText(); + String caption = urlText; + + if(urlText == null || urlText.length() <= 0){ + urlText = WolfBBS.encodeURLFromId(userId); + caption = "もしかして " + urlText; + } + + this.urlLine.setURLText(urlText); + this.urlLine.setCaption(caption); + + return; + } + + /** + * 顔アイコンセットの選択操作。 + * @param iconSet 顔アイコンセット + */ + private void selectIconSet(FaceIconSet iconSet){ + String author = iconSet.getAuthor(); + String urlText = iconSet.getUrlText(); + this.authorLabel .setText(author + "氏"); + this.authorUrlLabel.setText(urlText); + return; + } + + /** + * コンボボックス操作の受信。 + * @param event コンボボックス操作イベント + */ + public void itemStateChanged(ItemEvent event){ + int state = event.getStateChange(); + if(state != ItemEvent.SELECTED) return; + + Object source = event.getSource(); + Object item = event.getItem(); + if(item == null) return; + + if(source == this.playerBox){ + if( ! (item instanceof Avatar) ) return; + Avatar avatar = (Avatar) item; + selectPlayer(avatar); + }else if(source == this.iconSetBox){ + if( ! (item instanceof FaceIconSet) ) return; + FaceIconSet iconSet = (FaceIconSet) item; + selectIconSet(iconSet); + } + + return; + } + + /** + * 表示内容をクリアする。 + */ + private void clear(){ + this.templateArea.setText(""); + this.playerListModel.removeAllElements(); + return; + } + + // TODO ハムスター対応 +} diff --git a/src/main/java/jp/sourceforge/jindolf/VillageIconRenderer.java b/src/main/java/jp/sourceforge/jindolf/VillageIconRenderer.java index dd3b319..7ec01da 100644 --- a/src/main/java/jp/sourceforge/jindolf/VillageIconRenderer.java +++ b/src/main/java/jp/sourceforge/jindolf/VillageIconRenderer.java @@ -1,100 +1,100 @@ -/* - * Village icon renderer for JTree - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Component; -import java.net.URL; -import javax.swing.ImageIcon; -import javax.swing.JTree; -import javax.swing.tree.DefaultTreeCellRenderer; - -/** - * JTreeの村別アイコン表示。 - */ -@SuppressWarnings("serial") -public class VillageIconRenderer extends DefaultTreeCellRenderer{ - - private static final ImageIcon ICON_PROLOGUE; - private static final ImageIcon ICON_PROGRESS; - private static final ImageIcon ICON_EPILOGUE; - private static final ImageIcon ICON_GAMEOVER; - private static final ImageIcon ICON_INVALID; - - static{ - URL url; - url = Jindolf.getResource("resources/image/prologue.png"); - ICON_PROLOGUE = new ImageIcon(url); - url = Jindolf.getResource("resources/image/progress.png"); - ICON_PROGRESS = new ImageIcon(url); - url = Jindolf.getResource("resources/image/epilogue.png"); - ICON_EPILOGUE = new ImageIcon(url); - url = Jindolf.getResource("resources/image/gameover.png"); - ICON_GAMEOVER = new ImageIcon(url); - url = Jindolf.getResource("resources/image/cross.png"); - ICON_INVALID = new ImageIcon(url); - } - - /** - * コンストラクタ。 - */ - public VillageIconRenderer(){ - super(); - return; - } - - /** - * {@inheritDoc} - * 村種別によってツリーリストアイコンを書き分ける。 - * @param tree {@inheritDoc} - * @param value {@inheritDoc} - * @param sel {@inheritDoc} - * @param expanded {@inheritDoc} - * @param leaf {@inheritDoc} - * @param row {@inheritDoc} - * @param hasFocus {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Component getTreeCellRendererComponent( - JTree tree, - Object value, - boolean sel, - boolean expanded, - boolean leaf, - int row, - boolean hasFocus ){ - if(leaf && value instanceof Village){ - Village village = (Village) value; - ImageIcon icon = null; - switch(village.getState()){ - case PROLOGUE: icon = ICON_PROLOGUE; break; - case PROGRESS: icon = ICON_PROGRESS; break; - case EPILOGUE: icon = ICON_EPILOGUE; break; - case GAMEOVER: icon = ICON_GAMEOVER; break; - default: assert false; break; - } - if( ! village.isValid()) icon = ICON_INVALID; - setLeafIcon(icon); - } - - Component comp = - super - .getTreeCellRendererComponent( - tree, - value, - sel, - expanded, - leaf, - row, - hasFocus - ); - - return comp; - } - -} +/* + * Village icon renderer for JTree + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Component; +import java.net.URL; +import javax.swing.ImageIcon; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +/** + * JTreeの村別アイコン表示。 + */ +@SuppressWarnings("serial") +public class VillageIconRenderer extends DefaultTreeCellRenderer{ + + private static final ImageIcon ICON_PROLOGUE; + private static final ImageIcon ICON_PROGRESS; + private static final ImageIcon ICON_EPILOGUE; + private static final ImageIcon ICON_GAMEOVER; + private static final ImageIcon ICON_INVALID; + + static{ + URL url; + url = Jindolf.getResource("resources/image/prologue.png"); + ICON_PROLOGUE = new ImageIcon(url); + url = Jindolf.getResource("resources/image/progress.png"); + ICON_PROGRESS = new ImageIcon(url); + url = Jindolf.getResource("resources/image/epilogue.png"); + ICON_EPILOGUE = new ImageIcon(url); + url = Jindolf.getResource("resources/image/gameover.png"); + ICON_GAMEOVER = new ImageIcon(url); + url = Jindolf.getResource("resources/image/cross.png"); + ICON_INVALID = new ImageIcon(url); + } + + /** + * コンストラクタ。 + */ + public VillageIconRenderer(){ + super(); + return; + } + + /** + * {@inheritDoc} + * 村種別によってツリーリストアイコンを書き分ける。 + * @param tree {@inheritDoc} + * @param value {@inheritDoc} + * @param sel {@inheritDoc} + * @param expanded {@inheritDoc} + * @param leaf {@inheritDoc} + * @param row {@inheritDoc} + * @param hasFocus {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Component getTreeCellRendererComponent( + JTree tree, + Object value, + boolean sel, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus ){ + if(leaf && value instanceof Village){ + Village village = (Village) value; + ImageIcon icon = null; + switch(village.getState()){ + case PROLOGUE: icon = ICON_PROLOGUE; break; + case PROGRESS: icon = ICON_PROGRESS; break; + case EPILOGUE: icon = ICON_EPILOGUE; break; + case GAMEOVER: icon = ICON_GAMEOVER; break; + default: assert false; break; + } + if( ! village.isValid()) icon = ICON_INVALID; + setLeafIcon(icon); + } + + Component comp = + super + .getTreeCellRendererComponent( + tree, + value, + sel, + expanded, + leaf, + row, + hasFocus + ); + + return comp; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/VillageInfoPanel.java b/src/main/java/jp/sourceforge/jindolf/VillageInfoPanel.java index f2adf9c..fc5c87b 100644 --- a/src/main/java/jp/sourceforge/jindolf/VillageInfoPanel.java +++ b/src/main/java/jp/sourceforge/jindolf/VillageInfoPanel.java @@ -1,204 +1,204 @@ -/* - * village information panel - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import javax.swing.JLabel; -import javax.swing.JPanel; - -/** - * 村情報表示パネル。 - */ -@SuppressWarnings("serial") -public class VillageInfoPanel extends JPanel{ - - private Village village; - - private final JLabel landName = new JLabel(); - private final JLabel villageName = new JLabel(); - private final JLabel villageID = new JLabel(); - private final JLabel state = new JLabel(); - private final JLabel days = new JLabel(); - private final JLabel limit = new JLabel(); - - private final JLabel limitCaption = new JLabel(); - - /** - * コンストラクタ。 - */ - public VillageInfoPanel(){ - super(); - - design(); - - updateVillage(null); - - return; - } - - /** - * レイアウトを行う。 - */ - private void design(){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - setLayout(layout); - - constraints.insets = new Insets(2, 2, 2, 2); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(new JLabel("国名 : "), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.landName, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(new JLabel("村名 : "), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.villageName, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(new JLabel("村ID : "), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.villageID, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(new JLabel("状態 : "), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.state, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(new JLabel("所要日数 : "), constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.days, constraints); - - constraints.anchor = GridBagConstraints.EAST; - constraints.gridwidth = 1; - add(this.limitCaption, constraints); - constraints.anchor = GridBagConstraints.WEST; - constraints.gridwidth = GridBagConstraints.REMAINDER; - add(this.limit, constraints); - - constraints.fill = GridBagConstraints.BOTH; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.gridheight = GridBagConstraints.REMAINDER; - add(new JPanel(), constraints); // ダミー詰め物 - - return; - } - - /** - * 村を返す。 - * @return 村 - */ - public Village getVillage(){ - return this.village; - } - - /** - * 村情報を更新する。 - * @param villageArg 村 - */ - public final void updateVillage(Village villageArg){ - this.village = villageArg; - - if(this.village == null){ - this.landName .setText("???"); - this.villageName.setText("???"); - this.villageID .setText("???"); - this.state .setText("???"); - this.days .setText("???"); - this.limit .setText("???"); - - this.limitCaption.setText("更新日時 : "); - - return; - } - - String land = this.village.getParentLand() - .getLandDef() - .getLandName(); - String vName = "「" + this.village.getVillageFullName() + "」"; - String vID = this.village.getVillageID(); - - int progressDays = this.village.getProgressDays(); - String status; - String daysInfo; - String caption; - switch(this.village.getState()){ - case PROLOGUE: - status = "プロローグ中"; - daysInfo = "プロローグ中"; - caption = "プロローグ終了予想 : "; - break; - case PROGRESS: - status = "ゲーム進行中"; - daysInfo = "プロローグ + " + progressDays + "日目"; - caption = "更新日時 : "; - break; - case EPILOGUE: - status = "エピローグ中"; - daysInfo = "プロローグ + " + progressDays + "日 + エピローグ中"; - caption = "エピローグ終了予想 : "; - break; - case GAMEOVER: - status = "ゲーム終了"; - daysInfo = "プロローグ + " + progressDays + "日 + エピローグ"; - caption = "エピローグ終了日時 : "; - break; - case UNKNOWN: - status = "不明"; - daysInfo = "不明"; - caption = "更新日時 : "; - break; - default: - assert false; - status = "???"; - daysInfo = "???"; - caption = "更新日時 : "; - break; - } - - int limitMonth = this.village.getLimitMonth(); - int limitDay = this.village.getLimitDay(); - int limitHour = this.village.getLimitHour(); - int limitMinute = this.village.getLimitMinute(); - - String limitDate = limitMonth + "月" + limitDay + "日"; - String limitTime = ""; - if(limitHour < 10) limitTime += "0"; - limitTime += limitHour + ":"; - if(limitMinute < 10) limitTime += "0"; - limitTime += limitMinute; - - this.landName .setText(land); - this.villageName.setText(vName); - this.villageID .setText(vID); - this.state .setText(status); - this.days .setText(daysInfo); - this.limit .setText(limitDate + " " + limitTime); - - this.limitCaption.setText(caption); - - return; - } - -} +/* + * village information panel + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * 村情報表示パネル。 + */ +@SuppressWarnings("serial") +public class VillageInfoPanel extends JPanel{ + + private Village village; + + private final JLabel landName = new JLabel(); + private final JLabel villageName = new JLabel(); + private final JLabel villageID = new JLabel(); + private final JLabel state = new JLabel(); + private final JLabel days = new JLabel(); + private final JLabel limit = new JLabel(); + + private final JLabel limitCaption = new JLabel(); + + /** + * コンストラクタ。 + */ + public VillageInfoPanel(){ + super(); + + design(); + + updateVillage(null); + + return; + } + + /** + * レイアウトを行う。 + */ + private void design(){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + setLayout(layout); + + constraints.insets = new Insets(2, 2, 2, 2); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(new JLabel("国名 : "), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.landName, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(new JLabel("村名 : "), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.villageName, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(new JLabel("村ID : "), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.villageID, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(new JLabel("状態 : "), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.state, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(new JLabel("所要日数 : "), constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.days, constraints); + + constraints.anchor = GridBagConstraints.EAST; + constraints.gridwidth = 1; + add(this.limitCaption, constraints); + constraints.anchor = GridBagConstraints.WEST; + constraints.gridwidth = GridBagConstraints.REMAINDER; + add(this.limit, constraints); + + constraints.fill = GridBagConstraints.BOTH; + constraints.weightx = 1.0; + constraints.weighty = 1.0; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.gridheight = GridBagConstraints.REMAINDER; + add(new JPanel(), constraints); // ダミー詰め物 + + return; + } + + /** + * 村を返す。 + * @return 村 + */ + public Village getVillage(){ + return this.village; + } + + /** + * 村情報を更新する。 + * @param villageArg 村 + */ + public final void updateVillage(Village villageArg){ + this.village = villageArg; + + if(this.village == null){ + this.landName .setText("???"); + this.villageName.setText("???"); + this.villageID .setText("???"); + this.state .setText("???"); + this.days .setText("???"); + this.limit .setText("???"); + + this.limitCaption.setText("更新日時 : "); + + return; + } + + String land = this.village.getParentLand() + .getLandDef() + .getLandName(); + String vName = "「" + this.village.getVillageFullName() + "」"; + String vID = this.village.getVillageID(); + + int progressDays = this.village.getProgressDays(); + String status; + String daysInfo; + String caption; + switch(this.village.getState()){ + case PROLOGUE: + status = "プロローグ中"; + daysInfo = "プロローグ中"; + caption = "プロローグ終了予想 : "; + break; + case PROGRESS: + status = "ゲーム進行中"; + daysInfo = "プロローグ + " + progressDays + "日目"; + caption = "更新日時 : "; + break; + case EPILOGUE: + status = "エピローグ中"; + daysInfo = "プロローグ + " + progressDays + "日 + エピローグ中"; + caption = "エピローグ終了予想 : "; + break; + case GAMEOVER: + status = "ゲーム終了"; + daysInfo = "プロローグ + " + progressDays + "日 + エピローグ"; + caption = "エピローグ終了日時 : "; + break; + case UNKNOWN: + status = "不明"; + daysInfo = "不明"; + caption = "更新日時 : "; + break; + default: + assert false; + status = "???"; + daysInfo = "???"; + caption = "更新日時 : "; + break; + } + + int limitMonth = this.village.getLimitMonth(); + int limitDay = this.village.getLimitDay(); + int limitHour = this.village.getLimitHour(); + int limitMinute = this.village.getLimitMinute(); + + String limitDate = limitMonth + "月" + limitDay + "日"; + String limitTime = ""; + if(limitHour < 10) limitTime += "0"; + limitTime += limitHour + ":"; + if(limitMinute < 10) limitTime += "0"; + limitTime += limitMinute; + + this.landName .setText(land); + this.villageName.setText(vName); + this.villageID .setText(vID); + this.state .setText(status); + this.days .setText(daysInfo); + this.limit .setText(limitDate + " " + limitTime); + + this.limitCaption.setText(caption); + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/WebButton.java b/src/main/java/jp/sourceforge/jindolf/WebButton.java index 45beb66..27fbba7 100644 --- a/src/main/java/jp/sourceforge/jindolf/WebButton.java +++ b/src/main/java/jp/sourceforge/jindolf/WebButton.java @@ -1,165 +1,165 @@ -/* - * Web-browser invoke button - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; - -/** - * Webブラウザ起動ボタン。 - */ -@SuppressWarnings("serial") -public class WebButton extends JPanel implements ActionListener{ - - private static final String ACTION_SHOWWEB = "SHOWWEB"; - - private final JLabel caption; - private final JButton button; - private String webUrlText; - - /** - * コンストラクタ。 - */ - public WebButton(){ - super(); - - this.caption = new JLabel(); - this.button = new JButton("Web"); - - Monodizer.monodize(this.caption); - this.button.setIcon(GUIUtils.getWWWIcon()); - this.button.setMargin(new Insets(1, 1, 1, 1)); - this.button.setActionCommand(ACTION_SHOWWEB); - this.button.addActionListener(this); - this.button.setToolTipText("Webブラウザで表示"); - - design(); - - return; - } - - /** - * レイアウトの定義。 - */ - private void design(){ - setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - - GridBagLayout layout = new GridBagLayout(); - setLayout(layout); - - GridBagConstraints constraints = new GridBagConstraints(); - constraints.fill = GridBagConstraints.NONE; - constraints.gridwidth = GridBagConstraints.RELATIVE; - constraints.insets = new Insets(0, 0, 0, 10); - - add(this.caption, constraints); - add(this.button, constraints); - - return; - } - - /** - * {@inheritDoc} - * @param b {@inheritDoc} - */ - @Override - public void setEnabled(boolean b){ - super.setEnabled(b); - this.button.setEnabled(b); - return; - } - - /** - * キャプション文字列の変更。 - * 起動URLには影響しない。 - * @param seq キャプション文字列 - */ - public void setCaption(CharSequence seq){ - this.caption.setText(seq.toString()); - return; - } - - /** - * Webブラウザに表示させるURLを設定する。 - * キャプション文字列も更新される。 - * @param url URL - */ - public void setURL(URL url){ - setURLText(url.toString()); - return; - } - - /** - * Webブラウザに表示させるURIを設定する。 - * キャプション文字列も更新される。 - * @param uri URI - */ - public void setURI(URI uri){ - setURLText(uri.toString()); - return; - } - - /** - * Webブラウザに表示させるURL文字列を設定する。 - * キャプション文字列も更新される。 - * @param urlText URL文字列 - */ - public void setURLText(CharSequence urlText){ - String str = urlText.toString(); - - try{ - new URL(str); - setEnabled(true); - }catch(MalformedURLException e){ - setEnabled(false); - } - - this.webUrlText = str; - setCaption(this.webUrlText); - - return; - } - - /** - * WebブラウザにURLを表示させる。 - */ - public void showDialog(){ - Frame frame = - (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this); - WebIPCDialog.showDialog(frame, this.webUrlText); - return; - } - - /** - * ボタン押下イベントの受信。 - * @param event イベント - */ - public void actionPerformed(ActionEvent event){ - if(event.getSource() != this.button) return; - - String command = event.getActionCommand(); - if(command.equals(ACTION_SHOWWEB)){ - showDialog(); - } - - return; - } - -} +/* + * Web-browser invoke button + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/** + * Webブラウザ起動ボタン。 + */ +@SuppressWarnings("serial") +public class WebButton extends JPanel implements ActionListener{ + + private static final String ACTION_SHOWWEB = "SHOWWEB"; + + private final JLabel caption; + private final JButton button; + private String webUrlText; + + /** + * コンストラクタ。 + */ + public WebButton(){ + super(); + + this.caption = new JLabel(); + this.button = new JButton("Web"); + + Monodizer.monodize(this.caption); + this.button.setIcon(GUIUtils.getWWWIcon()); + this.button.setMargin(new Insets(1, 1, 1, 1)); + this.button.setActionCommand(ACTION_SHOWWEB); + this.button.addActionListener(this); + this.button.setToolTipText("Webブラウザで表示"); + + design(); + + return; + } + + /** + * レイアウトの定義。 + */ + private void design(){ + setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + GridBagLayout layout = new GridBagLayout(); + setLayout(layout); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.NONE; + constraints.gridwidth = GridBagConstraints.RELATIVE; + constraints.insets = new Insets(0, 0, 0, 10); + + add(this.caption, constraints); + add(this.button, constraints); + + return; + } + + /** + * {@inheritDoc} + * @param b {@inheritDoc} + */ + @Override + public void setEnabled(boolean b){ + super.setEnabled(b); + this.button.setEnabled(b); + return; + } + + /** + * キャプション文字列の変更。 + * 起動URLには影響しない。 + * @param seq キャプション文字列 + */ + public void setCaption(CharSequence seq){ + this.caption.setText(seq.toString()); + return; + } + + /** + * Webブラウザに表示させるURLを設定する。 + * キャプション文字列も更新される。 + * @param url URL + */ + public void setURL(URL url){ + setURLText(url.toString()); + return; + } + + /** + * Webブラウザに表示させるURIを設定する。 + * キャプション文字列も更新される。 + * @param uri URI + */ + public void setURI(URI uri){ + setURLText(uri.toString()); + return; + } + + /** + * Webブラウザに表示させるURL文字列を設定する。 + * キャプション文字列も更新される。 + * @param urlText URL文字列 + */ + public void setURLText(CharSequence urlText){ + String str = urlText.toString(); + + try{ + new URL(str); + setEnabled(true); + }catch(MalformedURLException e){ + setEnabled(false); + } + + this.webUrlText = str; + setCaption(this.webUrlText); + + return; + } + + /** + * WebブラウザにURLを表示させる。 + */ + public void showDialog(){ + Frame frame = + (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this); + WebIPCDialog.showDialog(frame, this.webUrlText); + return; + } + + /** + * ボタン押下イベントの受信。 + * @param event イベント + */ + public void actionPerformed(ActionEvent event){ + if(event.getSource() != this.button) return; + + String command = event.getActionCommand(); + if(command.equals(ACTION_SHOWWEB)){ + showDialog(); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/WebIPC.java b/src/main/java/jp/sourceforge/jindolf/WebIPC.java index 6f5a01e..fda6d26 100644 --- a/src/main/java/jp/sourceforge/jindolf/WebIPC.java +++ b/src/main/java/jp/sourceforge/jindolf/WebIPC.java @@ -1,347 +1,347 @@ -/* - * Inter Process Communication with Web browser - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.GraphicsEnvironment; -import java.awt.HeadlessException; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; - -/** - * Webブラウザとのプロセス間通信。 - * java.awt.Desktopの代用品。 - * JRE1.6でしか使えない。がしかし、JRE1.5でもコンパイル&ロード可能。 - * ※参照: java.awt.Desktop - */ -public final class WebIPC{ - - private static final Class DESKTOP_KLASS; - private static final Method METHOD_ISDESKTOPSUPPORTED; - private static final Method METHOD_GETDESKTOP; - private static final Method METHOD_ISSUPPORTED; - private static final Method METHOD_BROWSE; - - private static final Class DESKTOP_ACTION_KLASS; - private static final Enum BROWSE_ENUM; - - static{ - DESKTOP_KLASS = getClass("java.awt.Desktop"); - DESKTOP_ACTION_KLASS = getInnerClass(DESKTOP_KLASS, - "java.awt.Desktop.Action"); - - METHOD_ISDESKTOPSUPPORTED = getMethod("isDesktopSupported"); - METHOD_GETDESKTOP = getMethod("getDesktop"); - METHOD_ISSUPPORTED = getMethod("isSupported", - DESKTOP_ACTION_KLASS); - METHOD_BROWSE = getMethod("browse", - URI.class); - - BROWSE_ENUM = getEnumMember(DESKTOP_ACTION_KLASS, "BROWSE"); - } - - - private final Object desktop; - - - /** - * 見えないコンストラクタ。 - * @throws HeadlessException GUI環境が未接続 - * @throws UnsupportedOperationException 未サポート - */ - private WebIPC() - throws HeadlessException, - UnsupportedOperationException { - super(); - - try{ - this.desktop = METHOD_GETDESKTOP.invoke(null, (Object[]) null); - }catch(InvocationTargetException e){ - Throwable targetException = e.getTargetException(); - - if(targetException instanceof RuntimeException){ - throw (RuntimeException) targetException; - } - if(targetException instanceof Error){ - throw (Error) targetException; - } - - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - }catch(IllegalAccessException e){ - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - } - - return; - } - - - /** - * クラス名からClassインスタンスを探す。 - * @param klassName クラス名 - * @return Classインスタンス。クラスが見つからなければnull。 - */ - private static Class getClass(String klassName){ - Class result; - try{ - result = Class.forName(klassName); - }catch(ClassNotFoundException e){ - result = null; - } - return result; - } - - /** - * 内部クラス名からClassインスタンスを探す。 - * @param parent 囲む親クラス。 - * @param canonical 内部クラスのカノニカル名 - * @return Classインスタンス。クラスが見つからなければnull。 - */ - private static Class getInnerClass(Class parent, - String canonical){ - if(parent == null) return null; - - Class result = null; - - Class[] innerKlasses = parent.getClasses(); - for(Class klass : innerKlasses){ - if(klass.getCanonicalName().equals(canonical)){ - result = klass; - break; - } - } - - return result; - } - - /** - * Desktopクラスのメソッド名からMethodインスタンスを探す。 - * @param methodName メソッド名 - * @param paramTypes 引数型並び - * @return Methodインスタンス。見つからなければnull。 - */ - private static Method getMethod(String methodName, - Class... paramTypes){ - if(DESKTOP_KLASS == null) return null; - - Method result; - - try{ - result = DESKTOP_KLASS.getMethod(methodName, paramTypes); - }catch(NoSuchMethodException e){ - result = null; - } - - return result; - } - - /** - * Enumのメンバを探す。 - * @param parent Enumの型 - * @param memberName メンバ名 - * @return Enumインスタンス。見つからなければnull。 - */ - private static Enum getEnumMember(Class parent, - String memberName){ - if(parent == null) return null; - - Field field; - try{ - field = parent.getField(memberName); - }catch(NoSuchFieldException e){ - return null; - } - - Object value; - try{ - value = field.get(null); - }catch(IllegalAccessException e){ - return null; - }catch(IllegalArgumentException e){ - return null; - } - - if( ! (value instanceof Enum) ) return null; - - return (Enum) value; - } - - /** - * JRE1.6より提供されたjava.awt.Desktopが利用可能か判定する。 - * ※参照: java.awt.DesktopのisDesktopSupported() - * @return Desktopが利用可能ならtrue。JRE1.5以前だとたぶんfalse。 - */ - public static boolean isDesktopSupported(){ - if(METHOD_ISDESKTOPSUPPORTED == null) return false; - - Object invokeResult; - try{ - invokeResult = METHOD_ISDESKTOPSUPPORTED.invoke(null, - (Object[]) null); - }catch(InvocationTargetException e){ - Throwable targetException = e.getTargetException(); - - if(targetException instanceof RuntimeException){ - throw (RuntimeException) targetException; - }else if(targetException instanceof Error){ - throw (Error) targetException; - } - - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - }catch(IllegalAccessException e){ - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - } - - if( ! (invokeResult instanceof Boolean) ){ - assert false; - return false; - } - - boolean result = (Boolean) invokeResult; - - return result; - } - - /** - * WebIPCインスタンスを得る。 - * ※参照: java.awt.DesktopのgetDesktop() - * @return インスタンス - * @throws java.awt.HeadlessException スクリーンデバイスが見つからない - * @throws java.lang.UnsupportedOperationException 未サポートの機能 - */ - public static WebIPC getWebIPC() - throws HeadlessException, - UnsupportedOperationException { - if(GraphicsEnvironment.isHeadless()) throw new HeadlessException(); - if( ! isDesktopSupported() ){ - throw new UnsupportedOperationException(); - } - - WebIPC webIPC = new WebIPC(); - - return webIPC; - } - - /** - * Webブラウザに任意のURIを表示させる。 - * ※参照: java.awt.Desktopのbrowse(java.net.URI) - * @param uri URI - * @throws NullPointerException 引数がnull - * @throws UnsupportedOperationException 未サポートの機能 - * @throws IOException ブラウザが見つからない - * @throws SecurityException セキュリティ違反 - * @throws IllegalArgumentException URI形式が変 - */ - public void browse(URI uri) - throws NullPointerException, - UnsupportedOperationException, - IOException, - SecurityException, - IllegalArgumentException { - if(uri == null) throw new NullPointerException(); - if( ! isSupported(Action.BROWSE) ){ - throw new UnsupportedOperationException(); - } - - try{ - METHOD_BROWSE.invoke(this.desktop, uri); - }catch(InvocationTargetException e){ - Throwable targetException = e.getTargetException(); - - if(targetException instanceof IOException){ - throw (IOException) targetException; - } - if(targetException instanceof RuntimeException){ - throw (RuntimeException) targetException; - } - if(targetException instanceof Error){ - throw (Error) targetException; - } - - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - }catch(IllegalAccessException e){ - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - } - - return; - } - - /** - * 指定した機能(アクション)がサポートされているか否か判定する。 - * ※参照: java.awt.Desktop#isSupported(java.awt.Desktop.Action) - * @param action アクション - * @return アクションがサポートされていればtrue。 - */ - public boolean isSupported(Action action){ - switch(action){ - case BROWSE: - break; - default: - return false; - } - - Object invokeResult; - try{ - invokeResult = METHOD_ISSUPPORTED.invoke(this.desktop, - BROWSE_ENUM); - }catch(InvocationTargetException e){ - Throwable targetException = e.getTargetException(); - - if(targetException instanceof RuntimeException){ - throw (RuntimeException) targetException; - } - if(targetException instanceof Error){ - throw (Error) targetException; - } - - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - }catch(IllegalAccessException e){ - AssertionError thw = new AssertionError(); - thw.initCause(e); - throw thw; - } - - if( ! (invokeResult instanceof Boolean) ){ - assert false; - return false; - } - - boolean result = (Boolean) invokeResult; - - return result; - } - - /** - * 各種デスクトップアクション。 - * ※参照 java.awt.Desktop.Action - */ - public static enum Action{ - /** Webブラウザでのブラウズ。 */ - BROWSE, - // EDIT, - // MAIL, - // OPEN, - // PRINT, - } - -} +/* + * Inter Process Communication with Web browser + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; + +/** + * Webブラウザとのプロセス間通信。 + * java.awt.Desktopの代用品。 + * JRE1.6でしか使えない。がしかし、JRE1.5でもコンパイル&ロード可能。 + * ※参照: java.awt.Desktop + */ +public final class WebIPC{ + + private static final Class DESKTOP_KLASS; + private static final Method METHOD_ISDESKTOPSUPPORTED; + private static final Method METHOD_GETDESKTOP; + private static final Method METHOD_ISSUPPORTED; + private static final Method METHOD_BROWSE; + + private static final Class DESKTOP_ACTION_KLASS; + private static final Enum BROWSE_ENUM; + + static{ + DESKTOP_KLASS = getClass("java.awt.Desktop"); + DESKTOP_ACTION_KLASS = getInnerClass(DESKTOP_KLASS, + "java.awt.Desktop.Action"); + + METHOD_ISDESKTOPSUPPORTED = getMethod("isDesktopSupported"); + METHOD_GETDESKTOP = getMethod("getDesktop"); + METHOD_ISSUPPORTED = getMethod("isSupported", + DESKTOP_ACTION_KLASS); + METHOD_BROWSE = getMethod("browse", + URI.class); + + BROWSE_ENUM = getEnumMember(DESKTOP_ACTION_KLASS, "BROWSE"); + } + + + private final Object desktop; + + + /** + * 見えないコンストラクタ。 + * @throws HeadlessException GUI環境が未接続 + * @throws UnsupportedOperationException 未サポート + */ + private WebIPC() + throws HeadlessException, + UnsupportedOperationException { + super(); + + try{ + this.desktop = METHOD_GETDESKTOP.invoke(null, (Object[]) null); + }catch(InvocationTargetException e){ + Throwable targetException = e.getTargetException(); + + if(targetException instanceof RuntimeException){ + throw (RuntimeException) targetException; + } + if(targetException instanceof Error){ + throw (Error) targetException; + } + + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + }catch(IllegalAccessException e){ + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + } + + return; + } + + + /** + * クラス名からClassインスタンスを探す。 + * @param klassName クラス名 + * @return Classインスタンス。クラスが見つからなければnull。 + */ + private static Class getClass(String klassName){ + Class result; + try{ + result = Class.forName(klassName); + }catch(ClassNotFoundException e){ + result = null; + } + return result; + } + + /** + * 内部クラス名からClassインスタンスを探す。 + * @param parent 囲む親クラス。 + * @param canonical 内部クラスのカノニカル名 + * @return Classインスタンス。クラスが見つからなければnull。 + */ + private static Class getInnerClass(Class parent, + String canonical){ + if(parent == null) return null; + + Class result = null; + + Class[] innerKlasses = parent.getClasses(); + for(Class klass : innerKlasses){ + if(klass.getCanonicalName().equals(canonical)){ + result = klass; + break; + } + } + + return result; + } + + /** + * Desktopクラスのメソッド名からMethodインスタンスを探す。 + * @param methodName メソッド名 + * @param paramTypes 引数型並び + * @return Methodインスタンス。見つからなければnull。 + */ + private static Method getMethod(String methodName, + Class... paramTypes){ + if(DESKTOP_KLASS == null) return null; + + Method result; + + try{ + result = DESKTOP_KLASS.getMethod(methodName, paramTypes); + }catch(NoSuchMethodException e){ + result = null; + } + + return result; + } + + /** + * Enumのメンバを探す。 + * @param parent Enumの型 + * @param memberName メンバ名 + * @return Enumインスタンス。見つからなければnull。 + */ + private static Enum getEnumMember(Class parent, + String memberName){ + if(parent == null) return null; + + Field field; + try{ + field = parent.getField(memberName); + }catch(NoSuchFieldException e){ + return null; + } + + Object value; + try{ + value = field.get(null); + }catch(IllegalAccessException e){ + return null; + }catch(IllegalArgumentException e){ + return null; + } + + if( ! (value instanceof Enum) ) return null; + + return (Enum) value; + } + + /** + * JRE1.6より提供されたjava.awt.Desktopが利用可能か判定する。 + * ※参照: java.awt.DesktopのisDesktopSupported() + * @return Desktopが利用可能ならtrue。JRE1.5以前だとたぶんfalse。 + */ + public static boolean isDesktopSupported(){ + if(METHOD_ISDESKTOPSUPPORTED == null) return false; + + Object invokeResult; + try{ + invokeResult = METHOD_ISDESKTOPSUPPORTED.invoke(null, + (Object[]) null); + }catch(InvocationTargetException e){ + Throwable targetException = e.getTargetException(); + + if(targetException instanceof RuntimeException){ + throw (RuntimeException) targetException; + }else if(targetException instanceof Error){ + throw (Error) targetException; + } + + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + }catch(IllegalAccessException e){ + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + } + + if( ! (invokeResult instanceof Boolean) ){ + assert false; + return false; + } + + boolean result = (Boolean) invokeResult; + + return result; + } + + /** + * WebIPCインスタンスを得る。 + * ※参照: java.awt.DesktopのgetDesktop() + * @return インスタンス + * @throws java.awt.HeadlessException スクリーンデバイスが見つからない + * @throws java.lang.UnsupportedOperationException 未サポートの機能 + */ + public static WebIPC getWebIPC() + throws HeadlessException, + UnsupportedOperationException { + if(GraphicsEnvironment.isHeadless()) throw new HeadlessException(); + if( ! isDesktopSupported() ){ + throw new UnsupportedOperationException(); + } + + WebIPC webIPC = new WebIPC(); + + return webIPC; + } + + /** + * Webブラウザに任意のURIを表示させる。 + * ※参照: java.awt.Desktopのbrowse(java.net.URI) + * @param uri URI + * @throws NullPointerException 引数がnull + * @throws UnsupportedOperationException 未サポートの機能 + * @throws IOException ブラウザが見つからない + * @throws SecurityException セキュリティ違反 + * @throws IllegalArgumentException URI形式が変 + */ + public void browse(URI uri) + throws NullPointerException, + UnsupportedOperationException, + IOException, + SecurityException, + IllegalArgumentException { + if(uri == null) throw new NullPointerException(); + if( ! isSupported(Action.BROWSE) ){ + throw new UnsupportedOperationException(); + } + + try{ + METHOD_BROWSE.invoke(this.desktop, uri); + }catch(InvocationTargetException e){ + Throwable targetException = e.getTargetException(); + + if(targetException instanceof IOException){ + throw (IOException) targetException; + } + if(targetException instanceof RuntimeException){ + throw (RuntimeException) targetException; + } + if(targetException instanceof Error){ + throw (Error) targetException; + } + + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + }catch(IllegalAccessException e){ + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + } + + return; + } + + /** + * 指定した機能(アクション)がサポートされているか否か判定する。 + * ※参照: java.awt.Desktop#isSupported(java.awt.Desktop.Action) + * @param action アクション + * @return アクションがサポートされていればtrue。 + */ + public boolean isSupported(Action action){ + switch(action){ + case BROWSE: + break; + default: + return false; + } + + Object invokeResult; + try{ + invokeResult = METHOD_ISSUPPORTED.invoke(this.desktop, + BROWSE_ENUM); + }catch(InvocationTargetException e){ + Throwable targetException = e.getTargetException(); + + if(targetException instanceof RuntimeException){ + throw (RuntimeException) targetException; + } + if(targetException instanceof Error){ + throw (Error) targetException; + } + + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + }catch(IllegalAccessException e){ + AssertionError thw = new AssertionError(); + thw.initCause(e); + throw thw; + } + + if( ! (invokeResult instanceof Boolean) ){ + assert false; + return false; + } + + boolean result = (Boolean) invokeResult; + + return result; + } + + /** + * 各種デスクトップアクション。 + * ※参照 java.awt.Desktop.Action + */ + public static enum Action{ + /** Webブラウザでのブラウズ。 */ + BROWSE, + // EDIT, + // MAIL, + // OPEN, + // PRINT, + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/WebIPCDialog.java b/src/main/java/jp/sourceforge/jindolf/WebIPCDialog.java index 6f8b563..14ee499 100644 --- a/src/main/java/jp/sourceforge/jindolf/WebIPCDialog.java +++ b/src/main/java/jp/sourceforge/jindolf/WebIPCDialog.java @@ -1,466 +1,466 @@ -/* - * Dialog for WebIPC - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.awt.Container; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.datatransfer.Transferable; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextArea; -import javax.swing.SwingConstants; -import javax.swing.TransferHandler; -import javax.swing.border.Border; -import javax.swing.border.EtchedBorder; - -/** - * Webブラウザ起動用の専用ダイアログ。 - */ -@SuppressWarnings("serial") -public class WebIPCDialog - extends JDialog - implements ActionListener { - - private static final String FRAMETITLE = - "URLへのアクセス確認 - " + Jindolf.TITLE; - - - private final String warnMessage; - - private final JLabel info = - new JLabel("以下のURLへのアクセスが指示されました。"); - private final JTextArea urltext = - new JTextArea(""); - private final JButton browse = - new JButton("デフォルトのWebブラウザで表示"); - private final JButton clipcopy = - new JButton("URLをクリップボードにコピー"); - private final JLabel dndLabel = - new JLabel("…またはブラウザにDrag&Drop →"); - private final JButton cancel = - new JButton("閉じる"); - - private final WebIPC ipc; - - private URI uri; - - - /** - * コンストラクタ。 - * @param owner オーナーフレーム - */ - public WebIPCDialog(Frame owner){ - super(owner, FRAMETITLE, true); - - GUIUtils.modifyWindowAttributes(this, true, false, true); - - WebIPC webipc = null; - if(WebIPC.isDesktopSupported()){ - webipc = WebIPC.getWebIPC(); - if( ! webipc.isSupported(WebIPC.Action.BROWSE) ){ - webipc = null; - } - } - this.ipc = webipc; - - if(this.ipc == null){ - if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){ - this.warnMessage = - "この機能を利用するには、JRE1.6以上が必要です"; - }else{ - this.warnMessage = - "何らかの理由でこの機能は利用不可になっています"; - } - }else{ - this.warnMessage = ""; - } - - Border inside = - BorderFactory.createEmptyBorder(1, 4, 1, 4); - Border outside = - BorderFactory.createEtchedBorder(EtchedBorder.RAISED); - Border border = - BorderFactory.createCompoundBorder(outside, inside); - this.urltext.setBorder(border); - this.urltext.setEditable(false); - this.urltext.setLineWrap(true); - this.urltext.setComponentPopupMenu(new TextPopup()); - Monodizer.monodize(this.urltext); - - this.dndLabel.setIcon(GUIUtils.getWWWIcon()); - this.dndLabel.setHorizontalTextPosition(SwingConstants.LEFT); - this.dndLabel.setTransferHandler(new DnDHandler()); - this.dndLabel.addMouseListener(new DragIgniter()); - - Container container = getContentPane(); - design(container); - - this.browse .addActionListener(this); - this.clipcopy.addActionListener(this); - this.cancel .addActionListener(this); - - getRootPane().setDefaultButton(this.browse); - this.browse.requestFocusInWindow(); - - if(this.ipc == null){ - this.browse.setToolTipText(this.warnMessage); - } - - setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter(){ - @Override - public void windowClosing(WindowEvent event){ - actionCancel(); - return; - } - }); - - return; - } - - - /** - * Webブラウザ起動用のモーダルダイアログを表示する。 - * @param owner オーナーフレーム - * @param url URL文字列 - */ - public static void showDialog(Frame owner, String url){ - WebIPCDialog dialog = new WebIPCDialog(owner); - - dialog.setUrlText(url); - dialog.pack(); - dialog.setLocationRelativeTo(owner); - dialog.setVisible(true); - - return; - } - - /** - * 有効なURIか判定する。 - * @param uri URI - * @return 有効ならtrue - */ - private static boolean isValidURI(URI uri){ - if(uri == null) return false; - - if( ! uri.isAbsolute() ) return false; - - String scheme = uri.getScheme(); - if(scheme == null) return false; - if( ! scheme.equalsIgnoreCase("http") - && ! scheme.equalsIgnoreCase("https") ) return false; - - String host = uri.getHost(); - if(host == null) return false; - - return true; - } - - /** - * レイアウトを行う。 - * @param container レイアウトコンテナ - */ - private void design(Container container){ - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - container.setLayout(layout); - - JComponent buttonPanel = buildButtonPanel(); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(5, 5, 5, 5); - - container.add(this.info, constraints); - container.add(this.urltext, constraints); - container.add(buttonPanel, constraints); - container.add(this.cancel, constraints); - - return; - } - - /** - * ボタンパネルを生成する。 - * @return ボタンパネル - */ - private JComponent buildButtonPanel(){ - JPanel buttonPanel = new JPanel(); - - Border border = BorderFactory.createTitledBorder( - "アクセスする方法を選択してください。" - ); - buttonPanel.setBorder(border); - - GridBagLayout layout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - buttonPanel.setLayout(layout); - - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(3, 3, 3, 3); - buttonPanel.add(this.browse, constraints); - buttonPanel.add(this.clipcopy, constraints); - - constraints.fill = GridBagConstraints.NONE; - constraints.anchor = GridBagConstraints.EAST; - constraints.insets = new Insets(10, 3, 10, 3); - buttonPanel.add(this.dndLabel, constraints); - - return buttonPanel; - } - - /** - * URL文字列を設定する。 - * @param url URL文字列 - */ - public void setUrlText(String url){ - URI uriarg = null; - try{ - uriarg = new URI(url); - }catch(URISyntaxException e){ - // NOTHING - } - - this.uri = uriarg; - if(this.uri == null) return; - - if( ! isValidURI(this.uri) ) return; - - String uriText = this.uri.toASCIIString(); - this.urltext.setText(uriText); - - this.urltext.revalidate(); - pack(); - - return; - } - - /** - * ボタン押下リスナ。 - * @param event ボタン押下イベント - */ - public void actionPerformed(ActionEvent event){ - Object source = event.getSource(); - if(source == this.browse){ - actionBrowse(); - }else if(source == this.clipcopy){ - actionClipboardCopy(); - }else if(source == this.cancel){ - actionCancel(); - } - return; - } - - /** - * WebブラウザでURLを表示。 - */ - private void actionBrowse(){ - if(this.uri == null){ - close(); - return; - } - - if(this.ipc == null){ - String title; - if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){ - title = "新しいJavaを入手しましょう"; - }else{ - title = "報告"; - } - JOptionPane.showMessageDialog( - this, - this.warnMessage, title, - JOptionPane.INFORMATION_MESSAGE); - return; - } - - try{ - try{ - this.ipc.browse(this.uri); - }catch(NullPointerException e){ - assert false; - }catch(UnsupportedOperationException e){ - // NOTHING - }catch(IOException e){ - // NOTHING - }catch(SecurityException e){ - // NOTHING - }catch(IllegalArgumentException e){ - // NOTHING - } - String logmsg = "URL " - + this.uri.toASCIIString() - + " へのアクセスをWebブラウザに指示しました"; - Jindolf.logger().info(logmsg); - }finally{ - close(); - } - - return; - } - - /** - * URLをクリップボードにコピーする。 - */ - private void actionClipboardCopy(){ - if(this.uri == null){ - close(); - return; - } - - String uristring = this.uri.toASCIIString(); - - try{ - ClipboardAction.copyToClipboard(uristring); - String logmsg = "文字列「" - + uristring - + "」をクリップボードにコピーしました"; - Jindolf.logger().info(logmsg); - }finally{ - close(); - } - - return; - } - - /** - * 何もせずダイアログを閉じる。 - */ - private void actionCancel(){ - close(); - return; - } - - /** - * ダイアログを閉じる。 - */ - private void close(){ - setVisible(false); - return; - } - - /** - * Drag&Dropの転送処理を管理。 - */ - private class DnDHandler extends TransferHandler{ - - /** - * コンストラクタ。 - */ - public DnDHandler(){ - super(); - return; - } - - /** - * {@inheritDoc} - * コピー動作のみをサポートすることを伝える。 - * @param comp {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int getSourceActions(JComponent comp){ - return 0 | COPY; - } - - /** - * {@inheritDoc} - * URIエクスポータを生成する。 - * URIも指定される。 - * @param comp {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - protected Transferable createTransferable(JComponent comp){ - UriExporter result = new UriExporter(WebIPCDialog.this.uri); - return result; - } - - /** - * {@inheritDoc} - * D&Dに成功したらダイアログを閉じる。 - * @param source {@inheritDoc} - * @param data {@inheritDoc} - * @param action {@inheritDoc} - */ - @Override - protected void exportDone(JComponent source, - Transferable data, - int action ){ - if(action == NONE) return; - - String logmsg = "URL " - + WebIPCDialog.this.uri.toASCIIString() - + " がどこかへドラッグ&ドロップされました"; - Jindolf.logger().info(logmsg); - - close(); - - return; - } - - /** - * {@inheritDoc} - * ※ SunのJRE1.6.0_11前後では、BugID 4816922のため決して呼ばれない。 - * @param tx {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Icon getVisualRepresentation(Transferable tx){ - return GUIUtils.getWWWIcon(); - } - - } - - /** - * ドラッグ開始イベント処理。 - */ - private static class DragIgniter extends MouseAdapter{ - - /** - * コンストラクタ。 - */ - public DragIgniter(){ - super(); - return; - } - - /** - * {@inheritDoc} - * ドラッグ開始イベント受信。 - * @param event {@inheritDoc} - */ - @Override - public void mousePressed(MouseEvent event){ - JComponent comp = (JComponent)event.getSource(); - TransferHandler handler = comp.getTransferHandler(); - handler.exportAsDrag(comp, event, TransferHandler.COPY); - return; - } - - } - -} +/* + * Dialog for WebIPC + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.datatransfer.Transferable; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.SwingConstants; +import javax.swing.TransferHandler; +import javax.swing.border.Border; +import javax.swing.border.EtchedBorder; + +/** + * Webブラウザ起動用の専用ダイアログ。 + */ +@SuppressWarnings("serial") +public class WebIPCDialog + extends JDialog + implements ActionListener { + + private static final String FRAMETITLE = + "URLへのアクセス確認 - " + Jindolf.TITLE; + + + private final String warnMessage; + + private final JLabel info = + new JLabel("以下のURLへのアクセスが指示されました。"); + private final JTextArea urltext = + new JTextArea(""); + private final JButton browse = + new JButton("デフォルトのWebブラウザで表示"); + private final JButton clipcopy = + new JButton("URLをクリップボードにコピー"); + private final JLabel dndLabel = + new JLabel("…またはブラウザにDrag&Drop →"); + private final JButton cancel = + new JButton("閉じる"); + + private final WebIPC ipc; + + private URI uri; + + + /** + * コンストラクタ。 + * @param owner オーナーフレーム + */ + public WebIPCDialog(Frame owner){ + super(owner, FRAMETITLE, true); + + GUIUtils.modifyWindowAttributes(this, true, false, true); + + WebIPC webipc = null; + if(WebIPC.isDesktopSupported()){ + webipc = WebIPC.getWebIPC(); + if( ! webipc.isSupported(WebIPC.Action.BROWSE) ){ + webipc = null; + } + } + this.ipc = webipc; + + if(this.ipc == null){ + if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){ + this.warnMessage = + "この機能を利用するには、JRE1.6以上が必要です"; + }else{ + this.warnMessage = + "何らかの理由でこの機能は利用不可になっています"; + } + }else{ + this.warnMessage = ""; + } + + Border inside = + BorderFactory.createEmptyBorder(1, 4, 1, 4); + Border outside = + BorderFactory.createEtchedBorder(EtchedBorder.RAISED); + Border border = + BorderFactory.createCompoundBorder(outside, inside); + this.urltext.setBorder(border); + this.urltext.setEditable(false); + this.urltext.setLineWrap(true); + this.urltext.setComponentPopupMenu(new TextPopup()); + Monodizer.monodize(this.urltext); + + this.dndLabel.setIcon(GUIUtils.getWWWIcon()); + this.dndLabel.setHorizontalTextPosition(SwingConstants.LEFT); + this.dndLabel.setTransferHandler(new DnDHandler()); + this.dndLabel.addMouseListener(new DragIgniter()); + + Container container = getContentPane(); + design(container); + + this.browse .addActionListener(this); + this.clipcopy.addActionListener(this); + this.cancel .addActionListener(this); + + getRootPane().setDefaultButton(this.browse); + this.browse.requestFocusInWindow(); + + if(this.ipc == null){ + this.browse.setToolTipText(this.warnMessage); + } + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter(){ + @Override + public void windowClosing(WindowEvent event){ + actionCancel(); + return; + } + }); + + return; + } + + + /** + * Webブラウザ起動用のモーダルダイアログを表示する。 + * @param owner オーナーフレーム + * @param url URL文字列 + */ + public static void showDialog(Frame owner, String url){ + WebIPCDialog dialog = new WebIPCDialog(owner); + + dialog.setUrlText(url); + dialog.pack(); + dialog.setLocationRelativeTo(owner); + dialog.setVisible(true); + + return; + } + + /** + * 有効なURIか判定する。 + * @param uri URI + * @return 有効ならtrue + */ + private static boolean isValidURI(URI uri){ + if(uri == null) return false; + + if( ! uri.isAbsolute() ) return false; + + String scheme = uri.getScheme(); + if(scheme == null) return false; + if( ! scheme.equalsIgnoreCase("http") + && ! scheme.equalsIgnoreCase("https") ) return false; + + String host = uri.getHost(); + if(host == null) return false; + + return true; + } + + /** + * レイアウトを行う。 + * @param container レイアウトコンテナ + */ + private void design(Container container){ + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + container.setLayout(layout); + + JComponent buttonPanel = buildButtonPanel(); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(5, 5, 5, 5); + + container.add(this.info, constraints); + container.add(this.urltext, constraints); + container.add(buttonPanel, constraints); + container.add(this.cancel, constraints); + + return; + } + + /** + * ボタンパネルを生成する。 + * @return ボタンパネル + */ + private JComponent buildButtonPanel(){ + JPanel buttonPanel = new JPanel(); + + Border border = BorderFactory.createTitledBorder( + "アクセスする方法を選択してください。" + ); + buttonPanel.setBorder(border); + + GridBagLayout layout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + buttonPanel.setLayout(layout); + + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(3, 3, 3, 3); + buttonPanel.add(this.browse, constraints); + buttonPanel.add(this.clipcopy, constraints); + + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.EAST; + constraints.insets = new Insets(10, 3, 10, 3); + buttonPanel.add(this.dndLabel, constraints); + + return buttonPanel; + } + + /** + * URL文字列を設定する。 + * @param url URL文字列 + */ + public void setUrlText(String url){ + URI uriarg = null; + try{ + uriarg = new URI(url); + }catch(URISyntaxException e){ + // NOTHING + } + + this.uri = uriarg; + if(this.uri == null) return; + + if( ! isValidURI(this.uri) ) return; + + String uriText = this.uri.toASCIIString(); + this.urltext.setText(uriText); + + this.urltext.revalidate(); + pack(); + + return; + } + + /** + * ボタン押下リスナ。 + * @param event ボタン押下イベント + */ + public void actionPerformed(ActionEvent event){ + Object source = event.getSource(); + if(source == this.browse){ + actionBrowse(); + }else if(source == this.clipcopy){ + actionClipboardCopy(); + }else if(source == this.cancel){ + actionCancel(); + } + return; + } + + /** + * WebブラウザでURLを表示。 + */ + private void actionBrowse(){ + if(this.uri == null){ + close(); + return; + } + + if(this.ipc == null){ + String title; + if( ! Jindolf.JRE_PACKAGE.isCompatibleWith("1.6") ){ + title = "新しいJavaを入手しましょう"; + }else{ + title = "報告"; + } + JOptionPane.showMessageDialog( + this, + this.warnMessage, title, + JOptionPane.INFORMATION_MESSAGE); + return; + } + + try{ + try{ + this.ipc.browse(this.uri); + }catch(NullPointerException e){ + assert false; + }catch(UnsupportedOperationException e){ + // NOTHING + }catch(IOException e){ + // NOTHING + }catch(SecurityException e){ + // NOTHING + }catch(IllegalArgumentException e){ + // NOTHING + } + String logmsg = "URL " + + this.uri.toASCIIString() + + " へのアクセスをWebブラウザに指示しました"; + Jindolf.logger().info(logmsg); + }finally{ + close(); + } + + return; + } + + /** + * URLをクリップボードにコピーする。 + */ + private void actionClipboardCopy(){ + if(this.uri == null){ + close(); + return; + } + + String uristring = this.uri.toASCIIString(); + + try{ + ClipboardAction.copyToClipboard(uristring); + String logmsg = "文字列「" + + uristring + + "」をクリップボードにコピーしました"; + Jindolf.logger().info(logmsg); + }finally{ + close(); + } + + return; + } + + /** + * 何もせずダイアログを閉じる。 + */ + private void actionCancel(){ + close(); + return; + } + + /** + * ダイアログを閉じる。 + */ + private void close(){ + setVisible(false); + return; + } + + /** + * Drag&Dropの転送処理を管理。 + */ + private class DnDHandler extends TransferHandler{ + + /** + * コンストラクタ。 + */ + public DnDHandler(){ + super(); + return; + } + + /** + * {@inheritDoc} + * コピー動作のみをサポートすることを伝える。 + * @param comp {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int getSourceActions(JComponent comp){ + return 0 | COPY; + } + + /** + * {@inheritDoc} + * URIエクスポータを生成する。 + * URIも指定される。 + * @param comp {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + protected Transferable createTransferable(JComponent comp){ + UriExporter result = new UriExporter(WebIPCDialog.this.uri); + return result; + } + + /** + * {@inheritDoc} + * D&Dに成功したらダイアログを閉じる。 + * @param source {@inheritDoc} + * @param data {@inheritDoc} + * @param action {@inheritDoc} + */ + @Override + protected void exportDone(JComponent source, + Transferable data, + int action ){ + if(action == NONE) return; + + String logmsg = "URL " + + WebIPCDialog.this.uri.toASCIIString() + + " がどこかへドラッグ&ドロップされました"; + Jindolf.logger().info(logmsg); + + close(); + + return; + } + + /** + * {@inheritDoc} + * ※ SunのJRE1.6.0_11前後では、BugID 4816922のため決して呼ばれない。 + * @param tx {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Icon getVisualRepresentation(Transferable tx){ + return GUIUtils.getWWWIcon(); + } + + } + + /** + * ドラッグ開始イベント処理。 + */ + private static class DragIgniter extends MouseAdapter{ + + /** + * コンストラクタ。 + */ + public DragIgniter(){ + super(); + return; + } + + /** + * {@inheritDoc} + * ドラッグ開始イベント受信。 + * @param event {@inheritDoc} + */ + @Override + public void mousePressed(MouseEvent event){ + JComponent comp = (JComponent)event.getSource(); + TransferHandler handler = comp.getTransferHandler(); + handler.exportAsDrag(comp, event, TransferHandler.COPY); + return; + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/WolfBBS.java b/src/main/java/jp/sourceforge/jindolf/WolfBBS.java index f113b12..5088bfa 100644 --- a/src/main/java/jp/sourceforge/jindolf/WolfBBS.java +++ b/src/main/java/jp/sourceforge/jindolf/WolfBBS.java @@ -1,518 +1,518 @@ -/* - * WolfBBS - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import jp.sourceforge.jindolf.corelib.Destiny; -import jp.sourceforge.jindolf.corelib.GameRole; - -/** - * まちゅ氏運営のまとめサイト(wolfbbs)に関する諸々。 - * PukiWikiベース。 - * @see まとめサイト - * @see PukiWiki - */ -public final class WolfBBS{ - - /** PukiWikiコメント行。 */ - public static final String COMMENTLINE; - - private static final String WIKICHAR = "#&[]()<>+-*:|~/,'%?"; - private static final Pattern WIKINAME_PATTERN = - Pattern.compile("[A-Z][a-z]+([A-Z])[a-z]+"); - - private static final String FACEICONSET = - "resources/faceIconSet.properties"; - private static final String ORDER_PREFIX = "iconset.order."; - private static final List FACEICONSET_LIST = - new LinkedList(); - - private static final Charset CHARSET_EUC = Charset.forName("EUC-JP"); - - private static final String WOLFBBS_URL = "http://wolfbbs.jp/"; - - static{ - loadFaceIconSet(); - - StringBuilder wikicomment = new StringBuilder(); - wikicomment.append("// "); - while(wikicomment.length() < 72){ - wikicomment.append('='); - } - wikicomment.append('\n'); - COMMENTLINE = wikicomment.toString(); - } - - - /** - * 隠しコンストラクタ。 - */ - private WolfBBS(){ - assert false; - throw new AssertionError(); - } - - - /** - * アイコンセットのロード。 - */ - private static void loadFaceIconSet(){ - InputStream is = Jindolf.getResourceAsStream(FACEICONSET); - Properties properties = new Properties(); - try{ - properties.load(is); - is.close(); - }catch(IOException e){ - Jindolf.logger().fatal( - "顔アイコンセットの読み込みに失敗しました", e); - Jindolf.exit(1); - } - - loadFaceIconSet(properties); - - return; - } - - /** - * アイコンセットのロード。 - * @param properties プロパティ - */ - private static void loadFaceIconSet(Properties properties){ - String codeCheck = properties.getProperty("codeCheck"); - if( codeCheck == null - || codeCheck.length() != 1 - || codeCheck.charAt(0) != '\u72fc'){ // 「狼」 - Jindolf.logger().fatal( - "顔アイコンセットプロパティファイルの" - +"文字コードがおかしいようです。" - +"native2ascii は正しく適用しましたか?"); - Jindolf.exit(1); - return; - } - - Set keySet = properties.keySet(); - - SortedSet orderSet = new TreeSet(); - for(Object keyObj : keySet){ - if(keyObj == null) continue; - String key = keyObj.toString(); - if( ! key.startsWith(ORDER_PREFIX) ) continue; - key = key.replace(ORDER_PREFIX, ""); - Integer order; - try{ - order = Integer.valueOf(key); - }catch(NumberFormatException e){ - continue; - } - orderSet.add(order); - } - - for(Integer orderNum : orderSet){ - String setName = properties.getProperty(ORDER_PREFIX + orderNum); - FaceIconSet iconSet = loadFaceIconSet(properties, setName); - FACEICONSET_LIST.add(iconSet); - } - - return; - } - - /** - * アイコンセットのロード。 - * @param properties プロパティ - * @param setName アイコンセット名 - * @return アイコンセット - */ - private static FaceIconSet loadFaceIconSet(Properties properties, - String setName){ - String author = properties.getProperty(setName + ".author"); - String caption = properties.getProperty(setName + ".caption"); - String urlText = properties.getProperty(setName + ".url"); - - FaceIconSet iconSet = new FaceIconSet(caption, author, urlText); - - List avatarList = Avatar.getPredefinedAvatarList(); - for(Avatar avatar : avatarList){ - String identifier = avatar.getIdentifier(); - String key = setName + ".iconWiki." + identifier; - String wiki = properties.getProperty(key); - iconSet.registIconWiki(avatar, wiki); - } - - return iconSet; - } - - /** - * 顔アイコンセットのリストを取得する。 - * @return 顔アイコンセットのリスト - */ - public static List getFaceIconSetList(){ - List result = - Collections.unmodifiableList(FACEICONSET_LIST); - return result; - } - - /** - * 任意の文字がWikiの特殊キャラクタか否か判定する。 - * @param ch 文字 - * @return 特殊キャラクタならtrue - */ - public static boolean isWikiChar(char ch){ - if(WIKICHAR.indexOf(ch) < 0) return false; - return true; - } - - /** - * Wiki特殊文字を数値参照文字でエスケープする。 - * @param seq Wiki特殊文字を含むかもしれない文字列。 - * @return エスケープされた文字列 - */ - public static CharSequence escapeWikiChar(CharSequence seq){ - StringBuilder result = new StringBuilder(); - - int seqLength = seq.length(); - for(int pos = 0; pos < seqLength; pos++){ - char ch = seq.charAt(pos); - if(isWikiChar(ch)){ - try{ - appendNumCharRef(result, ch); - }catch(IOException e){ - assert false; - return null; - } - }else{ - result.append(ch); - } - } - - return result; - } - - /** - * WikiNameを数値参照文字でエスケープする。 - * @param seq WikiNameを含むかもしれない文字列 - * @return エスケープされた文字列。 - */ - public static CharSequence escapeWikiName(CharSequence seq){ - StringBuilder result = null; - Matcher matcher = WIKINAME_PATTERN.matcher(seq); - - int pos = 0; - while(matcher.find(pos)){ - int matchStart = matcher.start(); - int matchEnd = matcher.end(); - int capStart = matcher.start(1); - int capEnd = matcher.end(1); - - if(result == null) result = new StringBuilder(); - result.append(seq, pos, matchStart); - result.append(seq, matchStart, capStart); - try{ - appendNumCharRef(result, seq.charAt(capStart)); - }catch(IOException e){ - assert false; - return null; - } - result.append(seq, capEnd, matchEnd); - - pos = matchEnd; - } - - if(pos == 0) return seq; - - result.append(seq, pos, seq.length()); - - return result; - } - - /** - * 任意の文字列をWiki表記へ変換する。 - * @param seq 任意の文字列 - * @return Wiki用表記 - */ - public static CharSequence escapeWikiSyntax(CharSequence seq){ - CharSequence result = seq; - result = escapeWikiChar(result); // この順番は大事 - result = escapeWikiName(result); - // TODO さらにURLとメールアドレスのエスケープも - return result; - } - - /** - * ブラケットに入れる文字をエスケープする。 - * @param seq 文字列。 - * @return エスケープされた文字列 - */ - public static CharSequence escapeWikiBracket(CharSequence seq){ - StringBuilder result = new StringBuilder(); - - int seqLength = seq.length(); - for(int pos = 0; pos < seqLength; pos++){ - char ch = seq.charAt(pos); - - switch(ch){ - case '#': ch = '#'; break; - case '&': ch = '&'; break; - case '[': ch = '['; break; - case ']': ch = ']'; break; - case '<': ch = '<'; break; - case '>': ch = '>'; break; - default: break; - } - - result.append(ch); - } - - int resultLength; - - resultLength = result.length(); - while(result.length() > 0 && result.charAt(0) == '/'){ - result.deleteCharAt(0); - } - - resultLength = result.length(); - for(int pos = resultLength - 1; pos >= 0; pos--){ - char ch = result.charAt(pos); - if(ch != '/') break; - result.deleteCharAt(pos); - } - - resultLength = result.length(); - for(int pos = 1; pos < resultLength - 1; pos++){ - char ch = result.charAt(pos); - if(ch == ':'){ - result.setCharAt(pos, ':'); - } - } - - resultLength = result.length(); - if(resultLength == 1 && result.charAt(0) == ':'){ - result.setCharAt(0, ':'); - } - - return result; - } - - /** - * 数値参照文字に変換された文字を追加する。 - * 例)'D' => "D" - * @param app 追加対象 - * @param ch 1文字 - * @return 引数と同じ - * @throws java.io.IOException 入出力エラー。文字列の場合はありえない。 - */ - public static Appendable appendNumCharRef(Appendable app, char ch) - throws IOException{ - app.append("&#x"); - - int ival = ch; - String hex = Integer.toHexString(ival); - app.append(hex); - - app.append(';'); - - return app; - } - - /** - * 任意の文字を数値参照文字列に変換する。 - * 例)'D' => "D" - * @param ch 文字 - * @return 変換後の文字列 - */ - public static CharSequence toNumCharRef(char ch){ - StringBuilder result = new StringBuilder(8); - try{ - appendNumCharRef(result, ch); - }catch(IOException e){ - assert false; - return null; - } - return result; - } - - /** - * 陣営の色Wiki表記を返す。 - * @param role 役職 - * @return 色Wiki表記 - */ - public static String getTeamWikiColor(GameRole role){ - String result; - - switch(role){ - case INNOCENT: - case SEER: - case SHAMAN: - case HUNTER: - case FRATER: - result = "#b7bad3"; - break; - case WOLF: - case MADMAN: - result = "#e0b8b8"; - break; - case HAMSTER: - result = "#b9d0be"; - break; - default: - assert false; - return null; - } - - return result; - } - - /** - * 各役職のアイコンWikiを返す。 - * @param role 役職 - * @return アイコンWiki - */ - public static String getRoleIconWiki(GameRole role){ - String result; - - switch(role){ - case INNOCENT: - result = "&char(村人,nolink);"; - break; - case WOLF: - result = "&char(人狼,nolink);"; - break; - case SEER: - result = "&char(占い師,nolink);"; - break; - case SHAMAN: - result = "&char(霊能者,nolink);"; - break; - case MADMAN: - result = "&char(狂人,nolink);"; - break; - case HUNTER: - result = "&char(狩人,nolink);"; - break; - case FRATER: - result = "&char(共有者,nolink);"; - break; - case HAMSTER: - result = "&char(ハムスター人間,nolink);"; - break; - default: - assert false; - result = ""; - break; - } - - return result; - } - - /** - * 運命に対応する色Wiki表記を返す。 - * @param destiny 運命 - * @return 色Wiki表記 - */ - public static String getDestinyColorWiki(Destiny destiny){ - String result; - if(destiny == Destiny.ALIVE) result = "#ffffff"; - else result = "#aaaaaa"; - return result; - } - - /** - * そのまままとめサイトパス名に使えそうなシンプルな文字か判定する。 - * @param ch 文字 - * @return まとめサイトパス名に使えそうならtrue - */ - private static boolean isSimpleIdToken(char ch){ - if('0' <= ch && ch <= '9') return true; - if('A' <= ch && ch <= 'Z') return true; - if('a' <= ch && ch <= 'z') return true; - if(ch == '-' || ch == '_') return true; - return false; - } - - /** - * プレイヤーIDを構成する文字からパス名を組み立てる。 - * @param seq パス名 - * @param ch 文字 - * @return 引数と同じもの - */ - private static StringBuilder encodeId(StringBuilder seq, char ch){ - if(isSimpleIdToken(ch)){ - seq.append(ch); - return seq; - } - - CharBuffer cbuf = CharBuffer.allocate(1); - cbuf.append(ch); - cbuf.rewind(); - - CharsetEncoder encoder = CHARSET_EUC.newEncoder(); - ByteBuffer bytebuf; - try{ - bytebuf = encoder.encode(cbuf); - }catch(CharacterCodingException e){ - seq.append('X'); - return seq; - } - - int limit = bytebuf.limit(); - while(bytebuf.position() < limit){ - int iVal = bytebuf.get(); - if(iVal < 0) iVal += 0x0100; - String hex = Integer.toHexString(iVal).toUpperCase(Locale.JAPAN); - seq.append('%'); - if(hex.length() < 2) seq.append('0'); - seq.append(hex); - } - - return seq; - } - - /** - * プレイヤーIDからパス名の一部を予測する。 - * @param id プレイヤーID - * @return .htmlを抜いたパス名 - */ - private static StringBuilder encodeId(CharSequence id){ - StringBuilder result = new StringBuilder(); - int length = id.length(); - for(int pt = 0; pt < length; pt++){ - char ch = id.charAt(pt); - encodeId(result, ch); - } - return result; - } - - /** - * プレイヤーIDからまとめサイト上の個人ページを推測する。 - * @param id プレイヤーID - * @return 個人ページURL文字列 - */ - public static String encodeURLFromId(CharSequence id){ - CharSequence encodedId = encodeId(id); - - String result = WOLFBBS_URL + encodedId + ".html"; - - return result; - } - -} +/* + * WolfBBS + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import jp.sourceforge.jindolf.corelib.Destiny; +import jp.sourceforge.jindolf.corelib.GameRole; + +/** + * まちゅ氏運営のまとめサイト(wolfbbs)に関する諸々。 + * PukiWikiベース。 + * @see まとめサイト + * @see PukiWiki + */ +public final class WolfBBS{ + + /** PukiWikiコメント行。 */ + public static final String COMMENTLINE; + + private static final String WIKICHAR = "#&[]()<>+-*:|~/,'%?"; + private static final Pattern WIKINAME_PATTERN = + Pattern.compile("[A-Z][a-z]+([A-Z])[a-z]+"); + + private static final String FACEICONSET = + "resources/faceIconSet.properties"; + private static final String ORDER_PREFIX = "iconset.order."; + private static final List FACEICONSET_LIST = + new LinkedList(); + + private static final Charset CHARSET_EUC = Charset.forName("EUC-JP"); + + private static final String WOLFBBS_URL = "http://wolfbbs.jp/"; + + static{ + loadFaceIconSet(); + + StringBuilder wikicomment = new StringBuilder(); + wikicomment.append("// "); + while(wikicomment.length() < 72){ + wikicomment.append('='); + } + wikicomment.append('\n'); + COMMENTLINE = wikicomment.toString(); + } + + + /** + * 隠しコンストラクタ。 + */ + private WolfBBS(){ + assert false; + throw new AssertionError(); + } + + + /** + * アイコンセットのロード。 + */ + private static void loadFaceIconSet(){ + InputStream is = Jindolf.getResourceAsStream(FACEICONSET); + Properties properties = new Properties(); + try{ + properties.load(is); + is.close(); + }catch(IOException e){ + Jindolf.logger().fatal( + "顔アイコンセットの読み込みに失敗しました", e); + Jindolf.exit(1); + } + + loadFaceIconSet(properties); + + return; + } + + /** + * アイコンセットのロード。 + * @param properties プロパティ + */ + private static void loadFaceIconSet(Properties properties){ + String codeCheck = properties.getProperty("codeCheck"); + if( codeCheck == null + || codeCheck.length() != 1 + || codeCheck.charAt(0) != '\u72fc'){ // 「狼」 + Jindolf.logger().fatal( + "顔アイコンセットプロパティファイルの" + +"文字コードがおかしいようです。" + +"native2ascii は正しく適用しましたか?"); + Jindolf.exit(1); + return; + } + + Set keySet = properties.keySet(); + + SortedSet orderSet = new TreeSet(); + for(Object keyObj : keySet){ + if(keyObj == null) continue; + String key = keyObj.toString(); + if( ! key.startsWith(ORDER_PREFIX) ) continue; + key = key.replace(ORDER_PREFIX, ""); + Integer order; + try{ + order = Integer.valueOf(key); + }catch(NumberFormatException e){ + continue; + } + orderSet.add(order); + } + + for(Integer orderNum : orderSet){ + String setName = properties.getProperty(ORDER_PREFIX + orderNum); + FaceIconSet iconSet = loadFaceIconSet(properties, setName); + FACEICONSET_LIST.add(iconSet); + } + + return; + } + + /** + * アイコンセットのロード。 + * @param properties プロパティ + * @param setName アイコンセット名 + * @return アイコンセット + */ + private static FaceIconSet loadFaceIconSet(Properties properties, + String setName){ + String author = properties.getProperty(setName + ".author"); + String caption = properties.getProperty(setName + ".caption"); + String urlText = properties.getProperty(setName + ".url"); + + FaceIconSet iconSet = new FaceIconSet(caption, author, urlText); + + List avatarList = Avatar.getPredefinedAvatarList(); + for(Avatar avatar : avatarList){ + String identifier = avatar.getIdentifier(); + String key = setName + ".iconWiki." + identifier; + String wiki = properties.getProperty(key); + iconSet.registIconWiki(avatar, wiki); + } + + return iconSet; + } + + /** + * 顔アイコンセットのリストを取得する。 + * @return 顔アイコンセットのリスト + */ + public static List getFaceIconSetList(){ + List result = + Collections.unmodifiableList(FACEICONSET_LIST); + return result; + } + + /** + * 任意の文字がWikiの特殊キャラクタか否か判定する。 + * @param ch 文字 + * @return 特殊キャラクタならtrue + */ + public static boolean isWikiChar(char ch){ + if(WIKICHAR.indexOf(ch) < 0) return false; + return true; + } + + /** + * Wiki特殊文字を数値参照文字でエスケープする。 + * @param seq Wiki特殊文字を含むかもしれない文字列。 + * @return エスケープされた文字列 + */ + public static CharSequence escapeWikiChar(CharSequence seq){ + StringBuilder result = new StringBuilder(); + + int seqLength = seq.length(); + for(int pos = 0; pos < seqLength; pos++){ + char ch = seq.charAt(pos); + if(isWikiChar(ch)){ + try{ + appendNumCharRef(result, ch); + }catch(IOException e){ + assert false; + return null; + } + }else{ + result.append(ch); + } + } + + return result; + } + + /** + * WikiNameを数値参照文字でエスケープする。 + * @param seq WikiNameを含むかもしれない文字列 + * @return エスケープされた文字列。 + */ + public static CharSequence escapeWikiName(CharSequence seq){ + StringBuilder result = null; + Matcher matcher = WIKINAME_PATTERN.matcher(seq); + + int pos = 0; + while(matcher.find(pos)){ + int matchStart = matcher.start(); + int matchEnd = matcher.end(); + int capStart = matcher.start(1); + int capEnd = matcher.end(1); + + if(result == null) result = new StringBuilder(); + result.append(seq, pos, matchStart); + result.append(seq, matchStart, capStart); + try{ + appendNumCharRef(result, seq.charAt(capStart)); + }catch(IOException e){ + assert false; + return null; + } + result.append(seq, capEnd, matchEnd); + + pos = matchEnd; + } + + if(pos == 0) return seq; + + result.append(seq, pos, seq.length()); + + return result; + } + + /** + * 任意の文字列をWiki表記へ変換する。 + * @param seq 任意の文字列 + * @return Wiki用表記 + */ + public static CharSequence escapeWikiSyntax(CharSequence seq){ + CharSequence result = seq; + result = escapeWikiChar(result); // この順番は大事 + result = escapeWikiName(result); + // TODO さらにURLとメールアドレスのエスケープも + return result; + } + + /** + * ブラケットに入れる文字をエスケープする。 + * @param seq 文字列。 + * @return エスケープされた文字列 + */ + public static CharSequence escapeWikiBracket(CharSequence seq){ + StringBuilder result = new StringBuilder(); + + int seqLength = seq.length(); + for(int pos = 0; pos < seqLength; pos++){ + char ch = seq.charAt(pos); + + switch(ch){ + case '#': ch = '#'; break; + case '&': ch = '&'; break; + case '[': ch = '['; break; + case ']': ch = ']'; break; + case '<': ch = '<'; break; + case '>': ch = '>'; break; + default: break; + } + + result.append(ch); + } + + int resultLength; + + resultLength = result.length(); + while(result.length() > 0 && result.charAt(0) == '/'){ + result.deleteCharAt(0); + } + + resultLength = result.length(); + for(int pos = resultLength - 1; pos >= 0; pos--){ + char ch = result.charAt(pos); + if(ch != '/') break; + result.deleteCharAt(pos); + } + + resultLength = result.length(); + for(int pos = 1; pos < resultLength - 1; pos++){ + char ch = result.charAt(pos); + if(ch == ':'){ + result.setCharAt(pos, ':'); + } + } + + resultLength = result.length(); + if(resultLength == 1 && result.charAt(0) == ':'){ + result.setCharAt(0, ':'); + } + + return result; + } + + /** + * 数値参照文字に変換された文字を追加する。 + * 例)'D' => "D" + * @param app 追加対象 + * @param ch 1文字 + * @return 引数と同じ + * @throws java.io.IOException 入出力エラー。文字列の場合はありえない。 + */ + public static Appendable appendNumCharRef(Appendable app, char ch) + throws IOException{ + app.append("&#x"); + + int ival = ch; + String hex = Integer.toHexString(ival); + app.append(hex); + + app.append(';'); + + return app; + } + + /** + * 任意の文字を数値参照文字列に変換する。 + * 例)'D' => "D" + * @param ch 文字 + * @return 変換後の文字列 + */ + public static CharSequence toNumCharRef(char ch){ + StringBuilder result = new StringBuilder(8); + try{ + appendNumCharRef(result, ch); + }catch(IOException e){ + assert false; + return null; + } + return result; + } + + /** + * 陣営の色Wiki表記を返す。 + * @param role 役職 + * @return 色Wiki表記 + */ + public static String getTeamWikiColor(GameRole role){ + String result; + + switch(role){ + case INNOCENT: + case SEER: + case SHAMAN: + case HUNTER: + case FRATER: + result = "#b7bad3"; + break; + case WOLF: + case MADMAN: + result = "#e0b8b8"; + break; + case HAMSTER: + result = "#b9d0be"; + break; + default: + assert false; + return null; + } + + return result; + } + + /** + * 各役職のアイコンWikiを返す。 + * @param role 役職 + * @return アイコンWiki + */ + public static String getRoleIconWiki(GameRole role){ + String result; + + switch(role){ + case INNOCENT: + result = "&char(村人,nolink);"; + break; + case WOLF: + result = "&char(人狼,nolink);"; + break; + case SEER: + result = "&char(占い師,nolink);"; + break; + case SHAMAN: + result = "&char(霊能者,nolink);"; + break; + case MADMAN: + result = "&char(狂人,nolink);"; + break; + case HUNTER: + result = "&char(狩人,nolink);"; + break; + case FRATER: + result = "&char(共有者,nolink);"; + break; + case HAMSTER: + result = "&char(ハムスター人間,nolink);"; + break; + default: + assert false; + result = ""; + break; + } + + return result; + } + + /** + * 運命に対応する色Wiki表記を返す。 + * @param destiny 運命 + * @return 色Wiki表記 + */ + public static String getDestinyColorWiki(Destiny destiny){ + String result; + if(destiny == Destiny.ALIVE) result = "#ffffff"; + else result = "#aaaaaa"; + return result; + } + + /** + * そのまままとめサイトパス名に使えそうなシンプルな文字か判定する。 + * @param ch 文字 + * @return まとめサイトパス名に使えそうならtrue + */ + private static boolean isSimpleIdToken(char ch){ + if('0' <= ch && ch <= '9') return true; + if('A' <= ch && ch <= 'Z') return true; + if('a' <= ch && ch <= 'z') return true; + if(ch == '-' || ch == '_') return true; + return false; + } + + /** + * プレイヤーIDを構成する文字からパス名を組み立てる。 + * @param seq パス名 + * @param ch 文字 + * @return 引数と同じもの + */ + private static StringBuilder encodeId(StringBuilder seq, char ch){ + if(isSimpleIdToken(ch)){ + seq.append(ch); + return seq; + } + + CharBuffer cbuf = CharBuffer.allocate(1); + cbuf.append(ch); + cbuf.rewind(); + + CharsetEncoder encoder = CHARSET_EUC.newEncoder(); + ByteBuffer bytebuf; + try{ + bytebuf = encoder.encode(cbuf); + }catch(CharacterCodingException e){ + seq.append('X'); + return seq; + } + + int limit = bytebuf.limit(); + while(bytebuf.position() < limit){ + int iVal = bytebuf.get(); + if(iVal < 0) iVal += 0x0100; + String hex = Integer.toHexString(iVal).toUpperCase(Locale.JAPAN); + seq.append('%'); + if(hex.length() < 2) seq.append('0'); + seq.append(hex); + } + + return seq; + } + + /** + * プレイヤーIDからパス名の一部を予測する。 + * @param id プレイヤーID + * @return .htmlを抜いたパス名 + */ + private static StringBuilder encodeId(CharSequence id){ + StringBuilder result = new StringBuilder(); + int length = id.length(); + for(int pt = 0; pt < length; pt++){ + char ch = id.charAt(pt); + encodeId(result, ch); + } + return result; + } + + /** + * プレイヤーIDからまとめサイト上の個人ページを推測する。 + * @param id プレイヤーID + * @return 個人ページURL文字列 + */ + public static String encodeURLFromId(CharSequence id){ + CharSequence encodedId = encodeId(id); + + String result = WOLFBBS_URL + encodedId + ".html"; + + return result; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/XmlResourceResolver.java b/src/main/java/jp/sourceforge/jindolf/XmlResourceResolver.java index bf3aa02..b528a68 100644 --- a/src/main/java/jp/sourceforge/jindolf/XmlResourceResolver.java +++ b/src/main/java/jp/sourceforge/jindolf/XmlResourceResolver.java @@ -1,358 +1,358 @@ -/* - * xml resource resolver - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; -import jp.sourceforge.jindolf.corelib.XmlResource; -import org.w3c.dom.ls.LSInput; -import org.w3c.dom.ls.LSResourceResolver; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * URL変換マップに従い、XML文書からの外部参照をリダイレクトする。 - * 相対URIはこのクラスをベースに解決される。 - * 主な用途は外部スキーマのリソース化など。 - */ -public class XmlResourceResolver - implements LSResourceResolver, - EntityResolver{ - - private static final URI EMPTY_URI = URI.create(""); - - - private final Map uriMap = XmlResource.RESOLVE_MAP; - - - /** - * コンストラクタ。 - */ - public XmlResourceResolver(){ - super(); - return; - } - - - /** - * 絶対URIと相対URIを合成したURIを返す。 - * 正規化も行われる。 - * @param base 絶対URIでなければならない。nullでもよい。 - * @param relative 絶対URIでもよいがその場合baseは無視される。null可。 - * @return 合成結果のURLオブジェクト。必ず絶対URIになる。 - * @throws java.net.URISyntaxException URIとして変。 - * @throws java.lang.IllegalArgumentException 絶対URIが生成できない。 - */ - protected static URI buildBaseRelativeURI(String base, String relative) - throws URISyntaxException, - IllegalArgumentException { - URI baseURI = null; - if(base != null){ - baseURI = new URI(base); - if( ! baseURI.isAbsolute() ) throw new IllegalArgumentException(); - } - - URI relativeURI = EMPTY_URI; - if(relative != null){ - relativeURI = new URI(relative); - } - - URI resultURI; - if(baseURI == null || relativeURI.isAbsolute()){ - resultURI = relativeURI; - }else{ - resultURI = baseURI.resolve(relativeURI); - } - - if( ! resultURI.isAbsolute() ) throw new IllegalArgumentException(); - - resultURI = resultURI.normalize(); - - return resultURI; - } - - /** - * LSInput実装を生成する。 - * @return LSInput実装 - */ - public static LSInput createLSInput(){ - LSInput input = new LSInputImpl(); - return input; - } - - /** - * 変換後のリソースの入力ストリームを得る。 - * @param originalURI オリジナルURI - * @return 入力ストリーム - * @throws java.io.IOException 入出力エラー - */ - public InputStream getXMLResourceAsStream(URI originalURI) - throws IOException{ - URI resourceURI = this.uriMap.get(originalURI.normalize()); - URL resourceURL = resourceURI.toURL(); - InputStream is = resourceURL.openStream(); - - return is; - } - - /** - * {@inheritDoc} - * URL変換したあとの入力ソースを返す。 - * @param type {@inheritDoc} - * @param namespaceURI {@inheritDoc} - * @param publicId {@inheritDoc} - * @param systemId {@inheritDoc} - * @param baseURI {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public LSInput resolveResource(String type, - String namespaceURI, - String publicId, - String systemId, - String baseURI ){ - if(systemId == null) return null; - - URI originalURI; - try{ - originalURI = buildBaseRelativeURI(baseURI, systemId); - }catch(URISyntaxException e){ - return null; - } - - InputStream is; - try{ - is = getXMLResourceAsStream(originalURI); - }catch(IOException e){ - return null; - } - - LSInput input = createLSInput(); - input.setBaseURI(baseURI); - input.setPublicId(publicId); - input.setSystemId(systemId); - input.setByteStream(is); - - return input; - } - - /** - * {@inheritDoc} - * URL変換したあとの入力ソースを返す。 - * @param publicId {@inheritDoc} - * @param systemId {@inheritDoc} - * @return {@inheritDoc} - * @throws org.xml.sax.SAXException {@inheritDoc} - * @throws java.io.IOException {@inheritDoc} - */ - @Override - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException{ - if(systemId == null) return null; - - URI originalUri; - try{ - originalUri = new URI(systemId); - }catch(URISyntaxException e){ - return null; - } - - InputStream is = getXMLResourceAsStream(originalUri); - - InputSource source = new InputSource(is); - source.setPublicId(publicId); - source.setSystemId(systemId); - - return source; - } - - /** - * JRE1.5用LSInput実装。 - * JRE1.6なら - * org.w3c.dom.ls.DOMImplementationLS#createLSInput() - * で生成可能かも。 - */ - public static class LSInputImpl implements LSInput{ - - private String baseURI = null; - private InputStream byteStream = null; - private boolean certifiedText = false; - private Reader characterStream = null; - private String encoding = null; - private String publicId = null; - private String stringData = null; - private String systemId = null; - - /** - * コンストラクタ。 - */ - public LSInputImpl(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getBaseURI(){ - return this.baseURI; - } - - /** - * {@inheritDoc} - * @param baseURI {@inheritDoc} - */ - @Override - public void setBaseURI(String baseURI){ - this.baseURI = baseURI; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public InputStream getByteStream(){ - return this.byteStream; - } - - /** - * {@inheritDoc} - * @param byteStream {@inheritDoc} - */ - @Override - public void setByteStream(InputStream byteStream){ - this.byteStream = byteStream; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean getCertifiedText(){ - return this.certifiedText; - } - - /** - * {@inheritDoc} - * @param certifiedText {@inheritDoc} - */ - @Override - public void setCertifiedText(boolean certifiedText){ - this.certifiedText = certifiedText; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public Reader getCharacterStream(){ - return this.characterStream; - } - - /** - * {@inheritDoc} - * @param characterStream {@inheritDoc} - */ - @Override - public void setCharacterStream(Reader characterStream){ - this.characterStream = characterStream; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getEncoding(){ - return this.encoding; - } - - /** - * {@inheritDoc} - * @param encoding {@inheritDoc} - */ - @Override - public void setEncoding(String encoding){ - this.encoding = encoding; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getPublicId(){ - return this.publicId; - } - - /** - * {@inheritDoc} - * @param publicId {@inheritDoc} - */ - @Override - public void setPublicId(String publicId){ - this.publicId = publicId; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getStringData(){ - return this.stringData; - } - - /** - * {@inheritDoc} - * @param stringData {@inheritDoc} - */ - @Override - public void setStringData(String stringData){ - this.stringData = stringData; - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String getSystemId(){ - return this.systemId; - } - - /** - * {@inheritDoc} - * @param systemId {@inheritDoc} - */ - @Override - public void setSystemId(String systemId){ - this.systemId = systemId; - return; - } - - } - - // TODO OASIS XML Catalog などと調和したい。 -} +/* + * xml resource resolver + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; +import jp.sourceforge.jindolf.corelib.XmlResource; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * URL変換マップに従い、XML文書からの外部参照をリダイレクトする。 + * 相対URIはこのクラスをベースに解決される。 + * 主な用途は外部スキーマのリソース化など。 + */ +public class XmlResourceResolver + implements LSResourceResolver, + EntityResolver{ + + private static final URI EMPTY_URI = URI.create(""); + + + private final Map uriMap = XmlResource.RESOLVE_MAP; + + + /** + * コンストラクタ。 + */ + public XmlResourceResolver(){ + super(); + return; + } + + + /** + * 絶対URIと相対URIを合成したURIを返す。 + * 正規化も行われる。 + * @param base 絶対URIでなければならない。nullでもよい。 + * @param relative 絶対URIでもよいがその場合baseは無視される。null可。 + * @return 合成結果のURLオブジェクト。必ず絶対URIになる。 + * @throws java.net.URISyntaxException URIとして変。 + * @throws java.lang.IllegalArgumentException 絶対URIが生成できない。 + */ + protected static URI buildBaseRelativeURI(String base, String relative) + throws URISyntaxException, + IllegalArgumentException { + URI baseURI = null; + if(base != null){ + baseURI = new URI(base); + if( ! baseURI.isAbsolute() ) throw new IllegalArgumentException(); + } + + URI relativeURI = EMPTY_URI; + if(relative != null){ + relativeURI = new URI(relative); + } + + URI resultURI; + if(baseURI == null || relativeURI.isAbsolute()){ + resultURI = relativeURI; + }else{ + resultURI = baseURI.resolve(relativeURI); + } + + if( ! resultURI.isAbsolute() ) throw new IllegalArgumentException(); + + resultURI = resultURI.normalize(); + + return resultURI; + } + + /** + * LSInput実装を生成する。 + * @return LSInput実装 + */ + public static LSInput createLSInput(){ + LSInput input = new LSInputImpl(); + return input; + } + + /** + * 変換後のリソースの入力ストリームを得る。 + * @param originalURI オリジナルURI + * @return 入力ストリーム + * @throws java.io.IOException 入出力エラー + */ + public InputStream getXMLResourceAsStream(URI originalURI) + throws IOException{ + URI resourceURI = this.uriMap.get(originalURI.normalize()); + URL resourceURL = resourceURI.toURL(); + InputStream is = resourceURL.openStream(); + + return is; + } + + /** + * {@inheritDoc} + * URL変換したあとの入力ソースを返す。 + * @param type {@inheritDoc} + * @param namespaceURI {@inheritDoc} + * @param publicId {@inheritDoc} + * @param systemId {@inheritDoc} + * @param baseURI {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public LSInput resolveResource(String type, + String namespaceURI, + String publicId, + String systemId, + String baseURI ){ + if(systemId == null) return null; + + URI originalURI; + try{ + originalURI = buildBaseRelativeURI(baseURI, systemId); + }catch(URISyntaxException e){ + return null; + } + + InputStream is; + try{ + is = getXMLResourceAsStream(originalURI); + }catch(IOException e){ + return null; + } + + LSInput input = createLSInput(); + input.setBaseURI(baseURI); + input.setPublicId(publicId); + input.setSystemId(systemId); + input.setByteStream(is); + + return input; + } + + /** + * {@inheritDoc} + * URL変換したあとの入力ソースを返す。 + * @param publicId {@inheritDoc} + * @param systemId {@inheritDoc} + * @return {@inheritDoc} + * @throws org.xml.sax.SAXException {@inheritDoc} + * @throws java.io.IOException {@inheritDoc} + */ + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException{ + if(systemId == null) return null; + + URI originalUri; + try{ + originalUri = new URI(systemId); + }catch(URISyntaxException e){ + return null; + } + + InputStream is = getXMLResourceAsStream(originalUri); + + InputSource source = new InputSource(is); + source.setPublicId(publicId); + source.setSystemId(systemId); + + return source; + } + + /** + * JRE1.5用LSInput実装。 + * JRE1.6なら + * org.w3c.dom.ls.DOMImplementationLS#createLSInput() + * で生成可能かも。 + */ + public static class LSInputImpl implements LSInput{ + + private String baseURI = null; + private InputStream byteStream = null; + private boolean certifiedText = false; + private Reader characterStream = null; + private String encoding = null; + private String publicId = null; + private String stringData = null; + private String systemId = null; + + /** + * コンストラクタ。 + */ + public LSInputImpl(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getBaseURI(){ + return this.baseURI; + } + + /** + * {@inheritDoc} + * @param baseURI {@inheritDoc} + */ + @Override + public void setBaseURI(String baseURI){ + this.baseURI = baseURI; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public InputStream getByteStream(){ + return this.byteStream; + } + + /** + * {@inheritDoc} + * @param byteStream {@inheritDoc} + */ + @Override + public void setByteStream(InputStream byteStream){ + this.byteStream = byteStream; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean getCertifiedText(){ + return this.certifiedText; + } + + /** + * {@inheritDoc} + * @param certifiedText {@inheritDoc} + */ + @Override + public void setCertifiedText(boolean certifiedText){ + this.certifiedText = certifiedText; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public Reader getCharacterStream(){ + return this.characterStream; + } + + /** + * {@inheritDoc} + * @param characterStream {@inheritDoc} + */ + @Override + public void setCharacterStream(Reader characterStream){ + this.characterStream = characterStream; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getEncoding(){ + return this.encoding; + } + + /** + * {@inheritDoc} + * @param encoding {@inheritDoc} + */ + @Override + public void setEncoding(String encoding){ + this.encoding = encoding; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getPublicId(){ + return this.publicId; + } + + /** + * {@inheritDoc} + * @param publicId {@inheritDoc} + */ + @Override + public void setPublicId(String publicId){ + this.publicId = publicId; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getStringData(){ + return this.stringData; + } + + /** + * {@inheritDoc} + * @param stringData {@inheritDoc} + */ + @Override + public void setStringData(String stringData){ + this.stringData = stringData; + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String getSystemId(){ + return this.systemId; + } + + /** + * {@inheritDoc} + * @param systemId {@inheritDoc} + */ + @Override + public void setSystemId(String systemId){ + this.systemId = systemId; + return; + } + + } + + // TODO OASIS XML Catalog などと調和したい。 +} diff --git a/src/main/java/jp/sourceforge/jindolf/XmlUtils.java b/src/main/java/jp/sourceforge/jindolf/XmlUtils.java index 45939ea..70533ab 100644 --- a/src/main/java/jp/sourceforge/jindolf/XmlUtils.java +++ b/src/main/java/jp/sourceforge/jindolf/XmlUtils.java @@ -1,158 +1,158 @@ -/* - * XML utilities - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import javax.xml.XMLConstants; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import jp.sourceforge.jindolf.corelib.XmlResource; -import org.w3c.dom.ls.LSResourceResolver; -import org.xml.sax.EntityResolver; -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -/** - * XML関連のユーティリティ。 - */ -public final class XmlUtils{ - - private static final ErrorHandler STRICT_HANDLER = new StrictHandler(); - private static final XmlResourceResolver RESOLVER = - new XmlResourceResolver(); - - - /** - * 隠しコンストラクタ。 - */ - private XmlUtils(){ - super(); - return; - } - - - /** - * リゾルバ経由でリソースにアクセスし、 - * 共通スキーマによるバリデーションを行うためのDocumentBuilderを生成する。 - * @return ビルダ - * @throws URISyntaxException URIが不正 - * @throws IOException 入出力エラー - * @throws ParserConfigurationException パーサ準備失敗 - * @throws SAXException スキーマが変 - */ - public static DocumentBuilder createDocumentBuilder() - throws IOException, - URISyntaxException, - ParserConfigurationException, - SAXException { - Schema xsdSchema = createCoreSchema(RESOLVER); - DocumentBuilder builder = createBuilder(xsdSchema, RESOLVER); - - return builder; - } - - /** - * 共通コアスキーマを得る。 - * このスキーマはLSResourceResolverによるリダイレクトをサポートする。 - * @param resolver リゾルバ - * @return スキーマ - * @throws URISyntaxException URIが不正 - * @throws IOException 入出力エラー - * @throws SAXException スキーマが変 - */ - public static Schema createCoreSchema(LSResourceResolver resolver) - throws URISyntaxException, - IOException, - SAXException { - SchemaFactory xsdSchemaFactory = - SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - xsdSchemaFactory.setResourceResolver(resolver); - xsdSchemaFactory.setErrorHandler(STRICT_HANDLER); - - InputStream is = XmlResource.I_URL_COREXML.openStream(); - StreamSource source = new StreamSource(is); - Schema schema = xsdSchemaFactory.newSchema(source); - - return schema; - } - - /** - * スキーマによる妥当性検証を兼用するDocumentBuilderを生成する。 - * @param schema スキーマ - * @param resolver リゾルバ - * @return DOM用ビルダ - * @throws ParserConfigurationException パーサ準備失敗 - */ - public static DocumentBuilder createBuilder(Schema schema, - EntityResolver resolver) - throws ParserConfigurationException{ - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(false); - factory.setSchema(schema); - - DocumentBuilder builder = factory.newDocumentBuilder(); - builder.setErrorHandler(STRICT_HANDLER); - builder.setEntityResolver(resolver); - - return builder; - } - - /** - * スキーマ検証用の厳密なエラーハンドラ。 - */ - public static class StrictHandler implements ErrorHandler{ - - /** - * コンストラクタ。 - */ - public StrictHandler(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param e {@inheritDoc} エラー情報 - * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 - */ - @Override - public void error(SAXParseException e) throws SAXException{ - throw e; - } - - /** - * {@inheritDoc} - * @param e {@inheritDoc} エラー情報 - * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 - */ - @Override - public void fatalError(SAXParseException e) throws SAXException{ - throw e; - } - - /** - * {@inheritDoc} - * @param e {@inheritDoc} エラー情報 - * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 - */ - @Override - public void warning(SAXParseException e) throws SAXException{ - throw e; - } - - } - -} +/* + * XML utilities + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import jp.sourceforge.jindolf.corelib.XmlResource; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * XML関連のユーティリティ。 + */ +public final class XmlUtils{ + + private static final ErrorHandler STRICT_HANDLER = new StrictHandler(); + private static final XmlResourceResolver RESOLVER = + new XmlResourceResolver(); + + + /** + * 隠しコンストラクタ。 + */ + private XmlUtils(){ + super(); + return; + } + + + /** + * リゾルバ経由でリソースにアクセスし、 + * 共通スキーマによるバリデーションを行うためのDocumentBuilderを生成する。 + * @return ビルダ + * @throws URISyntaxException URIが不正 + * @throws IOException 入出力エラー + * @throws ParserConfigurationException パーサ準備失敗 + * @throws SAXException スキーマが変 + */ + public static DocumentBuilder createDocumentBuilder() + throws IOException, + URISyntaxException, + ParserConfigurationException, + SAXException { + Schema xsdSchema = createCoreSchema(RESOLVER); + DocumentBuilder builder = createBuilder(xsdSchema, RESOLVER); + + return builder; + } + + /** + * 共通コアスキーマを得る。 + * このスキーマはLSResourceResolverによるリダイレクトをサポートする。 + * @param resolver リゾルバ + * @return スキーマ + * @throws URISyntaxException URIが不正 + * @throws IOException 入出力エラー + * @throws SAXException スキーマが変 + */ + public static Schema createCoreSchema(LSResourceResolver resolver) + throws URISyntaxException, + IOException, + SAXException { + SchemaFactory xsdSchemaFactory = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + xsdSchemaFactory.setResourceResolver(resolver); + xsdSchemaFactory.setErrorHandler(STRICT_HANDLER); + + InputStream is = XmlResource.I_URL_COREXML.openStream(); + StreamSource source = new StreamSource(is); + Schema schema = xsdSchemaFactory.newSchema(source); + + return schema; + } + + /** + * スキーマによる妥当性検証を兼用するDocumentBuilderを生成する。 + * @param schema スキーマ + * @param resolver リゾルバ + * @return DOM用ビルダ + * @throws ParserConfigurationException パーサ準備失敗 + */ + public static DocumentBuilder createBuilder(Schema schema, + EntityResolver resolver) + throws ParserConfigurationException{ + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + factory.setSchema(schema); + + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setErrorHandler(STRICT_HANDLER); + builder.setEntityResolver(resolver); + + return builder; + } + + /** + * スキーマ検証用の厳密なエラーハンドラ。 + */ + public static class StrictHandler implements ErrorHandler{ + + /** + * コンストラクタ。 + */ + public StrictHandler(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param e {@inheritDoc} エラー情報 + * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 + */ + @Override + public void error(SAXParseException e) throws SAXException{ + throw e; + } + + /** + * {@inheritDoc} + * @param e {@inheritDoc} エラー情報 + * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 + */ + @Override + public void fatalError(SAXParseException e) throws SAXException{ + throw e; + } + + /** + * {@inheritDoc} + * @param e {@inheritDoc} エラー情報 + * @throws org.xml.sax.SAXException {@inheritDoc} 引数と同じ物 + */ + @Override + public void warning(SAXParseException e) throws SAXException{ + throw e; + } + + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java b/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java index 13a4347..7fed800 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java +++ b/src/main/java/jp/sourceforge/jindolf/json/AbstractJsValue.java @@ -1,53 +1,53 @@ -/* - * JSON abstract value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * JSON 各種Value共通実装。 - * 継承必須。 - */ -public class AbstractJsValue implements JsValue{ - - /** - * コンストラクタ。 - */ - protected AbstractJsValue(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param visitor {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void traverse(ValueVisitor visitor) - throws JsVisitException{ - visitor.visitValue(this); - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean hasChanged(){ - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void setUnchanged(){ - return; - } - -} +/* + * JSON abstract value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * JSON 各種Value共通実装。 + * 継承必須。 + */ +public class AbstractJsValue implements JsValue{ + + /** + * コンストラクタ。 + */ + protected AbstractJsValue(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param visitor {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void traverse(ValueVisitor visitor) + throws JsVisitException{ + visitor.visitValue(this); + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean hasChanged(){ + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setUnchanged(){ + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsArray.java b/src/main/java/jp/sourceforge/jindolf/json/JsArray.java index 98501ec..93c3aee 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsArray.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsArray.java @@ -1,232 +1,232 @@ -/* - * JSON array value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * JSON 配列Value。 - */ -public class JsArray - extends AbstractJsValue - implements Iterable { - - private final List valueList = new ArrayList(); - private boolean changed = false; - - - /** - * コンストラクタ。 - */ - public JsArray(){ - super(); - return; - } - - - /** - * JSON Arrayを文字ストリームからパースする。 - * @param reader 文字入力 - * @return JSON Array。入力終了ならnull - * @throws IOException 入力エラー - * @throws JsParseException パースエラー - */ - static JsArray parseArray(JsonReader reader) - throws IOException, - JsParseException { - int chData; - - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') return null; - if(chData != '[') throw new JsParseException(); - - JsArray result = new JsArray(); - - for(;;){ - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData == ']') break; - - if(result.size() <= 0){ - reader.unread(chData); - }else{ - if(chData != ',') throw new JsParseException(); - Json.skipWhiteSpace(reader); - } - - JsValue value = Json.parseValue(reader); - if(value == null){ - throw new JsParseException(); - } - - result.add(value); - } - - return result; - } - - - /** - * JSON Valueを追加する。 - * @param value JSON Value - */ - public void add(JsValue value){ - this.valueList.add(value); - this.changed = true; - return; - } - - /** - * 指定された位置のValueを返す。 - * @param index 0で始まる位置 - * @return Value - * @throws IndexOutOfBoundsException 不正な位置指定 - */ - public JsValue get(int index) throws IndexOutOfBoundsException{ - return this.valueList.get(index); - } - - /** - * 空にする。 - */ - public void clear(){ - if(this.valueList.size() > 0) this.changed = true; - this.valueList.clear(); - return; - } - - /** - * JSON Valueを削除する。 - * @param value JSON Value - * @return 既存のValueが削除されたならtrue - */ - public boolean remove(JsValue value){ - boolean removed = this.valueList.remove(value); - if(removed) this.changed = true; - return removed; - } - - /** - * Value総数を返す。 - * @return 総数 - */ - public int size(){ - return this.valueList.size(); - } - - /** - * Valueにアクセスするための反復子を提供する。 - * この反復子での削除作業はできない。 - * @return 反復子イテレータ - */ - public Iterator iterator(){ - Collection unmodColl = - Collections.unmodifiableCollection(this.valueList); - return unmodColl.iterator(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.valueList.hashCode(); - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(this == obj) return true; - - if( ! (obj instanceof JsArray) ) return false; - JsArray array = (JsArray) obj; - - return this.valueList.equals(array.valueList); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - StringBuilder text = new StringBuilder(); - - text.append("["); - boolean hasElem = false; - for(JsValue value : this.valueList){ - if(hasElem) text.append(','); - text.append(value); - hasElem = true; - } - text.append("]"); - - return text.toString(); - } - - /** - * {@inheritDoc} - * @param visitor {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void traverse(ValueVisitor visitor) throws JsVisitException{ - visitor.visitValue(this); - - for(JsValue value : this.valueList){ - value.traverse(visitor); - } - - visitor.visitCollectionClose(this); - - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean hasChanged(){ - if(this.changed) return true; - - for(JsValue value : this.valueList){ - if(value.hasChanged()) return true; - } - - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void setUnchanged(){ - this.changed = false; - - for(JsValue value : this.valueList){ - value.setUnchanged(); - } - - return; - } - -} +/* + * JSON array value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * JSON 配列Value。 + */ +public class JsArray + extends AbstractJsValue + implements Iterable { + + private final List valueList = new ArrayList(); + private boolean changed = false; + + + /** + * コンストラクタ。 + */ + public JsArray(){ + super(); + return; + } + + + /** + * JSON Arrayを文字ストリームからパースする。 + * @param reader 文字入力 + * @return JSON Array。入力終了ならnull + * @throws IOException 入力エラー + * @throws JsParseException パースエラー + */ + static JsArray parseArray(JsonReader reader) + throws IOException, + JsParseException { + int chData; + + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') return null; + if(chData != '[') throw new JsParseException(); + + JsArray result = new JsArray(); + + for(;;){ + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData == ']') break; + + if(result.size() <= 0){ + reader.unread(chData); + }else{ + if(chData != ',') throw new JsParseException(); + Json.skipWhiteSpace(reader); + } + + JsValue value = Json.parseValue(reader); + if(value == null){ + throw new JsParseException(); + } + + result.add(value); + } + + return result; + } + + + /** + * JSON Valueを追加する。 + * @param value JSON Value + */ + public void add(JsValue value){ + this.valueList.add(value); + this.changed = true; + return; + } + + /** + * 指定された位置のValueを返す。 + * @param index 0で始まる位置 + * @return Value + * @throws IndexOutOfBoundsException 不正な位置指定 + */ + public JsValue get(int index) throws IndexOutOfBoundsException{ + return this.valueList.get(index); + } + + /** + * 空にする。 + */ + public void clear(){ + if(this.valueList.size() > 0) this.changed = true; + this.valueList.clear(); + return; + } + + /** + * JSON Valueを削除する。 + * @param value JSON Value + * @return 既存のValueが削除されたならtrue + */ + public boolean remove(JsValue value){ + boolean removed = this.valueList.remove(value); + if(removed) this.changed = true; + return removed; + } + + /** + * Value総数を返す。 + * @return 総数 + */ + public int size(){ + return this.valueList.size(); + } + + /** + * Valueにアクセスするための反復子を提供する。 + * この反復子での削除作業はできない。 + * @return 反復子イテレータ + */ + public Iterator iterator(){ + Collection unmodColl = + Collections.unmodifiableCollection(this.valueList); + return unmodColl.iterator(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.valueList.hashCode(); + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(this == obj) return true; + + if( ! (obj instanceof JsArray) ) return false; + JsArray array = (JsArray) obj; + + return this.valueList.equals(array.valueList); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + StringBuilder text = new StringBuilder(); + + text.append("["); + boolean hasElem = false; + for(JsValue value : this.valueList){ + if(hasElem) text.append(','); + text.append(value); + hasElem = true; + } + text.append("]"); + + return text.toString(); + } + + /** + * {@inheritDoc} + * @param visitor {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void traverse(ValueVisitor visitor) throws JsVisitException{ + visitor.visitValue(this); + + for(JsValue value : this.valueList){ + value.traverse(visitor); + } + + visitor.visitCollectionClose(this); + + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean hasChanged(){ + if(this.changed) return true; + + for(JsValue value : this.valueList){ + if(value.hasChanged()) return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setUnchanged(){ + this.changed = false; + + for(JsValue value : this.valueList){ + value.setUnchanged(); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java b/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java index 45d5efa..6bda497 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsBoolean.java @@ -1,121 +1,121 @@ -/* - * JSON boolean value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * JSON 真偽Value。 - */ -public final class JsBoolean - extends AbstractJsValue - implements Comparable { - - /** 真。 */ - public static final JsBoolean TRUE = new JsBoolean(); - /** 偽。 */ - public static final JsBoolean FALSE = new JsBoolean(); - - - /** - * コンストラクタ。 - * 2回しか呼ばれないはず。 - */ - private JsBoolean(){ - super(); - return; - } - - - /** - * boolean値から真偽Valueを返す。 - * @param bool boolean値 - * @return TRUEかFALSE - */ - public static JsBoolean valueOf(boolean bool){ - if(bool) return TRUE; - return FALSE; - } - - /** - * boolean値を返す。 - * @return boolean値 - */ - public boolean booleanValue(){ - if(this == TRUE) return true; - return false; - } - - /** - * 真か判定する。 - * @return 真ならtrue - */ - public boolean isTrue(){ - if(this == TRUE) return true; - return false; - } - - /** - * 偽か判定する。 - * @return 偽ならtrue - */ - public boolean isFalse(){ - if(this == FALSE) return true; - return false; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - if(this.isTrue()) return Boolean.TRUE.hashCode(); - return Boolean.FALSE.hashCode(); - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(this == obj) return true; - - if( ! (obj instanceof JsBoolean) ) return false; - - return false; - } - - /** - * {@inheritDoc} - * @param value {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(JsBoolean value){ - if(value == null) throw new NullPointerException(); - if(this == value) return 0; - - if (this.isTrue() && value.isFalse()) return -1; - else if(this.isFalse() && value.isTrue() ) return +1; - - return 0; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - if(this.isTrue()) return "true"; - return "false"; - } - -} +/* + * JSON boolean value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * JSON 真偽Value。 + */ +public final class JsBoolean + extends AbstractJsValue + implements Comparable { + + /** 真。 */ + public static final JsBoolean TRUE = new JsBoolean(); + /** 偽。 */ + public static final JsBoolean FALSE = new JsBoolean(); + + + /** + * コンストラクタ。 + * 2回しか呼ばれないはず。 + */ + private JsBoolean(){ + super(); + return; + } + + + /** + * boolean値から真偽Valueを返す。 + * @param bool boolean値 + * @return TRUEかFALSE + */ + public static JsBoolean valueOf(boolean bool){ + if(bool) return TRUE; + return FALSE; + } + + /** + * boolean値を返す。 + * @return boolean値 + */ + public boolean booleanValue(){ + if(this == TRUE) return true; + return false; + } + + /** + * 真か判定する。 + * @return 真ならtrue + */ + public boolean isTrue(){ + if(this == TRUE) return true; + return false; + } + + /** + * 偽か判定する。 + * @return 偽ならtrue + */ + public boolean isFalse(){ + if(this == FALSE) return true; + return false; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + if(this.isTrue()) return Boolean.TRUE.hashCode(); + return Boolean.FALSE.hashCode(); + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(this == obj) return true; + + if( ! (obj instanceof JsBoolean) ) return false; + + return false; + } + + /** + * {@inheritDoc} + * @param value {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(JsBoolean value){ + if(value == null) throw new NullPointerException(); + if(this == value) return 0; + + if (this.isTrue() && value.isFalse()) return -1; + else if(this.isFalse() && value.isTrue() ) return +1; + + return 0; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + if(this.isTrue()) return "true"; + return "false"; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsNull.java b/src/main/java/jp/sourceforge/jindolf/json/JsNull.java index 4a3a9cf..f3aa8bb 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsNull.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsNull.java @@ -1,71 +1,71 @@ -/* - * JSON null value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * JSON Null Value。 - * その実体はシングルトン - */ -public final class JsNull - extends AbstractJsValue - implements Comparable { - - /** ただ唯一のインスタンス。 */ - public static final JsNull NULL = new JsNull(); - - /** - * 隠しコンストラクタ。 - * 1回しか呼ばれないはず - */ - private JsNull(){ - super(); - return; - } - - /** - * {@inheritDoc} - * @param value {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(JsNull value){ - if(value == null) throw new NullPointerException(); - return 0; - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if( ! (obj instanceof JsNull) ) return false; - if(obj != this) return false; - return true; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return 7777; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return "null"; - } - -} +/* + * JSON null value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * JSON Null Value。 + * その実体はシングルトン + */ +public final class JsNull + extends AbstractJsValue + implements Comparable { + + /** ただ唯一のインスタンス。 */ + public static final JsNull NULL = new JsNull(); + + /** + * 隠しコンストラクタ。 + * 1回しか呼ばれないはず + */ + private JsNull(){ + super(); + return; + } + + /** + * {@inheritDoc} + * @param value {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(JsNull value){ + if(value == null) throw new NullPointerException(); + return 0; + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if( ! (obj instanceof JsNull) ) return false; + if(obj != this) return false; + return true; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return 7777; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return "null"; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java b/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java index 8613f7f..5d75e69 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsNumber.java @@ -1,328 +1,328 @@ -/* - * JSON number value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; - -/** - * JSON 数値Value。 - * 10を基数としたjava.math.BigDecimalを実装ベースとする - * IEEE754浮動小数ではない。 - */ -public class JsNumber - extends AbstractJsValue - implements Comparable { - - private BigDecimal decimal; - - - /** - * コンストラクタ。 - * @param val 初期数値 - */ - public JsNumber(long val){ - this(BigDecimal.valueOf(val)); - return; - } - - /** - * コンストラクタ。 - * @param val 初期数値 - */ - public JsNumber(double val){ - this(BigDecimal.valueOf(val)); - return; - } - - /** - * コンストラクタ。 - * @param val 初期数値 - */ - public JsNumber(BigInteger val){ - this(new BigDecimal(val)); - return; - } - - /** - * コンストラクタ。 - * 書式はjava.math.BigDecinal#BigDecimal(String)に準ずる。 - * @param val 初期数値の文字列表記 - * @throws NumberFormatException 不正な数値表記 - */ - public JsNumber(CharSequence val) throws NumberFormatException{ - this(new BigDecimal(val.toString())); - return; - } - - /** - * コンストラクタ。 - * @param val 初期数値 - * @throws NullPointerException 引数がnull - */ - public JsNumber(BigDecimal val) throws NullPointerException{ - super(); - if(val == null) throw new NullPointerException(); - this.decimal = val; - return; - } - - - /** - * 文字ストリームから符号付きの数字並びを読み込む。 - * +符号は読み飛ばされる。 - * 冒頭の連続する0はそのまま読まれる。 - * @param reader 文字入力 - * @param app 出力先 - * @param allowZeroTrail 冒頭の2つ以上連続するゼロを許すならtrue - * @return 引数と同じ出力先 - * @throws IOException 入出力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - private static Appendable appendDigitText(JsonReader reader, - Appendable app, - boolean allowZeroTrail) - throws IOException, JsParseException{ - int chData; - - chData = reader.read(); - if (chData < '\u0000') throw new JsParseException(); - else if(chData == '-') app.append('-'); - else if(chData != '+') reader.unread(chData); - - boolean hasAppended = false; - boolean zeroStarted = false; - for(;;){ - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - - if('0' <= chData && chData <= '9'){ - app.append((char)chData); - - if(zeroStarted && ! allowZeroTrail){ - throw new JsParseException(); - } - - if(chData == '0' && ! hasAppended ){ - zeroStarted = true; - } - - hasAppended = true; - }else{ - if( ! hasAppended ) throw new JsParseException(); - reader.unread(chData); - break; - } - } - - return app; - } - - /** - * 文字ストリームから符号付きの数字並びを読み込む。 - * +符号はパースエラーとなる。 - * @param reader 文字入力 - * @param app 出力先 - * @return 引数と同じ出力先 - * @throws IOException 入出力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - private static Appendable appendIntegerPart(JsonReader reader, - Appendable app ) - throws IOException, JsParseException{ - int chData; - - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData == '+') throw new JsParseException(); - reader.unread(chData); - - appendDigitText(reader, app, false); - - return app; - } - - /** - * 文字ストリームから「.」で始まる小数部を読み込む。 - * 小数部がなければなにもせずに戻る。 - * @param reader 文字入力 - * @param app 出力先 - * @return 引数と同じ出力先 - * @throws IOException 入出力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - private static Appendable appendFractionPart(JsonReader reader, - Appendable app ) - throws IOException, JsParseException{ - int chData; - - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData != '.'){ - reader.unread(chData); - return app; - } - - app.append("."); - - boolean hasAppended = false; - for(;;){ - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - - if('0' <= chData && chData <= '9'){ - app.append((char)chData); - hasAppended = true; - }else{ - if( ! hasAppended ) throw new JsParseException(); - reader.unread(chData); - break; - } - } - - return app; - } - - /** - * 文字ストリームから「e」もしくは「E」で始まる指数部を読み込む。 - * 指数部がなければなにもせずに戻る。 - * @param reader 文字入力 - * @param app 出力先 - * @return 引数と同じ出力先 - * @throws IOException 入出力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - private static Appendable appendExpPart(JsonReader reader, - Appendable app ) - throws IOException, JsParseException{ - int chData; - - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData != 'e' && chData != 'E'){ - reader.unread(chData); - return app; - } - - app.append('E'); - - appendDigitText(reader, app, true); - - return app; - } - - /** - * 文字ストリームからJSON数値Valueを読み込む。 - * @param reader 文字入力 - * @return 数値Value - * @throws IOException 入力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - static JsNumber parseNumber(JsonReader reader) - throws IOException, JsParseException{ - Json.skipWhiteSpace(reader); - - StringBuilder numText = new StringBuilder(); - appendIntegerPart (reader, numText); - appendFractionPart(reader, numText); - appendExpPart (reader, numText); - JsNumber result = new JsNumber(numText); - - return result; - } - - /** - * BigDecimal型の数値を返す。 - * @return BigDecimal型数値 - */ - public BigDecimal getBigDecimal(){ - return this.decimal; - } - - /** - * int型の数値を返す。 - * @return int型数値 - */ - public int intValue(){ - return this.decimal.intValue(); - } - - /** - * long型の数値を返す。 - * @return long型数値 - */ - public long longValue(){ - return this.decimal.longValue(); - } - - /** - * float型の数値を返す。 - * @return float型数値 - */ - public float floatValue(){ - return this.decimal.floatValue(); - } - - /** - * double型の数値を返す。 - * @return double型数値 - */ - public double doubleValue(){ - return this.decimal.doubleValue(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.decimal.hashCode(); - } - - /** - * {@inheritDoc} - * 「1.2」と「0.12E+1」など、スケールの一致しない値は異なる値と見なされる。 - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(this == obj) return true; - if( ! (obj instanceof JsNumber) ) return false; - JsNumber number = (JsNumber) obj; - return this.decimal.equals(number.decimal); - } - - /** - * {@inheritDoc} - * 「1.2」と「0.12E+1」など、スケールが異なっても値が同じであれば - * 等しいと見なされる。 - * @param value {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(JsNumber value){ - if(this == value) return 0; - return this.decimal.compareTo(value.decimal); - } - - /** - * {@inheritDoc} - * java.math.BigDecimal#toString()に準ずる。 - * ※ JSON規格のパーサで解釈できるはず。 - * @return {@inheritDoc} - */ - @Override - public String toString(){ - return this.decimal.toString(); - } - -} +/* + * JSON number value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * JSON 数値Value。 + * 10を基数としたjava.math.BigDecimalを実装ベースとする + * IEEE754浮動小数ではない。 + */ +public class JsNumber + extends AbstractJsValue + implements Comparable { + + private BigDecimal decimal; + + + /** + * コンストラクタ。 + * @param val 初期数値 + */ + public JsNumber(long val){ + this(BigDecimal.valueOf(val)); + return; + } + + /** + * コンストラクタ。 + * @param val 初期数値 + */ + public JsNumber(double val){ + this(BigDecimal.valueOf(val)); + return; + } + + /** + * コンストラクタ。 + * @param val 初期数値 + */ + public JsNumber(BigInteger val){ + this(new BigDecimal(val)); + return; + } + + /** + * コンストラクタ。 + * 書式はjava.math.BigDecinal#BigDecimal(String)に準ずる。 + * @param val 初期数値の文字列表記 + * @throws NumberFormatException 不正な数値表記 + */ + public JsNumber(CharSequence val) throws NumberFormatException{ + this(new BigDecimal(val.toString())); + return; + } + + /** + * コンストラクタ。 + * @param val 初期数値 + * @throws NullPointerException 引数がnull + */ + public JsNumber(BigDecimal val) throws NullPointerException{ + super(); + if(val == null) throw new NullPointerException(); + this.decimal = val; + return; + } + + + /** + * 文字ストリームから符号付きの数字並びを読み込む。 + * +符号は読み飛ばされる。 + * 冒頭の連続する0はそのまま読まれる。 + * @param reader 文字入力 + * @param app 出力先 + * @param allowZeroTrail 冒頭の2つ以上連続するゼロを許すならtrue + * @return 引数と同じ出力先 + * @throws IOException 入出力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + private static Appendable appendDigitText(JsonReader reader, + Appendable app, + boolean allowZeroTrail) + throws IOException, JsParseException{ + int chData; + + chData = reader.read(); + if (chData < '\u0000') throw new JsParseException(); + else if(chData == '-') app.append('-'); + else if(chData != '+') reader.unread(chData); + + boolean hasAppended = false; + boolean zeroStarted = false; + for(;;){ + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + + if('0' <= chData && chData <= '9'){ + app.append((char)chData); + + if(zeroStarted && ! allowZeroTrail){ + throw new JsParseException(); + } + + if(chData == '0' && ! hasAppended ){ + zeroStarted = true; + } + + hasAppended = true; + }else{ + if( ! hasAppended ) throw new JsParseException(); + reader.unread(chData); + break; + } + } + + return app; + } + + /** + * 文字ストリームから符号付きの数字並びを読み込む。 + * +符号はパースエラーとなる。 + * @param reader 文字入力 + * @param app 出力先 + * @return 引数と同じ出力先 + * @throws IOException 入出力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + private static Appendable appendIntegerPart(JsonReader reader, + Appendable app ) + throws IOException, JsParseException{ + int chData; + + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData == '+') throw new JsParseException(); + reader.unread(chData); + + appendDigitText(reader, app, false); + + return app; + } + + /** + * 文字ストリームから「.」で始まる小数部を読み込む。 + * 小数部がなければなにもせずに戻る。 + * @param reader 文字入力 + * @param app 出力先 + * @return 引数と同じ出力先 + * @throws IOException 入出力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + private static Appendable appendFractionPart(JsonReader reader, + Appendable app ) + throws IOException, JsParseException{ + int chData; + + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData != '.'){ + reader.unread(chData); + return app; + } + + app.append("."); + + boolean hasAppended = false; + for(;;){ + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + + if('0' <= chData && chData <= '9'){ + app.append((char)chData); + hasAppended = true; + }else{ + if( ! hasAppended ) throw new JsParseException(); + reader.unread(chData); + break; + } + } + + return app; + } + + /** + * 文字ストリームから「e」もしくは「E」で始まる指数部を読み込む。 + * 指数部がなければなにもせずに戻る。 + * @param reader 文字入力 + * @param app 出力先 + * @return 引数と同じ出力先 + * @throws IOException 入出力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + private static Appendable appendExpPart(JsonReader reader, + Appendable app ) + throws IOException, JsParseException{ + int chData; + + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData != 'e' && chData != 'E'){ + reader.unread(chData); + return app; + } + + app.append('E'); + + appendDigitText(reader, app, true); + + return app; + } + + /** + * 文字ストリームからJSON数値Valueを読み込む。 + * @param reader 文字入力 + * @return 数値Value + * @throws IOException 入力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + static JsNumber parseNumber(JsonReader reader) + throws IOException, JsParseException{ + Json.skipWhiteSpace(reader); + + StringBuilder numText = new StringBuilder(); + appendIntegerPart (reader, numText); + appendFractionPart(reader, numText); + appendExpPart (reader, numText); + JsNumber result = new JsNumber(numText); + + return result; + } + + /** + * BigDecimal型の数値を返す。 + * @return BigDecimal型数値 + */ + public BigDecimal getBigDecimal(){ + return this.decimal; + } + + /** + * int型の数値を返す。 + * @return int型数値 + */ + public int intValue(){ + return this.decimal.intValue(); + } + + /** + * long型の数値を返す。 + * @return long型数値 + */ + public long longValue(){ + return this.decimal.longValue(); + } + + /** + * float型の数値を返す。 + * @return float型数値 + */ + public float floatValue(){ + return this.decimal.floatValue(); + } + + /** + * double型の数値を返す。 + * @return double型数値 + */ + public double doubleValue(){ + return this.decimal.doubleValue(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.decimal.hashCode(); + } + + /** + * {@inheritDoc} + * 「1.2」と「0.12E+1」など、スケールの一致しない値は異なる値と見なされる。 + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(this == obj) return true; + if( ! (obj instanceof JsNumber) ) return false; + JsNumber number = (JsNumber) obj; + return this.decimal.equals(number.decimal); + } + + /** + * {@inheritDoc} + * 「1.2」と「0.12E+1」など、スケールが異なっても値が同じであれば + * 等しいと見なされる。 + * @param value {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(JsNumber value){ + if(this == value) return 0; + return this.decimal.compareTo(value.decimal); + } + + /** + * {@inheritDoc} + * java.math.BigDecimal#toString()に準ずる。 + * ※ JSON規格のパーサで解釈できるはず。 + * @return {@inheritDoc} + */ + @Override + public String toString(){ + return this.decimal.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsObject.java b/src/main/java/jp/sourceforge/jindolf/json/JsObject.java index 4c96492..fc4744a 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsObject.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsObject.java @@ -1,305 +1,305 @@ -/* - * JSON object value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * JSON オブジェクト Value。 - */ -public class JsObject - extends AbstractJsValue - implements Iterable { - - private final Map valueMap = - new TreeMap(); - private boolean changed = false; - - - /** - * コンストラクタ。 - */ - public JsObject(){ - super(); - return; - } - - - /** - * JSON Objectを文字ストリームからパースする。 - * @param reader 文字入力 - * @return JSON Object。入力終了ならnull - * @throws IOException 入力エラー - * @throws JsParseException パースエラー - */ - static JsObject parseObject(JsonReader reader) - throws IOException, JsParseException{ - int chData; - - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') return null; - if(chData != '{') throw new JsParseException(); - - JsObject result = new JsObject(); - - for(;;){ - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData == '}') break; - - if(result.size() <= 0){ - reader.unread(chData); - }else{ - if(chData != ',') throw new JsParseException(); - Json.skipWhiteSpace(reader); - } - - JsString name = JsString.parseString(reader); - if(name == null){ - throw new JsParseException(); - } - - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData != ':') throw new JsParseException(); - Json.skipWhiteSpace(reader); - - JsValue value = Json.parseValue(reader); - if(value == null){ - throw new JsParseException(); - } - - result.putValue(name.toRawString(), value); - } - - return result; - } - - - /** - * 名前とValueからpairを登録する。 - * @param name 名前 - * @param value Value - * @return 旧Value。同じ内容のpairがすでに存在していたらnull - * @throws NullPointerException 引数のいずれかがnull - */ - public JsValue putValue(String name, JsValue value) - throws NullPointerException{ - if(name == null) throw new NullPointerException(); - if(value == null) throw new NullPointerException(); - - JsValue oldValue = this.valueMap.get(name); - if(value.equals(oldValue)) return null; - - JsValue old = this.valueMap.put(name, value); - this.changed = true; - return old; - } - - /** - * 名前からValueを取得する。 - * @param name 名前 - * @return 対応するValue。見つからなければnull - */ - public JsValue getValue(String name){ - return this.valueMap.get(name); - } - - /** - * JSON pairを追加する。 - * @param pair JSON pair - */ - public void putPair(JsPair pair){ - putValue(pair.getName(), pair.getValue()); - return; - } - - /** - * 名前からJSON pairを返す。 - * @param name 名前 - * @return JSON Pair。見つからなければnull - */ - public JsPair getPair(String name){ - JsValue value = getValue(name); - if(value == null) return null; - - return new JsPair(name, value); - } - - /** - * 空にする。 - */ - public void clear(){ - if(this.valueMap.size() > 0) this.changed = true; - this.valueMap.clear(); - return; - } - - /** - * 指定した名前のpairを削除する。 - * @param name pairの名前 - * @return 消されたValue。該当するpairがなければnull - */ - public JsValue remove(String name){ - JsValue old = this.valueMap.remove(name); - if(old != null) this.changed = true; - return old; - } - - /** - * 保持する全pairの名前の集合を返す。 - * @return すべての名前 - */ - public Set nameSet(){ - return this.valueMap.keySet(); - } - - /** - * pairのリストを返す。 - * 格納順は名前順。 - * @return pairリスト - */ - public List getPairList(){ - List result = new ArrayList(); - - for(String name : nameSet()){ - JsPair pair = getPair(name); - result.add(pair); - } - - return result; - } - - /** - * pairにアクセスするための反復子を提供する。 - * この反復子での削除作業はできない。 - * @return 反復子イテレータ - */ - public Iterator iterator(){ - return getPairList().iterator(); - } - - /** - * pair総数を返す。 - * @return pair総数 - */ - public int size(){ - return this.valueMap.size(); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.valueMap.hashCode(); - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(this == obj) return true; - - if( ! (obj instanceof JsObject) ) return false; - JsObject composit = (JsObject) obj; - - return this.valueMap.equals(composit.valueMap); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - StringBuilder text = new StringBuilder(); - - text.append("{"); - boolean hasElem = false; - for(JsPair pair : this){ - if(hasElem) text.append(','); - try{ - JsString.writeText(text, pair.getName()); - }catch(IOException e){ - assert false; - } - text.append(':') - .append(pair.getValue()); - hasElem = true; - } - text.append("}"); - - return text.toString(); - } - - /** - * {@inheritDoc} - * @param visitor {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void traverse(ValueVisitor visitor) throws JsVisitException{ - visitor.visitValue(this); - - for(JsPair pair : this){ - String name = pair.getName(); - JsValue value = pair.getValue(); - visitor.visitPairName(name); - value.traverse(visitor); - } - - visitor.visitCollectionClose(this); - - return; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean hasChanged(){ - if(this.changed) return true; - - for(JsValue value : this.valueMap.values()){ - if(value.hasChanged()) return true; - } - - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void setUnchanged(){ - this.changed = false; - - for(JsValue value : this.valueMap.values()){ - value.setUnchanged(); - } - - return; - } - -} +/* + * JSON object value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * JSON オブジェクト Value。 + */ +public class JsObject + extends AbstractJsValue + implements Iterable { + + private final Map valueMap = + new TreeMap(); + private boolean changed = false; + + + /** + * コンストラクタ。 + */ + public JsObject(){ + super(); + return; + } + + + /** + * JSON Objectを文字ストリームからパースする。 + * @param reader 文字入力 + * @return JSON Object。入力終了ならnull + * @throws IOException 入力エラー + * @throws JsParseException パースエラー + */ + static JsObject parseObject(JsonReader reader) + throws IOException, JsParseException{ + int chData; + + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') return null; + if(chData != '{') throw new JsParseException(); + + JsObject result = new JsObject(); + + for(;;){ + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData == '}') break; + + if(result.size() <= 0){ + reader.unread(chData); + }else{ + if(chData != ',') throw new JsParseException(); + Json.skipWhiteSpace(reader); + } + + JsString name = JsString.parseString(reader); + if(name == null){ + throw new JsParseException(); + } + + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData != ':') throw new JsParseException(); + Json.skipWhiteSpace(reader); + + JsValue value = Json.parseValue(reader); + if(value == null){ + throw new JsParseException(); + } + + result.putValue(name.toRawString(), value); + } + + return result; + } + + + /** + * 名前とValueからpairを登録する。 + * @param name 名前 + * @param value Value + * @return 旧Value。同じ内容のpairがすでに存在していたらnull + * @throws NullPointerException 引数のいずれかがnull + */ + public JsValue putValue(String name, JsValue value) + throws NullPointerException{ + if(name == null) throw new NullPointerException(); + if(value == null) throw new NullPointerException(); + + JsValue oldValue = this.valueMap.get(name); + if(value.equals(oldValue)) return null; + + JsValue old = this.valueMap.put(name, value); + this.changed = true; + return old; + } + + /** + * 名前からValueを取得する。 + * @param name 名前 + * @return 対応するValue。見つからなければnull + */ + public JsValue getValue(String name){ + return this.valueMap.get(name); + } + + /** + * JSON pairを追加する。 + * @param pair JSON pair + */ + public void putPair(JsPair pair){ + putValue(pair.getName(), pair.getValue()); + return; + } + + /** + * 名前からJSON pairを返す。 + * @param name 名前 + * @return JSON Pair。見つからなければnull + */ + public JsPair getPair(String name){ + JsValue value = getValue(name); + if(value == null) return null; + + return new JsPair(name, value); + } + + /** + * 空にする。 + */ + public void clear(){ + if(this.valueMap.size() > 0) this.changed = true; + this.valueMap.clear(); + return; + } + + /** + * 指定した名前のpairを削除する。 + * @param name pairの名前 + * @return 消されたValue。該当するpairがなければnull + */ + public JsValue remove(String name){ + JsValue old = this.valueMap.remove(name); + if(old != null) this.changed = true; + return old; + } + + /** + * 保持する全pairの名前の集合を返す。 + * @return すべての名前 + */ + public Set nameSet(){ + return this.valueMap.keySet(); + } + + /** + * pairのリストを返す。 + * 格納順は名前順。 + * @return pairリスト + */ + public List getPairList(){ + List result = new ArrayList(); + + for(String name : nameSet()){ + JsPair pair = getPair(name); + result.add(pair); + } + + return result; + } + + /** + * pairにアクセスするための反復子を提供する。 + * この反復子での削除作業はできない。 + * @return 反復子イテレータ + */ + public Iterator iterator(){ + return getPairList().iterator(); + } + + /** + * pair総数を返す。 + * @return pair総数 + */ + public int size(){ + return this.valueMap.size(); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.valueMap.hashCode(); + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(this == obj) return true; + + if( ! (obj instanceof JsObject) ) return false; + JsObject composit = (JsObject) obj; + + return this.valueMap.equals(composit.valueMap); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + StringBuilder text = new StringBuilder(); + + text.append("{"); + boolean hasElem = false; + for(JsPair pair : this){ + if(hasElem) text.append(','); + try{ + JsString.writeText(text, pair.getName()); + }catch(IOException e){ + assert false; + } + text.append(':') + .append(pair.getValue()); + hasElem = true; + } + text.append("}"); + + return text.toString(); + } + + /** + * {@inheritDoc} + * @param visitor {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void traverse(ValueVisitor visitor) throws JsVisitException{ + visitor.visitValue(this); + + for(JsPair pair : this){ + String name = pair.getName(); + JsValue value = pair.getValue(); + visitor.visitPairName(name); + value.traverse(visitor); + } + + visitor.visitCollectionClose(this); + + return; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean hasChanged(){ + if(this.changed) return true; + + for(JsValue value : this.valueMap.values()){ + if(value.hasChanged()) return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setUnchanged(){ + this.changed = false; + + for(JsValue value : this.valueMap.values()){ + value.setUnchanged(); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsPair.java b/src/main/java/jp/sourceforge/jindolf/json/JsPair.java index 7d7575d..7962a0f 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsPair.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsPair.java @@ -1,123 +1,123 @@ -/* - * JSON pair in object - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; - -/** - * JSON オブジェクトValue内に列挙される、名前の付いたValueとの組。 - * 後での変更は不可能。 - */ -public class JsPair{ - - private final String name; - private final JsValue value; - - /** - * コンストラクタ。 - * @param name 名前 - * @param value JSON Value - * @throws NullPointerException 名前もしくはValueがnull - */ - public JsPair(String name, JsValue value) - throws NullPointerException{ - super(); - - if(name == null || value == null) throw new NullPointerException(); - - this.name = name; - this.value = value; - - return; - } - - /** - * コンストラクタ。 - * @param name 名前 - * @param text 文字列 - * @throws NullPointerException 名前がnull - */ - public JsPair(String name, CharSequence text) - throws NullPointerException{ - this(name, (JsValue) new JsString(text) ); - return; - } - - /** - * コンストラクタ。 - * @param name 名前 - * @param bool 真偽 - * @throws NullPointerException 名前がnull - */ - public JsPair(String name, boolean bool) - throws NullPointerException{ - this(name, JsBoolean.valueOf(bool)); - return; - } - - /** - * コンストラクタ。 - * @param name 名前 - * @param number 数値 - * @throws NullPointerException 名前がnull - */ - public JsPair(String name, long number) - throws NullPointerException{ - this(name, new JsNumber(number)); - return; - } - - /** - * コンストラクタ。 - * @param name 名前 - * @param number 数値 - * @throws NullPointerException 名前がnull - */ - public JsPair(String name, double number) - throws NullPointerException{ - this(name, new JsNumber(number)); - return; - } - - /** - * 名前を返す。 - * @return 名前 - */ - public String getName(){ - return this.name; - } - - /** - * JSON Valueを返す。 - * @return JSON Value - */ - public JsValue getValue(){ - return this.value; - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - StringBuilder text = new StringBuilder(); - - try{ - JsString.writeText(text, this.name); - }catch(IOException e){ - assert false; // NEVER! - } - - text.append(':') - .append(this.value); - - return text.toString(); - } - -} +/* + * JSON pair in object + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; + +/** + * JSON オブジェクトValue内に列挙される、名前の付いたValueとの組。 + * 後での変更は不可能。 + */ +public class JsPair{ + + private final String name; + private final JsValue value; + + /** + * コンストラクタ。 + * @param name 名前 + * @param value JSON Value + * @throws NullPointerException 名前もしくはValueがnull + */ + public JsPair(String name, JsValue value) + throws NullPointerException{ + super(); + + if(name == null || value == null) throw new NullPointerException(); + + this.name = name; + this.value = value; + + return; + } + + /** + * コンストラクタ。 + * @param name 名前 + * @param text 文字列 + * @throws NullPointerException 名前がnull + */ + public JsPair(String name, CharSequence text) + throws NullPointerException{ + this(name, (JsValue) new JsString(text) ); + return; + } + + /** + * コンストラクタ。 + * @param name 名前 + * @param bool 真偽 + * @throws NullPointerException 名前がnull + */ + public JsPair(String name, boolean bool) + throws NullPointerException{ + this(name, JsBoolean.valueOf(bool)); + return; + } + + /** + * コンストラクタ。 + * @param name 名前 + * @param number 数値 + * @throws NullPointerException 名前がnull + */ + public JsPair(String name, long number) + throws NullPointerException{ + this(name, new JsNumber(number)); + return; + } + + /** + * コンストラクタ。 + * @param name 名前 + * @param number 数値 + * @throws NullPointerException 名前がnull + */ + public JsPair(String name, double number) + throws NullPointerException{ + this(name, new JsNumber(number)); + return; + } + + /** + * 名前を返す。 + * @return 名前 + */ + public String getName(){ + return this.name; + } + + /** + * JSON Valueを返す。 + * @return JSON Value + */ + public JsValue getValue(){ + return this.value; + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + StringBuilder text = new StringBuilder(); + + try{ + JsString.writeText(text, this.name); + }catch(IOException e){ + assert false; // NEVER! + } + + text.append(':') + .append(this.value); + + return text.toString(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java b/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java index d3ef862..9716ced 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsParseException.java @@ -1,33 +1,33 @@ -/* - * JSON parse error information - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * JSON パースの異常系情報。 - */ -@SuppressWarnings("serial") -public class JsParseException extends Exception{ - - /** - * コンストラクタ。 - */ - public JsParseException(){ - super(); - return; - } - - /** - * コンストラクタ。 - * @param th 原因となった例外 - */ - public JsParseException(Throwable th){ - super(th); - return; - } - -} +/* + * JSON parse error information + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * JSON パースの異常系情報。 + */ +@SuppressWarnings("serial") +public class JsParseException extends Exception{ + + /** + * コンストラクタ。 + */ + public JsParseException(){ + super(); + return; + } + + /** + * コンストラクタ。 + * @param th 原因となった例外 + */ + public JsParseException(Throwable th){ + super(th); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsString.java b/src/main/java/jp/sourceforge/jindolf/json/JsString.java index 9f25782..b8fb391 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsString.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsString.java @@ -1,272 +1,272 @@ -/* - * JSON string value - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; - -/** - * JSON 文字列Value。 - */ -public class JsString - extends AbstractJsValue - implements CharSequence, Comparable { - - private final String text; - - - /** - * コンストラクタ。 - * 空文字が設定される。 - */ - public JsString(){ - this(null); - return; - } - - /** - * コンストラクタ。 - * 引数はJSON書式ではない。 - * @param seq 文字列。nullなら空文字が設定される。 - */ - public JsString(CharSequence seq){ - super(); - if(seq == null){ - this.text = ""; - }else{ - this.text = seq.toString(); - } - return; - } - - - /** - * FFFF形式4桁で16進エスケープされた文字列を読み、 - * 1文字にデコードする。 - * @param reader 文字入力 - * @return 文字 - * @throws IOException 入力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - static char parseHexChar(JsonReader reader) - throws IOException, JsParseException{ - int hex1 = reader.read(); - int hex2 = reader.read(); - int hex3 = reader.read(); - int hex4 = reader.read(); - if(hex4 < '\u0000') throw new JsParseException(); - - char hex1Ch = (char) hex1; - char hex2Ch = (char) hex2; - char hex3Ch = (char) hex3; - char hex4Ch = (char) hex4; - - int digit1 = Character.digit(hex1Ch, 16); - int digit2 = Character.digit(hex2Ch, 16); - int digit3 = Character.digit(hex3Ch, 16); - int digit4 = Character.digit(hex4Ch, 16); - - if(digit1 < 0) throw new JsParseException(); - if(digit2 < 0) throw new JsParseException(); - if(digit3 < 0) throw new JsParseException(); - if(digit4 < 0) throw new JsParseException(); - - int digit = 0; - digit += digit1; - digit <<= 4; - digit += digit2; - digit <<= 4; - digit += digit3; - digit <<= 4; - digit += digit4; - - char result = (char) digit; - - return result; - } - - /** - * ダブルクォーテーションで囲まれた文字列を読み込む。 - * @param reader 文字入力 - * @return 文字列Value - * @throws IOException 入力エラー - * @throws JsParseException パースエラーもしくは入力終了 - */ - static JsString parseString(JsonReader reader) - throws IOException, JsParseException{ - int chData; - - Json.skipWhiteSpace(reader); - chData = reader.read(); - if(chData < '\u0000') return null; - if(chData != '"') throw new JsParseException(); - - StringBuilder text = new StringBuilder(); - - for(;;){ - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if(chData == '"') break; - - if(chData == '\\'){ - chData = reader.read(); - if(chData < '\u0000') throw new JsParseException(); - if (chData == '"' ) text.append('"'); - else if(chData == '\\') text.append('\\'); - else if(chData == '/' ) text.append('/'); - else if(chData == 'b' ) text.append('\b'); - else if(chData == 'f' ) text.append('\f'); - else if(chData == 'n' ) text.append('\n'); - else if(chData == 'r' ) text.append('\r'); - else if(chData == 't' ) text.append('\t'); - else if(chData == 'u') text.append(parseHexChar(reader)); - else throw new JsParseException(); - }else{ - text.append((char)chData); - } - } - - JsString result = new JsString(text); - - return result; - } - - /** - * JSON 文字列Value形式で文字列を出力する。 - * @param appout 文字出力 - * @param seq 文字列 - * @throws IOException 出力エラー - */ - public static void writeText(Appendable appout, CharSequence seq) - throws IOException{ - appout.append('"'); - - int length = seq.length(); - for(int pos = 0; pos < length; pos++){ - char ch = seq.charAt(pos); - - switch(ch){ - case '"' : appout.append('\\').append('"'); break; - case '\\': appout.append('\\').append('\\'); break; - case '/' : appout.append('\\').append('/'); break; - case '\b': appout.append('\\').append('b'); break; - case '\f': appout.append('\\').append('f'); break; - case '\n': appout.append('\\').append('n'); break; - case '\r': appout.append('\\').append('r'); break; - case '\t': appout.append('\\').append('t'); break; - default: - if(Character.isISOControl(ch)){ - String hex = "0000" + Integer.toHexString(ch); - hex = hex.substring(hex.length() - 4); - appout.append("\\u").append(hex); - }else{ - appout.append(ch); - } - break; - } - } - - appout.append('"'); - - return; - } - - /** - * {@inheritDoc} - * @param index {@inheritDoc} - * @return {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - @Override - public char charAt(int index) - throws IndexOutOfBoundsException{ - return this.text.charAt(index); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int length(){ - return this.text.length(); - } - - /** - * {@inheritDoc} - * @param start {@inheritDoc} - * @param end {@inheritDoc} - * @return {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - @Override - public CharSequence subSequence(int start, int end) - throws IndexOutOfBoundsException{ - return this.text.subSequence(start, end); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int hashCode(){ - return this.text.hashCode(); - } - - /** - * {@inheritDoc} - * @param obj {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public boolean equals(Object obj){ - if(obj == null) return false; - if(this == obj) return true; - - if( ! (obj instanceof JsString) ) return false; - JsString string = (JsString) obj; - - return this.text.equals(string.text); - } - - /** - * {@inheritDoc} - * @param value {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public int compareTo(JsString value){ - if(this == value) return 0; - if(value == null) return +1; - return this.text.compareTo(value.text); - } - - /** - * {@inheritDoc} - * @return {@inheritDoc} - */ - @Override - public String toString(){ - StringBuilder string = new StringBuilder(); - try{ - writeText(string, this.text); - }catch(IOException e){ - assert false; - } - return string.toString(); - } - - /** - * クォーテーションされていない生の文字列を返す。 - * @return 生の文字列 - */ - public String toRawString(){ - return this.text; - } - -} +/* + * JSON string value + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; + +/** + * JSON 文字列Value。 + */ +public class JsString + extends AbstractJsValue + implements CharSequence, Comparable { + + private final String text; + + + /** + * コンストラクタ。 + * 空文字が設定される。 + */ + public JsString(){ + this(null); + return; + } + + /** + * コンストラクタ。 + * 引数はJSON書式ではない。 + * @param seq 文字列。nullなら空文字が設定される。 + */ + public JsString(CharSequence seq){ + super(); + if(seq == null){ + this.text = ""; + }else{ + this.text = seq.toString(); + } + return; + } + + + /** + * FFFF形式4桁で16進エスケープされた文字列を読み、 + * 1文字にデコードする。 + * @param reader 文字入力 + * @return 文字 + * @throws IOException 入力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + static char parseHexChar(JsonReader reader) + throws IOException, JsParseException{ + int hex1 = reader.read(); + int hex2 = reader.read(); + int hex3 = reader.read(); + int hex4 = reader.read(); + if(hex4 < '\u0000') throw new JsParseException(); + + char hex1Ch = (char) hex1; + char hex2Ch = (char) hex2; + char hex3Ch = (char) hex3; + char hex4Ch = (char) hex4; + + int digit1 = Character.digit(hex1Ch, 16); + int digit2 = Character.digit(hex2Ch, 16); + int digit3 = Character.digit(hex3Ch, 16); + int digit4 = Character.digit(hex4Ch, 16); + + if(digit1 < 0) throw new JsParseException(); + if(digit2 < 0) throw new JsParseException(); + if(digit3 < 0) throw new JsParseException(); + if(digit4 < 0) throw new JsParseException(); + + int digit = 0; + digit += digit1; + digit <<= 4; + digit += digit2; + digit <<= 4; + digit += digit3; + digit <<= 4; + digit += digit4; + + char result = (char) digit; + + return result; + } + + /** + * ダブルクォーテーションで囲まれた文字列を読み込む。 + * @param reader 文字入力 + * @return 文字列Value + * @throws IOException 入力エラー + * @throws JsParseException パースエラーもしくは入力終了 + */ + static JsString parseString(JsonReader reader) + throws IOException, JsParseException{ + int chData; + + Json.skipWhiteSpace(reader); + chData = reader.read(); + if(chData < '\u0000') return null; + if(chData != '"') throw new JsParseException(); + + StringBuilder text = new StringBuilder(); + + for(;;){ + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if(chData == '"') break; + + if(chData == '\\'){ + chData = reader.read(); + if(chData < '\u0000') throw new JsParseException(); + if (chData == '"' ) text.append('"'); + else if(chData == '\\') text.append('\\'); + else if(chData == '/' ) text.append('/'); + else if(chData == 'b' ) text.append('\b'); + else if(chData == 'f' ) text.append('\f'); + else if(chData == 'n' ) text.append('\n'); + else if(chData == 'r' ) text.append('\r'); + else if(chData == 't' ) text.append('\t'); + else if(chData == 'u') text.append(parseHexChar(reader)); + else throw new JsParseException(); + }else{ + text.append((char)chData); + } + } + + JsString result = new JsString(text); + + return result; + } + + /** + * JSON 文字列Value形式で文字列を出力する。 + * @param appout 文字出力 + * @param seq 文字列 + * @throws IOException 出力エラー + */ + public static void writeText(Appendable appout, CharSequence seq) + throws IOException{ + appout.append('"'); + + int length = seq.length(); + for(int pos = 0; pos < length; pos++){ + char ch = seq.charAt(pos); + + switch(ch){ + case '"' : appout.append('\\').append('"'); break; + case '\\': appout.append('\\').append('\\'); break; + case '/' : appout.append('\\').append('/'); break; + case '\b': appout.append('\\').append('b'); break; + case '\f': appout.append('\\').append('f'); break; + case '\n': appout.append('\\').append('n'); break; + case '\r': appout.append('\\').append('r'); break; + case '\t': appout.append('\\').append('t'); break; + default: + if(Character.isISOControl(ch)){ + String hex = "0000" + Integer.toHexString(ch); + hex = hex.substring(hex.length() - 4); + appout.append("\\u").append(hex); + }else{ + appout.append(ch); + } + break; + } + } + + appout.append('"'); + + return; + } + + /** + * {@inheritDoc} + * @param index {@inheritDoc} + * @return {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + @Override + public char charAt(int index) + throws IndexOutOfBoundsException{ + return this.text.charAt(index); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int length(){ + return this.text.length(); + } + + /** + * {@inheritDoc} + * @param start {@inheritDoc} + * @param end {@inheritDoc} + * @return {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + @Override + public CharSequence subSequence(int start, int end) + throws IndexOutOfBoundsException{ + return this.text.subSequence(start, end); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int hashCode(){ + return this.text.hashCode(); + } + + /** + * {@inheritDoc} + * @param obj {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public boolean equals(Object obj){ + if(obj == null) return false; + if(this == obj) return true; + + if( ! (obj instanceof JsString) ) return false; + JsString string = (JsString) obj; + + return this.text.equals(string.text); + } + + /** + * {@inheritDoc} + * @param value {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public int compareTo(JsString value){ + if(this == value) return 0; + if(value == null) return +1; + return this.text.compareTo(value.text); + } + + /** + * {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public String toString(){ + StringBuilder string = new StringBuilder(); + try{ + writeText(string, this.text); + }catch(IOException e){ + assert false; + } + return string.toString(); + } + + /** + * クォーテーションされていない生の文字列を返す。 + * @return 生の文字列 + */ + public String toRawString(){ + return this.text; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsValue.java b/src/main/java/jp/sourceforge/jindolf/json/JsValue.java index a161dae..7f04052 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsValue.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsValue.java @@ -1,33 +1,33 @@ -/* - * JSON value common interface - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * JSON 各種Value共通インタフェース。 - */ -public interface JsValue{ - - /** - * 深さ優先探索を行い各種構造の出現をビジターに通知する。 - * @param visitor ビジター - * @throws JsVisitException トラバース中断。 - */ - void traverse(ValueVisitor visitor) throws JsVisitException; - - /** - * このValueおよび子孫に変更があったか判定する。 - * @return 変更があればtrue - */ - boolean hasChanged(); - - /** - * このValueおよび子孫に変更がなかったことにする。 - */ - void setUnchanged(); - -} +/* + * JSON value common interface + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * JSON 各種Value共通インタフェース。 + */ +public interface JsValue{ + + /** + * 深さ優先探索を行い各種構造の出現をビジターに通知する。 + * @param visitor ビジター + * @throws JsVisitException トラバース中断。 + */ + void traverse(ValueVisitor visitor) throws JsVisitException; + + /** + * このValueおよび子孫に変更があったか判定する。 + * @return 変更があればtrue + */ + boolean hasChanged(); + + /** + * このValueおよび子孫に変更がなかったことにする。 + */ + void setUnchanged(); + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java b/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java index f810a46..1438269 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsVisitException.java @@ -1,33 +1,33 @@ -/* - * JSON traverse error exception - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * トラバース中断例外。 - */ -@SuppressWarnings("serial") -public class JsVisitException extends Exception{ - - /** - * コンストラクタ。 - */ - public JsVisitException(){ - super(); - return; - } - - /** - * コンストラクタ。 - * @param th 原因となった例外 - */ - public JsVisitException(Throwable th){ - super(th); - return; - } - -} +/* + * JSON traverse error exception + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * トラバース中断例外。 + */ +@SuppressWarnings("serial") +public class JsVisitException extends Exception{ + + /** + * コンストラクタ。 + */ + public JsVisitException(){ + super(); + return; + } + + /** + * コンストラクタ。 + * @param th 原因となった例外 + */ + public JsVisitException(Throwable th){ + super(th); + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/Json.java b/src/main/java/jp/sourceforge/jindolf/json/Json.java index aaa2913..d2da108 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/Json.java +++ b/src/main/java/jp/sourceforge/jindolf/json/Json.java @@ -1,194 +1,194 @@ -/* - * JSON utilities - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.IOException; -import java.io.Reader; - -/** - * JSON各種共通ユーティリティ。 - */ -public final class Json{ - - /** - * 隠しコンストラクタ。 - */ - private Json(){ - assert false; - throw new AssertionError(); - } - - - /** - * JSON最上位構造から文字出力を開始する。 - * @param appout 出力先 - * @param value JSONのObjectかArray - * @throws IOException 出力エラー - * @throws IllegalArgumentException 出力対象がObjectでもArrayでもない。 - */ - public static void writeJsonTop(Appendable appout, JsValue value) - throws IOException, - IllegalArgumentException { - if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){ - throw new IllegalArgumentException(); - } - - JsonAppender appender = new JsonAppender(appout); - - try{ - value.traverse(appender); - }catch(JsVisitException e){ - Throwable cause = e.getCause(); - if(cause instanceof IOException){ - throw (IOException) cause; - }else if(cause instanceof RuntimeException){ - throw (RuntimeException) cause; - }else if(cause instanceof Error){ - throw (Error) cause; - }else{ - assert false; - return; - } - } - - appender.flush(); - - return; - } - - /** - * JSON規格のwhitespace文字を判定する。 - * @param ch 判定対象文字 - * @return whitespaceならtrue - */ - public static boolean isWhitespace(char ch){ - if(ch == '\t' ) return true; - if(ch == '\r' ) return true; - if(ch == '\n' ) return true; - if(ch == '\u0020') return true; - return false; - } - - /** - * whitespace文字を読み飛ばす。 - * @param reader 文字入力 - * @throws IOException 入力エラー - */ - static void skipWhiteSpace(JsonReader reader) - throws IOException{ - for(;;){ - int chData = reader.read(); - if(chData < '\u0000') break; - if( ! isWhitespace((char)chData) ){ - reader.unread(chData); - break; - } - } - return; - } - - /** - * 各種定数(true,false,null)を文字ストリームから読み取る。 - * 長さ0の文字定数には無条件でfalseを返す。 - * @param reader 文字入力 - * @param text 文字定数 - * @return 文字定数が文字入力に現れればtrue。 - * 見つからないもしくはストリームの終わりに達したときはfalse - * @throws IOException 入力エラー - * @throws IllegalArgumentException 文字定数が長すぎる - */ - static boolean parseConst(JsonReader reader, - CharSequence text) - throws IOException, - IllegalArgumentException { - int textLength = text.length(); - if(textLength <= 0) return false; - if(textLength >= JsonReader.PUSHBACK_TOKENS){ - throw new IllegalArgumentException(); - } - - int[] backData = new int[textLength - 1]; - int readed = 0; - - for(;;){ - int chData = reader.read(); - if(chData != text.charAt(readed)){ - if(chData >= '\u0000') reader.unread(chData); - for(int pos = readed - 1; pos >= 0; pos--){ - reader.unread(backData[pos]); - } - break; - } - - if(readed >= backData.length) return true; - - backData[readed++] = chData; - } - - return false; - } - - /** - * JSONの各種Valueを文字ストリームから読み取る。 - * @param reader 文字入力 - * @return 各種Value - * @throws IOException 入力エラー - * @throws JsParseException パースエラー - */ - public static JsValue parseValue(Reader reader) - throws IOException, JsParseException{ - JsonReader jsreader; - if(reader instanceof JsonReader){ - jsreader = (JsonReader) reader; - }else{ - jsreader = new JsonReader(reader); - } - - return parseValue(jsreader); - } - - /** - * JSONの各種Valueを文字ストリームから読み取る。 - * @param reader 文字入力 - * @return 各種Value。ストリームの終わりに達したときはnull - * @throws IOException 入力エラー - * @throws JsParseException パースエラー - */ - static JsValue parseValue(JsonReader reader) - throws IOException, JsParseException{ - skipWhiteSpace(reader); - - if(parseConst(reader, JsNull.NULL.toString())){ - return JsNull.NULL; - }else if(parseConst(reader, JsBoolean.TRUE.toString())){ - return JsBoolean.TRUE; - }else if(parseConst(reader, JsBoolean.FALSE.toString())){ - return JsBoolean.FALSE; - } - - int head = reader.read(); - if(head < '\u0000') return null; - - if( head == '-' || ('0' <= head && head <= '9') ){ - reader.unread(head); - return JsNumber.parseNumber(reader); - }else if(head == '{'){ - reader.unread(head); - return JsObject.parseObject(reader); - }else if(head == '['){ - reader.unread(head); - return JsArray.parseArray(reader); - }else if(head == '"'){ - reader.unread(head); - return JsString.parseString(reader); - } - - throw new JsParseException(); - } - -} +/* + * JSON utilities + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.IOException; +import java.io.Reader; + +/** + * JSON各種共通ユーティリティ。 + */ +public final class Json{ + + /** + * 隠しコンストラクタ。 + */ + private Json(){ + assert false; + throw new AssertionError(); + } + + + /** + * JSON最上位構造から文字出力を開始する。 + * @param appout 出力先 + * @param value JSONのObjectかArray + * @throws IOException 出力エラー + * @throws IllegalArgumentException 出力対象がObjectでもArrayでもない。 + */ + public static void writeJsonTop(Appendable appout, JsValue value) + throws IOException, + IllegalArgumentException { + if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){ + throw new IllegalArgumentException(); + } + + JsonAppender appender = new JsonAppender(appout); + + try{ + value.traverse(appender); + }catch(JsVisitException e){ + Throwable cause = e.getCause(); + if(cause instanceof IOException){ + throw (IOException) cause; + }else if(cause instanceof RuntimeException){ + throw (RuntimeException) cause; + }else if(cause instanceof Error){ + throw (Error) cause; + }else{ + assert false; + return; + } + } + + appender.flush(); + + return; + } + + /** + * JSON規格のwhitespace文字を判定する。 + * @param ch 判定対象文字 + * @return whitespaceならtrue + */ + public static boolean isWhitespace(char ch){ + if(ch == '\t' ) return true; + if(ch == '\r' ) return true; + if(ch == '\n' ) return true; + if(ch == '\u0020') return true; + return false; + } + + /** + * whitespace文字を読み飛ばす。 + * @param reader 文字入力 + * @throws IOException 入力エラー + */ + static void skipWhiteSpace(JsonReader reader) + throws IOException{ + for(;;){ + int chData = reader.read(); + if(chData < '\u0000') break; + if( ! isWhitespace((char)chData) ){ + reader.unread(chData); + break; + } + } + return; + } + + /** + * 各種定数(true,false,null)を文字ストリームから読み取る。 + * 長さ0の文字定数には無条件でfalseを返す。 + * @param reader 文字入力 + * @param text 文字定数 + * @return 文字定数が文字入力に現れればtrue。 + * 見つからないもしくはストリームの終わりに達したときはfalse + * @throws IOException 入力エラー + * @throws IllegalArgumentException 文字定数が長すぎる + */ + static boolean parseConst(JsonReader reader, + CharSequence text) + throws IOException, + IllegalArgumentException { + int textLength = text.length(); + if(textLength <= 0) return false; + if(textLength >= JsonReader.PUSHBACK_TOKENS){ + throw new IllegalArgumentException(); + } + + int[] backData = new int[textLength - 1]; + int readed = 0; + + for(;;){ + int chData = reader.read(); + if(chData != text.charAt(readed)){ + if(chData >= '\u0000') reader.unread(chData); + for(int pos = readed - 1; pos >= 0; pos--){ + reader.unread(backData[pos]); + } + break; + } + + if(readed >= backData.length) return true; + + backData[readed++] = chData; + } + + return false; + } + + /** + * JSONの各種Valueを文字ストリームから読み取る。 + * @param reader 文字入力 + * @return 各種Value + * @throws IOException 入力エラー + * @throws JsParseException パースエラー + */ + public static JsValue parseValue(Reader reader) + throws IOException, JsParseException{ + JsonReader jsreader; + if(reader instanceof JsonReader){ + jsreader = (JsonReader) reader; + }else{ + jsreader = new JsonReader(reader); + } + + return parseValue(jsreader); + } + + /** + * JSONの各種Valueを文字ストリームから読み取る。 + * @param reader 文字入力 + * @return 各種Value。ストリームの終わりに達したときはnull + * @throws IOException 入力エラー + * @throws JsParseException パースエラー + */ + static JsValue parseValue(JsonReader reader) + throws IOException, JsParseException{ + skipWhiteSpace(reader); + + if(parseConst(reader, JsNull.NULL.toString())){ + return JsNull.NULL; + }else if(parseConst(reader, JsBoolean.TRUE.toString())){ + return JsBoolean.TRUE; + }else if(parseConst(reader, JsBoolean.FALSE.toString())){ + return JsBoolean.FALSE; + } + + int head = reader.read(); + if(head < '\u0000') return null; + + if( head == '-' || ('0' <= head && head <= '9') ){ + reader.unread(head); + return JsNumber.parseNumber(reader); + }else if(head == '{'){ + reader.unread(head); + return JsObject.parseObject(reader); + }else if(head == '['){ + reader.unread(head); + return JsArray.parseArray(reader); + }else if(head == '"'){ + reader.unread(head); + return JsString.parseString(reader); + } + + throw new JsParseException(); + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java b/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java index db38715..244dba8 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsonAppender.java @@ -1,285 +1,285 @@ -/* - * JSON string output - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.Flushable; -import java.io.IOException; -import java.util.Stack; - -/** - * JSON文字出力用ビジター。 - * JSON Valueのトラバース時にこのビジターを指定すると、 - * 事前に用意した文字出力先にJSONフォーマットで出力される - */ -class JsonAppender - implements ValueVisitor, - Flushable { - - private static final String NEWLINE = "\n"; - private static final String INDENT_UNIT = "\u0020\u0020"; - private static final String HASH_SEPARATOR = "\u0020:\u0020"; - private static final String ELEM_DELIMITOR = "\u0020,"; - - - private final Appendable appout; - - private final Stack valueStack = new Stack(); - private final Stack hasChildStack = new Stack(); - - private boolean afterPairName = false; - - - /** - * コンストラクタ。 - * @param appout 出力先 - */ - public JsonAppender(Appendable appout){ - super(); - this.appout = appout; - return; - } - - /** - * 1文字出力。 - * @param ch 文字 - * @throws IOException 出力エラー - */ - protected void append(char ch) throws IOException{ - this.appout.append(ch); - return; - } - - /** - * 文字列出力。 - * @param seq 文字列 - * @throws IOException 出力エラー - */ - protected void append(CharSequence seq) throws IOException{ - this.appout.append(seq); - return; - } - - /** - * 最後の改行を出力した後、可能であれば出力先をフラッシュする。 - * @throws IOException 出力エラー - */ - public void flush() throws IOException{ - putNewLine(); - if(this.appout instanceof Flushable){ - ((Flushable)this.appout).flush(); - } - return; - } - - /** - * 改行を出力する。 - * @throws IOException 出力エラー - */ - protected void putNewLine() - throws IOException{ - append(NEWLINE); - return; - } - - /** - * インデントを出力する。 - * @throws IOException 出力エラー - */ - protected void indentOut() throws IOException{ - int level = stackLength(); - for(int ct = 1; ct <= level; ct++){ - append(INDENT_UNIT); - } - return; - } - - /** - * 要素間区切りコンマを出力する。 - * JSONでは最後の要素の後にコンマを出力してはいけない。 - * @throws IOException 出力エラー - */ - protected void putElemDelimitor() - throws IOException{ - append(ELEM_DELIMITOR); - return; - } - - /** - * pairの名前を出力する。 - * @param name pair名 - * @throws IOException 出力エラー - */ - protected void putPairName(String name) - throws IOException{ - JsString.writeText(this.appout, name); - return; - } - - /** - * pair区切りコロンを出力する。 - * @throws IOException 出力エラー - */ - protected void putPairSeparator() - throws IOException{ - append(HASH_SEPARATOR); - return; - } - - /** - * 一段ネストする。 - * @param value JSON Value - * @throws IllegalArgumentException 引数がObjectでもArrayでもなかった - */ - protected void pushValue(JsValue value) - throws IllegalArgumentException{ - if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){ - throw new IllegalArgumentException(); - } - - this.valueStack.push(value); - this.hasChildStack.push(false); - - return; - } - - /** - * ネストを一段解除する。 - * @return 最後にネストしていたValue - */ - protected JsValue popValue(){ - this.hasChildStack.pop(); - return this.valueStack.pop(); - } - - /** - * ネストのスタック段数を返す。 - * @return 段数 - */ - protected int stackLength(){ - return this.valueStack.size(); - } - - /** - * ネスト後、一つでも子要素が出力されたか判定する。 - * @return 子要素が出力されていればtrue - */ - protected boolean hasChildOut(){ - if(stackLength() <= 0) return false; - return this.hasChildStack.peek(); - } - - /** - * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。 - */ - protected void setChildOut(){ - if(stackLength() <= 0) return; - this.hasChildStack.pop(); - this.hasChildStack.push(true); - } - - /** - * {@inheritDoc} - * Valueの出力を行う。 - * @param value {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void visitValue(JsValue value) - throws JsVisitException{ - try{ - if( ! this.afterPairName ){ - if(hasChildOut()){ - putElemDelimitor(); - } - putNewLine(); - indentOut(); - } - this.afterPairName = false; - - setChildOut(); - - if(value instanceof JsObject){ - append('{'); - pushValue(value); - }else if(value instanceof JsArray){ - append('['); - pushValue(value); - }else{ - append(value.toString()); - } - }catch(IOException e){ - throw new JsVisitException(e); - } - - return; - } - - /** - * {@inheritDoc} - * pairの名前を出力する。 - * @param name {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void visitPairName(String name) - throws JsVisitException{ - try{ - if(hasChildOut()){ - putElemDelimitor(); - } - putNewLine(); - indentOut(); - putPairName(name); - putPairSeparator(); - }catch(IOException e){ - throw new JsVisitException(e); - } - - setChildOut(); - this.afterPairName = true; - - return; - } - - /** - * {@inheritDoc} - * 閉じ括弧を出力する。 - * @param composite {@inheritDoc} - * @throws JsVisitException {@inheritDoc} - */ - @Override - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - boolean hasChild = hasChildOut(); - - JsValue value = popValue(); - - try{ - if(hasChild){ - putNewLine(); - indentOut(); - }else{ - append('\u0020'); - } - - if(value instanceof JsObject){ - append('}'); - }else if(value instanceof JsArray){ - append(']'); - }else{ - assert false; - throw new JsVisitException(); - } - }catch(IOException e){ - throw new JsVisitException(e); - } - - return; - } - -} +/* + * JSON string output + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.Flushable; +import java.io.IOException; +import java.util.Stack; + +/** + * JSON文字出力用ビジター。 + * JSON Valueのトラバース時にこのビジターを指定すると、 + * 事前に用意した文字出力先にJSONフォーマットで出力される + */ +class JsonAppender + implements ValueVisitor, + Flushable { + + private static final String NEWLINE = "\n"; + private static final String INDENT_UNIT = "\u0020\u0020"; + private static final String HASH_SEPARATOR = "\u0020:\u0020"; + private static final String ELEM_DELIMITOR = "\u0020,"; + + + private final Appendable appout; + + private final Stack valueStack = new Stack(); + private final Stack hasChildStack = new Stack(); + + private boolean afterPairName = false; + + + /** + * コンストラクタ。 + * @param appout 出力先 + */ + public JsonAppender(Appendable appout){ + super(); + this.appout = appout; + return; + } + + /** + * 1文字出力。 + * @param ch 文字 + * @throws IOException 出力エラー + */ + protected void append(char ch) throws IOException{ + this.appout.append(ch); + return; + } + + /** + * 文字列出力。 + * @param seq 文字列 + * @throws IOException 出力エラー + */ + protected void append(CharSequence seq) throws IOException{ + this.appout.append(seq); + return; + } + + /** + * 最後の改行を出力した後、可能であれば出力先をフラッシュする。 + * @throws IOException 出力エラー + */ + public void flush() throws IOException{ + putNewLine(); + if(this.appout instanceof Flushable){ + ((Flushable)this.appout).flush(); + } + return; + } + + /** + * 改行を出力する。 + * @throws IOException 出力エラー + */ + protected void putNewLine() + throws IOException{ + append(NEWLINE); + return; + } + + /** + * インデントを出力する。 + * @throws IOException 出力エラー + */ + protected void indentOut() throws IOException{ + int level = stackLength(); + for(int ct = 1; ct <= level; ct++){ + append(INDENT_UNIT); + } + return; + } + + /** + * 要素間区切りコンマを出力する。 + * JSONでは最後の要素の後にコンマを出力してはいけない。 + * @throws IOException 出力エラー + */ + protected void putElemDelimitor() + throws IOException{ + append(ELEM_DELIMITOR); + return; + } + + /** + * pairの名前を出力する。 + * @param name pair名 + * @throws IOException 出力エラー + */ + protected void putPairName(String name) + throws IOException{ + JsString.writeText(this.appout, name); + return; + } + + /** + * pair区切りコロンを出力する。 + * @throws IOException 出力エラー + */ + protected void putPairSeparator() + throws IOException{ + append(HASH_SEPARATOR); + return; + } + + /** + * 一段ネストする。 + * @param value JSON Value + * @throws IllegalArgumentException 引数がObjectでもArrayでもなかった + */ + protected void pushValue(JsValue value) + throws IllegalArgumentException{ + if( ! (value instanceof JsObject) && ! (value instanceof JsArray) ){ + throw new IllegalArgumentException(); + } + + this.valueStack.push(value); + this.hasChildStack.push(false); + + return; + } + + /** + * ネストを一段解除する。 + * @return 最後にネストしていたValue + */ + protected JsValue popValue(){ + this.hasChildStack.pop(); + return this.valueStack.pop(); + } + + /** + * ネストのスタック段数を返す。 + * @return 段数 + */ + protected int stackLength(){ + return this.valueStack.size(); + } + + /** + * ネスト後、一つでも子要素が出力されたか判定する。 + * @return 子要素が出力されていればtrue + */ + protected boolean hasChildOut(){ + if(stackLength() <= 0) return false; + return this.hasChildStack.peek(); + } + + /** + * 現時点でのネストに対し、子要素が一つ以上出力済みであると設定する。 + */ + protected void setChildOut(){ + if(stackLength() <= 0) return; + this.hasChildStack.pop(); + this.hasChildStack.push(true); + } + + /** + * {@inheritDoc} + * Valueの出力を行う。 + * @param value {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void visitValue(JsValue value) + throws JsVisitException{ + try{ + if( ! this.afterPairName ){ + if(hasChildOut()){ + putElemDelimitor(); + } + putNewLine(); + indentOut(); + } + this.afterPairName = false; + + setChildOut(); + + if(value instanceof JsObject){ + append('{'); + pushValue(value); + }else if(value instanceof JsArray){ + append('['); + pushValue(value); + }else{ + append(value.toString()); + } + }catch(IOException e){ + throw new JsVisitException(e); + } + + return; + } + + /** + * {@inheritDoc} + * pairの名前を出力する。 + * @param name {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void visitPairName(String name) + throws JsVisitException{ + try{ + if(hasChildOut()){ + putElemDelimitor(); + } + putNewLine(); + indentOut(); + putPairName(name); + putPairSeparator(); + }catch(IOException e){ + throw new JsVisitException(e); + } + + setChildOut(); + this.afterPairName = true; + + return; + } + + /** + * {@inheritDoc} + * 閉じ括弧を出力する。 + * @param composite {@inheritDoc} + * @throws JsVisitException {@inheritDoc} + */ + @Override + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + boolean hasChild = hasChildOut(); + + JsValue value = popValue(); + + try{ + if(hasChild){ + putNewLine(); + indentOut(); + }else{ + append('\u0020'); + } + + if(value instanceof JsObject){ + append('}'); + }else if(value instanceof JsArray){ + append(']'); + }else{ + assert false; + throw new JsVisitException(); + } + }catch(IOException e){ + throw new JsVisitException(e); + } + + return; + } + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java b/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java index b6a58df..6860668 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java +++ b/src/main/java/jp/sourceforge/jindolf/json/JsonReader.java @@ -1,38 +1,38 @@ -/* - * JSON raeder - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.PushbackReader; -import java.io.Reader; - -/** - * JSONデータ用入力文字ストリーム。 - */ -class JsonReader extends PushbackReader{ - - /** 入力ストリームに必要なプッシュバック文字数。 */ - public static final int PUSHBACK_TOKENS = 10; - - static{ - assert JsBoolean.TRUE .toString().length() < PUSHBACK_TOKENS; - assert JsBoolean.FALSE.toString().length() < PUSHBACK_TOKENS; - assert JsNull .NULL .toString().length() < PUSHBACK_TOKENS; - assert "\\uXXXX" .length() < PUSHBACK_TOKENS; - } - - /** - * コンストラクタ。 - * @param reader 文字入力 - */ - public JsonReader(Reader reader){ - super(reader, PUSHBACK_TOKENS); - return; - } - - // TODO エラー報告用に行数、文字数をカウント -} +/* + * JSON raeder + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.PushbackReader; +import java.io.Reader; + +/** + * JSONデータ用入力文字ストリーム。 + */ +class JsonReader extends PushbackReader{ + + /** 入力ストリームに必要なプッシュバック文字数。 */ + public static final int PUSHBACK_TOKENS = 10; + + static{ + assert JsBoolean.TRUE .toString().length() < PUSHBACK_TOKENS; + assert JsBoolean.FALSE.toString().length() < PUSHBACK_TOKENS; + assert JsNull .NULL .toString().length() < PUSHBACK_TOKENS; + assert "\\uXXXX" .length() < PUSHBACK_TOKENS; + } + + /** + * コンストラクタ。 + * @param reader 文字入力 + */ + public JsonReader(Reader reader){ + super(reader, PUSHBACK_TOKENS); + return; + } + + // TODO エラー報告用に行数、文字数をカウント +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java b/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java index 6ffef21..9eccf64 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java +++ b/src/main/java/jp/sourceforge/jindolf/json/ValueVisitor.java @@ -1,36 +1,36 @@ -/* - * JSON value visitor - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -/** - * Valueへのビジター共通インタフェース。 - */ -public interface ValueVisitor{ - - /** - * Value登場の通知を受け取る。 - * @param value JSON Value - * @throws JsVisitException トラバース中止 - */ - void visitValue(JsValue value) throws JsVisitException; - - /** - * pair名登場の通知を受け取る。 - * @param name pair名 - * @throws JsVisitException トラバース中止 - */ - void visitPairName(String name) throws JsVisitException; - - /** - * 括弧終了の通知を受け取る。 - * @param composite JSON Object か JSON Array - * @throws JsVisitException トラバース中止 - */ - void visitCollectionClose(JsValue composite) throws JsVisitException; - -} +/* + * JSON value visitor + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +/** + * Valueへのビジター共通インタフェース。 + */ +public interface ValueVisitor{ + + /** + * Value登場の通知を受け取る。 + * @param value JSON Value + * @throws JsVisitException トラバース中止 + */ + void visitValue(JsValue value) throws JsVisitException; + + /** + * pair名登場の通知を受け取る。 + * @param name pair名 + * @throws JsVisitException トラバース中止 + */ + void visitPairName(String name) throws JsVisitException; + + /** + * 括弧終了の通知を受け取る。 + * @param composite JSON Object か JSON Array + * @throws JsVisitException トラバース中止 + */ + void visitCollectionClose(JsValue composite) throws JsVisitException; + +} diff --git a/src/main/java/jp/sourceforge/jindolf/json/package-info.java b/src/main/java/jp/sourceforge/jindolf/json/package-info.java index 8691c23..78ab92f 100644 --- a/src/main/java/jp/sourceforge/jindolf/json/package-info.java +++ b/src/main/java/jp/sourceforge/jindolf/json/package-info.java @@ -1,57 +1,57 @@ -/* - * JSON Library パッケージコメント - * - * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、 - * 特別な名前を持つソースファイルです。 - * このファイルはソースコードを含まず、 - * パッケージコメントとパッケージ宣言のみが含まれます。 - * - * License : The MIT License - * Copyright(c) 2009 olyutorskii - */ - -/** - * このライブラリは - * JSON形式のデータファイルの読み書きを行うために作られました。 - * - *
- * - *

- * The MIT License - *

- *

- * Copyright(c) 2009 olyutorskii - *

- *

- * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - *

- *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - *

- * - *
- * - * @see JSONの紹介 - * @see RFC4627 - * @see - * Wikipedia解説 - */ - -package jp.sourceforge.jindolf.json; - -/* EOF */ +/* + * JSON Library パッケージコメント + * + * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、 + * 特別な名前を持つソースファイルです。 + * このファイルはソースコードを含まず、 + * パッケージコメントとパッケージ宣言のみが含まれます。 + * + * License : The MIT License + * Copyright(c) 2009 olyutorskii + */ + +/** + * このライブラリは + * JSON形式のデータファイルの読み書きを行うために作られました。 + * + *
+ * + *

+ * The MIT License + *

+ *

+ * Copyright(c) 2009 olyutorskii + *

+ *

+ * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + *

+ *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + *

+ * + *
+ * + * @see JSONの紹介 + * @see RFC4627 + * @see + * Wikipedia解説 + */ + +package jp.sourceforge.jindolf.json; + +/* EOF */ diff --git a/src/main/java/jp/sourceforge/jindolf/package-info.java b/src/main/java/jp/sourceforge/jindolf/package-info.java index c6fb85a..f0851c2 100644 --- a/src/main/java/jp/sourceforge/jindolf/package-info.java +++ b/src/main/java/jp/sourceforge/jindolf/package-info.java @@ -1,62 +1,62 @@ -/* - * Jindolf パッケージコメント - * - * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、 - * 特別な名前を持つソースファイルです。 - * このファイルはソースコードを含まず、 - * パッケージコメントとパッケージ宣言のみが含まれます。 - * - * License : The MIT License - * Copyright(c) 2008 olyutorskii - */ - -/** - * これはSwingアプリケーション「Jindolf」を構成するパッケージです。 - * - * Jindolf プロジェクトは、 - * CGIゲーム - * 「人狼BBS」 - * を快適にプレイするための専用クライアントを製作するために発足した - * オープンソースプロジェクトです。 - * - *
- * - *

- * The MIT License - *

- *

- * Copyright(c) 2008 olyutorskii - *

- *

- * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - *

- *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - *

- * - *
- * - * @see - * Jindolfポータルサイト - * @see - * Jindolf開発プロジェクト - */ - -package jp.sourceforge.jindolf; - -/* EOF */ +/* + * Jindolf パッケージコメント + * + * このファイルは、SunJDK5.0以降に含まれるJavadoc用に用意された、 + * 特別な名前を持つソースファイルです。 + * このファイルはソースコードを含まず、 + * パッケージコメントとパッケージ宣言のみが含まれます。 + * + * License : The MIT License + * Copyright(c) 2008 olyutorskii + */ + +/** + * これはSwingアプリケーション「Jindolf」を構成するパッケージです。 + * + * Jindolf プロジェクトは、 + * CGIゲーム + * 「人狼BBS」 + * を快適にプレイするための専用クライアントを製作するために発足した + * オープンソースプロジェクトです。 + * + *
+ * + *

+ * The MIT License + *

+ *

+ * Copyright(c) 2008 olyutorskii + *

+ *

+ * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + *

+ *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + *

+ * + *
+ * + * @see + * Jindolfポータルサイト + * @see + * Jindolf開発プロジェクト + */ + +package jp.sourceforge.jindolf; + +/* EOF */ diff --git a/src/test/java/jp/sourceforge/jindolf/AvatarTest.java b/src/test/java/jp/sourceforge/jindolf/AvatarTest.java index 6f6c0d9..79557c4 100644 --- a/src/test/java/jp/sourceforge/jindolf/AvatarTest.java +++ b/src/test/java/jp/sourceforge/jindolf/AvatarTest.java @@ -1,185 +1,185 @@ -/* - * Avatar Test - * - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class AvatarTest { - - public AvatarTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of getPredefinedAvatarList method, of class Avatar. - */ - @Test - public void testGetPredefinedAvatarList(){ - System.out.println("getPredefinedAvatarList"); - List result = Avatar.getPredefinedAvatarList(); - assertNotNull(result); - assertEquals(20, result.size()); - return; - } - - /** - * Test of getPredefinedAvatar method, of class Avatar. - */ - @Test - public void testGetPredefinedAvatar(){ - System.out.println("getPredefinedAvatar"); - Avatar result; - result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertNotNull(result); - assertTrue(result.equals(result)); - result = Avatar.getPredefinedAvatar((CharSequence)"農夫 ヤコブ"); - assertNotNull(result); - assertTrue(result.equals(result)); - return; - } - - /** - * Test of lookingAtAvatar method, of class Avatar. - */ - @Test - public void testMatchAvatar(){ - System.out.println("matchAvatar"); - Matcher matcher; - Avatar avatar; - - Pattern pattern = Pattern.compile(".+"); - - matcher = pattern.matcher("農夫 ヤコブ"); - avatar = Avatar.lookingAtAvatar(matcher); - assertNotNull(avatar); - assertEquals("農夫 ヤコブ", avatar.getFullName()); - - matcher = pattern.matcher("農夫 ヤコブXYZ"); - avatar = Avatar.lookingAtAvatar(matcher); - assertNotNull(avatar); - assertEquals("農夫 ヤコブ", avatar.getFullName()); - - matcher = pattern.matcher("ABC農夫 ヤコブ"); - avatar = Avatar.lookingAtAvatar(matcher); - assertNull(avatar); - - matcher = pattern.matcher("農夫 ヤコブならず者 ディーター"); - avatar = Avatar.lookingAtAvatar(matcher); - assertNotNull(avatar); - assertEquals("農夫 ヤコブ", avatar.getFullName()); - int regionStart; - int regionEnd; - regionStart = matcher.end(); - regionEnd = matcher.regionEnd(); - matcher.region(regionStart, regionEnd); - avatar = Avatar.lookingAtAvatar(matcher); - assertNotNull(avatar); - assertEquals("ならず者 ディーター", avatar.getFullName()); - - return; - } - - /** - * Test of getFullName method, of class Avatar. - */ - @Test - public void testGetFullName(){ - System.out.println("getFullName"); - Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertNotNull(result); - assertEquals("農夫 ヤコブ", result.getFullName()); - return; - } - - /** - * Test of getJobTitle method, of class Avatar. - */ - @Test - public void testGetJobTitle(){ - System.out.println("getJobTitle"); - Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertNotNull(result); - assertEquals("農夫", result.getJobTitle()); - return; - } - - /** - * Test of getName method, of class Avatar. - */ - @Test - public void testGetName(){ - System.out.println("getName"); - Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertNotNull(result); - assertEquals("ヤコブ", result.getName()); - return; - } - - /** - * Test of getIdNum method, of class Avatar. - */ - @Test - public void testGetIdNum(){ - System.out.println("getIdNum"); - Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertNotNull(result); - assertEquals(15, result.getIdNum()); - return; - } - - /** - * Test of equals method, of class Avatar. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - assertTrue(result.equals(result)); - return; - } - - /** - * Test of compareTo method, of class Avatar. - */ - @Test - public void testCompareTo(){ - System.out.println("compareTo"); - Avatar avatar1 = Avatar.getPredefinedAvatar("農夫 ヤコブ"); - Avatar avatar2 = Avatar.getPredefinedAvatar("シスター フリーデル"); - Avatar avatar3 = Avatar.getPredefinedAvatar("羊飼い カタリナ"); - assertTrue(avatar1.compareTo(avatar2) < 0); - assertTrue(avatar2.compareTo(avatar3) > 0); - assertTrue(avatar2.compareTo(avatar2) == 0); - return; - } -} +/* + * Avatar Test + * + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class AvatarTest { + + public AvatarTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getPredefinedAvatarList method, of class Avatar. + */ + @Test + public void testGetPredefinedAvatarList(){ + System.out.println("getPredefinedAvatarList"); + List result = Avatar.getPredefinedAvatarList(); + assertNotNull(result); + assertEquals(20, result.size()); + return; + } + + /** + * Test of getPredefinedAvatar method, of class Avatar. + */ + @Test + public void testGetPredefinedAvatar(){ + System.out.println("getPredefinedAvatar"); + Avatar result; + result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertNotNull(result); + assertTrue(result.equals(result)); + result = Avatar.getPredefinedAvatar((CharSequence)"農夫 ヤコブ"); + assertNotNull(result); + assertTrue(result.equals(result)); + return; + } + + /** + * Test of lookingAtAvatar method, of class Avatar. + */ + @Test + public void testMatchAvatar(){ + System.out.println("matchAvatar"); + Matcher matcher; + Avatar avatar; + + Pattern pattern = Pattern.compile(".+"); + + matcher = pattern.matcher("農夫 ヤコブ"); + avatar = Avatar.lookingAtAvatar(matcher); + assertNotNull(avatar); + assertEquals("農夫 ヤコブ", avatar.getFullName()); + + matcher = pattern.matcher("農夫 ヤコブXYZ"); + avatar = Avatar.lookingAtAvatar(matcher); + assertNotNull(avatar); + assertEquals("農夫 ヤコブ", avatar.getFullName()); + + matcher = pattern.matcher("ABC農夫 ヤコブ"); + avatar = Avatar.lookingAtAvatar(matcher); + assertNull(avatar); + + matcher = pattern.matcher("農夫 ヤコブならず者 ディーター"); + avatar = Avatar.lookingAtAvatar(matcher); + assertNotNull(avatar); + assertEquals("農夫 ヤコブ", avatar.getFullName()); + int regionStart; + int regionEnd; + regionStart = matcher.end(); + regionEnd = matcher.regionEnd(); + matcher.region(regionStart, regionEnd); + avatar = Avatar.lookingAtAvatar(matcher); + assertNotNull(avatar); + assertEquals("ならず者 ディーター", avatar.getFullName()); + + return; + } + + /** + * Test of getFullName method, of class Avatar. + */ + @Test + public void testGetFullName(){ + System.out.println("getFullName"); + Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertNotNull(result); + assertEquals("農夫 ヤコブ", result.getFullName()); + return; + } + + /** + * Test of getJobTitle method, of class Avatar. + */ + @Test + public void testGetJobTitle(){ + System.out.println("getJobTitle"); + Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertNotNull(result); + assertEquals("農夫", result.getJobTitle()); + return; + } + + /** + * Test of getName method, of class Avatar. + */ + @Test + public void testGetName(){ + System.out.println("getName"); + Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertNotNull(result); + assertEquals("ヤコブ", result.getName()); + return; + } + + /** + * Test of getIdNum method, of class Avatar. + */ + @Test + public void testGetIdNum(){ + System.out.println("getIdNum"); + Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertNotNull(result); + assertEquals(15, result.getIdNum()); + return; + } + + /** + * Test of equals method, of class Avatar. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + Avatar result = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + assertTrue(result.equals(result)); + return; + } + + /** + * Test of compareTo method, of class Avatar. + */ + @Test + public void testCompareTo(){ + System.out.println("compareTo"); + Avatar avatar1 = Avatar.getPredefinedAvatar("農夫 ヤコブ"); + Avatar avatar2 = Avatar.getPredefinedAvatar("シスター フリーデル"); + Avatar avatar3 = Avatar.getPredefinedAvatar("羊飼い カタリナ"); + assertTrue(avatar1.compareTo(avatar2) < 0); + assertTrue(avatar2.compareTo(avatar3) > 0); + assertTrue(avatar2.compareTo(avatar2) == 0); + return; + } +} diff --git a/src/test/java/jp/sourceforge/jindolf/HttpUtilsTest.java b/src/test/java/jp/sourceforge/jindolf/HttpUtilsTest.java index 5dcf9d6..b674bb3 100644 --- a/src/test/java/jp/sourceforge/jindolf/HttpUtilsTest.java +++ b/src/test/java/jp/sourceforge/jindolf/HttpUtilsTest.java @@ -1,93 +1,93 @@ -/* - * HttpUtils Test - * - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * - */ -public class HttpUtilsTest { - - public HttpUtilsTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of getHTMLCharset method, of class HttpUtils. - */ - @Test - public void getHTMLCharset() { - System.out.println("getHTMLCharset"); - String contentType; - - contentType = "text/html;charset = Shift_JIS"; - String result = HttpUtils.getHTMLCharset(contentType); - assertEquals("Shift_JIS", result); - - contentType = "text/html ; charset=Shift_JIS ; a = b ; d=\"xyz\" "; - result = HttpUtils.getHTMLCharset(contentType); - assertEquals("Shift_JIS", result); - - return; - } - - /** - * Test of escapeHttpComment method, of class HttpUtils. - */ - @Test - public void testEscapeHttpComment(){ - System.out.println("escapeHttpComment"); - - CharSequence comment; - String expResult; - String result; - - comment = "abc"; - expResult = "(abc)"; - result = HttpUtils.escapeHttpComment(comment); - assertEquals(expResult, result); - - comment = "abc(pqr)xyz"; - expResult = "(abc\\(pqr\\)xyz)"; - result = HttpUtils.escapeHttpComment(comment); - assertEquals(expResult, result); - - comment = "a\nb"; - expResult = "(a?b)"; - result = HttpUtils.escapeHttpComment(comment); - assertEquals(expResult, result); - - comment = "a狼b"; - expResult = "(a?b)"; - result = HttpUtils.escapeHttpComment(comment); - assertEquals(expResult, result); - - return; - } -} +/* + * HttpUtils Test + * + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * + */ +public class HttpUtilsTest { + + public HttpUtilsTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getHTMLCharset method, of class HttpUtils. + */ + @Test + public void getHTMLCharset() { + System.out.println("getHTMLCharset"); + String contentType; + + contentType = "text/html;charset = Shift_JIS"; + String result = HttpUtils.getHTMLCharset(contentType); + assertEquals("Shift_JIS", result); + + contentType = "text/html ; charset=Shift_JIS ; a = b ; d=\"xyz\" "; + result = HttpUtils.getHTMLCharset(contentType); + assertEquals("Shift_JIS", result); + + return; + } + + /** + * Test of escapeHttpComment method, of class HttpUtils. + */ + @Test + public void testEscapeHttpComment(){ + System.out.println("escapeHttpComment"); + + CharSequence comment; + String expResult; + String result; + + comment = "abc"; + expResult = "(abc)"; + result = HttpUtils.escapeHttpComment(comment); + assertEquals(expResult, result); + + comment = "abc(pqr)xyz"; + expResult = "(abc\\(pqr\\)xyz)"; + result = HttpUtils.escapeHttpComment(comment); + assertEquals(expResult, result); + + comment = "a\nb"; + expResult = "(a?b)"; + result = HttpUtils.escapeHttpComment(comment); + assertEquals(expResult, result); + + comment = "a狼b"; + expResult = "(a?b)"; + result = HttpUtils.escapeHttpComment(comment); + assertEquals(expResult, result); + + return; + } +} diff --git a/src/test/java/jp/sourceforge/jindolf/StringUtilsTest.java b/src/test/java/jp/sourceforge/jindolf/StringUtilsTest.java index 31a6043..9efaf9f 100644 --- a/src/test/java/jp/sourceforge/jindolf/StringUtilsTest.java +++ b/src/test/java/jp/sourceforge/jindolf/StringUtilsTest.java @@ -1,277 +1,277 @@ -/* - * StringUtils Test - * - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class StringUtilsTest { - - public StringUtilsTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of parseInt method, of class StringUtils. - */ - @Test - public void testParseInt_3args_1(){ - System.out.println("parseInt"); - - int result; - Matcher matcher; - Pattern pattern; - String input = "ABC123PQR456XYZ"; - - pattern = Pattern.compile("([0-9]+)[A-Z]*([0-9]+)"); - matcher = pattern.matcher(input); - - assertTrue(matcher.find()); - - result = StringUtils.parseInt(input, matcher, 1); - assertEquals(123, result); - - result = StringUtils.parseInt(input, matcher, 2); - assertEquals(456, result); - - try{ - result = StringUtils.parseInt(null, matcher, 1); - fail(); - }catch(NullPointerException e){ - } - - try{ - result = StringUtils.parseInt(input, null, 1); - fail(); - }catch(NullPointerException e){ - } - - return; - } - - /** - * Test of parseInt method, of class StringUtils. - */ - @Test - public void testParseInt_CharSequence(){ - System.out.println("parseInt"); - - int result; - - try{ - result = StringUtils.parseInt(null); - fail(); - }catch(NullPointerException e){ - } - - result = StringUtils.parseInt(""); - assertEquals(0, result); - - result = StringUtils.parseInt("0"); - assertEquals(0, result); - - result = StringUtils.parseInt("999"); - assertEquals(999, result); - - result = StringUtils.parseInt("X"); - assertEquals(0, result); - - result = StringUtils.parseInt("-1"); - assertEquals(0, result); - - return; - } - - /** - * Test of parseInt method, of class StringUtils. - */ - @Test - public void testParseInt_3args_2(){ - System.out.println("parseInt"); - - int result; - - try{ - result = StringUtils.parseInt(null, 1, 3); - fail(); - }catch(NullPointerException e){ - } - - result = StringUtils.parseInt("1234567", 2, 5); - assertEquals(345, result); - - result = StringUtils.parseInt("1234567", 2, 3); - assertEquals(3, result); - - result = StringUtils.parseInt("1234567", 2, 2); - assertEquals(0, result); - - result = StringUtils.parseInt("1234567", 2, 1); - assertEquals(0, result); - - result = StringUtils.parseInt("1234567", 0, 0); - assertEquals(0, result); - - try{ - result = StringUtils.parseInt("1234567", 2, 999); - fail(); - }catch(StringIndexOutOfBoundsException e){ - } - - try{ - result = StringUtils.parseInt("1234567", -1, 5); - fail(); - }catch(StringIndexOutOfBoundsException e){ - } - - return; - } - - /** - * Test of suppressString method, of class StringUtils. - */ - @Test - public void testSuppressString(){ - System.out.println("suppressString"); - - CharSequence result; - - try{ - result = StringUtils.suppressString(null); - fail(); - }catch(NullPointerException e){ - } - - result = StringUtils.suppressString(""); - assertEquals("", result); - - result = StringUtils.suppressString("ABCDE12345"); - assertEquals("ABCDE12345", result); - - result = StringUtils.suppressString("ABCDEF123456"); - assertEquals("ABCDE…23456", result); - - result = StringUtils.suppressString(" A\tBCDEF123 4\n5\r6"); - assertEquals("ABCDE…23456", result); - - return; - } - - /** - * Test of isTerminated method, of class StringUtils. - */ - @Test - public void testIsTerminated(){ - System.out.println("isTerminated"); - - try{ - StringUtils.isTerminated(null, null); - fail(); - }catch(NullPointerException e){ - } - - try{ - StringUtils.isTerminated("A", null); - fail(); - }catch(NullPointerException e){ - } - - try{ - StringUtils.isTerminated(null, "X"); - fail(); - }catch(NullPointerException e){ - } - - assertTrue(StringUtils.isTerminated("ABCXYZ", "XYZ")); - assertTrue(StringUtils.isTerminated("ABCXYZ", "")); - assertTrue(StringUtils.isTerminated("", "")); - - assertFalse(StringUtils.isTerminated("ABCXYZ", "PQR")); - assertFalse(StringUtils.isTerminated("ABC", "ABCXYZ")); - assertFalse(StringUtils.isTerminated("", "XYZ")); - - return; - } - - /** - * Test of compareSubSequence method, of class StringUtils. - */ - @Test - public void testCompareSubSequence_6args(){ - System.out.println("compareSubSequence"); - - int result; - - result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",1,3); - assertTrue(result == 0); - - result = StringUtils.compareSubSequence("ABCDE",1,3,"ABXDE",1,3); - assertTrue(result < 0); - - result = StringUtils.compareSubSequence("ABXDE",1,3,"ABCDE",1,3); - assertTrue(result > 0); - - result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",2,4); - assertTrue(result < 0); - - result = StringUtils.compareSubSequence("ABCDE",1,3,"#ABCDE",2,4); - assertTrue(result == 0); - - result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",1,4); - assertTrue(result < 0); - - result = StringUtils.compareSubSequence("ABCDE",1,4,"ABCDE",1,3); - assertTrue(result > 0); - - return; - } - - /** - * Test of compareSubSequence method, of class StringUtils. - */ - @Test - public void testCompareSubSequence_4args(){ - System.out.println("compareSubSequence"); - - int result; - - result = StringUtils.compareSubSequence("BCD","ABCDE",1,4); - assertTrue(result == 0); - - result = StringUtils.compareSubSequence("BXD","ABCDE",1,4); - assertTrue(result > 0); - - result = StringUtils.compareSubSequence("BCD","ABXDE",1,4); - assertTrue(result < 0); - - return; - } -} +/* + * StringUtils Test + * + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class StringUtilsTest { + + public StringUtilsTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of parseInt method, of class StringUtils. + */ + @Test + public void testParseInt_3args_1(){ + System.out.println("parseInt"); + + int result; + Matcher matcher; + Pattern pattern; + String input = "ABC123PQR456XYZ"; + + pattern = Pattern.compile("([0-9]+)[A-Z]*([0-9]+)"); + matcher = pattern.matcher(input); + + assertTrue(matcher.find()); + + result = StringUtils.parseInt(input, matcher, 1); + assertEquals(123, result); + + result = StringUtils.parseInt(input, matcher, 2); + assertEquals(456, result); + + try{ + result = StringUtils.parseInt(null, matcher, 1); + fail(); + }catch(NullPointerException e){ + } + + try{ + result = StringUtils.parseInt(input, null, 1); + fail(); + }catch(NullPointerException e){ + } + + return; + } + + /** + * Test of parseInt method, of class StringUtils. + */ + @Test + public void testParseInt_CharSequence(){ + System.out.println("parseInt"); + + int result; + + try{ + result = StringUtils.parseInt(null); + fail(); + }catch(NullPointerException e){ + } + + result = StringUtils.parseInt(""); + assertEquals(0, result); + + result = StringUtils.parseInt("0"); + assertEquals(0, result); + + result = StringUtils.parseInt("999"); + assertEquals(999, result); + + result = StringUtils.parseInt("X"); + assertEquals(0, result); + + result = StringUtils.parseInt("-1"); + assertEquals(0, result); + + return; + } + + /** + * Test of parseInt method, of class StringUtils. + */ + @Test + public void testParseInt_3args_2(){ + System.out.println("parseInt"); + + int result; + + try{ + result = StringUtils.parseInt(null, 1, 3); + fail(); + }catch(NullPointerException e){ + } + + result = StringUtils.parseInt("1234567", 2, 5); + assertEquals(345, result); + + result = StringUtils.parseInt("1234567", 2, 3); + assertEquals(3, result); + + result = StringUtils.parseInt("1234567", 2, 2); + assertEquals(0, result); + + result = StringUtils.parseInt("1234567", 2, 1); + assertEquals(0, result); + + result = StringUtils.parseInt("1234567", 0, 0); + assertEquals(0, result); + + try{ + result = StringUtils.parseInt("1234567", 2, 999); + fail(); + }catch(StringIndexOutOfBoundsException e){ + } + + try{ + result = StringUtils.parseInt("1234567", -1, 5); + fail(); + }catch(StringIndexOutOfBoundsException e){ + } + + return; + } + + /** + * Test of suppressString method, of class StringUtils. + */ + @Test + public void testSuppressString(){ + System.out.println("suppressString"); + + CharSequence result; + + try{ + result = StringUtils.suppressString(null); + fail(); + }catch(NullPointerException e){ + } + + result = StringUtils.suppressString(""); + assertEquals("", result); + + result = StringUtils.suppressString("ABCDE12345"); + assertEquals("ABCDE12345", result); + + result = StringUtils.suppressString("ABCDEF123456"); + assertEquals("ABCDE…23456", result); + + result = StringUtils.suppressString(" A\tBCDEF123 4\n5\r6"); + assertEquals("ABCDE…23456", result); + + return; + } + + /** + * Test of isTerminated method, of class StringUtils. + */ + @Test + public void testIsTerminated(){ + System.out.println("isTerminated"); + + try{ + StringUtils.isTerminated(null, null); + fail(); + }catch(NullPointerException e){ + } + + try{ + StringUtils.isTerminated("A", null); + fail(); + }catch(NullPointerException e){ + } + + try{ + StringUtils.isTerminated(null, "X"); + fail(); + }catch(NullPointerException e){ + } + + assertTrue(StringUtils.isTerminated("ABCXYZ", "XYZ")); + assertTrue(StringUtils.isTerminated("ABCXYZ", "")); + assertTrue(StringUtils.isTerminated("", "")); + + assertFalse(StringUtils.isTerminated("ABCXYZ", "PQR")); + assertFalse(StringUtils.isTerminated("ABC", "ABCXYZ")); + assertFalse(StringUtils.isTerminated("", "XYZ")); + + return; + } + + /** + * Test of compareSubSequence method, of class StringUtils. + */ + @Test + public void testCompareSubSequence_6args(){ + System.out.println("compareSubSequence"); + + int result; + + result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",1,3); + assertTrue(result == 0); + + result = StringUtils.compareSubSequence("ABCDE",1,3,"ABXDE",1,3); + assertTrue(result < 0); + + result = StringUtils.compareSubSequence("ABXDE",1,3,"ABCDE",1,3); + assertTrue(result > 0); + + result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",2,4); + assertTrue(result < 0); + + result = StringUtils.compareSubSequence("ABCDE",1,3,"#ABCDE",2,4); + assertTrue(result == 0); + + result = StringUtils.compareSubSequence("ABCDE",1,3,"ABCDE",1,4); + assertTrue(result < 0); + + result = StringUtils.compareSubSequence("ABCDE",1,4,"ABCDE",1,3); + assertTrue(result > 0); + + return; + } + + /** + * Test of compareSubSequence method, of class StringUtils. + */ + @Test + public void testCompareSubSequence_4args(){ + System.out.println("compareSubSequence"); + + int result; + + result = StringUtils.compareSubSequence("BCD","ABCDE",1,4); + assertTrue(result == 0); + + result = StringUtils.compareSubSequence("BXD","ABCDE",1,4); + assertTrue(result > 0); + + result = StringUtils.compareSubSequence("BCD","ABXDE",1,4); + assertTrue(result < 0); + + return; + } +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsArrayTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsArrayTest.java index dec5f17..9226d49 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsArrayTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsArrayTest.java @@ -1,424 +1,424 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.StringReader; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsArrayTest { - - public JsArrayTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of parseArray method, of class JsArray. - */ - @Test - public void testParseArray() throws Exception{ - System.out.println("parseArray"); - - JsonReader reader; - JsArray array; - - reader = new JsonReader(new StringReader("[]")); - array = JsArray.parseArray(reader); - assertEquals(0, array.size()); - - reader = new JsonReader(new StringReader("[true]")); - array = JsArray.parseArray(reader); - assertEquals(1, array.size()); - assertEquals(JsBoolean.TRUE, array.get(0)); - - reader = new JsonReader(new StringReader("[true,false]")); - array = JsArray.parseArray(reader); - assertEquals(2, array.size()); - assertEquals(JsBoolean.TRUE, array.get(0)); - assertEquals(JsBoolean.FALSE, array.get(1)); - - reader = new JsonReader(new StringReader("\n[\rtrue\t, false\n]\r")); - array = JsArray.parseArray(reader); - assertEquals(2, array.size()); - assertEquals(JsBoolean.TRUE, array.get(0)); - assertEquals(JsBoolean.FALSE, array.get(1)); - - try{ - reader = new JsonReader(new StringReader("[,]")); - array = JsArray.parseArray(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("[true,]")); - array = JsArray.parseArray(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("[true")); - array = JsArray.parseArray(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("true]")); - array = JsArray.parseArray(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - return; - } - - /** - * Test of add method, of class JsArray. - */ - @Test - public void testAdd(){ - System.out.println("add"); - - JsArray array = new JsArray(); - - JsNumber number = new JsNumber("1.23"); - assertEquals(0, array.size()); - array.add(number); - assertEquals(1, array.size()); - array.add(number); - assertEquals(2, array.size()); - - return; - } - - /** - * Test of get method, of class JsArray. - */ - @Test - public void testGet(){ - System.out.println("get"); - - JsArray array = new JsArray(); - - JsValue val1 = new JsNumber("1.23"); - JsValue val2 = new JsString("abc"); - - array.add(val1); - array.add(val2); - - assertEquals(val1, array.get(0)); - assertEquals(val2, array.get(1)); - - try{ - array.get(2); - fail(); - }catch(IndexOutOfBoundsException e){ - // NOTHING - } - - return; - } - - /** - * Test of clear method, of class JsArray. - */ - @Test - public void testClear(){ - System.out.println("clear"); - - JsArray array = new JsArray(); - - JsValue val1 = new JsNumber("1.23"); - JsValue val2 = new JsString("abc"); - - array.add(val1); - array.add(val2); - assertEquals(2, array.size()); - - array.clear(); - assertEquals(0, array.size()); - - try{ - array.get(0); - fail(); - }catch(IndexOutOfBoundsException e){ - // NOTHING - } - - return; - } - - /** - * Test of remove method, of class JsArray. - */ - @Test - public void testRemove(){ - System.out.println("remove"); - - JsArray array = new JsArray(); - - JsValue val1 = new JsNumber("1.23"); - JsValue val2 = new JsString("abc"); - JsValue val3 = JsBoolean.TRUE; - - array.add(val1); - array.add(val2); - assertEquals(2, array.size()); - - assertTrue(array.remove(val1)); - assertEquals(1, array.size()); - assertEquals(val2, array.get(0)); - - assertFalse(array.remove(val3)); - assertEquals(1, array.size()); - - return; - } - - /** - * Test of size method, of class JsArray. - */ - @Test - public void testSize(){ - System.out.println("size"); - - JsArray array = new JsArray(); - assertEquals(0, array.size()); - - JsValue val1 = new JsNumber("1.23"); - - array.add(val1); - assertEquals(1, array.size()); - - return; - } - - /** - * Test of iterator method, of class JsArray. - */ - @Test - public void testIterator(){ - System.out.println("iterator"); - - JsArray array = new JsArray(); - - JsValue val1 = new JsNumber("1.23"); - JsValue val2 = new JsString("abc"); - - array.add(val1); - array.add(val2); - - Iterator it = array.iterator(); - - assertTrue(it.hasNext()); - assertEquals(val1, it.next()); - - assertTrue(it.hasNext()); - assertEquals(val2, it.next()); - - assertFalse(it.hasNext()); - - return; - } - - /** - * Test of hashCode method, of class JsArray. - */ - @Test - public void testHashCode(){ - System.out.println("hashCode"); - - JsArray array1 = new JsArray(); - JsArray array2 = new JsArray(); - - assertEquals(array1.hashCode(), array2.hashCode()); - - array1.add(new JsString("abc")); - array2.add(new JsString("abc")); - - assertEquals(array1.hashCode(), array2.hashCode()); - - return; - } - - /** - * Test of equals method, of class JsArray. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - - JsArray array1 = new JsArray(); - JsArray array2 = new JsArray(); - - assertTrue(array1.equals(array2)); - - array1.add(new JsString("abc")); - array2.add(new JsString("abc")); - - assertTrue(array1.equals(array2)); - - array1.add(new JsString("xyz")); - array2.add(new JsString("XYZ")); - - assertFalse(array1.equals(array2)); - - assertFalse(array1.equals(null)); - - return; - } - - /** - * Test of toString method, of class JsArray. - */ - @Test - public void testToString(){ - System.out.println("toString"); - - JsArray array = new JsArray(); - - assertEquals("[]", array.toString()); - - array.add(JsBoolean.TRUE); - assertEquals("[true]", array.toString()); - - array.add(JsBoolean.FALSE); - assertEquals("[true,false]", array.toString()); - - array.add(new JsArray()); - assertEquals("[true,false,[]]", array.toString()); - - return; - } - - /** - * Test of traverse method, of class JsArray. - */ - @Test - public void testTraverse() throws Exception{ - System.out.println("traverse"); - - JsArray array = new JsArray(); - JsValue val1 = new JsNumber("12"); - JsValue val2 = new JsString("AB"); - array.add(val1); - array.add(val2); - - final List visited = new LinkedList(); - - try{ - array.traverse(new ValueVisitor(){ - public void visitValue(JsValue value) - throws JsVisitException{ - visited.add(value); - return; - } - - public void visitPairName(String name) - throws JsVisitException{ - visited.add(name); - return; - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - visited.add(composite); - return; - } - }); - }catch(JsVisitException e){ - fail(); - } - - assertEquals(4, visited.size()); - assertEquals(array, visited.get(0)); - assertEquals(val1, visited.get(1)); - assertEquals(val2, visited.get(2)); - assertEquals(array, visited.get(3)); - - return; - } - - /** - * Test of hasChanged method, of class JsArray. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - - JsArray array = new JsArray(); - assertFalse(array.hasChanged()); - - array.add(new JsNumber("0")); - assertTrue(array.hasChanged()); - - array.setUnchanged(); - assertFalse(array.hasChanged()); - - JsArray child = new JsArray(); - array.add(child); - array.setUnchanged(); - assertFalse(array.hasChanged()); - - child.add(JsNull.NULL); - assertTrue(array.hasChanged()); - array.setUnchanged(); - assertFalse(array.hasChanged()); - - return; - } - - /** - * Test of setUnchanged method, of class JsArray. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - - JsArray array = new JsArray(); - JsArray child = new JsArray(); - array.add(child); - - child.add(JsNull.NULL); - assertTrue(child.hasChanged()); - - array.setUnchanged(); - assertFalse(child.hasChanged()); - - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.StringReader; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsArrayTest { + + public JsArrayTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of parseArray method, of class JsArray. + */ + @Test + public void testParseArray() throws Exception{ + System.out.println("parseArray"); + + JsonReader reader; + JsArray array; + + reader = new JsonReader(new StringReader("[]")); + array = JsArray.parseArray(reader); + assertEquals(0, array.size()); + + reader = new JsonReader(new StringReader("[true]")); + array = JsArray.parseArray(reader); + assertEquals(1, array.size()); + assertEquals(JsBoolean.TRUE, array.get(0)); + + reader = new JsonReader(new StringReader("[true,false]")); + array = JsArray.parseArray(reader); + assertEquals(2, array.size()); + assertEquals(JsBoolean.TRUE, array.get(0)); + assertEquals(JsBoolean.FALSE, array.get(1)); + + reader = new JsonReader(new StringReader("\n[\rtrue\t, false\n]\r")); + array = JsArray.parseArray(reader); + assertEquals(2, array.size()); + assertEquals(JsBoolean.TRUE, array.get(0)); + assertEquals(JsBoolean.FALSE, array.get(1)); + + try{ + reader = new JsonReader(new StringReader("[,]")); + array = JsArray.parseArray(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("[true,]")); + array = JsArray.parseArray(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("[true")); + array = JsArray.parseArray(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("true]")); + array = JsArray.parseArray(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + return; + } + + /** + * Test of add method, of class JsArray. + */ + @Test + public void testAdd(){ + System.out.println("add"); + + JsArray array = new JsArray(); + + JsNumber number = new JsNumber("1.23"); + assertEquals(0, array.size()); + array.add(number); + assertEquals(1, array.size()); + array.add(number); + assertEquals(2, array.size()); + + return; + } + + /** + * Test of get method, of class JsArray. + */ + @Test + public void testGet(){ + System.out.println("get"); + + JsArray array = new JsArray(); + + JsValue val1 = new JsNumber("1.23"); + JsValue val2 = new JsString("abc"); + + array.add(val1); + array.add(val2); + + assertEquals(val1, array.get(0)); + assertEquals(val2, array.get(1)); + + try{ + array.get(2); + fail(); + }catch(IndexOutOfBoundsException e){ + // NOTHING + } + + return; + } + + /** + * Test of clear method, of class JsArray. + */ + @Test + public void testClear(){ + System.out.println("clear"); + + JsArray array = new JsArray(); + + JsValue val1 = new JsNumber("1.23"); + JsValue val2 = new JsString("abc"); + + array.add(val1); + array.add(val2); + assertEquals(2, array.size()); + + array.clear(); + assertEquals(0, array.size()); + + try{ + array.get(0); + fail(); + }catch(IndexOutOfBoundsException e){ + // NOTHING + } + + return; + } + + /** + * Test of remove method, of class JsArray. + */ + @Test + public void testRemove(){ + System.out.println("remove"); + + JsArray array = new JsArray(); + + JsValue val1 = new JsNumber("1.23"); + JsValue val2 = new JsString("abc"); + JsValue val3 = JsBoolean.TRUE; + + array.add(val1); + array.add(val2); + assertEquals(2, array.size()); + + assertTrue(array.remove(val1)); + assertEquals(1, array.size()); + assertEquals(val2, array.get(0)); + + assertFalse(array.remove(val3)); + assertEquals(1, array.size()); + + return; + } + + /** + * Test of size method, of class JsArray. + */ + @Test + public void testSize(){ + System.out.println("size"); + + JsArray array = new JsArray(); + assertEquals(0, array.size()); + + JsValue val1 = new JsNumber("1.23"); + + array.add(val1); + assertEquals(1, array.size()); + + return; + } + + /** + * Test of iterator method, of class JsArray. + */ + @Test + public void testIterator(){ + System.out.println("iterator"); + + JsArray array = new JsArray(); + + JsValue val1 = new JsNumber("1.23"); + JsValue val2 = new JsString("abc"); + + array.add(val1); + array.add(val2); + + Iterator it = array.iterator(); + + assertTrue(it.hasNext()); + assertEquals(val1, it.next()); + + assertTrue(it.hasNext()); + assertEquals(val2, it.next()); + + assertFalse(it.hasNext()); + + return; + } + + /** + * Test of hashCode method, of class JsArray. + */ + @Test + public void testHashCode(){ + System.out.println("hashCode"); + + JsArray array1 = new JsArray(); + JsArray array2 = new JsArray(); + + assertEquals(array1.hashCode(), array2.hashCode()); + + array1.add(new JsString("abc")); + array2.add(new JsString("abc")); + + assertEquals(array1.hashCode(), array2.hashCode()); + + return; + } + + /** + * Test of equals method, of class JsArray. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + + JsArray array1 = new JsArray(); + JsArray array2 = new JsArray(); + + assertTrue(array1.equals(array2)); + + array1.add(new JsString("abc")); + array2.add(new JsString("abc")); + + assertTrue(array1.equals(array2)); + + array1.add(new JsString("xyz")); + array2.add(new JsString("XYZ")); + + assertFalse(array1.equals(array2)); + + assertFalse(array1.equals(null)); + + return; + } + + /** + * Test of toString method, of class JsArray. + */ + @Test + public void testToString(){ + System.out.println("toString"); + + JsArray array = new JsArray(); + + assertEquals("[]", array.toString()); + + array.add(JsBoolean.TRUE); + assertEquals("[true]", array.toString()); + + array.add(JsBoolean.FALSE); + assertEquals("[true,false]", array.toString()); + + array.add(new JsArray()); + assertEquals("[true,false,[]]", array.toString()); + + return; + } + + /** + * Test of traverse method, of class JsArray. + */ + @Test + public void testTraverse() throws Exception{ + System.out.println("traverse"); + + JsArray array = new JsArray(); + JsValue val1 = new JsNumber("12"); + JsValue val2 = new JsString("AB"); + array.add(val1); + array.add(val2); + + final List visited = new LinkedList(); + + try{ + array.traverse(new ValueVisitor(){ + public void visitValue(JsValue value) + throws JsVisitException{ + visited.add(value); + return; + } + + public void visitPairName(String name) + throws JsVisitException{ + visited.add(name); + return; + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + visited.add(composite); + return; + } + }); + }catch(JsVisitException e){ + fail(); + } + + assertEquals(4, visited.size()); + assertEquals(array, visited.get(0)); + assertEquals(val1, visited.get(1)); + assertEquals(val2, visited.get(2)); + assertEquals(array, visited.get(3)); + + return; + } + + /** + * Test of hasChanged method, of class JsArray. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + + JsArray array = new JsArray(); + assertFalse(array.hasChanged()); + + array.add(new JsNumber("0")); + assertTrue(array.hasChanged()); + + array.setUnchanged(); + assertFalse(array.hasChanged()); + + JsArray child = new JsArray(); + array.add(child); + array.setUnchanged(); + assertFalse(array.hasChanged()); + + child.add(JsNull.NULL); + assertTrue(array.hasChanged()); + array.setUnchanged(); + assertFalse(array.hasChanged()); + + return; + } + + /** + * Test of setUnchanged method, of class JsArray. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + + JsArray array = new JsArray(); + JsArray child = new JsArray(); + array.add(child); + + child.add(JsNull.NULL); + assertTrue(child.hasChanged()); + + array.setUnchanged(); + assertFalse(child.hasChanged()); + + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsBooleanTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsBooleanTest.java index e21e861..734800c 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsBooleanTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsBooleanTest.java @@ -1,271 +1,271 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.util.SortedSet; -import java.util.TreeSet; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsBooleanTest { - - public JsBooleanTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of traverse method, of class JsBoolean. - */ - @Test - public void testTraverse(){ - System.out.println("traverse"); - - try{ - JsBoolean.TRUE.traverse(new ValueVisitor(){ - int ct = 0; - - public void visitValue(JsValue value) - throws JsVisitException{ - assertEquals(JsBoolean.TRUE, value); - assertTrue(this.ct++ <= 0); - } - - public void visitPairName(String name) - throws JsVisitException{ - throw new JsVisitException(); - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - throw new JsVisitException(); - } - }); - }catch(JsVisitException e){ - fail(); - } - - try{ - JsBoolean.FALSE.traverse(new ValueVisitor(){ - int ct = 0; - - public void visitValue(JsValue value) - throws JsVisitException{ - assertEquals(JsBoolean.FALSE, value); - assertTrue(this.ct++ <= 0); - } - - public void visitPairName(String name) - throws JsVisitException{ - throw new JsVisitException(); - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - throw new JsVisitException(); - } - }); - }catch(JsVisitException e){ - fail(); - } - - return; - } - - /** - * Test of hasChanged method, of class JsBoolean. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - - assertFalse(JsBoolean.TRUE.hasChanged()); - JsBoolean.TRUE.setUnchanged(); - assertFalse(JsBoolean.TRUE.hasChanged()); - - assertFalse(JsBoolean.FALSE.hasChanged()); - JsBoolean.FALSE.setUnchanged(); - assertFalse(JsBoolean.FALSE.hasChanged()); - - return; - } - - /** - * Test of setUnchanged method, of class JsBoolean. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - - JsBoolean.TRUE.setUnchanged(); - assertFalse(JsBoolean.TRUE.hasChanged()); - - JsBoolean.FALSE.setUnchanged(); - assertFalse(JsBoolean.FALSE.hasChanged()); - - return; - } - - /** - * Test of valueOf method, of class JsBoolean. - */ - @Test - public void testValueOf(){ - System.out.println("valueOf"); - assertEquals(JsBoolean.TRUE, JsBoolean.valueOf(true)); - assertEquals(JsBoolean.FALSE, JsBoolean.valueOf(false)); - return; - } - - /** - * Test of booleanValue method, of class JsBoolean. - */ - @Test - public void testBooleanValue(){ - System.out.println("booleanValue"); - assertTrue(JsBoolean.TRUE.booleanValue()); - assertFalse(JsBoolean.FALSE.booleanValue()); - return; - } - - /** - * Test of isTrue method, of class JsBoolean. - */ - @Test - public void testIsTrue(){ - System.out.println("isTrue"); - assertTrue(JsBoolean.TRUE.isTrue()); - assertFalse(JsBoolean.FALSE.isTrue()); - return; - } - - /** - * Test of isFalse method, of class JsBoolean. - */ - @Test - public void testIsFalse(){ - System.out.println("isFalse"); - assertFalse(JsBoolean.TRUE.isFalse()); - assertTrue(JsBoolean.FALSE.isFalse()); - return; - } - - /** - * Test of hashCode method, of class JsBoolean. - */ - @Test - public void testHashCode(){ - System.out.println("hashCode"); - // NOTHING - return; - } - - /** - * Test of equals method, of class JsBoolean. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - - assertTrue(JsBoolean.TRUE.equals(JsBoolean.TRUE)); - assertFalse(JsBoolean.TRUE.equals(JsBoolean.FALSE)); -// assertFalse(JsBoolean.TRUE.equals(JsNull.NULL)); - assertFalse(JsBoolean.TRUE.equals(null)); - - assertFalse(JsBoolean.FALSE.equals(JsBoolean.TRUE)); - assertTrue(JsBoolean.FALSE.equals(JsBoolean.FALSE)); -// assertFalse(JsBoolean.TRUE.equals(JsNull.NULL)); - assertFalse(JsBoolean.FALSE.equals(null)); - - return; - } - - /** - * Test of compareTo method, of class JsBoolean. - */ - @Test - public void testCompareTo(){ - System.out.println("compareTo"); - assertEquals(0, JsBoolean.TRUE.compareTo(JsBoolean.TRUE)); - assertEquals(0, JsBoolean.FALSE.compareTo(JsBoolean.FALSE)); - assertTrue(0 > JsBoolean.TRUE.compareTo(JsBoolean.FALSE)); - assertTrue(0 < JsBoolean.FALSE.compareTo(JsBoolean.TRUE)); - - try{ - JsBoolean.TRUE.compareTo(null); - fail(); - }catch(NullPointerException e){ - // NOTHING - } - - try{ - JsBoolean.FALSE.compareTo(null); - fail(); - }catch(NullPointerException e){ - // NOTHING - } - - SortedSet set = new TreeSet(); - - set.clear(); - set.add(JsBoolean.TRUE); - set.add(JsBoolean.FALSE); - assertEquals(JsBoolean.TRUE, set.first()); - assertEquals(JsBoolean.FALSE, set.last()); - set.clear(); - set.add(JsBoolean.FALSE); - set.add(JsBoolean.TRUE); - assertEquals(JsBoolean.TRUE, set.first()); - assertEquals(JsBoolean.FALSE, set.last()); - - return; - } - - /** - * Test of toString method, of class JsBoolean. - */ - @Test - public void testToString(){ - System.out.println("toString"); - assertEquals("true", JsBoolean.TRUE.toString()); - assertEquals("false", JsBoolean.FALSE.toString()); - return; - } - - /** - * Test of toString method, of class JsBoolean. - */ - @Test - public void testEtc(){ - System.out.println("etc."); - assertNotNull(JsBoolean.TRUE); - assertNotNull(JsBoolean.FALSE); - assertTrue(JsBoolean.TRUE instanceof JsBoolean); - assertTrue(JsBoolean.FALSE instanceof JsBoolean); - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.util.SortedSet; +import java.util.TreeSet; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsBooleanTest { + + public JsBooleanTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of traverse method, of class JsBoolean. + */ + @Test + public void testTraverse(){ + System.out.println("traverse"); + + try{ + JsBoolean.TRUE.traverse(new ValueVisitor(){ + int ct = 0; + + public void visitValue(JsValue value) + throws JsVisitException{ + assertEquals(JsBoolean.TRUE, value); + assertTrue(this.ct++ <= 0); + } + + public void visitPairName(String name) + throws JsVisitException{ + throw new JsVisitException(); + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + throw new JsVisitException(); + } + }); + }catch(JsVisitException e){ + fail(); + } + + try{ + JsBoolean.FALSE.traverse(new ValueVisitor(){ + int ct = 0; + + public void visitValue(JsValue value) + throws JsVisitException{ + assertEquals(JsBoolean.FALSE, value); + assertTrue(this.ct++ <= 0); + } + + public void visitPairName(String name) + throws JsVisitException{ + throw new JsVisitException(); + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + throw new JsVisitException(); + } + }); + }catch(JsVisitException e){ + fail(); + } + + return; + } + + /** + * Test of hasChanged method, of class JsBoolean. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + + assertFalse(JsBoolean.TRUE.hasChanged()); + JsBoolean.TRUE.setUnchanged(); + assertFalse(JsBoolean.TRUE.hasChanged()); + + assertFalse(JsBoolean.FALSE.hasChanged()); + JsBoolean.FALSE.setUnchanged(); + assertFalse(JsBoolean.FALSE.hasChanged()); + + return; + } + + /** + * Test of setUnchanged method, of class JsBoolean. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + + JsBoolean.TRUE.setUnchanged(); + assertFalse(JsBoolean.TRUE.hasChanged()); + + JsBoolean.FALSE.setUnchanged(); + assertFalse(JsBoolean.FALSE.hasChanged()); + + return; + } + + /** + * Test of valueOf method, of class JsBoolean. + */ + @Test + public void testValueOf(){ + System.out.println("valueOf"); + assertEquals(JsBoolean.TRUE, JsBoolean.valueOf(true)); + assertEquals(JsBoolean.FALSE, JsBoolean.valueOf(false)); + return; + } + + /** + * Test of booleanValue method, of class JsBoolean. + */ + @Test + public void testBooleanValue(){ + System.out.println("booleanValue"); + assertTrue(JsBoolean.TRUE.booleanValue()); + assertFalse(JsBoolean.FALSE.booleanValue()); + return; + } + + /** + * Test of isTrue method, of class JsBoolean. + */ + @Test + public void testIsTrue(){ + System.out.println("isTrue"); + assertTrue(JsBoolean.TRUE.isTrue()); + assertFalse(JsBoolean.FALSE.isTrue()); + return; + } + + /** + * Test of isFalse method, of class JsBoolean. + */ + @Test + public void testIsFalse(){ + System.out.println("isFalse"); + assertFalse(JsBoolean.TRUE.isFalse()); + assertTrue(JsBoolean.FALSE.isFalse()); + return; + } + + /** + * Test of hashCode method, of class JsBoolean. + */ + @Test + public void testHashCode(){ + System.out.println("hashCode"); + // NOTHING + return; + } + + /** + * Test of equals method, of class JsBoolean. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + + assertTrue(JsBoolean.TRUE.equals(JsBoolean.TRUE)); + assertFalse(JsBoolean.TRUE.equals(JsBoolean.FALSE)); +// assertFalse(JsBoolean.TRUE.equals(JsNull.NULL)); + assertFalse(JsBoolean.TRUE.equals(null)); + + assertFalse(JsBoolean.FALSE.equals(JsBoolean.TRUE)); + assertTrue(JsBoolean.FALSE.equals(JsBoolean.FALSE)); +// assertFalse(JsBoolean.TRUE.equals(JsNull.NULL)); + assertFalse(JsBoolean.FALSE.equals(null)); + + return; + } + + /** + * Test of compareTo method, of class JsBoolean. + */ + @Test + public void testCompareTo(){ + System.out.println("compareTo"); + assertEquals(0, JsBoolean.TRUE.compareTo(JsBoolean.TRUE)); + assertEquals(0, JsBoolean.FALSE.compareTo(JsBoolean.FALSE)); + assertTrue(0 > JsBoolean.TRUE.compareTo(JsBoolean.FALSE)); + assertTrue(0 < JsBoolean.FALSE.compareTo(JsBoolean.TRUE)); + + try{ + JsBoolean.TRUE.compareTo(null); + fail(); + }catch(NullPointerException e){ + // NOTHING + } + + try{ + JsBoolean.FALSE.compareTo(null); + fail(); + }catch(NullPointerException e){ + // NOTHING + } + + SortedSet set = new TreeSet(); + + set.clear(); + set.add(JsBoolean.TRUE); + set.add(JsBoolean.FALSE); + assertEquals(JsBoolean.TRUE, set.first()); + assertEquals(JsBoolean.FALSE, set.last()); + set.clear(); + set.add(JsBoolean.FALSE); + set.add(JsBoolean.TRUE); + assertEquals(JsBoolean.TRUE, set.first()); + assertEquals(JsBoolean.FALSE, set.last()); + + return; + } + + /** + * Test of toString method, of class JsBoolean. + */ + @Test + public void testToString(){ + System.out.println("toString"); + assertEquals("true", JsBoolean.TRUE.toString()); + assertEquals("false", JsBoolean.FALSE.toString()); + return; + } + + /** + * Test of toString method, of class JsBoolean. + */ + @Test + public void testEtc(){ + System.out.println("etc."); + assertNotNull(JsBoolean.TRUE); + assertNotNull(JsBoolean.FALSE); + assertTrue(JsBoolean.TRUE instanceof JsBoolean); + assertTrue(JsBoolean.FALSE instanceof JsBoolean); + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsNullTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsNullTest.java index 3ff6e5e..88fcb66 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsNullTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsNullTest.java @@ -1,130 +1,130 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsNullTest { - - public JsNullTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of etc of class JsNull. - */ - @Test - public void testEtc(){ - System.out.println("etc"); - assertNotNull(JsNull.NULL); - assertTrue(JsNull.NULL instanceof JsNull); - return; - } - - /** - * Test of hasChanged method, of class JsNull. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - assertFalse(JsNull.NULL.hasChanged()); - JsNull.NULL.setUnchanged(); - assertFalse(JsNull.NULL.hasChanged()); - return; - } - - /** - * Test of setUnchanged method, of class JsNull. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - JsNull.NULL.setUnchanged(); - assertFalse(JsNull.NULL.hasChanged()); - return; - } - - /** - * Test of traverse method, of class JsNull. - */ - @Test - public void testTraverse(){ - System.out.println("traverse"); - try{ - JsNull.NULL.traverse(new ValueVisitor(){ - int ct = 0; - - public void visitValue(JsValue value) - throws JsVisitException{ - assertEquals(JsNull.NULL, value); - assertTrue(this.ct++ <= 0); - } - - public void visitPairName(String name) - throws JsVisitException{ - throw new JsVisitException(); - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - throw new JsVisitException(); - } - }); - }catch(JsVisitException e){ - fail(); - } - return; - } - - /** - * Test of compareTo method, of class JsNull. - */ - @Test - public void testCompareTo(){ - System.out.println("compareTo"); - assertEquals(0, JsNull.NULL.compareTo(JsNull.NULL)); - try{ - JsNull.NULL.compareTo(null); - fail(); - }catch(NullPointerException e){ - // NOTHING - } - return; - } - - /** - * Test of toString method, of class JsNull. - */ - @Test - public void testToString(){ - System.out.println("toString"); - assertEquals("null", JsNull.NULL.toString()); - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsNullTest { + + public JsNullTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of etc of class JsNull. + */ + @Test + public void testEtc(){ + System.out.println("etc"); + assertNotNull(JsNull.NULL); + assertTrue(JsNull.NULL instanceof JsNull); + return; + } + + /** + * Test of hasChanged method, of class JsNull. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + assertFalse(JsNull.NULL.hasChanged()); + JsNull.NULL.setUnchanged(); + assertFalse(JsNull.NULL.hasChanged()); + return; + } + + /** + * Test of setUnchanged method, of class JsNull. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + JsNull.NULL.setUnchanged(); + assertFalse(JsNull.NULL.hasChanged()); + return; + } + + /** + * Test of traverse method, of class JsNull. + */ + @Test + public void testTraverse(){ + System.out.println("traverse"); + try{ + JsNull.NULL.traverse(new ValueVisitor(){ + int ct = 0; + + public void visitValue(JsValue value) + throws JsVisitException{ + assertEquals(JsNull.NULL, value); + assertTrue(this.ct++ <= 0); + } + + public void visitPairName(String name) + throws JsVisitException{ + throw new JsVisitException(); + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + throw new JsVisitException(); + } + }); + }catch(JsVisitException e){ + fail(); + } + return; + } + + /** + * Test of compareTo method, of class JsNull. + */ + @Test + public void testCompareTo(){ + System.out.println("compareTo"); + assertEquals(0, JsNull.NULL.compareTo(JsNull.NULL)); + try{ + JsNull.NULL.compareTo(null); + fail(); + }catch(NullPointerException e){ + // NOTHING + } + return; + } + + /** + * Test of toString method, of class JsNull. + */ + @Test + public void testToString(){ + System.out.println("toString"); + assertEquals("null", JsNull.NULL.toString()); + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsNumberTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsNumberTest.java index 09172a6..3b8a057 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsNumberTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsNumberTest.java @@ -1,470 +1,470 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.StringReader; -import java.math.BigDecimal; -import java.math.BigInteger; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsNumberTest { - - public JsNumberTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of parseNumber method, of class JsNumber. - */ - @Test - public void testParseNumber() throws Exception{ - System.out.println("parseNumber"); - - JsonReader reader; - JsNumber number; - - try{ - reader = new JsonReader(new StringReader("0")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - reader = new JsonReader(new StringReader("0,")); - number = JsNumber.parseNumber(reader); - assertEquals("0", number.toString()); - - reader = new JsonReader(new StringReader("\n\r\t\u00200,")); - number = JsNumber.parseNumber(reader); - assertEquals("0", number.toString()); - - reader = new JsonReader(new StringReader("-0,")); - number = JsNumber.parseNumber(reader); - assertEquals("0", number.toString()); - - reader = new JsonReader(new StringReader("12,")); - number = JsNumber.parseNumber(reader); - assertEquals("12", number.toString()); - - reader = new JsonReader(new StringReader("-12,")); - number = JsNumber.parseNumber(reader); - assertEquals("-12", number.toString()); - - try{ - reader = new JsonReader(new StringReader("+12,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("12.,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - reader = new JsonReader(new StringReader("12.34,")); - number = JsNumber.parseNumber(reader); - assertEquals("12.34", number.toString()); - - reader = new JsonReader(new StringReader("12.0,")); - number = JsNumber.parseNumber(reader); - assertEquals("12.0", number.toString()); - - reader = new JsonReader(new StringReader("12.00,")); - number = JsNumber.parseNumber(reader); - assertEquals("12.00", number.toString()); - - reader = new JsonReader(new StringReader("12.003,")); - number = JsNumber.parseNumber(reader); - assertEquals("12.003", number.toString()); - - reader = new JsonReader(new StringReader("12.0030,")); - number = JsNumber.parseNumber(reader); - assertEquals("12.0030", number.toString()); - - try{ - reader = new JsonReader(new StringReader("09,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - reader = new JsonReader(new StringReader("12e34,")); - number = JsNumber.parseNumber(reader); - assertEquals("1.2E+35", number.toString()); - - reader = new JsonReader(new StringReader("12E34,")); - number = JsNumber.parseNumber(reader); - assertEquals("1.2E+35", number.toString()); - - reader = new JsonReader(new StringReader("12e+34,")); - number = JsNumber.parseNumber(reader); - assertEquals("1.2E+35", number.toString()); - - reader = new JsonReader(new StringReader("12e-34,")); - number = JsNumber.parseNumber(reader); - assertEquals("1.2E-33", number.toString()); - - reader = new JsonReader(new StringReader("12e0034,")); - number = JsNumber.parseNumber(reader); - assertEquals("1.2E+35", number.toString()); - - try{ - reader = new JsonReader(new StringReader("12e,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("12e+,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("12e-,")); - number = JsNumber.parseNumber(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - reader = new JsonReader(new StringReader("-12.34e-056,")); - number = JsNumber.parseNumber(reader); - assertEquals("-1.234E-55", number.toString()); - - return; - } - - /** - * Test of constructor, of class JsNumber. - */ - @Test - public void testConstructors() throws Exception{ - System.out.println("constructor"); - - JsNumber number; - BigDecimal decimal; - - number = new JsNumber(99L); - decimal = number.getBigDecimal(); - assertEquals(new BigInteger("99"), decimal.unscaledValue()); - assertEquals(0, decimal.scale()); - - number = new JsNumber(99.0); - decimal = number.getBigDecimal(); - assertEquals(new BigInteger("990"), decimal.unscaledValue()); - assertEquals(1, decimal.scale()); - - number = new JsNumber(new BigInteger("99")); - decimal = number.getBigDecimal(); - assertEquals(new BigInteger("99"), decimal.unscaledValue()); - assertEquals(0, decimal.scale()); - - number = new JsNumber("99.9"); - decimal = number.getBigDecimal(); - assertEquals(new BigInteger("999"), decimal.unscaledValue()); - assertEquals(1, decimal.scale()); - - number = new JsNumber(new BigDecimal("99.9")); - decimal = number.getBigDecimal(); - assertEquals(new BigInteger("999"), decimal.unscaledValue()); - assertEquals(1, decimal.scale()); - - return; - } - - /** - * Test of traverse method, of class JsNumber. - */ - @Test - public void testTraverse(){ - System.out.println("traverse"); - - JsNumber number = new JsNumber("0"); - - try{ - number.traverse(new ValueVisitor(){ - int ct = 0; - - public void visitValue(JsValue value) - throws JsVisitException{ - assertEquals(new JsNumber("0"), value); - assertTrue(this.ct++ <= 0); - } - - public void visitPairName(String name) - throws JsVisitException{ - throw new JsVisitException(); - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - throw new JsVisitException(); - } - }); - }catch(JsVisitException e){ - fail(); - } - - return; - } - - /** - * Test of hasChanged method, of class JsNumber. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - - JsNumber number = new JsNumber("0"); - - assertFalse(number.hasChanged()); - number.setUnchanged(); - assertFalse(number.hasChanged()); - - return; - } - - /** - * Test of setUnchanged method, of class JsNumber. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - - JsNumber number = new JsNumber("0"); - - number.setUnchanged(); - assertFalse(number.hasChanged()); - - return; - } - - /** - * Test of getBigDecimal method, of class JsNumber. - */ - @Test - public void testGetBigDecimal(){ - System.out.println("getBigDecimal"); - - JsNumber number = new JsNumber("-123.456e+1"); - BigDecimal decimal = number.getBigDecimal(); - - assertEquals(new BigDecimal("-123.456e+1"), decimal); - assertEquals(2, decimal.scale()); - assertEquals(new BigInteger("-123456"), decimal.unscaledValue()); - - return; - } - - /** - * Test of intValue method, of class JsNumber. - */ - @Test - public void testIntValue(){ - System.out.println("intValue"); - - assertEquals(0, new JsNumber("0").intValue()); - assertEquals(99, new JsNumber("99.9").intValue()); - assertEquals(-99, new JsNumber("-99.9").intValue()); - assertEquals(2147483647, new JsNumber("2147483647").intValue()); - - return; - } - - /** - * Test of longValue method, of class JsNumber. - */ - @Test - public void testLongValue(){ - System.out.println("longValue"); - - assertEquals(0L, new JsNumber("0").longValue()); - assertEquals(99L, new JsNumber("99.9").longValue()); - assertEquals(-99L, new JsNumber("-99.9").longValue()); - assertEquals(999999999999L, new JsNumber("999999999999").longValue()); - - return; - } - - /** - * Test of floatValue method, of class JsNumber. - */ - @Test - public void testFloatValue(){ - System.out.println("floatValue"); - - assertEquals(1.25f, new JsNumber("1.25").floatValue(), 0.0); - assertEquals(1.25f, new JsNumber("125E-2").floatValue(), 0.0); - - return; - } - - /** - * Test of doubleValue method, of class JsNumber. - */ - @Test - public void testDoubleValue(){ - System.out.println("doubleValue"); - - assertEquals(1.25, new JsNumber("1.25").doubleValue(), 0.0); - assertEquals(1.25, new JsNumber("125E-2").doubleValue(), 0.0); - - return; - } - - /** - * Test of hashCode method, of class JsNumber. - */ - @Test - public void testHashCode(){ - System.out.println("hashCode"); - - assertEquals(new JsNumber("1").hashCode(), new JsNumber("1").hashCode()); - assertEquals(new JsNumber("1.23").hashCode(), new JsNumber("123e-2").hashCode()); - - return; - } - - /** - * Test of equals method, of class JsNumber. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - - assertTrue(new JsNumber("1").equals(new JsNumber("1"))); - assertFalse(new JsNumber("1").equals(new JsNumber("2"))); - assertFalse(new JsNumber("1").equals(null)); - - assertTrue(new JsNumber("1.23").equals(new JsNumber("123e-2"))); - assertFalse(new JsNumber("1.0").equals(new JsNumber("1.00"))); - - return; - } - - /** - * Test of compareTo method, of class JsNumber. - */ - @Test - public void testCompareTo(){ - System.out.println("compareTo"); - - assertTrue(0 > new JsNumber("-1").compareTo(new JsNumber("1"))); - assertTrue(0 < new JsNumber("1").compareTo(new JsNumber("-1"))); - assertTrue(new JsNumber("1").compareTo(new JsNumber("1")) == 0); - - assertTrue(0 > new JsNumber("1").compareTo(new JsNumber("2"))); - assertTrue(0 < new JsNumber("9").compareTo(new JsNumber("8"))); - - assertTrue(new JsNumber("1.23").compareTo(new JsNumber("123e-2")) == 0); - assertTrue(new JsNumber("1.0").compareTo(new JsNumber("1.00")) == 0); - - return; - } - - /** - * Test of toString method, of class JsNumber. - */ - @Test - public void testToString(){ - System.out.println("toString"); - - JsNumber number; - - number = new JsNumber("0"); - assertEquals("0", number.toString()); - number = new JsNumber("+0"); - assertEquals("0", number.toString()); - number = new JsNumber("-0"); - assertEquals("0", number.toString()); - - number = new JsNumber("1"); - assertEquals("1", number.toString()); - number = new JsNumber("+1"); - assertEquals("1", number.toString()); - number = new JsNumber("-1"); - assertEquals("-1", number.toString()); - - number = new JsNumber("0.0"); - assertEquals("0.0", number.toString()); - - number = new JsNumber("1.0"); - assertEquals("1.0", number.toString()); - - number = new JsNumber("1.00"); - assertEquals("1.00", number.toString()); - - number = new JsNumber("0.1"); - assertEquals("0.1", number.toString()); - - number = new JsNumber("0.10"); - assertEquals("0.10", number.toString()); - - number = new JsNumber("0.000001"); - assertEquals("0.000001", number.toString()); - - number = new JsNumber("0.0000001"); - assertEquals("1E-7", number.toString()); - - number = new JsNumber("123e0"); - assertEquals("123", number.toString()); - - number = new JsNumber("123e1"); - assertEquals("1.23E+3", number.toString()); - - number = new JsNumber("123E1"); - assertEquals("1.23E+3", number.toString()); - - number = new JsNumber("123e+1"); - assertEquals("1.23E+3", number.toString()); - - number = new JsNumber("123e-1"); - assertEquals("12.3", number.toString()); - - number = new JsNumber("123e-8"); - assertEquals("0.00000123", number.toString()); - - number = new JsNumber("123e-9"); - assertEquals("1.23E-7", number.toString()); - - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsNumberTest { + + public JsNumberTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of parseNumber method, of class JsNumber. + */ + @Test + public void testParseNumber() throws Exception{ + System.out.println("parseNumber"); + + JsonReader reader; + JsNumber number; + + try{ + reader = new JsonReader(new StringReader("0")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + reader = new JsonReader(new StringReader("0,")); + number = JsNumber.parseNumber(reader); + assertEquals("0", number.toString()); + + reader = new JsonReader(new StringReader("\n\r\t\u00200,")); + number = JsNumber.parseNumber(reader); + assertEquals("0", number.toString()); + + reader = new JsonReader(new StringReader("-0,")); + number = JsNumber.parseNumber(reader); + assertEquals("0", number.toString()); + + reader = new JsonReader(new StringReader("12,")); + number = JsNumber.parseNumber(reader); + assertEquals("12", number.toString()); + + reader = new JsonReader(new StringReader("-12,")); + number = JsNumber.parseNumber(reader); + assertEquals("-12", number.toString()); + + try{ + reader = new JsonReader(new StringReader("+12,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("12.,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + reader = new JsonReader(new StringReader("12.34,")); + number = JsNumber.parseNumber(reader); + assertEquals("12.34", number.toString()); + + reader = new JsonReader(new StringReader("12.0,")); + number = JsNumber.parseNumber(reader); + assertEquals("12.0", number.toString()); + + reader = new JsonReader(new StringReader("12.00,")); + number = JsNumber.parseNumber(reader); + assertEquals("12.00", number.toString()); + + reader = new JsonReader(new StringReader("12.003,")); + number = JsNumber.parseNumber(reader); + assertEquals("12.003", number.toString()); + + reader = new JsonReader(new StringReader("12.0030,")); + number = JsNumber.parseNumber(reader); + assertEquals("12.0030", number.toString()); + + try{ + reader = new JsonReader(new StringReader("09,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + reader = new JsonReader(new StringReader("12e34,")); + number = JsNumber.parseNumber(reader); + assertEquals("1.2E+35", number.toString()); + + reader = new JsonReader(new StringReader("12E34,")); + number = JsNumber.parseNumber(reader); + assertEquals("1.2E+35", number.toString()); + + reader = new JsonReader(new StringReader("12e+34,")); + number = JsNumber.parseNumber(reader); + assertEquals("1.2E+35", number.toString()); + + reader = new JsonReader(new StringReader("12e-34,")); + number = JsNumber.parseNumber(reader); + assertEquals("1.2E-33", number.toString()); + + reader = new JsonReader(new StringReader("12e0034,")); + number = JsNumber.parseNumber(reader); + assertEquals("1.2E+35", number.toString()); + + try{ + reader = new JsonReader(new StringReader("12e,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("12e+,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("12e-,")); + number = JsNumber.parseNumber(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + reader = new JsonReader(new StringReader("-12.34e-056,")); + number = JsNumber.parseNumber(reader); + assertEquals("-1.234E-55", number.toString()); + + return; + } + + /** + * Test of constructor, of class JsNumber. + */ + @Test + public void testConstructors() throws Exception{ + System.out.println("constructor"); + + JsNumber number; + BigDecimal decimal; + + number = new JsNumber(99L); + decimal = number.getBigDecimal(); + assertEquals(new BigInteger("99"), decimal.unscaledValue()); + assertEquals(0, decimal.scale()); + + number = new JsNumber(99.0); + decimal = number.getBigDecimal(); + assertEquals(new BigInteger("990"), decimal.unscaledValue()); + assertEquals(1, decimal.scale()); + + number = new JsNumber(new BigInteger("99")); + decimal = number.getBigDecimal(); + assertEquals(new BigInteger("99"), decimal.unscaledValue()); + assertEquals(0, decimal.scale()); + + number = new JsNumber("99.9"); + decimal = number.getBigDecimal(); + assertEquals(new BigInteger("999"), decimal.unscaledValue()); + assertEquals(1, decimal.scale()); + + number = new JsNumber(new BigDecimal("99.9")); + decimal = number.getBigDecimal(); + assertEquals(new BigInteger("999"), decimal.unscaledValue()); + assertEquals(1, decimal.scale()); + + return; + } + + /** + * Test of traverse method, of class JsNumber. + */ + @Test + public void testTraverse(){ + System.out.println("traverse"); + + JsNumber number = new JsNumber("0"); + + try{ + number.traverse(new ValueVisitor(){ + int ct = 0; + + public void visitValue(JsValue value) + throws JsVisitException{ + assertEquals(new JsNumber("0"), value); + assertTrue(this.ct++ <= 0); + } + + public void visitPairName(String name) + throws JsVisitException{ + throw new JsVisitException(); + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + throw new JsVisitException(); + } + }); + }catch(JsVisitException e){ + fail(); + } + + return; + } + + /** + * Test of hasChanged method, of class JsNumber. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + + JsNumber number = new JsNumber("0"); + + assertFalse(number.hasChanged()); + number.setUnchanged(); + assertFalse(number.hasChanged()); + + return; + } + + /** + * Test of setUnchanged method, of class JsNumber. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + + JsNumber number = new JsNumber("0"); + + number.setUnchanged(); + assertFalse(number.hasChanged()); + + return; + } + + /** + * Test of getBigDecimal method, of class JsNumber. + */ + @Test + public void testGetBigDecimal(){ + System.out.println("getBigDecimal"); + + JsNumber number = new JsNumber("-123.456e+1"); + BigDecimal decimal = number.getBigDecimal(); + + assertEquals(new BigDecimal("-123.456e+1"), decimal); + assertEquals(2, decimal.scale()); + assertEquals(new BigInteger("-123456"), decimal.unscaledValue()); + + return; + } + + /** + * Test of intValue method, of class JsNumber. + */ + @Test + public void testIntValue(){ + System.out.println("intValue"); + + assertEquals(0, new JsNumber("0").intValue()); + assertEquals(99, new JsNumber("99.9").intValue()); + assertEquals(-99, new JsNumber("-99.9").intValue()); + assertEquals(2147483647, new JsNumber("2147483647").intValue()); + + return; + } + + /** + * Test of longValue method, of class JsNumber. + */ + @Test + public void testLongValue(){ + System.out.println("longValue"); + + assertEquals(0L, new JsNumber("0").longValue()); + assertEquals(99L, new JsNumber("99.9").longValue()); + assertEquals(-99L, new JsNumber("-99.9").longValue()); + assertEquals(999999999999L, new JsNumber("999999999999").longValue()); + + return; + } + + /** + * Test of floatValue method, of class JsNumber. + */ + @Test + public void testFloatValue(){ + System.out.println("floatValue"); + + assertEquals(1.25f, new JsNumber("1.25").floatValue(), 0.0); + assertEquals(1.25f, new JsNumber("125E-2").floatValue(), 0.0); + + return; + } + + /** + * Test of doubleValue method, of class JsNumber. + */ + @Test + public void testDoubleValue(){ + System.out.println("doubleValue"); + + assertEquals(1.25, new JsNumber("1.25").doubleValue(), 0.0); + assertEquals(1.25, new JsNumber("125E-2").doubleValue(), 0.0); + + return; + } + + /** + * Test of hashCode method, of class JsNumber. + */ + @Test + public void testHashCode(){ + System.out.println("hashCode"); + + assertEquals(new JsNumber("1").hashCode(), new JsNumber("1").hashCode()); + assertEquals(new JsNumber("1.23").hashCode(), new JsNumber("123e-2").hashCode()); + + return; + } + + /** + * Test of equals method, of class JsNumber. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + + assertTrue(new JsNumber("1").equals(new JsNumber("1"))); + assertFalse(new JsNumber("1").equals(new JsNumber("2"))); + assertFalse(new JsNumber("1").equals(null)); + + assertTrue(new JsNumber("1.23").equals(new JsNumber("123e-2"))); + assertFalse(new JsNumber("1.0").equals(new JsNumber("1.00"))); + + return; + } + + /** + * Test of compareTo method, of class JsNumber. + */ + @Test + public void testCompareTo(){ + System.out.println("compareTo"); + + assertTrue(0 > new JsNumber("-1").compareTo(new JsNumber("1"))); + assertTrue(0 < new JsNumber("1").compareTo(new JsNumber("-1"))); + assertTrue(new JsNumber("1").compareTo(new JsNumber("1")) == 0); + + assertTrue(0 > new JsNumber("1").compareTo(new JsNumber("2"))); + assertTrue(0 < new JsNumber("9").compareTo(new JsNumber("8"))); + + assertTrue(new JsNumber("1.23").compareTo(new JsNumber("123e-2")) == 0); + assertTrue(new JsNumber("1.0").compareTo(new JsNumber("1.00")) == 0); + + return; + } + + /** + * Test of toString method, of class JsNumber. + */ + @Test + public void testToString(){ + System.out.println("toString"); + + JsNumber number; + + number = new JsNumber("0"); + assertEquals("0", number.toString()); + number = new JsNumber("+0"); + assertEquals("0", number.toString()); + number = new JsNumber("-0"); + assertEquals("0", number.toString()); + + number = new JsNumber("1"); + assertEquals("1", number.toString()); + number = new JsNumber("+1"); + assertEquals("1", number.toString()); + number = new JsNumber("-1"); + assertEquals("-1", number.toString()); + + number = new JsNumber("0.0"); + assertEquals("0.0", number.toString()); + + number = new JsNumber("1.0"); + assertEquals("1.0", number.toString()); + + number = new JsNumber("1.00"); + assertEquals("1.00", number.toString()); + + number = new JsNumber("0.1"); + assertEquals("0.1", number.toString()); + + number = new JsNumber("0.10"); + assertEquals("0.10", number.toString()); + + number = new JsNumber("0.000001"); + assertEquals("0.000001", number.toString()); + + number = new JsNumber("0.0000001"); + assertEquals("1E-7", number.toString()); + + number = new JsNumber("123e0"); + assertEquals("123", number.toString()); + + number = new JsNumber("123e1"); + assertEquals("1.23E+3", number.toString()); + + number = new JsNumber("123E1"); + assertEquals("1.23E+3", number.toString()); + + number = new JsNumber("123e+1"); + assertEquals("1.23E+3", number.toString()); + + number = new JsNumber("123e-1"); + assertEquals("12.3", number.toString()); + + number = new JsNumber("123e-8"); + assertEquals("0.00000123", number.toString()); + + number = new JsNumber("123e-9"); + assertEquals("1.23E-7", number.toString()); + + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsObjectTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsObjectTest.java index 5ca887a..5de7273 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsObjectTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsObjectTest.java @@ -1,463 +1,463 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.StringReader; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsObjectTest { - - public JsObjectTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of parseObject method, of class JsObject. - */ - @Test - public void testParseObject() throws Exception{ - System.out.println("parseObject"); - - JsonReader reader; - JsObject object; - - reader = new JsonReader(new StringReader("{}")); - object = JsObject.parseObject(reader); - assertEquals(0, object.size()); - - reader = new JsonReader(new StringReader("{\"A\":true}")); - object = JsObject.parseObject(reader); - assertEquals(1, object.size()); - assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); - - reader = new JsonReader(new StringReader("{\"A\":true,\"B\":false}")); - object = JsObject.parseObject(reader); - assertEquals(2, object.size()); - assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); - assertEquals(JsBoolean.FALSE, object.getPair("B").getValue()); - - reader = new JsonReader(new StringReader("\n{\r\"A\"\t: true,\"B\":false\n}\r")); - object = JsObject.parseObject(reader); - assertEquals(2, object.size()); - assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); - assertEquals(JsBoolean.FALSE, object.getPair("B").getValue()); - - try{ - reader = new JsonReader(new StringReader("{,}")); - object = JsObject.parseObject(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("{true,}")); - object = JsObject.parseObject(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("{true")); - object = JsObject.parseObject(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("true}")); - object = JsObject.parseObject(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - return; - } - - /** - * Test of putValue method, of class JsObject. - */ - @Test - public void testPutGetValue(){ - System.out.println("putValue"); - - JsObject object = new JsObject(); - assertEquals(0, object.size()); - - object.putValue("x", JsNull.NULL); - assertEquals(1, object.size()); - assertEquals(JsNull.NULL, object.getValue("x")); - assertEquals(null, object.getValue("y")); - - object.putValue("y", JsBoolean.TRUE); - assertEquals(2, object.size()); - assertEquals(JsBoolean.TRUE, object.getValue("y")); - - object.putValue("x", JsBoolean.FALSE); - assertEquals(2, object.size()); - assertEquals(JsBoolean.FALSE, object.getValue("x")); - - return; - } - - /** - * Test of putPair method, of class JsObject. - */ - @Test - public void testPutGetPair(){ - System.out.println("putPair"); - - JsObject object = new JsObject(); - assertEquals(0, object.size()); - - JsPair pair = new JsPair("x", JsNull.NULL); - object.putPair(pair); - assertEquals(1, object.size()); - assertEquals(JsNull.NULL, object.getValue("x")); - - JsPair pair2 = object.getPair("x"); - assertNotNull(pair2); - assertEquals("x", pair2.getName()); - assertEquals(JsNull.NULL, pair2.getValue()); - - assertNull(object.getPair("y")); - - return; - } - - /** - * Test of clear method, of class JsObject. - */ - @Test - public void testClear(){ - System.out.println("clear"); - - JsObject object = new JsObject(); - assertEquals(0, object.size()); - - object.putValue("x", JsNull.NULL); - assertEquals(1, object.size()); - - object.clear(); - assertEquals(0, object.size()); - - return; - } - - /** - * Test of remove method, of class JsObject. - */ - @Test - public void testRemove(){ - System.out.println("remove"); - - JsObject object = new JsObject(); - - object.putValue("x", JsNull.NULL); - assertEquals(1, object.size()); - - assertNotNull(object.getValue("x")); - - assertEquals(JsNull.NULL, object.remove("x")); - assertEquals(0, object.size()); - assertNull(object.getValue("x")); - - assertNull(object.remove("y")); - - return; - } - - /** - * Test of nameSet method, of class JsObject. - */ - @Test - public void testNameSet(){ - System.out.println("nameSet"); - - JsObject object = new JsObject(); - Set set; - - set = object.nameSet(); - assertEquals(0, set.size()); - - object.putValue("y", JsNull.NULL); - object.putValue("z", JsNull.NULL); - object.putValue("x", JsNull.NULL); - - set = object.nameSet(); - assertEquals(3, set.size()); - Object[] names = set.toArray(); - - assertEquals("x", names[0]); - assertEquals("y", names[1]); - assertEquals("z", names[2]); - - return; - } - - /** - * Test of getPairList method, of class JsObject. - */ - @Test - public void testGetPairList(){ - System.out.println("getPairList"); - - JsObject object = new JsObject(); - List list; - - list = object.getPairList(); - assertEquals(0, list.size()); - - object.putValue("y", JsNull.NULL); - object.putValue("z", JsBoolean.TRUE); - object.putValue("x", JsBoolean.FALSE); - - list = object.getPairList(); - assertEquals(3, list.size()); - - assertEquals("x", list.get(0).getName()); - assertEquals("y", list.get(1).getName()); - assertEquals("z", list.get(2).getName()); - assertEquals(JsBoolean.FALSE, list.get(0).getValue()); - assertEquals(JsNull.NULL, list.get(1).getValue()); - assertEquals(JsBoolean.TRUE, list.get(2).getValue()); - - return; - } - - /** - * Test of iterator method, of class JsObject. - */ - @Test - public void testIterator(){ - System.out.println("iterator"); - - JsObject object = new JsObject(); - object.putValue("y", JsBoolean.FALSE); - object.putValue("x", JsBoolean.TRUE); - - Iterator it = object.iterator(); - - assertTrue(it.hasNext()); - assertEquals(JsBoolean.TRUE, it.next().getValue()); - assertTrue(it.hasNext()); - assertEquals(JsBoolean.FALSE, it.next().getValue()); - assertFalse(it.hasNext()); - - return; - } - - /** - * Test of size method, of class JsObject. - */ - @Test - public void testSize(){ - System.out.println("size"); - - JsObject object = new JsObject(); - assertEquals(0, object.size()); - object.putValue("x", JsBoolean.TRUE); - assertEquals(1, object.size()); - object.putValue("y", JsBoolean.FALSE); - assertEquals(2, object.size()); - - return; - } - - /** - * Test of hashCode method, of class JsObject. - */ - @Test - public void testHashCode(){ - System.out.println("hashCode"); - - JsObject obj1 = new JsObject(); - JsObject obj2 = new JsObject(); - assertEquals(obj1.hashCode(), obj2.hashCode()); - - obj1.putValue("x", new JsNumber("99")); - obj2.putValue("x", new JsNumber("99")); - assertEquals(obj1.hashCode(), obj2.hashCode()); - - return; - } - - /** - * Test of equals method, of class JsObject. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - - JsObject obj1 = new JsObject(); - JsObject obj2 = new JsObject(); - assertTrue(obj1.equals(obj2)); - - obj1.putValue("x", new JsNumber("99")); - obj2.putValue("x", new JsNumber("99")); - assertTrue(obj1.equals(obj2)); - - obj1.putValue("x", new JsNumber("99")); - obj2.putValue("x", new JsNumber("999")); - assertFalse(obj1.equals(obj2)); - - assertFalse(obj1.equals(null)); - - return; - } - - /** - * Test of toString method, of class JsObject. - */ - @Test - public void testToString(){ - System.out.println("toString"); - - JsObject object = new JsObject(); - - assertEquals("{}", object.toString()); - - object.putValue("x", JsBoolean.TRUE); - assertEquals("{\"x\":true}", object.toString()); - - object.putValue("y", JsBoolean.FALSE); - assertEquals("{\"x\":true,\"y\":false}", object.toString()); - - object.putValue("z", new JsObject()); - assertEquals("{\"x\":true,\"y\":false,\"z\":{}}", object.toString()); - - return; - } - - /** - * Test of traverse method, of class JsObject. - */ - @Test - public void testTraverse() throws Exception{ - System.out.println("traverse"); - - JsObject obj = new JsObject(); - JsValue val1 = new JsNumber("12"); - JsValue val2 = new JsString("AB"); - obj.putValue("x", val1); - obj.putValue("y", val2); - - final List visited = new LinkedList(); - - try{ - obj.traverse(new ValueVisitor(){ - public void visitValue(JsValue value) - throws JsVisitException{ - visited.add(value); - return; - } - - public void visitPairName(String name) - throws JsVisitException{ - visited.add(name); - return; - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - visited.add(composite); - return; - } - }); - }catch(JsVisitException e){ - fail(); - } - - assertEquals(6, visited.size()); - assertEquals(obj, visited.get(0)); - assertEquals("x", visited.get(1)); - assertEquals(val1, visited.get(2)); - assertEquals("y", visited.get(3)); - assertEquals(val2, visited.get(4)); - assertEquals(obj, visited.get(5)); - - return; - } - - /** - * Test of hasChanged method, of class JsObject. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - - JsObject obj = new JsObject(); - assertFalse(obj.hasChanged()); - - obj.putValue("x", JsNull.NULL); - assertTrue(obj.hasChanged()); - - obj.setUnchanged(); - assertFalse(obj.hasChanged()); - - JsObject child = new JsObject(); - obj.putValue("y", child); - obj.setUnchanged(); - assertFalse(obj.hasChanged()); - - child.putValue("z", JsBoolean.TRUE); - assertTrue(obj.hasChanged()); - obj.setUnchanged(); - assertFalse(obj.hasChanged()); - - return; - } - - /** - * Test of setUnchanged method, of class JsObject. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - - JsObject obj = new JsObject(); - JsObject child = new JsObject(); - obj.putValue("x", child); - - child.putValue("y", JsNull.NULL); - assertTrue(child.hasChanged()); - - obj.setUnchanged(); - assertFalse(child.hasChanged()); - - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.StringReader; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsObjectTest { + + public JsObjectTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of parseObject method, of class JsObject. + */ + @Test + public void testParseObject() throws Exception{ + System.out.println("parseObject"); + + JsonReader reader; + JsObject object; + + reader = new JsonReader(new StringReader("{}")); + object = JsObject.parseObject(reader); + assertEquals(0, object.size()); + + reader = new JsonReader(new StringReader("{\"A\":true}")); + object = JsObject.parseObject(reader); + assertEquals(1, object.size()); + assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); + + reader = new JsonReader(new StringReader("{\"A\":true,\"B\":false}")); + object = JsObject.parseObject(reader); + assertEquals(2, object.size()); + assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); + assertEquals(JsBoolean.FALSE, object.getPair("B").getValue()); + + reader = new JsonReader(new StringReader("\n{\r\"A\"\t: true,\"B\":false\n}\r")); + object = JsObject.parseObject(reader); + assertEquals(2, object.size()); + assertEquals(JsBoolean.TRUE, object.getPair("A").getValue()); + assertEquals(JsBoolean.FALSE, object.getPair("B").getValue()); + + try{ + reader = new JsonReader(new StringReader("{,}")); + object = JsObject.parseObject(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("{true,}")); + object = JsObject.parseObject(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("{true")); + object = JsObject.parseObject(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("true}")); + object = JsObject.parseObject(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + return; + } + + /** + * Test of putValue method, of class JsObject. + */ + @Test + public void testPutGetValue(){ + System.out.println("putValue"); + + JsObject object = new JsObject(); + assertEquals(0, object.size()); + + object.putValue("x", JsNull.NULL); + assertEquals(1, object.size()); + assertEquals(JsNull.NULL, object.getValue("x")); + assertEquals(null, object.getValue("y")); + + object.putValue("y", JsBoolean.TRUE); + assertEquals(2, object.size()); + assertEquals(JsBoolean.TRUE, object.getValue("y")); + + object.putValue("x", JsBoolean.FALSE); + assertEquals(2, object.size()); + assertEquals(JsBoolean.FALSE, object.getValue("x")); + + return; + } + + /** + * Test of putPair method, of class JsObject. + */ + @Test + public void testPutGetPair(){ + System.out.println("putPair"); + + JsObject object = new JsObject(); + assertEquals(0, object.size()); + + JsPair pair = new JsPair("x", JsNull.NULL); + object.putPair(pair); + assertEquals(1, object.size()); + assertEquals(JsNull.NULL, object.getValue("x")); + + JsPair pair2 = object.getPair("x"); + assertNotNull(pair2); + assertEquals("x", pair2.getName()); + assertEquals(JsNull.NULL, pair2.getValue()); + + assertNull(object.getPair("y")); + + return; + } + + /** + * Test of clear method, of class JsObject. + */ + @Test + public void testClear(){ + System.out.println("clear"); + + JsObject object = new JsObject(); + assertEquals(0, object.size()); + + object.putValue("x", JsNull.NULL); + assertEquals(1, object.size()); + + object.clear(); + assertEquals(0, object.size()); + + return; + } + + /** + * Test of remove method, of class JsObject. + */ + @Test + public void testRemove(){ + System.out.println("remove"); + + JsObject object = new JsObject(); + + object.putValue("x", JsNull.NULL); + assertEquals(1, object.size()); + + assertNotNull(object.getValue("x")); + + assertEquals(JsNull.NULL, object.remove("x")); + assertEquals(0, object.size()); + assertNull(object.getValue("x")); + + assertNull(object.remove("y")); + + return; + } + + /** + * Test of nameSet method, of class JsObject. + */ + @Test + public void testNameSet(){ + System.out.println("nameSet"); + + JsObject object = new JsObject(); + Set set; + + set = object.nameSet(); + assertEquals(0, set.size()); + + object.putValue("y", JsNull.NULL); + object.putValue("z", JsNull.NULL); + object.putValue("x", JsNull.NULL); + + set = object.nameSet(); + assertEquals(3, set.size()); + Object[] names = set.toArray(); + + assertEquals("x", names[0]); + assertEquals("y", names[1]); + assertEquals("z", names[2]); + + return; + } + + /** + * Test of getPairList method, of class JsObject. + */ + @Test + public void testGetPairList(){ + System.out.println("getPairList"); + + JsObject object = new JsObject(); + List list; + + list = object.getPairList(); + assertEquals(0, list.size()); + + object.putValue("y", JsNull.NULL); + object.putValue("z", JsBoolean.TRUE); + object.putValue("x", JsBoolean.FALSE); + + list = object.getPairList(); + assertEquals(3, list.size()); + + assertEquals("x", list.get(0).getName()); + assertEquals("y", list.get(1).getName()); + assertEquals("z", list.get(2).getName()); + assertEquals(JsBoolean.FALSE, list.get(0).getValue()); + assertEquals(JsNull.NULL, list.get(1).getValue()); + assertEquals(JsBoolean.TRUE, list.get(2).getValue()); + + return; + } + + /** + * Test of iterator method, of class JsObject. + */ + @Test + public void testIterator(){ + System.out.println("iterator"); + + JsObject object = new JsObject(); + object.putValue("y", JsBoolean.FALSE); + object.putValue("x", JsBoolean.TRUE); + + Iterator it = object.iterator(); + + assertTrue(it.hasNext()); + assertEquals(JsBoolean.TRUE, it.next().getValue()); + assertTrue(it.hasNext()); + assertEquals(JsBoolean.FALSE, it.next().getValue()); + assertFalse(it.hasNext()); + + return; + } + + /** + * Test of size method, of class JsObject. + */ + @Test + public void testSize(){ + System.out.println("size"); + + JsObject object = new JsObject(); + assertEquals(0, object.size()); + object.putValue("x", JsBoolean.TRUE); + assertEquals(1, object.size()); + object.putValue("y", JsBoolean.FALSE); + assertEquals(2, object.size()); + + return; + } + + /** + * Test of hashCode method, of class JsObject. + */ + @Test + public void testHashCode(){ + System.out.println("hashCode"); + + JsObject obj1 = new JsObject(); + JsObject obj2 = new JsObject(); + assertEquals(obj1.hashCode(), obj2.hashCode()); + + obj1.putValue("x", new JsNumber("99")); + obj2.putValue("x", new JsNumber("99")); + assertEquals(obj1.hashCode(), obj2.hashCode()); + + return; + } + + /** + * Test of equals method, of class JsObject. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + + JsObject obj1 = new JsObject(); + JsObject obj2 = new JsObject(); + assertTrue(obj1.equals(obj2)); + + obj1.putValue("x", new JsNumber("99")); + obj2.putValue("x", new JsNumber("99")); + assertTrue(obj1.equals(obj2)); + + obj1.putValue("x", new JsNumber("99")); + obj2.putValue("x", new JsNumber("999")); + assertFalse(obj1.equals(obj2)); + + assertFalse(obj1.equals(null)); + + return; + } + + /** + * Test of toString method, of class JsObject. + */ + @Test + public void testToString(){ + System.out.println("toString"); + + JsObject object = new JsObject(); + + assertEquals("{}", object.toString()); + + object.putValue("x", JsBoolean.TRUE); + assertEquals("{\"x\":true}", object.toString()); + + object.putValue("y", JsBoolean.FALSE); + assertEquals("{\"x\":true,\"y\":false}", object.toString()); + + object.putValue("z", new JsObject()); + assertEquals("{\"x\":true,\"y\":false,\"z\":{}}", object.toString()); + + return; + } + + /** + * Test of traverse method, of class JsObject. + */ + @Test + public void testTraverse() throws Exception{ + System.out.println("traverse"); + + JsObject obj = new JsObject(); + JsValue val1 = new JsNumber("12"); + JsValue val2 = new JsString("AB"); + obj.putValue("x", val1); + obj.putValue("y", val2); + + final List visited = new LinkedList(); + + try{ + obj.traverse(new ValueVisitor(){ + public void visitValue(JsValue value) + throws JsVisitException{ + visited.add(value); + return; + } + + public void visitPairName(String name) + throws JsVisitException{ + visited.add(name); + return; + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + visited.add(composite); + return; + } + }); + }catch(JsVisitException e){ + fail(); + } + + assertEquals(6, visited.size()); + assertEquals(obj, visited.get(0)); + assertEquals("x", visited.get(1)); + assertEquals(val1, visited.get(2)); + assertEquals("y", visited.get(3)); + assertEquals(val2, visited.get(4)); + assertEquals(obj, visited.get(5)); + + return; + } + + /** + * Test of hasChanged method, of class JsObject. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + + JsObject obj = new JsObject(); + assertFalse(obj.hasChanged()); + + obj.putValue("x", JsNull.NULL); + assertTrue(obj.hasChanged()); + + obj.setUnchanged(); + assertFalse(obj.hasChanged()); + + JsObject child = new JsObject(); + obj.putValue("y", child); + obj.setUnchanged(); + assertFalse(obj.hasChanged()); + + child.putValue("z", JsBoolean.TRUE); + assertTrue(obj.hasChanged()); + obj.setUnchanged(); + assertFalse(obj.hasChanged()); + + return; + } + + /** + * Test of setUnchanged method, of class JsObject. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + + JsObject obj = new JsObject(); + JsObject child = new JsObject(); + obj.putValue("x", child); + + child.putValue("y", JsNull.NULL); + assertTrue(child.hasChanged()); + + obj.setUnchanged(); + assertFalse(child.hasChanged()); + + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsPairTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsPairTest.java index dca63a4..159fde5 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsPairTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsPairTest.java @@ -1,104 +1,104 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsPairTest { - - public JsPairTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of getName method, of class JsPair. - */ - @Test - public void testGetName(){ - System.out.println("getName"); - - JsPair pair; - - pair = new JsPair("", JsNull.NULL); - assertEquals("", pair.getName()); - - pair = new JsPair("a", JsNull.NULL); - assertEquals("a", pair.getName()); - - return; - } - - /** - * Test of getValue method, of class JsPair. - */ - @Test - public void testGetValue(){ - System.out.println("getValue"); - - JsPair pair; - - pair = new JsPair("x", JsNull.NULL); - assertEquals(JsNull.NULL, pair.getValue()); - - pair = new JsPair("x", "abc"); - assertEquals(new JsString("abc"), pair.getValue()); - - pair = new JsPair("x", true); - assertEquals(JsBoolean.TRUE, pair.getValue()); - - pair = new JsPair("x", false); - assertEquals(JsBoolean.FALSE, pair.getValue()); - - pair = new JsPair("x", 999999999999L); - assertEquals(new JsNumber("999999999999"), pair.getValue()); - - pair = new JsPair("x", 1.25); - assertEquals(new JsNumber("1.25"), pair.getValue()); - - return; - } - - /** - * Test of toString method, of class JsPair. - */ - @Test - public void testToString(){ - System.out.println("toString"); - - JsPair pair; - - pair = new JsPair("x", JsNull.NULL); - assertEquals("\"x\":null", pair.toString()); - - pair = new JsPair("", JsNull.NULL); - assertEquals("\"\":null", pair.toString()); - - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsPairTest { + + public JsPairTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of getName method, of class JsPair. + */ + @Test + public void testGetName(){ + System.out.println("getName"); + + JsPair pair; + + pair = new JsPair("", JsNull.NULL); + assertEquals("", pair.getName()); + + pair = new JsPair("a", JsNull.NULL); + assertEquals("a", pair.getName()); + + return; + } + + /** + * Test of getValue method, of class JsPair. + */ + @Test + public void testGetValue(){ + System.out.println("getValue"); + + JsPair pair; + + pair = new JsPair("x", JsNull.NULL); + assertEquals(JsNull.NULL, pair.getValue()); + + pair = new JsPair("x", "abc"); + assertEquals(new JsString("abc"), pair.getValue()); + + pair = new JsPair("x", true); + assertEquals(JsBoolean.TRUE, pair.getValue()); + + pair = new JsPair("x", false); + assertEquals(JsBoolean.FALSE, pair.getValue()); + + pair = new JsPair("x", 999999999999L); + assertEquals(new JsNumber("999999999999"), pair.getValue()); + + pair = new JsPair("x", 1.25); + assertEquals(new JsNumber("1.25"), pair.getValue()); + + return; + } + + /** + * Test of toString method, of class JsPair. + */ + @Test + public void testToString(){ + System.out.println("toString"); + + JsPair pair; + + pair = new JsPair("x", JsNull.NULL); + assertEquals("\"x\":null", pair.toString()); + + pair = new JsPair("", JsNull.NULL); + assertEquals("\"\":null", pair.toString()); + + return; + } + +} diff --git a/src/test/java/jp/sourceforge/jindolf/json/JsStringTest.java b/src/test/java/jp/sourceforge/jindolf/json/JsStringTest.java index fd98a56..7bcb819 100644 --- a/src/test/java/jp/sourceforge/jindolf/json/JsStringTest.java +++ b/src/test/java/jp/sourceforge/jindolf/json/JsStringTest.java @@ -1,431 +1,431 @@ -/* - * Copyright(c) 2009 olyutorskii - */ - -package jp.sourceforge.jindolf.json; - -import java.io.StringReader; -import java.util.SortedSet; -import java.util.TreeSet; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class JsStringTest { - - public JsStringTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception{ - } - - @AfterClass - public static void tearDownClass() throws Exception{ - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - /** - * Test of parseHexChar method, of class JsString. - */ - @Test - public void testParseHexChar() throws Exception{ - System.out.println("parseHexChar"); - - JsonReader reader; - char ch; - - reader = new JsonReader(new StringReader("0000")); - ch = JsString.parseHexChar(reader); - assertEquals('\u0000', ch); - - reader = new JsonReader(new StringReader("ffff")); - ch = JsString.parseHexChar(reader); - assertEquals('\uffff', ch); - - reader = new JsonReader(new StringReader("FFFF")); - ch = JsString.parseHexChar(reader); - assertEquals('\uffff', ch); - - reader = new JsonReader(new StringReader("dead")); - ch = JsString.parseHexChar(reader); - assertEquals('\udead', ch); - - reader = new JsonReader(new StringReader("abcde")); - ch = JsString.parseHexChar(reader); - assertEquals('\uabcd', ch); - - try{ - reader = new JsonReader(new StringReader("000,")); - ch = JsString.parseHexChar(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - return; - } - - /** - * Test of parseString method, of class JsString. - */ - @Test - public void testParseString() throws Exception{ - System.out.println("parseString"); - - JsonReader reader; - JsString string; - - reader = new JsonReader(new StringReader("\"abc\"")); - string = JsString.parseString(reader); - assertEquals("abc", string.toRawString()); - - reader = new JsonReader(new StringReader("\"あいう\"")); - string = JsString.parseString(reader); - assertEquals("あいう", string.toRawString()); - - reader = new JsonReader(new StringReader("\"\\\"\\\\\\/\"")); - string = JsString.parseString(reader); - assertEquals("\"\\/", string.toRawString()); - - reader = new JsonReader(new StringReader("\"\\b\\f\\n\\r\\t\"")); - string = JsString.parseString(reader); - assertEquals("\b\f\n\r\t", string.toRawString()); - - reader = new JsonReader(new StringReader("\"\\uabcd\\uCDEF\"")); - string = JsString.parseString(reader); - assertEquals("\uabcd\ucdef", string.toRawString()); - - try{ - reader = new JsonReader(new StringReader("abc\"")); - string = JsString.parseString(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - try{ - reader = new JsonReader(new StringReader("\"abc")); - string = JsString.parseString(reader); - fail(); - }catch(JsParseException e){ - // NOTHING - } - - return; - } - - /** - * Test of writeText method, of class JsString. - */ - @Test - public void testWriteText() throws Exception{ - System.out.println("writeText"); - - Appendable appout; - JsString string; - - appout = new StringBuilder(); - string = new JsString(); - JsString.writeText(appout, string); - assertEquals("\"\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("abc"); - JsString.writeText(appout, string); - assertEquals("\"abc\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\""); - JsString.writeText(appout, string); - assertEquals("\"\\\"\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\\"); - JsString.writeText(appout, string); - assertEquals("\"\\\\\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("/"); - JsString.writeText(appout, string); - assertEquals("\"\\/\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\b"); - JsString.writeText(appout, string); - assertEquals("\"\\b\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\f"); - JsString.writeText(appout, string); - assertEquals("\"\\f\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\n"); - JsString.writeText(appout, string); - assertEquals("\"\\n\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\r"); - JsString.writeText(appout, string); - assertEquals("\"\\r\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\t"); - JsString.writeText(appout, string); - assertEquals("\"\\t\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("\u0001"); - JsString.writeText(appout, string); - assertEquals("\"\\u0001\"", appout.toString()); - - appout = new StringBuilder(); - string = new JsString("あ"); - JsString.writeText(appout, string); - assertEquals("\"あ\"", appout.toString()); - - return; - } - - /** - * Test of traverse method, of class JsString. - */ - @Test - public void testTraverse(){ - System.out.println("traverse"); - - JsString string = new JsString("A"); - - try{ - string.traverse(new ValueVisitor(){ - int ct = 0; - - public void visitValue(JsValue value) - throws JsVisitException{ - assertEquals(new JsString("A"), value); - assertTrue(this.ct++ <= 0); - } - - public void visitPairName(String name) - throws JsVisitException{ - throw new JsVisitException(); - } - - public void visitCollectionClose(JsValue composite) - throws JsVisitException{ - throw new JsVisitException(); - } - }); - }catch(JsVisitException e){ - fail(); - } - - return; - } - - /** - * Test of hasChanged method, of class JsString. - */ - @Test - public void testHasChanged(){ - System.out.println("hasChanged"); - - JsString string = new JsString("A"); - - assertFalse(string.hasChanged()); - string.setUnchanged(); - assertFalse(string.hasChanged()); - - return; - } - - /** - * Test of setUnchanged method, of class JsString. - */ - @Test - public void testSetUnchanged(){ - System.out.println("setUnchanged"); - - JsString string = new JsString("A"); - - string.setUnchanged(); - assertFalse(string.hasChanged()); - - return; - } - - /** - * Test of charAt method, of class JsString. - */ - @Test - public void testCharAt(){ - System.out.println("charAt"); - - JsString string; - - string = new JsString("abcde"); - assertEquals('b', string.charAt(1)); - - try{ - string.charAt(999); - fail(); - }catch(IndexOutOfBoundsException e){ - // NOTHING - } - - return; - } - - /** - * Test of length method, of class JsString. - */ - @Test - public void testLength(){ - System.out.println("length"); - - assertEquals(0, new JsString().length()); - assertEquals(0, new JsString("").length()); - assertEquals(1, new JsString("A").length()); - assertEquals(2, new JsString("AB").length()); - assertEquals(3, new JsString("A\"B").length()); - - return; - } - - /** - * Test of subSequence method, of class JsString. - */ - @Test - public void testSubSequence(){ - System.out.println("subSequence"); - - JsString string; - - string = new JsString("abcde"); - assertEquals("bcd", string.subSequence(1, 4).toString()); - assertEquals("", string.subSequence(1, 1).toString()); - - try{ - string.subSequence(1,999); - fail(); - }catch(IndexOutOfBoundsException e){ - // NOTHING - } - - return; - } - - /** - * Test of hashCode method, of class JsString. - */ - @Test - public void testHashCode(){ - System.out.println("hashCode"); - assertEquals(new JsString("A").hashCode(), new JsString("A").hashCode()); - return; - } - - /** - * Test of equals method, of class JsString. - */ - @Test - public void testEquals(){ - System.out.println("equals"); - - assertTrue(new JsString("A").equals(new JsString("A"))); - assertFalse(new JsString("A").equals(new JsString("a"))); - assertFalse(new JsString("A").equals(null)); - - return; - } - - /** - * Test of compareTo method, of class JsString. - */ - @Test - public void testCompareTo(){ - System.out.println("compareTo"); - - assertTrue(0 == new JsString("A").compareTo(new JsString("A"))); - assertTrue(0 > new JsString("A").compareTo(new JsString("a"))); - assertTrue(0 < new JsString("a").compareTo(new JsString("A"))); - assertTrue(0 < new JsString("A").compareTo(null)); - - SortedSet set = new TreeSet(); - - set.clear(); - set.add(new JsString("A")); - set.add(new JsString("a")); - assertEquals(new JsString("A"), set.first()); - assertEquals(new JsString("a"), set.last()); - - set.clear(); - set.add(new JsString("a")); - set.add(new JsString("A")); - assertEquals(new JsString("A"), set.first()); - assertEquals(new JsString("a"), set.last()); - - return; - } - - /** - * Test of toString method, of class JsString. - */ - @Test - public void testToString(){ - System.out.println("toString"); - - assertEquals("\"\"", new JsString("").toString()); - assertEquals("\"abc\"", new JsString("abc").toString()); - assertEquals("\"\\\"\"", new JsString("\"").toString()); - assertEquals("\"\\\\\"", new JsString("\\").toString()); - assertEquals("\"\\/\"", new JsString("/").toString()); - assertEquals("\"\\b\"", new JsString("\b").toString()); - assertEquals("\"\\f\"", new JsString("\f").toString()); - assertEquals("\"\\n\"", new JsString("\n").toString()); - assertEquals("\"\\r\"", new JsString("\r").toString()); - assertEquals("\"\\t\"", new JsString("\t").toString()); - assertEquals("\"\\u0001\"", new JsString("\u0001").toString()); - assertEquals("\"あ\"", new JsString("あ").toString()); - - return; - } - - /** - * Test of toRawString method, of class JsString. - */ - @Test - public void testToRawString(){ - System.out.println("toRawString"); - - assertEquals("", new JsString("").toRawString()); - assertEquals("abc", new JsString("abc").toRawString()); - assertEquals("\"", new JsString("\"").toRawString()); - assertEquals("\\", new JsString("\\").toRawString()); - assertEquals("/", new JsString("/").toRawString()); - assertEquals("\b", new JsString("\b").toRawString()); - assertEquals("\f", new JsString("\f").toRawString()); - assertEquals("\n", new JsString("\n").toRawString()); - assertEquals("\r", new JsString("\r").toRawString()); - assertEquals("\t", new JsString("\t").toRawString()); - assertEquals("\u0001", new JsString("\u0001").toRawString()); - assertEquals("あ", new JsString("あ").toRawString()); - - return; - } - -} +/* + * Copyright(c) 2009 olyutorskii + */ + +package jp.sourceforge.jindolf.json; + +import java.io.StringReader; +import java.util.SortedSet; +import java.util.TreeSet; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class JsStringTest { + + public JsStringTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception{ + } + + @AfterClass + public static void tearDownClass() throws Exception{ + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of parseHexChar method, of class JsString. + */ + @Test + public void testParseHexChar() throws Exception{ + System.out.println("parseHexChar"); + + JsonReader reader; + char ch; + + reader = new JsonReader(new StringReader("0000")); + ch = JsString.parseHexChar(reader); + assertEquals('\u0000', ch); + + reader = new JsonReader(new StringReader("ffff")); + ch = JsString.parseHexChar(reader); + assertEquals('\uffff', ch); + + reader = new JsonReader(new StringReader("FFFF")); + ch = JsString.parseHexChar(reader); + assertEquals('\uffff', ch); + + reader = new JsonReader(new StringReader("dead")); + ch = JsString.parseHexChar(reader); + assertEquals('\udead', ch); + + reader = new JsonReader(new StringReader("abcde")); + ch = JsString.parseHexChar(reader); + assertEquals('\uabcd', ch); + + try{ + reader = new JsonReader(new StringReader("000,")); + ch = JsString.parseHexChar(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + return; + } + + /** + * Test of parseString method, of class JsString. + */ + @Test + public void testParseString() throws Exception{ + System.out.println("parseString"); + + JsonReader reader; + JsString string; + + reader = new JsonReader(new StringReader("\"abc\"")); + string = JsString.parseString(reader); + assertEquals("abc", string.toRawString()); + + reader = new JsonReader(new StringReader("\"あいう\"")); + string = JsString.parseString(reader); + assertEquals("あいう", string.toRawString()); + + reader = new JsonReader(new StringReader("\"\\\"\\\\\\/\"")); + string = JsString.parseString(reader); + assertEquals("\"\\/", string.toRawString()); + + reader = new JsonReader(new StringReader("\"\\b\\f\\n\\r\\t\"")); + string = JsString.parseString(reader); + assertEquals("\b\f\n\r\t", string.toRawString()); + + reader = new JsonReader(new StringReader("\"\\uabcd\\uCDEF\"")); + string = JsString.parseString(reader); + assertEquals("\uabcd\ucdef", string.toRawString()); + + try{ + reader = new JsonReader(new StringReader("abc\"")); + string = JsString.parseString(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + try{ + reader = new JsonReader(new StringReader("\"abc")); + string = JsString.parseString(reader); + fail(); + }catch(JsParseException e){ + // NOTHING + } + + return; + } + + /** + * Test of writeText method, of class JsString. + */ + @Test + public void testWriteText() throws Exception{ + System.out.println("writeText"); + + Appendable appout; + JsString string; + + appout = new StringBuilder(); + string = new JsString(); + JsString.writeText(appout, string); + assertEquals("\"\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("abc"); + JsString.writeText(appout, string); + assertEquals("\"abc\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\""); + JsString.writeText(appout, string); + assertEquals("\"\\\"\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\\"); + JsString.writeText(appout, string); + assertEquals("\"\\\\\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("/"); + JsString.writeText(appout, string); + assertEquals("\"\\/\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\b"); + JsString.writeText(appout, string); + assertEquals("\"\\b\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\f"); + JsString.writeText(appout, string); + assertEquals("\"\\f\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\n"); + JsString.writeText(appout, string); + assertEquals("\"\\n\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\r"); + JsString.writeText(appout, string); + assertEquals("\"\\r\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\t"); + JsString.writeText(appout, string); + assertEquals("\"\\t\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("\u0001"); + JsString.writeText(appout, string); + assertEquals("\"\\u0001\"", appout.toString()); + + appout = new StringBuilder(); + string = new JsString("あ"); + JsString.writeText(appout, string); + assertEquals("\"あ\"", appout.toString()); + + return; + } + + /** + * Test of traverse method, of class JsString. + */ + @Test + public void testTraverse(){ + System.out.println("traverse"); + + JsString string = new JsString("A"); + + try{ + string.traverse(new ValueVisitor(){ + int ct = 0; + + public void visitValue(JsValue value) + throws JsVisitException{ + assertEquals(new JsString("A"), value); + assertTrue(this.ct++ <= 0); + } + + public void visitPairName(String name) + throws JsVisitException{ + throw new JsVisitException(); + } + + public void visitCollectionClose(JsValue composite) + throws JsVisitException{ + throw new JsVisitException(); + } + }); + }catch(JsVisitException e){ + fail(); + } + + return; + } + + /** + * Test of hasChanged method, of class JsString. + */ + @Test + public void testHasChanged(){ + System.out.println("hasChanged"); + + JsString string = new JsString("A"); + + assertFalse(string.hasChanged()); + string.setUnchanged(); + assertFalse(string.hasChanged()); + + return; + } + + /** + * Test of setUnchanged method, of class JsString. + */ + @Test + public void testSetUnchanged(){ + System.out.println("setUnchanged"); + + JsString string = new JsString("A"); + + string.setUnchanged(); + assertFalse(string.hasChanged()); + + return; + } + + /** + * Test of charAt method, of class JsString. + */ + @Test + public void testCharAt(){ + System.out.println("charAt"); + + JsString string; + + string = new JsString("abcde"); + assertEquals('b', string.charAt(1)); + + try{ + string.charAt(999); + fail(); + }catch(IndexOutOfBoundsException e){ + // NOTHING + } + + return; + } + + /** + * Test of length method, of class JsString. + */ + @Test + public void testLength(){ + System.out.println("length"); + + assertEquals(0, new JsString().length()); + assertEquals(0, new JsString("").length()); + assertEquals(1, new JsString("A").length()); + assertEquals(2, new JsString("AB").length()); + assertEquals(3, new JsString("A\"B").length()); + + return; + } + + /** + * Test of subSequence method, of class JsString. + */ + @Test + public void testSubSequence(){ + System.out.println("subSequence"); + + JsString string; + + string = new JsString("abcde"); + assertEquals("bcd", string.subSequence(1, 4).toString()); + assertEquals("", string.subSequence(1, 1).toString()); + + try{ + string.subSequence(1,999); + fail(); + }catch(IndexOutOfBoundsException e){ + // NOTHING + } + + return; + } + + /** + * Test of hashCode method, of class JsString. + */ + @Test + public void testHashCode(){ + System.out.println("hashCode"); + assertEquals(new JsString("A").hashCode(), new JsString("A").hashCode()); + return; + } + + /** + * Test of equals method, of class JsString. + */ + @Test + public void testEquals(){ + System.out.println("equals"); + + assertTrue(new JsString("A").equals(new JsString("A"))); + assertFalse(new JsString("A").equals(new JsString("a"))); + assertFalse(new JsString("A").equals(null)); + + return; + } + + /** + * Test of compareTo method, of class JsString. + */ + @Test + public void testCompareTo(){ + System.out.println("compareTo"); + + assertTrue(0 == new JsString("A").compareTo(new JsString("A"))); + assertTrue(0 > new JsString("A").compareTo(new JsString("a"))); + assertTrue(0 < new JsString("a").compareTo(new JsString("A"))); + assertTrue(0 < new JsString("A").compareTo(null)); + + SortedSet set = new TreeSet(); + + set.clear(); + set.add(new JsString("A")); + set.add(new JsString("a")); + assertEquals(new JsString("A"), set.first()); + assertEquals(new JsString("a"), set.last()); + + set.clear(); + set.add(new JsString("a")); + set.add(new JsString("A")); + assertEquals(new JsString("A"), set.first()); + assertEquals(new JsString("a"), set.last()); + + return; + } + + /** + * Test of toString method, of class JsString. + */ + @Test + public void testToString(){ + System.out.println("toString"); + + assertEquals("\"\"", new JsString("").toString()); + assertEquals("\"abc\"", new JsString("abc").toString()); + assertEquals("\"\\\"\"", new JsString("\"").toString()); + assertEquals("\"\\\\\"", new JsString("\\").toString()); + assertEquals("\"\\/\"", new JsString("/").toString()); + assertEquals("\"\\b\"", new JsString("\b").toString()); + assertEquals("\"\\f\"", new JsString("\f").toString()); + assertEquals("\"\\n\"", new JsString("\n").toString()); + assertEquals("\"\\r\"", new JsString("\r").toString()); + assertEquals("\"\\t\"", new JsString("\t").toString()); + assertEquals("\"\\u0001\"", new JsString("\u0001").toString()); + assertEquals("\"あ\"", new JsString("あ").toString()); + + return; + } + + /** + * Test of toRawString method, of class JsString. + */ + @Test + public void testToRawString(){ + System.out.println("toRawString"); + + assertEquals("", new JsString("").toRawString()); + assertEquals("abc", new JsString("abc").toRawString()); + assertEquals("\"", new JsString("\"").toRawString()); + assertEquals("\\", new JsString("\\").toRawString()); + assertEquals("/", new JsString("/").toRawString()); + assertEquals("\b", new JsString("\b").toRawString()); + assertEquals("\f", new JsString("\f").toRawString()); + assertEquals("\n", new JsString("\n").toRawString()); + assertEquals("\r", new JsString("\r").toRawString()); + assertEquals("\t", new JsString("\t").toRawString()); + assertEquals("\u0001", new JsString("\u0001").toRawString()); + assertEquals("あ", new JsString("あ").toRawString()); + + return; + } + +} -- 2.11.0