/**
* 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.protonetwork;
import static org.openbel.framework.common.BELUtilities.hasItems;
import static org.openbel.framework.common.BELUtilities.noItems;
import static org.openbel.framework.common.enums.FunctionEnum.LIST;
import static org.openbel.framework.common.enums.RelationshipType.HAS_COMPONENT;
import static org.openbel.framework.common.enums.RelationshipType.HAS_COMPONENTS;
import static org.openbel.framework.common.enums.RelationshipType.HAS_MEMBER;
import static org.openbel.framework.common.enums.RelationshipType.HAS_MEMBERS;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openbel.framework.common.enums.RelationshipType;
import org.openbel.framework.common.model.*;
import org.openbel.framework.common.protonetwork.model.*;
import org.openbel.framework.common.protonetwork.model.AnnotationDefinitionTable.TableAnnotationDefinition;
import org.openbel.framework.common.protonetwork.model.DocumentTable.DocumentHeader;
import org.openbel.framework.common.protonetwork.model.NamespaceTable.TableNamespace;
import org.openbel.framework.common.protonetwork.model.ParameterTable.TableParameter;
import org.openbel.framework.common.protonetwork.model.StatementAnnotationMapTable.AnnotationPair;
import org.openbel.framework.common.protonetwork.model.StatementTable.TableStatement;
import org.openbel.framework.common.util.PackUtils;
/**
* ProtoNetworkBuilder breaks down a {@link Document} into a proto network that
* individual identifies the following:
* <ul>
* <li>Document header information</li>
* <li>Namespaces</li>
* <li>Which document defines which namespace</li>
* <li>Parameters</li>
* <li>Terms</li>
* <li>Which parameters are defined in which terms and in what order</li>
* <li>Statements</li>
* <li>Which document defines which statement</li>
* <li>Annotation definitions</li>
* <li>Annotation values</li>
* <li>Which statements reference which annotations</li>
* </ul>
*
* @author Anthony Bargnesi {@code <abargnesi@selventa.com>}
*/
public class ProtoNetworkBuilder {
/**
* Defines the BEL {@link Document} that will be traversed to build the
* {@link ProtoNetwork}.
*/
private Document doc;
/**
* Defines the date format to use for dates ({@code ISO 8601}).
*/
private SimpleDateFormat datef = new SimpleDateFormat("yyyy-MM-dd");
/**
* Creates the ProtoNetworkBuilder from the {@link Document} that should be
* compiled.
*
* @param doc {@link Document}, the document to compile
*/
public ProtoNetworkBuilder(Document doc) {
this.doc = doc;
}
/**
* Builds the {@link ProtoNetwork} at the {@link Document} level.
*
* @return {@link ProtoNetwork}, the built proto network
*/
public ProtoNetwork buildProtoNetwork() {
ProtoNetwork pn = new ProtoNetwork();
// handle document header
DocumentTable dt = pn.getDocumentTable();
dt.addDocumentHeader(new DocumentHeader(doc.getHeader()));
// handle namespaces
NamespaceGroup nsg = doc.getNamespaceGroup();
if (nsg != null) {
NamespaceTable nt = pn.getNamespaceTable();
if (nsg.getDefaultResourceLocation() != null) {
org.openbel.framework.common.model.Namespace dns =
new Namespace("", nsg.getDefaultResourceLocation());
nt.addNamespace(new TableNamespace(dns), 0);
}
// associate namespaces to document id 0
final List<Namespace> nsl = nsg.getNamespaces();
if (hasItems(nsl)) {
for (org.openbel.framework.common.model.Namespace ns : nsl) {
nt.addNamespace(new TableNamespace(ns), 0);
}
}
}
// handle annotation definitions
if (hasItems(doc.getDefinitions())) {
for (AnnotationDefinition ad : doc.getDefinitions()) {
AnnotationDefinitionTable adt =
pn.getAnnotationDefinitionTable();
adt.addAnnotationDefinition(new TableAnnotationDefinition(ad),
0);
}
}
// handle statement groups
for (StatementGroup statementGroup : doc.getStatementGroups()) {
buildProtoNetwork(pn, statementGroup,
new HashMap<String, Annotation>());
}
// create proto nodes
final TermTable tt = pn.getTermTable();
final ProtoNodeTable pnt = pn.getProtoNodeTable();
final List<String> labels = tt.getTermValues();
for (int i = 0, n = labels.size(); i < n; i++) {
pnt.addNode(i, labels.get(i));
}
// create proto edges, process only simple edges now
final Map<Integer, Integer> termNodeMap = pnt.getTermNodeIndex();
final StatementTable st = pn.getStatementTable();
final ProtoEdgeTable pet = pn.getProtoEdgeTable();
final List<TableStatement> stmts = st.getStatements();
for (int i = 0, n = stmts.size(); i < n; i++) {
final TableStatement ts = stmts.get(i);
final int subjectTerm = ts.getSubjectTermId();
if (ts.getObjectTermId() != null) {
final int objectTerm = ts.getObjectTermId();
// find proto node index for subject / object terms
final int source = termNodeMap.get(subjectTerm);
final int target = termNodeMap.get(objectTerm);
// create proto edge and add to table
pet.addEdges(i, new ProtoEdgeTable.TableProtoEdge(source, ts
.getRelationshipName(), target));
}
}
return pn;
}
/**
* Builds the {@link ProtoNetwork} at the {@link StatementGroup} level.
*
* @param pn {@link ProtoNetwork}, the proto network
* @param statementGroup {@link StatementGroup}, the statement group
* @param annotationMap {@link Map}, the parent statement group annotations
* accumulated to this point
*/
protected void
buildProtoNetwork(ProtoNetwork pn,
StatementGroup statementGroup,
Map<String, Annotation> annotationMap) {
handleAnnotationGroup(pn, annotationMap,
statementGroup.getAnnotationGroup());
// build each statement
for (Statement statement : statementGroup.getStatements()) {
if (MacroStatementHelper.isMacroStatement(statement)) {
try {
List<Statement> expandedStatements =
MacroStatementHelper.expand(statement);
for (Statement expandedStatement : expandedStatements) {
buildProtoNetwork(pn, expandedStatement, annotationMap);
}
} catch (ProtoNetworkError e) {
//invalid statement expansion, skip
}
} else {
buildProtoNetwork(pn, statement, annotationMap);
}
}
// handle nested statement groups
if (hasItems(statementGroup.getStatementGroups())) {
for (StatementGroup childStatementGroup : statementGroup
.getStatementGroups()) {
buildProtoNetwork(pn, childStatementGroup,
new HashMap<String, Annotation>(annotationMap));
}
}
}
/**
* Builds the {@link ProtoNetwork} at the {@link Statement} level.
*
* @param pn {@link ProtoNetwork}, the proto network
* @param statement {@link Statement}, the statement to evaluate
* @param annotationMap {@link Map}, the parent statement group annotations
* accumulated to this point
* @return {@code int[]}, the statement indexes in the
* {@link StatementTable}
*/
protected int buildProtoNetwork(ProtoNetwork pn,
Statement statement, Map<String, Annotation> annotationMap) {
// build new list of annotations from statement group + individual
// statement
Map<String, Annotation> localAnnotationMap =
new HashMap<String, Annotation>(
annotationMap);
handleAnnotationGroup(pn, localAnnotationMap,
statement.getAnnotationGroup());
// assembly annotation definitions and values up to this point
Set<AnnotationPair> annotationPairs = buildAnnotationPairs(
pn, localAnnotationMap);
// handle statement subject and object
int subjectTerm = buildProtoNetwork(pn, statement.getSubject());
// compiling only one document, so index is 0
final int did = 0;
final int sid;
final StatementTable st = pn.getStatementTable();
if (statement.getObject() == null) {
// definitional statement
sid =
st.addStatement(new TableStatement(subjectTerm), statement,
did);
} else if (statement.getObject().getStatement() == null) {
// simple statement
int objectTerm = buildProtoNetwork(pn, statement.getObject()
.getTerm());
sid = st.addStatement(new TableStatement(subjectTerm, statement
.getRelationshipType().getDisplayValue(),
objectTerm), statement, did);
} else {
// nested statement
final Statement nested = statement.getObject().getStatement();
int nestedSubject = buildProtoNetwork(pn, nested.getSubject());
int nestedObject = buildProtoNetwork(pn, nested.getObject()
.getTerm());
sid = st.addStatement(
new TableStatement(subjectTerm, statement
.getRelationshipType().getDisplayValue(),
nestedSubject, nested.getRelationshipType()
.getDisplayValue(), nestedObject),
statement, did);
}
// associate annotations to new statement
StatementAnnotationMapTable samt = pn.getStatementAnnotationMapTable();
if (hasItems(annotationPairs)) {
samt.addStatementAnnotation(sid, annotationPairs);
}
return sid;
}
/**
* Builds the {@link ProtoNetwork} at the {@link Term} level. The found
* parameters are handled via
* {@link #handleParameter(ProtoNetwork, Parameter)} and indexed against the
* term.
*
* @param pn {@link ProtoNetwork}, the proto network
* @param term {@link Term}, the term to evaluate
* @return {@code int}, the term index in the {@link TermTable}
*/
public int buildProtoNetwork(final ProtoNetwork pn, final Term term) {
int ti = pn.getTermTable().addTerm(term);
List<Integer> pvals = new ArrayList<Integer>();
List<Parameter> ps = term.getAllParametersLeftToRight();
for (Parameter p : ps) {
pvals.add(handleParameter(pn, p));
}
TermParameterMapTable tpmt = pn.getTermParameterMapTable();
if (!tpmt.getTermParameterIndex().containsKey(ti)) {
tpmt.addTermParameterMapping(ti, pvals);
}
return ti;
}
/**
* Builds the {@link ProtoNetwork} at the {@link Parameter} level.
*
* @param pn {@link ProtoNetwork}, the proto network
* @param p {@link Parameter}, the parameter to evaluate
* @return {@code int}, the parameter index in the {@link ParameterTable}
*/
protected int handleParameter(ProtoNetwork pn, Parameter p) {
ParameterTable pt = pn.getParameterTable();
Integer npidx;
if (p.getNamespace() != null) {
// add parameter with namespace
TableNamespace ns = new TableNamespace(p.getNamespace());
npidx = pt.addTableParameter(new TableParameter(ns, p.getValue()));
} else if (doc.getNamespaceGroup() != null
&& doc.getNamespaceGroup().getDefaultResourceLocation() != null) {
// add parameter with user-defined default namespace
TableNamespace ns = new TableNamespace(doc.getNamespaceGroup()
.getDefaultResourceLocation());
npidx = pt.addTableParameter(new TableParameter(ns, p.getValue()));
} else {
// add parameter with TEXT namespace
npidx = pt.addTableParameter(new TableParameter(p.getValue()));
}
return npidx;
}
/**
* Processes an {@link AnnotationGroup} for citations, evidence, and
* user-defined annotations.
*
* @param protoNetwork {@link ProtoNetwork}, the proto network
* @param annotationMap {@link Map}, the annotations map to add to
* @param ag {@link AnnotationGroup}, the annotation group to process
*/
protected void handleAnnotationGroup(ProtoNetwork protoNetwork,
Map<String, Annotation> annotationMap, AnnotationGroup ag) {
if (ag != null) {
// handle citation
if (ag.getCitation() != null) {
handleCitationAnnotations(protoNetwork, ag.getCitation(),
annotationMap);
}
// handle evidence
if (ag.getEvidence() != null) {
handleEvidenceAnnotations(protoNetwork, ag.getEvidence(),
annotationMap);
}
// handle user annotations (which were already defined)
if (hasItems(ag.getAnnotations())) {
for (Annotation a : ag.getAnnotations()) {
annotationMap.put(a.getDefinition().getId(), a);
}
}
}
}
/**
* Build a {@link Set} of {@link AnnotationPair} objects from a {@link Map}
* of {@link Annotation} values.
*
* @param protoNetwork {@link ProtoNetwork}, the proto network
* @param annotationMap {@link Map}, the annotations map
* @return {@link Set}, the {@link Set} of annotation definition and value
* pairs
*/
protected Set<AnnotationPair> buildAnnotationPairs(
ProtoNetwork protoNetwork,
Map<String, Annotation> annotationMap) {
// build annotation value to annotation definition map
Set<AnnotationPair> valueDefinitions =
new LinkedHashSet<AnnotationPair>();
AnnotationValueTable avt = protoNetwork.getAnnotationValueTable();
AnnotationDefinitionTable adt =
protoNetwork.getAnnotationDefinitionTable();
Set<Annotation> annotationMapValues = new HashSet<Annotation>(
annotationMap.values());
for (Annotation a : annotationMapValues) {
int adid =
adt.getDefinitionIndex().get(
new TableAnnotationDefinition(a.getDefinition()));
int aid = avt.addAnnotationValue(adid, a.getValue());
valueDefinitions.add(new AnnotationPair(adid, aid));
}
return valueDefinitions;
}
/**
* Breaks down {@link Citation} annotations into individual name-value
* annotations.
*
* @param protoNetwork {@link ProtoNetwork}, the proto network
* @param citation {@link Citation}, the citation
* @param annotationMap {@link Map}, the annotations map to add to
*/
protected void handleCitationAnnotations(ProtoNetwork protoNetwork,
Citation citation, Map<String, Annotation> annotationMap) {
AnnotationDefinitionTable adt = protoNetwork
.getAnnotationDefinitionTable();
final CommonModelFactory af = CommonModelFactory.getInstance();
// handle citation name (always exists)
CitationNameAnnotationDefinition cnad =
new CitationNameAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(
cnad), 0);
annotationMap.put(
CitationNameAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(citation.getName(), cnad));
// handle citation id (if not null)
if (citation.getReference() != null) {
CitationReferenceAnnotationDefinition ciad =
new CitationReferenceAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(ciad),
0);
annotationMap
.put(CitationReferenceAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(citation.getReference(), ciad));
}
// handle citation comment (if not null)
if (citation.getComment() != null) {
CitationCommentAnnotationDefinition cdad =
new CitationCommentAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(cdad),
0);
annotationMap
.put(CitationCommentAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(citation.getComment(), cdad));
}
// handle citation date (if not null)
if (citation.getDate() != null) {
CitationDateAnnotationDefinition cdtad =
new CitationDateAnnotationDefinition();
adt.addAnnotationDefinition(
new TableAnnotationDefinition(cdtad), 0);
annotationMap.put(
CitationDateAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(
datef.format(citation.getDate().getTime()),
cdtad));
}
// handle citation authors (if not null)
if (citation.getAuthors() != null) {
CitationAuthorsAnnotationDefinition cuad =
new CitationAuthorsAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(cuad),
0);
annotationMap
.put(CitationAuthorsAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(PackUtils.packValues(citation
.getAuthors()), cuad));
}
// handle citation type (if not null)
if (citation.getType() != null) {
CitationTypeAnnotationDefinition ctad =
new CitationTypeAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(ctad),
0);
annotationMap.put(
CitationTypeAnnotationDefinition.ANNOTATION_DEFINITION_ID,
af.createAnnotation(citation.getType().getDisplayValue(),
ctad));
}
}
/**
* Breaks down {@link Evidence} annotations into individual name-value
* annotations.
*
* @param protoNetwork {@link ProtoNetwork}, the proto network
* @param evidence {@link Evidence}, the evidence
* @param annotationMap {@link Map}, the annotations map to add to
*/
protected void handleEvidenceAnnotations(ProtoNetwork protoNetwork,
Evidence evidence, Map<String, Annotation> annotationMap) {
AnnotationDefinitionTable adt =
protoNetwork.getAnnotationDefinitionTable();
EvidenceAnnotationDefinition ead =
new EvidenceAnnotationDefinition();
adt.addAnnotationDefinition(new TableAnnotationDefinition(ead), 0);
annotationMap.put(
EvidenceAnnotationDefinition.ANNOTATION_DEFINITION_ID,
CommonModelFactory.getInstance().createAnnotation(
evidence.getValue(), ead));
}
/**
* Helper class to handle expansion of macro statements.
*/
private static class MacroStatementHelper {
/**
* Expands a macro statement to the actual statements. If the statement
* cannot be expanded, the original statement is returned wrapped in a
* List.
*
* @param statement
* @return {@link List} list of expanded statements
*/
public static List<Statement> expand(Statement statement)
throws ProtoNetworkError {
List<Statement> statements = null;
if (statement != null) {
switch (statement.getRelationshipType()) {
case HAS_MEMBERS:
statements = expandHasMembers(statement);
break;
case HAS_COMPONENTS:
statements = expandHasComponents(statement);
break;
default:
statements = new ArrayList<Statement>();
statements.add(statement);
}
}
return statements;
}
/**
* Determines if <tt>statement</tt> is a macro statement which
* can be expanded.
*
* @param statement {@link Statement}, the statement to determine if
* it can be expanded
* @return the macro statement result, <tt>true</tt> if
* <tt>statement</tt> is a macro, <tt>false</tt> if <tt>statement</tt>
* is <tt>null</tt>, <tt>false</tt> if <tt>statement</tt> is not macro
*/
public static boolean isMacroStatement(Statement statement) {
if (statement == null) {
return false;
}
RelationshipType rel = statement.getRelationshipType();
return HAS_MEMBERS == rel || HAS_COMPONENTS == rel;
}
/**
* Expand a statement with a {@link RelationshipType#HAS_MEMBERS}
* relationship.
*
* @param statement {@link Statement}, the statement to expand
* @return {@link List} of {@link Statement}, the expanded statements
* @throws ProtoNetworkError Thrown if statement expansion cannot be
* carried out
*/
protected static List<Statement> expandHasMembers(Statement statement)
throws ProtoNetworkError {
return expandTermList(statement, HAS_MEMBER);
}
/**
* Expand a statement with a {@link RelationshipType#HAS_COMPONENTS}
* relationship.
*
* @param statement {@link Statement}, the statement to expand
* @return {@link List} of {@link Statement}, the expanded statements
* @throws ProtoNetworkError Thrown if statement expansion cannot be
* carried out
*/
protected static List<Statement>
expandHasComponents(Statement statement)
throws ProtoNetworkError {
return expandTermList(statement, HAS_COMPONENT);
}
/**
* Expand terms for <tt>statement</tt> into separate {@link Statement}.
*
* @param statement {@link Statement}, the statement to expand
* @param relationshipType {@link RelationshipType}, the relationship
* type
* @return {@link List} of {@link Statement}, the expanded statements
* @throws ProtoNetworkError Thrown if statement expansion cannot be
* carried out
*/
protected static List<Statement> expandTermList(Statement statement,
RelationshipType relationshipType) throws ProtoNetworkError {
List<Statement> statements = new ArrayList<Statement>();
if (statement.getObject() == null
|| statement.getObject().getTerm() == null) {
throw new ProtoNetworkError(
"Unable to expand macro statement",
"Object term is null");
}
if (statement.getObject().getTerm().getFunctionEnum() != LIST) {
throw new ProtoNetworkError(
"Unable to expand macro statement",
"Object term is not a term list");
}
List<BELObject> objectTerms =
statement.getObject().getTerm().getFunctionArguments();
if (noItems(objectTerms))
return statements;
for (BELObject bo : objectTerms) {
Statement s = statement.clone();
s.setRelationshipType(relationshipType);
s.setObject(new Statement.Object((Term) bo));
statements.add(s);
}
return statements;
}
}
}