OSDN Git Service

4129062f4fd35e4f44f98ec8fbc01571461cc127
[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                                 // いきなり「で始まる場合や、タイトル中に『TOKYO「萌」探偵』のように「ほげほげ」を含む場合\r
533                                 Matcher mc = Pattern.compile("^([^  ]*「.+?」[^  ]+)(.*)$").matcher(title);\r
534                                 if ( mc.find() ) {\r
535                                         Matcher md = Pattern.compile("^[  ]*(.*?)[  ]*?"+spep_expr).matcher(mc.group(2));\r
536                                         if ( md.find() ) {\r
537                                                 if ( md.group(1).length() == 0 ) {\r
538                                                         return(new String[] { mc.group(1),md.group(2)+" " });\r
539                                                 }\r
540                                                 else {\r
541                                                         return(new String[] { mc.group(1)+" "+md.group(1),md.group(2)+" " });\r
542                                                 }\r
543                                         }\r
544                                         else {\r
545                                                 return(new String[] { title,"" });\r
546                                         }\r
547                                 }\r
548                                 // まあこれが普通\r
549                                 mc = Pattern.compile("^(.+?)[  ]*?"+spep_expr).matcher(title);\r
550                                 if ( mc.find() ) {\r
551                                         return(new String[] { mc.group(1),mc.group(2)+" " });\r
552                                 }\r
553                         }\r
554                 }\r
555                 return(new String[] { title,"" });\r
556         }\r
557         \r
558         // サブタイトル判定条件\r
559         private static final String spep_expr = "(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?|「).*)$";\r
560         \r
561         // サブタイトル判定条件(ジャンル=ドラマ専用)\r
562         private static final String spep_expr_dorama = "^(.+?)[  ]*?(([<<]?[((##♯第全「][第]?[12345678901234567890一二三四五六七八九十百千]+?[回話章]?).*)$";\r
563         \r
564         \r
565         /**\r
566          * NGワード\r
567          */\r
568         public void abon(ArrayList<String> ngword) {\r
569                 //\r
570                 if (ngword.size() == 0) {\r
571                         return;\r
572                 }\r
573                 for ( ProgList p : pcenter ) {\r
574                         for ( ProgDateList c : p.pdate ) {\r
575                                 for (ProgDetailList d : c.pdetail) {\r
576                                         for (String ngs : ngword) {\r
577                                                 if (d.title.indexOf(ngs) != -1 || d.detail.indexOf(ngs) != -1) {\r
578                                                         d.abon();\r
579                                                         break;\r
580                                                 }\r
581                                         }\r
582                                 }\r
583                         }\r
584                 }\r
585         }\r
586         \r
587         \r
588         \r
589         /*******************************************************************************\r
590          * 番組情報キャッシュ\r
591          ******************************************************************************/\r
592         \r
593         // キャッシュファイルが有効期限内か確認する\r
594         public boolean isCacheOld(String fname) {\r
595                 // キャッシュ期限が無期限の場合はDL禁止\r
596                 if (cacheExpired == 0) {\r
597                         return(false);\r
598                 }\r
599                 // キャッシュ期限があってファイルがない場合はDLしなさい\r
600                 //if (cacheExpired > 0 && fname == null) {\r
601                 if (fname == null) {    // あれだ、期限に関係なくファイル名の指定がなきゃDLするしかないよ\r
602                         return(true);\r
603                 }\r
604                 // 実際のファイルのタイムスタンプを確認する\r
605                 try {\r
606                         File f = new File(fname);\r
607                         if (f.exists() == true) {\r
608                                 long t = System.currentTimeMillis();\r
609                                 if (f.lastModified() < (t - cacheExpired*3600000L) ) {\r
610                                         // ファイルを更新\r
611                                         f.delete();\r
612                                         f.createNewFile();\r
613                                         return(true);\r
614                                 }\r
615                         }\r
616                         else {\r
617                                 // ファイルを作成        \r
618                                 f.createNewFile();\r
619                                 return(true);\r
620                         }\r
621                 }\r
622                 catch (Exception e) {\r
623                         // 例外\r
624                         System.out.println("Exception: isCacheOld() "+e);\r
625                 }\r
626                 return(false);\r
627         }\r
628         \r
629         \r
630         /*******************************************************************************\r
631          * 番組詳細キャッシュ(現在は使われていない)\r
632          ******************************************************************************/\r
633         \r
634         /**\r
635          * 番組詳細をオンライン取得するかどうか\r
636          */\r
637         @Deprecated\r
638         protected void chkForceLoadDetInfo(boolean force) {\r
639                 File f = new File(getDetCacheFile());\r
640                 if (force == true ||\r
641                                 (f.exists() == true && isCacheOld(getDetCacheFile()) == true) ||\r
642                                 (f.exists() == false && isCacheOld(null) == true)) {\r
643                         // Webからロードします\r
644                         forceLoadDetInfo = true;\r
645                 }\r
646                 else if (f.exists()) {\r
647                         // キャッシュファイルが有効なようなので利用します\r
648                         forceLoadDetInfo = false;\r
649                 }\r
650                 else {\r
651                         // 無くても平気…\r
652                         forceLoadDetInfo = false;\r
653                 }\r
654         }\r
655         \r
656         // 番組詳細キャッシュのLOAD\r
657         @Deprecated\r
658         protected HashMap<String,String> loadDetCache(){\r
659                 \r
660                 // 設定ファイルが存在していればファイルから\r
661                 File f = new File(getDetCacheFile());\r
662                 if (f.exists() == true) {\r
663                         @SuppressWarnings("unchecked")\r
664                         HashMap<String,String> cache = (HashMap<String, String>) CommonUtils.readXML(getDetCacheFile());\r
665                         if ( cache != null ) {\r
666                                 return cache;\r
667                         }\r
668                         \r
669                         System.out.println(ERRID+"【致命的エラー】番組詳細キャッシュが読み込めません: "+getDetCacheFile());\r
670                 }\r
671                 \r
672                 // キャッシュなし&エラーは空配列を返す\r
673                 return new HashMap<String, String>();\r
674         }\r
675         \r
676         // 番組詳細キャッシュのSAVE\r
677         @Deprecated\r
678         protected void saveDetCache(HashMap<String,String> cache) {\r
679                 if ( ! CommonUtils.writeXML(getDetCacheFile(), cache) ) {\r
680                         System.err.println(ERRID+"【致命的エラー】番組詳細キャッシュが書き込めません: "+getDetCacheFile());\r
681                 }\r
682         }\r
683         \r
684         \r
685         /*******************************************************************************\r
686          * 番組情報のリフレッシュ関連\r
687          ******************************************************************************/\r
688         \r
689         /**\r
690          * 日付変更線(29:00)をまたいだら過去のデータはカットする\r
691          * <B> PassedProgramでは使わない\r
692          */\r
693         public void refresh() {\r
694                 \r
695                 String critDate = CommonUtils.getDate529(0, true);\r
696                 for ( ProgList p : pcenter ) {\r
697                         int i = 0;\r
698                         for ( ProgDateList c : p.pdate ) {\r
699                                 if ( c.Date.compareTo(critDate) >= 0 ) {\r
700                                         break;\r
701                                 }\r
702                                 i++;\r
703                         }\r
704                         for ( int j=0; j<i; j++) {\r
705                                 p.pdate.remove(0);\r
706                         }\r
707                 }\r
708         }\r
709         \r
710         /**\r
711          * 24時間分番組枠が埋まっているかどうか確認する\r
712          */\r
713         public String chkComplete() {\r
714                 for ( ProgList p : pcenter ) {\r
715                         if (p.enabled) {\r
716                                 for ( ProgDateList c : p.pdate ) {\r
717                                         if (c.pdetail.size()<=1) {\r
718                                                 String msg = "番組枠が存在しません.("+p.Center+","+c.Date+")";\r
719                                                 System.out.println(msg);\r
720                                                 return(msg);\r
721                                         }\r
722                                         if (c.row < 24*60) {\r
723                                                 String msg = "番組枠が24時間分取得できませんでした.("+p.Center+","+c.Date+","+c.row+")";\r
724                                                 System.out.println(msg);\r
725                                                 return(msg);\r
726                                         }\r
727                                 }\r
728                                 if (p.pdate.size() < 7) {\r
729                                         String msg = "番組表が一週間分取得できませんでした.("+p.Center+")";\r
730                                         System.out.println(msg);\r
731                                         return(msg);\r
732                                 }\r
733                         }\r
734                 }\r
735                 return null;\r
736         }\r
737         \r
738         /**\r
739          * 開始終了日時の整理\r
740          */\r
741         public void setAccurateDate(ArrayList<ProgDateList> pcenter) {\r
742         \r
743                 // 先頭のエントリの開始時刻が 5:00 以前の場合\r
744                 for ( ProgDateList pcl : pcenter ) {\r
745                         if (pcl.pdetail.size() <= 0) {\r
746                                 continue;\r
747                         }\r
748                         \r
749                         ProgDetailList pd = pcl.pdetail.get(0);\r
750                         Matcher ma = Pattern.compile("(\\d\\d):(\\d\\d)").matcher(pd.start);\r
751                         if (ma.find()) {\r
752                                 int prelength = 0;\r
753                                 int ahh = Integer.valueOf(ma.group(1));\r
754                                 int amm = Integer.valueOf(ma.group(2));\r
755                                 \r
756                                 GregorianCalendar c = new GregorianCalendar();\r
757                                 c.setTime(new Date());\r
758                                 c.set(Calendar.HOUR_OF_DAY, ahh);\r
759                                 c.set(Calendar.MINUTE, amm);\r
760                                 \r
761                                 if ( pd.start.compareTo("05:00") < 0 ) {\r
762                                         // 5:00以前\r
763                                         prelength = (5*60+0)-(ahh*60+amm);\r
764                                         c.add(Calendar.MINUTE,prelength+pd.length);\r
765                                         pd.end = CommonUtils.getTime(c);\r
766                                 }\r
767                                 else if ( pd.start.compareTo("18:00") >= 0 && pd.start.compareTo("24:00") < 0 ) {\r
768                                         // 前日の24:00以前\r
769                                         prelength = (24*60+0)-(ahh*60+amm)+(5*60);\r
770                                         c.add(Calendar.MINUTE,prelength+pd.length);\r
771                                         pd.end = CommonUtils.getTime(c);\r
772                                 }\r
773                         }\r
774                 }\r
775 \r
776                 for ( ProgDateList pcl : pcenter ) {\r
777         \r
778                         GregorianCalendar c = CommonUtils.getCalendar(pcl.Date);\r
779         \r
780                         boolean extend = false;\r
781                         boolean overtwodays = false;\r
782                         for ( int i=0; i<pcl.pdetail.size(); i++ ) {\r
783                                 \r
784                                 ProgDetailList pdl = pcl.pdetail.get(i);\r
785                                 \r
786                                 // 番組情報がありません\r
787                                 if (pdl.start.compareTo("") == 0) {\r
788                                         continue;\r
789                                 }\r
790                                 \r
791                                 // 表示上の開始日時\r
792                                 if ( i == 0 ) {\r
793                                         if ( pdl.start.compareTo("18:00") >= 0 && pdl.start.compareTo("24:00") < 0 ) {\r
794                                                 // いったい何時間放送するんだよ(--#\r
795                                                 c.add(Calendar.DAY_OF_MONTH, -1);\r
796                                                 overtwodays = true;\r
797                                         }\r
798                                 }\r
799                                 else {\r
800                                         if ( (pdl.start.compareTo("00:00") >= 0 && pdl.start.compareTo("05:00") < 0 && pdl.end.compareTo("05:00") < 0) && extend == false) {\r
801                                                 c.add(Calendar.DAY_OF_MONTH, 1);\r
802                                                 extend = true;\r
803                                         }\r
804                                 }\r
805                                 pdl.startDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.start);\r
806         \r
807                                 // 正確な開始日\r
808                                 pdl.accurateDate = CommonUtils.getDate(c);\r
809                                 \r
810                                 // 表示上の終了日時\r
811                                 if ( overtwodays ) {\r
812                                         c.add(Calendar.DAY_OF_MONTH, 1);\r
813                                         overtwodays = false;\r
814                                 }\r
815                                 else {\r
816                                         if ( pdl.start.compareTo(pdl.end) > 0  && extend == false) {\r
817                                                 c.add(Calendar.DAY_OF_MONTH, 1);\r
818                                                 extend = true;\r
819                                         }\r
820                                 }\r
821                                 \r
822                                 pdl.endDateTime = String.format("%s %s", CommonUtils.getDate(c,false), pdl.end);\r
823                         }\r
824                 }\r
825                 \r
826                 // 29時をまたいで同タイトルが続いている場合は同一番組とみなす\r
827                 if ( continueTomorrow ) {\r
828                         for (int w=0; w<pcenter.size()-1; w++) {\r
829                                 if (pcenter.get(w).pdetail.size() > 0 && pcenter.get(w+1).pdetail.size() > 0) {\r
830                                         ProgDetailList pd1 = pcenter.get(w).pdetail.get(pcenter.get(w).pdetail.size()-1);\r
831                                         ProgDetailList pd2 = pcenter.get(w+1).pdetail.get(0);\r
832                                         if (pd1.title.equals(pd2.title)) {\r
833                                                 pd1.end = pd2.end;\r
834                                                 pd1.endDateTime = pd2.endDateTime;\r
835                                                 \r
836                                                 pd2.start = pd1.start;\r
837                                                 pd2.startDateTime = pd1.startDateTime;\r
838                                                 pd2.accurateDate = pd1.accurateDate;\r
839                                         }\r
840                                         else if (pd2.title.equals("承前")) {\r
841                                                 pd1.end = pd2.end;\r
842                                                 pd1.endDateTime = pd2.endDateTime;\r
843                                                 \r
844                                                 pd2.start = pd1.start;\r
845                                                 pd2.startDateTime = pd1.startDateTime;\r
846                                                 pd2.accurateDate = pd1.accurateDate;\r
847                                                 \r
848                                                 pd2.title = pd1.title;\r
849                                                 pd2.detail = pd1.detail;\r
850                                                 pd2.setAddedDetail(pd1.getAddedDetail());\r
851                                                 pd2.link = pd1.link;\r
852                                                 pd2.titlePop = pd1.titlePop;\r
853                                                 pd2.detailPop = pd1.detailPop;\r
854                                                 pd2.nosyobo = pd1.nosyobo;\r
855                                                 pd2.extension = pd1.extension;\r
856                                                 pd2.flag = pd1.flag;\r
857                                                 pd2.genre = pd1.genre;\r
858                                         }\r
859                                 }\r
860                         }\r
861                 }\r
862         }\r
863         \r
864         // 以前に取得したデータから当日の取得不能領域のデータを補完する\r
865         protected void CompensatesPrograms(ArrayList<ProgList> newplist) {\r
866                 //\r
867                 for ( ProgList newpl : newplist ) {\r
868                         \r
869                         if ( newpl.enabled != true ) {\r
870                                 // 無効局は処理なし\r
871                                 continue;\r
872                         }\r
873                         \r
874                         ArrayList<ProgDateList> newpcla = newpl.pdate; \r
875                         if ( newpcla.size() == 0 ) {\r
876                                 // 日付リストが存在しない場合は処理なし\r
877                                 continue;\r
878                         }\r
879                         ProgDateList newpcl = newpcla.get(0);\r
880                         \r
881                         ArrayList<ProgDetailList> newpdla = newpcl.pdetail;\r
882                         if ( newpdla.size() == 0 ) {\r
883                                 // 番組情報が存在しない場合は処理なし\r
884                                 if (debug) System.out.println(DBGID+"番組表情報がないので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center);\r
885                                 continue;\r
886                         }\r
887                         \r
888                         if ( newpdla.get(0).start.length() != 0 ) {\r
889                                 // 先頭の番組情報が「番組情報がありません」以外の場合は処理なし\r
890                                 if (debug) System.out.println(DBGID+"先頭から有効な情報なので過去ログは参照しない: "+newpcl.Date+" "+newpl.Center+" "+newpdla.get(0).start+" "+newpdla.get(0).title);\r
891                                 continue;\r
892                         }\r
893                         \r
894                         PassedProgram oldplist = new PassedProgram();\r
895                         if ( ! oldplist.loadByCenter(newpcl.Date, newpl.Center) || oldplist.getProgCount() == 0 ) {\r
896                                 // 過去情報が取得できなければ処理なし\r
897                                 System.out.println(DBGID+"過去ログに情報はありませんでした");\r
898                                 continue;\r
899                         }\r
900                         ProgDateList oldpcl = oldplist.pcenter.get(0).pdate.get(0);\r
901                         \r
902                         // 補填候補抽出\r
903                         ArrayList<ProgDetailList> tmppdla = new ArrayList<ProgDetailList>();\r
904                         if ( newpdla.size() == 1 ) {\r
905                                 // 「番組情報がありません」しかない場合は全面複写\r
906                                 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {\r
907                                         tmppdla.add(oldpdl.clone());\r
908                                 }\r
909                         }\r
910                         else {\r
911                                 int idx = 0;\r
912                                 for ( ProgDetailList oldpdl : oldpcl.pdetail ) {\r
913                                         if ( idx == 0 ) {\r
914                                                 // 過去ログの最初は無条件に追加してよい\r
915                                                 tmppdla.add(oldpdl.clone());\r
916                                         }\r
917                                         else if ( oldpdl.startDateTime.compareTo(newpdla.get(1).startDateTime) < 0 ) {\r
918                                                 // 2個目以降は当日の有効情報の前まで(「番組情報がありません」は無条件追加)\r
919                                                 tmppdla.add(oldpdl.clone());\r
920                                         }\r
921                                         else {\r
922                                                 // 有効情報を越えたら終了\r
923                                                 break;\r
924                                         }\r
925                                         idx++;\r
926                                 }\r
927                         }\r
928                         \r
929                         // 先頭の「番組情報はありません」と差し替えて補填\r
930                         newpdla.remove(0);\r
931                         for ( int i=0; i<tmppdla.size(); i++ ) {\r
932                                 newpdla.add(i,tmppdla.get(i));\r
933                         }\r
934                         tmppdla = null;\r
935                 }\r
936         }\r
937 \r
938         protected void addEnmptyInfo(ProgDateList pcl, String sdat, String edat) {\r
939                 ProgDetailList pdl = new ProgDetailList();\r
940                 pdl.title = pdl.splitted_title = "番組情報がありません";\r
941                 pdl.detail = "";\r
942                 pdl.length = (int)(CommonUtils.getDiffDateTime(sdat, edat)/60000L);\r
943                 pdl.genre = ProgGenre.NOGENRE;\r
944                 pdl.start = "";\r
945                 pdl.startDateTime = sdat;\r
946                 pdl.endDateTime = edat;\r
947                 pcl.pdetail.add(pdl);\r
948                 pcl.row += pdl.length;\r
949         }\r
950         \r
951         /*******************************************************************************\r
952          * フラグ処理関連\r
953          ******************************************************************************/\r
954         \r
955         /**\r
956          * 延長警告を設定する\r
957          */\r
958         public void setExtension(String spoexSearchStart, String spoexSearchEnd, boolean spoexLimitation, ArrayList<SearchKey> extKeys) {\r
959                 //\r
960                 for ( ProgList p : pcenter ) {\r
961                         // 局ごと\r
962                         \r
963                         // キーワード検索用\r
964                         String centerPop = TraceProgram.replacePop(p.Center);\r
965                         \r
966                         for ( ProgDateList c : p.pdate ) {\r
967                                 // 日ごと\r
968                                 boolean poisoned = false;\r
969                                 for (ProgDetailList d : c.pdetail) {\r
970                                         // 番組ごと\r
971                                         boolean soi = false;\r
972                                         for ( SearchKey k : extKeys ) {\r
973                                                 // 個別設定による延長可否\r
974                                                 boolean isMatch = SearchProgram.isMatchKeyword(k, ((k.getCaseSensitive()==false)?(centerPop):(p.Center)), d);\r
975                                                 if (isMatch) {\r
976                                                         if (k.getInfection().equals("0")) {\r
977                                                                 // 延長感染源にする\r
978                                                                 soi = true;\r
979                                                         }\r
980                                                         else {\r
981                                                                 // 延長感染源にしない(優先)\r
982                                                                 soi = false;\r
983                                                                 break;\r
984                                                         }\r
985                                                 }\r
986                                         }\r
987                                         if (soi) {\r
988                                                 poisoned = true;\r
989                                         }\r
990                                         \r
991                                         d.extension = poisoned;\r
992                                 }\r
993                         }\r
994                 }\r
995         }\r
996         \r
997         /*\r
998          * タイトルからフラグを抽出する\r
999          */\r
1000         protected void doSplitFlags(ProgDetailList pdl, HashMap<String, String> nf) {\r
1001                 \r
1002                 Matcher md = Pattern.compile("(#1|第1話)\\b").matcher(pdl.title);\r
1003                 if ( md.find() ) {\r
1004                         pdl.flag = ProgFlags.NEW;\r
1005                 }\r
1006                 \r
1007                 md = Pattern.compile("([  ]?[<<]新[>>]| 新$| NEW$)").matcher(pdl.title);\r
1008                 if ( md.find() ) {\r
1009                         pdl.flag = ProgFlags.NEW;\r
1010                         pdl.title = md.replaceAll("");\r
1011                 }\r
1012                 md = Pattern.compile("([  ]?[<<]終[>>]| 終$| END$)").matcher(pdl.title);\r
1013                 if ( md.find() ) {\r
1014                         pdl.flag = ProgFlags.LAST;\r
1015                         pdl.title = md.replaceAll("");  \r
1016                 }\r
1017                 md = Pattern.compile("[((]終[))]",Pattern.DOTALL).matcher(pdl.detail);\r
1018                 if ( md.find() ) {\r
1019                         pdl.flag = ProgFlags.LAST;\r
1020                 }\r
1021                 md = Pattern.compile("^無料≫").matcher(pdl.title);\r
1022                 if ( md.find() ) {\r
1023                         pdl.noscrumble = ProgScrumble.NOSCRUMBLE;\r
1024                         pdl.title = md.replaceAll("");  \r
1025                 }\r
1026                 \r
1027                 Pattern pat = Pattern.compile("初放送",Pattern.DOTALL);\r
1028                 if ( pat.matcher(pdl.detail).find() ) {\r
1029                         pdl.addOption(ProgOption.FIRST);\r
1030                 }\r
1031                 else if ( pat.matcher(pdl.title).find() ) {\r
1032                         pdl.addOption(ProgOption.FIRST);\r
1033                 }\r
1034 \r
1035                 pat = Pattern.compile("(視聴(..)?制限|[RR]([--]?[11][5588][++]?|指定))",Pattern.DOTALL);\r
1036                 if ( pat.matcher(pdl.detail).find() ) {\r
1037                         pdl.addOption(ProgOption.RATING);\r
1038                 }\r
1039                 else if ( pat.matcher(pdl.title).find() ) {\r
1040                         pdl.addOption(ProgOption.RATING);\r
1041                 }\r
1042                 \r
1043                 if ( pdl.detail.indexOf("5.1サラウンド") != -1 ) {\r
1044                         pdl.addOption(ProgOption.SURROUND);\r
1045                 }\r
1046 \r
1047                 HashMap<String, String> xf = new HashMap<String, String>();\r
1048                 \r
1049                 String flagExpr = "[\\[[((【](.{1,3})[\\]]))】]";\r
1050                 Matcher mx = Pattern.compile(flagExpr).matcher(pdl.title);\r
1051                 while (mx.find()) {\r
1052                         if (mx.group(1).equals("新") || mx.group(1).equals("新番組")) {\r
1053                                 pdl.flag = ProgFlags.NEW;\r
1054                         }\r
1055                         else if (mx.group(1).equals("終") || mx.group(1).equals("完") || mx.group(1).equals("最終回")) {\r
1056                                 pdl.flag = ProgFlags.LAST;\r
1057                         }\r
1058                         else if (mx.group(1).equals("再")) {\r
1059                                 pdl.addOption(ProgOption.REPEAT);\r
1060                         }\r
1061                         else if (mx.group(1).equals("初")) {\r
1062                                 pdl.addOption(ProgOption.FIRST);\r
1063                         }\r
1064                         else if (mx.group(1).equals("生")) {\r
1065                                 pdl.addOption(ProgOption.LIVE);\r
1066                         }\r
1067                         \r
1068                         else if (mx.group(1).equals("二/吹")) {\r
1069                                 pdl.addOption(ProgOption.BILINGUAL);\r
1070                                 pdl.addOption(ProgOption.STANDIN);\r
1071                         }\r
1072                         else if (mx.group(1).equals("字") || mx.group(1).equals("字幕") || mx.group(1).equals("字幕版")) {\r
1073                                 pdl.addOption(ProgOption.SUBTITLE);\r
1074                         }\r
1075                         else if (mx.group(1).equals("二")) {\r
1076                                 pdl.addOption(ProgOption.BILINGUAL);\r
1077                         }\r
1078                         else if (mx.group(1).equals("多")) {\r
1079                                 pdl.addOption(ProgOption.MULTIVOICE);\r
1080                         }\r
1081                         else if (mx.group(1).equals("SS") || mx.group(1).equals("5.1")) {\r
1082                                 pdl.addOption(ProgOption.SURROUND);\r
1083                         }\r
1084                         else if (mx.group(1).equals("吹") || mx.group(1).equals("吹替") || mx.group(1).equals("吹替版")) {\r
1085                                 pdl.addOption(ProgOption.STANDIN);      // (ないよ)\r
1086                         }\r
1087                         else if (mx.group(1).equals("デ")) {\r
1088                                 pdl.addOption(ProgOption.DATA);\r
1089                         }\r
1090                         else if (mx.group(1).equals("無") || mx.group(1).equals("無料")) {\r
1091                                 //pdl.addOption(ProgOption.NOSCRUMBLE);\r
1092                                 pdl.noscrumble = ProgScrumble.NOSCRUMBLE;\r
1093                         }\r
1094                         \r
1095                         else if (mx.group(1).matches("^(S|N|B|映|双|解|手|天|英|日|録|HV)$")) {\r
1096                                 // 無視するフラグ\r
1097                                 if ( mx.group(1).equals("日") && ( ! pdl.title.matches(String.format("^(%s)*[((]日[))].*", flagExpr)) && ! pdl.title.matches(".*[\\[[]日[\\]]].*")) ) {\r
1098                                         // 削除しないフラグ(特殊)\r
1099                                         continue;\r
1100                                 }\r
1101                         }\r
1102                         else if (mx.group(1).matches("^(韓|仮|[前後][編篇半]|[月火水木金土]|[0-90-9]+)$")) {\r
1103                                 // 削除しないフラグ\r
1104                                 continue;\r
1105                         }\r
1106                         \r
1107                         else {\r
1108                                 // 未知のフラグ\r
1109                                 nf.put(mx.group(1),null);\r
1110                                 continue;\r
1111                         }\r
1112                         \r
1113                         // 削除するフラグ\r
1114                         xf.put(mx.group(1), null);\r
1115                 }\r
1116                 {\r
1117                         // 認識されたフラグだけ削除する.\r
1118                         String repExpr = "";\r
1119                         for ( String f : xf.keySet() ) {\r
1120                                 repExpr += String.format("%s|",f);\r
1121                         }\r
1122                         if ( repExpr.length() > 0 ) {\r
1123                                 repExpr = "[\\[[((【]("+repExpr.substring(0, repExpr.length()-1)+")[\\]]))】]";\r
1124                                 pdl.title = pdl.title.replaceAll(repExpr, "");\r
1125                         }\r
1126                 }\r
1127                 \r
1128                 if ( pdl.title.matches("^特[::].*") ) {\r
1129                         pdl.option.add(ProgOption.SPECIAL);\r
1130                         pdl.title = pdl.title.substring(2);\r
1131                 }\r
1132                 else if ( pdl.detail.contains("OVA") && ! pdl.detail.contains("+OVA") ) {\r
1133                         pdl.option.add(ProgOption.SPECIAL);\r
1134                 }\r
1135                 else if ( pdl.detail.contains("未放送") ) {\r
1136                         pdl.option.add(ProgOption.SPECIAL);\r
1137                 }\r
1138         }\r
1139         \r
1140         /**\r
1141          * マルチジャンル処理\r
1142          */\r
1143         protected void setMultiGenre(ProgDetailList pdl, ArrayList<String> genrelist) {\r
1144                 // コード順にならべかえる\r
1145                 Collections.sort(genrelist);\r
1146 \r
1147                 // ここに入ってこない限り genrelist == null なので対応プラグイン以外ではマルチジャンルは機能しない\r
1148                 pdl.genrelist = new ArrayList<TVProgram.ProgGenre>();\r
1149                 pdl.subgenrelist = new ArrayList<TVProgram.ProgSubgenre>(); \r
1150                                 \r
1151                 String gcode = ProgGenre.NOGENRE.toIEPG();\r
1152                 String subgcode = ProgSubgenre.NOGENRE_ETC.toIEPG();\r
1153                 \r
1154                 // マルチジャンルコードを設定する\r
1155                 for ( String gstr : genrelist ) {\r
1156                         // ジャンルコードが複数ある場合は基本的に最初のものを代表にするが、一部例外をもうける(ニュースよりドキュメンタリー優先、など)\r
1157                         // 鯛ナビの一覧で表示されるジャンルは代表のものだけである\r
1158                         String gv = gstr.substring(0,1); \r
1159                         String subgv = gstr.substring(1,2); \r
1160                         if ( gcode.equals(ProgGenre.NOGENRE.toIEPG()) ) {\r
1161                                 gcode = gv;\r
1162                                 subgcode = subgv;\r
1163                         }\r
1164                         else if ( gcode.equals(ProgGenre.NEWS.toIEPG()) && gv.equals(ProgGenre.DOCUMENTARY.toIEPG())) {\r
1165                                 gcode = gv;\r
1166                                 subgcode = subgv;\r
1167                         }\r
1168                         /*\r
1169                         else if ( gcode.equals(ProgGenre.MUSIC.toIEPG()) && md.group(1).equals(ProgGenre.VARIETY.toIEPG())) {\r
1170                                 gcode = md.group(1);\r
1171                                 subgcode = md.group(2);\r
1172                         }\r
1173                         */\r
1174                         \r
1175                         // 3.14.12βでマルチジャンル対応を追加した\r
1176                         // 一覧では代表しか見えないが、検索処理ではすべてのジャンルコードが対象になる\r
1177                         {\r
1178                                 ProgGenre ng = ProgGenre.NOGENRE;\r
1179                                 ProgSubgenre nsubg = ProgSubgenre.NOGENRE_ETC;\r
1180                                 for ( ProgGenre g : ProgGenre.values() ) {\r
1181                                         if ( g.toIEPG().equals(gv) ) {\r
1182                                                 ng = g;\r
1183                                                 for ( ProgSubgenre subg : ProgSubgenre.values() ) {\r
1184                                                         if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgv) ) {\r
1185                                                                 nsubg = subg;\r
1186                                                                 break;\r
1187                                                         }\r
1188                                                 }\r
1189                                                 break;\r
1190                                         }\r
1191                                 }\r
1192                                 if ( ng != ProgGenre.NOGENRE ) {\r
1193                                         pdl.genrelist.add(ng);\r
1194                                         pdl.subgenrelist.add(nsubg);\r
1195                                 }\r
1196                         }\r
1197                         if ( pdl.genrelist.size() == 0 ) {\r
1198                                 pdl.genrelist.add(ProgGenre.NOGENRE);\r
1199                                 pdl.subgenrelist.add(ProgSubgenre.NOGENRE_ETC);\r
1200                         }\r
1201                 }\r
1202                 \r
1203                 // 代表ジャンルコードを設定する\r
1204                 for ( ProgGenre g : ProgGenre.values() ) {\r
1205                         if ( g.toIEPG().equals(gcode) ) {\r
1206                                 pdl.genre = g;\r
1207                                 for ( ProgSubgenre subg : ProgSubgenre.values() ) {\r
1208                                         if ( subg.getGenre().equals(g) && subg.toIEPG().equals(subgcode) ) {\r
1209                                                 pdl.subgenre = subg;\r
1210                                                 break;\r
1211                                         }\r
1212                                 }\r
1213                                 break;\r
1214                         }\r
1215                 }\r
1216         }\r
1217         \r
1218         \r
1219         /*******************************************************************************\r
1220          * 通信系\r
1221          ******************************************************************************/\r
1222         \r
1223         // Web上から取得してファイルにキャッシュする\r
1224         \r
1225         // GET型\r
1226         public void webToFile(String uri, String fname, String thisEncoding) {\r
1227                 webToFile(uri, null, null, null, fname, thisEncoding);\r
1228         }\r
1229         \r
1230         // POST型\r
1231         public void webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {\r
1232                 int retry = 0;\r
1233                 while (true) {\r
1234                         if ( _webToFile(uri, pstr, cookie, referer, fname, thisEncoding) == true ) {\r
1235                                 break;\r
1236                         }\r
1237                         if ( ++retry > retrycount ) {\r
1238                                 break;\r
1239                         }\r
1240                         System.out.println("wait for retry...");\r
1241                         CommonUtils.milSleep(1000);\r
1242                 }\r
1243         }\r
1244         \r
1245         // GET/POST本体\r
1246         private boolean _webToFile(String uri, String pstr, String cookie, String referer, String fname, String thisEncoding) {\r
1247                 \r
1248                 HttpURLConnection ucon = null;\r
1249                 BufferedWriter filewriter = null;\r
1250                 BufferedReader filereader = null;\r
1251                 BufferedOutputStream streamwriter = null;\r
1252                 BufferedInputStream streamreader = null;\r
1253                 try {\r
1254                         ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);\r
1255                         if (ucon == null) {\r
1256                                 return false;\r
1257                         }\r
1258                         \r
1259                         // 一時ファイルに書き出し\r
1260                         if (thisEncoding != null) {\r
1261                                 filewriter = new BufferedWriter(new FileWriter(fname+".tmp"));\r
1262                                 filereader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));\r
1263                                 String str;\r
1264                             while((str = filereader.readLine()) != null){\r
1265                                 filewriter.write(str);\r
1266                                 filewriter.write("\n");\r
1267                             }\r
1268                             filewriter.close();\r
1269                             filewriter = null;\r
1270                             filereader.close();\r
1271                             filereader = null;\r
1272                         }\r
1273                         else {\r
1274                                 streamwriter = new BufferedOutputStream(new FileOutputStream(fname+".tmp"));\r
1275                                 streamreader = new BufferedInputStream(ucon.getInputStream());\r
1276                                 byte[] buf = new byte[65536];\r
1277                                 int len;\r
1278                             while((len = streamreader.read(buf,0,buf.length)) != -1){\r
1279                                 streamwriter.write(buf,0,len);\r
1280                             }\r
1281                             streamwriter.close();\r
1282                             streamwriter = null;\r
1283                             streamreader.close();\r
1284                             streamreader = null;\r
1285                         }\r
1286                     ucon.disconnect();\r
1287                     ucon = null;\r
1288                         \r
1289                     // クローズしてからじゃないと失敗するよ \r
1290                     \r
1291                         // キャッシュファイルに変換\r
1292                     File o = new File(fname);\r
1293                     if ( o.exists() && ! o.delete() ) {\r
1294                         System.err.println("削除できないよ: "+fname);\r
1295                     }\r
1296                     File n = new File(fname+".tmp");\r
1297                     if ( ! n.renameTo(o) ) {\r
1298                         System.err.println("リネームできないよ: "+fname+".tmp to "+fname);\r
1299                         return false;\r
1300                     }\r
1301                         \r
1302                     return true;\r
1303                 }\r
1304                 catch (Exception e) {\r
1305                         // 例外\r
1306                         System.out.println("Webアクセスに失敗しました("+uri+"): "+e);\r
1307                 }\r
1308                 finally {\r
1309                         CommonUtils.closing(filewriter);\r
1310                         CommonUtils.closing(filereader);\r
1311                         CommonUtils.closing(streamwriter);\r
1312                         CommonUtils.closing(streamreader);\r
1313                         CommonUtils.closing(ucon);\r
1314                 }\r
1315                 return false;\r
1316         }\r
1317 \r
1318         // Web上から取得してバッファに格納する\r
1319         \r
1320         // GET型\r
1321         public String webToBuffer(String uri, String thisEncoding, boolean nocr) {\r
1322                 return webToBuffer(uri, null, null, null, thisEncoding, nocr);\r
1323         }\r
1324         \r
1325         // POST型\r
1326         public String webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {\r
1327                 int retry = 0;\r
1328                 while (true) {\r
1329                         String response = _webToBuffer(uri, pstr, cookie, referer, thisEncoding, nocr); \r
1330                         if ( response != null ) {\r
1331                                 return response;\r
1332                         }\r
1333                         if ( ++retry > retrycount ) {\r
1334                                 break;\r
1335                         }\r
1336                         System.out.println("wait for retry...");\r
1337                         CommonUtils.milSleep(1000);\r
1338                 }\r
1339                 return null;\r
1340         }\r
1341         \r
1342         // 本体\r
1343         private String _webToBuffer(String uri, String pstr, String cookie, String referer, String thisEncoding, boolean nocr) {\r
1344 \r
1345                 if ( thisEncoding == null ) {\r
1346                         return null;\r
1347                 }\r
1348                 \r
1349                 try {\r
1350                         HttpURLConnection ucon = null;\r
1351                         BufferedReader reader = null;\r
1352                         try {\r
1353                                 ucon = _webToXXX(uri,pstr,cookie,referer,thisEncoding);\r
1354                                 if (ucon == null) {\r
1355                                         return null;\r
1356                                 }\r
1357                                 \r
1358                                 // バッファ作成\r
1359                                 StringBuilder sb = new StringBuilder();\r
1360                                 reader = new BufferedReader(new InputStreamReader(ucon.getInputStream(), thisEncoding));\r
1361                                 String str;\r
1362                             while((str = reader.readLine()) != null){\r
1363                                 sb.append(str);\r
1364                                 if ( ! nocr) sb.append("\n");\r
1365                             }\r
1366                             return sb.toString();\r
1367                         }\r
1368                         catch (Exception e) {\r
1369                                 System.out.println("Webアクセスに失敗しました("+uri+"): "+e.toString());\r
1370                                 //e.printStackTrace();\r
1371                         }\r
1372                         finally {\r
1373                                 if ( reader != null ) {\r
1374                                         reader.close();\r
1375                                         reader = null;\r
1376                                 }\r
1377                                 if ( ucon != null ) {\r
1378                                         ucon.disconnect();\r
1379                                         ucon = null;\r
1380                                 }\r
1381                         }\r
1382                 }\r
1383                 catch ( Exception e ) {\r
1384                         // close()の例外は無視\r
1385                 }\r
1386                 return null;\r
1387         }\r
1388         \r
1389         /*\r
1390          * File/Bufferの共通部品\r
1391          */\r
1392         \r
1393         private HttpURLConnection _webToXXX(String uri, String pstr, String cookie, String referer, String thisEncoding) {\r
1394                 try {\r
1395                         URL url = new URL(uri);\r
1396                         HttpURLConnection ucon;\r
1397                         if ( getProxy() == null ) {\r
1398                                 ucon = (HttpURLConnection)url.openConnection();\r
1399                         }\r
1400                         else {\r
1401                                 ucon = (HttpURLConnection)url.openConnection(getProxy());\r
1402                         }\r
1403                         ucon.setConnectTimeout(conntout*1000);\r
1404                         ucon.setReadTimeout(readtout*1000);\r
1405         \r
1406                         ucon.addRequestProperty("User-Agent", getUserAgent());\r
1407                         \r
1408                         if ( referer != null ) {\r
1409                                 ucon.addRequestProperty("Referer", referer);\r
1410                         }\r
1411                         \r
1412                         if ( cookie != null ) {\r
1413                                 ucon.addRequestProperty("Cookie", cookie);\r
1414                         }\r
1415                         \r
1416                         // Cookie処理(CookieManagerはなぜうまく動かないんだ…orz)\r
1417                         if ( cookie_cache.size() > 0 ) {\r
1418                                 String cStr = "";\r
1419                                 for ( String key : cookie_cache.keySet() ) {\r
1420                                         cStr += key+"="+cookie_cache.get(key)+"; ";\r
1421                                 }\r
1422                                 ucon.addRequestProperty("Cookie", cStr);\r
1423                         }\r
1424         \r
1425                         if (pstr != null) {\r
1426                                 ucon.setRequestMethod("POST");\r
1427                                 ucon.setDoOutput(true);\r
1428                                 ucon.setDoInput(true);\r
1429                         }\r
1430                         else {\r
1431                                 ucon.setRequestMethod("GET");\r
1432                                 ucon.connect();\r
1433                         }\r
1434 \r
1435                         // POSTの場合は別途データを送る\r
1436                         if (pstr != null && thisEncoding != null) {\r
1437                                 OutputStream writer = ucon.getOutputStream();\r
1438                                 writer.write(pstr.getBytes(thisEncoding));\r
1439                                 writer.flush();\r
1440                                 writer.close();\r
1441                         }\r
1442                         \r
1443                         // Cookie処理\r
1444                         if ( uri.matches(".*dimora.jp.*") || uri.matches(".*\\.yahoo\\.co\\.jp.*") ) {\r
1445                                 List<String> hf = ucon.getHeaderFields().get("Set-Cookie");\r
1446                                 if ( hf != null ) {\r
1447                                 for ( String rcookie :  hf ) {\r
1448                                         String[] rc1 = rcookie.split(";",2);\r
1449                                         String[] rc2 = rc1[0].split("=",2);\r
1450                                         cookie_cache.put(rc2[0], rc2[1]);\r
1451                                 }\r
1452                                 }\r
1453                     }\r
1454                         \r
1455                         return ucon;\r
1456                         \r
1457                 } catch (MalformedURLException e) {\r
1458                         e.printStackTrace();\r
1459                 } catch (IOException e) {\r
1460                         e.printStackTrace();\r
1461                 }\r
1462                 \r
1463                 return null;\r
1464         }\r
1465         \r
1466         protected String getCookie(String key) {\r
1467                 return cookie_cache.get(key);\r
1468         }\r
1469         protected void addCookie(String key, String val) {\r
1470                 cookie_cache.put(key,val);\r
1471         }\r
1472         protected void delCookie(String key) {\r
1473                 cookie_cache.remove(key);\r
1474         }\r
1475         protected void clrCookie() {\r
1476                 cookie_cache.clear();\r
1477         }\r
1478         \r
1479         // Cookieの情報をキャッシュするよ\r
1480         private HashMap<String,String> cookie_cache = new HashMap<String, String>();\r
1481                 \r
1482         \r
1483         /*******************************************************************************\r
1484          * ここから下は該当機能が無効なプラグイン用のダミー\r
1485          ******************************************************************************/\r
1486         \r
1487         public String getTVProgramId() { return "DUMMY"; }\r
1488         \r
1489         public String getDefaultArea() { return "東京"; }\r
1490         \r
1491         // 番組詳細キャッシュファイル名\r
1492         protected String getDetCacheFile() { return ""; }\r
1493         \r
1494         // 番組詳細をattachする\r
1495         protected void attachDetails(ArrayList<ProgList> plist, HashMap<String,String> oldcache, HashMap<String,String> newcache) {\r
1496                 // ダミー\r
1497         }\r
1498 \r
1499         // フリーテキストによるオプション指定\r
1500         public boolean setOptString(String s) { return true; }  // ダミー\r
1501         public String getOptString() { return null; }                   // ダミー\r
1502         \r
1503 }\r