/** * 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 org.openbel.framework.common.BELUtilities.hasItems; import static org.openbel.framework.common.BELUtilities.sizedArrayList; 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.enums.FunctionEnum.isProteinDecorator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.openbel.framework.common.InvalidArgument; import org.openbel.framework.common.enums.FunctionEnum; /** * BEL terms represent biological entities. * <p> * They contain a number of {@link Term terms} and {@link Parameter parameters}. * Terms are parameter-iterable: * * <pre> * <code> * for (final Parameter parameter : term) { * // ... * } * </code> * </pre> * * </p> * * @author Anthony Bargnesi {@code <abargnesi@selventa.com>} */ public class Term implements BELObject, Iterable<Parameter> { private static final long serialVersionUID = 8921981074652515078L; private final FunctionEnum function; private List<Parameter> parameters; private List<Term> terms; private List<BELObject> functionArgs; /** * Creates a term with the required function and optional arguments * property. * * @param f {@link FunctionEnum} * @param functionArgs List of BEL object function arguments * @throws InvalidArgument Thrown if {@code f} is null */ public Term(FunctionEnum f, List<BELObject> functionArgs) { if (f == null) throw new InvalidArgument("function enum is null"); this.function = f; setFunctionArgs(functionArgs); } /** * Creates a term with the required function. * * @param f {@link FunctionEnum} * @throws InvalidArgument Thrown if {@code f} is null */ public Term(FunctionEnum f) { if (f == null) throw new InvalidArgument("function enum is null"); this.function = f; } /** * Returns the term's function. * * @return {@link FunctionEnum}, the function type */ public FunctionEnum getFunctionEnum() { return function; } /** * Returns the term's list of parameters. * * @return List of parameters, which may be null */ public List<Parameter> getParameters() { return parameters; } /** * Returns the term's nested list of terms. * * @return List of terms, which may be null */ public List<Term> getTerms() { return terms; } /** * Returns the number of function arguments. * <p> * The arguments to a term's function can either be a {@link Parameter * parameter} or another {@link Term term} (considered an <i>inner</i> * term). This means the {@code number_of_arguments} is equal to the * {@code (number_of_parameters + number_of_terms)}. I.e., * * <pre> * <code> * getNumberOfArguments() == (getNumberOfParameters() + getNumberOfTerms()) * </code> * </pre> * * </p> * * @return int * @see #getNumberOfParameters() * @see #getNumberOfTerms() */ public int getNumberOfArguments() { if (functionArgs != null) return functionArgs.size(); return 0; } /** * Returns the number of parameters. * <p> * The arguments to a term's function can either be a {@link Parameter * parameter} or another {@link Term term} (considered an <i>inner</i> * term). This means the {@code number_of_parameters} is equal to the * {@code (number_of_arguments - number_of_terms)}. I.e., * * <pre> * <code> * getNumberOfParameters() == (getNumberOfArguments() - getNumberOfTerms()) * </code> * </pre> * * </p> * * @return int * @see #getNumberOfArguments() * @see #getNumberOfTerms() */ public int getNumberOfParameters() { return parameters == null ? 0 : parameters.size(); } /** * Returns the number of terms. * <p> * The arguments to a term's function can either be a {@link Parameter * parameter} or another {@link Term term} (considered an <i>inner</i> * term). This means the {@code number_of_terms} is equal to the * {@code (number_of_arguments - number_of_parameters)}. I.e., * * <pre> * <code> * getNumberOfTerms() == (getNumberOfArguments() - getNumberOfParameters()) * </code> * </pre> * * </p> * * @return int * @see #getNumberOfArguments() * @see #getNumberOfParameters() */ public int getNumberOfTerms() { return terms == null ? 0 : terms.size(); } /** * Returns the term's arguments, <b>in order</b>, to its associated * function. * <p> * The term's {@link #getParameters() parameters} and {@link #getTerms() * nested terms} compose the arguments to its associated BEL function. This * method encapsulates the ordering of these arguments for the term's * function. * </p> * * @return List of {@link BELObject BEL objects}, which may be null, in * order for its associated {@link #getFunctionEnum() function} */ public List<BELObject> getFunctionArguments() { return functionArgs; } /** * Adds a function argument to the end of the function argument list. * <p> * The argument will be additionally added to the terms or parameters, * depending on its type. * </p> * * @param arg BEL object function argument * @throws InvalidArgument Thrown if {@code arg} is null * @throws UnsupportedOperationException Thrown if {@code arg} is not a * {@link Term} or {@link Parameter} */ public void addFunctionArgument(final BELObject arg) { if (arg == null) throw new InvalidArgument("arg is null"); if (!(arg instanceof Term) && !(arg instanceof Parameter)) { String err = arg.getClass().getName(); err = err.concat(" is not a valid function argument"); throw new UnsupportedOperationException(err); } if (functionArgs == null) { functionArgs = new ArrayList<BELObject>(); terms = new ArrayList<Term>(); parameters = new ArrayList<Parameter>(); } functionArgs.add(arg); if (arg instanceof Term) { terms.add((Term) arg); } else { parameters.add((Parameter) arg); } } /** * Sets the term's arguments. This list backs the term's ordered function * argument, term, and parameter lists. * * @param args List of BEL objects * @see #getFunctionArguments() */ public void setFunctionArguments(List<BELObject> args) { setFunctionArgs(args); } /** * Returns a list of all parameters contained by both this term and any * nested terms. * * @return Non-null list of parameters */ public List<Parameter> getAllParameters() { List<Parameter> ret = new ArrayList<Parameter>(); if (parameters != null) ret.addAll(parameters); if (terms != null) { for (final Term t : terms) { ret.addAll(t.getAllParameters()); } } return ret; } /** * Returns an {@link Iterator iterator} over the document's * {@link Parameter parameters}. * <p> * The following code is guaranteed to be safe (no * {@link NullPointerException null pointer exceptions} will be thrown): * * <pre> * <code> * for (final Parameter parameter : term) { * } * </code> * </pre> * * </p> * * @return {@link Iterator} of {@link Parameter parameters} */ @Override public Iterator<Parameter> iterator() { return getAllParameters().iterator(); } /** * Returns a list of all terms contained by and within this term. * * @return Non-null list of terms */ public List<Term> getAllTerms() { List<Term> ret = new ArrayList<Term>(); if (terms != null) { ret.addAll(terms); for (final Term term : terms) { ret.addAll(term.getAllTerms()); } } return ret; } /** * Returns all of this term's {@link Parameter} objects, in a left to right * sequence, contained within both the outer and inner terms. * <p> * Parameters are found in a recursive depth-first search using the * implementation in {@link #findParameters(List, Term)}. * </p> * * @return {@link List} of all {@link Parameter} objects for this term, * which can be empty but will not be null */ public List<Parameter> getAllParametersLeftToRight() { List<Parameter> params = new ArrayList<Parameter>(); findParameters(params, this); return params; } /** * Returns {@code true} if this term is parameterized, e.g., has parameters; * {@code false} otherwise. * * @return boolean */ public boolean isParameterized() { if (getParameters() == null) { return false; } return true; } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("Term ["); builder.append("function="); builder.append(function); builder.append(", "); if (functionArgs != null) { builder.append("functionArgs="); builder.append(functionArgs); builder.append(", "); } if (parameters != null) { builder.append("parameters="); builder.append(parameters); builder.append(", "); } if (terms != null) { builder.append("terms="); builder.append(terms); } builder.append("]"); return builder.toString(); } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result *= prime; result += function.hashCode(); result *= prime; if (functionArgs != null) result += functionArgs.hashCode(); result *= prime; if (parameters != null) result += parameters.hashCode(); result *= prime; if (terms != null) result += terms.hashCode(); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Term)) return false; final Term t = (Term) o; if (function != t.function) return false; if (functionArgs == null) { if (t.functionArgs != null) return false; } else if (!functionArgs.equals(t.functionArgs)) return false; if (parameters == null) { if (t.parameters != null) return false; } else if (!parameters.equals(t.parameters)) return false; if (terms == null) { if (t.terms != null) return false; } else if (!terms.equals(t.terms)) return false; return true; } /** * {@inheritDoc} */ @Override public String toBELLongForm() { StringBuilder belBuilder = new StringBuilder(); String fs = function.getDisplayValue(); belBuilder.append(fs).append("("); if (hasItems(functionArgs)) { for (BELObject functionArg : functionArgs) { belBuilder.append(functionArg.toBELLongForm()); belBuilder.append(","); } belBuilder.deleteCharAt(belBuilder.length() - 1); } belBuilder.append(")"); return belBuilder.toString(); } /** * {@inheritDoc} */ @Override public String toBELShortForm() { StringBuilder belBuilder = new StringBuilder(); String fs = function.getShortestForm(); belBuilder.append(fs).append("("); if (hasItems(functionArgs)) { for (BELObject functionArg : functionArgs) { belBuilder.append(functionArg.toBELShortForm()); belBuilder.append(","); } belBuilder.deleteCharAt(belBuilder.length() - 1); } belBuilder.append(")"); return belBuilder.toString(); } /** * Return a {@link String} with {@link Parameter parameters} replaced * with {@code #}. This is useful to search for terms in the KAMStore. * * <p> * Example: * <br> * {@code tloc(p(HGNC:AKT1), GOACC:"GO:1", GOACC:"GO:2")} converts to * {@code tloc(p(#),#,#)} * * @return {@link String} */ public String toTermSignature() { final StringBuilder b = new StringBuilder(); replaceParameters(this, b); return b.toString(); } /** * {@inheritDoc} */ @Override public Term clone() { List<BELObject> functionArgs2 = null; if (functionArgs != null) { functionArgs2 = sizedArrayList(functionArgs.size()); for (final BELObject bo : functionArgs) functionArgs2.add(bo.clone()); } return new Term(function, functionArgs2); } /** * Find all parameters in <tt>term</tt> in a left-to-right, depth-first * fashion. The term's function arguments are read in order and the * following rules apply: * <ul> * <li>If the function argument is a parameter: * <ul> * <li>Add it to the parameter list (<tt>ps</tt>).</li> * </ul> * </li> * <li>If the function argument is a term: * <ul> * <li>Recurse into {@link #findParameters(List, Term)} for the nested term. * </li> * </ul> * </ul> * <p> * The results are captured in the <tt>params</tt> {@link List}. * </p> * * @param params {@link List} of {@link Parameter}, the list of parameters * found from the top-level term so far */ protected void findParameters(final List<Parameter> parms, final Term trm) { List<BELObject> tfa = trm.getFunctionArguments(); if (hasItems(tfa)) { for (BELObject bmo : tfa) { if (Parameter.class.isAssignableFrom(bmo.getClass())) { Parameter p = (Parameter) bmo; parms.add(p); } else { Term inner = (Term) bmo; if (!isProteinDecorator(inner.function)) { findParameters(parms, inner); } else if (FUSION.equals(inner.function) && hasItems(inner.getParameters())) { parms.add(inner.getParameters().get(0)); } } } } } /** * Sets the function arguments, terms, and parameters. A null argument will * result in null function arguments, terms, and parameters. * * @param args List of BEL objects, or null * @throws UnsupportedOperationException Thrown if a {@link BELObject} * within {@code args} is not a {@link Term} or {@link Parameter} */ private void setFunctionArgs(final List<BELObject> args) { if (args != null) { this.functionArgs = args; this.terms = new ArrayList<Term>(); this.parameters = new ArrayList<Parameter>(); for (final BELObject arg : functionArgs) { if (arg instanceof Term) { terms.add((Term) arg); } else if (arg instanceof Parameter) { parameters.add((Parameter) arg); } else { String err = arg.getClass().getName(); err = err.concat(" is not a valid function argument"); throw new UnsupportedOperationException(err); } } } else { this.functionArgs = null; this.terms = null; this.parameters = null; } } /** * Builds a {@link String term string} where {@link Parameter parameters} * are replaced with {@code #} characters. * * @param t {@link Term} * @param b {@link StringBuilder} */ private static void replaceParameters(Term t, StringBuilder b) { FunctionEnum f = t.getFunctionEnum(); String fx = f.getShortestForm(); b.append(fx).append("("); if (hasItems(t.getFunctionArguments())) { for (BELObject bo : t.getFunctionArguments()) { if (Term.class.isAssignableFrom(bo.getClass())) { Term inner = (Term) bo; if (inner.getFunctionEnum() == PROTEIN_MODIFICATION || inner.getFunctionEnum() == SUBSTITUTION || inner.getFunctionEnum() == TRUNCATION) { b.append(inner.toBELShortForm()); } else { replaceParameters((Term) bo, b); } } else { b.append("#"); } b.append(","); } b.deleteCharAt(b.length() - 1); b.append(")"); } } }