OSDN Git Service

メイン画面の言語プルダウンに存在しないコードを手入力するとエラーになる問題を修正。
[wptscs/wpts.git] / Wptscs / Logics / Translator.cs
1 // ================================================================================================
2 // <summary>
3 //      翻訳支援処理を実装するための共通クラスソース</summary>
4 //
5 // <copyright file="Translator.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs.Logics
12 {
13     using System;
14     using System.Diagnostics;
15     using System.IO;
16     using System.Net;
17     using System.Net.NetworkInformation;
18     using System.Reflection;
19     using Honememo.Utilities;
20     using Honememo.Wptscs.Models;
21     using Honememo.Wptscs.Properties;
22     using Honememo.Wptscs.Websites;
23
24     /// <summary>
25     /// 翻訳支援処理を実装するための共通クラスです。
26     /// </summary>
27     public abstract class Translator
28     {
29         #region private変数
30
31         /// <summary>
32         /// 処理状態メッセージ。
33         /// </summary>
34         private string status = String.Empty;
35
36         /// <summary>
37         /// 変換後テキスト。
38         /// </summary>
39         private string text = String.Empty;
40
41         /// <summary>
42         /// ログテキスト生成用ロガー。
43         /// </summary>
44         private Logger logger;
45
46         #endregion
47
48         #region コンストラクタ
49
50         /// <summary>
51         /// インスタンスを生成する。
52         /// </summary>
53         public Translator()
54         {
55             this.Stopwatch = new Stopwatch();
56             this.Logger = new Logger();
57         }
58
59         #endregion
60
61         #region デリゲート
62
63         /// <summary>
64         /// <see cref="ChangeStatusInExecuting"/> で実行する処理のためのデリゲート。
65         /// </summary>
66         protected delegate void MethodWithChangeStatus();
67
68         #endregion
69
70         #region イベント
71
72         /// <summary>
73         /// ログ更新伝達イベント。
74         /// </summary>
75         public event EventHandler LogUpdate;
76
77         /// <summary>
78         /// 処理状態更新伝達イベント。
79         /// </summary>
80         public event EventHandler StatusUpdate;
81
82         #endregion
83
84         #region プロパティ
85
86         /// <summary>
87         /// 言語間の項目の対訳表。
88         /// </summary>
89         public TranslationDictionary ItemTable
90         {
91             get;
92             set;
93         }
94
95         /// <summary>
96         /// 言語間の見出しの対訳表。
97         /// </summary>
98         public TranslationTable HeadingTable
99         {
100             get;
101             set;
102         }
103
104         /// <summary>
105         /// ログメッセージ。
106         /// </summary>
107         public string Log
108         {
109             get
110             {
111                 return this.Logger.ToString();
112             }
113         }
114
115         /// <summary>
116         /// 処理状態メッセージ。
117         /// </summary>
118         public string Status
119         {
120             get
121             {
122                 return this.status;
123             }
124
125             protected set
126             {
127                 this.status = StringUtils.DefaultString(value);
128                 if (this.StatusUpdate != null)
129                 {
130                     this.StatusUpdate(this, EventArgs.Empty);
131                 }
132             }
133         }
134
135         /// <summary>
136         /// 処理時間ストップウォッチ。
137         /// </summary>
138         public Stopwatch Stopwatch
139         {
140             get;
141             private set;
142         }
143
144         /// <summary>
145         /// 変換後テキスト。
146         /// </summary>
147         public string Text
148         {
149             get
150             {
151                 return this.text;
152             }
153
154             protected set
155             {
156                 this.text = StringUtils.DefaultString(value);
157             }
158         }
159
160         /// <summary>
161         /// 処理を途中で終了させるためのフラグ。
162         /// </summary>
163         public bool CancellationPending
164         {
165             get;
166             set;
167         }
168
169         /// <summary>
170         /// 翻訳元言語のサイト。
171         /// </summary>
172         public Website From
173         {
174             get;
175             set;
176         }
177
178         /// <summary>
179         /// 翻訳先言語のサイト。
180         /// </summary>
181         public Website To
182         {
183             get;
184             set;
185         }
186
187         /// <summary>
188         /// ログテキスト生成用ロガー。
189         /// </summary>
190         protected Logger Logger
191         {
192             get
193             {
194                 return this.logger;
195             }
196
197             set
198             {
199                 // nullは不可。また、ロガー変更後はイベントを設定
200                 this.logger = Validate.NotNull(value);
201                 this.logger.LogUpdate += this.GetLogUpdate;
202             }
203         }
204
205         #endregion
206
207         #region 静的メソッド
208
209         /// <summary>
210         /// 翻訳支援処理のインスタンスを作成。
211         /// </summary>
212         /// <param name="config">アプリケーション設定。</param>
213         /// <param name="from">翻訳元言語。</param>
214         /// <param name="to">翻訳先言語。</param>
215         /// <returns>生成したインスタンス。</returns>
216         /// <remarks>
217         /// 設定は設定クラスより取得、無ければ一部自動生成する。
218         /// インスタンス生成失敗時は例外を投げる。
219         /// </remarks>
220         public static Translator Create(Config config, string from, string to)
221         {
222             // 処理対象に応じてTranslatorを継承したオブジェクトを生成
223             ConstructorInfo constructor = config.Translator.GetConstructor(Type.EmptyTypes);
224             if (constructor == null)
225             {
226                 throw new NotImplementedException(config.Translator.FullName + " default constructor is not found");
227             }
228
229             // 設定に指定されたクラスを、引数無しのコンストラクタを用いて生成する
230             Translator translator = (Translator)constructor.Invoke(null);
231
232             // Webサイトの設定
233             translator.From = config.GetWebsite(from);
234             translator.To = config.GetWebsite(to);
235
236             // 対訳表(項目)の設定
237             translator.ItemTable = config.GetItemTableNeedCreate(from, to);
238
239             // 対訳表(見出し)の設定、使用する言語は決まっているので組み合わせを設定
240             translator.HeadingTable = config.HeadingTable;
241             translator.HeadingTable.From = from;
242             translator.HeadingTable.To = to;
243
244             return translator;
245         }
246
247         #endregion
248
249         #region publicメソッド
250
251         /// <summary>
252         /// 翻訳支援処理実行。
253         /// </summary>
254         /// <param name="name">記事名。</param>
255         /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Logger"/>に出力される。</exception>
256         /// <exception cref="InvalidOperationException"><see cref="From"/>, <see cref="To"/>が設定されていない場合。</exception>
257         public void Run(string name)
258         {
259             // ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す
260             if (this.From == null || this.To == null)
261             {
262                 throw new InvalidOperationException("From or To is null");
263             }
264
265             // 変数を初期化、処理時間を測定開始
266             this.Initialize();
267             this.Stopwatch.Start();
268
269             // サーバー接続チェック
270             string host = new Uri(this.From.Location).Host;
271             if (!String.IsNullOrEmpty(host) && !Settings.Default.IgnoreError)
272             {
273                 if (!this.Ping(host))
274                 {
275                     throw new ApplicationException("ping failed");
276                 }
277             }
278
279             // ここまでの間に終了要求が出ているかを確認
280             this.ThrowExceptionIfCanceled();
281
282             // 翻訳支援処理実行部の本体を実行
283             // ※以降の処理は、継承クラスにて定義
284             try
285             {
286                 this.RunBody(name);
287             }
288             finally
289             {
290                 // 終了後は処理状態をクリア、処理時間を測定終了
291                 this.Status = String.Empty;
292                 this.Stopwatch.Stop();
293             }
294         }
295         
296         #endregion
297
298         #region protectedメソッド
299
300         /// <summary>
301         /// 翻訳支援処理実行部の本体。
302         /// </summary>
303         /// <param name="name">記事名。</param>
304         /// <exception cref="ApplicationException">処理を中断する場合。中断の理由は<see cref="Logger"/>に出力する。</exception>
305         /// <remarks>テンプレートメソッド的な構造になっています。</remarks>
306         protected abstract void RunBody(string name);
307
308         /// <summary>
309         /// ログ出力によるエラー処理を含んだページ取得処理。
310         /// </summary>
311         /// <param name="title">ページタイトル。</param>
312         /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
313         /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
314         /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
315         /// <remarks>
316         /// 本メソッドは、大きく3パターンの動作を行う。
317         /// <list type="number">
318         /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
319         /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
320         /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
321         ///                    or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
322         /// </list>
323         /// また、実行中は処理状態をサーバー接続中に更新する。
324         /// 実行前後には終了要求のチェックも行う。
325         /// </remarks>
326         protected bool TryGetPage(string title, out Page page)
327         {
328             // 通信開始の前に終了要求が出ているかを確認
329             this.ThrowExceptionIfCanceled();
330
331             // ページ取得処理、実行中は処理状態を変更
332             bool success = false;
333             Page result = null;
334             this.ChangeStatusInExecuting(
335                 () => success = this.TryGetPageBody(title, out result),
336                 Resources.StatusDownloading);
337             page = result;
338
339             // 通信終了後にも再度終了要求を確認
340             this.ThrowExceptionIfCanceled();
341             return success;
342         }
343
344         /// <summary>
345         /// 終了要求が出ている場合、例外を投げる。
346         /// </summary>
347         /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
348         protected void ThrowExceptionIfCanceled()
349         {
350             if (this.CancellationPending)
351             {
352                 throw new ApplicationException("CancellationPending is true");
353             }
354         }
355
356         /// <summary>
357         /// 指定された処理を実行する間、処理状態を渡された値に更新する。
358         /// 処理終了後は以前の処理状態に戻す。
359         /// </summary>
360         /// <param name="method">実行する処理。</param>
361         /// <param name="status">処理状態。</param>
362         protected void ChangeStatusInExecuting(MethodWithChangeStatus method, string status)
363         {
364             // 現在の処理状態を保存、新しい処理状態をセットし、処理を実行する
365             string oldStatus = this.Status;
366             this.Status = status;
367             try
368             {
369                 method();
370             }
371             finally
372             {
373                 // 処理状態を以前の状態に戻す
374                 this.Status = oldStatus;
375             }
376         }
377
378         #endregion
379
380         #region privateメソッド
381
382         /// <summary>
383         /// 翻訳支援処理実行時の初期化処理。
384         /// </summary>
385         private void Initialize()
386         {
387             // 変数を初期化
388             this.Logger.Clear();
389             this.Status = String.Empty;
390             this.Stopwatch.Reset();
391             this.Text = String.Empty;
392             this.CancellationPending = false;
393         }
394
395         /// <summary>
396         /// サーバー接続チェック。
397         /// </summary>
398         /// <param name="server">サーバー名。</param>
399         /// <returns><c>true</c> 接続成功。</returns>
400         /// <remarks>実行中は処理状態をサーバー接続中に更新する。</remarks>
401         private bool Ping(string server)
402         {
403             // サーバー接続チェック、実行中は処理状態を変更
404             bool result = false;
405             this.ChangeStatusInExecuting(
406                 () => result = this.PingBody(server),
407                 Resources.StatusPinging);
408             return result;
409         }
410
411         /// <summary>
412         /// サーバー接続チェック本体。
413         /// </summary>
414         /// <param name="server">サーバー名。</param>
415         /// <returns><c>true</c> 接続成功。</returns>
416         private bool PingBody(string server)
417         {
418             // サーバー接続チェック
419             Ping ping = new Ping();
420             try
421             {
422                 PingReply reply = ping.Send(server);
423                 if (reply.Status != IPStatus.Success)
424                 {
425                     this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, reply.Status.ToString());
426                     return false;
427                 }
428             }
429             catch (Exception e)
430             {
431                 this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, e.InnerException.Message);
432                 return false;
433             }
434
435             return true;
436         }
437
438         /// <summary>
439         /// ログ出力によるエラー処理を含んだページ取得処理本体。
440         /// </summary>
441         /// <param name="title">ページタイトル。</param>
442         /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
443         /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
444         /// <remarks>
445         /// 本メソッドは、大きく3パターンの動作を行う。
446         /// <list type="number">
447         /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
448         /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
449         /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
450         ///                    or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
451         /// </list>
452         /// </remarks>
453         private bool TryGetPageBody(string title, out Page page)
454         {
455             page = null;
456             try
457             {
458                 // 普通に取得できた場合はここで終了
459                 page = this.From.GetPage(title);
460                 return true;
461             }
462             catch (FileNotFoundException)
463             {
464                 // ページ無しによる例外も正常終了
465                 return true;
466             }
467             catch (Exception e)
468             {
469                 // その他例外の場合、まずエラー情報を出力
470                 this.Logger.AddResponse(e.Message);
471                 if (e is WebException && ((WebException)e).Response != null)
472                 {
473                     // 出せるならエラーとなったURLも出力
474                     this.Logger.AddResponse(Resources.LogMessageErrorURL, ((WebException)e).Response.ResponseUri);
475                 }
476
477                 // エラーを無視しない場合、ここで翻訳支援処理を中断する
478                 if (!Settings.Default.IgnoreError)
479                 {
480                     throw new ApplicationException(e.Message, e);
481                 }
482
483                 return false;
484             }
485         }
486
487         /// <summary>
488         /// ロガーのログ状態更新イベント用。
489         /// </summary>
490         /// <param name="sender">イベント発生オブジェクト。</param>
491         /// <param name="e">発生したイベント。</param>
492         private void GetLogUpdate(object sender, EventArgs e)
493         {
494             // もともとこのクラスにあったログ通知イベントをロガーに移動したため、入れ子で呼び出す
495             if (this.LogUpdate != null)
496             {
497                 this.LogUpdate(this, EventArgs.Empty);
498             }
499         }
500
501         #endregion
502     }
503 }