001    /*
002     * Copyright (c) 2009 The openGion Project.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013     * either express or implied. See the License for the specific language
014     * governing permissions and limitations under the License.
015     */
016    package org.opengion.fukurou.util;
017    
018    import java.awt.Rectangle;
019    import java.awt.Robot;
020    import java.awt.image.BufferedImage;
021    import java.awt.AWTException;
022    import javax.imageio.ImageIO;
023    
024    import java.awt.Toolkit;
025    import java.awt.datatransfer.Clipboard;
026    import java.awt.datatransfer.DataFlavor;
027    import java.awt.datatransfer.StringSelection;
028    import java.awt.datatransfer.FlavorListener;
029    import java.awt.datatransfer.FlavorEvent;
030    import java.awt.datatransfer.UnsupportedFlavorException;
031    import java.io.IOException;
032    import java.io.File;
033    
034    /**
035     * DisplayCapture.java は、画面イメージをキャプチャして、ファイルに書き?すため?クラスです?
036     *
037     * 基本?使?は、main メソ?から立ち上げて、クリ??ボ?ド?状態を監視します?
038     * クリ??ボ?ドに?GUI:画面ID xxxxx.jsp" 形式??が書き込まれると、flavorsChanged イベントが
039     * 発生して、画面を?ファイルに書き?す??実行されます?
040     * エンジンの機?と連動すれ?、画面ID?ァイル名をクリ??ボ?ド経由でこ?アプリケーションに
041     * 渡すことで、画面操作を行う?で、?動的に画面キャプチャを行うことが可能です?
042     *
043     * エンジンでは、jsp/indexc.jsp に、この機?が?込まれて?ため、gf\BAT\displayCapture の
044     * DisplayCapture.bat を起動後?アプリケーションを?jsp/indexc.jsp で呼び出せ?、?動的に
045     * 画面のキャプチャを開始できます?
046     * キャプチャは、?画面のみです?で、IEを最大に?て操作してください?
047     * また??Prnt Scrn?ボタンにも対応して?す?で、操作中????ア??等?非?動化の
048     * 画面キャプチャも?手動で取得できます?
049     *
050     * 起動時の引数に応じて、??制御することが可能です?
051     *
052     * 書き?すフォル??、BASE_DIR で?します?
053     *  すべてのキャプチャ画像?、?ースフォル?に??て保存します?
054     *  キャプチャ画像???は、?力されるファイル名に反映されます?ファイル名?形式も?種類あり?
055     *  キャプチャ?、画面ID??できます?
056     *  初期値は、Java起動時のフォル?なります?
057     *
058     * 書き?すファイル名?初期形式?、firstID の設定により異なります?
059     *   firstID ?gui に設定した??
060     *      ベ?スフォル?フォル?作?し?そ?中に、画面ID_JSPファイル名_連番.画像形?ファイルを作?します?
061     *      画面ID 単位に、画面のキャプチャを整??使用した??合に便利です?
062     *      ファイルのタイ?タン?作?時刻)で並び替えを行えば、キャプチャ?並び替えできます?
063     *   firstID ?seq に設定した??
064     *      ベ?スフォル?フォル?作?し?そ?中に??番_画面ID_JSPファイル?画像形?ファイルを作?します?
065     *      ファイルは、キャプチャされた?番に、画面IDも混在して作?されます?つまり?ファイル名??に
066     *      再生すれば、リンク??シス?との連携などで、画面が行き来しても?作業の?にキャプチャできて
067     *      ?事になります?
068     *
069     * こ?クラスは、これらを実現するために利用して?、static メソ?をいくつか持って?す?
070     *   BufferedImage doCapture()
071     *      画面イメージをキャプチャします?これは、?画面です?
072     *   void saveImage( File saveFile, BufferedImage img, String imgType )
073     *      ??ファイルに、画面イメージを書き?します?
074     *      imgType に、画像?種?png|gif|jpg)を指定しま?初期値:png)?
075     *   String getClipboard()
076     *      現在のクリ??ボ?ド?値を取り?します?ここでは、文字?のみ取り出すことが可能です?
077     *      こ?メソ?の特徴?ところは、PrintScreenなどの??以外?値をクリ??ボ?ドにセ?
078     *      した場合に?GUI:PRINT SCREEN.img" と???を返すところです?つまり?そ?場合??
079     *      全画面のキャプチャが行われると?事です?
080     *   void setClipboard( String txt )
081     *      クリ??ボ?ドに、文字?をセ?します?
082     *
083     * こ?クラスが実?て? FlavorListener は、クリ??ボ?ド?"値"の更新には追従して?せん?
084     * ?の Transferable オブジェクトが変更された?合に、flavorsChanged メソ?が呼び出されます?
085     * つまり??セ?された文字型??タは、取り?した後?別のTransferable オブジェクトに変更して
086     * おかな?、次の??の変更が拾えなくなります?また?こ?別のTransferableオブジェクト?
087     * 設定で、?び、イベントが発生する?で、そのままでは、無限ループになってしま?す?
088     * そこで、少し、トリ?ーなのですが、setClipboard( String ) すると、?びイベントが呼び出され
089     * な??、取得した文字?の先?が?"GUI:" で始まる?合?み、?設定するよ?して?す?
090     *
091     * Usage: java org.opengion.fukurou.util.DisplayCapture
092     *                   [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)]
093     *
094     *     args[0]  BASE_DIR    : キャプチャファイルをセーブする?ースとなるディレクトリ(初期値:起動フォル?
095     *     args[1]  firstID     : キャプチャ画像をセーブするファイル方式を?しま?初期値:seq)
096     *                            gui (画面ID_JSPファイル名_連番.画像形?
097     *                            seq (連番_画面ID_JSPファイル?画像形?
098     *     args[2]  imageFormat : 作?するイメージの形式?png|gif|jpg のどれか(初期値:png)
099     *     args[3]  startCnt    : セーブファイル名をユニ?クにするためのカウン?初期値:100)
100     *
101     * こ?実??同期化されません?
102     *
103     * @og.rev 5.1.7.0 (2010/06/01) 新規追?
104     * @og.rev 5.2.1.0 (2010/10/01) 実用性を重視した改修
105     *
106     * @version  5.0
107     * @author   Kazuhiko Hasegawa
108     * @since    JDK6.0,
109     */
110    public final class DisplayCapture implements FlavorListener {
111            private static final Clipboard CLIP_BOARD = Toolkit.getDefaultToolkit().getSystemClipboard();
112            private static final Rectangle SCRN_SIZE  = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
113    
114            private File    baseDir         = new File(".");                // セーブする?ース?レクトリ
115            private String  firstID         = "seq";                                // 保存時のファイル名?形?seq|gui)
116            private String  imgType         = "png" ;                               // 画像形?png|gif|jpg)
117            private int             cnt                     = 100;                                  // ユニ?ク番号(セーブファイル名に付?
118    
119            /**
120             * キャプチャファイルをセーブする?ースとなるディレクトリを設定しま?初期値:java実行フォル??
121             *
122             * クラスの??は、Java の実行フォル? new File(".") ) が?期?です?
123             *
124             * @param       bsDir   セーブする?ース?レクトリ
125             * @throws RuntimeException セーブフォル?作?できなかった??
126             */
127            public void setBaseDir( final String bsDir ) {
128                    if( bsDir != null && bsDir.length() > 0 ) {
129                            baseDir = new File( bsDir );
130    
131                            // ベ?スフォル??作?。?期?は起動フォル?ので、?存在する(は?
132                            if( !baseDir.exists() && !baseDir.mkdirs() ) {
133                                    String errMsg = "ERROR:セーブフォル?作?できませんでした? + baseDir.getAbsolutePath()  ;
134                                    throw new RuntimeException( errMsg );
135                            }
136                    }
137            }
138    
139            /**
140             * キャプチャ画像をセーブするファイル方式を?しま?初期値:seq)?
141             *
142             *    seq (連番_画面ID_JSPファイル?画像形?
143             *    gui (画面ID_JSPファイル名_連番.画像形?
144             *
145             * 初期値は、seq です?
146             *
147             * @param       firstID セーブするファイル方?seq|gui)
148             * @throws RuntimeException ファイル方式??が間違って?場?
149             */
150            public void setFirstID( final String firstID ) {
151                    if( firstID != null ) {
152                            if( firstID.matches( "seq|gui" ) ) {
153                                    this.firstID = firstID;
154                            }
155                            else {
156                                    String errMsg = "ERROR:firstID 属?は?seq|gui)でお願いします?firstID=[" + firstID + "]" ;
157                                    throw new RuntimeException( errMsg );
158                            }
159                    }
160            }
161    
162            /**
163             * キャプチャ画像をセーブする画像形式を?しま?初期値:png)?
164             *
165             * キャプチャされたイメージをセーブするとき?画像形式を?できます?
166             * ここでは、png , gif ,jpg を指定できます?
167             *
168             * 初期値は、png 形式です?
169             *
170             * @param       imgType セーブする画像形?png|gif|jpg)
171             */
172            public void setImageType( final String imgType ) {
173                    if( imgType != null && imgType.matches( "png|gif|jpg" ) ) {
174                            this.imgType = imgType;
175                    }
176            }
177    
178            /**
179             * キャプチャ画像をセーブするファイル名?先?に付ける?番の開始数(初期値:100)?
180             *
181             * キャプチャされたイメージをセーブするとき?画面IDとJSPファイル名だけでは、前回??
182             * 上書きしてしま?め?ファイル名?先?に連番を付与して?す?
183             * ここでは、その連番の開始番号を指定できます?
184             *
185             * 初期値は?00 です?
186             *
187             * @param       startCnt        連番の開始数(初期値:100)
188             */
189            public void setStartCnt( final String startCnt ) {
190                    if( startCnt != null && startCnt.length() > 0 ) {
191                            cnt = Integer.parseInt( startCnt );
192                    }
193            }
194    
195            /**
196             * 全画面の画像イメージ(キャプチャ画?を取得します?
197             *
198             * java.awt.Toolkit で、?画面のスクリーンサイズを取得し、java.awt.Robot の
199             * createScreenCapture( Rectangle ) メソ?で、BufferedImage を取得して?す?
200             *
201             * @return      全画面の画像イメージ
202             * @throws RuntimeException AWTException が発生した??
203             */
204            public static BufferedImage doCapture() {
205                    BufferedImage img = null;
206                    try{
207                            Robot robo = new Robot();
208                            img = robo.createScreenCapture( SCRN_SIZE );
209                    }
210                    catch( AWTException ex ) {
211    //                      ex.printStackTrace();
212                            String errMsg = "ERROR:画像イメージ(キャプチャ画?が取得できませんでした? ;
213                            setClipboard( errMsg );
214                            throw new RuntimeException( errMsg,ex );
215                    }
216                    catch( Throwable th ) {
217                            String errMsg = "ERROR:" + th.getLocalizedMessage() ;
218                            setClipboard( errMsg );
219                            throw new RuntimeException( errMsg,th );
220                    }
221    
222                    return img;
223            }
224    
225            /**
226             * キャプチャ画像をファイルにセーブします?
227             *
228             * ここでは、単純に、引数そ?ままで、ImageIO.write( BufferedImage,String,File ) して?す?
229             * saveFile の?レクトリ存在チェ???ファイル名?拡張?png,gif,jpgなど)の修正?
230             * imgType の形式チェ?などは、行って?せん?
231             * それら?処??、事前に、調整しておいてください?
232             *
233             * @param       img     セーブする画像イメージ
234             * @param       imgType セーブする画像形?png|gif|jpg)
235             * @param       saveFile        セーブする画像ファイルオブジェク?
236             * @throws RuntimeException IOException が発生した??
237             * @see  javax.imageio.ImageIO#write( java.awt.image.RenderedImage , String , java.io.File )
238             */
239            public static void saveImage( final BufferedImage img , final String imgType , final File saveFile ) {
240                    try{
241                            ImageIO.write( img,imgType,saveFile );
242                    }
243                    catch( IOException ex ) {
244    //                      ex.printStackTrace();
245                            String errMsg = "ERROR:キャプチャ画像をファイルにセーブできませんでした? + saveFile.getAbsolutePath()  ;
246                            setClipboard( errMsg );
247                            throw new RuntimeException( errMsg,ex );
248                    }
249                    catch( Throwable th ) {
250                            String errMsg = "ERROR:" + th.getLocalizedMessage() ;
251                            setClipboard( errMsg );
252                            throw new RuntimeException( errMsg,th );
253                    }
254            }
255    
256            /**
257             * シス?のクリ??ボ?ド???を取得します?
258             *
259             * Toolkit.getDefaultToolkit().getSystemClipboard() で取得された  Clipboard オブジェクトか?
260             * ????(DataFlavor.stringFlavor)を取得します?
261             * ????が取得できな??合?(UnsupportedFlavorException が発生した?? 例えば?
262             * PrntScrn ボタンが押された?合などは、文字?として?GUI:PRINT SCREEN.img" を返します?
263             * これは、文字?が返せな??合でも?クリ??ボ?ドに書き込まれたイベントで、?画面のキャプチャ?
264             * 取得するため?、特殊なコマンドに相当します?
265             *
266             * @return      クリ??ボ?ド???
267             * @throws RuntimeException IOException が発生した??
268             * @see  java.awt.datatransfer.Clipboard#getData( DataFlavor )
269             */
270            public static String getClipboard() {
271                    String strClip = null;
272    
273                    // 方法として、Transferable を取得後?getTransferData する事もできる?
274            //      Transferable data = CLIP_BOARD.getContents(null);
275    
276                    try {
277                            // クリ??ボ?ド?値を取?
278            //              strClip = (String)data.getTransferData(DataFlavor.stringFlavor);
279                            strClip = (String)CLIP_BOARD.getData( DataFlavor.stringFlavor );
280                    }
281                    catch( UnsupportedFlavorException ex ) {
282                            // PrintScreen が押された?合?
283                            strClip = "GUI:PRINT SCREEN.img" ;              // 形式をGUI:画面ID xxxxx.jsp 形式に合わす為
284                    }
285                    catch( IOException ex ) {
286    //                      ex.printStackTrace();
287                            String errMsg = "ERROR:クリ??ボ?ド?値を取得できませんでした? ;
288                            setClipboard( errMsg );
289                            throw new RuntimeException( errMsg,ex );
290                    }
291                    catch( Throwable th ) {
292                            String errMsg = "ERROR:" + th.getLocalizedMessage() ;
293                            setClipboard( errMsg );
294                            throw new RuntimeException( errMsg,th );
295                    }
296    
297                    return strClip;
298            }
299    
300            /**
301             * シス?のクリ??ボ?ドに??を書き込みます?
302             *
303             * シス?の Clipboard オブジェクトに、StringSelection ?セ?します?
304             * 通常であれば、単純に、クリ??ボ?ド経由で??タの?取りをするだけ?機構ですが?
305             * FlavorListener を実?て?関係上?flavorsChanged が発生します?
306             * こ?イベントにつ?は?flavorsChanged( FlavorEvent ) を参照ください?
307             *
308             * @param       txt     クリ??ボ?ドに書き込?字?
309             * @see  java.awt.datatransfer.StringSelection
310             * @see  java.awt.datatransfer.Clipboard#setContents( Transferable , ClipboardOwner )
311             */
312            public static void setClipboard( final String txt ) {
313                    StringSelection strSel = new StringSelection( txt );
314                    CLIP_BOARD.setContents( strSel, null );
315            }
316    
317            /**
318             * リスナ?対象の Clipboard で使用可能な DataFlavor が変更されたときに呼び出されます?
319             *
320             * これは、FlavorListener の イベント?実?す?
321             * DataFlavor が変更されたときであり、その??タの?が書き換えられた場合には、イベントが
322             * 発生しません?
323             * そ?ため、データを取り?したあとで、Transferable を?セ?する処?行って?す?
324             *
325             * クリ??ボ?ドで使用可能な??の DataFlavors の変更によるも?でな??余?な通知もあります?
326             * さらに、イベントを発生させるために、Transferable をセ?する処? #setClipboard(String) )?
327             * 実行しても?同様にイベントが発生します?
328             *
329             * ここでは、取得したクリ??ボ?ド???が?"GUI:" の場合?み処?て?す?
330             * これにより、取得後? Transferable の再セ?時???は?GUI:" を削除して?す?
331             *
332             * こ?メソ?では、画面キャプチャを取得し、クリ??ボ?ド???から、画面ID とJSPファイル名を
333             * 抜き出し?セーブする??の処?行って?す?
334             *
335             * @param       fe      イベントソース
336             * @see  java.awt.datatransfer.FlavorListener#flavorsChanged( FlavorEvent )
337             */
338            @Override
339            public void flavorsChanged( final FlavorEvent fe )  {
340                    String txt = getClipboard();
341    
342                    // クリ??ボ?ド?値をクリアしたとき?イベント?、拾わな?め?
343                    if( txt != null && txt.length() > 0 && txt.startsWith( "GUI:" ) ) {
344                            System.out.println( cnt + ":? + txt + "? );
345                            BufferedImage img = doCapture();
346    
347                            File saveFile = makeSaveFile( txt );
348    
349                            saveImage( img,imgType,saveFile );
350    
351                            // クリ??ボ?ド?Flavorを置換します?(Windwosからセ?された時にイベントを発生させるため?
352                            // 先?の GUI: を取り除く?イベント?無限ループを防ぐ意味?
353                            setClipboard( txt.substring( 4 ) );
354                    }
355            }
356    
357            /**
358             * キャプチャ画像を書き?すファイルオブジェクトを作?します?
359             *
360             * 引数は?GUI:画面ID xxxxx.jsp" 形式を想定した文字?です?
361             *
362             * ファイル名には?種類あります?
363             *   firstID ?seq に設定した??
364             *      ベ?スフォル?フォル?作?し?そ?中に??番_画面ID_JSPファイル?画像形?ファイルを作?します?
365             *      ファイルは、キャプチャされた?番に、画面IDも混在して作?されます?つまり?ファイル名??に
366             *      再生すれば、リンク??シス?との連携などで、画面が行き来しても?作業の?にキャプチャできて
367             *      ?事になります?
368             *   firstID ?gui に設定した??
369             *      ベ?スフォル?フォル?作?し?そ?中に、画面ID_JSPファイル名_連番.画像形?ファイルを作?します?
370             *      画面ID 単位に、画面のキャプチャを整??使用した??合に便利です?
371             *      ファイルのタイ?タン?作?時刻)で並び替えを行えば、キャプチャ?並び替えできます?
372             *
373             * こ?メソ?で、フォル??存在チェ?、およ?、無ければ作?(mkdirs)も行います?
374             *
375             * @param       txt     ファイル名??なる文字?("GUI:画面ID xxxxx.jsp" 形?
376             *
377             * @return      書き?すファイルオブジェク?
378             */
379            private File makeSaveFile( final String txt ) {
380                    int spc = txt.indexOf( ' ' );                                                   // "GUI:画面ID xxxxx.jsp" をスペ?スで?
381                    String gui = txt.substring( 4,spc );                                    //      画面ID の部??み?出す?
382                    String jsp = txt.substring( spc+1,txt.length()-4 );             //             xxxxx の部??み?出す?
383    
384                    String saveFile = null;
385                    if( "seq".equalsIgnoreCase( firstID ) ) {
386                            saveFile = cnt++ + "_" + gui + "_" + jsp + "." + imgType ;
387                    }
388                    else if( "gui".equalsIgnoreCase( firstID ) ) {
389                            saveFile = gui + "_" + jsp + "_" + cnt++ + "." + imgType ;
390                    }
391                    else {          // 5.5.2.6 (2012/05/25) findbugs対?
392                            saveFile = cnt++ + "_" + gui + "_" + jsp + "." + imgType ;              // seqかguiしかな?、経路として、?期?(seq)を設定しておく?
393                    }
394    
395                    File svf = new File( baseDir,saveFile );
396                    // セーブフォル??作??
397                    File parent = svf.getParentFile();
398                    if( !parent.exists() && !parent.mkdirs() ) {
399                            String errMsg = "ERROR:セーブフォル?作?できませんでした? + parent.getAbsolutePath()  ;
400                            setClipboard( errMsg );
401                            throw new RuntimeException( errMsg );
402                    }
403    
404                    // セーブファイルの存在チェ?。上書き禁止にしておきます?
405                    if( svf.exists() ) {
406                            String errMsg = "ERROR:セーブファイルがすでに作?されて?す?" + svf.getAbsolutePath()  ;
407                            setClipboard( errMsg );
408                            throw new RuntimeException( errMsg );
409                    }
410    
411                    return svf;
412            }
413    
414            /**
415             * DisplayCapture.java は、画面イメージをキャプチャする、メインメソ?です?
416             *
417             * Javaアプリケーションとして実行すると、無限??入ります?
418             * ??は、flavorsChanged イベン?によるクリ??ボ?ド?監視を行います?
419             * クリ??ボ?ドに?GUI:画面ID xxxxx.jsp" 形式??が書き込まれると、画面キャプチャを?
420             * ファイルに書き?す??実行されます?
421             * 書き?すファイル名?初期形式?、firstID の設定により異なります?
422             *   firstID ?seq に設定した??
423             *      ベ?スフォル?フォル?作?し?そ?中に??番_画面ID_JSPファイル?画像形?ファイルを作?します?
424             *      ファイルは、キャプチャされた?番に、画面IDも混在して作?されます?つまり?ファイル名??に
425             *      再生すれば、リンク??シス?との連携などで、画面が行き来しても?作業の?にキャプチャできて
426             *      ?事になります?
427             *   firstID ?gui に設定した??
428             *      ベ?スフォル?フォル?作?し?そ?中に、画面ID_JSPファイル名_連番.画像形?ファイルを作?します?
429             *      画面ID 単位に、画面のキャプチャを整??使用した??合に便利です?
430             *      ファイルのタイ?タン?作?時刻)で並び替えを行えば、キャプチャ?並び替えできます?
431             *
432             * Usage: java org.opengion.fukurou.util.DisplayCapture
433             *                   [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)]
434             *
435             *     args[0]  BASE_DIR    : キャプチャファイルをセーブする?ースとなるディレクトリ(初期値:起動フォル?
436             *     args[1]  firstID     : キャプチャ画像をセーブするファイル方式を?しま?初期値:seq)
437             *                            seq (連番_画面ID_JSPファイル?画像形?
438             *                            gui (画面ID_JSPファイル名_連番.画像形?
439             *     args[2]  imageFormat : 作?するイメージの形式?png|gif|jpg のどれか(初期値:png)
440             *     args[3]  startCnt    : セーブファイル名をユニ?クにするためのカウン?初期値:100)
441             *
442             * @param       args    引数 [BASE_DIR] [firstID(seq|gui)] [imageFormat(png|gif|jpg)] [startCnt(100)]
443             */
444            public static void main( final String[] args ) {
445                    System.out.println( "DisplayCapture を起動しました? );
446    
447                    DisplayCapture dispCap = new DisplayCapture();
448    
449                    if( args.length > 0 ) { dispCap.setBaseDir(   args[0] ); }
450                    if( args.length > 1 ) { dispCap.setFirstID(   args[1] ); }
451                    if( args.length > 2 ) { dispCap.setImageType( args[2] ); }
452                    if( args.length > 3 ) { dispCap.setStartCnt(  args[3] ); }
453    
454                    // クリ??ボ?ド?値をクリア(FlavorEvent を起こさせるため)
455                    DisplayCapture.setClipboard( null );
456    
457                    // FlavorListener を登録します?(自??身のオブジェク?
458                    CLIP_BOARD.addFlavorListener( dispCap );
459    
460                    // FlavorEvent で処?せるので、ずっとスレ?をSleepさせておけばよい?
461                    while( true ) {
462                            try {
463                                    Thread.sleep( 100000 );
464                            }
465                            catch( InterruptedException ex ) {}
466                    }
467            }
468    }