OSDN Git Service

・TVアニメ「Fate/...」のサブタイトル分離がうまくいかない問題を修正
[tainavi/TinyBannavi.git] / TinyBannavi / src / tainavi / TVProgramUtils.java
1 package tainavi;\r
2 \r
3 import java.io.BufferedInputStream;\r
4 import java.io.BufferedOutputStream;\r
5 import java.io.BufferedReader;\r
6 import java.io.BufferedWriter;\r
7 import java.io.File;\r
8 import java.io.FileOutputStream;\r
9 import java.io.FileWriter;\r
10 import java.io.IOException;\r
11 import java.io.InputStreamReader;\r
12 import java.io.OutputStream;\r
13 import java.net.HttpURLConnection;\r
14 import java.net.InetSocketAddress;\r
15 import java.net.MalformedURLException;\r
16 import java.net.Proxy;\r
17 import java.net.URL;\r
18 import java.util.ArrayList;\r
19 import java.util.Calendar;\r
20 import java.util.Collections;\r
21 import java.util.Date;\r
22 import java.util.GregorianCalendar;\r
23 import java.util.HashMap;\r
24 import java.util.List;\r
25 import java.util.regex.Matcher;\r
26 import java.util.regex.Pattern;\r
27 \r
28 import tainavi.TVProgram.ProgFlags;\r
29 import tainavi.TVProgram.ProgGenre;\r
30 import tainavi.TVProgram.ProgOption;\r
31 import tainavi.TVProgram.ProgScrumble;\r
32 import tainavi.TVProgram.ProgSubgenre;\r
33 \r
34 \r
35 /**\r
36  * {@link TVProgram}インタフェース をインプルメントしたWeb番組表プラグインのクラスで利用できる、共有部品の集合です。\r
37  */\r
38 public class TVProgramUtils implements Cloneable {\r
39 \r
40         /*******************************************************************************\r
41          * ディープコピーが意外と大変\r
42          ******************************************************************************/\r
43         \r
44         @Override\r
45         public TVProgramUtils clone() {\r
46                 try {\r
47                         TVProgramUtils p = (TVProgramUtils) super.clone();\r
48                         \r
49                         // フィールドコピーしてもらいたくないもの\r
50                         p.pcenter = null;\r
51                         \r
52                         // static化したのでコピー抑制を必要としなくなったものたち\r
53                         //p.setProgressArea(null);\r
54                         //p.setChConv(null);\r
55                         \r
56                         FieldUtils.deepCopy(p, this); // ディープコピーするよ\r
57                         \r
58                         p.pcenter = new ArrayList<ProgList>();\r
59                         \r
60                         /*\r
61                         // 地域設定をコピー\r
62                         p.aclist = new ArrayList<AreaCode>();\r
63                         for ( AreaCode ac : aclist ) {\r
64                                 p.aclist.add(ac.clone());\r
65                         }\r
66                         \r
67                         // 放送局設定をコピー\r
68                         p.crlist = new ArrayList<Center>();\r
69                         for ( Center cr : crlist ) {\r
70                                 p.crlist.add(cr.clone());\r
71                         }\r
72                         */\r
73 \r
74                         p.setSortedCRlist();\r
75 \r
76                         return p;\r
77                         \r
78                 } catch (CloneNotSupportedException e) {\r
79                         throw new InternalError(e.toString());\r
80                 }\r
81         }\r
82         \r
83         \r
84         /*******************************************************************************\r
85          * 共通情報\r
86          ******************************************************************************/\r
87         \r
88         /**\r
89          * Proxy\r
90          */\r
91         public static boolean setProxy(String host, String port) {\r
92                 Proxy newproxy = null;\r
93                 if ( host != null ) {\r
94                         try {\r
95                                 newproxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, Integer.valueOf(port)));\r
96                         }\r
97                         catch (Exception e) {\r
98                                 e.printStackTrace();\r
99                                 return false;\r
100                         }\r
101                 }\r
102                 proxy = newproxy;\r
103                 return true;\r
104         }\r
105         \r
106         private Proxy getProxy() { return proxy; }\r
107         \r
108         private static Proxy proxy = null;\r
109         \r
110         /**\r
111          * ChannelConvert.dat\r
112          */\r
113         public static void setChConv(ChannelConvert cc) { chconv = cc; }\r
114         \r
115         private ChannelConvert getChConv() { return chconv; }\r
116         \r
117         private static ChannelConvert chconv = null;\r
118         \r
119         /**\r
120          *  ログと進捗ダイアログ\r
121          */\r
122         public static void setProgressArea(StatusWindow o) { stw = o; }\r
123         \r
124         protected void reportProgress(String msg) {\r
125                 if (stw != null) {\r
126                         stw.append(msg);\r
127                 }\r
128                 System.out.println(msg);\r
129         }\r
130 \r
131         private static StatusWindow stw = null;\r
132         \r
133         \r
134         /*******************************************************************************\r
135          * オプション確認\r
136          ******************************************************************************/\r
137         \r
138         public boolean isAreaSelectSupported() { return true; }         // デフォルトはエリアを選べる\r
139         \r
140         \r
141         /*******************************************************************************\r
142          * 定数\r
143          ******************************************************************************/\r
144         \r
145         // コネクションタイムアウト\r
146         private static final int conntout = 5;\r
147         \r
148         // read()タイムアウト\r
149         private static final int readtout = 30;\r
150 \r
151         // 番組表が複数ページに跨っている場合、何ページまで追いかけるか\r
152         protected static final int REFPAGESMAX = 10;\r
153         \r
154         // 番組詳細に情報をconcatする時のセパレータ\r
155         protected final String DETAIL_SEP = "\n\n";\r
156         \r
157         // メッセージID\r
158         private static final String MSGID = "[番組表共通] ";\r
159         private static final String DBGID = "[DEBUG]"+MSGID;\r
160         private static final String ERRID = "[ERROR]"+MSGID;\r
161         \r
162         /*******************************************************************************\r
163          * メンバ変数関連\r
164          ******************************************************************************/\r
165         \r
166         // デバッグフラグ\r
167         public void setDebug(boolean b) { debug = b; }\r
168         protected boolean getDebug() { return debug; }  // 参照はサブクラスのみに許可\r
169         private boolean debug = false;\r
170         \r
171         // 通信エラー時のリトライ回数\r
172         protected void setRetryCount(int n) { retrycount = n; } // サブクラスのみに許可(特殊)\r
173         private int retrycount = 1;\r
174 \r
175         // User-Agent\r
176         public String getUserAgent() { return userAgent; }\r
177         public void setUserAgent(String s) { userAgent = s; }\r
178         private String userAgent = "";\r
179 \r
180         // 番組表キャッシュの格納フォルダ\r
181         public String getProgDir() { return progDir; }\r
182         public void setProgDir(String s) { progDir = s; }\r
183         private String progDir = "progcache";\r
184         \r
185         // 番組表キャッシュの有効時間(H)\r
186         public int getCacheExpired() { return cacheExpired; }\r
187         public void setCacheExpired(int h) { cacheExpired = h; }\r
188         private int cacheExpired = 6;\r
189         \r
190         // 番組詳細取得を行なうかどうか\r
191         public boolean getUseDetailCache() { return useDetailCache; }\r
192         public void setUseDetailCache(boolean b) { useDetailCache = b; }\r
193         private boolean useDetailCache = true;\r
194         \r
195         // 29時跨ぎで2つに分かれた番組情報をくっつけるかどうか\r
196         public boolean getContinueTomorrow() { return continueTomorrow; }\r
197         public void setContinueTomorrow(boolean b) { continueTomorrow = b; }\r
198         private boolean continueTomorrow = false;\r
199         \r
200         // タイトルから話数以降を分離する\r
201         public void setSplitEpno(boolean b) { splitEpno = b; }\r
202         public boolean isSplitEpno() { return splitEpno; }\r
203         private boolean splitEpno = true;\r
204         \r
205         // 可能なら8日分取得する\r
206         public boolean getExpandTo8() { return expandTo8; }\r
207         public void setExpandTo8(boolean b) { expandTo8 = b; }\r
208         private boolean expandTo8 = false;\r
209         \r
210         // 番組詳細キャッシュをオンライン取得するかどうか\r
211         @Deprecated\r
212         protected boolean isForceLoadDetInfo() { return forceLoadDetInfo; }\r
213         @Deprecated\r
214         private boolean forceLoadDetInfo = false;\r
215         \r
216         // もう使っていないようだ\r
217         //public void setAbnormal(boolean b) { abnormal = b; }\r
218         //public boolean getAbnormal() { return abnormal; }\r
219         //private boolean abnormal = false;\r
220 \r
221         \r
222         /*******************************************************************************\r
223          * 番組表固有の情報\r
224          ******************************************************************************/\r
225 \r
226         \r
227         /*******************************************************************************\r
228          * 番組情報\r
229          ******************************************************************************/\r
230         \r
231         /**\r
232          * <P>番組表が格納される場所\r
233          * <P>上から順の{@link ProgList}(pcenter/放送局別)->{@link ProgDateList}(pdate/日付別)→{@link ProgDetailList}pdetail/(番組詳細)\r
234          */\r
235         public ArrayList<ProgList> getCenters() { return(pcenter); }\r
236         public ArrayList<ProgList> pcenter = new ArrayList<ProgList>();\r
237         \r
238         \r
239         /*******************************************************************************\r
240          * 放送局情報\r
241          ******************************************************************************/\r
242         \r
243         /**\r
244          * 放送局名リスト\r
245          * @see ProgList#Center\r
246          * @see #sortedcrlist\r
247          */\r
248         public ArrayList<Center> getCRlist() { return(crlist); }\r
249         public ArrayList<Center> crlist = new ArrayList<Center>();\r
250         \r
251         /**\r
252          * ソート済み放送局名リスト\r
253          * @see ProgList#Center\r
254          * @see #crlist\r
255          */\r
256         public ArrayList<Center> getSortedCRlist() { return(sortedcrlist); }\r
257         public void setSortedCRlist() {\r
258                 sortedcrlist = new ArrayList<Center>();\r
259                 for (int order=1; order<=crlist.size(); order++) {\r
260                         for (Center center : crlist) {\r
261                                 if (center.getOrder() == order) {\r
262                                         sortedcrlist.add(center);\r
263                                         break;\r
264                                 }\r
265                         }\r
266                 }\r
267         }\r
268         public ArrayList<Center> sortedcrlist = new ArrayList<Center>();\r
269         \r
270         // 設定ファイルへ書き出し\r
271         public boolean saveCenter() {\r
272                 String centerListFile = getCenterListFile(getTVProgramId(), getSelectedCode());\r
273                 if ( ! CommonUtils.writeXML(centerListFile,crlist) ) {\r
274                         System.out.println("放送局リストの保存に失敗しました: "+centerListFile+", "+getSelectedCode());\r
275                         return false;\r
276                 }\r
277                 System.out.println("放送局リストを保存しました: "+centerListFile+", "+getSelectedCode());\r
278                 return true;\r
279         }\r
280         \r
281         // 放送局リストファイル名\r
282         protected String getCenterListFile(String id, String code) {\r
283                 return String.format("env%scenter.%s.%s.xml", File.separator, id, code);\r
284         }\r
285         \r
286         // 放送局名に一括してフィルタをかける\r
287         protected void attachChFilters() {\r
288                 for ( Center c : crlist ) {\r
289                         if ( c.getCenterOrig() == null ) {\r
290                                 c.setCenterOrig(c.getCenter());\r
291                         }\r
292                         c.setCenter(getChConv().get(c.getCenterOrig()));\r
293                 }\r
294         }\r
295         \r
296         // 放送局名個別にフィルタをかけたい\r
297         protected String getChName(String chorig) {\r
298                 return getChConv().get(chorig);\r
299         }\r
300 \r
301 \r
302         /*******************************************************************************\r
303          * 地域情報\r
304          ******************************************************************************/\r
305         \r
306         /**\r
307          * 地域コード のリストを取得する\r
308          */\r
309         public ArrayList<AreaCode> getAClist() { return(aclist); }\r
310         public ArrayList<AreaCode> aclist = new ArrayList<AreaCode>();\r
311         \r
312         /**\r
313          * 地域コードから地域名を取得\r
314          */\r
315         public String getArea(String code) {\r
316                 for (AreaCode ac : aclist) {\r
317                         if (ac.getCode().equals(code)) {\r
318                                 return(ac.getArea());\r
319                         }\r
320                 }\r
321                 return(null);\r
322         }\r
323 \r
324         /**\r
325          * 地域名から地域コードを取得\r
326          */\r
327         public String getCode(String area) {\r
328                 for (AreaCode ac : aclist) {\r
329                         if (ac.getArea().equals(area)) {\r
330                                 return(ac.getCode());\r
331                         }\r
332                 }\r
333                 return(null);\r
334         }\r
335         \r
336         /**\r
337          * 地域名を指定して選択中の地域を変更し、ついでにその地域コードを返す\r
338          * @see #getSelectedCode()\r
339          */\r
340         public String setSelectedAreaByName(String area) {\r
341                 AreaCode ac = null;\r
342                 for (AreaCode atmp : aclist) {\r
343                         if (atmp.getArea().equals(area)) {\r
344                         atmp.setSelected(true);\r
345                         ac = atmp;\r
346                         }\r
347                         else {\r
348                         atmp.setSelected(false);\r
349                         }\r
350                 }\r
351                 if (ac != null) {\r
352                         return(ac.getCode());\r
353                 }\r
354                 return(null);\r
355         }\r
356         \r
357         /**\r
358          * 地域コードを指定して選択中の地域を変更し、ついでにその地域名を返す\r
359          * @see #getSelectedArea()\r
360          */\r
361         public String setSelectedAreaByCode(String code) {\r
362                 AreaCode ac = null;\r
363                 for (AreaCode atmp : aclist) {\r
364                         if (atmp.getCode().equals(code)) {\r
365                         atmp.setSelected(true);\r
366                         ac = atmp;\r
367                         }\r
368                         else {\r
369                         atmp.setSelected(false);\r
370                         }\r
371                 }\r
372                 if (ac != null) {\r
373                         return(ac.getArea());\r
374                 }\r
375                 return(null);\r
376         }\r
377         \r
378         /**\r
379          * 選択中の地域名を返す\r
380          */\r
381         public String getSelectedArea() {\r
382                 for (AreaCode ac : aclist) {\r
383                         if (ac.getSelected() == true) {\r
384                                 return(ac.getArea());\r
385                         }\r
386                 }\r
387                 return(getDefaultArea());\r
388         }\r
389 \r
390         /**\r
391          * 選択中の地域コードを返す\r
392          */\r
393         public String getSelectedCode() {\r
394         for (AreaCode ac : aclist) {\r
395                 if (ac.getSelected() == true) {\r
396                         return(ac.getCode());\r
397                 }\r
398         }\r
399                 return(getCode(getDefaultArea()));\r
400         }\r
401         \r
402         // 設定ファイルへ書き出し\r
403         public void saveAreaCode() {\r
404                 if ( CommonUtils.writeXML(getAreaSelectedFile(), aclist) ) {\r
405                         System.out.println(MSGID+"地域リストを保存しました: "+getAreaSelectedFile());\r
406                 }\r
407                 else {\r
408                         System.err.println(ERRID+"地域リストの保存に失敗しました: "+getAreaSelectedFile());\r
409                 }\r
410         }\r
411         \r
412         // 地域情報ファイル名\r
413         protected String getAreaSelectedFile() {\r
414                 return String.format("env%sarea.%s.xml", File.separator, getTVProgramId());\r
415         }\r
416         \r
417         \r
418         /*******************************************************************************\r
419          * タイトル操作関連\r
420          ******************************************************************************/\r
421         \r
422         /**\r
423          * 話数重複排除\r
424          */\r
425         protected String doCutDupEpno(String title, String detail) {\r
426                 // タイトルの末尾に話数がついているかな?\r
427                 Matcher md = Pattern.compile("[  ]*?(#[#  -・0-9]+|第[0-9]+話)$").matcher(title);\r
428                 if ( ! md.find() ) {\r
429                         return title;\r
430                 }\r
431                 \r
432                 ArrayList<String> tnoa = new ArrayList<String>();\r
433                 {\r
434                         Matcher me = Pattern.compile("(\\d+)").matcher(md.group(1));\r
435                         while ( me.find() ) {\r
436                                 tnoa.add(me.group(1));\r
437                         }\r
438                         if ( tnoa.size() == 0 ) {\r
439                                 return title;\r
440                         }\r
441                 }\r
442                 \r
443                 // 番組詳細と重複しているかな?\r
444                 {\r
445                         ArrayList<String> dnoa = new ArrayList<String>();\r
446                         Matcher me = Pattern.compile("#[  ]*([0-9]+)|第([0-9]+)話").matcher(detail);\r
447                         while ( me.find() ) {\r
448                                 if ( me.group(1) != null ) {\r
449                                         dnoa.add(me.group(1));\r
450                                 }\r
451                                 else if ( me.group(2) != null ) {\r
452                                         dnoa.add(me.group(2));\r
453                                 }\r
454                         }\r
455                         if ( dnoa.size() == 0 ) {\r
456                                 return title;\r
457                         }\r
458                         \r
459                         for ( String tno : tnoa ) {\r
460                                 for ( String dno : dnoa ) {\r
461                                         if ( dno.equals(tno) ) {\r
462                                                 dnoa.remove(dno);\r
463                                                 break;\r
464                                         }\r
465                                 }\r
466                         }\r
467                         if ( dnoa.size() == 0 ) {\r
468                                 title = md.replaceFirst("");\r
469                         }\r
470                 }\r
471                 \r
472                 return title;\r
473         }\r
474         \r
475         /**\r
476          * サブタイトルの分離\r
477          * @param pdl\r
478          */\r
479         protected void doSplitSubtitle(ProgDetailList pdl)      {\r
480                 \r
481                 pdl.splitted_title = doCutDupEpno(pdl.title, pdl.detail);       // タイトルと番組詳細中の話数の重複排除\r
482                 \r
483                 String [] d = doSplitEpno(pdl.genre, pdl.splitted_title);       // 分離!\r
484                 \r
485                 pdl.splitted_title = pdl.title.substring(0,d[0].length());\r
486                 \r
487                 if ( d[1].length() > 0 ) {\r
488                         // 番組詳細はサブタイトル分離番組詳細へのポインタでいいよ\r
489                         pdl.splitted_detail = d[1]+DETAIL_SEP+pdl.detail;\r
490                         pdl.detail = pdl.splitted_detail.substring(d[1].length()+DETAIL_SEP.length());\r
491                 }\r
492                 else {\r
493                         // サブタイトルが分離されなかったから同じでいいよ\r
494                         pdl.splitted_detail = pdl.detail;\r
495                 }\r
496                 \r
497                 // タイトル&番組詳細のキーワード検索情報の設定\r
498                 String key_title;\r
499                 String key_detail;\r
500                 if (isSplitEpno()) {\r
501                         // サブタイトルを分離するならばそれを考慮した値を使う\r
502                         key_title = pdl.splitted_title;\r
503                         key_detail = pdl.splitted_detail;\r
504                 }\r
505                 else {\r
506                         key_title = pdl.title;\r
507                         key_detail = pdl.detail;\r
508                 }\r
509                 pdl.titlePop = TraceProgram.replacePop(key_title);\r
510                 pdl.detailPop = TraceProgram.replacePop(key_detail);\r
511                 \r
512                 // 分離しない場合でも、番組追跡はサブタイトル抜きでの検索ができるようにしたい\r
513                 pdl.splitted_titlePop = TraceProgram.replacePop(pdl.splitted_title);\r
514         }\r
515         \r
516         /**\r
517          * サブタイトル分離(Part.11 444-)\r
518          */\r
519         private String[] doSplitEpno(ProgGenre genre, String title) {\r
520                 if ( genre == ProgGenre.MOVIE || genre == ProgGenre.DOCUMENTARY ) {\r
521                         // 映画とドキュメンタリーは何もしない\r
522                 }\r
523                 else {\r
524                         if ( genre == ProgGenre.DORAMA ) {\r
525                                 // ドラマの場合は、"「*」"での分割をしない(土曜ドラマ「タイトル」とかあるため)\r
526                                 Matcher mc = Pattern.compile(SPEP_EXPR_DORAMA).matcher(title);\r
527                                 if ( mc.find() ) {\r
528                                         return(new String[] { mc.group(1),mc.group(2)+" " });\r
529                                 }\r
530                         }\r
531                         else {\r
532                                 String ani = "";\r
533                                 String tit = title;\r
534                                 Matcher mani = Pattern.compile("^(\\s*(?:TV|TV)?アニメ\\s*)(.*)$").matcher(title);\r
535                                 if ( mani.find() ) {\r
536                                         ani = mani.group(1);\r
537                                         tit = mani.group(2);\r
538                                 }\r
539 \r
540                                 // いきなり「で始まる場合や、タイトル中に『TOKYO「萌」探偵』のように「ほげほげ」を含む場合\r
541                                 Matcher mc = Pattern.compile("^([^  ]*「.+?」[^  ]+)(.*)$").matcher(tit);\r
542                                 if (mc.find()) {\r
543                                         Matcher md = Pattern.compile("^[  ]*(.*?)[  ]*?" + SPEP_EXPR).matcher(mc.group(2));\r
544                                         if (md.find()) {\r
545                                                 if (md.group(1).length() == 0) {\r
546                                                         return (new String[]{ani+mc.group(1), md.group(2) + " "});\r
547                                                 } else {\r
548                                                         return (new String[]{ani+mc.group(1) + " " + md.group(1), md.group(2) + " "});\r
549                                                 }\r
550                                         } else {\r
551                                                 return (new String[]{ani+tit, ""});\r
552                                         }\r
553                                 }\r
554                                 mc = Pattern.compile("^(.+?)[  ]*?" + SPEP_EXPR).matcher(tit);\r
555                                 if (mc.find()) {\r
556                                         return (new String[]{ani+mc.group(1), mc.group(2) + " "});\r
557                                 }\r
558                         }\r
559                 }\r
560                 return(new String[] { title,"" });\r
561         }\r
562         \r
563         // サブタイトル判定条件\r
564         private static final String SPEP_EXPR = "(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?|「).*)$";\r
565         \r
566         // サブタイトル判定条件(ジャンル=ドラマ専用)\r
567         private static final String SPEP_EXPR_DORAMA = "^(.+?)[  ]*?(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?).*)$";\r
568         \r
569         \r
570         /**\r
571          * NGワード\r
572          */\r
573         public void abon(ArrayList<String> ngword) {\r
574                 //\r
575                 if (ngword.size() == 0) {\r
576                         return;\r
577                 }\r
578                 for ( ProgList p : pcenter ) {\r
579                         for ( ProgDateList c : p.pdate ) {\r
580                                 for (ProgDetailList d : c.pdetail) {\r
581                                         for (String ngs : ngword) {\r
582                                                 if (d.title.indexOf(ngs) != -1 || d.detail.indexOf(ngs) != -1) {\r
583                                                         d.abon();\r
584                                                         break;\r
585                                                 }\r
586                                         }\r
587                                 }\r
588                         }\r
589                 }\r
590         }\r
591         \r
592         \r
593         \r
594         /*******************************************************************************\r
595          * 番組情報キャッシュ\r
596          ******************************************************************************/\r
597         \r
598         // キャッシュファイルが有効期限内か確認する\r
599         public boolean isCacheOld(String fname) {\r
600                 // キャッシュ期限が無期限の場合はDL禁止\r
601                 if (cacheExpired == 0) {\r
602                         return(false);\r
603                 }\r
604                 // キャッシュ期限があってファイルがない場合はDLしなさい\r
605                 //if (cacheExpired > 0 && fname == null) {\r
606                 if (fname == null) {    // あれだ、期限に関係なくファイル名の指定がなきゃDLするしかないよ\r
607                         return(true);\r
608                 }\r
609                 // 実際のファイルのタイムスタンプを確認する\r
610                 try {\r
611                         File f = new File(fname);\r
612                         if (f.exists() == true) {\r
613                                 long t = System.currentTimeMillis();\r
614                                 if (f.lastModified() < (t - cacheExpired*3600000L) ) {\r
615                                         // ファイルを更新\r
616                                         f.delete();\r
617                                         f.createNewFile();\r
618                                         return(true);\r
619                                 }\r
620                         }\r
621                         else {\r
622                                 // ファイルを作成        \r
623                                 f.createNewFile();\r
624                                 return(true);\r
625                         }\r
626                 }\r
627                 catch (Exception e) {\r
628                         // 例外\r
629                         System.out.println("Exception: isCacheOld() "+e);\r
630                 }\r
631                 return(false);\r
632         }\r
633         \r
634         \r
635         /*******************************************************************************\r
636          * 番組詳細キャッシュ(現在は使われていない)\r
637          ******************************************************************************/\r
638         \r
639         /**\r
640          * 番組詳細をオンライン取得するかどうか\r
641          */\r
642         @Deprecated\r
643         protected void chkForceLoadDetInfo(boolean force) {\r
644                 File f = new File(getDetCacheFile());\r
645                 if (force == true ||\r
646                                 (f.exists() == true && isCacheOld(getDetCacheFile()) == true) ||\r
647                                 (f.exists() == false && isCacheOld(null) == true)) {\r
648                         // Webからロードします\r
649                         forceLoadDetInfo = true;\r
650                 }\r
651                 else if (f.exists()) {\r
652                         // キャッシュファイルが有効なようなので利用します\r
653                         forceLoadDetInfo = false;\r
654                 }\r
655                 else {\r
656                         // 無くても平気…\r
657                         forceLoadDetInfo = false;\r
658                 }\r
659         }\r
660         \r
661         // 番組詳細キャッシュのLOAD\r
662         @Deprecated\r
663         protected HashMap<String,String> loadDetCache(){\r
664                 \r
665                 // 設定ファイルが存在していればファイルから\r
666                 File f = new File(getDetCacheFile());\r
667                 if (f.exists() == true) {\r
668                         @SuppressWarnings("unchecked")\r
669                         HashMap<String,String> cache = (HashMap<String, String>) CommonUtils.readXML(getDetCacheFile());\r
670                         if ( cache != null ) {\r
671                                 return cache;\r
672                         }\r
673                         \r
674                         System.out.println(ERRID+"【致命的エラー】番組詳細キャッシュが読み込めません: "+getDetCacheFile());\r
675                 }\r
676                 \r
677                 // キャッシュなし&エラーは空配列を返す\r
678                 return new HashMap<String, String>();\r
679         }\r
680         \r
681         // 番組詳細キャッシュのSAVE\r
682         @Deprecated\r
683         protected void saveDetCache(HashMap<String,String> cache) {\r
684                 if ( ! CommonUtils.writeXML(getDetCacheFile(), cache) ) {\r
685                         System.err.println(ERRID+"【致命的エラー】番組詳細キャッシュが書き込めません: "+getDetCacheFile());\r
686                 }\r
687         }\r
688         \r
689         \r
690         /*******************************************************************************\r
691          * 番組情報のリフレッシュ関連\r
692          ******************************************************************************/\r
693         \r
694         /**\r
695          * 日付変更線(29:00)をまたいだら過去のデータはカットする\r
696          * <B> PassedProgramでは使わない\r
697          */\r
698         public void refresh() {\r
699                 \r
700                 String critDate = CommonUtils.getDate529(0, true);\r
701                 for ( ProgList p : pcenter ) {\r
702                         int i = 0;\r
703                         for ( ProgDateList c : p.pdate ) {\r
704                                 if ( c.Date.compareTo(critDate) >= 0 ) {\r
705                                         break;\r
706                                 }\r
707                                 i++;\r
708                         }\r
709                         for ( int j=0; j<i; j++) {\r
710                                 p.pdate.remove(0);\r
711                         }\r
712                 }\r
713         }\r
714         \r
715         /**\r
716          * 24時間分番組枠が埋まっているかどうか確認する\r
717          */\r
718         public String chkComplete() {\r
719                 for ( ProgList p : pcenter ) {\r
720                         if (p.enabled) {\r
721                                 for ( ProgDateList c : p.pdate ) {\r
722                                         if (c.pdetail.size()<=1) {\r
723                                                 String msg = "番組枠が存在しません.("+p.Center+","+c.Date+")";\r
724                                                 System.out.println(msg);\r
725                                                 return(msg);\r
726                                         }\r
727                                         if (c.row < 24*60) {\r
728                                                 String msg = "番組枠が24時間分取得できませんでした.("+p.Center+","+c.Date+","+c.row+")";\r
729                                                 System.out.println(msg);\r
730                                                 return(msg);\r
731                                         }\r
732                                 }\r
733                                 if (p.pdate.size() < 7) {\r
734                                         String msg = "番組表が一週間分取得できませんでした.("+p.Center+")";\r
735                                         System.out.println(msg);\r
736                                         return(msg);\r
737                                 }\r
738                         }\r
739                 }\r
740                 return null;\r
741         }\r
742         \r
743         /**\r
744          * 開始終了日時の整理\r
745          */\r
746         public void setAccurateDate(ArrayList<ProgDateList> pcenter) {\r
747         \r
748                 // 先頭のエントリの開始時刻が 5:00 以前の場合\r
749                 for ( ProgDateList pcl : pcenter ) {\r
750                         if (pcl.pdetail.size() <= 0) {\r
751                                 continue;\r
752                         }\r
753                         \r
754                         ProgDetailList pd = pcl.pdetail.get(0);\r
755                         Matcher ma = Pattern.compile("(\\d\\d):(\\d\\d)").matcher(pd.start);\r
756                         if (ma.find()) {\r
757                                 int prelength = 0;\r
758                                 int ahh = Integer.valueOf(ma.group(1));\r
759                                 int amm = Integer.valueOf(ma.group(2));\r
760                                 \r
761                                 GregorianCalendar c = new GregorianCalendar();\r
762                                 c.setTime(new Date());\r
763                                 c.set(Calendar.HOUR_OF_DAY, ahh);\r
764                                 c.set(Calendar.MINUTE, amm);\r
765                                 \r
766                                 if ( pd.start.compareTo("05:00") < 0 ) {\r
767                                         // 5:00以前\r
768                                         prelength = (5*60+0)-(ahh*60+amm);\r
769                                         c.add(Calendar.MINUTE,prelength+pd.length);\r
770                                         pd.end = CommonUtils.getTime(c);\r
771                                 }\r
772                                 else if ( pd.start.compareTo("18:00") >= 0 && pd.start.compareTo("24:00") < 0 ) {\r
773                                         // 前日の24:00以前\r
774                                         prelength = (24*60+0)-(ahh*60+amm)+(5*60);\r
775                                         c.add(Calendar.MINUTE,prelength+pd.length);\r
776                                         pd.end = CommonUtils.getTime(c);\r
777                                 }\r
778                         }\r
779                 }\r
780 \r
781                 for ( ProgDateList pcl : pcenter ) {\r
782         \r
783                         GregorianCalendar c = CommonUtils.getCalendar(pcl.Date);\r
784         \r
785                         boolean extend = false;\r
786                         boolean overtwodays = false;\r
787                         for ( int i=0; i<pcl.pdetail.size(); i++ ) {\r
788                                 \r
789                                 ProgDetailList pdl = pcl.pdetail.get(i);\r
790                                 \r
791                                 // 番組情報がありません\r
792                                 if (pdl.start.compareTo("") == 0) {\r
793                                         continue;\r
794                                 }\r
795                                 \r
796                                 // 表示上の開始日時\r
797                                 if ( i == 0 ) {\r
798                                         if ( pdl.start.compareTo("18:00") >= 0 && pdl.start.compareTo("24:00") < 0 ) {\r
799                                                 // いったい何時間放送するんだよ(--#\r
800                                                 c.add(Calendar.DAY_OF_MONTH, -1);\r
801                                                 overtwodays = true;\r
802                                         }\r
803                                 }\r
804                                 else {\r
805                                         if ( (pdl.start.compareTo("00:00") >= 0 && pdl.start.compareTo("05:00") < 0 && pdl.end.compareTo("05:00") < 0) && extend == false) {\r
806                                                 c.add(Calendar.DAY_OF_MONTH, 1);\r
807                                                 extend = true;\r
808                                         }\r
809                                 }\r
810                                 pdl.startDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.start);\r
811         \r
812                                 // 正確な開始日\r
813                                 pdl.accurateDate = CommonUtils.getDate(c);\r
814                                 \r
815                                 // 表示上の終了日時\r
816                                 if ( overtwodays ) {\r
817                                         c.add(Calendar.DAY_OF_MONTH, 1);\r
818                                         overtwodays = false;\r
819                                 }\r
820                                 else {\r
821                                         if ( pdl.start.compareTo(pdl.end) > 0  && extend == false) {\r
822                                                 c.add(Calendar.DAY_OF_MONTH, 1);\r
823                                                 extend = true;\r
824                                         }\r
825                                 }\r
826                                 \r
827                                 pdl.endDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.end);\r
828                         }\r
829                 }\r
830                 \r
831                 // 29時をまたいで同タイトルが続いている場合は同一番組とみなす\r
832                 if ( continueTomorrow ) {\r
833                         for (int w=0; w<pcenter.size()-1; w++) {\r
834                                 if (pcenter.get(w).pdetail.size() > 0 && pcenter.get(w+1).pdetail.size() > 0) {\r
835                                         ProgDetailList pd1 = pcenter.get(w).pdetail.get(pcenter.get(w).pdetail.size()-1);\r
836                                         ProgDetailList pd2 = pcenter.get(w+1).pdetail.get(0);\r
837                                         if (pd1.title.equals(pd2.title)) {\r
838                                                 pd1.end = pd2.end;\r
839                                                 pd1.endDateTime = pd2.endDateTime;\r
840                                                 \r
841                                                 pd2.start = pd1.start;\r
842                                                 pd2.startDateTime = pd1.startDateTime;\r
843                                                 pd2.accurateDate = pd1.accurateDate;\r
844                                         }\r
845                                         else if (pd2.title.equals("承前")) {\r
846                                                 pd1.end = pd2.end;\r
847                                                 pd1.endDateTime = pd2.endDateTime;\r
848                                                 \r
849                                                 pd2.start = pd1.start;\r
850                                                 pd2.startDateTime = pd1.startDateTime;\r
851                                                 pd2.accurateDate = pd1.accurateDate;\r
852                                                 \r
853                                                 pd2.title = pd1.title;\r
854                                                 pd2.detail = pd1.detail;\r
855                                                 pd2.setAddedDetail(pd1.getAddedDetail());\r
856                                                 pd2.link = pd1.link;\r
857                                                 pd2.titlePop = pd1.titlePop;\r
858                                                 pd2.detailPop = pd1.detailPop;\r
859                                                 pd2.nosyobo = pd1.nosyobo;\r
860                                                 pd2.extension = pd1.extension;\r
861                                                 pd2.flag = pd1.flag;\r
862                                                 pd2.genre = pd1.genre;\r
863                                         }\r
864                                 }\r
865                         }\r
866                 }\r
867         }\r
868         \r
869         // 以前に取得したデータから当日の取得不能領域のデータを補完する\r
870         protected void CompensatesPrograms(ArrayList<ProgList> newplist) {\r
871                 //\r
872                 for ( ProgList newpl : newplist ) {\r
873                         \r
874                         if ( newpl.enabled != true ) {\r
875                                 // 無効局は処理なし\r
876                                 continue;\r
877                         }\r
878                         \r
879                         ArrayList<ProgDateList> newpcla = newpl.pdate; \r
880                         if ( newpcla.size() == 0 ) {\r
881                                 // 日付リストが存在しない場合は処理なし\r
882                                 continue;\r
883                         }\r
884                         ProgDateList newpcl = newpcla.get(0);\r
885                         \r
886                         ArrayList<ProgDetailList> newpdla = newpcl.pdetail;\r
887                         if ( newpdla.size() == 0 ) {\r
888                                 // 番組情報が存在しない場合は処理なし\r
889                                 if (debug) System.out.println(DBGID+"番組表情報がないので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center);\r
890                                 continue;\r
891                         }\r
892                         \r
893                         if ( newpdla.get(0).start.length() != 0 ) {\r
894                                 // 先頭の番組情報が「番組情報がありません」以外の場合は処理なし\r
895                                 if (debug) System.out.println(DBGID+"先頭から有効な情報なので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center+" "+newpdla.get(0).start+" "+newpdla.get(0).title);\r
896                                 continue;\r
897                         }\r
898                         \r
899                         PassedProgram oldplist = new PassedProgram();\r
900                         if ( ! oldplist.loadByCenter(newpcl.Date, newpl.Center) || oldplist.getProgCount() == 0 ) {\r
901                                 // 過去情報が取得できなければ処理なし\r
902                                 System.out.println(DBGID+"過去ログに情報はありませんでした");\r
903                                 continue;\r
904                         }\r
905                         ProgDateList oldpcl = oldplist.pcenter.get(0).pdate.get(0);\r
906                         \r
907                         // 補填候補抽出\r
908                         ArrayList<ProgDetailList> tmppdla = new ArrayList<ProgDetailList>();\r
909                         if ( newpdla.size() == 1 ) {\r
910                                 // 「番組情報がありません」しかない場合は全面複写\r
911                                 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {\r
912                                         tmppdla.add(oldpdl.clone());\r
913                                 }\r
914                         }\r
915                         else {\r
916                                 int idx = 0;\r
917                                 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {\r
918                                         if ( idx == 0 ) {\r
919                                                 // 過去ログの最初は無条件に追加してよい\r
920                                                 tmppdla.add(oldpdl.clone());\r
921                                         }\r
922                                         else if ( oldpdl.startDateTime.compareTo(newpdla.get(1).startDateTime) < 0 ) {\r
923                                                 // 2個目以降は当日の有効情報の前まで(「番組情報がありません」は無条件追加)\r
924                                                 tmppdla.add(oldpdl.clone());\r
925                                         }\r
926                                         else {\r
927                                                 // 有効情報を越えたら終了\r
928                                                 break;\r
929                                         }\r
930                                         idx++;\r
931                                 }\r
932                         }\r
933                         \r
934                         // 先頭の「番組情報はありません」と差し替えて補填\r
935                         newpdla.remove(0);\r
936                         for ( int i=0; i<tmppdla.size(); i++ ) {\r
937                                 newpdla.add(i,tmppdla.get(i));\r
938                         }\r
939                         tmppdla = null;\r
940                 }\r
941         }\r
942 \r
943         protected void addEmptyInfo(ProgDateList pcl, String sdat, String edat) {\r
944                 ProgDetailList pdl = new ProgDetailList();\r
945                 pdl.abon();\r
946                 pdl.startDateTime = sdat;\r
947                 //pdl.endDateTime = edat;\r
948                 pdl.length = (int)(CommonUtils.getDiffDateTime(sdat, edat)/60000L);\r
949                 pcl.pdetail.add(pdl);\r
950                 pcl.row += pdl.length;\r
951         }\r
952         \r
953         /*******************************************************************************\r
954          * フラグ処理関連\r
955          ******************************************************************************/\r
956         \r
957         /**\r
958          * 延長警告を設定する\r
959          */\r
960         public void setExtension(String spoexSearchStart, String spoexSearchEnd, boolean spoexLimitation, ArrayList<SearchKey> extKeys) {\r
961                 //\r
962                 for ( ProgList p : pcenter ) {\r
963                         // 局ごと\r
964                         \r
965                         // キーワード検索用\r
966                         String centerPop = TraceProgram.replacePop(p.Center);\r
967                         \r
968                         for ( ProgDateList c : p.pdate ) {\r
969                                 // 日ごと\r
970                                 boolean poisoned = false;\r
971                                 for (ProgDetailList d : c.pdetail) {\r
972                                         // 番組ごと\r
973                                         boolean soi = false;\r
974                                         for ( SearchKey k : extKeys ) {\r
975                                                 // 個別設定による延長可否\r
976                                                 boolean isMatch = SearchProgram.isMatchKeyword(k, ((k.getCaseSensitive()==false)?(centerPop):(p.Center)), d);\r
977                                                 if (isMatch) {\r
978                                                         if (k.getInfection().equals("0")) {\r
979                                                                 // 延長感染源にする\r
980                                                                 soi = true;\r
981                                                         }\r
982                                                         else {\r
983                                                                 // 延長感染源にしない(優先)\r
984                                                                 soi = false;\r
985                                                                 break;\r
986                                                         }\r
987                                                 }\r
988                                         }\r
989                                         if (soi) {\r
990                                                 poisoned = true;\r
991                                         }\r
992                                         \r
993                                         d.extension = poisoned;\r
994                                 }\r
995                         }\r
996                 }\r
997         }\r
998         \r
999         /*\r
1000          * タイトルからフラグを抽出する\r
1001          */\r
1002         protected void doSplitFlags(ProgDetailList pdl, HashMap<String, String> nf) {\r
1003                 \r
1004                 Matcher md = Pattern.compile("(#1|第1話)\\b").matcher(pdl.title);\r
1005                 if ( md.find() ) {\r
1006                         pdl.flag = ProgFlags.NEW;\r
1007                 }\r
1008                 \r
1009                 md = Pattern.compile("([  ]?[<<]新[>>]| 新$| NEW$)").matcher(pdl.title);\r
1010                 if ( md.find() ) {\r
1011                         pdl.flag = ProgFlags.NEW;\r
1012                         pdl.title = md.replaceAll("");\r
1013                 }\r
1014                 md = Pattern.compile("([  ]?[<<]終[>>]| 終$| END$)").matcher(pdl.title);\r
1015                 if ( md.find() ) {\r
1016                         pdl.flag = ProgFlags.LAST;\r
1017                         pdl.title = md.replaceAll("");  \r
1018                 }\r
1019                 md = Pattern.compile("[((]終[))]",Pattern.DOTALL).matcher(pdl.detail);\r
1020                 if ( md.find() ) {\r
1021                         pdl.flag = ProgFlags.LAST;\r
1022                 }\r
1023                 md = Pattern.compile("^無料≫").matcher(pdl.title);\r
1024                 if ( md.find() ) {\r
1025                         pdl.noscrumble = ProgScrumble.NOSCRUMBLE;\r
1026                         pdl.title = md.replaceAll("");  \r
1027                 }\r
1028                 \r
1029                 Pattern pat = Pattern.compile("初放送",Pattern.DOTALL);\r
1030                 if ( pat.matcher(pdl.detail).find() ) {\r
1031                         pdl.addOption(ProgOption.FIRST);\r
1032                 }\r
1033                 else if ( pat.matcher(pdl.title).find() ) {\r
1034                         pdl.addOption(ProgOption.FIRST);\r
1035                 }\r
1036 \r
1037                 pat = Pattern.compile("(視聴(..)?制限|[RR]([--]?[11][5588][++]?|指定))",Pattern.DOTALL);\r
1038                 if ( pat.matcher(pdl.detail).find() ) {\r
1039                         pdl.addOption(ProgOption.RATING);\r
1040                 }\r
1041                 else if ( pat.matcher(pdl.title).find() ) {\r
1042                         pdl.addOption(ProgOption.RATING);\r
1043                 }\r
1044                 \r
1045                 if ( pdl.detail.indexOf("5.1サラウンド") != -1 ) {\r
1046                         pdl.addOption(ProgOption.SURROUND);\r
1047                 }\r
1048 \r
1049                 HashMap<String, String> xf = new HashMap<String, String>();\r
1050                 \r
1051                 String flagExpr = "[\\[[((【](.{1,3})[\\]]))】]";\r
1052                 Matcher mx = Pattern.compile(flagExpr).matcher(pdl.title);\r
1053                 while (mx.find()) {\r
1054                         if (mx.group(1).equals("新") || mx.group(1).equals("新番組")) {\r
1055                                 pdl.flag = ProgFlags.NEW;\r
1056                         }\r
1057                         else if (mx.group(1).equals("終") || mx.group(1).equals("完") || mx.group(1).equals("最終回")) {\r
1058                                 pdl.flag = ProgFlags.LAST;\r
1059                         }\r
1060                         else if (mx.group(1).equals("再")) {\r
1061                                 pdl.addOption(ProgOption.REPEAT);\r
1062                         }\r
1063                         else if (mx.group(1).equals("初")) {\r
1064                                 pdl.addOption(ProgOption.FIRST);\r
1065                         }\r
1066                         else if (mx.group(1).equals("生")) {\r
1067                                 pdl.addOption(ProgOption.LIVE);\r
1068                         }\r
1069                         \r
1070                         else if (mx.group(1).equals("二/吹")) {\r
1071                                 pdl.addOption(ProgOption.BILINGUAL);\r
1072                                 pdl.addOption(ProgOption.STANDIN);\r
1073                         }\r
1074                         else if (mx.group(1).equals("字") || mx.group(1).equals("字幕") || mx.group(1).equals("字幕版")) {\r
1075                                 pdl.addOption(ProgOption.SUBTITLE);\r
1076                         }\r
1077                         else if (mx.group(1).equals("二")) {\r
1078                                 pdl.addOption(ProgOption.BILINGUAL);\r
1079                         }\r
1080                         else if (mx.group(1).equals("多")) {\r
1081                                 pdl.addOption(ProgOption.MULTIVOICE);\r
1082                         }\r
1083                         else if (mx.group(1).equals("SS") || mx.group(1).equals("5.1")) {\r
1084                                 pdl.addOption(ProgOption.SURROUND);\r
1085                         }\r
1086                         else if (mx.group(1).equals("吹") || mx.group(1).equals("吹替") || mx.group(1).equals("吹替版")) {\r
1087                                 pdl.addOption(ProgOption.STANDIN);      // (ないよ)\r
1088                         }\r
1089                         else if (mx.group(1).equals("デ")) {\r
1090                                 pdl.addOption(ProgOption.DATA);\r
1091                         }\r
1092                         else if (mx.group(1).equals("無") || mx.group(1).equals("無料")) {\r
1093                                 //pdl.addOption(ProgOption.NOSCRUMBLE);\r
1094                                 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;\r
1095                         }\r
1096                         \r
1097                         else if (mx.group(1).matches("^(S|N|B|映|双|解|手|天|英|日|録|HV)$")) {\r
1098                                 // 無視するフラグ\r
1099                                 if ( mx.group(1).equals("日") && ( ! pdl.title.matches(String.format("^(%s)*[((]日[))].*", flagExpr)) && ! pdl.title.matches(".*[\\[[]日[\\]]].*")) ) {\r
1100                                         // 削除しないフラグ(特殊)\r
1101                                         continue;\r
1102                                 }\r
1103                         }\r
1104                         else if (mx.group(1).matches("^(韓|仮|[前後][編篇半]|[月火水木金土]|[0-90-9]+)$")) {\r
1105                                 // 削除しないフラグ\r
1106                                 continue;\r
1107                         }\r
1108                         \r
1109                         else {\r
1110                                 // 未知のフラグ\r
1111                                 nf.put(mx.group(1),null);\r
1112                                 continue;\r
1113                         }\r
1114                         \r
1115                         // 削除するフラグ\r
1116                         xf.put(mx.group(1), null);\r
1117                 }\r
1118                 {\r
1119                         // 認識されたフラグだけ削除する.\r
1120                         String repExpr = "";\r
1121                         for ( String f : xf.keySet() ) {\r
1122                                 repExpr += String.format("%s|",f);\r
1123                         }\r
1124                         if ( repExpr.length() > 0 ) {\r
1125                                 repExpr = "[\\[[((【]("+repExpr.substring(0, repExpr.length()-1)+")[\\]]))】]";\r
1126                                 pdl.title = pdl.title.replaceAll(repExpr, "");\r
1127                         }\r
1128                 }\r
1129                 \r
1130                 if ( pdl.title.matches("^特[::].*") ) {\r
1131                         pdl.option.add(ProgOption.SPECIAL);\r
1132                         pdl.title = pdl.title.substring(2);\r
1133                 }\r
1134                 else if ( pdl.detail.contains("OVA") && ! pdl.detail.contains("+OVA") ) {\r
1135                         pdl.option.add(ProgOption.SPECIAL);\r
1136                 }\r
1137                 else if ( pdl.detail.contains("未放送") ) {\r
1138                         pdl.option.add(ProgOption.SPECIAL);\r
1139                 }\r
1140         }\r
1141         \r
1142         /**\r
1143          * マルチジャンル処理\r
1144          */\r
1145         protected void setMultiGenre(ProgDetailList pdl, ArrayList<String> genrelist) {\r
1146                 // コード順にならべかえる\r
1147                 Collections.sort(genrelist);\r
1148 \r
1149                 // ここに入ってこない限り genrelist == null なので対応プラグイン以外ではマルチジャンルは機能しない\r
1150                 pdl.genrelist = new ArrayList<TVProgram.ProgGenre>();\r
1151                 pdl.subgenrelist = new ArrayList<TVProgram.ProgSubgenre>(); \r
1152                                 \r
1153                 String gcode = ProgGenre.NOGENRE.toIEPG();\r
1154                 String subgcode = ProgSubgenre.NOGENRE_ETC.toIEPG();\r
1155                 \r
1156                 // マルチジャンルコードを設定する\r
1157                 for ( String gstr : genrelist ) {\r
1158                         // ジャンルコードが複数ある場合は基本的に最初のものを代表にするが、一部例外をもうける(ニュースよりドキュメンタリー優先、など)\r
1159                         // 鯛ナビの一覧で表示されるジャンルは代表のものだけである\r
1160                         String gv = gstr.substring(0,1); \r
1161                         String subgv = gstr.substring(1,2); \r
1162                         if ( gcode.equals(ProgGenre.NOGENRE.toIEPG()) ) {\r
1163                                 gcode = gv;\r
1164                                 subgcode = subgv;\r
1165                         }\r
1166                         else if ( gcode.equals(ProgGenre.NEWS.toIEPG()) && gv.equals(ProgGenre.DOCUMENTARY.toIEPG())) {\r
1167                                 gcode = gv;\r
1168                                 subgcode = subgv;\r
1169                         }\r
1170                         /*\r
1171                         else if ( gcode.equals(ProgGenre.MUSIC.toIEPG()) && md.group(1).equals(ProgGenre.VARIETY.toIEPG())) {\r
1172                                 gcode = md.group(1);\r
1173                                 subgcode = md.group(2);\r
1174                         }\r
1175                         */\r
1176                         \r
1177                         // 3.14.12βでマルチジャンル対応を追加した\r
1178                         // 一覧では代表しか見えないが、検索処理ではすべてのジャンルコードが対象になる\r
1179                         {\r
1180                                 ProgGenre ng = ProgGenre.NOGENRE;\r
1181                                 ProgSubgenre nsubg = ProgSubgenre.NOGENRE_ETC;\r
1182                                 for ( ProgGenre g : ProgGenre.values() ) {\r
1183                                         if ( g.toIEPG().equals(gv) ) {\r
1184                                                 ng = g;\r
1185                                                 for ( ProgSubgenre subg : ProgSubgenre.values() ) {\r
1186                                                         if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgv) ) {\r
1187                                                                 nsubg = subg;\r
1188                                                                 break;\r
1189                                                         }\r
1190                                                 }\r
1191                                                 break;\r
1192                                         }\r
1193                                 }\r
1194                                 if ( ng != ProgGenre.NOGENRE ) {\r
1195                                         pdl.genrelist.add(ng);\r
1196                                         pdl.subgenrelist.add(nsubg);\r
1197                                 }\r
1198                         }\r
1199                         if ( pdl.genrelist.size() == 0 ) {\r
1200                                 pdl.genrelist.add(ProgGenre.NOGENRE);\r
1201                                 pdl.subgenrelist.add(ProgSubgenre.NOGENRE_ETC);\r
1202                         }\r
1203                 }\r
1204                 \r
1205                 // 代表ジャンルコードを設定する\r
1206                 for ( ProgGenre g : ProgGenre.values() ) {\r
1207                         if ( g.toIEPG().equals(gcode) ) {\r
1208                                 pdl.genre = g;\r
1209                                 for ( ProgSubgenre subg : ProgSubgenre.values() ) {\r
1210                                         if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgcode) ) {\r
1211                                                 pdl.subgenre = subg;\r
1212                                                 break;\r
1213                                         }\r
1214                                 }\r
1215                                 break;\r
1216                         }\r
1217                 }\r
1218         }\r
1219         \r
1220         \r
1221         /*******************************************************************************\r
1222          * 通信系\r
1223          ******************************************************************************/\r
1224         \r
1225         // Web上から取得してファイルにキャッシュする\r
1226         \r
1227         // GET型\r
1228         public void webToFile(String uri, String fname, String thisEncoding) {\r
1229                 webToFile(uri, null, null, null, fname, thisEncoding);\r
1230         }\r
1231         \r
1232         // POST型\r
1233         public void webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {\r
1234                 int retry = 0;\r
1235                 while (true) {\r
1236                         if ( _webToFile(uri, pstr, cookie, referer, fname, thisEncoding) == true ) {\r
1237                                 break;\r
1238                         }\r
1239                         if ( ++retry > retrycount ) {\r
1240                                 break;\r
1241                         }\r
1242                         System.out.println("wait for retry...");\r
1243                         CommonUtils.milSleep(1000);\r
1244                 }\r
1245         }\r
1246         \r
1247         // GET/POST本体\r
1248         private boolean _webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {\r
1249                 \r
1250                 HttpURLConnection ucon = null;\r
1251                 BufferedWriter filewriter = null;\r
1252                 BufferedReader filereader = null;\r
1253                 BufferedOutputStream streamwriter = null;\r
1254                 BufferedInputStream streamreader = null;\r
1255                 try {\r
1256                         ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);\r
1257                         if (ucon == null) {\r
1258                                 return false;\r
1259                         }\r
1260                         \r
1261                         // 一時ファイルに書き出し\r
1262                         if (thisEncoding != null) {\r
1263                                 filewriter = new BufferedWriter(new FileWriter(fname+".tmp"));\r
1264                                 filereader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));\r
1265                                 String str;\r
1266                             while((str = filereader.readLine()) != null){\r
1267                                 filewriter.write(str);\r
1268                                 filewriter.write("\n");\r
1269                             }\r
1270                             filewriter.close();\r
1271                             filewriter = null;\r
1272                             filereader.close();\r
1273                             filereader = null;\r
1274                         }\r
1275                         else {\r
1276                                 streamwriter = new BufferedOutputStream(new FileOutputStream(fname+".tmp"));\r
1277                                 streamreader = new BufferedInputStream(ucon.getInputStream());\r
1278                                 byte[] buf = new byte[65536];\r
1279                                 int len;\r
1280                             while((len = streamreader.read(buf,0,buf.length)) != -1){\r
1281                                 streamwriter.write(buf,0,len);\r
1282                             }\r
1283                             streamwriter.close();\r
1284                             streamwriter = null;\r
1285                             streamreader.close();\r
1286                             streamreader = null;\r
1287                         }\r
1288                     ucon.disconnect();\r
1289                     ucon = null;\r
1290                         \r
1291                     // クローズしてからじゃないと失敗するよ \r
1292                     \r
1293                         // キャッシュファイルに変換\r
1294                     File o = new File(fname);\r
1295                     if ( o.exists() && ! o.delete() ) {\r
1296                         System.err.println("削除できないよ: "+fname);\r
1297                     }\r
1298                     File n = new File(fname+".tmp");\r
1299                     if ( ! n.renameTo(o) ) {\r
1300                         System.err.println("リネームできないよ: "+fname+".tmp to "+fname);\r
1301                         return false;\r
1302                     }\r
1303                         \r
1304                     return true;\r
1305                 }\r
1306                 catch (Exception e) {\r
1307                         // 例外\r
1308                         System.out.println("Webアクセスに失敗しました("+uri+"): "+e);\r
1309                 }\r
1310                 finally {\r
1311                         CommonUtils.closing(filewriter);\r
1312                         CommonUtils.closing(filereader);\r
1313                         CommonUtils.closing(streamwriter);\r
1314                         CommonUtils.closing(streamreader);\r
1315                         CommonUtils.closing(ucon);\r
1316                 }\r
1317                 return false;\r
1318         }\r
1319 \r
1320         // Web上から取得してバッファに格納する\r
1321         \r
1322         // GET型\r
1323         public String webToBuffer(String uri, String thisEncoding, boolean nocr) {\r
1324                 return webToBuffer(uri, null, null, null, thisEncoding, nocr);\r
1325         }\r
1326         \r
1327         // POST型\r
1328         public String webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {\r
1329                 int retry = 0;\r
1330                 while (true) {\r
1331                         String response = _webToBuffer(uri, pstr, cookie, referer, thisEncoding, nocr); \r
1332                         if ( response != null ) {\r
1333                                 return response;\r
1334                         }\r
1335                         if ( ++retry > retrycount ) {\r
1336                                 break;\r
1337                         }\r
1338                         System.out.println("wait for retry...");\r
1339                         CommonUtils.milSleep(1000);\r
1340                 }\r
1341                 return null;\r
1342         }\r
1343         \r
1344         // 本体\r
1345         private String _webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {\r
1346 \r
1347                 if ( thisEncoding == null ) {\r
1348                         return null;\r
1349                 }\r
1350                 \r
1351                 try {\r
1352                         HttpURLConnection ucon = null;\r
1353                         BufferedReader reader = null;\r
1354                         try {\r
1355                                 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);\r
1356                                 if (ucon == null) {\r
1357                                         return null;\r
1358                                 }\r
1359                                 \r
1360                                 // バッファ作成\r
1361                                 StringBuilder sb = new StringBuilder();\r
1362                                 reader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));\r
1363                                 String str;\r
1364                             while((str = reader.readLine()) != null){\r
1365                                 sb.append(str);\r
1366                                 if ( ! nocr) sb.append("\n");\r
1367                             }\r
1368                             return sb.toString();\r
1369                         }\r
1370                         catch (Exception e) {\r
1371                                 System.out.println("Webアクセスに失敗しました("+uri+"): "+e.toString());\r
1372                                 //e.printStackTrace();\r
1373                         }\r
1374                         finally {\r
1375                                 if ( reader != null ) {\r
1376                                         reader.close();\r
1377                                         reader = null;\r
1378                                 }\r
1379                                 if ( ucon != null ) {\r
1380                                         ucon.disconnect();\r
1381                                         ucon = null;\r
1382                                 }\r
1383                         }\r
1384                 }\r
1385                 catch ( Exception e ) {\r
1386                         // close()の例外は無視\r
1387                 }\r
1388                 return null;\r
1389         }\r
1390         \r
1391         /*\r
1392          * File/Bufferの共通部品\r
1393          */\r
1394         \r
1395         private HttpURLConnection _webToXXX(String uri, String pstr, String cookie, String referer, String thisEncoding) {\r
1396                 try {\r
1397                         URL url = new URL(uri);\r
1398                         HttpURLConnection ucon;\r
1399                         if ( getProxy() == null ) {\r
1400                                 ucon = (HttpURLConnection)url.openConnection();\r
1401                         }\r
1402                         else {\r
1403                                 ucon = (HttpURLConnection)url.openConnection(getProxy());\r
1404                         }\r
1405                         ucon.setConnectTimeout(conntout*1000);\r
1406                         ucon.setReadTimeout(readtout*1000);\r
1407         \r
1408                         ucon.addRequestProperty("User-Agent", getUserAgent());\r
1409                         \r
1410                         if ( referer != null ) {\r
1411                                 ucon.addRequestProperty("Referer", referer);\r
1412                         }\r
1413                         \r
1414                         if ( cookie != null ) {\r
1415                                 ucon.addRequestProperty("Cookie", cookie);\r
1416                         }\r
1417                         \r
1418                         // Cookie処理(CookieManagerはなぜうまく動かないんだ…orz)\r
1419                         if ( cookie_cache.size() > 0 ) {\r
1420                                 String cStr = "";\r
1421                                 for ( String key : cookie_cache.keySet() ) {\r
1422                                         cStr += key+"="+cookie_cache.get(key)+"; ";\r
1423                                 }\r
1424                                 ucon.addRequestProperty("Cookie", cStr);\r
1425                         }\r
1426         \r
1427                         if (pstr != null) {\r
1428                                 ucon.setRequestMethod("POST");\r
1429                                 ucon.setDoOutput(true);\r
1430                                 ucon.setDoInput(true);\r
1431                         }\r
1432                         else {\r
1433                                 ucon.setRequestMethod("GET");\r
1434                                 ucon.connect();\r
1435                         }\r
1436 \r
1437                         // POSTの場合は別途データを送る\r
1438                         if (pstr != null && thisEncoding != null) {\r
1439                                 OutputStream writer = ucon.getOutputStream();\r
1440                                 writer.write(pstr.getBytes(thisEncoding));\r
1441                                 writer.flush();\r
1442                                 writer.close();\r
1443                         }\r
1444                         \r
1445                         // Cookie処理\r
1446                         if ( uri.matches(".*dimora.jp.*") || uri.matches(".*\\.yahoo\\.co\\.jp.*") ) {\r
1447                                 List<String> hf = ucon.getHeaderFields().get("Set-Cookie");\r
1448                                 if ( hf != null ) {\r
1449                                 for ( String rcookie :  hf ) {\r
1450                                         String[] rc1 = rcookie.split(";",2);\r
1451                                         String[] rc2 = rc1[0].split("=",2);\r
1452                                         cookie_cache.put(rc2[0], rc2[1]);\r
1453                                 }\r
1454                                 }\r
1455                     }\r
1456                         \r
1457                         return ucon;\r
1458                         \r
1459                 } catch (MalformedURLException e) {\r
1460                         e.printStackTrace();\r
1461                 } catch (IOException e) {\r
1462                         e.printStackTrace();\r
1463                 }\r
1464                 \r
1465                 return null;\r
1466         }\r
1467         \r
1468         protected String getCookie(String key) {\r
1469                 return cookie_cache.get(key);\r
1470         }\r
1471         protected void addCookie(String key, String val) {\r
1472                 cookie_cache.put(key,val);\r
1473         }\r
1474         protected void delCookie(String key) {\r
1475                 cookie_cache.remove(key);\r
1476         }\r
1477         protected void clrCookie() {\r
1478                 cookie_cache.clear();\r
1479         }\r
1480         \r
1481         // Cookieの情報をキャッシュするよ\r
1482         private HashMap<String,String> cookie_cache = new HashMap<String, String>();\r
1483                 \r
1484         \r
1485         /*******************************************************************************\r
1486          * ここから下は該当機能が無効なプラグイン用のダミー\r
1487          ******************************************************************************/\r
1488         \r
1489         public String getTVProgramId() { return "DUMMY"; }\r
1490         \r
1491         public String getDefaultArea() { return "東京"; }\r
1492         \r
1493         // 番組詳細キャッシュファイル名\r
1494         protected String getDetCacheFile() { return ""; }\r
1495         \r
1496         // 番組詳細をattachする\r
1497         protected void attachDetails(ArrayList<ProgList> plist, HashMap<String,String> oldcache, HashMap<String,String> newcache) {\r
1498                 // ダミー\r
1499         }\r
1500 \r
1501         // フリーテキストによるオプション指定\r
1502         public boolean setOptString(String s) { return true; }  // ダミー\r
1503         public String getOptString() { return null; }                   // ダミー\r
1504         \r
1505 }\r