/** * 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 java.lang.System.currentTimeMillis; import static java.util.Arrays.asList; import static org.openbel.framework.common.BELUtilities.asPath; import static org.openbel.framework.common.BELUtilities.isBELScript; import static org.openbel.framework.common.Strings.BAD_INPUT_PATH; import static org.openbel.framework.common.Strings.INPUT_FILE_UNREADABLE; import static org.openbel.framework.common.Strings.NO_DOCUMENT_FILES; import static org.openbel.framework.common.Strings.NO_XBEL_INPUT; import static org.openbel.framework.common.Strings.PHASE1_STAGE1_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE2_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE3_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE4_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE5_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE6_HDR; import static org.openbel.framework.common.Strings.PHASE1_STAGE7_HDR; import static org.openbel.framework.common.Strings.PHASE_1_DISABLE_NESTED_STMTS; import static org.openbel.framework.common.Strings.PHASE_1_INPUT_FILE; import static org.openbel.framework.common.Strings.PHASE_1_INPUT_PATH; import static org.openbel.framework.common.Strings.PHASE_1_NO_SEMANTICS; import static org.openbel.framework.common.Strings.PHASE_1_NO_SYNTAX; import static org.openbel.framework.common.Strings.SEMANTIC_CHECKS_DISABLED; import static org.openbel.framework.common.Strings.SYMBOL_CHECKS_DISABLED; import static org.openbel.framework.common.enums.ExitCode.NAMESPACE_RESOLUTION_FAILURE; import static org.openbel.framework.common.enums.ExitCode.NO_VALID_DOCUMENTS; import static org.openbel.framework.common.enums.ExitCode.PROTO_NETWORK_SAVE_FAILURE; import static org.openbel.framework.common.enums.ExitCode.SEMANTIC_VERIFICATION_FAILURE; import static org.openbel.framework.common.enums.ExitCode.STATEMENT_EXPANSION_FAILURE; import static org.openbel.framework.common.enums.ExitCode.SYMBOL_VERIFICATION_FAILURE; import static org.openbel.framework.common.enums.ExitCode.VALIDATION_FAILURE; import static org.openbel.framework.tools.PhaseOneOptions.phaseOneOptions; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.openbel.framework.common.model.Document; import org.openbel.framework.common.model.Namespace; import org.openbel.framework.common.protonetwork.model.ProtoNetwork; import org.openbel.framework.common.protonetwork.model.ProtoNetworkError; import org.openbel.framework.common.util.BELPathFilters.BELFileFilter; import org.openbel.framework.common.util.BELPathFilters.XBELFileFilter; import org.openbel.framework.compiler.DefaultPhaseOne; import org.openbel.framework.compiler.DefaultPhaseOne.Stage1Output; import org.openbel.framework.compiler.PhaseOneImpl; 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.XBELConverterService; import org.openbel.framework.core.XBELValidatorService; 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.SemanticFailure; import org.openbel.framework.core.compiler.SemanticService; import org.openbel.framework.core.compiler.SemanticServiceImpl; import org.openbel.framework.core.compiler.SymbolWarning; import org.openbel.framework.core.compiler.ValidationError; import org.openbel.framework.core.compiler.expansion.ExpansionService; import org.openbel.framework.core.compiler.expansion.ExpansionServiceImpl; 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.indexer.IndexingFailure; 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.openbel.framework.core.protocol.ResourceDownloadError; import org.openbel.framework.core.protonetwork.ProtoNetworkBuilder; import org.openbel.framework.core.protonetwork.ProtoNetworkService; import org.openbel.framework.core.protonetwork.ProtoNetworkServiceImpl; import org.openbel.framework.core.protonetwork.TextProtoNetworkExternalizer; /** * BEL phase one compiler. */ public final class PhaseOneApplication extends PhaseApplication { private final DefaultPhaseOne p1; /** Phase one artifact directory. */ public final static String DIR_ARTIFACT = "phaseI"; private final static String SHORT_OPT_IN_PATH = "p"; private final static String LONG_OPT_IN_PATH = "input-path"; private final static String INFILE_SHORT_OPT = "f"; private final static String INFILE_LONG_OPT = "file"; private final static String NO_NS_LONG_OPT = "no-nested-statements"; private final static String NO_SEMANTIC_CHECK = "no-semantic-check"; private final static String NO_SYNTAX_CHECK = "no-syntax-check"; private final static String NUM_PHASES = "7"; /** * Phase one application constructor. * * @param args Command-line arguments */ public PhaseOneApplication(String[] args) { super(args); final XBELValidatorService validator = createValidator(); final XBELConverterService converter = createConverter(); final BELValidatorService belValidator = new BELValidatorServiceImpl(); final BELConverterService belConverter = new BELConverterServiceImpl(); final NamespaceIndexerService nsindexer = new NamespaceIndexerServiceImpl(); final CacheableResourceService cache = new DefaultCacheableResourceService(); final CacheLookupService cacheLookup = new DefaultCacheLookupService(); final NamespaceService nsService = new DefaultNamespaceService( cache, cacheLookup, nsindexer); final ProtoNetworkService protonetService = new ProtoNetworkServiceImpl(); final SemanticService semantics = new SemanticServiceImpl(nsService); final ExpansionService expansion = new ExpansionServiceImpl(); final AnnotationService annotationService = new DefaultAnnotationService(); final AnnotationDefinitionService annotationDefinitionService = new DefaultAnnotationDefinitionService(cache, cacheLookup); p1 = new PhaseOneImpl(validator, converter, belValidator, belConverter, nsService, semantics, expansion, protonetService, annotationService, annotationDefinitionService); } /** * {@inheritDoc} */ @Override public void start() { super.start(); if (hasOption(SHORT_OPT_IN_PATH)) { processInputDirectories(); } else if (hasOption(INFILE_SHORT_OPT)) { processFiles(); } else { error(NO_XBEL_INPUT); failUsage(); } } /** * Process documents in the input directories. */ private void processInputDirectories() { String[] inputdirs = getOptionValues(SHORT_OPT_IN_PATH); final List<File> files = new ArrayList<File>(); final XBELFileFilter xbelFileFilter = new XBELFileFilter(); final BELFileFilter belFileFilter = new BELFileFilter(); for (String inputdir : inputdirs) { final File workingPath = new File(inputdir); if (!workingPath.canRead()) { error(BAD_INPUT_PATH + inputdir); failUsage(); } final File[] xbels = workingPath.listFiles(xbelFileFilter); if (xbels != null && xbels.length != 0) { files.addAll(asList(xbels)); } final File[] bels = workingPath.listFiles(belFileFilter); if (bels != null && bels.length != 0) { files.addAll(asList(bels)); } } // Fail if none of the input directories contain BEL/XBEL files if (files.size() == 0) { error(NO_DOCUMENT_FILES); failUsage(); } // Create the directory artifact or fail artifactPath = createDirectoryArtifact(outputDirectory, DIR_ARTIFACT); processFiles(files.toArray(new File[0])); } /** * Process file arguments. */ private void processFiles() { String[] fileArgs = getOptionValues(INFILE_SHORT_OPT); final List<File> files = new ArrayList<File>(fileArgs.length); for (final String fileArg : fileArgs) { File file = new File(fileArg); if (!file.canRead()) { error(INPUT_FILE_UNREADABLE + file); } else { files.add(file); } } if (files.size() == 0) { error(NO_DOCUMENT_FILES); failUsage(); } // Create the directory artifact or fail artifactPath = createDirectoryArtifact(outputDirectory, DIR_ARTIFACT); processFiles(files.toArray(new File[0])); } /** * Starts phase one compilation from {@link File XBEL files}. * * @param files the {@link File XBEL files} to compile */ private void processFiles(File[] files) { phaseOutput(format("=== %s ===", getApplicationName())); phaseOutput(format("Compiling %d BEL Document(s)", files.length)); boolean pedantic = withPedantic(); boolean verbose = withVerbose(); int validated = 0; for (int i = 1; i <= files.length; i++) { File file = files[i - 1]; if (verbose) { phaseOutput("Compiling " + i + " of " + files.length + " BEL Document(s)"); } // validate document Document document = stage1(file); if (document == null) { if (pedantic) { bail(VALIDATION_FAILURE); } continue; } validated++; // run stages 2 - 7 runCommonStages(pedantic, document); } if (validated == 0) { bail(NO_VALID_DOCUMENTS); } } /** * Runs stages 2 - 7 which remain static regardless of the BEL document * input. * * @param pedantic the flag for pedantic-ness * @param document the {@link Document BEL document} */ private void runCommonStages(boolean pedantic, Document document) { if (!stage2(document)) { if (pedantic) { bail(NAMESPACE_RESOLUTION_FAILURE); } } if (!stage3(document)) { if (pedantic) { bail(SYMBOL_VERIFICATION_FAILURE); } } if (!stage4(document)) { if (pedantic) { bail(SEMANTIC_VERIFICATION_FAILURE); } } ProtoNetwork pn = stage5(document); if (!stage6(document, pn)) { if (pedantic) { bail(STATEMENT_EXPANSION_FAILURE); } } if (!stage7(pn, document)) { if (pedantic) { bail(PROTO_NETWORK_SAVE_FAILURE); } } } /** * Stage one validation of the file, returning the converted document or * {@code null}. * * @param file XBEL file * @return Document */ private Document stage1(final File file) { beginStage(PHASE1_STAGE1_HDR, "1", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); stageOutput("Validating " + file); long t1 = currentTimeMillis(); final Stage1Output output; if (isBELScript(file)) { output = p1.stage1BELValidation(file); } else { output = p1.stage1XBELValidation(file); } if (output.hasValidationErrors()) { for (final ValidationError error : output.getValidationErrors()) { stageError(error.getUserFacingMessage()); } return null; } if (output.hasConversionError()) { stageError(output.getConversionError().getUserFacingMessage()); return null; } if (output.getSymbolWarning() != null) { stageWarning(output.getSymbolWarning().getUserFacingMessage()); } long t2 = currentTimeMillis(); markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); return output.getDocument(); } /** * Stage two resolution of document namespaces. * * @param document BEL common document * @return boolean true if success, false otherwise */ private boolean stage2(final Document document) { beginStage(PHASE1_STAGE2_HDR, "2", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); Collection<Namespace> namespaces = document.getNamespaceMap().values(); final int docNSCount = namespaces.size(); bldr.append("Compiling "); bldr.append(docNSCount); bldr.append(" namespace"); if (docNSCount > 1) { bldr.append("s"); } bldr.append(" for "); bldr.append(document.getName()); stageOutput(bldr.toString()); boolean success = true; long t1 = currentTimeMillis(); try { p1.stage2NamespaceCompilation(document); } catch (ResourceDownloadError e) { success = false; stageError(e.getUserFacingMessage()); } catch (IndexingFailure e) { success = false; stageError(e.getUserFacingMessage()); } long t2 = currentTimeMillis(); bldr.setLength(0); markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); return success; } /** * Stage three symbol verification of the document. * * @param document BEL common document * @return boolean true if success, false otherwise */ private boolean stage3(final Document document) { beginStage(PHASE1_STAGE3_HDR, "3", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); if (hasOption(NO_SYNTAX_CHECK)) { bldr.append(SYMBOL_CHECKS_DISABLED); markEndStage(bldr); stageOutput(bldr.toString()); return true; } bldr.append("Verifying symbols in "); bldr.append(document.getName()); stageOutput(bldr.toString()); boolean warnings = false; long t1 = currentTimeMillis(); try { p1.stage3SymbolVerification(document); } catch (SymbolWarning e) { warnings = true; String resname = e.getName(); if (resname == null) { e.setName(document.getName()); } else { e.setName(resname + " (" + document.getName() + ")"); } stageWarning(e.getUserFacingMessage()); } catch (IndexingFailure e) { stageError("Failed to open namespace index file for symbol " + "verification."); } catch (ResourceDownloadError e) { stageError("Failed to resolve namespace during symbol " + "verification."); } long t2 = currentTimeMillis(); bldr.setLength(0); if (warnings) { bldr.append("Symbol verification resulted in warnings in "); bldr.append(document.getName()); bldr.append("\n"); } markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); if (warnings) { return false; } return true; } /** * Stage four semantic verification of the document. * * @param document BEL common document * @return boolean true if success, false otherwise */ private boolean stage4(final Document document) { beginStage(PHASE1_STAGE4_HDR, "4", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); if (hasOption(NO_SEMANTIC_CHECK)) { bldr.append(SEMANTIC_CHECKS_DISABLED); markEndStage(bldr); stageOutput(bldr.toString()); return true; } bldr.append("Verifying semantics in "); bldr.append(document.getName()); stageOutput(bldr.toString()); boolean warnings = false; long t1 = currentTimeMillis(); try { p1.stage4SemanticVerification(document); } catch (SemanticFailure sf) { warnings = true; String resname = sf.getName(); if (resname == null) { sf.setName(document.getName()); } else { sf.setName(resname + " (" + document.getName() + ")"); } stageWarning(sf.getUserFacingMessage()); } catch (IndexingFailure e) { stageError("Failed to process namespace index files for semantic" + " verification."); } long t2 = currentTimeMillis(); bldr.setLength(0); if (warnings) { bldr.append("Semantic verification resulted in warnings in "); bldr.append(document.getName()); bldr.append("\n"); } markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); if (warnings) { return false; } return true; } /** * Stage five build of proto-network. * * @param document BEL common document */ private ProtoNetwork stage5(final Document document) { beginStage(PHASE1_STAGE5_HDR, "5", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); bldr.append("Building proto-network for "); bldr.append(document.getName()); stageOutput(bldr.toString()); long t1 = currentTimeMillis(); ProtoNetwork ret = p1.stage5Building(document); long t2 = currentTimeMillis(); bldr.setLength(0); markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); return ret; } /** * Stage six expansion of the document. * * @param doc the original {@link Document document} needed as a dependency * when using {@link ProtoNetworkBuilder proto network builder} * @param pn the {@link ProtoNetwork proto network} to expand into */ public boolean stage6(final Document doc, final ProtoNetwork pn) { beginStage(PHASE1_STAGE6_HDR, "6", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); boolean stmtSuccess = false, termSuccess = false; bldr.append("Expanding statements and terms"); stageOutput(bldr.toString()); long t1 = currentTimeMillis(); boolean stmtExpand = !hasOption(NO_NS_LONG_OPT); p1.stage6Expansion(doc, pn, stmtExpand); termSuccess = true; stmtSuccess = true; long t2 = currentTimeMillis(); bldr.setLength(0); markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); return stmtSuccess && termSuccess; } /** * Stage seven saving of proto-network. * * @param pn Proto-network * @param doc {@link Document}, the document * @return boolean true if success, false otherwise */ private boolean stage7(final ProtoNetwork pn, final Document doc) { beginStage(PHASE1_STAGE7_HDR, "7", NUM_PHASES); final StringBuilder bldr = new StringBuilder(); bldr.append("Saving proto-network for "); bldr.append(doc.getName()); stageOutput(bldr.toString()); long t1 = currentTimeMillis(); String dir = artifactPath.getAbsolutePath(); final String path = asPath(dir, nextUniqueFolderName()); File pnpathname = new File(path); boolean success = true; try { p1.stage7Saving(pn, pnpathname); if (withDebug()) { TextProtoNetworkExternalizer textExternalizer = new TextProtoNetworkExternalizer(); textExternalizer.writeProtoNetwork(pn, pnpathname.getAbsolutePath()); } } catch (ProtoNetworkError e) { success = false; error("failed to save proto-network"); e.printStackTrace(); } long t2 = currentTimeMillis(); bldr.setLength(0); markTime(bldr, t1, t2); markEndStage(bldr); stageOutput(bldr.toString()); return success; } private static int unique = 1; private static String nextUniqueFolderName() { return Integer.toString(unique++); } /** * {@inheritDoc} */ @Override public PhaseOneOptions getPhaseConfiguration() { return phaseOneOptions(); } /** * {@inheritDoc} */ @Override public boolean validCommandLine() { final CommandLineParser parser = new AntelopeParser(); List<Option> myOpts = getCommandLineOptions(); final Options options = new Options(); for (final Option option : myOpts) { options.addOption(option); } CommandLine parse; try { parse = parser.parse(options, getCommandLineArguments(), false); } catch (ParseException e) { return false; } // Verify one form of valid input is present. if (parse.hasOption(SHORT_OPT_IN_PATH)) { return true; } if (parse.hasOption(INFILE_SHORT_OPT)) { return true; } return false; } /** * {@inheritDoc} */ @Override public List<Option> getCommandLineOptions() { List<Option> options = super.getCommandLineOptions(); String help; Option o; help = PHASE_1_INPUT_PATH; o = new Option(SHORT_OPT_IN_PATH, LONG_OPT_IN_PATH, true, help); o.setArgName("path"); options.add(o); help = PHASE_1_INPUT_FILE; o = new Option(INFILE_SHORT_OPT, INFILE_LONG_OPT, true, help); o.setArgName("filename"); options.add(o); help = PHASE_1_DISABLE_NESTED_STMTS; o = new Option(null, NO_NS_LONG_OPT, false, help); options.add(o); help = PHASE_1_NO_SEMANTICS; o = new Option(null, NO_SEMANTIC_CHECK, false, help); options.add(o); help = PHASE_1_NO_SYNTAX; o = new Option(null, NO_SYNTAX_CHECK, false, help); options.add(o); return options; } /** * Returns {@code "Phase I: Compiling proto-networks"}. * * @return String */ @Override public String getApplicationName() { return "Phase I: Compiling proto-networks"; } /** * Returns {@code "Phase I"}. * * @return String */ @Override public String getApplicationShortName() { return "Phase I"; } /** * Returns {@code "Compiles BEL documents into proto-networks."}. * * @return String */ @Override public String getApplicationDescription() { return "Compiles BEL Documents into proto-networks."; } /** * Returns phase one usage. * * @return String */ @Override public String getUsage() { StringBuilder bldr = new StringBuilder(); bldr.append("[OPTION]..."); bldr.append(" [-p <path>]..."); bldr.append(" [-f <filename>]..."); return bldr.toString(); } /** * Invokes {@link #harness(PhaseApplication)} for * {@link PhaseOneApplication}. * * @param args Command-line arguments */ public static void main(String[] args) { harness(new PhaseOneApplication(args)); } /** * {@inheritDoc} */ public static String getRequiredArguments() { return "[-p <path>...] [-f <filename>...]"; } }