/**
* 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 org.openbel.framework.common.BELUtilities.hasItems;
import static org.openbel.framework.common.BELUtilities.isBELDocument;
import static org.openbel.framework.common.BELUtilities.isBELScript;
import static org.openbel.framework.common.BELUtilities.isXBEL;
import static org.openbel.framework.common.BELUtilities.readable;
import static org.openbel.framework.common.Strings.*;
import static org.openbel.framework.core.StandardOptions.ARG_SYSCFG;
import static org.openbel.framework.core.StandardOptions.LONG_OPT_SYSCFG;
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.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.JAXBException;
import org.apache.commons.cli.Option;
import org.openbel.bel.model.BELParseErrorException;
import org.openbel.framework.common.AnnotationDefinitionResolutionException;
import org.openbel.framework.common.SimpleOutput;
import org.openbel.framework.common.Strings;
import org.openbel.framework.common.bel.parser.BELParseResults;
import org.openbel.framework.common.enums.ExitCode;
import org.openbel.framework.common.model.AnnotationDefinition;
import org.openbel.framework.common.model.Document;
import org.openbel.framework.core.BELConverterService;
import org.openbel.framework.core.BELConverterServiceImpl;
import org.openbel.framework.core.BELValidatorService;
import org.openbel.framework.core.BELValidatorServiceImpl;
import org.openbel.framework.core.CommandLineApplication;
import org.openbel.framework.core.StandardOptions;
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.openbel.framework.core.annotation.AnnotationDefinitionService;
import org.openbel.framework.core.annotation.AnnotationService;
import org.openbel.framework.core.annotation.DefaultAnnotationDefinitionService;
import org.openbel.framework.core.annotation.DefaultAnnotationService;
import org.openbel.framework.core.compiler.DefaultValidationService;
import org.openbel.framework.core.compiler.SemanticService;
import org.openbel.framework.core.compiler.SemanticServiceImpl;
import org.openbel.framework.core.compiler.ValidationError;
import org.openbel.framework.core.compiler.ValidationResult;
import org.openbel.framework.core.compiler.ValidationService;
import org.openbel.framework.core.df.cache.CacheLookupService;
import org.openbel.framework.core.df.cache.CacheableResourceService;
import org.openbel.framework.core.df.cache.DefaultCacheLookupService;
import org.openbel.framework.core.df.cache.DefaultCacheableResourceService;
import org.openbel.framework.core.namespace.DefaultNamespaceService;
import org.openbel.framework.core.namespace.NamespaceIndexerService;
import org.openbel.framework.core.namespace.NamespaceIndexerServiceImpl;
import org.openbel.framework.core.namespace.NamespaceService;
import org.xml.sax.SAXException;
/**
* BelCheck is a {@link CommandLineApplication} to validate the syntax and
* semantics of a BEL or XBEL document.
*
* @author Steve Ungerer
*/
public class BelCheck extends CommandLineApplication {
private static final String BEL_CHECK_APP_NAME = "BelCheck";
private static final String BEL_CHECK_DESC =
"Validates BEL and XBEL documents.";
private static final String PEDANTIC_OPTION = "pedantic";
private static final String PERMISSIVE_OPTION = "permissive";
private static final String QUIET_OPTION = "q";
private static final String SUMMARY_OPTION = "summary";
private final XBELValidatorService validator;
private final XBELConverterService converter;
private final BELConverterService belConverter;
private final BELValidatorService belValidator;
private final AnnotationDefinitionService annoDefService;
private final ValidationService validationService;
public BelCheck(String[] args) {
super(args);
final SimpleOutput reportable = new SimpleOutput();
reportable.setErrorStream(System.err);
reportable.setOutputStream(System.out);
setReportable(reportable);
printApplicationInfo("BEL Check Utility");
initializeSystemConfiguration();
validator = createValidator();
converter = createConverter();
belValidator = new BELValidatorServiceImpl();
belConverter = new BELConverterServiceImpl();
final CacheableResourceService cache =
new DefaultCacheableResourceService();
final CacheLookupService cacheLookup = new DefaultCacheLookupService();
annoDefService = new DefaultAnnotationDefinitionService(
cache, cacheLookup);
final NamespaceIndexerService nsindexer =
new NamespaceIndexerServiceImpl();
final NamespaceService nsService = new DefaultNamespaceService(
cache, cacheLookup, nsindexer);
final SemanticService semantics = new SemanticServiceImpl(nsService);
final AnnotationService annotationService =
new DefaultAnnotationService();
validationService = new DefaultValidationService(
nsService, semantics, annotationService);
boolean pedantic = hasOption(PEDANTIC_OPTION);
boolean permissive = hasOption(PERMISSIVE_OPTION);
boolean verbose = hasOption(StandardOptions.LONG_OPT_VERBOSE);
boolean quiet = hasOption(QUIET_OPTION);
boolean summary = hasOption(SUMMARY_OPTION);
if (pedantic && permissive) {
fatal(CHECK_PEDANTIC_PERMISSIVE_ERROR);
}
List<String> fileArgs = getExtraneousArguments();
if (!hasItems(fileArgs)) {
// print out the usage if no arguments were given
printUsage();
reportable.error("\n");
reportable.error(NO_DOCUMENT_FILES);
end();
}
if (fileArgs.size() > 1) {
fatal("Only a single document can be specified.");
}
String fileArg = fileArgs.get(0);
if (!isBELDocument(fileArg)) {
final String error = fileArg + " is not a BEL document.";
reportable.error(error);
bail(ExitCode.GENERAL_FAILURE);
return;
}
File file = new File(fileArg);
if (!readable(file)) {
final String error = Strings.INPUT_FILE_UNREADABLE + file;
reportable.error(error);
bail(ExitCode.GENERAL_FAILURE);
return;
}
try {
final String abspath = file.getAbsolutePath();
if (verbose) {
reportable.output("Validating BEL Document: " + abspath);
}
int numWarnings = 0;
int numErrors = 0;
final Document document;
if (isXBEL(file)) {
List<ValidationError> validationErrors = validator
.validateWithErrors(file);
// if validation errors exist then report and fail document
if (hasItems(validationErrors)) {
numErrors += validationErrors.size();
if (!quiet) {
reportValidationError(validationErrors);
}
if (summary) {
printSummary(fileArg, numWarnings, numErrors);
}
bail(ExitCode.VALIDATION_FAILURE);
return;
}
document = converter.toCommon(file);
} else if (isBELScript(file)) {
List<ValidationError> validationErrors =
new ArrayList<ValidationError>();
BELParseResults results = belValidator
.validateBELScript(file);
// if validation errors exist then report and fail document
if (hasItems(results.getSyntaxErrors())) {
for (BELParseErrorException syntaxError : results
.getSyntaxErrors()) {
validationErrors.add(new ValidationError(file
.getAbsolutePath(), syntaxError
.getMessage(),
syntaxError.getLine(), syntaxError
.getCharacter()));
}
numErrors += validationErrors.size();
if (!quiet) {
reportValidationError(validationErrors);
}
if (summary) {
printSummary(fileArg, numWarnings, numErrors);
}
bail(ExitCode.VALIDATION_FAILURE);
return;
}
document = belConverter.toCommon(results.getDocument());
} else {
// unreachable; checked with isBelDocument
fatal("Unsupported document type");
return;
}
int numAnnoDefErrors =
validateAnnotationDefinitions(document, quiet, permissive,
verbose);
if (permissive) {
numWarnings += numAnnoDefErrors;
} else {
numErrors += numAnnoDefErrors;
}
ValidationResult vr = validationService.validate(document);
for (String e : vr.getErrors()) {
if (permissive) {
numWarnings++;
if (!quiet) {
reportable.warning("VALIDATION WARNING: " + e);
}
} else {
numErrors++;
if (!quiet) {
reportable.error("VALIDATION ERROR: " + e);
}
}
}
for (String w : vr.getWarnings()) {
if (pedantic) {
numErrors++;
if (!quiet) {
reportable.error("VALIDATION ERROR: " + w);
}
} else {
numWarnings++;
if (!quiet) {
reportable.warning("VALIDATION WARNING: " + w);
}
}
}
if (summary) {
printSummary(fileArg, numWarnings, numErrors);
}
if (numErrors == 0) {
reportable.output(ALL_DOCUMENTS_PASSED_VALIDATION);
bail(ExitCode.SUCCESS);
} else {
bail(ExitCode.VALIDATION_FAILURE);
}
} catch (Exception e) {
reportable.error("Failed to import BEL Document.");
reportable.error("Reason: " + e.getMessage());
bail(ExitCode.GENERAL_FAILURE);
}
}
private void reportValidationError(List<ValidationError> validationErrors) {
if (hasItems(validationErrors)) {
for (ValidationError validationError : validationErrors) {
reportable.error(validationError.getUserFacingMessage());
}
}
}
private int validateAnnotationDefinitions(Document document, boolean quiet,
boolean permissive, boolean verbose) {
int adFailures = 0;
if (hasItems(document.getDefinitions())) {
for (final AnnotationDefinition annodef : document.getDefinitions()) {
String url = annodef.getURL();
if (url == null) {
continue;
}
try {
if (verbose) {
reportable.output("Resolving Annotation Definition: "
+ url);
}
AnnotationDefinition external =
annoDefService.resolveAnnotationDefinition(url);
annodef.setDescription(external.getDescription());
annodef.setEnums(external.getEnums());
annodef.setType(external.getType());
annodef.setURL(external.getURL());
annodef.setUsage(external.getUsage());
annodef.setValue(external.getValue());
} catch (AnnotationDefinitionResolutionException e) {
adFailures++;
if (!quiet) {
if (permissive) {
reportable.warning(e.getUserFacingMessage());
} else {
reportable.error(e.getUserFacingMessage());
}
}
}
}
}
return adFailures;
}
private void printSummary(String file, int numWarnings, int numErrors) {
reportable.output("---------------------------------------");
reportable.output("BelCheck results for " + file);
reportable.output(numWarnings + " Warnings");
reportable.output(numErrors + " Errors");
reportable.output("---------------------------------------");
}
/**
* {@inheritDoc}
*/
@Override
public String getApplicationName() {
return BEL_CHECK_APP_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public String getApplicationShortName() {
// Can't abbreviate 'BelCheck' much more
return getApplicationName();
}
/**
* {@inheritDoc}
*/
@Override
public String getApplicationDescription() {
return BEL_CHECK_DESC;
}
/**
* {@inheritDoc}
*/
@Override
public String getUsage() {
StringBuilder bldr = new StringBuilder();
bldr.append(" [FILE]");
return bldr.toString();
}
/**
* {@inheritDoc}
*/
@Override
public List<Option> getCommandLineOptions() {
final List<Option> ret = new LinkedList<Option>();
ret.add(new Option(null, PEDANTIC_OPTION, false, CHECK_PEDANTIC));
ret.add(new Option(null, PERMISSIVE_OPTION, false, CHECK_PERMISSIVE));
ret.add(new Option(QUIET_OPTION, "quiet", false, CHECK_QUIET));
ret.add(new Option(null, SUMMARY_OPTION, false, CHECK_SUMMARY));
Option o =
new Option(SHRT_OPT_SYSCFG, LONG_OPT_SYSCFG, true,
SYSTEM_CONFIG_PATH);
o.setArgName(ARG_SYSCFG);
ret.add(o);
return ret;
}
public static void main(String... args) {
BelCheck xi = new BelCheck(args);
xi.end();
}
private 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;
}
private 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;
}
}