/**
* 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.Runtime.getRuntime;
import static java.lang.System.currentTimeMillis;
import static java.lang.System.err;
import static org.openbel.framework.common.BELUtilities.asPath;
import static org.openbel.framework.common.BELUtilities.timeFormat;
import static org.openbel.framework.common.Strings.DIRECTORY_CREATION_FAILED;
import static org.openbel.framework.common.Strings.SYSTEM_CONFIG_PATH;
import static org.openbel.framework.common.Strings.TIME_HELP;
import static org.openbel.framework.common.Strings.WARNINGS_AS_ERRORS;
import static org.openbel.framework.common.cfg.SystemConfiguration.getSystemConfiguration;
import static org.openbel.framework.common.enums.ExitCode.GENERAL_FAILURE;
import static org.openbel.framework.core.StandardOptions.ARG_SYSCFG;
import static org.openbel.framework.core.StandardOptions.LONG_OPT_DEBUG;
import static org.openbel.framework.core.StandardOptions.LONG_OPT_SYSCFG;
import static org.openbel.framework.core.StandardOptions.LONG_OPT_TIME;
import static org.openbel.framework.core.StandardOptions.LONG_OPT_VERBOSE;
import static org.openbel.framework.core.StandardOptions.SHRT_OPT_SYSCFG;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.JAXBException;
import org.apache.commons.cli.Option;
import org.openbel.framework.common.BELRuntimeException;
import org.openbel.framework.common.Reportable;
import org.openbel.framework.common.SimpleOutput;
import org.openbel.framework.common.cfg.RuntimeConfiguration;
import org.openbel.framework.common.cfg.SystemConfiguration;
import org.openbel.framework.common.enums.ExitCode;
import org.openbel.framework.core.CommandLineApplication;
import org.openbel.framework.core.XBELConverterService;
import org.openbel.framework.core.XBELConverterServiceImpl;
import org.openbel.framework.core.XBELValidatorService;
import org.openbel.framework.core.XBELValidatorServiceImpl;
import org.xml.sax.SAXException;
/**
* Base class for BEL phase compilers.
*/
public abstract class PhaseApplication extends CommandLineApplication {
private final static String INDENT = " ";
/**
* The artifact path.
*/
protected File artifactPath;
/**
* The output directory.
*/
protected File outputDirectory;
/**
* Time command-line argument short option, {@value #SHORT_OPT_TIME}.
*
* @deprecated In favor of {@link #LONG_OPT_TIME} only
*/
@Deprecated
protected final static String SHORT_OPT_TIME = "t";
/**
* Pedantic command-line argument long option, {@value #LONG_OPT_PEDANTIC}.
*/
protected final static String LONG_OPT_PEDANTIC = "pedantic";
private long starttime;
private long endtime;
/**
* @param args
* @param configs
*/
public PhaseApplication(String[] args) {
super(args);
final SimpleOutput reportable = new SimpleOutput();
reportable.setErrorStream(System.err);
reportable.setOutputStream(System.out);
setReportable(reportable);
setOptions(getPhaseConfiguration());
// Initialize the system configuration either through the
// command-line or system defaults.
initializeSystemConfiguration();
SystemConfiguration syscfg = getSystemConfiguration();
outputDirectory = syscfg.getWorkingDirectory();
}
public void start() {
starttime = currentTimeMillis();
}
public void stop() {
endtime = currentTimeMillis();
final StringBuilder bldr = new StringBuilder();
bldr.append(INDENT);
bldr.append("=== Phase Complete === ");
markTime(bldr, starttime, endtime);
output(bldr.toString());
}
/**
* Fail with a {@link ExitCode#GENERAL_FAILURE general failure}, printing
* the application's usage.
*/
public void failUsage() {
printUsage();
bail(GENERAL_FAILURE);
}
/**
* Returns a list of phase-agnostic command-line options.
*
* @return List of options
*/
@Override
public List<Option> getCommandLineOptions() {
final List<Option> ret = new LinkedList<Option>();
String help;
help = TIME_HELP;
ret.add(new Option(SHORT_OPT_TIME, LONG_OPT_TIME, false, help));
help = WARNINGS_AS_ERRORS;
ret.add(new Option(null, LONG_OPT_PEDANTIC, false, help));
help = SYSTEM_CONFIG_PATH;
Option o = new Option(SHRT_OPT_SYSCFG, LONG_OPT_SYSCFG, true, help);
o.setArgName(ARG_SYSCFG);
ret.add(o);
return ret;
}
/**
* Returns the phase options.
*
* @return {@link RuntimeConfiguration}
*/
public abstract RuntimeConfiguration getPhaseConfiguration();
/**
* Returns {@code true} if the command-line arguments are valid for this
* phase, {@code false} otherwise.
*
* @param args Command-line arguments
* @return boolean
*/
public abstract boolean validCommandLine();
/**
* Returns {@link #getPhaseConfiguration()}
* {@link RuntimeConfiguration#isTime()}, a delegate method for convenience.
*
* @return boolean
*/
protected boolean withTime() {
return getPhaseConfiguration().isTime();
}
/**
* Returns {@link #getPhaseConfiguration()}
* {@link RuntimeConfiguration#isDebug()}, a delegate method for
* convenience.
*
* @return boolean
*/
protected boolean withDebug() {
return getPhaseConfiguration().isDebug();
}
/**
* Returns {@link #getPhaseConfiguration()}
* {@link RuntimeConfiguration#isVerbose()}, a delegate method for
* convenience.
*
* @return boolean
*/
protected boolean withVerbose() {
return getPhaseConfiguration().isVerbose();
}
/**
* Returns {@link #getPhaseConfiguration()}
* {@link RuntimeConfiguration#isWarningsAsErrors()}, a delegate method for
* convenience.
*
* @return boolean
*/
protected boolean withPedantic() {
return getPhaseConfiguration().isWarningsAsErrors();
}
/**
* Sets the phase-agnostic runtime configuration.
*/
private void setOptions(RuntimeConfiguration rc) {
if (hasOption(SHORT_OPT_TIME)) {
rc.setTime(true);
}
if (hasOption(LONG_OPT_DEBUG)) {
rc.setDebug(true);
}
if (hasOption(LONG_OPT_VERBOSE)) {
rc.setVerbose(true);
}
if (hasOption(LONG_OPT_PEDANTIC)) {
rc.setWarningsAsErrors(true);
}
}
/**
* Sends {@code "=== <header> ==="} to {@link Reportable#output(String...)
* output}
*
* @param header Non-null header
* @param phase Non-null phase
* @param stage Non-null stage
*/
protected void beginStage(final String header, final String stage,
final String numStages) {
boolean print = getPhaseConfiguration().isVerbose();
if (print) {
StringBuilder bldr = new StringBuilder();
bldr.append(INDENT);
bldr.append("Stage ");
bldr.append(stage);
bldr.append(" of ");
bldr.append(numStages);
bldr.append(": ");
bldr.append(header);
reportable.output(bldr.toString());
}
}
/**
* Prints phase output.
*
* @param output Output
*/
protected void phaseOutput(final String output) {
output(INDENT.concat(output));
}
/**
* Prints stage output with two levels of indentation.
*
* @param output Output
*/
protected void stageOutput(final String output) {
boolean print = getPhaseConfiguration().isVerbose();
if (print) {
output(INDENT + INDENT + output);
}
}
/**
* Prints stage error with two levels of indentation and {@code "[ERROR]: "}
* prefix.
*
* @param error Error
*/
protected void stageError(final String error) {
error(INDENT + INDENT + "[ERROR]: " + error);
}
/**
* Prints stage warning with two levels of indentation and
* {@code "[WARNING]: "} prefix.
*
* @param warning Warning
*/
protected void stageWarning(final String warning) {
warning(INDENT + INDENT + "[WARNING]: " + warning);
}
/**
* Creates the directory artifact, failing if the directory couldn't be
* created.
*
* @param outputDirectory Phase output directory
* @param artifact Directory artifact to be created
*/
protected final File createDirectoryArtifact(final File outputDirectory,
final String artifact) {
// Construct artifact path based on output directory
String path = asPath(outputDirectory.getAbsolutePath(), artifact);
final File ret = new File(path);
if (!ret.isDirectory()) {
// Fail if the directory artifact couldn't be created
if (!ret.mkdir()) {
error(DIRECTORY_CREATION_FAILED + ret);
bail(ExitCode.BAD_OUTPUT_DIRECTORY);
}
}
return ret;
}
/**
* Invokes {@link Reportable#error(String...)} with {@code errors}.
*
* @param errors Error strings
* @see org.openbel.framework.common.Reportable#error(java.lang.String[])
*/
public void error(String... errors) {
reportable.error(errors);
}
/**
* Invokes {@link Reportable#warning(String...)} with {@code warnings}.
*
* @param warnings Warning strings
* @see org.openbel.framework.common.Reportable#warning(java.lang.String[])
*/
public void warning(String... warnings) {
reportable.warning(warnings);
}
/**
* Invokes {@link Reportable#output(String...)} with {@code outputs}.
*
* @param outputs Output strings
* @see org.openbel.framework.common.Reportable#output(java.lang.String[])
*/
public void output(String... outputs) {
reportable.output(outputs);
}
/**
* Appends {@code (#.### seconds)} to the builder, if
* {@link RuntimeConfiguration#isTime() runtime configuration timing} is
* set.
*
* @param b Non-null builder
* @param t1 Time at {@code t1}
* @param t2 Time at {@code t2}
*/
protected void markTime(final StringBuilder b, long t1, long t2) {
boolean time = getPhaseConfiguration().isTime();
if (time) {
String seconds = timeFormat((t2 - t1));
b.append("(");
b.append(seconds);
b.append(" seconds) ");
}
}
/**
* Marks the end of a stage by appending a newline to the builder.
*
* @param b Non-null builder
*/
protected void markEndStage(final StringBuilder b) {
b.append("\n");
}
/**
* Reports {@link ExitCode#toString()} to error, and invokes
* {@link #bail(ExitCode)} with the provided exit code.
*
* @param e Exit code
*/
protected void exit(ExitCode e) {
reportable.error(e.toString());
bail(e);
}
/**
* Directs phase application lifecycle.
*
* @param phase Phase application
*/
protected static void harness(PhaseApplication phase) {
try {
phase.start();
phase.stop();
phase.end();
} catch (BELRuntimeException bre) {
err.println(bre.getUserFacingMessage());
systemExit(bre.getExitCode());
} catch (OutOfMemoryError oom) {
err.println();
oom.printStackTrace();
long upperlimit = getRuntime().maxMemory();
double ulMB = upperlimit * 9.53674316e-7;
final NumberFormat fmt = new DecimalFormat("#0");
String allocation = fmt.format(ulMB);
err.println("\n(current allocation is " + allocation + " MB)");
systemExit(ExitCode.OOM_ERROR);
}
}
protected XBELValidatorService createValidator() {
try {
return new XBELValidatorServiceImpl();
} catch (SAXException e) {
fatal("SAX exception creating validator service");
} catch (MalformedURLException e) {
fatal("Malformed URL exception creating validator service");
} catch (IOException e) {
fatal("IO exception creating validator service");
} catch (URISyntaxException e) {
fatal("URL syntax exception creating validator service");
}
return null;
}
protected XBELConverterService createConverter() {
try {
return new XBELConverterServiceImpl();
} catch (SAXException e) {
fatal("SAX exception creating converter service");
} catch (MalformedURLException e) {
fatal("Malformed URL excpetion creating converter service");
} catch (URISyntaxException e) {
fatal("URI Syntax excpetion creating converter service");
} catch (IOException e) {
fatal("IO exception creating converter service");
} catch (JAXBException e) {
fatal("JAXB exception creating converter service");
}
return null;
}
}