/* $RCSfile$
* $Author$
* $Date$
* $Revision$
*
* Copyright (C) 2004-2008 Rajarshi Guha <rajarshi.guha@gmail.com>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* 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 2.1
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import javax.vecmath.Point3d;
import java.util.*;
/**
* A memory-efficient data structure to store conformers for a single molecule.
* <p/>
* Since all the conformers for a given molecule only differ in their 3D coordinates
* this data structure stores a single {@link IAtomContainer} containing the atom and bond
* details and a List of 3D coordinate sets, each element being the set of 3D coordinates
* for a given conformer.
* <p/>
* The class behaves in many ways as a List<IAtomContainer> object, though a few methods are not
* implemented. Though it is possible to add conformers by hand, this data structure is
* probably best used in combination with {@link org.openscience.cdk.io.iterator.IteratingMDLConformerReader} as
* <pre>
* IteratingMDLConformerReader reader = new IteratingMDLConformerReader(
* new FileReader(new File(filename)),
* DefaultChemObjectBuilder.getInstance());
* while (reader.hasNext()) {
* ConformerContainer cc = (ConformerContainer) reader.next();
* for (IAtomContainer conformer : cc) {
* // do something with each conformer
* }
* }
* </pre>
*
* @cdk.module data
* @cdk.githash
* @author Rajarshi Guha
* @see org.openscience.cdk.io.iterator.IteratingMDLConformerReader
*/
@TestClass("org.openscience.cdk.ConformerContainer")
public class ConformerContainer implements List<IAtomContainer> {
private IAtomContainer atomContainer = null;
private String title = null;
private List<Point3d[]> coordinates;
private Point3d[] getCoordinateList(IAtomContainer atomContainer) {
Point3d[] tmp = new Point3d[atomContainer.getAtomCount()];
for (int i = 0; i < atomContainer.getAtomCount(); i++) {
IAtom atom = atomContainer.getAtom(i);
if (atom.getPoint3d() == null) throw new NullPointerException("Molecule must have 3D coordinates");
tmp[i] = new Point3d(atom.getPoint3d());
}
return tmp;
}
public ConformerContainer() {
coordinates = new ArrayList<Point3d[]>();
}
/**
* Create a ConformerContainer object from a single molecule object.
* <p/>
* Using this constructor, the resultant conformer container will
* contain a single conformer. More conformers can be added using the
* {@link #add} method.
* <p/>
* Note that the constructor will use the title of the input molecule
* when adding new molecules as conformers. That is, the title of any molecule
* to be added as a conformer should match the title of the input molecule.
*
* @param atomContainer The base molecule (or first conformer).
*/
public ConformerContainer(IAtomContainer atomContainer) {
this.atomContainer = atomContainer;
title = (String) atomContainer.getProperty(CDKConstants.TITLE);
coordinates = new ArrayList<Point3d[]>();
coordinates.add(getCoordinateList(atomContainer));
}
/**
* Create a ConformerContainer from an array of molecules.
* <p/>
* This constructor can be used when you have an array of conformers of a given
* molecule. Note that this constructor will assume that all molecules in the
* input array will have the same title.
*
* @param atomContainers The array of conformers
*/
public ConformerContainer(IAtomContainer[] atomContainers) {
if (atomContainers.length == 0)
throw new IllegalArgumentException("Can't use a zero-length molecule array");
// lets check that the titles match
title = (String) atomContainers[0].getProperty(CDKConstants.TITLE);
for (IAtomContainer atomContainer : atomContainers) {
String nextTitle = (String) atomContainer.getProperty(CDKConstants.TITLE);
if (title != null && !nextTitle.equals(title))
throw new IllegalArgumentException("Titles of all molecules must match");
}
this.atomContainer = atomContainers[0];
coordinates = new ArrayList<Point3d[]>();
for (IAtomContainer container : atomContainers) {
coordinates.add(getCoordinateList(container));
}
}
/**
* Get the title of the conformers.
* <p/>
* Note that all conformers for a given molecule will have the same
* title.
*
* @return The title for the conformers
*/
@TestMethod("testGetTitle")
public String getTitle() {
return title;
}
/**
* Get the number of conformers stored.
*
* @return The number of conformers
*/
@TestMethod("testSize")
public int size() {
return coordinates.size();
}
/**
* Checks whether any conformers are stored or not.
*
* @return true if there is at least one conformer, otherwise false
*/
@TestMethod("testIsEmpty")
public boolean isEmpty() {
return coordinates.isEmpty();
}
/**
* Checks to see whether the specified conformer is currently stored.
* <p/>
* This method first checks whether the title of the supplied molecule
* matches the stored title. If not, it returns false. If the title matches
* it then checks all the coordinates to see whether they match. If all
* coordinates match it returns true else false.
*
* @param o The IAtomContainer to check for
* @return true if it is present, false otherwise
*/
@TestMethod("testContains, testContains_Object")
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* Gets an iterator over the conformers.
*
* @return an iterator over the conformers. Each iteration will return an IAtomContainer object
* corresponding to the current conformer.
*/
@TestMethod("testIterator, testIterator2")
public Iterator<IAtomContainer> iterator() {
return new CCIterator();
}
/**
* Returns the conformers in the form of an array of IAtomContainers.
* <p/>
* Beware that if you have a large number of conformers you may run out
* memory during construction of the array since IAtomContainer's are not
* light weight objects!
*
* @return The conformers as an array of individual IAtomContainers.
*/
@TestMethod("testToArray")
public Object[] toArray() {
IAtomContainer[] ret = new IAtomContainer[coordinates.size()];
int index = 0;
for (Point3d[] coords : coordinates) {
try {
IAtomContainer conf = (IAtomContainer) atomContainer.clone();
for (int i = 0; i < coords.length; i++) {
IAtom atom = conf.getAtom(i);
atom.setPoint3d(coords[i]);
}
ret[index++] = conf;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
return ret;
}
@TestMethod("testToArray_arrayObject")
public <IAtomContainer> IAtomContainer[] toArray(IAtomContainer[] ts) {
throw new UnsupportedOperationException();
}
/**
* Add a conformer to the end of the list.
* <p/>
* This method allows you to add a IAtomContainer object as another conformer.
* Before adding it ensures that the title of specific object matches the
* stored title for these conformers. It will also check that the number of
* atoms in the specified molecule match the number of atoms in the current set
* of conformers.
* <p/>
* This method will not check for duplicate conformers.
*
* @param atomContainer The new conformer to add.
* @return true
*/
@TestMethod("testAdd_IAtomContainer")
public boolean add(IAtomContainer atomContainer) {
if (this.atomContainer == null) {
this.atomContainer = atomContainer;
title = (String) atomContainer.getProperty(CDKConstants.TITLE);
}
if (title==null){
throw new IllegalArgumentException(
"At least one of the input molecules does not have a title");
}
if (!title.equals(atomContainer.getProperty(CDKConstants.TITLE)))
throw new IllegalArgumentException(
"The input molecules does not have the same title ('" + title +
"') as the other conformers ('" + atomContainer.getProperty(CDKConstants.TITLE) + "')");
if (atomContainer.getAtomCount() != this.atomContainer.getAtomCount())
throw new IllegalArgumentException("Doesn't have the same number of atoms as the rest of the conformers");
coordinates.add(getCoordinateList(atomContainer));
return true;
}
/**
* Remove the specified conformer.
*
* @param o The conformer to remove (should be castable to IAtomContainer)
* @return true if the specified conformer was present and removed, false if not found
*/
@TestMethod("testRemove_Object")
public boolean remove(Object o) {
IAtomContainer atomContainer = (IAtomContainer) o;
// we should never have a null conformer
if (atomContainer == null) return false;
int index = indexOf(atomContainer);
if (index >= 0) {
remove(index);
return true;
}
return false;
}
@TestMethod("testContainsAll_Collection")
public boolean containsAll(Collection<?> objects) {
throw new UnsupportedOperationException();
}
@TestMethod("testAddAll_Collection")
public boolean addAll(Collection<? extends IAtomContainer> atomContainers) {
throw new UnsupportedOperationException();
}
@TestMethod("testAddAll_int_Collection")
public boolean addAll(int i, Collection<? extends IAtomContainer> iAtomContainers) {
throw new UnsupportedOperationException();
}
@TestMethod("testRemoveAll_Collectio")
public boolean removeAll(Collection<?> objects) {
throw new UnsupportedOperationException();
}
@TestMethod("testRetainAll_Collection")
public boolean retainAll(Collection<?> objects) {
throw new UnsupportedOperationException();
}
/**
* Get rid of all the conformers but keeps atom and bond information.
*/
@TestMethod("testClear")
public void clear() {
coordinates.clear();
}
/**
* Get the conformer at a specified position.
*
* @param i The position of the requested conformer
* @return The conformer
*/
@TestMethod("testGet_int, testGet2")
public IAtomContainer get(int i) {
Point3d[] tmp = coordinates.get(i);
for (int j = 0; j < atomContainer.getAtomCount(); j++) {
IAtom atom = atomContainer.getAtom(j);
atom.setPoint3d(tmp[j]);
}
return atomContainer;
}
@TestMethod("testSet_int_IAtomContainer")
public IAtomContainer set(int i, IAtomContainer atomContainer) {
if (!title.equals(atomContainer.getProperty(CDKConstants.TITLE)))
throw new IllegalArgumentException("The input molecules does not have the same title as the other conformers");
Point3d[] tmp = getCoordinateList(atomContainer);
IAtomContainer oldAtomContainer = get(i);
coordinates.set(i, tmp);
return oldAtomContainer;
}
@TestMethod("testAdd_int_IAtomContainer")
public void add(int i, IAtomContainer atomContainer) {
if (this.atomContainer == null) {
this.atomContainer = atomContainer;
title = (String) atomContainer.getProperty(CDKConstants.TITLE);
}
if (!title.equals(atomContainer.getProperty(CDKConstants.TITLE)))
throw new IllegalArgumentException("The input molecules does not have the same title as the other conformers");
if (atomContainer.getAtomCount() != this.atomContainer.getAtomCount())
throw new IllegalArgumentException("Doesn't have the same number of atoms as the rest of the conformers");
Point3d[] tmp = getCoordinateList(atomContainer);
coordinates.add(i, tmp);
}
/**
* Removes the conformer at the specified position.
*
* @param i The position in the list to remove
* @return The conformer that was at the specified position
*/
@TestMethod("testRemove_int")
public IAtomContainer remove(int i) {
IAtomContainer oldAtomContainer = get(i);
coordinates.remove(i);
return oldAtomContainer;
}
/**
* Returns the lowest index at which the specific IAtomContainer appears in the list or -1 if is not found.
* <p/>
* A given IAtomContainer will occur in the list if the title matches the stored title for
* the conformers in this container and if the coordinates for each atom in the specified molecule
* are equal to the coordinates of the corresponding atoms in a conformer.
*
* @param o The IAtomContainer whose presence is being tested
* @return The index where o was found
*/
@TestMethod("testIndexOf_Object")
public int indexOf(Object o) {
IAtomContainer atomContainer = (IAtomContainer) o;
if (!atomContainer.getProperty(CDKConstants.TITLE).equals(title)) return -1;
if (atomContainer.getAtomCount() != this.atomContainer.getAtomCount()) return -1;
boolean coordsMatch;
int index = 0;
for (Point3d[] coords : coordinates) {
coordsMatch = true;
for (int i = 0; i < atomContainer.getAtomCount(); i++) {
Point3d p = atomContainer.getAtom(i).getPoint3d();
if (!(p.x == coords[i].x && p.y == coords[i].y && p.z == coords[i].z)) {
coordsMatch = false;
break;
}
}
if (coordsMatch) return index;
index++;
}
return -1;
}
/**
* Returns the highest index at which the specific IAtomContainer appears in the list or -1 if is not found.
* <p/>
* A given IAtomContainer will occur in the list if the title matches the stored title for
* the conformers in this container and if the coordinates for each atom in the specified molecule
* are equal to the coordinates of the corresponding atoms in a conformer.
*
* @param o The IAtomContainer whose presence is being tested
* @return The index where o was found
*/
@TestMethod("testLastIndexOf_Object")
public int lastIndexOf(Object o) {
IAtomContainer atomContainer = (IAtomContainer) o;
if (!atomContainer.getProperty(CDKConstants.TITLE).equals(title)) return -1;
if (atomContainer.getAtomCount() != coordinates.get(0).length) return -1;
boolean coordsMatch;
for (int j = coordinates.size() - 1; j >= 0; j--) {
Point3d[] coords = coordinates.get(j);
coordsMatch = true;
for (int i = 0; i < atomContainer.getAtomCount(); i++) {
Point3d p = atomContainer.getAtom(i).getPoint3d();
if (!(p.x == coords[i].x && p.y == coords[i].y && p.z == coords[i].z)) {
coordsMatch = false;
break;
}
}
if (coordsMatch) return j;
}
return -1;
}
@TestMethod("testListIterator")
public ListIterator<IAtomContainer> listIterator() {
throw new UnsupportedOperationException();
}
@TestMethod("testListIterator_int")
public ListIterator<IAtomContainer> listIterator(int i) {
throw new UnsupportedOperationException();
}
@TestMethod("testSubList_int_int")
public List<IAtomContainer> subList(int i, int i1) {
throw new UnsupportedOperationException();
}
private class CCIterator implements Iterator<IAtomContainer> {
int current = 0;
int last = -1;
public boolean hasNext() {
return current != coordinates.size();
}
public IAtomContainer next() {
Point3d[] tmp = coordinates.get(current);
for (int j = 0; j < atomContainer.getAtomCount(); j++) {
IAtom atom = atomContainer.getAtom(j);
atom.setPoint3d(tmp[j]);
}
last = current++;
return atomContainer;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}