    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 */
    016package org.opengion.hayabusa.report2;
    017
    018import java.io.File;
    019import java.util.Map;
    020import java.util.HashMap;
    021import java.util.Locale;
    022
    023import org.opengion.fukurou.util.FileUtil;
    024import org.opengion.fukurou.util.StringUtil;
    025import org.opengion.hayabusa.common.HybsSystem;
    026import org.opengion.hayabusa.common.HybsSystemException;
    027import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.1.0.0 (2014/12/26)
    028
    029import com.sun.star.beans.PropertyValue;
    030import com.sun.star.frame.XComponentLoader;
    031import com.sun.star.frame.XController;
    032import com.sun.star.frame.XDispatchHelper;
    033import com.sun.star.frame.XDispatchProvider;
    034import com.sun.star.frame.XModel;
    035import com.sun.star.frame.XStorable;
    036import com.sun.star.io.IOException;
    037import com.sun.star.lang.EventObject;
    038import com.sun.star.lang.IllegalArgumentException;
    039import com.sun.star.lang.XComponent;
    040import com.sun.star.uno.UnoRuntime;
    041import com.sun.star.util.CloseVetoException;
    042import com.sun.star.util.XCloseable;
    043import com.sun.star.view.PrintJobEvent;
    044import com.sun.star.view.PrintableState;
    045import com.sun.star.view.XPrintJobBroadcaster;
    046import com.sun.star.view.XPrintJobListener;
    047import com.sun.star.view.XPrintable;
    048
    049/**
    050 * OpenOfficeを利用して様々な形式のファイルを読み込み、出力・印刷を行うための変換クラスです。
    051 *
    052 * 変換を行うことのできる入出力のフォーマット以下の通りです。
    053 *
    054 * [対応フォーマット]
    055 *  入力[Calc(ODS)   ,Excel(XLS)     ] ⇒ 出力[Calc(ODS)   ,Excel(XLS)     ,PDF]
    056 *  入力[Writer(ODT) ,Word(DOC)      ] ⇒ 出力[Writer(ODT) ,Word(DOC)      ,PDF]
    057 *  入力[Impress(ODP),PowerPoint(PPT)] ⇒ 出力[Impress(ODP),PowerPoint(PPT),PDF]
    058 *  入力[ * 上記の全て               ] ⇒ 印刷
    059 *
    060 * 変換を行うには、以下の2通りの方法があります。
    061 * (1)簡易的な変換メソッドを利用する場合
    062 *   {@link #convert(String, String)}を利用して、変換を行います。
    063 *   この場合、出力形式は、出力ファイルの拡張子に従って自動的に決定されます。
    064 *   このため、印刷処理などを行う場合は、(2)の方法で出力して下さい。
    065 * (2)段階的に書くメソッドを呼び出して変換する場合
    066 *   オブジェクトを生成した後、{@link #open()}、#(各種変換メソッド)、{@link #clone()}を
    067 *   順番に呼び出して変換を行います。
    068 *   この場合、出力形式は、それに対応するメソッドを呼び出ることで決定されます。
    069 *
    070 *   また、変換を行うための、各種メソッドは、例外としてThrowableを投げるように定義されています。
    071 *   このクラスを利用する場合は、このThrowableをcatchし、catch句で、必ず{@link #close( boolean )}に、
    072 *   "true"(エラー発生時のクローズ処理)を指定して、終了処理を行って下さい。
    073 *   (これを行わない場合、OpenOfficeの不要なプロセスが残ってしまう可能性があります)
    074 *
    075 * また、出力ファイルが既に存在する場合、出力ファイルは一旦削除された後、処理されます。
    076 * なお、入力ファイルと出力ファイルが同じ場合、何も処理されません。(例外も発行されません)
    077 *
    078 * 入力ファイルを、CSV形式で複数指定した場合、複数の入力ファイルをマージして出力します。
    079 * ※1 現状は、ファイルのマージは、入力ファイルがExcelまたはCalcの場合のみ対応しています。
    080 *
    081 * @og.group 帳票システム
    082 *
    083 * @version  4.0
    084 * @author   Hiroki.Nakamura
    085 * @since    JDK1.6
    086 */
    087public class DocConverter_OOO {
    088
    089        private final boolean                   isOnline;                       // オンライン処理かどうか(オンライン処理の場合、プロセスはファクトリクラス経由で生成されます)
    090        private final String[]                  mergeFile;
    091
    092        private String                                  inputName;
    093        private String                                  origName;
    094
    095        private XComponent                              xComp;                          // 5.1.8.0 (2010/07/01) メソッドと重なる変数名の変更
    096        private SOfficeProcess                  soffice;
    097
    098        // 入力、出力の拡張子とこれに対応するフィルター名
    099        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    100        private static final Map<String,String> FILTER_MAP      = new HashMap<>();              // 6.4.1.1 (2016/01/16) filterMap → FILTER_MAP refactoring
    101        static {
    102                FILTER_MAP.put( "ods_ods", "calc8" );
    103                FILTER_MAP.put( "xls_ods", "calc8" );
    104                FILTER_MAP.put( "ods_xls", "MS Excel 97" );
    105                FILTER_MAP.put( "xls_xls", "MS Excel 97" );
    106                FILTER_MAP.put( "ods_pdf", "calc_pdf_Export" );
    107                FILTER_MAP.put( "xls_pdf", "calc_pdf_Export" );
    108                FILTER_MAP.put( "odt_odt", "writer8" );
    109                FILTER_MAP.put( "doc_odt", "writer8" );
    110                FILTER_MAP.put( "odt_doc", "MS Word 97" );
    111                FILTER_MAP.put( "doc_doc", "MS Word 97" );
    112                FILTER_MAP.put( "odt_pdf", "writer_pdf_Export" );
    113                FILTER_MAP.put( "doc_pdf", "writer_pdf_Export" );
    114                FILTER_MAP.put( "odp_odp", "impress8" );
    115                FILTER_MAP.put( "ppt_odp", "impress8" );
    116                FILTER_MAP.put( "odp_ppt", "MS PowerPoint 97" );
    117                FILTER_MAP.put( "ppt_ppt", "MS PowerPoint 97" );
    118                FILTER_MAP.put( "odp_pdf", "impress_pdf_Export" );
    119                FILTER_MAP.put( "ppt_pdf", "impress_pdf_Export" );
    120        };
    121
    122        /**
    123         * コンストラクタです。
    124         *
    125         * #DocConverter(input, true)と同じです。
    126         *
    127         * @param input ファイル一覧(CSV形式)
    128         * @see #DocConverter_OOO(String[])
    129         */
    130        public DocConverter_OOO( final String input ) {
    131                this( StringUtil.csv2Array( input ), true );
    132        }
    133
    134        /**
    135         * コンストラクタです。
    136         *
    137         * #DocConverter(input, true)と同じです。
    138         *
    139         * @param input ファイル一覧(配列)
    140         * @see #DocConverter_OOO(String[], boolean)
    141         */
    142        public DocConverter_OOO( final String input[] ) {
    143                this( input, true );
    144        }
    145
    146        /**
    147         * コンストラクタです。
    148         *
    149         * isOnline(isOl)がtrueに指定された場合、soffice.binのプロセスをファクトリークラス経由で生成し、
    150         * キャッシュします。
    151         * 但し、システムリソースが読み込まれないような、バッチファイルから起動した場合は、この方法は
    152         * 利用できないため、isOnlineをfalseに指定する必要があります。
    153         *
    154         * @param input ファイル一覧(配列)
    155         * @param isOl オンライン(Web環境での使用)かどうか
    156         */
    157        public DocConverter_OOO( final String input[], final boolean isOl ) {
    158                if( input == null || input.length == 0 || input[0].isEmpty() ) {
    159                        throw new HybsSystemException( "入力ファイルが指定されていません。" );
    160                }
    161                final File inFile = new File( input[0] );
    162                if( !inFile.exists() ) {
    163                        throw new HybsSystemException( "入力ファイルが存在しません。[file=" + input[0] + "]" );
    164                }
    165                isOnline = isOl;
    166                inputName = input[0];
    167                origName = input[0];
    168
    169                if( input.length == 1 ) {
    170                        mergeFile = null;
    171                }
    172                else {
    173                        if( !"xls".equals( getSuffix( input[0] ) ) && !"ods".equals( getSuffix( input[0] ) ) ) {
    174                                throw new HybsSystemException( "ファイルのマージを行う場合、入力ファイルは、ExcelまたはCacl形式である必要があります。" );
    175                        }
    176
    177                        mergeFile = new String[input.length-1];
    178                        for( int i=0; i<mergeFile.length; i++ ) {
    179                                if( input[i+1].isEmpty() || !( new File( input[i+1] ) ).exists() ) {
    180                                        throw new HybsSystemException( "マージファイルが指定されていないか、または存在しません。[file=" + input[i+1] + "]" );
    181                                }
    182                                if( inputName.equals( input[i] + 1 ) ) {
    183                                        throw new HybsSystemException( "マージファイルに入力ファイルと同じファイルが指定されてます。[file=" + input[i+1] + "]" );
    184                                }
    185                                if( !"xls".equals( getSuffix( input[i+1] ) ) && !"ods".equals( getSuffix( input[i+1] ) ) ) {
    186                                        throw new HybsSystemException( "ファイルのマージを行う場合、マージファイルは、ExcelまたはCacl形式である必要があります。" );
    187                                }
    188                                mergeFile[i] = input[i+1];
    189                        }
    190                }
    191        }
    192
    193        /**
    194         * SOficeのコンポーネントを起動します。
    195         *
    196         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    197         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    198         *
    199         * @og.rev 5.1.7.0 (2010/06/01) マージ処理対応
    200         *
    201         * @throws Throwable 何らかのエラーが発生した場合。
    202         * @see #close()
    203         * @see #close(boolean)
    204         */
    205        public void open() throws Throwable {
    206                // プロセスの取得
    207                if( soffice == null ) {
    208                        if( isOnline ) {
    209                                soffice = ProcessFactory.newInstance();
    210                        }
    211                        else {
    212                                soffice = new SOfficeProcess( "docconverter.class" );
    213                                soffice.bootstrap();
    214                        }
    215
    216                        // マージする場合は、マージ対象のファイルをテンポラリにコピーする(readOnly回避のため)
    217                        // テンプレート(無題)として上げると、シートコピー先として特定できなくなるため
    218                        if( mergeFile != null ) {
    219                                final File origFile = new File( origName );
    220                                inputName = soffice.getTempPath() + System.currentTimeMillis() + "_" + origFile.getName();
    221                                FileUtil.copy( origFile, new File( inputName ) );
    222                        }
    223                }
    224
    225                // 5.1.7.0 (2010/06/01) マージ処理対応
    226                xComp = getComponent( inputName, ( mergeFile == null ? true : false ), false );
    227
    228                if( mergeFile != null ) {
    229                        for( int i=0; i<mergeFile.length; i++ ) {
    230                                merge( mergeFile[i] );
    231                        }
    232                }
    233        }
    234
    235        /**
    236         * ドキュメントコンポーネントを取得します。
    237         *
    238         * @param       input                   ファイル名
    239         * @param       isHidden                隠し属性[true/false]
    240         * @param       isAsTemplate    OpenOffice上のTemplate属性[true/false]
    241         *
    242         * @return      ドキュメントコンポーネント
    243         */
    244        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    245        private XComponent getComponent( final String input, final boolean isHidden, final boolean isAsTemplate ) {
    246                PropertyValue[] calcProps = new PropertyValue[2];
    247                calcProps[0] = new PropertyValue();
    248                calcProps[0].Name = "Hidden";
    249                calcProps[0].Value = isHidden;
    250                calcProps[1] = new PropertyValue();
    251                calcProps[1].Name = "AsTemplate";
    252                calcProps[1].Value = isAsTemplate;
    253
    254                final String url = "file:///" + input.replace( '\\', '/' );
    255
    256                XComponent rtnDoc;
    257                final XComponentLoader cloader = (XComponentLoader) UnoRuntime.queryInterface( XComponentLoader.class, soffice.getDesktop() );
    258                try {
    259                        rtnDoc = cloader.loadComponentFromURL( url, "_blank", 0, calcProps );
    260                }
    261                catch( final IOException ex ) {
    262                        throw new HybsSystemException( "OpenOfficeの立ち上げ時にエラーが発生しました(入出力エラー)。", ex );
    263                }
    264                catch( final IllegalArgumentException ex ) {
    265                        throw new HybsSystemException( "OpenOfficeの立ち上げ時にエラーが発生しました(パラメーター不正)。", ex );
    266                }
    267                return rtnDoc;
    268        }
    269
    270        /**
    271         * ドキュメント(xls,ods)のマージを行います。
    272         *
    273         * @param mergeInputName マージ対象のファイル名
    274         */
    275        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    276        private void merge( final String mergeInputName ) {
    277                // マージする副ファイルは、テンプレート(無題)として上げる(readOnly回避のため)
    278                final XComponent subDoc = getComponent( mergeInputName, false, true );
    279
    280                final XDispatchProvider dispatchProvider
    281                        = (XDispatchProvider)UnoRuntime.queryInterface( XDispatchProvider.class
    282                                ,((XController)UnoRuntime.queryInterface( XController.class
    283                                        ,((XModel)UnoRuntime.queryInterface( XModel.class
    284                                                ,subDoc
    285                                        )).getCurrentController()
    286                                )).getFrame()
    287                        );
    288
    289                final XDispatchHelper xDispatchHelper = soffice.getDispatcher();
    290                xDispatchHelper.executeDispatch(dispatchProvider, ".uno:TableSelectAll", "", 0, new PropertyValue[0]);
    291
    292                String title = new File( inputName ).getName() ;
    293                title = title.substring( 0, title.indexOf( '.' ) );
    294
    295                PropertyValue[] moveProps = new PropertyValue[3];
    296                moveProps[0] = new PropertyValue();
    297                moveProps[0].Name = "DocName";
    298                moveProps[0].Value = title;
    299                moveProps[1] = new PropertyValue();
    300                moveProps[1].Name = "Index";
    301                moveProps[1].Value = 32767;
    302                moveProps[2] = new PropertyValue();
    303                moveProps[2].Name = "Copy";
    304                moveProps[2].Value = true;
    305                xDispatchHelper.executeDispatch(dispatchProvider, ".uno:Move", "", 0, moveProps);
    306
    307                closeComponent( subDoc );
    308        }
    309
    310        /**
    311         * Calcコンポーネントをクローズします。
    312         *
    313         * このクローズ処理は、処理が正常終了した場合に呼び出しする必要があります。
    314         * 例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    315         *
    316         * このメソッドは#close(false)と同じです。
    317         *
    318         * @throws Throwable 何らかのエラーが発生した場合。
    319         * @see #close(boolean)
    320         */
    321        public void close() throws Throwable  {
    322                close( false );
    323        }
    324
    325        /**
    326         * Calcコンポーネントをクローズします。
    327         *
    328         * 引数のisErrがtrueの場合、この変換オブジェクトで生成されたプロセスは強制的に破棄されます。
    329         * falseの場合は、プロセスは、ファクトリクラスを経由して、キャッシュに戻されます。
    330         * (バッチ処理の場合は、いずれの場合も、プロセスは強制的に破棄されます)
    331         *
    332         * 起動から変換、クローズまでの書く処理で例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    333         *
    334         * #close(false)は#close()と同じであるため、通常利用することはありません。
    335         *
    336         * @og.rev 4.2.4.1 (2008/07/07 ) 終了処理を60回で終わるように修正
    337         * @og.rev 4.3.0.0 (2008/07/15 ) ↑は6秒しか待っていなかったので、60秒待つように修正
    338         *
    339         * @param       isErr   trueの場合、この変換オブジェクトで生成されたプロセスは強制的に破棄されます。
    340         */
    341        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    342        public void close( final boolean isErr ) {
    343                if( xComp != null ) {
    344                        closeComponent( xComp );
    345                }
    346
    347                if( soffice != null ) {
    348                        if( isOnline ) {
    349                                if( isErr ) {
    350                                        ProcessFactory.remove( soffice );
    351                                }
    352                                else {
    353                                        ProcessFactory.release( soffice );
    354                                }
    355                        }
    356                        else {
    357                                soffice.close();
    358                        }
    359                }
    360
    361                // マージした場合は、テンポラリにコピーしたファイルを削除
    362                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
    363                if( mergeFile != null && ! ( new File( inputName ) ).delete() ) {
    364                        System.err.println( "テンポラリにコピーしたファイルを削除できませんでした。[" + inputName + "]" );
    365                }
    366        }
    367
    368        /**
    369         * ドキュメントコンポーネントをクローズします。
    370         *
    371         * @param comp ドキュメントコンポーネント
    372         */
    373        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    374        private void closeComponent( final XComponent comp ) {
    375                XCloseable closeable = null;
    376                for( int i=0;; ++i ) {
    377                        try {
    378                                closeable = (XCloseable) UnoRuntime.queryInterface( XCloseable.class, comp );
    379                                closeable.close( true );
    380                                break;
    381                        }
    382                        catch( final CloseVetoException ex ) {
    383                                // 4.2.4.1 (2008/07/07 )
    384                                // 4.3.4.4 (2009/01/01)
    385                                if( i == 600 ) { throw new HybsSystemException( "sofficeプロセスに接続できません。", ex ); }
    386                                try {
    387                                        Thread.sleep( 100 );
    388                                }
    389                                catch( final InterruptedException ex2 ) {
    390                        //              throw new HybsSystemException( ex2 );
    391                                }
    392                        }
    393                }
    394        }
    395
    396        /**
    397         * 印刷を行います。
    398         *
    399         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    400         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    401         *
    402         * @og.rev 4.3.0.0 (2008/07/16) スプールが終わるまでwaitし、さらにプリンタ発行の状況を監視し、正常終了かどうかを判断
    403         * @og.rev 4.3.7.3 (2009/06/22) 存在しないプリンターを指定した場合のエラーハンドリングを追加
    404         * @og.rev 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
    405         *
    406         * @param       printer プリンター名
    407         * @throws Throwable 何らかのエラーが発生した場合。
    408         */
    409        public void print( final String printer ) throws Throwable {
    410                if( printer == null || printer.isEmpty() ) {
    411                        throw new HybsSystemException( "プリンターが指定されていません。" );
    412                }
    413
    414        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(1)" ); }
    415                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    416                final XPrintable xprintable = (XPrintable) UnoRuntime.queryInterface( XPrintable.class, xComp );
    417                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    418                final XPrintJobBroadcaster selection = (XPrintJobBroadcaster) UnoRuntime.queryInterface(XPrintJobBroadcaster.class, xprintable);
    419                final MyPrintJobListener listener = new MyPrintJobListener ();
    420                selection.addPrintJobListener( listener );
    421
    422                PropertyValue[] tmpProps = new PropertyValue[1];
    423                tmpProps[0] = new PropertyValue();
    424                tmpProps[0].Name = "Name";
    425                // 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
    426                // OSがLinuxの場合は、プリンタ名称の前後に"<",">"を付加
    427                tmpProps[0].Value = "LINUX".indexOf( HybsSystem.sys( "OS_INFO" ).toUpperCase( Locale.JAPAN ) ) >= 0 ? "<" + printer + ">" : printer;
    428
    429                // 4.3.4.4 (2009/01/01)
    430                try {
    431                        xprintable.setPrinter( tmpProps );
    432                }
    433                catch( final IllegalArgumentException ex ) {
    434                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
    435                }
    436
    437                // 4.3.7.3 (2009/06/22) 存在しないプリンタを指定した場合は、PropertyValueに
    438                // デフォルトプリンターが入るため、引数の値と合致しているかで正しく設定されたかを確認
    439                String curPrinter = null;
    440                final PropertyValue[] chkProps = xprintable.getPrinter();
    441                for( int i=0; i<chkProps.length; i++ ) {
    442                        if( "Name".equals( chkProps[i].Name) ) {
    443                                curPrinter = (String)chkProps[i].Value;
    444                                break;
    445                        }
    446                }
    447                if( !(printer.equalsIgnoreCase( curPrinter ) ) ) {
    448                        final String errMsg = "プリンター[" + printer + "]を発行先に指定できませんでした。" + CR
    449                                                        + "存在しないプリンタ名が指定されている可能性があります。";
    450                        throw new HybsSystemException( errMsg );
    451                }
    452
    453                // 4.3.0.0 (2008/07/16)
    454                PropertyValue[] printProps = new PropertyValue[1];
    455                printProps[0] = new PropertyValue();
    456                printProps[0].Name = "Wait";
    457                printProps[0].Value = true;
    458
    459                // 4.3.4.4 (2009/01/01)
    460                try {
    461                        xprintable.print( printProps );
    462                }
    463                catch( final IllegalArgumentException ex ) {
    464                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
    465                }
    466
    467                // 4.3.0.0 (2008/07/16)
    468                if( listener.getStatus() == null
    469                                || listener.getStatus() != PrintableState.JOB_COMPLETED && listener.getStatus() != PrintableState.JOB_SPOOLED ) {                       // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
    470                        throw new HybsSystemException ( "Error Occured while spooling print job. Check Spooler-Service!!!");
    471                }
    472        }
    473
    474        /**
    475         * プリンタジョブの状況を監視するリスナーです。
    476         *
    477         * @author Hiroki.Nakamura
    478         */
    479        private static final class MyPrintJobListener implements XPrintJobListener {
    480                private PrintableState status   ;
    481
    482                /**
    483                 * PrintJobEventのステータスを内部変数にセットします。
    484                 *
    485                 * @param       event   PrintJobEventオブジェクト
    486                 */
    487                @Override
    488                public void printJobEvent( final PrintJobEvent event ) {
    489                        status = event.State;
    490                }
    491
    492                /**
    493                 * EventObjectの処理を実施します。(ここでは何も行いません。)
    494                 *
    495                 * @param       event   EventObjectオブジェクト
    496                 */
    497                @Override
    498                public void disposing( final EventObject event ) {
    499                        // 何もありません。(PMD エラー回避)
    500                }
    501
    502                /**
    503                 * PrintableStateオブジェクトを返します。
    504                 *
    505                 * @return      PrintableStateオブジェクト
    506                 */
    507                public PrintableState getStatus() {
    508                        return status;
    509                }
    510        }
    511
    512        /**
    513         * PDF出力を行います。
    514         *
    515         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    516         *
    517         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    518         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    519         *
    520         * @param       outputName      出力ファイル名
    521         * @param       pdfPasswd       PDFパスワード
    522         * @throws Throwable 何らかのエラーが発生した場合。
    523         */
    524        public void pdf( final String outputName, final String pdfPasswd ) throws Throwable  {
    525                savePdf( outputName, getFilterName( getSuffix( inputName ), "pdf" ), pdfPasswd );
    526        }
    527
    528        /**
    529         * Calc(ods)出力を行います。
    530         *
    531         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    532         *
    533         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    534         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    535         *
    536         * @param       outputName      出力ファイル名
    537         * @throws Throwable 何らかのエラーが発生した場合。
    538         */
    539        public void ods( final String outputName ) throws Throwable  {
    540                saveDoc( outputName, getFilterName( getSuffix( inputName ), "ods" ) );
    541        }
    542
    543        /**
    544         * Excel(xls)出力を行います。
    545         *
    546         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    547         *
    548         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    549         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    550         *
    551         * @param       outputName      出力ファイル名
    552         * @throws Throwable 何らかのエラーが発生した場合。
    553         */
    554        public void xls( final String outputName ) throws Throwable {
    555                saveDoc( outputName, getFilterName( getSuffix( inputName ), "xls" ) );
    556        }
    557
    558        /**
    559         * Writer(ods)出力を行います。
    560         *
    561         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    562         *
    563         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    564         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    565         *
    566         * @param       outputName      出力ファイル名
    567         * @throws Throwable 何らかのエラーが発生した場合。
    568         */
    569        public void odt( final String outputName ) throws Throwable {
    570                saveDoc( outputName, getFilterName( getSuffix( inputName ), "odt" ) );
    571        }
    572
    573        /**
    574         * Word(doc)出力を行います。
    575         *
    576         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    577         *
    578         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    579         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    580         *
    581         * @param       outputName      出力ファイル名
    582         * @throws Throwable 何らかのエラーが発生した場合。
    583         */
    584        public void doc( final String outputName ) throws Throwable {
    585                saveDoc( outputName, getFilterName( getSuffix( inputName ), "doc" ) );
    586        }
    587
    588        /**
    589         * Impress(odp)出力を行います。
    590         *
    591         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    592         *
    593         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    594         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    595         *
    596         * @param       outputName      出力ファイル名
    597         * @throws Throwable 何らかのエラーが発生した場合。
    598         */
    599        public void odp( final String outputName ) throws Throwable {
    600                saveDoc( outputName, getFilterName( getSuffix( inputName ), "odp" ) );
    601        }
    602
    603        /**
    604         * PowerPoint(ppt)出力を行います。
    605         *
    606         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
    607         *
    608         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    609         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    610         *
    611         * @param       outputName      出力ファイル名
    612         * @throws Throwable 何らかのエラーが発生した場合。
    613         */
    614        public void ppt( final String outputName ) throws Throwable {
    615                saveDoc( outputName, getFilterName( getSuffix( inputName ), "ppt" ) );
    616        }
    617
    618        /**
    619         * 出力ファイルから出力形式を自動判別し、変換を行います。
    620         *
    621         * 入出力形式で未対応の場合(形式は入出力ファイルの拡張子で判別)、例外が発行されます。
    622         *
    623         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
    624         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
    625         *
    626         * @param       outputName      出力ファイル名
    627         * @throws Throwable 何らかのエラーが発生した場合。
    628         */
    629        public void auto( final String outputName ) throws Throwable {
    630                final String outSuffix = getSuffix( outputName );
    631                if( "pdf".equalsIgnoreCase( outSuffix ) ) {
    632                        savePdf( outputName, getFilterName( getSuffix( inputName ), outSuffix ), null );
    633                }
    634                else {
    635                        saveDoc( outputName, getFilterName( getSuffix( inputName ), outSuffix ) );
    636                }
    637        }
    638
    639        /**
    640         * フィルター名を指定して、各種ファイル形式に出力を行います。
    641         *
    642         * @param       outputName      出力ファイル名
    643         * @param       filter          フィルター名
    644         */
    645        private void saveDoc(  final String outputName, final String filter ) {
    646                if( !checkOutput( outputName ) ){ return; }
    647
    648                PropertyValue[] storeProps = new PropertyValue[1];
    649                storeProps[0] = new PropertyValue();
    650                storeProps[0].Name = "FilterName";
    651                storeProps[0].Value = filter;
    652
    653                final String url = "file:///" + outputName.replace( '\\', '/' );
    654        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(2)" ); }
    655                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    656                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
    657                try {
    658                        xstorable.storeAsURL( url, storeProps );
    659                }
    660                catch( final Throwable th ) {
    661                        throw new HybsSystemException( "ファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
    662                }
    663        }
    664
    665        /**
    666         * フィルターを指定してPDF出力を行います。
    667         *
    668         * @param       outputName      出力ファイル名
    669         * @param       filter          フィルター名
    670         * @param       pdfPasswd       PDFパスワード
    671         */
    672        private void savePdf( final String outputName, final String filter, final String pdfPasswd ) {
    673                if( !checkOutput( outputName ) ){ return; }
    674
    675                PropertyValue[] storeProps;
    676                if( pdfPasswd == null || pdfPasswd.isEmpty() ) {
    677                        storeProps = new PropertyValue[1];
    678                        storeProps[0] = new PropertyValue();
    679                        storeProps[0].Name = "FilterName";
    680                        storeProps[0].Value = filter;
    681                }
    682                // 帳票要求テーブルでPDFパスワードが設定されている場合
    683                else {
    684                        PropertyValue[] filterProps = new PropertyValue[2];
    685                        filterProps[0] = new PropertyValue();
    686                        filterProps[0].Name = "EncryptFile";
    687                        filterProps[0].Value = true;
    688                        filterProps[1] = new PropertyValue();
    689                        filterProps[1].Name = "DocumentOpenPassword";
    690                        filterProps[1].Value = pdfPasswd;
    691
    692                        storeProps = new PropertyValue[2];
    693                        storeProps[0] = new PropertyValue();
    694                        storeProps[0].Name = "FilterName";
    695                        storeProps[0].Value = "calc_pdf_Export";
    696                        storeProps[1] = new PropertyValue();
    697                        storeProps[1].Name = "FilterData";
    698                        storeProps[1].Value = filterProps;
    699                }
    700
    701                final String url = "file:///" + outputName.replace( '\\', '/' );
    702        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(3)" ); }
    703                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
    704                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
    705                try {
    706                        xstorable.storeToURL( url, storeProps );
    707                }
    708                catch( final Throwable th ) {
    709                        final String err = "PDFファイルへの変換時にエラーが発生しました。[filter=" + filter + "]"
    710                                                        + " URL=" + url + " , storeProps=" + storeProps + " , xComp=" + xComp ;
    711
    712//                      throw new HybsSystemException( "PDFファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
    713                        throw new HybsSystemException( err, th );
    714                }
    715        }
    716
    717        /**
    718         * 出力ファイルのチェックを行います。
    719         *
    720         * @param       outputName      出力ファイル名
    721         *
    722         * @return      処理対象かどうか(入力ファイルと出力ファイルが同じ場合は、falseが返ります)
    723         */
    724        private boolean checkOutput( final String outputName ) {
    725                if( outputName == null || outputName.isEmpty() ) {
    726                        throw new HybsSystemException( "出力ファイルが指定されていません。" );
    727                }
    728
    729                final File inFile = new File( inputName );
    730                final File outFile = new File( outputName );
    731
    732                if( outFile.exists() ) {
    733                        if( inFile.getAbsoluteFile().equals( outFile.getAbsoluteFile() ) ) {
    734                                // 入力と出力が同じファイルの場合な何もしない
    735                                return false;
    736                        }
    737                        else if( !outFile.delete() ) {
    738                                throw new HybsSystemException( "出力先の既存ファイルが削除できません。[file=" + outputName + "]" );
    739                        }
    740                }
    741                return true;
    742        }
    743
    744        /**
    745         * 入出力の形式(拡張子)からフィルター名を取得します。
    746         *
    747         * @param       inSuffix        入力拡張子
    748         * @param       outSuffix       出力拡張子
    749         *
    750         * @return      フィルター名
    751         */
    752        private static String getFilterName( final String inSuffix, final String outSuffix ) {
    753                final String filterName = FILTER_MAP.get( inSuffix + "_" + outSuffix );
    754                if( filterName == null ) {
    755                        final String errMsg = "入力形式、出力形式は、以下の対応表に基づき、設定して下さい。" + CR
    756                                                        + "入力[Calc(ods)   ,Excel(xls)     ] ⇒ 出力[Calc(ods)   ,Excel(xls)     ,PDF]" + CR
    757                                                        + "入力[Writer(odt) ,Word(doc)      ] ⇒ 出力[Writer(odt) ,Word(doc)      ,PDF]" + CR
    758                                                        + "入力[Impress(odp),PowerPoint(ppt)] ⇒ 出力[Impress(odp),PowerPoint(ppt),PDF]" + CR;
    759                        throw new HybsSystemException( errMsg );
    760                }
    761                return filterName;
    762        }
    763
    764        /**
    765         * ファイル名から拡張子(小文字)を求めます。
    766         *
    767         * @param       fileName        ファイル名
    768         *
    769         * @return      拡張子(小文字)
    770         */
    771        private static String getSuffix( final String fileName ) {
    772                String suffix = null;
    773                if( fileName != null ) {
    774                        final int sufIdx = fileName.lastIndexOf( '.' );
    775                        if( sufIdx >= 0 ) {
    776                                suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
    777                        }
    778                }
    779                return suffix;
    780        }
    781
    782        /**
    783         * ドキュメントの変換を行うための簡易メソッドです。
    784         *
    785         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
    786         *
    787         * @param       inputFile       入力ファイル名
    788         * @param       outputFile      出力ファイル名
    789         * @see #convert(String[], String, boolean)
    790         */
    791        public static final void convert( final String inputFile, final String outputFile ) {
    792                convert( StringUtil.csv2Array( inputFile ), outputFile );
    793        }
    794
    795        /**
    796         * ドキュメントの変換を行うための簡易メソッドです。
    797         *
    798         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
    799         *
    800         * @param       inputFile       入力ファイル名配列
    801         * @param       outputFile      出力ファイル名
    802         * @see #convert(String[], String, boolean)
    803         */
    804        public static final void convert( final String[] inputFile, final String outputFile ) {
    805                convert( inputFile, outputFile, true );
    806        }
    807
    808        /**
    809         * ドキュメントの変換を行うための簡易メソッドです。
    810         *
    811         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
    812         *
    813         * isOnlineがtrueに指定された場合、soffice.binのプロセスをファクトリークラス経由で生成し、
    814         * キャッシュします。
    815         * 但し、システムリソースが読み込まれないような、バッチファイルから起動した場合は、この方法は
    816         * 利用できないため、isOnlineをfalseに指定する必要があります。
    817         *
    818         * @param       inputFile       入力ファイル名配列
    819         * @param       outputFile      出力ファイル名
    820         * @param       isOnline        オンライン(Web環境での使用)かどうか
    821         */
    822        public static final void convert( final String inputFile[], final String outputFile, final boolean isOnline ) {
    823                final DocConverter_OOO dc = new DocConverter_OOO( inputFile, isOnline );
    824                try {
    825                        dc.open();
    826                        dc.auto( outputFile );
    827                        dc.close();
    828                }
    829                catch( final Throwable th ) {
    830                        dc.close( true );
    831                        throw new HybsSystemException( th );
    832                }
    833        }
    834
    835        /**
    836         * ドキュメントの変換を行います。
    837         *
    838         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
    839         *
    840         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    841         * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。
    842         *
    843         * @param       args    コマンド引数配列
    844         */
    845        public static void main( final String[] args ) {
    846                if( args.length < 2 ) {
    847                        System.out.println( "usage : OdsConverter [inputFile or inputDir] [outputDir]" );
    848                        return;
    849                }
    850
    851                final File input = new File( args[0] );
    852                final File output = new File( args[1] );
    853
    854                // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
    855                if( output.mkdirs() ) {
    856                        System.err.println( args[1] + " の ディレクトリ作成に失敗しました。" );
    857                }
    858
    859                if( input.isDirectory() ) {
    860                        final File[] inputFiles = input.listFiles();
    861                        // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値を利用している(findbugs)
    862                        if( inputFiles != null ) {
    863                                for( final File file : inputFiles ) {
    864                                        final String inputFile = file.getAbsolutePath();
    865                                        final String outputFile = output.getAbsolutePath() + File.separator + file.getName().replace( ".xls", ".ods" );
    866                                        convert( StringUtil.csv2Array( inputFile ), outputFile, false );
    867                                }
    868                        }
    869                }
    870                else {
    871                        final String inputFile = input.getAbsolutePath();
    872                        final String outputFile = output.getAbsolutePath() + File.separator + input.getName().replace( ".xls", ".ods" );
    873                        convert( StringUtil.csv2Array( inputFile ), outputFile, false );
    874                }
    875        }
    876}