/**
* 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.common.model;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.openbel.framework.common.BELUtilities.*;
import static org.openbel.framework.common.model.Namespace.DEFAULT_NAMESPACE_PREFIX;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openbel.framework.common.InvalidArgument;
/**
* BEL documents are structured text documents which contain BEL statements
* along with sufficient additional information to fully describe and process
* the document.
* <p>
* BEL documents contain a number of {@link Statement statements} organized into
* {@link StatementGroup statement groups}. Documents are statement-iterable:
*
* <pre>
* <code>
* for (final Statement statement : document) {
* // ...
* }
* </code>
* </pre>
*
* </p>
*
* @author Anthony Bargnesi {@code <abargnesi@selventa.com>}
*/
public class Document implements BELModelObject, Iterable<Statement> {
private static final long serialVersionUID = -6709772752903171478L;
private final Header header;
private NamespaceGroup namespaceGroup;
private List<AnnotationDefinition> definitions =
new ArrayList<AnnotationDefinition>();
private final List<StatementGroup> statementGroups;
/**
* Creates a document with the provided header, initially containing the
* supplied statement group.
*
* @param header Non-null header
* @param group Non-null statement group
* @throws InvalidArgument Thrown if {@code header} or {@code group} is null
*/
public Document(final Header header, final StatementGroup group) {
if (group == null) {
throw new InvalidArgument("group is null");
}
if (header == null) {
throw new InvalidArgument("header is null");
}
statementGroups = new ArrayList<StatementGroup>();
statementGroups.add(group);
this.header = header;
}
/**
* Creates a document with the provided header, containing the supplied
* statement groups.
*
* @param header Non-null header
* @param groups Non-null, nonempty list of statement groups
* @throws InvalidArgument Thrown if {@code header} is null or
* {@code groups} is null or empty
*/
public Document(final Header header, final List<StatementGroup> groups) {
if (noItems(groups)) {
throw new InvalidArgument("groups has no items");
}
if (header == null) {
throw new InvalidArgument("header is null");
}
this.statementGroups = groups;
this.header = header;
}
/**
* Creates a document with the provided header, containing the supplied
* statement groups and optional properties.
*
* @param header Non-null header
* @param groups Non-null, nonempty list of statement groups
* @param nsGroup Namespace group
* @param ad Annotation definition list
* @throws InvalidArgument Thrown if {@code groups} has no items
*/
public Document(final Header header, final List<StatementGroup> groups,
NamespaceGroup nsGroup, List<AnnotationDefinition> ad) {
if (noItems(groups)) {
throw new InvalidArgument("groups has no items");
}
this.statementGroups = groups;
this.header = header;
this.namespaceGroup = nsGroup;
this.definitions = ad;
}
/**
* Returns the document's header.
*
* @return Header
*/
public Header getHeader() {
return header;
}
/**
* Returns the document's namespace group.
*
* @return NamespaceGroup, which may be null
*/
public NamespaceGroup getNamespaceGroup() {
return namespaceGroup;
}
/**
* Returns all annotations contained within the document.
*
* @return List of annotations, which may be empty
*/
public List<Annotation> getAllAnnotations() {
List<Annotation> ret = new ArrayList<Annotation>();
for (StatementGroup sg : statementGroups) {
ret.addAll(sg.getAllAnnotations());
}
return ret;
}
/**
* Returns the set of all namespaces contained within the document.
*
* @return Set of namespaces, which may be empty
*/
public Set<Namespace> getAllNamespaces() {
if (namespaceGroup == null) {
return emptySet();
}
return new HashSet<Namespace>(namespaceGroup.getAllNamespaces());
}
/**
* Returns the set of all terms contained within the document.
*
* @return Set of terms, which may be empty
*/
public Set<Term> getAllTerms() {
final Set<Term> ret = new HashSet<Term>();
for (final StatementGroup sg : statementGroups) {
for (final Statement stmt : sg.getAllStatements()) {
ret.addAll(stmt.getAllTerms());
}
}
return ret;
}
/**
* Returns the set of all parameters contained within the document.
*
* @return Set of parameters, which may be empty
*/
public Set<Parameter> getAllParameters() {
final Set<Parameter> ret = new HashSet<Parameter>();
for (final Term term : getAllTerms()) {
List<Parameter> params = term.getParameters();
if (params == null) {
continue;
}
ret.addAll(params);
}
return ret;
}
/**
* Returns a list of all statement groups contained within the document.
*
* @return List of statement groups
*/
public List<StatementGroup> getAllStatementGroups() {
final List<StatementGroup> ret = new ArrayList<StatementGroup>();
ret.addAll(statementGroups);
for (final StatementGroup sg : statementGroups) {
ret.addAll(sg.getAllStatementGroups());
}
return ret;
}
/**
* Returns a list of all statements contained within the document.
*
* @return List of statements, which may be empty
*/
public List<Statement> getAllStatements() {
final List<Statement> ret = new LinkedList<Statement>();
for (final StatementGroup sg : statementGroups) {
ret.addAll(sg.getAllStatements());
}
return ret;
}
/**
* Returns the number of statements contained within the document.
*
* @return int
*/
public int getNumberOfStatements() {
return getAllStatements().size();
}
/**
* Sets the document's namespace group.
*
* @param namespaceGroup Document's namespace group
*/
public void setNamespaceGroup(NamespaceGroup namespaceGroup) {
this.namespaceGroup = namespaceGroup;
}
/**
* Returns the document's namespaces as a mapped keyed by namespace
* {@link Namespace#getPrefix() namespace prefix}.
*
* @return Map of namespaces to instances
*/
public Map<String, Namespace> getNamespaceMap() {
final Map<String, Namespace> ret = new HashMap<String, Namespace>();
if (namespaceGroup == null) {
return ret;
}
final List<Namespace> namespaces = namespaceGroup.getNamespaces();
if (hasItems(namespaces)) {
for (final Namespace ns : namespaces)
ret.put(ns.getPrefix(), ns);
}
final String defns = namespaceGroup.getDefaultResourceLocation();
if (defns != null) {
Namespace ns = new Namespace(DEFAULT_NAMESPACE_PREFIX, defns);
ret.put(DEFAULT_NAMESPACE_PREFIX, ns);
}
return ret;
}
/**
* Returns the count of statements contained within the document.
*
* @return int
*/
public int getStatementCount() {
int ret = 0;
for (final StatementGroup sg : statementGroups) {
ret += sg.getAllStatements().size();
}
return ret;
}
/**
* Returns the count of namespaces contained within the document.
* <p>
* This method simply returns {@link Map#size()} on
* {@link #getNamespaceMap()}.
* </p>
*
* @return int
* @see #getNamespaceMap()
*/
public int getNamespaceCount() {
return getNamespaceMap().size();
}
/**
* Returns the document's definitions.
*
* @return List of annotation definitions, which may be null
*/
public List<AnnotationDefinition> getDefinitions() {
return definitions;
}
/**
* Sets the document's definitions.
*
* @param definitions Document's definitions
*/
public void setDefinitions(List<AnnotationDefinition> definitions) {
this.definitions = definitions;
}
/**
* Returns the document's annotation definitions as a mapped keyed by
* annotation definition {@link AnnotationDefinition#getId() identifier}.
*
* @return Non-null map of annotation definition identifiers to instances,
* which may be empty
*/
public Map<String, AnnotationDefinition> getDefinitionMap() {
if (definitions == null) {
return emptyMap();
}
final Map<String, AnnotationDefinition> ret =
new HashMap<String, AnnotationDefinition>(definitions.size());
for (final AnnotationDefinition ad : definitions) {
ret.put(ad.getId(), ad);
}
return ret;
}
/**
* Returns the document's statement groups.
*
* @return Non-null, nonempty list of statement groups
*/
public List<StatementGroup> getStatementGroups() {
return statementGroups;
}
/**
* Adds a statement group to the document's statement groups.
*
* @param group Statement group
*/
public void addStatementGroup(StatementGroup group) {
statementGroups.add(group);
}
/**
* Returns the document's name, contained in its {@link Header#getName()
* header}.
*
* @return String
*/
public String getName() {
return header.getName();
}
/**
* Resolves any property references in use within the document to their
* corresponding instances.
*/
public void resolveReferences() {
// Resolve annotations to their definitions
Map<String, AnnotationDefinition> defs = getDefinitionMap();
final List<Annotation> annos = new ArrayList<Annotation>();
// Get all the annotation used by the document's statement groups
for (final StatementGroup sg : statementGroups) {
annos.addAll(sg.getAllAnnotations());
}
// Resolve the references
for (final Annotation a : annos) {
AnnotationDefinition ad = a.getDefinition();
// Skip annotations without definitions
if (ad == null) {
continue;
}
final String id = ad.getId();
AnnotationDefinition documentDefinition = defs.get(id);
// BEL document annotations must reference valid definitions...
assert documentDefinition != null;
// ... so sanity check that for now.
// Resolve the reference to the instance
a.setDefinition(documentDefinition);
}
// Resolve parameters to their namespaces
Map<String, Namespace> nspcs = getNamespaceMap();
final List<Parameter> params = new ArrayList<Parameter>();
// Get all the parameters within the document
for (final StatementGroup sg : statementGroups) {
params.addAll(sg.getAllParameters());
}
// Get the default namespace if present
final Namespace defaultNS = nspcs.get(DEFAULT_NAMESPACE_PREFIX);
// Resolve the references
for (final Parameter av : params) {
Namespace ns = av.getNamespace();
// Skip parameters without namespaces
if (ns == null) {
if (defaultNS != null) {
av.setNamespace(defaultNS);
}
continue;
}
final String prefix = ns.getPrefix();
Namespace documentNamespace = nspcs.get(prefix);
// BEL document parameters must reference valid namespaces...
assert documentNamespace != null;
// ... so sanity check that for now.
// Resolve the reference to the instance
av.setNamespace(documentNamespace);
}
}
/**
* Returns a map of {@link StatementGroup statement group} to a {@link Set
* set} of {@link Statement statements}.
* <p>
* The returned map is not backed by the document. If the document is
* modified in any way, the returned map may no longer reflect a correct
* mapping of statement group to statement set.
* </p>
*
* @return {@link Map} of {@link StatementGroup} to {@link Set} of
* {@link Statement Statements}
*/
public Map<StatementGroup, Set<Statement>> mapStatements() {
List<StatementGroup> groups = getStatementGroups();
final int size = groups.size();
Map<StatementGroup, Set<Statement>> ret = sizedHashMap(size);
for (final StatementGroup group : groups) {
List<Statement> stmts = group.getStatements();
Set<Statement> stmtset;
// No statements? Map to an empty set
if (stmts == null) {
stmtset = emptySet();
ret.put(group, stmtset);
continue;
}
stmtset = sizedHashSet(stmts.size());
stmtset.addAll(stmts);
ret.put(group, stmtset);
}
return ret;
}
/**
* Returns an {@link Iterator iterator} over the document's
* {@link Statement statements}.
* <p>
* The following code is guaranteed to be safe (no
* {@link NullPointerException null pointer exceptions} will be thrown):
*
* <pre>
* <code>
* for (final Statement statement : document) {
* }
* </code>
* </pre>
*
* </p>
*
* @return {@link Iterator} of {@link Statement statements}
*/
@Override
public Iterator<Statement> iterator() {
return getAllStatements().iterator();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Document [");
// header is non-null by contract
builder.append("header=");
builder.append(header);
builder.append(", ");
if (namespaceGroup != null) {
builder.append("namespaceGroup=");
builder.append(namespaceGroup);
builder.append(", ");
}
if (definitions != null) {
builder.append("definitions=");
builder.append(definitions);
builder.append(", ");
}
// statement groups is non-null by contract
builder.append("statementGroups=");
builder.append(statementGroups);
builder.append("]");
return builder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result *= prime;
if (definitions != null) {
result += definitions.hashCode();
}
result *= prime;
result += header.hashCode();
result *= prime;
if (namespaceGroup != null) {
result += namespaceGroup.hashCode();
}
result *= prime;
// statement groups is non-null by contract
result += statementGroups.hashCode();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Document)) {
return false;
}
final Document d = (Document) o;
if (definitions == null) {
if (d.definitions != null) {
return false;
}
} else if (!definitions.equals(d.definitions)) {
return false;
}
// header is non-null by contract
if (!header.equals(d.header)) {
return false;
}
if (namespaceGroup == null) {
if (d.namespaceGroup != null) {
return false;
}
} else if (!namespaceGroup.equals(d.namespaceGroup)) {
return false;
}
// statement groups is non-null by contract
if (!statementGroups.equals(d.statementGroups)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public Document clone() {
Header header2 = header.clone();
NamespaceGroup namespaceGroup2 = null;
if (namespaceGroup != null) {
namespaceGroup2 = namespaceGroup.clone();
}
List<AnnotationDefinition> definitions2 = null;
if (definitions != null) {
definitions2 = sizedArrayList(definitions.size());
for (AnnotationDefinition a : definitions)
definitions2.add(a.clone());
}
List<StatementGroup> statementGroups2 = null;
if (statementGroups != null) {
statementGroups2 = sizedArrayList(statementGroups.size());
for (StatementGroup s : statementGroups)
statementGroups2.add(s.clone());
}
return new Document(header2, statementGroups2, namespaceGroup2,
definitions2);
}
}