1 /* Copyright (C) 2000-2007  Christoph Steinbeck <steinbeck@users.sf.net>
2  *
3  * Contact: cdk-devel@lists.sourceforge.net
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public License
7  * as published by the Free Software Foundation; either version 2.1
8  * of the License, or (at your option) any later version.
9  * All we ask is that proper credit is given for our work, which includes
10  * - but is not limited to - adding the above copyright notice to the beginning
11  * of your source code files, and to any copyright notice that you may distribute
12  * with programs based on this work.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 package org.openscience.cdk;
24 
25 import org.openscience.cdk.config.Elements;
26 import org.openscience.cdk.interfaces.IAtom;
27 import org.openscience.cdk.interfaces.IAtomContainer;
28 import org.openscience.cdk.interfaces.IBond;
29 import org.openscience.cdk.interfaces.IElement;
30 
31 import javax.vecmath.Point2d;
32 import javax.vecmath.Point3d;
33 import java.io.Serializable;
34 import java.util.Objects;
35 
36 /**
37  * Represents the idea of an chemical atom.
38  *
39  * <p>An Atom class is instantiated with at least the atom symbol:
40  * <pre>
41  *   Atom a = new Atom("C");
42  * </pre>
43  *
44  * <p>Once instantiated all field not filled by passing parameters
45  * to the constructor are null. Atoms can be configured by using
46  * the IsotopeFactory.configure() method:
47  * <pre>
48  *   IsotopeFactory if = IsotopeFactory.getInstance(a.getNewBuilder());
49  *   if.configure(a);
50  * </pre>
51  *
52  * <p>More examples about using this class can be found in the
53  * Junit test for this class.
54  *
55  * @cdk.module data
56  * @cdk.githash
57  *
58  * @author     steinbeck
59  * @cdk.created    2000-10-02
60  * @cdk.keyword    atom
61  *
62  * @see  org.openscience.cdk.config.XMLIsotopeFactory#getInstance(org.openscience.cdk.interfaces.IChemObjectBuilder)
63  */
64 public class Atom extends AtomType implements IAtom, Serializable, Cloneable {
65 
66     /*
67      * Let's keep this exact specification of what kind of point2d we're talking
68      * of here, since there are so many around in the java standard api
69      */
70 
71     /**
72      * Determines if a de-serialized object is compatible with this class.
73      *
74      * This value must only be changed if and only if the new version
75      * of this class is incompatible with the old version. See Sun docs
76      * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
77      * /serialization/spec/version.doc.html>details</a>.
78      */
79     private static final long serialVersionUID  = -3137373012494608794L;
80 
81     /**
82      *  A 2D point specifying the location of this atom in a 2D coordinate
83      *  space.
84      */
85     protected Point2d         point2d           = (Point2d) CDKConstants.UNSET;
86     /**
87      *  A 3 point specifying the location of this atom in a 3D coordinate
88      *  space.
89      */
90     protected Point3d         point3d           = (Point3d) CDKConstants.UNSET;
91     /**
92      *  A 3 point specifying the location of this atom in a crystal unit cell.
93      */
94     protected Point3d         fractionalPoint3d = (Point3d) CDKConstants.UNSET;
95     /**
96      *  The number of implicitly bound hydrogen atoms for this atom.
97      */
98     protected Integer         hydrogenCount     = (Integer) CDKConstants.UNSET;
99     /**
100      *  A stereo parity descriptor for the stereochemistry of this atom.
101      */
102     protected Integer         stereoParity      = (Integer) CDKConstants.UNSET;
103     /**
104      *  The partial charge of the atom.
105      *
106      * The default value is {@link CDKConstants#UNSET} and serves to provide a check whether the charge has been
107      * set or not
108      */
109     protected Double          charge            = (Double) CDKConstants.UNSET;
110 
111     /**
112      * Constructs an completely unset Atom.
113      */
Atom()114     public Atom() {
115         super((String) null);
116     }
117 
118     /**
119      * Create a new atom with of the specified element.
120      *
121      * @param elem atomic number
122      */
Atom(int elem)123     public Atom(int elem) {
124         this(elem, 0, 0);
125     }
126 
127     /**
128      * Create a new atom with of the specified element and hydrogen count.
129      *
130      * @param elem atomic number
131      * @param hcnt hydrogen count
132      */
Atom(int elem, int hcnt)133     public Atom(int elem, int hcnt) {
134         this(elem, hcnt, 0);
135     }
136 
137     /**
138      * Create a new atom with of the specified element, hydrogen count, and formal charge.
139      *
140      * @param elem atomic number
141      * @param hcnt hydrogen count
142      * @param fchg formal charge
143      */
Atom(int elem, int hcnt, int fchg)144     public Atom(int elem, int hcnt, int fchg) {
145         super((String)null);
146         setAtomicNumber(elem);
147         setSymbol(Elements.ofNumber(elem).symbol());
148         setImplicitHydrogenCount(hcnt);
149         setFormalCharge(fchg);
150     }
151 
152     /**
153      * Constructs an Atom from a string containing an element symbol and optionally
154      * the atomic mass, hydrogen count, and formal charge. The symbol grammar allows
155      * easy construction from common symbols, for example:
156      *
157      * <pre>
158      *     new Atom("NH+");   // nitrogen cation with one hydrogen
159      *     new Atom("OH");    // hydroxy
160      *     new Atom("O-");    // oxygen anion
161      *     new Atom("13CH3"); // methyl isotope 13
162      * </pre>
163      *
164      * <pre>
165      *     atom := {mass}? {symbol} {hcnt}? {fchg}?
166      *     mass := \d+
167      *     hcnt := 'H' \d+
168      *     fchg := '+' \d+? | '-' \d+?
169      * </pre>
170      *
171      * @param symbol string with the element symbol
172      */
Atom(String symbol)173     public Atom(String symbol) {
174         super((String)null);
175         if (!parseAtomSymbol(this, symbol))
176             throw new IllegalArgumentException("Cannot pass atom symbol: " + symbol);
177     }
178 
179     /**
180      * Constructs an Atom from an Element and a Point3d.
181      *
182      * @param   elementSymbol   The symbol of the atom
183      * @param   point3d         The 3D coordinates of the atom
184      */
Atom(String elementSymbol, Point3d point3d)185     public Atom(String elementSymbol, Point3d point3d) {
186         this(elementSymbol);
187         this.point3d = point3d;
188     }
189 
190     /**
191      * Constructs an Atom from an Element and a Point2d.
192      *
193      * @param   elementSymbol   The Element
194      * @param   point2d         The Point
195      */
Atom(String elementSymbol, Point2d point2d)196     public Atom(String elementSymbol, Point2d point2d) {
197         this(elementSymbol);
198         this.point2d = point2d;
199     }
200 
201     /**
202      * Constructs an isotope by copying the symbol, atomic number,
203      * flags, identifier, exact mass, natural abundance, mass
204      * number, maximum bond order, bond order sum, van der Waals
205      * and covalent radii, formal charge, hybridization, electron
206      * valency, formal neighbour count and atom type name from the
207      * given IAtomType. It does not copy the listeners and
208      * properties. If the element is an instance of
209      * IAtom, then the 2D, 3D and fractional coordinates, partial
210      * atomic charge, hydrogen count and stereo parity are copied
211      * too.
212      *
213      * @param element IAtomType to copy information from
214      */
Atom(IElement element)215     public Atom(IElement element) {
216         super(element);
217         if (element instanceof IAtom) {
218             if (((IAtom) element).getPoint2d() != null) {
219                 this.point2d = new Point2d(((IAtom) element).getPoint2d());
220             } else {
221                 this.point2d = null;
222             }
223             if (((IAtom) element).getPoint3d() != null) {
224                 this.point3d = new Point3d(((IAtom) element).getPoint3d());
225             } else {
226                 this.point3d = null;
227             }
228             if (((IAtom) element).getFractionalPoint3d() != null) {
229                 this.fractionalPoint3d = new Point3d(((IAtom) element).getFractionalPoint3d());
230             } else {
231                 this.fractionalPoint3d = null;
232             }
233             this.hydrogenCount = ((IAtom) element).getImplicitHydrogenCount();
234             this.charge = ((IAtom) element).getCharge();
235             this.stereoParity = ((IAtom) element).getStereoParity();
236         }
237     }
238 
239     /**
240      * {@inheritDoc}
241      */
242     @Override
getContainer()243     public IAtomContainer getContainer() {
244         return null;
245     }
246 
247     /**
248      * {@inheritDoc}
249      */
250     @Override
getIndex()251     public int getIndex() {
252         return -1;
253     }
254 
255     /**
256      * {@inheritDoc}
257      */
258     @Override
bonds()259     public Iterable<IBond> bonds() {
260         throw new UnsupportedOperationException();
261     }
262 
263     /**
264      * {@inheritDoc}
265      */
266     @Override
getBondCount()267     public int getBondCount() {
268         throw new UnsupportedOperationException();
269     }
270 
271     /**
272      * {@inheritDoc}
273      */
274     @Override
getBond(IAtom atom)275     public IBond getBond(IAtom atom) {
276         throw new UnsupportedOperationException();
277     }
278 
279     /**
280      *  Sets the partial charge of this atom.
281      *
282      * @param  charge  The partial charge
283      *
284      * @see    #getCharge
285      */
286     @Override
setCharge(Double charge)287     public void setCharge(Double charge) {
288         this.charge = charge;
289         notifyChanged();
290     }
291 
292     /**
293      *  Returns the partial charge of this atom.
294      *
295      * If the charge has not been set the return value is Double.NaN
296      *
297      * @return the charge of this atom
298      *
299      * @see    #setCharge
300      */
301     @Override
getCharge()302     public Double getCharge() {
303         return this.charge;
304     }
305 
306     /**
307      *  Sets the number of implicit hydrogen count of this atom.
308      *
309      * @param  hydrogenCount  The number of hydrogen atoms bonded to this atom.
310      *
311      * @see    #getImplicitHydrogenCount
312      */
313     @Override
setImplicitHydrogenCount(Integer hydrogenCount)314     public void setImplicitHydrogenCount(Integer hydrogenCount) {
315         this.hydrogenCount = hydrogenCount;
316         notifyChanged();
317     }
318 
319     /**
320      *  Returns the hydrogen count of this atom.
321      *
322      * @return    The hydrogen count of this atom.
323      *
324      * @see       #setImplicitHydrogenCount
325      */
326     @Override
getImplicitHydrogenCount()327     public Integer getImplicitHydrogenCount() {
328         return this.hydrogenCount;
329     }
330 
331     /**
332      *
333      * Sets a point specifying the location of this
334      * atom in a 2D space.
335      *
336      * @param  point2d  A point in a 2D plane
337      *
338      * @see    #getPoint2d
339      */
340     @Override
setPoint2d(Point2d point2d)341     public void setPoint2d(Point2d point2d) {
342         this.point2d = point2d;
343         notifyChanged();
344     }
345 
346     /**
347      *
348      * Sets a point specifying the location of this
349      * atom in 3D space.
350      *
351      * @param  point3d  A point in a 3-dimensional space
352      *
353      * @see    #getPoint3d
354      */
355     @Override
setPoint3d(Point3d point3d)356     public void setPoint3d(Point3d point3d) {
357         this.point3d = point3d;
358         notifyChanged();
359     }
360 
361     /**
362      * Sets a point specifying the location of this
363      * atom in a Crystal unit cell.
364      *
365      * @param  point3d  A point in a 3d fractional unit cell space
366      *
367      * @see    #getFractionalPoint3d
368      * @see    org.openscience.cdk.Crystal
369      */
370     @Override
setFractionalPoint3d(Point3d point3d)371     public void setFractionalPoint3d(Point3d point3d) {
372         this.fractionalPoint3d = point3d;
373         notifyChanged();
374     }
375 
376     /**
377      * Sets the stereo parity for this atom.
378      *
379      * @param  stereoParity  The stereo parity for this atom
380      *
381      * @see    org.openscience.cdk.CDKConstants for predefined values.
382      * @see    #getStereoParity
383      */
384     @Override
setStereoParity(Integer stereoParity)385     public void setStereoParity(Integer stereoParity) {
386         this.stereoParity = stereoParity;
387         notifyChanged();
388     }
389 
390     /**
391      * Returns a point specifying the location of this
392      * atom in a 2D space.
393      *
394      * @return    A point in a 2D plane. Null if unset.
395      *
396      * @see       #setPoint2d
397      */
398     @Override
getPoint2d()399     public Point2d getPoint2d() {
400         return this.point2d;
401     }
402 
403     /**
404      * Returns a point specifying the location of this
405      * atom in a 3D space.
406      *
407      * @return    A point in 3-dimensional space. Null if unset.
408      *
409      * @see       #setPoint3d
410      */
411     @Override
getPoint3d()412     public Point3d getPoint3d() {
413         return this.point3d;
414     }
415 
416     /**
417      * Returns a point specifying the location of this
418      * atom in a Crystal unit cell.
419      *
420      * @return    A point in 3d fractional unit cell space. Null if unset.
421      *
422      * @see       #setFractionalPoint3d
423      * @see       org.openscience.cdk.CDKConstants for predefined values.
424      */
425     @Override
getFractionalPoint3d()426     public Point3d getFractionalPoint3d() {
427         return this.fractionalPoint3d;
428     }
429 
430     /**
431      *  Returns the stereo parity of this atom. It uses the predefined values
432      *  found in CDKConstants.
433      *
434      * @return    The stereo parity for this atom
435      *
436      * @see       org.openscience.cdk.CDKConstants
437      * @see       #setStereoParity
438      */
439     @Override
getStereoParity()440     public Integer getStereoParity() {
441         return this.stereoParity;
442     }
443 
444     /**
445      * Compares a atom with this atom.
446      *
447      * @param     object of type Atom
448      * @return    true, if the atoms are equal
449      */
450     @Override
compare(Object object)451     public boolean compare(Object object) {
452         if (!(object instanceof IAtom)) {
453             return false;
454         }
455         if (!super.compare(object)) {
456             return false;
457         }
458         Atom atom = (Atom) object;
459         // XXX: floating point comparision!
460         if (((point2d == atom.point2d) || ((point2d != null) && (point2d.equals(atom.point2d))))
461             && ((point3d == atom.point3d) || ((point3d != null) && (point3d.equals(atom.point3d))))
462             && (Objects.equals(hydrogenCount, atom.hydrogenCount)) && (Objects.equals(stereoParity, atom.stereoParity))
463             && (Objects.equals(charge, atom.charge))) {
464             return true;
465         }
466         return false;
467     }
468 
469     /** {@inheritDoc} */
470     @Override
isAromatic()471     public boolean isAromatic() {
472         return getFlag(CDKConstants.ISAROMATIC);
473     }
474 
475     /** {@inheritDoc} */
476     @Override
setIsAromatic(boolean arom)477     public void setIsAromatic(boolean arom) {
478         setFlag(CDKConstants.ISAROMATIC, arom);
479     }
480 
481     /** {@inheritDoc} */
482     @Override
isInRing()483     public boolean isInRing() {
484         return getFlag(CDKConstants.ISINRING);
485     }
486 
487     /** {@inheritDoc} */
488     @Override
setIsInRing(boolean ring)489     public void setIsInRing(boolean ring) {
490         setFlag(CDKConstants.ISINRING, ring);
491     }
492 
493     /**
494      * Returns a one line string representation of this Atom.
495      * Methods is conform RFC #9.
496      *
497      * @return  The string representation of this Atom
498      */
499     @Override
toString()500     public String toString() {
501         StringBuffer stringContent = new StringBuffer(64);
502         stringContent.append("Atom(").append(hashCode());
503         if (getSymbol() != null) {
504             stringContent.append(", S:").append(getSymbol());
505         }
506         if (getImplicitHydrogenCount() != null) {
507             stringContent.append(", H:").append(getImplicitHydrogenCount());
508         }
509         if (getStereoParity() != null) {
510             stringContent.append(", SP:").append(getStereoParity());
511         }
512         if (getPoint2d() != null) {
513             stringContent.append(", 2D:[").append(getPoint2d()).append(']');
514         }
515         if (getPoint3d() != null) {
516             stringContent.append(", 3D:[").append(getPoint3d()).append(']');
517         }
518         if (getFractionalPoint3d() != null) {
519             stringContent.append(", F3D:[").append(getFractionalPoint3d());
520         }
521         if (getCharge() != null) {
522             stringContent.append(", C:").append(getCharge());
523         }
524         stringContent.append(", ").append(super.toString());
525         stringContent.append(')');
526         return stringContent.toString();
527     }
528 
529     /**
530      * Clones this atom object and its content.
531      *
532      * @return  The cloned object
533      */
534     @Override
clone()535     public IAtom clone() throws CloneNotSupportedException {
536         Object clone = super.clone();
537         if (point2d != null) {
538             ((Atom) clone).setPoint2d(new Point2d(point2d.x, point2d.y));
539         }
540         if (point3d != null) {
541             ((Atom) clone).setPoint3d(new Point3d(point3d.x, point3d.y, point3d.z));
542         }
543         if (fractionalPoint3d != null) {
544             ((Atom) clone).setFractionalPoint3d(new Point3d(fractionalPoint3d.x, fractionalPoint3d.y,
545                     fractionalPoint3d.z));
546         }
547         return (IAtom) clone;
548     }
549 
isUpper(char c)550     private static boolean isUpper(char c) {
551         return c >= 'A' && c <= 'Z';
552     }
553 
isLower(char c)554     private static boolean isLower(char c) {
555         return c >= 'a' && c <= 'z';
556     }
557 
isDigit(char c)558     private static boolean isDigit(char c) {
559         return c >= '0' && c <= '9';
560     }
561 
parseAtomSymbol(IAtom atom, String str)562     private static boolean parseAtomSymbol(IAtom atom, String str) {
563         final int len = str.length();
564         int pos = 0;
565 
566         int mass = -1;
567         int anum = 0;
568         int hcnt = -1;
569         int chg = 0;
570         String symbol = null;
571         boolean flag = false;
572 
573         // optional mass
574         if (pos < len && isDigit(str.charAt(pos))) {
575             mass = (str.charAt(pos++) - '0');
576             while (pos < len && isDigit(str.charAt(pos)))
577                 mass = 10 * mass + (str.charAt(pos++) - '0');
578         } else if ("R".equals(str)) {
579             anum = 0;
580             symbol = "R";
581             flag = true;
582         } else if ("*".equals(str)) {
583             anum = 0;
584             symbol = "*";
585             flag = true;
586         } else if ("D".equals(str)) {
587             anum = 1;
588             mass = 2;
589             symbol = "H";
590             flag = true;
591         } else if ("T".equals(str)) {
592             anum = 1;
593             mass = 3;
594             symbol = "H";
595             flag = true;
596         }
597 
598         if (flag == false) {
599             // atom symbol
600             if (pos < len && isUpper(str.charAt(pos))) {
601                 int beg = pos;
602                 pos++;
603                 while (pos < len && isLower(str.charAt(pos)))
604                     pos++;
605                 Elements elem = Elements.ofString(str.substring(beg, pos));
606                 if (elem == Elements.Unknown)
607                     return false;
608                 anum = elem.number();
609 
610                 // optional fields after atom symbol
611                 while (pos < len) {
612                     switch (str.charAt(pos)) {
613                         case 'H':
614                             pos++;
615                             if (pos < len && isDigit(str.charAt(pos))) {
616                             	hcnt = 0;
617                                 while (pos < len && isDigit(str.charAt(pos)))
618                                     hcnt = 10 * hcnt + (str.charAt(pos++) - '0');
619                             } else {
620                                 hcnt = 1;
621                             }
622                             break;
623                         case '+':
624                             pos++;
625                             if (pos < len && isDigit(str.charAt(pos))) {
626                                 chg = (str.charAt(pos++) - '0');
627                                 while (pos < len && isDigit(str.charAt(pos)))
628                                     chg = 10 * chg + (str.charAt(pos++) - '0');
629                             } else {
630                                 chg = +1;
631                             }
632                             break;
633                         case '-':
634                             pos++;
635                             if (pos < len && isDigit(str.charAt(pos))) {
636                                 chg = (str.charAt(pos++) - '0');
637                                 while (pos < len && isDigit(str.charAt(pos)))
638                                     chg = 10 * chg + (str.charAt(pos++) - '0');
639                                 chg *= -1;
640                             } else {
641                                 chg = -1;
642                             }
643                             break;
644                         default:
645                             return false;
646                     }
647                 }
648             } else {
649                 return false;
650             }
651             flag = pos == len && len > 0;
652             symbol = Elements.ofNumber(anum).symbol();
653         }
654 
655         if (!flag)
656             return false;
657 
658         if (mass < 0)
659             atom.setMassNumber(null);
660         else
661             atom.setMassNumber(mass);
662         atom.setAtomicNumber(anum);
663         atom.setSymbol(symbol);
664         if (hcnt >= 0)
665         	atom.setImplicitHydrogenCount(hcnt);
666         atom.setFormalCharge(chg);
667 
668         return true;
669     }
670 
671     /**
672      * {@inheritDoc}
673      */
674     @Override
hashCode()675     public int hashCode() {
676         return super.hashCode();
677     }
678 
679     /**
680      * {@inheritDoc}
681      */
682     @Override
equals(Object obj)683     public boolean equals(Object obj) {
684         if (obj instanceof AtomRef)
685             return super.equals(((AtomRef) obj).deref());
686         return super.equals(obj);
687     }
688 }
689