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