/** * 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.core.compiler; import static java.lang.Integer.parseInt; import static java.lang.String.format; import static java.util.Collections.emptyList; import static org.openbel.framework.common.BELUtilities.hasItems; import static org.openbel.framework.common.BELUtilities.noItems; import static org.openbel.framework.common.enums.FunctionEnum.PROTEIN_ABUNDANCE; import static org.openbel.framework.common.enums.FunctionEnum.GENE_ABUNDANCE; import static org.openbel.framework.common.enums.FunctionEnum.RNA_ABUNDANCE; import static org.openbel.framework.common.enums.FunctionEnum.FUSION; import static org.openbel.framework.common.enums.FunctionEnum.PROTEIN_MODIFICATION; import static org.openbel.framework.common.enums.FunctionEnum.SUBSTITUTION; import static org.openbel.framework.common.enums.FunctionEnum.TRUNCATION; import static org.openbel.framework.common.Strings.*; import static org.openbel.framework.common.enums.AminoAcid.getAminoAcid; import static org.openbel.framework.common.enums.CovalentModification.getCovalentModification; import static org.openbel.framework.common.enums.ValueEncoding.getValueEncoding; import static org.openbel.framework.common.lang.Signature.WILDCARD_ENCODING; import static org.openbel.framework.common.lang.Signature.encode; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.openbel.framework.common.InvalidArgument; import org.openbel.framework.common.enums.FunctionEnum; import org.openbel.framework.common.enums.RelationshipType; import org.openbel.framework.common.enums.SemanticStatus; import org.openbel.framework.common.enums.ValueEncoding; import org.openbel.framework.common.lang.Function; import org.openbel.framework.common.lang.Signature; import org.openbel.framework.common.model.BELObject; import org.openbel.framework.common.model.Document; import org.openbel.framework.common.model.Parameter; import org.openbel.framework.common.model.Statement; import org.openbel.framework.common.model.StatementGroup; import org.openbel.framework.common.model.Term; import org.openbel.framework.common.model.Statement.Object; import org.openbel.framework.core.indexer.IndexingFailure; import org.openbel.framework.core.namespace.NamespaceService; import org.openbel.framework.core.namespace.NamespaceSyntaxWarning; /** * BEL semantic service implementation. */ public final class SemanticServiceImpl implements SemanticService { private final NamespaceService nsService; /** * Creates a semantic service implementation. * * @param nsService {@link NamespaceService}, the namespace service */ public SemanticServiceImpl(final NamespaceService nsService) { this.nsService = nsService; } /** * {@inheritDoc} */ @Override public void checkParameterizedTerm(final Term term) throws SemanticWarning { final FunctionEnum funcEnum = term.getFunctionEnum(); // Construct the signature final StringBuilder bldr = new StringBuilder(); bldr.append(funcEnum.getDisplayValue()); bldr.append("("); List<BELObject> functionArguments = term.getFunctionArguments(); if (hasItems(functionArguments)) { for (int i = 0; i < functionArguments.size(); i++) { final BELObject bo = functionArguments.get(i); String arg = null; if (bo instanceof Parameter) { arg = processParameter((Parameter) bo); } else if (bo instanceof Term) { arg = processTerm((Term) bo); if (arg == null) continue; } else { String type = bo.getClass().getName(); final String err = "unknown function argument " + type; throw new UnsupportedOperationException(err); } if (i != 0) bldr.append(","); bldr.append(arg); } } bldr.append(")"); bldr.append(funcEnum.getReturnType().getDisplayValue()); Signature sig = null; try { sig = new Signature(bldr.toString()); } catch (InvalidArgument e) { final String lf = term.toBELLongForm(); final String err = e.getMessage(); throw new SemanticWarning(lf, err); } final Function function = funcEnum.getFunction(); if (!function.validSignature(sig)) { final Map<Signature, SemanticStatus> map = function.getStatus(sig); final String lf = term.toBELLongForm(); final String err = format(SEMANTIC_TERM_FAILURE, lf); throw new SemanticWarning(null, err, sig, map); } } /** * {@inheritDoc} */ @Override public void checkTerm(final Term term) throws SemanticWarning { final FunctionEnum funcEnum = term.getFunctionEnum(); // Construct the signature final StringBuilder bldr = new StringBuilder(); bldr.append(funcEnum.getDisplayValue()); bldr.append("("); List<BELObject> functionArguments = term.getFunctionArguments(); if (hasItems(functionArguments)) { for (int i = 0; i < functionArguments.size(); i++) { final BELObject bo = functionArguments.get(i); String arg = null; if (bo instanceof Term) { arg = processTerm((Term) bo); if (arg == null) continue; } else { String type = bo.getClass().getName(); final String err = "unhandled function argument " + type; throw new UnsupportedOperationException(err); } if (i != 0) bldr.append(","); bldr.append(arg); } } bldr.append(")"); bldr.append(funcEnum.getReturnType().getDisplayValue()); Signature sig = null; try { sig = new Signature(bldr.toString()); } catch (InvalidArgument e) { final String lf = term.toBELLongForm(); final String err = e.getMessage(); throw new SemanticWarning(lf, err); } final Function function = funcEnum.getFunction(); if (!function.validSignature(sig)) { final Map<Signature, SemanticStatus> map = function.getStatus(sig); final String lf = term.toBELLongForm(); final String err = format(SEMANTIC_TERM_FAILURE, lf); throw new SemanticWarning(null, err, sig, map); } } /** * {@inheritDoc} */ @Override public void checkRelationship(final Statement statement) throws SemanticWarning { RelationshipType r = statement.getRelationshipType(); Object o = statement.getObject(); if (o != null) { // Relationship must be present. if (r == null) { final String err = SEMANTIC_STATEMENT_REQUIRES_RELATIONSHIP; final String name = statement.toBELShortForm(); throw new SemanticWarning(name, err, null); } } else { // Relationship must be absent. if (r != null) { final String err = SEMANTIC_STATEMENT_PROVIDES_RELATIONSHIP; final String name = statement.toBELShortForm(); throw new SemanticWarning(name, err, null); } } } /** * {@inheritDoc} */ @Override public void checkListUsage(final Statement statement, final Document document) throws SemanticWarning { Object object = statement.getObject(); if (object != null && !statement.hasNestedStatement() && object.getTerm().getFunctionEnum() == FunctionEnum.LIST && !statement.getRelationshipType().isListable()) { if (document != null) { pruneStatement(statement, document); } final String err = SEMANTIC_LIST_IMPROPER_CONTEXT; final String name = statement.toBELShortForm(); throw new SemanticWarning(name, err, null); } } /** * {@inheritDoc} */ @Override public void checkNested(final Statement statement) throws SemanticWarning { // Ignore non-nested statements if (!statement.hasNestedStatement()) { return; } final RelationshipType relationship = statement.getRelationshipType(); // No relationship? Ignore it, this is a different semantic check if (relationship == null) { return; } // Nested statement is causal, gold star for you if (relationship.isCausal()) { return; } // Try to provide context for where the warning is final StringBuilder bldr = new StringBuilder(); bldr.append("Nested statement with non-causal relationship "); bldr.append(relationship); final String err = SEMANTIC_NESTED_REQUIRE_CAUSAL; final String name = bldr.toString(); throw new SemanticWarning(name, err); } /** * {@inheritDoc} */ @Override public void checkMultiNested(final Statement stmt, final Document doc) throws SemanticWarning { // is the statement nested? if (!stmt.hasNestedStatement()) { // not nested return; } // does it contain more than one nested statement? Statement objstmt = stmt.getObject().getStatement(); if (objstmt.getObject().getStatement() == null) { // object term in nested statement return; } // Multi-nested statement, prune stmt then throw semantic warning pruneStatement(stmt, doc); // throw semantic warning final StringBuilder bldr = new StringBuilder(); bldr.append("statement nested more than once, " + "pruning statement."); final String err = SEMANTIC_MULTI_NESTED; final String name = bldr.toString(); throw new SemanticWarning(name, err); } /** * Prunes the provided {@link Statement statement} from the {@link Document document}. */ private void pruneStatement(final Statement stmt, final Document doc) { final List<StatementGroup> stmtgroups = doc.getAllStatementGroups(); for (final StatementGroup stmtgroup : stmtgroups) { final Iterator<Statement> sgi = stmtgroup.getStatements() .iterator(); while (sgi.hasNext()) { final Statement sgs = sgi.next(); if (stmt.equals(sgs)) { // prune sgi.remove(); return; } } } } /** * {@inheritDoc} */ @Override public List<SemanticWarning> checkAbundanceSubsets(final Term term) { final FunctionEnum fe = term.getFunctionEnum(); if (fe != PROTEIN_ABUNDANCE && fe != GENE_ABUNDANCE && fe != RNA_ABUNDANCE) { return emptyList(); } List<Term> nestedTerms = term.getTerms(); if (noItems(nestedTerms)) { return emptyList(); } List<SemanticWarning> ret = new ArrayList<SemanticWarning>(); switch (fe) { case GENE_ABUNDANCE: case RNA_ABUNDANCE: for (final Term nestedTerm : nestedTerms) { if (nestedTerm.getFunctionEnum() == FUSION) { ret.addAll(checkPFusion(nestedTerm)); } } break; case PROTEIN_ABUNDANCE: for (final Term nestedTerm : nestedTerms) { if (!nestedTerm.getFunctionEnum().isProteinDecorator()) { continue; } ret.addAll(checkAbundanceSubset(nestedTerm)); } break; default: break; } return ret; } private List<SemanticWarning> checkAbundanceSubset(final Term term) { final FunctionEnum fe = term.getFunctionEnum(); if (fe == PROTEIN_MODIFICATION) { return checkPModification(term); } else if (fe == SUBSTITUTION) { return checkPSubstitution(term); } else if (fe == TRUNCATION) { return checkPTruncation(term); } else if (fe == FUSION) { return checkPFusion(term); } return emptyList(); } private List<SemanticWarning> checkPModification(final Term term) { List<SemanticWarning> ret = new ArrayList<SemanticWarning>(); int numargs = term.getNumberOfArguments(); // Indicates signatures do not match, return // (no semantic warning should be generated here for this) if (!PROTEIN_MODIFICATION.isValidArgumentCount(numargs)) { return emptyList(); } List<Parameter> parameters = term.getParameters(); // Check mod type Parameter parameter = parameters.get(0); String pval = parameter.getValue(); if (!validPType(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_CM; } else { msg = format(SEMANTIC_PTYPE_NOT_CM, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } if (numargs == 1) { return ret; } // Check mod code parameter = parameters.get(1); pval = parameter.getValue(); if (!validAcid(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_AA; } else { msg = format(SEMANTIC_CODE_NOT_AA, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } if (numargs == 2) { return ret; } // Check mod position parameter = parameters.get(2); pval = parameter.getValue(); if (!validPosition(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_INT; } else { msg = format(SEMANTIC_POS_NOT_INT, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } return ret; } private List<SemanticWarning> checkPSubstitution(final Term term) { List<SemanticWarning> ret = new ArrayList<SemanticWarning>(); int numargs = term.getNumberOfArguments(); // Indicates signatures do not match, return // (no semantic warning should be generated here for this) if (!SUBSTITUTION.isValidArgumentCount(numargs)) { return emptyList(); } List<Parameter> parameters = term.getParameters(); // Sanity check three parameters if (numargs != 3) { return ret; } // Check amino acid variant Parameter parameter = parameters.get(0); String pval = parameter.getValue(); if (!validAcid(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_AA; } else { msg = format(SEMANTIC_CODE_NOT_AA, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } // Check mod position parameter = parameters.get(1); pval = parameter.getValue(); if (!validPosition(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_INT; } else { msg = format(SEMANTIC_POS_NOT_INT, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } // Check amino acid variant parameter = parameters.get(2); pval = parameter.getValue(); if (!validAcid(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_AA; } else { msg = format(SEMANTIC_CODE_NOT_AA, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } return ret; } private List<SemanticWarning> checkPTruncation(final Term term) { List<SemanticWarning> ret = new ArrayList<SemanticWarning>(); int numargs = term.getNumberOfArguments(); // Indicates signatures do not match, return // (no semantic warning should be generated here for this) if (!TRUNCATION.isValidArgumentCount(numargs)) { return emptyList(); } List<Parameter> parameters = term.getParameters(); // Sanity check one parameter if (numargs != 1) { return ret; } // Check mod position Parameter parameter = parameters.get(0); String pval = parameter.getValue(); if (!validPosition(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_INT; } else { msg = format(SEMANTIC_POS_NOT_INT, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } return ret; } private List<SemanticWarning> checkPFusion(final Term term) { List<SemanticWarning> ret = new ArrayList<SemanticWarning>(); int numargs = term.getNumberOfArguments(); // Indicates signatures do not match, return // (no semantic warning should be generated here for this) if (!FUSION.isValidArgumentCount(numargs)) { return emptyList(); } List<Parameter> parameters = term.getParameters(); // Sanity check three parameters - we only semantically validate last // two are valid positions if (parameters.size() != 3) { return ret; } for (int i = 1; i <= 2; i++) { Parameter parameter = parameters.get(i); String pval = parameter.getValue(); if (!validPosition(pval)) { String msg; if (pval == null) { msg = SEMANTIC_NOT_INT; } else { msg = format(SEMANTIC_POS_NOT_INT, pval); } ret.add(new SemanticWarning(term.toBELLongForm(), msg)); } } return ret; } /* * Check protein abundance 'type' is valid. */ private boolean validPType(String pval) { if (getCovalentModification(pval) == null) { return false; } return true; } /* * Check amino acid is valid. */ private boolean validAcid(String pval) { if (getAminoAcid(pval) == null) { return false; } return true; } /* * Check position is valid. */ private boolean validPosition(String pval) { if (pval == null) { return false; } try { int ival = parseInt(pval); if (ival < 1) { return false; } } catch (NumberFormatException nfe) { return false; } return true; } /* * Returns the parameter formatted as a string, suitable for semantic * checking. */ private String processParameter(final Parameter p) { try { String encoding = nsService.lookup(p); if (encoding == null) { return encode(WILDCARD_ENCODING); } final ValueEncoding ve = getValueEncoding(encoding); if (ve == null) { return encode(encoding); } return encode(ve.getDisplayValue()); } catch (NamespaceSyntaxWarning e) { return encode(WILDCARD_ENCODING); } catch (IndexingFailure idxf) { return encode(WILDCARD_ENCODING); } } /* * Returns the term formatted as a string, suitable for semantic checking. */ private String processTerm(final Term t) { return encode(t.getFunctionEnum().getReturnType()); } }