/** * Copyright (C) 2012-2013 Selventa, Inc. * * This file is part of the OpenBEL Framework. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The OpenBEL Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>. * * Additional Terms under LGPL v3: * * This license does not authorize you and you are prohibited from using the * name, trademarks, service marks, logos or similar indicia of Selventa, Inc., * or, in the discretion of other licensors or authors of the program, the * name, trademarks, service marks, logos or similar indicia of such authors or * licensors, in any marketing or advertising materials relating to your * distribution of the program or any covered product. This restriction does * not waive or limit your obligation to keep intact all copyright notices set * forth in the program as delivered to you. * * If you distribute the program in whole or in part, or any modified version * of the program, and you assume contractual liability to the recipient with * respect to the program or modified version, then you will indemnify the * authors and licensors of the program for any liabilities that these * contractual assumptions directly impose on those licensors and authors. */ package org.openbel.framework.tools; import static java.lang.String.format; import static org.openbel.framework.common.BELUtilities.hasItems; import static org.openbel.framework.common.Strings.UTF_8; import static org.openbel.framework.common.enums.ExitCode.GENERAL_FAILURE; import static org.openbel.framework.core.StandardOptions.LONG_OPT_DEBUG; import static org.openbel.framework.core.StandardOptions.SHORT_OPT_VERBOSE; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URISyntaxException; import java.util.LinkedList; import java.util.List; import javax.xml.bind.JAXBException; import org.apache.commons.cli.Option; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.openbel.bel.model.BELParseErrorException; import org.openbel.bel.model.BELParseWarningException; import org.openbel.framework.common.SimpleOutput; import org.openbel.framework.common.bel.converters.BELDocumentConverter; import org.openbel.framework.common.bel.parser.BELParseResults; import org.openbel.framework.common.bel.parser.BELParser; import org.openbel.framework.common.belscript.BELScriptExporter; import org.openbel.framework.common.model.Document; import org.openbel.framework.common.xbel.parser.XBELConverter; import org.openbel.framework.core.CommandLineApplication; import org.openbel.framework.core.XBELConverterServiceImpl; import org.openbel.framework.core.compiler.ValidationError; import org.xml.sax.SAXException; public final class DocumentConverter extends CommandLineApplication { private static final String DOCUMENT_CONVERTER_NAME = "Document Converter"; private static final String DOCUMENT_CONVERTER_DESC = "Convert between BEL and XBEL formats"; private static final String LONG_OPT_NO_PRESERVE = "no-preserve"; private static final String USAGE_OUTPUT_FILE = " [{-o | --output-file} <output file name> [-f | --force | --no-preserve] ]"; private static final String USAGE_TYPE = " [{-t | --type} {bel | BEL | xbel | XBEL}]"; private static final String BEL_EXTENSION = ".bel"; private static final String XBEL_EXTENSION = ".xbel"; // Flags specified by command line options private boolean verbose, debug; /** * Static main method to launch the Document Converter tool. * * @param args {@link String String[]} the command-line arguments */ public static void main(String[] args) { DocumentConverter app = new DocumentConverter(args); app.run(); } public DocumentConverter(String[] args) { super(args, false); final SimpleOutput reportable = new SimpleOutput(); reportable.setErrorStream(System.err); // Normal output is written to stderr because the output document // may be written to stdout. reportable.setOutputStream(System.err); setReportable(reportable); printApplicationInfo(); if (hasOption('h')) { printHelp(true); } } private void run() { verbose = hasOption(SHORT_OPT_VERBOSE); debug = hasOption(LONG_OPT_DEBUG); final List<String> extraArgs = getExtraneousArguments(); // Determine the input document file path if (extraArgs.size() == 0) { // print out the usage if no arguments are given printUsage(); // Another SimpleOutput is used here, and it uses stdout because at this // point nothing else will need to be output. SimpleOutput so = new SimpleOutput(); so.setErrorStream(System.out); so.error("\n"); so.error("No documents specified."); end(); } else if (extraArgs.size() > 1) { fatal("Only a single document can be specified."); } final String inputFileName = extraArgs.get(0); // Check that the input file exists final File inputFile = new File(inputFileName); if (!inputFile.exists()) { fatal("Input file does not exist - " + inputFile.getAbsolutePath()); } // Determine the format of the input document final DocumentFormat format = determineType(inputFile); if (format == null) { fatal("Could not determine the format of the input file '" + inputFileName + "'. " + "Please specify '-t BEL' or '-t XBEL'."); } OutputStream output = null; if (hasOption("o")) { final String outputFilename = getOptionValue("o"); final boolean noPreserve = (hasOption("f") || hasOption(LONG_OPT_NO_PRESERVE)); final File outputFile = new File(outputFilename); if (!noPreserve && outputFile.exists()) { fatal("File '" + outputFilename + "' exists, specify --" + LONG_OPT_NO_PRESERVE + " to override."); } else if (inputFile.equals(outputFile)) { fatal("The input file cannot be the same as the output file."); } else { // Create a file to write the output document try { outputFile.createNewFile(); output = new FileOutputStream(outputFile); } catch (IOException ex) { bailOnException(ex); } } } else { output = System.out; } if (format == DocumentFormat.BEL) { convertBelToXbel(inputFile, output); } else if (format == DocumentFormat.XBEL) { convertXbelToBel(inputFile, output); } } private DocumentFormat determineType(final File inputFile) { DocumentFormat format = null; if (hasOption("t")) { format = DocumentFormat.getFormatByLabel(getOptionValue("t")); } if (format == null) { final int index = inputFile.getName().lastIndexOf("."); if (index == -1) { return null; } final String extension = inputFile.getName().substring(index); if (extension.equals(BEL_EXTENSION)) { format = DocumentFormat.BEL; } else if (extension.equals(XBEL_EXTENSION)) { format = DocumentFormat.XBEL; } } return format; } private void convertBelToXbel(final File inputFile, final OutputStream output) { try { final String belText = FileUtils.readFileToString(inputFile, UTF_8); final BELParseResults result = BELParser.parse(belText); // Report any BEL parser warnings or errors if verbose mode is enabled if (verbose) { final List<BELParseWarningException> warnings = result.getSyntaxWarnings(); final List<BELParseErrorException> errors = result.getSyntaxErrors(); final String inputFilePath = inputFile.getAbsolutePath(); if (hasItems(warnings)) { final int numWarnings = warnings.size(); reportable.output(format("%d BEL parser warning%s:", numWarnings, (numWarnings == 1 ? "" : "s"))); int i = 0; for (BELParseWarningException warning : warnings) { final ValidationError err = new ValidationError( inputFilePath, warning.getMessage(), warning.getLine(), warning.getCharacter()); reportable.output(format("%5d. %s", ++i, err.getUserFacingMessage())); } } if (hasItems(errors)) { final int numErrors = errors.size(); reportable.output(format("%d BEL parser error%s:", numErrors, (numErrors == 1 ? "" : "s"))); int i = 0; for (BELParseErrorException error : errors) { final ValidationError err = new ValidationError( inputFilePath, error.getMessage(), error.getLine(), error.getCharacter()); reportable.output(format("%5d. %s", ++i, err.getUserFacingMessage())); } } } final Document bel = new BELDocumentConverter().convert(result.getDocument()); final org.openbel.framework.common.xbel.converters.DocumentConverter converter = new org.openbel.framework.common.xbel.converters.DocumentConverter(); new XBELConverter().marshal(converter.convert(bel), output); } catch (JAXBException ex) { close(output); bailOnException(ex); } catch (IOException ex) { close(output); bailOnException(ex); } catch (RuntimeException ex) { close(output); bailOnException(ex); } } private void convertXbelToBel(final File inputFile, final OutputStream output) { try { final boolean useShortForm = false; final BELScriptExporter exporter = new BELScriptExporter(); exporter.setUseShortForm(useShortForm); exporter.export(new XBELConverterServiceImpl().toCommon(inputFile), output); } catch (SAXException ex) { close(output); bailOnException(ex); } catch (URISyntaxException ex) { close(output); bailOnException(ex); } catch (IOException ex) { close(output); bailOnException(ex); } catch (JAXBException ex) { close(output); bailOnException(ex); } catch (RuntimeException ex) { close(output); bailOnException(ex); } } private void close(final OutputStream os) { if (os != System.out && os != System.err) { try { os.close(); } catch (IOException ex) {} } } private void bailOnException(final Exception e) { final Throwable cause = ExceptionUtils.getRootCause(e); reportable.error("Unable to run " + DOCUMENT_CONVERTER_NAME); final String reason = (cause == null ? e.getMessage() : cause.getMessage()); reportable.error("Reason: " + (reason != null ? reason : "none. Specify --debug for more information.")); if (debug) { e.printStackTrace(reportable.errorStream()); } bail(GENERAL_FAILURE); } @Override public String getApplicationName() { return DOCUMENT_CONVERTER_NAME; } @Override public String getApplicationShortName() { return DOCUMENT_CONVERTER_NAME; } @Override public String getApplicationDescription() { return DOCUMENT_CONVERTER_DESC; } @Override public String getUsage() { final StringBuilder bldr = new StringBuilder(); bldr.append(" <BEL or XBEL document name>") .append(USAGE_OUTPUT_FILE) .append(USAGE_TYPE) .append(" [-h | --help] [-v | --verbose]"); return bldr.toString(); } @Override public List<Option> getCommandLineOptions() { List<Option> options = new LinkedList<Option>(); // -f/--force/--no-preserve used with -o/--output-file to force the // file to be overwritten if it already exists final String noPreserveHelp = "Optional. If specified, if a file with the same name as the output file " + "already exists then it will be overwritten.\n" + "The default is always to preserve the existing file and " + "generate a warning."; options.add(new Option("f", "force", false, "Identical to --no-preserve.")); options.add(new Option(null, LONG_OPT_NO_PRESERVE, false, noPreserveHelp)); // -o/--output-file used to set the file to write the new BEL or XBEL document final String outputFileHelp = "Optional. If present, it indicates the name of the output file " + "to write the converted BEL or XBEL document to."; options.add(new Option("o", "output-file", true, outputFileHelp)); // -t/--type used to specify whether the input file is BEL (or XBEL), and // therefore whether the output file will be XBEL (or BEL). final String typeHelp = "Specifies the format of the input file. By default, " + getApplicationShortName() + " infers the format of the document by " + "its extension (" + BEL_EXTENSION + " or " + XBEL_EXTENSION + ").\n" + "If BEL then the input document is expected to be in BEL format and the " + "output document will be in XBEL format. If XBEL then the input document " + "is expected to be in XBEL format and the output document will be in BEL format."; options.add(new Option("t", "type", true, typeHelp)); // The base class CommandLineApplication will add the --debug, -h/--help, // and -v/--verbose options. return options; } public static enum DocumentFormat { BEL, XBEL; public static DocumentFormat getFormatByLabel(final String label) { if (label.equals("bel") || label.equals("BEL")) { return BEL; } else if (label.equals("xbel") || label.equals("XBEL")) { return XBEL; } else { return null; } } } /* * Override the printUsage() methods to avoid using stdout to * output anything other than a converted BEL or XBEL document. * * @see org.openbel.framework.clf.CommandLineApplication#printUsage() */ @Override public void printUsage() { super.printUsage(reportable.outputStream()); } @Override public void printUsage(final OutputStream os) { super.printUsage(reportable.outputStream()); } }