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( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい" ); }
411
412                if( printer == null || printer.isEmpty() ) {
413                        throw new HybsSystemException( "プリンターが指定されていません。" );
414                }
415
416                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
417                final XPrintable xprintable = (XPrintable) UnoRuntime.queryInterface( XPrintable.class, xComp );
418                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
419                final XPrintJobBroadcaster selection = (XPrintJobBroadcaster) UnoRuntime.queryInterface(XPrintJobBroadcaster.class, xprintable);
420                final MyPrintJobListener listener = new MyPrintJobListener ();
421                selection.addPrintJobListener( listener );
422
423                PropertyValue[] tmpProps = new PropertyValue[1];
424                tmpProps[0] = new PropertyValue();
425                tmpProps[0].Name = "Name";
426                // 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
427                // OSがLinuxの場合は、プリンタ名称の前後に"<",">"を付加
428                tmpProps[0].Value = "LINUX".indexOf( HybsSystem.sys( "OS_INFO" ).toUpperCase( Locale.JAPAN ) ) >= 0 ? "<" + printer + ">" : printer;
429
430                // 4.3.4.4 (2009/01/01)
431                try {
432                        xprintable.setPrinter( tmpProps );
433                }
434                catch( final IllegalArgumentException ex ) {
435                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
436                }
437
438                // 4.3.7.3 (2009/06/22) 存在しないプリンタを指定した場合は、PropertyValueに
439                // デフォルトプリンターが入るため、引数の値と合致しているかで正しく設定されたかを確認
440                String curPrinter = null;
441                final PropertyValue[] chkProps = xprintable.getPrinter();
442                for( int i=0; i<chkProps.length; i++ ) {
443                        if( "Name".equals( chkProps[i].Name) ) {
444                                curPrinter = (String)chkProps[i].Value;
445                                break;
446                        }
447                }
448                if( !(printer.equalsIgnoreCase( curPrinter ) ) ) {
449                        final String errMsg = "プリンター[" + printer + "]を発行先に指定できませんでした。" + CR
450                                                        + "存在しないプリンタ名が指定されている可能性があります。";
451                        throw new HybsSystemException( errMsg );
452                }
453
454                // 4.3.0.0 (2008/07/16)
455                PropertyValue[] printProps = new PropertyValue[1];
456                printProps[0] = new PropertyValue();
457                printProps[0].Name = "Wait";
458                printProps[0].Value = true;
459
460                // 4.3.4.4 (2009/01/01)
461                try {
462                        xprintable.print( printProps );
463                }
464                catch( final IllegalArgumentException ex ) {
465                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
466                }
467
468                // 4.3.0.0 (2008/07/16)
469                if( listener.getStatus() == null
470                                || listener.getStatus() != PrintableState.JOB_COMPLETED && listener.getStatus() != PrintableState.JOB_SPOOLED ) {                       // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
471                        throw new HybsSystemException ( "Error Occured while spooling print job. Check Spooler-Service!!!");
472                }
473        }
474
475        /**
476         * プリンタジョブの状況を監視するリスナーです。
477         *
478         * @author Hiroki.Nakamura
479         */
480        private static final class MyPrintJobListener implements XPrintJobListener {
481                private PrintableState status   ;
482
483                /**
484                 * PrintJobEventのステータスを内部変数にセットします。
485                 *
486                 * @param       event   PrintJobEventオブジェクト
487                 */
488                @Override
489                public void printJobEvent( final PrintJobEvent event ) {
490                        status = event.State;
491                }
492
493                /**
494                 * EventObjectの処理を実施します。(ここでは何も行いません。)
495                 *
496                 * @param       event   EventObjectオブジェクト
497                 */
498                @Override
499                public void disposing( final EventObject event ) {
500                        // 何もありません。(PMD エラー回避)
501                }
502
503                /**
504                 * PrintableStateオブジェクトを返します。
505                 *
506                 * @return      PrintableStateオブジェクト
507                 */
508                public PrintableState getStatus() {
509                        return status;
510                }
511        }
512
513        /**
514         * PDF出力を行います。
515         *
516         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
517         *
518         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
519         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
520         *
521         * @param       outputName      出力ファイル名
522         * @param       pdfPasswd       PDFパスワード
523         * @throws Throwable 何らかのエラーが発生した場合。
524         */
525        public void pdf( final String outputName, final String pdfPasswd ) throws Throwable  {
526                savePdf( outputName, getFilterName( getSuffix( inputName ), "pdf" ), pdfPasswd );
527        }
528
529        /**
530         * Calc(ods)出力を行います。
531         *
532         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
533         *
534         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
535         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
536         *
537         * @param       outputName      出力ファイル名
538         * @throws Throwable 何らかのエラーが発生した場合。
539         */
540        public void ods( final String outputName ) throws Throwable  {
541                saveDoc( outputName, getFilterName( getSuffix( inputName ), "ods" ) );
542        }
543
544        /**
545         * Excel(xls)出力を行います。
546         *
547         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
548         *
549         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
550         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
551         *
552         * @param       outputName      出力ファイル名
553         * @throws Throwable 何らかのエラーが発生した場合。
554         */
555        public void xls( final String outputName ) throws Throwable {
556                saveDoc( outputName, getFilterName( getSuffix( inputName ), "xls" ) );
557        }
558
559        /**
560         * Writer(ods)出力を行います。
561         *
562         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
563         *
564         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
565         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
566         *
567         * @param       outputName      出力ファイル名
568         * @throws Throwable 何らかのエラーが発生した場合。
569         */
570        public void odt( final String outputName ) throws Throwable {
571                saveDoc( outputName, getFilterName( getSuffix( inputName ), "odt" ) );
572        }
573
574        /**
575         * Word(doc)出力を行います。
576         *
577         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
578         *
579         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
580         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
581         *
582         * @param       outputName      出力ファイル名
583         * @throws Throwable 何らかのエラーが発生した場合。
584         */
585        public void doc( final String outputName ) throws Throwable {
586                saveDoc( outputName, getFilterName( getSuffix( inputName ), "doc" ) );
587        }
588
589        /**
590         * Impress(odp)出力を行います。
591         *
592         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
593         *
594         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
595         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
596         *
597         * @param       outputName      出力ファイル名
598         * @throws Throwable 何らかのエラーが発生した場合。
599         */
600        public void odp( final String outputName ) throws Throwable {
601                saveDoc( outputName, getFilterName( getSuffix( inputName ), "odp" ) );
602        }
603
604        /**
605         * PowerPoint(ppt)出力を行います。
606         *
607         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
608         *
609         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
610         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
611         *
612         * @param       outputName      出力ファイル名
613         * @throws Throwable 何らかのエラーが発生した場合。
614         */
615        public void ppt( final String outputName ) throws Throwable {
616                saveDoc( outputName, getFilterName( getSuffix( inputName ), "ppt" ) );
617        }
618
619        /**
620         * 出力ファイルから出力形式を自動判別し、変換を行います。
621         *
622         * 入出力形式で未対応の場合(形式は入出力ファイルの拡張子で判別)、例外が発行されます。
623         *
624         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
625         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
626         *
627         * @param       outputName      出力ファイル名
628         * @throws Throwable 何らかのエラーが発生した場合。
629         */
630        public void auto( final String outputName ) throws Throwable {
631                final String outSuffix = getSuffix( outputName );
632                if( "pdf".equalsIgnoreCase( outSuffix ) ) {
633                        savePdf( outputName, getFilterName( getSuffix( inputName ), outSuffix ), null );
634                }
635                else {
636                        saveDoc( outputName, getFilterName( getSuffix( inputName ), outSuffix ) );
637                }
638        }
639
640        /**
641         * フィルター名を指定して、各種ファイル形式に出力を行います。
642         *
643         * @param       outputName      出力ファイル名
644         * @param       filter          フィルター名
645         */
646        private void saveDoc(  final String outputName, final String filter ) {
647                if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい" ); }
648                if( !checkOutput( outputName ) ){ return; }
649
650                PropertyValue[] storeProps = new PropertyValue[1];
651                storeProps[0] = new PropertyValue();
652                storeProps[0].Name = "FilterName";
653                storeProps[0].Value = filter;
654
655                final String url = "file:///" + outputName.replace( '\\', '/' );
656                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
657                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
658                try {
659                        xstorable.storeAsURL( url, storeProps );
660                }
661                catch( final Throwable th ) {
662                        throw new HybsSystemException( "ファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
663                }
664        }
665
666        /**
667         * フィルターを指定してPDF出力を行います。
668         *
669         * @param       outputName      出力ファイル名
670         * @param       filter          フィルター名
671         * @param       pdfPasswd       PDFパスワード
672         */
673        private void savePdf( final String outputName, final String filter, final String pdfPasswd ) {
674                if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい" ); }
675                if( !checkOutput( outputName ) ){ return; }
676
677                PropertyValue[] storeProps;
678                if( pdfPasswd == null || pdfPasswd.isEmpty() ) {
679                        storeProps = new PropertyValue[1];
680                        storeProps[0] = new PropertyValue();
681                        storeProps[0].Name = "FilterName";
682                        storeProps[0].Value = filter;
683                }
684                // 帳票要求テーブルでPDFパスワードが設定されている場合
685                else {
686                        PropertyValue[] filterProps = new PropertyValue[2];
687                        filterProps[0] = new PropertyValue();
688                        filterProps[0].Name = "EncryptFile";
689                        filterProps[0].Value = true;
690                        filterProps[1] = new PropertyValue();
691                        filterProps[1].Name = "DocumentOpenPassword";
692                        filterProps[1].Value = pdfPasswd;
693
694                        storeProps = new PropertyValue[2];
695                        storeProps[0] = new PropertyValue();
696                        storeProps[0].Name = "FilterName";
697                        storeProps[0].Value = "calc_pdf_Export";
698                        storeProps[1] = new PropertyValue();
699                        storeProps[1].Name = "FilterData";
700                        storeProps[1].Value = filterProps;
701                }
702
703                final String url = "file:///" + outputName.replace( '\\', '/' );
704                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
705                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
706                try {
707                        xstorable.storeToURL( url, storeProps );
708                }
709                catch( final Throwable th ) {
710                        throw new HybsSystemException( "PDFファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
711                }
712        }
713
714        /**
715         * 出力ファイルのチェックを行います。
716         *
717         * @param       outputName      出力ファイル名
718         *
719         * @return      処理対象かどうか(入力ファイルと出力ファイルが同じ場合は、falseが返ります)
720         */
721        private boolean checkOutput( final String outputName ) {
722                if( outputName == null || outputName.isEmpty() ) {
723                        throw new HybsSystemException( "出力ファイルが指定されていません。" );
724                }
725
726                final File inFile = new File( inputName );
727                final File outFile = new File( outputName );
728
729                if( outFile.exists() ) {
730                        if( inFile.getAbsoluteFile().equals( outFile.getAbsoluteFile() ) ) {
731                                // 入力と出力が同じファイルの場合な何もしない
732                                return false;
733                        }
734                        else if( !outFile.delete() ) {
735                                throw new HybsSystemException( "出力先の既存ファイルが削除できません。[file=" + outputName + "]" );
736                        }
737                }
738                return true;
739        }
740
741        /**
742         * 入出力の形式(拡張子)からフィルター名を取得します。
743         *
744         * @param       inSuffix        入力拡張子
745         * @param       outSuffix       出力拡張子
746         *
747         * @return      フィルター名
748         */
749        private static String getFilterName( final String inSuffix, final String outSuffix ) {
750                final String filterName = FILTER_MAP.get( inSuffix + "_" + outSuffix );
751                if( filterName == null ) {
752                        final String errMsg = "入力形式、出力形式は、以下の対応表に基づき、設定して下さい。" + CR
753                                                        + "入力[Calc(ods)   ,Excel(xls)     ] ⇒ 出力[Calc(ods)   ,Excel(xls)     ,PDF]" + CR
754                                                        + "入力[Writer(odt) ,Word(doc)      ] ⇒ 出力[Writer(odt) ,Word(doc)      ,PDF]" + CR
755                                                        + "入力[Impress(odp),PowerPoint(ppt)] ⇒ 出力[Impress(odp),PowerPoint(ppt),PDF]" + CR;
756                        throw new HybsSystemException( errMsg );
757                }
758                return filterName;
759        }
760
761        /**
762         * ファイル名から拡張子(小文字)を求めます。
763         *
764         * @param       fileName        ファイル名
765         *
766         * @return      拡張子(小文字)
767         */
768        private static String getSuffix( final String fileName ) {
769                String suffix = null;
770                if( fileName != null ) {
771                        final int sufIdx = fileName.lastIndexOf( '.' );
772                        if( sufIdx >= 0 ) {
773                                suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
774                        }
775                }
776                return suffix;
777        }
778
779        /**
780         * ドキュメントの変換を行うための簡易メソッドです。
781         *
782         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
783         *
784         * @param       inputFile       入力ファイル名
785         * @param       outputFile      出力ファイル名
786         * @see #convert(String[], String, boolean)
787         */
788        public static final void convert( final String inputFile, final String outputFile ) {
789                convert( StringUtil.csv2Array( inputFile ), outputFile );
790        }
791
792        /**
793         * ドキュメントの変換を行うための簡易メソッドです。
794         *
795         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
796         *
797         * @param       inputFile       入力ファイル名配列
798         * @param       outputFile      出力ファイル名
799         * @see #convert(String[], String, boolean)
800         */
801        public static final void convert( final String[] inputFile, final String outputFile ) {
802                convert( inputFile, outputFile, true );
803        }
804
805        /**
806         * ドキュメントの変換を行うための簡易メソッドです。
807         *
808         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
809         *
810         * isOnlineがtrueに指定された場合、soffice.binのプロセスをファクトリークラス経由で生成し、
811         * キャッシュします。
812         * 但し、システムリソースが読み込まれないような、バッチファイルから起動した場合は、この方法は
813         * 利用できないため、isOnlineをfalseに指定する必要があります。
814         *
815         * @param       inputFile       入力ファイル名配列
816         * @param       outputFile      出力ファイル名
817         * @param       isOnline        オンライン(Web環境での使用)かどうか
818         */
819        public static final void convert( final String inputFile[], final String outputFile, final boolean isOnline ) {
820                final DocConverter_OOO dc = new DocConverter_OOO( inputFile, isOnline );
821                try {
822                        dc.open();
823                        dc.auto( outputFile );
824                        dc.close();
825                }
826                catch( final Throwable th ) {
827                        dc.close( true );
828                        throw new HybsSystemException( th );
829                }
830        }
831
832        /**
833         * ドキュメントの変換を行います。
834         *
835         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
836         *
837         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
838         * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。
839         *
840         * @param       args    コマンド引数配列
841         */
842        public static void main( final String[] args ) {
843                if( args.length < 2 ) {
844                        System.out.println( "usage : OdsConverter [inputFile or inputDir] [outputDir]" );
845                        return;
846                }
847
848                final File input = new File( args[0] );
849                final File output = new File( args[1] );
850
851                // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
852                if( output.mkdirs() ) {
853                        System.err.println( args[1] + " の ディレクトリ作成に失敗しました。" );
854                }
855
856                if( input.isDirectory() ) {
857                        final File[] inputFiles = input.listFiles();
858                        // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値を利用している(findbugs)
859                        if( inputFiles != null ) {
860                                for( final File file : inputFiles ) {
861                                        final String inputFile = file.getAbsolutePath();
862                                        final String outputFile = output.getAbsolutePath() + File.separator + file.getName().replace( ".xls", ".ods" );
863                                        convert( StringUtil.csv2Array( inputFile ), outputFile, false );
864                                }
865                        }
866                }
867                else {
868                        final String inputFile = input.getAbsolutePath();
869                        final String outputFile = output.getAbsolutePath() + File.separator + input.getName().replace( ".xls", ".ods" );
870                        convert( StringUtil.csv2Array( inputFile ), outputFile, false );
871                }
872        }
873}