Java tutorial
/** * Copyright (c) 2012-2013 Ojus Software Labs Private Limited. * * All rights reserved. Please see the files README.md, LICENSE and COPYRIGHT * for details. */ package com.ojuslabs.oct.data; import java.util.List; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.ojuslabs.oct.common.BondOrder; import com.ojuslabs.oct.common.BondStereo; import com.ojuslabs.oct.common.Constants; /** * Bond represents a chemical bond. This flavour is strictly between two atoms, * and does <b>not</b> cater to multi-bond requirements. */ public final class Bond { /* Unique ID of this bond in its molecule. */ private final int _id; private final Atom _a1; private final Atom _a2; private BondOrder _order; private BondStereo _stereo; /* Is this bond aromatic? */ private boolean _isAro; /* The rings in which this bond participates. */ private final List<Ring> _rings; /* Is this bond part of a chain linking two ring systems? */ private boolean _isLink; /* Cached in the object for faster search. */ private final int _hash; /** * The atoms participating in a bond cannot change. Accordingly, they have * be non-null and valid in the current molecule. However, this constructor * is package-internal. It is the responsibility of {@link Molecule} to * comply with these requirements. * * @param id * The unique ID of this bond. * @param a1 * The first atom participating in this bond. * @param a2 * The second atom participating in this bond. * @param order * The bond order of this bond. See {@link common.BondOrder} for * possible values. */ Bond(int id, Atom a1, Atom a2, BondOrder order) { _id = id; _a1 = a1; _a2 = a2; _hash = hash(_a1, _a2); _order = order; _stereo = BondStereo.NONE; _rings = Lists.newArrayListWithCapacity(Constants.LIST_SIZE_S); } /** * @return The unique ID of this bond. */ public int id() { return _id; } /** * @return The first atom participating in this bond. */ public Atom atom1() { return _a1; } /** * @return The second atom participating in this bond. */ public Atom atom2() { return _a2; } /** * @return Bond order of this bond. See {@link BondOrder} for possible bond * orders. */ public BondOrder order() { return _order; } /** * Sets the new order for this bond. It also adjusts the valence of the * member atoms appropriately. * * There are three broad scenarios. * <ol> * <li>The given bond order is the same as the current one. The method * returns immediately.</li> * <li>The given bond order violates the valence of at least one atom. An * exception is thrown.</li> * <li>The given bond order can be successfully set, and is set.</li> * </ol> * * @param o * The new bond order to set. See {@link BondOrder} for possible * bond orders. * @throws IllegalStateException * if the new bond order violates the valence configuration of * at least one atom. */ public void setOrder(BondOrder o) { if (o == _order) { return; } int delta = o.value() - _order.value(); int res = _a1.numberOfBonds() + delta; if (res > _a1.valence()) { throw new IllegalStateException(String.format( "Illegal state for atom: %d->%d. Current valence: %d. Number of bonds: %d, new bond order: %d", _a1.molecule().id(), _a1.id(), _a1.valence(), _a1.numberOfBonds(), o.value())); } res = _a2.numberOfBonds() + delta; if (res > _a2.valence()) { throw new IllegalStateException(String.format( "Illegal state for atom: %d->%d. Current valence: %d. Number of bonds: %d, new bond order: %d", _a2.molecule().id(), _a2.id(), _a2.valence(), _a2.numberOfBonds(), o.value())); } _order = o; } /** * @return The stereo configuration of this bond. See {@link BondStereo} for * possible stereo configurations. */ public BondStereo stereo() { return _stereo; } /** * @param s * The stereo configuration of this bond. See {@link BondStereo} * for possible stereo configurations. */ public void setStereo(BondStereo s) { _stereo = s; } /** * Answers the other participating atom in this bond. * * @param ido * ID of the atom whose pairing atom is requested. * @return The other atom participating in this bond. * @throws IllegalArgumentException * if the given atom is not a part of this bond. */ public Atom otherAtom(int ido) { if (_a1.id() == ido) { return _a2; } else if (_a2.id() == ido) { return _a1; } throw new IllegalArgumentException( String.format("Atoms in this bond: %d, %d; given ID: %d", _a1.id(), _a2.id(), ido)); } /** * Checks to see if the current bond binds the given atoms. * * @param a1 * One of the atoms in the bond. * @param a2 * The other atom in the bond. * @return {@code true} if this bond binds the given atoms; {@code false} * otherwise. */ public boolean binds(Atom a1, Atom a2) { if ((_a1 == a1) && (_a2 == a2)) { return true; } if ((_a1 == a2) && (_a2 == a1)) { return true; } return false; } /** * Computes and answers a unique hash for quick lookup. * * <b>N.B.</b> A better design should have this as a thread-local top-level * <i>function</i>. Unfortunately, Java does not offer a simple way of doing * that. * * @return A unique hash value that utilises the IDs of both the * participating atoms. */ public static int hash(Atom a1, Atom a2) { int result = 0; if (a1.id() < a2.id()) { result = 10000 * a1.id() + a2.id(); } else { result = 10000 * a2.id() + a1.id(); } return result; } /** * @return The precomputed hash value reflecting the atoms in this bond. */ int hashValue() { return _hash; } /** * Answers the aromaticity status of this bond. * * @return True if this bond has at least one aromatic atom participating. */ public boolean isAromatic() { return _isAro; } /** * Sets the new aromaticity status of this bond. * * @param aro * The new status of whether this bond is aromatic or not. */ void setAromatic(boolean aro) { _isAro = aro; } /** * <b>N.B.</b> The current bond must participate in the given ring; however, * this is assumed to have been taken care of by the parent molecule. * Accordingly, this method is package-internal. * * @param r * The ring to add to this bond. */ void addRing(Ring r) { if (!_rings.contains(r)) { _rings.add(r); } } /** * <b>N.B.</b> The current bond must participate in the given ring; however, * this is assumed to have been taken care of by the parent molecule. * Accordingly, this method is package-internal. * * @param r * The ring to remove from this bond. * @return {@code true} if the given ring was actually removed; * {@code false} otherwise. */ boolean removeRing(Ring r) { return _rings.remove(r); } /** * @param r * Ring in which we check this bond's membership. * @return True if this bond participates in the given ring; false * otherwise. */ public boolean inRing(Ring r) { return _rings.contains(r); } /** * @param n * Required size of the ring in which this bond has to * participate. * @return {@code true} if this bond participates in at least one such ring; * {@code false} otherwise. */ public boolean inRingOfSize(int n) { for (Ring r : _rings) { if (r.size() == n) { return true; } } return false; } /** * @return The smallest ring in which this bond participates, if one such * unique ring exists; {@code null} otherwise. * @throws IllegalStateException * if more than one ring of the smallest size are found. */ public Ring smallestRing() { int min = Integer.MAX_VALUE; int count = 0; Ring ret = null; for (Ring r : _rings) { int s = r.size(); if (s == min) { count++; } else if (s < min) { ret = r; count = 1; } } if (count > 1) { throw new IllegalStateException( String.format("Smallest ring size: %d, number of smallest rings: %d", ret.size(), count)); } if (1 == count) { return ret; } return null; } /** * @return The number of rings this bond participates in. */ public int numberOfRings() { return _rings.size(); } /** * @return {@code true} if this bond participates in at least one ring; * {@code false} otherwise. */ public boolean isCyclic() { return (_rings.size() > 0); } /** * @return A read-only copy of the rings this bond participates in. */ public List<Ring> rings() { return ImmutableList.copyOf(_rings); } /** * Resets this bond's ring information, as well as its aromaticity status. */ void resetRings() { _rings.clear(); _isAro = false; } /** * @param b * The new status indicating whether this bond is a linking bond * or not. */ public void setLinkStatus(boolean b) { _isLink = b; } /** * Answers if the current bond is a part of a linking chain of bonds between * two ring systems. * * @return {@code true} if this bond is a linking bond; {@code false} * otherwise. */ public boolean isLink() { return _isLink; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("Bond ID: %d [%d, %d]", _id, _a1.id(), _a2.id()); } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Bond)) { return false; } Bond other = (Bond) obj; if ((_order != other._order) || (_hash != other._hash)) { return false; } return true; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return _hash << _order.value(); } }