uk.ac.ebi.mdk.domain.entity.reaction.AbstractReaction.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.ebi.mdk.domain.entity.reaction.AbstractReaction.java

Source

/*
 * Copyright (c) 2013. EMBL, European Bioinformatics Institute
 *
 * 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.ac.ebi.mdk.domain.entity.reaction;

import com.google.common.base.Joiner;
import org.apache.commons.lang.mutable.MutableInt;
import org.apache.log4j.Logger;
import uk.ac.ebi.mdk.domain.entity.AbstractAnnotatedEntity;
import uk.ac.ebi.mdk.domain.entity.Reaction;
import uk.ac.ebi.mdk.domain.entity.reaction.filter.AbstractParticipantFilter;
import uk.ac.ebi.mdk.domain.entity.reaction.filter.AcceptAllFilter;
import uk.ac.ebi.mdk.domain.identifier.Identifier;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * AbstractReaction   2011.08.08 <br>
 * <p/>
 * A base reaction class that allows the specification of different types of
 * participant. Two important participant types are {@see Participant} and {@see
 * CompartmentalisedParticipant}. Due to the verboseness of the later when
 * provided with the three parameter types like {@see MetabolicParticipant} are
 * usable. <br><br>
 * <p/>
 * {@code Reaction<Participant<String,Integer>> rxn = new
 * AbstractReaction<Participant<String,Integer>>(); } <br><br> {@code
 * rxn.addReactant(new ParticipantImplementation<String, Integer>("NAD+", 1)); }
 * <br> {@code rxn.addReactant(new ParticipantImplementation<String,
 *Integer>("primary-alcohol", 1)); } <br><br> {@code rxn.addProduct(new
 *ParticipantImplementation<String, Integer>("NADH", 1)); } <br> {@code
 * rxn.addProduct(new ParticipantImplementation<String, Integer>("H+", 1)); }
 * <br> {@code rxn.addProduct(new ParticipantImplementation<String,
 *Integer>("aldehyde", 1)); } <br>
 * <p/>
 * <br>
 * <p/>
 * If you intend to use this library's {@see Metabolite} and {@see Compartment}
 * implementations please use the concrete class {@see MetabolicReaction}.
 * <p/>
 * <pre>
 *
 *          </pre>
 *
 * @param <P> Reaction participant type
 * @author johnmay
 * @author $Author$ (this version)
 * @version $Rev$ : Last Changed $Date$
 */
public class AbstractReaction<P extends Participant> extends AbstractAnnotatedEntity implements Reaction<P> {

    private static final Logger LOGGER = Logger.getLogger(AbstractReaction.class);

    // flags whether the reaction is generic or not
    transient private Boolean generic = false;
    // new class

    private List<P> reactants;

    private List<P> products;
    // whether the reaction is reversible

    private Direction direction = Direction.BIDIRECTIONAL;

    private AbstractParticipantFilter filter = new AcceptAllFilter(); // accepts all

    /**
     * Constructor for a generic reaction. The constructor must provide a
     * comparator for class of molecule used in the generic reaction. Ideally
     * this should be a singleton class.
     */
    public AbstractReaction(UUID uuid) {
        super(uuid);
        reactants = new LinkedList<P>();
        products = new LinkedList<P>();
    }

    public AbstractReaction(Identifier identifier, String abbreviation, String name) {
        super(identifier, abbreviation, name);
        reactants = new LinkedList<P>();
        products = new LinkedList<P>();
    }

    /**
     * Constructor to build a reaction and use the provided filter to trim down
     * included molecules. Note  the reaction doesn't need a new instance of
     * the filter every-time and it is better to provide all required reactions
     * with the same filter
     *
     * @param filter The filter to use
     */
    public AbstractReaction(AbstractParticipantFilter filter) {
        this(UUID.randomUUID());
        this.filter = filter;
    }

    /**
     * Sets the reversibility of the reaction. By default the reaction
     * reversibility is unknown. The reversibility is not tested in the {@see
     * equals(M} method as this method treats reactions with same products
     * (coefficients and compartments), different sides as being the same.
     *
     * @param reversibility The reaction reversibility
     */
    public void setDirection(Direction direction) {
        this.direction = direction;
    }

    /**
     * Get the direction of the reaction
     *
     * @return Reaction reversibility enumeration
     */
    public Direction getDirection() {
        return direction;
    }

    //    /**
    //     * Accessor for all the reactant compartments of the reaction
    //     * @return Fixed size array of reactant compartments
    //     */
    //    public List<C> getReactantCompartments() {
    //        List<C> compartments = new ArrayList();
    //        for (CompartmentalisedParticipant<M, S, C> p : getReactantParticipants()) {
    //            compartments.add(p.getCompartment());
    //        }
    //        return compartments;
    //    }
    //
    //
    //    /**
    //     * Accessor for all the reactant coefficients of the reaction
    //     * @return Fixed size array of reactant coefficients
    //     */
    public List getReactantStoichiometries() {
        List coefficients = new ArrayList();
        for (P p : getReactantParticipants()) {
            coefficients.add(p.getCoefficient());
        }
        return coefficients;
    }
    //
    //

    /**
     * Accessor for all the reactants of the reaction
     *
     * @return Fixed size array of reactants of class 'M'
     */
    public List getReactantMolecules() {
        List molecules = new ArrayList();
        for (Participant p : getReactantParticipants()) {
            molecules.add(p.getMolecule());
        }
        return molecules;
    }

    public List<P> getReactantParticipants() {
        return Collections.unmodifiableList(reactants);
    }

    public List<P> getReactants() {
        return Collections.unmodifiableList(reactants);
    }

    /**
     * Add a reactant (left side) to the reaction
     *
     * @param participant The reactant to add
     */
    public boolean addReactant(P participant) {
        //        if (filter.reject(participant)) {
        //            return;
        //        }
        return this.reactants.add(participant);
    }

    /**
     * @inheritDoc
     */
    @Override
    public boolean removeReactant(P participant) {
        return this.reactants.remove(participant);
    }

    /**
     * @inheritDoc
     */
    @Override
    public boolean removeProduct(P participant) {
        return this.products.remove(participant);
    }

    //    /**
    //     * Accessor for all the product compartments of the reaction
    //     * @return Fixed size array of compartments
    //     */
    //    public List<C> getProductCompartments() {
    //        List<C> compartments = new ArrayList();
    //        for (CompartmentalisedParticipant<M, S, C> p : getProductParticipants()) {
    //            compartments.add(p.getCompartment());
    //        }
    //        return compartments;
    //    }
    //
    //
    //    /**
    //     * Accessor for all the product coefficients of the reaction
    //     * @return Fixed size array of coefficients
    //     */
    public List getProductStoichiometries() {
        List coefficients = new ArrayList();
        for (P p : getProductParticipants()) {
            coefficients.add(p.getCoefficient());
        }
        return coefficients;
    }

    //

    /**
     * Accessor for all the products of the reaction
     *
     * @return Fixed size array of products of class 'M'
     */
    public List getProductMolecules() {
        List molecules = new ArrayList();
        for (Participant p : getProductParticipants()) {
            molecules.add(p.getMolecule());
        }
        return molecules;
    }

    public List<P> getProductParticipants() {
        return Collections.unmodifiableList(products);
    }

    public List<P> getProducts() {
        return Collections.unmodifiableList(products);
    }

    public List<P> getParticipants() {
        List<P> participants = new ArrayList<P>();
        participants.addAll(getReactantParticipants());
        participants.addAll(getProductParticipants());
        return participants;
    }

    /**
     * Add a product (right side) to the reaction
     *
     * @param participant The product to add
     */
    public boolean addProduct(P participant) {
        //        if (filter.reject(participant)) {
        //            return;
        //        }
        return this.products.add(participant);
    }

    /**
     * Accessor to all the reaction molecules
     *
     * @return shallow copy combined list of all products (ordered reactant,
     *         product)
     */
    public List getAllReactionMolecules() {
        List allMolecules = new ArrayList(getReactantMolecules());
        allMolecules.addAll(getProductMolecules());
        return allMolecules;
    }

    /**
     * Accessor to all the reaction participants
     *
     * @return shallow copy combined list of all products (ordered reactant,
     *         product)
     */
    public List<P> getAllReactionParticipants() {
        List<P> participants = new ArrayList<P>(reactants);
        participants.addAll(products);
        return participants;
    }

    /**
     * Accessor to all the reaction compartments
     *
     * @return shallow copy combined list of all coefficients (ordered reactant,
     *         product)
     */
    public List getAllReactionCoefficients() {
        List allMolecules = new ArrayList(getReactantStoichiometries());
        allMolecules.addAll(getProductStoichiometries());
        return allMolecules;
    }

    //
    //    /**
    //     * Accessor to all the reaction compartments
    //     * @return shallow copy combined list of all compartments (ordered reactant, product)
    //     */
    //    public List<C> getAllReactionCompartments() {
    //        List<C> allMolecules = new ArrayList<C>(getReactantCompartments());
    //        allMolecules.addAll(getProductCompartments());
    //        return allMolecules;
    //    }

    /**
     * Accessor for the number of reactants
     *
     * @return
     */
    public int getReactantCount() {
        return reactants.size();
    }

    /**
     * Accessor for the number of products
     *
     * @return
     */
    public int getProductCount() {
        return products.size();
    }

    public int getParticipantCount() {
        return getReactantCount() + getProductCount();
    }

    /**
     * Accessor to query whether the reaction is generic
     *
     * @return t/f
     */
    public Boolean isGeneric() {
        return generic;
    }

    /**
     * Indicate that this reaction contains GenericMolecules. This influences
     * the {@see equals(Object obj)} method into taking longer
     *
     * @param generic
     */
    public void setGeneric(Boolean generic) {
        this.generic = generic;
    }

    /**
     * Transposes the sides of the reaction. The method switches the reactants
     * for products and the products for reactants
     */
    public void transpose() {
        // transpose all the lists
        List tmp = reactants;
        reactants = products;
        products = tmp;
    }

    public Reaction newInstance() {
        return new AbstractReaction(UUID.randomUUID());
    }

    private Map<Integer, MutableInt> map = new HashMap<Integer, MutableInt>();

    public int getParticipantHashCode(Collection<P> participants) {
        int hash = 57;
        map.put(hash, new MutableInt());

        for (Participant p : participants) {
            int value = p.hashCode();
            if (!map.containsKey(value)) {
                map.put(value, new MutableInt());
            } else {
                map.get(value).increment();
            }
            hash ^= rotate(value, map);
            if (!map.containsKey(hash)) {
                map.put(hash, new MutableInt());
            } else {
                map.get(hash).increment();
            }

        }

        return hash;
    }

    /**
     * Calculates the hash code for the reaction in it's current state. As the
     * molecules need to be sorted this operation is more expensive and thus
     * non-optimal. The hash is therefore cached in a the variable
     * this.cachedHash which is 'nullified' if the state of the object changes.
     * Warning: This hash code will not persist between different virtual
     * machines if enumerations are used. However as the hash code is meant to
     * be calculated when needed this should not be a problem
     *
     * @return hash code the reaction, note  there is no guarantee of unique
     *         hash code and the equals method should be called if the the
     *         matching hashCodes are found
     */
    @Override
    public int hashCode() {

        int hash = super.hashCode();

        map.clear();
        map.put(hash, new MutableInt());

        int reactantHash = getParticipantHashCode(reactants);
        int productHash = getParticipantHashCode(products);

        if (!map.containsKey(reactantHash)) {
            map.put(reactantHash, new MutableInt());
        } else {
            map.get(reactantHash).increment();
        }
        hash ^= rotate(reactantHash, map);
        if (!map.containsKey(hash)) {
            map.put(hash, new MutableInt());
        } else {
            map.get(hash).increment();
        }

        if (!map.containsKey(productHash)) {
            map.put(productHash, new MutableInt());
        } else {
            map.get(productHash).increment();
        }
        hash ^= rotate(productHash, map);
        if (!map.containsKey(hash)) {
            map.put(hash, new MutableInt());
        } else {
            map.get(hash).increment();
        }

        return hash;

    }

    /**
     * Check equality of reactions based on state. Reactions are considered
     * equals if their reactants and products (coefficients and compartments)
     * are equals regardless of the side they reside on. For example A + B <-> C
     * + D is considered equals to C + D <-> A + B. The participant
     * stoichiometric coefficients and compartments are also checked.
     * <p/>
     * To accomplish this the reactionParticipants and copied, sorted and then a
     * hash for each side (four total) is made. Duplicate hashes are then
     * removed via the {@See Set} interface and the query (this) and the other
     * (obj) sides are tested for coefficient, compartment and finally molecule
     * similarity
     *
     * @param obj The other object to test equality with
     * @return Whether the objects are considered equals
     */
    @Override
    public boolean equals(Object obj) {

        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        if (!super.equals(obj)) {
            return false;
        }

        final AbstractReaction<P> other = (AbstractReaction<P>) obj;

        if (this.generic == false && other.generic == false) {

            map.clear();
            int queryReactantHash = getParticipantHashCode(this.reactants);
            int queryProductHash = getParticipantHashCode(this.products);
            map.clear();
            int otherReactantHash = getParticipantHashCode(other.reactants);
            int otherProductHash = getParticipantHashCode(other.products);

            /* calculate the hashes for these all the reaction sides and flattern into a hashset to test the length */
            Set hashes = new HashSet<Integer>(
                    Arrays.asList(queryReactantHash, queryProductHash, otherReactantHash, otherProductHash));

            Set<P> queryReactants = new HashSet<P>(this.reactants);
            Set<P> queryProducts = new HashSet<P>(this.products);
            Set<P> otherReactants = new HashSet<P>(other.reactants);
            Set<P> otherProducts = new HashSet<P>(other.products);

            /* Check the correct number of side */
            if (hashes.size() != 2) {
                // not handling cases where reactants and products are the same.. could just be same hashcode
                if (hashes.size() == 1) {
                    if (queryReactants.containsAll(otherReactants) && queryReactants.containsAll(queryProducts)
                            && queryProducts.containsAll(otherProducts)) {
                        return true;
                    }
                    throw new UnsupportedOperationException(
                            "Reaction.equals(): Unresolvable reaction comparision [1]\n\t" + this + "\n\t" + other);
                }
                return false;
            }

            // these are sorted so can do direct equals on the sets
            if (queryReactantHash == otherReactantHash) {
                return queryReactants.containsAll(otherReactants) && queryProducts.containsAll(otherProducts);
            } else if (queryReactantHash == otherProductHash) {
                return queryReactants.containsAll(otherProducts) && queryProducts.containsAll(otherReactants);
            } else {
                return false; // this.reac == this.prod and other.reac == other.prod... and so must be false (2x hashe values)
            }

        } else {
            LOGGER.info("Using generic comparisson");
            // XXX May be a quicker way but for not this works
            if (genericEquals(this.reactants, other.reactants) && genericEquals(this.products, other.products)) {
                return true;
            }
            if (genericEquals(this.reactants, other.products) && genericEquals(this.products, other.reactants)) {
                return true;
            }
        }

        return false;

    }

    /**
     * A simple hack that checks will be improved in future. Checks whether the
     * unsorted lists are matches
     *
     * @param query
     * @param other
     * @return
     */
    private boolean genericEquals(List<P> query, List<P> other) {

        if (query.size() != other.size()) {
            return false;
        }

        for (P p : query) {

            if (containedIn(other, p) == Boolean.FALSE) {
                return false;
            }

            other.remove(p);
        }

        return true;

    }

    /**
     * Rotates the seed if the seed has already been seen in the provided
     * occurrences map
     *
     * @param seed
     * @param occurences
     * @return
     */
    public static int rotate(int seed, Map<Integer, MutableInt> occurences) {
        if (occurences.get(seed) == null) {
            occurences.put(seed, new MutableInt());
        } else {
            occurences.get(seed).increment();
        }
        // System.out.printf("%10s", occMap.get(seed).value);
        return rotate(seed, occurences.get(seed).intValue());
    }

    /**
     * Rotates the seed using xor shift (pseudo random number generation) the
     * specified number of times.
     *
     * @param seed     the starting seed
     * @param rotation Number of xor rotations to perform
     * @return The starting seed rotated the specified number of times
     */
    public static int rotate(int seed, int rotation) {
        for (int j = 0; j < rotation; j++) {
            seed = xorShift(seed);
        }
        return seed;
    }

    public static int xorShift(int seed) {
        seed ^= seed << 6;
        seed ^= seed >>> 21;
        seed ^= (seed << 7);
        return seed;
    }

    public Boolean containedIn(List<P> list, P other) {
        for (P p : list) {
            if (other.equals(p)) {
                return true;
            }
        }
        // return list.contains(this); // this doens't work for some reason
        return false;
    }

    /**
     * Displays the objects stored in the reaction in string form prefixed with
     * the stoichiometric coefficients and post fixed with compartments if
     * either exists. The reaction reversibility is determined by the
     * enumeration value {@see Reversibility} enumeration. Example
     * representation: <code>(1)A [e] + (1)B [c] <=?=> (1)C [c] + (1)A
     * [c]</code>     *
     *
     * @return textual representation of the reaction
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(40);

        sb.append(Joiner.on(" + ").join(reactants)).append(' ').append(direction).append(' ')
                .append(Joiner.on(" + ").join(products));

        return sb.toString();
    }

    public void clear() {
        reactants.clear();
        products.clear();
    }
}