Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

982 lines (801 sloc) 20.915 kb
/*
* MX - Essential Cheminformatics
*
* Copyright (c) 2007-2009 Metamolecular, LLC
*
* http://metamolecular.com/mx
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.metamolecular.mx.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* @author Richard L. Apodaca <rapodaca at metamolecular.com>
* @author Duan Lian
*/
public class DefaultMolecule implements Molecule
{
private VirtualHydrogenCounter hCounter;
private List listeners;
private List atoms;
private List bonds;
private List substructures;
private int modifyDepth;
private boolean changed;
private ChangeEvent event;
public DefaultMolecule()
{
hCounter = new VirtualHydrogenCounter();
atoms = new ArrayList();
bonds = new ArrayList();
substructures = new ArrayList();
listeners = null;
modifyDepth = 0;
changed = false;
event = new ChangeEvent(this);
}
public Atom addAtom(String symbol, double x, double y, double z)
{
assertAtomSymbolValid(symbol);
AtomImpl atom = new AtomImpl(this);
atom.symbol = symbol;
atom.x = x;
atom.y = y;
atom.z = z;
atoms.add(atom);
fireChange();
return atom;
}
public Atom addAtom(String symbol)
{
return addAtom(symbol, 0, 0, 0);
}
public void addChangeListener(ChangeListener listener)
{
if (listeners == null)
{
listeners = new ArrayList();
}
listeners.add(listener);
}
public void removeChangeListener(ChangeListener listener)
{
listeners.remove(listener);
}
public void beginModify()
{
modifyDepth++;
}
public void clear()
{
atoms.clear();
bonds.clear();
substructures.clear();
fireChange();
}
public Bond connect(Atom source, Atom target, int type, int stereo)
{
assertAtomBelongs(source);
assertAtomBelongs(target);
assertAtomsAreDifferent(source, target);
assertBondTypeValid(type);
assertBondStereoValid(stereo);
BondImpl bond = new BondImpl(this);
bond.source = source;
bond.target = target;
bond.type = type;
bond.stereo = stereo;
bonds.add(bond);
AtomImpl sourceImpl = (AtomImpl) source;
AtomImpl targetImpl = (AtomImpl) target;
sourceImpl.neighbors.add(target);
sourceImpl.bonds.add(bond);
targetImpl.neighbors.add(source);
targetImpl.bonds.add(bond);
fireChange();
return bond;
}
public Bond connect(Atom source, Atom target, int type)
{
return connect(source, target, type, 0);
}
public void removeBond(Bond bond)
{
for (int i = 0; i < substructures.size(); i++)
{
Superatom substructure = (Superatom) substructures.get(i);
if (substructure.contains(bond))
{
substructure.removeCrossingBond(bond);
}
}
disconnect(bond.getSource(), bond.getTarget());
}
public void disconnect(Atom source, Atom target)
{
BondImpl bond = (BondImpl) getBond(source, target);
if (bond == null)
{
throw new RuntimeException("Attempt to disconnect unconnected atoms.");
}
AtomImpl sourceImpl = (AtomImpl) source;
AtomImpl targetImpl = (AtomImpl) target;
sourceImpl.bonds.remove(bond);
sourceImpl.neighbors.remove(target);
targetImpl.bonds.remove(bond);
targetImpl.neighbors.remove(source);
bonds.remove(bond);
fireChange();
}
private void addSgroup(Superatom substructure)
{
if (substructure.getMolecule() != this)
{
throw new IllegalStateException("Attempt to add substructure of another molecule.");
}
substructures.add(substructure);
}
public void removeSuperatom(Superatom substructure)
{
if (substructure.getMolecule() != this)
{
throw new IllegalStateException("Attempt to remove substructure of another molecule.");
}
if (!substructures.contains(substructure))
{
throw new IllegalStateException("Attempt to remove non-existant substructure.");
}
substructures.remove(substructure);
}
public void endModify()
{
modifyDepth--;
if (modifyDepth == 0)
{
if (changed)
{
changed = false;
fireChange();
}
}
}
public void removeAtom(Atom atom)
{
assertAtomBelongs(atom);
Atom[] neighbors = atom.getNeighbors();
beginModify();
for (int i = 0; i < neighbors.length; i++)
{
disconnect(atom, neighbors[i]);
}
for (int i = 0; i < substructures.size(); i++)
{
Superatom substructure = (Superatom) substructures.get(i);
if (substructure.contains(atom))
{
substructure.removeAtom(atom);
}
}
((AtomImpl) atom).molecule = null;
atoms.remove(atom);
// do this so that deleting an unconnected atom will
// fire event
if (neighbors.length == 0)
{
fireChange();
}
endModify();
}
public int countAtoms()
{
return atoms.size();
}
public int countBonds()
{
return bonds.size();
}
public int countSuperatoms()
{
return substructures.size();
}
public Atom getAtom(int index)
{
return (Atom) atoms.get(index);
}
public int getAtomIndex(Atom atom)
{
return atoms.indexOf(atom);
}
/* (non-Javadoc)
* @see com.metamolecular.firefly.model.Molecule#getCrossingBond(int)
*/
public Bond getBond(int index)
{
return (Bond) bonds.get(index);
}
public Bond getBond(Atom source, Atom target)
{
if (source.getMolecule() != this || target.getMolecule() != this)
{
return null;
}
if (source == target)
{
return null;
}
AtomImpl sourceImpl = (AtomImpl) source;
for (int i = 0; i < sourceImpl.bonds.size(); i++)
{
Bond bond = (Bond) sourceImpl.bonds.get(i);
if (bond.getSource() == target || bond.getTarget() == target)
{
return bond;
}
}
return null;
}
public Superatom getSuperatom(int i)
{
return (Superatom) substructures.get(i);
}
public Superatom addSuperatom()
{
SuperatomImpl sgroup = new SuperatomImpl(this);
addSgroup(sgroup);
return sgroup;
}
public int getBondIndex(Bond bond)
{
return bonds.indexOf(bond);
}
public Molecule copy()
{
DefaultMolecule result = new DefaultMolecule();
for (int i = 0; i < atoms.size(); i++)
{
AtomImpl oldAtom = (AtomImpl) atoms.get(i);
AtomImpl newAtom = new AtomImpl(result, oldAtom);
result.atoms.add(newAtom);
}
for (int i = 0; i < bonds.size(); i++)
{
BondImpl oldBond = (BondImpl) bonds.get(i);
AtomImpl newSource = (AtomImpl) result.atoms.get(atoms.indexOf(oldBond.source));
AtomImpl newTarget = (AtomImpl) result.atoms.get(atoms.indexOf(oldBond.target));
BondImpl newBond = (BondImpl) result.connect(newSource, newTarget, oldBond.type);
newBond.stereo = oldBond.stereo;
}
for (int i = 0; i < this.countSuperatoms(); i++)
{
Superatom substructure = this.getSuperatom(i);
Superatom newSubstructure = result.addSuperatom();
for (int j = 0; j < substructure.countAtoms(); j++)
{
newSubstructure.addAtom(result.getAtom(substructure.getAtom(j).getIndex()));
}
for (int j = 0; j < substructure.countCrossingBonds(); j++)
{
Bond crossingBond = substructure.getCrossingBond(j);
Bond bond = result.getBond(crossingBond.getIndex());
newSubstructure.addCrossingBond(bond);
newSubstructure.setCrossingVector(bond, substructure.getCrossingVectorX(crossingBond), substructure.getCrossingVectorY(crossingBond));
}
newSubstructure.setLabel(substructure.getLabel());
}
return result;
}
public void copy(Molecule molecule)
{
beginModify();
clear();
for (int i = 0; i < molecule.countAtoms(); i++)
{
Atom atom = molecule.getAtom(i);
Atom copy = addAtom(atom.getSymbol(), atom.getX(), atom.getY(), atom.getZ());
copy.setCharge(atom.getCharge());
if (atom.hasSingleIsotope())
{
copy.setIsotope(atom.getIsotope());
}
copy.setRadical(atom.getRadical());
}
for (int i = 0; i < molecule.countBonds(); i++)
{
Bond bond = molecule.getBond(i);
Atom source = getAtom(bond.getSource().getIndex());
Atom target = getAtom(bond.getTarget().getIndex());
connect(source, target, bond.getType(), bond.getStereo());
}
for (int i = 0; i < molecule.countSuperatoms(); i++)
{
Superatom superatom = molecule.getSuperatom(i);
Superatom newSubstructure = this.addSuperatom();
for (int j = 0; j < superatom.countAtoms(); j++)
{
newSubstructure.addAtom(this.getAtom(superatom.getAtom(j).getIndex()));
}
for (int j = 0; j < superatom.countCrossingBonds(); j++)
{
Bond crossingBond = superatom.getCrossingBond(j);
Bond bond = this.getBond(crossingBond.getIndex());
newSubstructure.addCrossingBond(bond);
newSubstructure.setCrossingVector(bond, superatom.getCrossingVectorX(crossingBond), superatom.getCrossingVectorY(crossingBond));
}
newSubstructure.setLabel(superatom.getLabel());
}
endModify();
}
protected void fireChange()
{
if (modifyDepth != 0)
{
changed = true;
return;
}
if (listeners == null)
{
return;
}
for (int i = 0; i < listeners.size(); i++)
{
ChangeListener l = (ChangeListener) listeners.get(i);
l.stateChanged(event);
}
}
protected void assertAtomBelongs(Atom atom)
{
if (atom.getMolecule() != this)
{
throw new IllegalStateException("Attempt to use a non-member atom.");
}
}
protected void assertBondBelongs(Bond bond)
{
if (bond.getMolecule() != this)
{
throw new IllegalStateException("Attempt to use a non-member bond.");
}
}
private void assertBondTypeValid(int type)
{
if (type < 1 || type > 3)
{
throw new IllegalStateException("Invalid bond type " + type + " (1-3)");
}
}
private void assertBondStereoValid(int stereo)
{
if (stereo >= 0 && stereo <= 6)
{
if (stereo != 2 && stereo != 5)
{
return;
}
}
throw new IllegalStateException("Invalid bond stereo " + stereo + " (0, 1, 3, 5, 6)");
}
private void assertAtomsAreDifferent(Atom source, Atom target)
{
if (source == target)
{
throw new IllegalStateException("Attempt to connect Atom to itself");
}
}
private void assertAtomSymbolValid(String symbol)
{
if (!AtomicSystem.getInstance().hasElement(symbol))
{
throw new IllegalStateException("Unsupported atom type \"" + symbol + "\"");
}
}
private void assertRadicalValid(int radical)
{
if (radical < 0 || radical > 3)
{
throw new IllegalStateException("Valid radical values are 0-3.");
}
}
private class AtomImpl implements Atom
{
private List neighbors;
private List bonds;
private DefaultMolecule molecule;
private String symbol;
private double x;
private double y;
private double z;
private int massDifference;
private int charge;
private int radical;
private boolean hasSingleIsotope;
private AtomImpl(DefaultMolecule parent, AtomImpl copyFrom)
{
this(parent);
symbol = copyFrom.symbol;
x = copyFrom.x;
y = copyFrom.y;
z = copyFrom.z;
massDifference = copyFrom.massDifference;
charge = copyFrom.charge;
radical = copyFrom.radical;
hasSingleIsotope = copyFrom.hasSingleIsotope;
}
private AtomImpl(DefaultMolecule parent)
{
neighbors = new ArrayList();
bonds = new ArrayList();
molecule = parent;
symbol = "";
}
public void setRadical(int radical)
{
assertRadicalValid(radical);
this.radical = radical;
fireChange();
}
public void setIsotope(int isotope)
{
this.massDifference = isotope;
this.hasSingleIsotope = true;
fireChange();
}
public void setCharge(int charge)
{
this.charge = charge;
fireChange();
}
public boolean isConnectedTo(Atom atom)
{
return neighbors.contains(atom);
}
public int getIndex()
{
return molecule.atoms.indexOf(this);
}
public int getValence()
{
int result = 0;
for (int i = 0; i < bonds.size(); i++)
{
result += ((Bond) bonds.get(i)).getType();
}
return result;
}
public int countNeighbors()
{
return neighbors.size();
}
public int countVirtualHydrogens()
{
return hCounter.countVirtualHydrogens(this);
}
public Bond[] getBonds()
{
return (Bond[]) bonds.toArray(new Bond[0]);
}
public Bond getBond(Atom neighbor)
{
for (int i = 0; i < bonds.size(); i++)
{
Bond bond = (Bond) bonds.get(i);
Atom mate = bond.getMate(this);
if (mate == neighbor)
{
return bond;
}
}
throw new RuntimeException("Attempting to get Bond to non-neighbor Atom " + neighbor);
}
public int getCharge()
{
return charge;
}
public int getIsotope()
{
return massDifference;
}
public Molecule getMolecule()
{
return molecule;
}
public Atom[] getNeighbors()
{
return (Atom[]) neighbors.toArray(new Atom[0]);
}
public int getRadical()
{
return radical;
}
public String getSymbol()
{
return symbol;
}
public void setSymbol(String newSymbol)
{
assertAtomSymbolValid(newSymbol);
this.symbol = newSymbol;
fireChange();
}
public double getX()
{
return x;
}
public double getY()
{
return y;
}
public double getZ()
{
return z;
}
public void move(double x, double y, double z)
{
if (this.x == x && this.y == y && this.z == z)
{
return;
}
this.x = x;
this.y = y;
this.z = z;
fireChange();
}
public boolean hasSingleIsotope()
{
return hasSingleIsotope;
}
}
private class BondImpl implements Bond
{
private Atom source;
private Atom target;
private int type;
private int stereo;
private Molecule molecule;
private BondImpl(Molecule parent)
{
source = null;
target = null;
molecule = parent;
}
public void reverse()
{
Atom oldSource = source;
Atom oldTarget = target;
source = oldTarget;
target = oldSource;
fireChange();
}
public void setStereo(int stereo)
{
assertBondStereoValid(stereo);
this.stereo = stereo;
fireChange();
}
public void setType(int type)
{
assertBondTypeValid(type);
this.type = type;
fireChange();
}
public int getIndex()
{
return DefaultMolecule.this.bonds.indexOf(this);
}
public boolean contains(Atom atom)
{
return (atom == source || atom == target);
}
public Atom getMate(Atom atom)
{
if (source.equals(atom))
{
return target;
}
if (target.equals(atom))
{
return source;
}
return null;
}
public Molecule getMolecule()
{
return molecule;
}
public Atom[] getNeighborAtoms()
{
Atom[] result = new Atom[source.countNeighbors() + target.countNeighbors() - 2];
int counter = 0;
Atom[] sourceNeighbors = source.getNeighbors();
Atom[] targetNeighbors = target.getNeighbors();
for (int i = 0; i < sourceNeighbors.length; i++)
{
Atom neighbor = sourceNeighbors[i];
if (neighbor == source || neighbor == target)
{
continue;
}
result[counter] = neighbor;
counter++;
}
for (int i = 0; i < targetNeighbors.length; i++)
{
Atom neighbor = targetNeighbors[i];
if (neighbor == source || neighbor == target)
{
continue;
}
result[counter] = neighbor;
counter++;
}
return result;
}
public Atom getSource()
{
return source;
}
public int getStereo()
{
return stereo;
}
public Atom getTarget()
{
return target;
}
public int getType()
{
return type;
}
}
private class SuperatomImpl implements Superatom
{
private List atoms;
private List bonds;
private String label;
private Molecule molecule;
private Map bondVectorMap;
public SuperatomImpl(Molecule parent)
{
molecule = parent;
atoms = new ArrayList();
bonds = new ArrayList();
bondVectorMap = new HashMap();
label = "";
}
public String getLabel()
{
return label;
}
public void setLabel(String label)
{
this.label = label;
fireChange();
}
public int countAtoms()
{
return atoms.size();
}
public int countCrossingBonds()
{
return bonds.size();
}
public Atom getAtom(int index)
{
return (Atom) atoms.get(index);
}
public Bond getCrossingBond(int index)
{
return (Bond) bonds.get(index);
}
public boolean contains(Atom atom)
{
return atoms.contains(atom);
}
public boolean contains(Bond bond)
{
return bonds.contains(bond);
}
public void addAtom(Atom atom)
{
if (contains(atom))
{
throw new RuntimeException("Trying to add the same atom twice");
}
assertAtomBelongs(atom);
atoms.add(atom);
fireChange();
}
public void removeAtom(Atom atom)
{
if (!contains(atom))
{
throw new RuntimeException("Trying to remove the non-existant atom");
}
assertAtomBelongs(atom);
atoms.remove(atom);
fireChange();
}
public void addCrossingBond(Bond bond)
{
assertBondBelongs(bond);
if (contains(bond))
{
throw new RuntimeException("Trying to add the same crossing bond twice");
}
bonds.add(bond);
//add default bond vector
double x = bond.getTarget().getX() - bond.getSource().getX();
double y = bond.getTarget().getY() - bond.getSource().getY();
bondVectorMap.put(bond, new double[]
{
x, y
});
fireChange();
}
public void removeCrossingBond(Bond bond)
{
if (!contains(bond))
{
throw new RuntimeException("Trying to remove the non-existant crossing bond");
}
assertCrossingBondBelongs(bond);
bonds.remove(bond);
fireChange();
}
public void setCrossingVector(Bond bond, double x, double y)
{
assertCrossingBondBelongs(bond);
bondVectorMap.put(bond, new double[]
{
x, y
});
fireChange();
}
public double getCrossingVectorX(Bond bond)
{
assertCrossingBondBelongs(bond);
return ((double[]) bondVectorMap.get(bond))[0];
}
public double getCrossingVectorY(Bond bond)
{
assertCrossingBondBelongs(bond);
return ((double[]) bondVectorMap.get(bond))[1];
}
public int getIndex()
{
return DefaultMolecule.this.substructures.indexOf(this);
}
public Molecule getMolecule()
{
return molecule;
}
private void assertCrossingBondBelongs(Bond bond)
{
assertBondBelongs(bond);
if (!bonds.contains(bond))
{
throw new IllegalStateException("Attempt to use a non-crossing bond.");
}
}
}
}
Jump to Line
Something went wrong with that request. Please try again.