OSDN Git Service

48dc5574d93dcd9a6004f92dbf30e61d8b2a5225
[mikutoga/Vmd2XML.git] / src / main / java / jp / sfjp / mikutoga / vmd2xml / Vmd2Xml.java
1 /*
2  * vmd to xml converter main entry
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.vmd2xml;
9
10 import java.io.BufferedOutputStream;
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.io.FileOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.OutputStream;
17 import java.io.PrintStream;
18 import java.nio.channels.FileChannel;
19 import java.text.MessageFormat;
20 import java.util.Properties;
21 import jp.sfjp.mikutoga.bin.parser.MmdFormatException;
22 import jp.sfjp.mikutoga.vmd.IllegalVmdDataException;
23 import jp.sfjp.mikutoga.xml.TogaXmlException;
24 import org.xml.sax.InputSource;
25 import org.xml.sax.SAXException;
26
27
28 /**
29  * VMDモーションファイルとXMLとの間で変換を行うアプリケーション。
30  */
31 public final class Vmd2Xml {
32
33     /** 正常系。 */
34     public static final int EXIT_OK     = 0;
35     /** 内部エラー。 */
36     public static final int EXIT_INTERR = 1;
37     /** 実行環境に起因するエラー。 */
38     public static final int EXIT_ENVERR = 2;
39     /** オプション指定に起因するエラー。 */
40     public static final int EXIT_OPTERR = 3;
41     /** ファイル入出力に起因するエラー。 */
42     public static final int EXIT_IOERR  = 4;
43     /** XMLフォーマットに起因するエラー。 */
44     public static final int EXIT_XMLERR = 5;
45     /** VMDフォーマットに起因するエラー。 */
46     public static final int EXIT_VMDERR = 7;
47
48     /** アプリ名。 */
49     public static final String APPNAME;
50     /** バージョン識別子。 */
51     public static final String APPVER;
52     /** ライセンス種別。 */
53     public static final String APPLICENSE;
54     /** 開発元URL。 */
55     public static final String APPURL;
56     /** ジェネレータ名。 */
57     public static final String GENERATOR;
58
59     private static final Class<?> THISCLASS;
60     private static final String RES_VER = "resources/version.properties";
61
62     private static final PrintStream ERROUT = System.err;
63     private static final String MSG_ERR = "ERROR:\n{0}\n(-h for help)";
64     private static final String MSG_HELP =
65               "{0} {1}\n"
66             + "\u0020\u0020License\u0020:\u0020{2}\n"
67             + "\u0020\u0020{3}\n";
68     private static final String MSG_NOINFILE = "Can''t find input file: {0}";
69     private static final String MSG_ABNFILE = "{0} is not file.";
70     private static final String MSG_OWOUTFILE =
71               "{0} already exists.\n"
72             + "If you want to overwrite, use -f.";
73
74     private static final String MSG_OLDJRE = "You need JRE {0} or later.";
75     private static final String REQUIRED_JRE = "1.8";
76
77     static{
78         THISCLASS = Vmd2Xml.class;
79         InputStream ver = THISCLASS.getResourceAsStream(RES_VER);
80         Properties verProps = new Properties();
81         try{
82             try{
83                 verProps.load(ver);
84             }finally{
85                 ver.close();
86             }
87         }catch(IOException e){
88             throw new ExceptionInInitializerError(e);
89         }
90
91         APPNAME    = verProps.getProperty("app.name");
92         APPVER     = verProps.getProperty("app.version");
93         APPLICENSE = verProps.getProperty("app.license");
94         APPURL     = verProps.getProperty("app.url");
95
96         GENERATOR = APPNAME + ' ' + APPVER;
97
98         new Vmd2Xml().hashCode();
99     }
100
101
102     /**
103      * ダミーコンストラクタ。
104      */
105     private Vmd2Xml(){
106         super();
107         assert this.getClass().equals(THISCLASS);
108         return;
109     }
110
111
112     /**
113      * VMを終了させる。
114      * @param code 終了コード
115      * @see java.lang.System#exit(int)
116      */
117     private static void exit(int code){
118         System.exit(code);
119         assert false;
120         throw new AssertionError();
121     }
122
123     /**
124      * 共通エラーメッセージを出力する。
125      * @param text 個別メッセージ
126      */
127     private static void errMsg(String text){
128         String msg = MessageFormat.format(MSG_ERR, text);
129         ERROUT.println(msg);
130         return;
131     }
132
133     /**
134      * 標準エラー出力へ例外情報出力。
135      * @param ex 例外
136      * @param dumpStack スタックトレースを出力するならtrue
137      */
138     private static void thPrintln(Throwable ex, boolean dumpStack){
139         if(dumpStack){
140             ex.printStackTrace(ERROUT);
141         }else{
142             String text = ex.toString();
143             ERROUT.println(text);
144         }
145
146         return;
147     }
148
149     /**
150      * 標準エラー出力へ例外情報出力。
151      * @param ex 例外
152      */
153     private static void thPrintln(Throwable ex){
154         thPrintln(ex, false);
155         return;
156     }
157
158     /**
159      * 入出力エラー処理。
160      * 例外を出力してVM終了する。
161      * @param ex 例外
162      */
163     private static void ioError(IOException ex){
164         thPrintln(ex);
165         exit(EXIT_IOERR);
166     }
167
168     /**
169      * XML構文エラー処理。
170      * 例外を出力してVM終了する。
171      * @param ex 例外
172      */
173     private static void xmlError(Throwable ex){
174         thPrintln(ex);
175         exit(EXIT_XMLERR);
176     }
177
178     /**
179      * VMDファイルフォーマットエラー処理。
180      * 例外を出力してVM終了する。
181      * @param ex 例外
182      */
183     private static void vmdError(MmdFormatException ex){
184         thPrintln(ex, true);
185         exit(EXIT_VMDERR);
186     }
187
188     /**
189      * 内部エラー処理。
190      * 例外を出力してVM終了する。
191      * @param ex 例外
192      */
193     private static void internalError(Throwable ex){
194         thPrintln(ex, true);
195         exit(EXIT_INTERR);
196     }
197
198     /**
199      * JREのバージョン判定を行う。
200      * 不適切ならVMごと終了。
201      */
202     private static void checkJRE(){
203         Package jrePackage = java.lang.Object.class.getPackage();
204         if( ! jrePackage.isCompatibleWith(REQUIRED_JRE)){
205             String msg = MessageFormat.format(MSG_OLDJRE, REQUIRED_JRE);
206             ERROUT.println(msg);
207             exit(EXIT_ENVERR);
208         }
209         return;
210     }
211
212     /**
213      * ヘルプメッセージを出力する。
214      */
215     private static void putHelp(){
216         String msg =
217                 MessageFormat.format(MSG_HELP,
218                 APPNAME, APPVER, APPLICENSE, APPURL);
219         ERROUT.println(msg);
220         ERROUT.println(OptSwitch.getConsoleHelp());
221         return;
222     }
223
224     /**
225      * ファイルサイズを0に切り詰める。
226      * <p>ファイルが存在しなければなにもしない。
227      * <p>通常ファイルでなければなにもしない。
228      * @param file ファイル
229      * @throws IOException 入出力エラー
230      */
231     private static void trunc(File file) throws IOException{
232         if( ! file.exists() ) return;
233         if( ! file.isFile() ) return;
234
235         if(file.length() <= 0L) return;
236
237         FileOutputStream foStream = new FileOutputStream(file);
238         try{
239             FileChannel channnel = foStream.getChannel();
240             try{
241                 channnel.truncate(0L);
242             }finally{
243                 channnel.close();
244             }
245         }finally{
246             foStream.close();
247         }
248
249         return;
250     }
251
252     /**
253      * 入力ソースを準備する。
254      * <p>入力ファイルが通常ファイルとして存在しなければエラー終了。
255      * @param optInfo オプション情報
256      * @return 入力ソース
257      */
258     private static InputSource openInfile(OptInfo optInfo){
259         String fileName = optInfo.getInFilename();
260         File inFile = new File(fileName);
261
262         if( (! inFile.exists()) || (! inFile.isFile()) ){
263             String absPath = inFile.getAbsolutePath();
264             String msg = MessageFormat.format(MSG_NOINFILE, absPath);
265             errMsg(msg);
266             exit(EXIT_IOERR);
267         }
268
269         InputSource source = XmlInputUtil.fileToSource(inFile);
270
271         return source;
272     }
273
274     /**
275      * 出力ストリームを準備する。
276      * <p>出力ファイルが通常ファイルでない場合はエラー終了。
277      * <p>既存の出力ファイルに上書き指示が伴っていなければエラー終了。
278      * @param optInfo オプション情報
279      * @return 出力ストリーム
280      */
281     private static OutputStream openOutfile(OptInfo optInfo) {
282         String outputFile = optInfo.getOutFilename();
283         boolean overwrite = optInfo.overwriteMode();
284
285         File outFile = new File(outputFile);
286
287         if(outFile.exists()){
288             String absPath = outFile.getAbsolutePath();
289             if( ! outFile.isFile() ){
290                 String msg = MessageFormat.format(MSG_ABNFILE, absPath);
291                 errMsg(msg);
292                 exit(EXIT_IOERR);
293             }else if( ! overwrite ){
294                 String msg = MessageFormat.format(MSG_OWOUTFILE, absPath);
295                 errMsg(msg);
296                 exit(EXIT_IOERR);
297             }
298         }
299
300         try{
301             trunc(outFile);
302         }catch(IOException e){
303             ioError(e);
304         }
305
306         OutputStream os;
307         try{
308             os = new FileOutputStream(outFile);
309         }catch(FileNotFoundException e){
310             ioError(e);
311             assert false;
312             throw new AssertionError(e);
313         }
314
315         os = new BufferedOutputStream(os);
316
317         return os;
318     }
319
320     /**
321      * オプション情報に従いコンバータを生成する。
322      * @param optInfo オプション情報
323      * @return コンバータ
324      */
325     private static Vmd2XmlConv buildConverter(OptInfo optInfo){
326         Vmd2XmlConv converter = new Vmd2XmlConv();
327
328         converter.setInType (optInfo.getInFileType());
329         converter.setOutType(optInfo.getOutFileType());
330
331         converter.setNewline(optInfo.getNewline());
332         converter.setGenerator(optInfo.getGenerator());
333         converter.setQuaterniomMode(optInfo.isQuaterniomMode());
334
335         return converter;
336     }
337
338     /**
339      * 実際のコンバート作業と異常系処理を行う。
340      * <p>異常系が起きた場合、このメソッドは制御を戻さない。
341      * @param converter コンバータ
342      * @param source 入力ソース
343      * @param ostream 出力ストリーム
344      */
345     private static void doConvert(Vmd2XmlConv converter,
346                                    InputSource source,
347                                    OutputStream ostream ){
348         try{
349             converter.convert(source, ostream);
350         }catch(IOException e){
351             ioError(e);
352         }catch(IllegalVmdDataException e){
353             internalError(e);
354         }catch(MmdFormatException e){
355             vmdError(e);
356         }catch(TogaXmlException e){
357             xmlError(e);
358         }catch(SAXException e){
359             xmlError(e);
360         }
361
362         return;
363     }
364
365     /**
366      * コマンドライン文字列をオプション情報としてパースする。
367      * <p>異常系が起きた場合、このメソッドは制御を戻さない。
368      * @param args コマンドライン文字列群
369      * @return オプション情報
370      */
371     private static OptInfo parseOption(String[] args){
372         OptInfo optInfo;
373
374         try{
375             optInfo = OptInfo.parseOption(args);
376         }catch(CmdLineException e){
377             String optErrMsg = e.getLocalizedMessage();
378             errMsg(optErrMsg);
379             exit(EXIT_OPTERR);
380
381             assert false;
382             throw new AssertionError(e);
383         }
384
385         return optInfo;
386     }
387
388     /**
389      * Mainエントリ。
390      * @param args コマンドパラメータ
391      */
392     public static void main(String[] args){
393         checkJRE();
394
395         OptInfo optInfo = parseOption(args);
396         if(optInfo.needHelp()){
397             putHelp();
398             exit(EXIT_OK);
399         }
400
401         Vmd2XmlConv converter = buildConverter(optInfo);
402         InputSource source = openInfile(optInfo);
403         OutputStream ostream = openOutfile(optInfo);
404
405         doConvert(converter, source, ostream);
406
407         try{
408             ostream.close();
409         }catch(IOException e){
410             ioError(e);
411         }
412
413         exit(EXIT_OK);
414
415         return;
416     }
417
418 }