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