/*
* MX Cheminformatics Tools for Java
*
* Copyright (c) 2007, 2008 Metamolecular, LLC
*
* http://metamolecular.com
*
* 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.chemhack.jsMolEditor.client.model;
import com.chemhack.jsMolEditor.client.jre.emulation.java.event.ChangeListener;
import com.chemhack.jsMolEditor.client.jre.emulation.java.event.ChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
/**
* Modified with AtomicSystem usage removed to cut down size
* @author Richard L. Apodaca
* Duan Lian
*/
public class DefaultMolecule implements Molecule {
private VirtualHydrogenCounter hCounter;
private List<ChangeListener> listeners;
private List<Atom> atoms;
private List<Bond> bonds;
private int modifyDepth;
private boolean changed;
private ChangeEvent event;
public DefaultMolecule() {
hCounter = new VirtualHydrogenCounter();
atoms = new ArrayList<Atom>();
bonds = new ArrayList<Bond>();
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<ChangeListener>();
}
listeners.add(listener);
}
public void removeChangeListener(ChangeListener listener) {
listeners.remove(listener);
}
public void beginModify() {
modifyDepth++;
}
public void clear() {
atoms.clear();
bonds.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) {
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();
}
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]);
}
((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 Atom getAtom(int index) {
return atoms.get(index);
}
public int getAtomIndex(Atom atom) {
return atoms.indexOf(atom);
}
/* (non-Javadoc)
* @see com.metamolecular.firefly.model.Molecule#getBond(int)
*/
public Bond getBond(int index) {
return 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 = sourceImpl.bonds.get(i);
if (bond.getSource() == target || bond.getTarget() == target) {
return bond;
}
}
return null;
}
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);
result.atoms.add(new AtomImpl(result, oldAtom));
}
for (int i = 0; i < bonds.size(); i++) {
BondImpl oldBond = (BondImpl) bonds.get(i);
int sourceIndex = atoms.indexOf(oldBond.source);
int targetIndex = atoms.indexOf(oldBond.target);
BondImpl newBond = (BondImpl) result.connect((AtomImpl) result.atoms.get(sourceIndex),
(AtomImpl) result.atoms.get(targetIndex),
oldBond.type);
newBond.stereo = oldBond.stereo;
}
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());
}
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<Atom> neighbors;
private List<Bond> bonds;
private Molecule 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 HashMap<String,Object> properties;
private AtomImpl(Molecule 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(Molecule parent) {
neighbors = new ArrayList<Atom>();
bonds = new ArrayList<Bond>();
molecule = parent;
symbol = "";
properties=new HashMap<String,Object>();
}
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 DefaultMolecule.this.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 bonds.toArray(new Bond[bonds.size()]);
}
public int getCharge() {
return charge;
}
public int getIsotope() {
return massDifference;
}
public Molecule getMolecule() {
return molecule;
}
public Object getProperty(String key) {
return properties.get(key);
}
public void setProperty(String key, Object value) {
properties.put(key,value);
}
public Atom[] getNeighbors() {
return neighbors.toArray(new Atom[neighbors.size()]);
}
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;
}
}
}