f404d35b915ebe5e5c9e075b6f50c2dffc166b72
[jalview.git] / src / jalview / structure / AtomSpec.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.structure;
22
23 /**
24  * Java bean representing an atom in a PDB (or similar) structure model or
25  * viewer
26  * 
27  * @author gmcarstairs
28  *
29  */
30 public class AtomSpec
31 {
32   int modelNo;
33
34   private String pdbFile;
35
36   private String chain;
37
38   private int pdbResNum;
39
40   private int atomIndex;
41
42   /**
43    * Parses a Chimera atomspec e.g. #1:12.A to construct an AtomSpec model (with
44    * null pdb file name)
45    * 
46    * <pre>
47    * Chimera format: 
48    *    #1.2:12-20.A     model 1, submodel 2, chain A, atoms 12-20
49    * </pre>
50    * 
51    * @param spec
52    * @return
53    * @throw IllegalArgumentException if the spec cannot be parsed, or represents
54    *        more than one residue
55    * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
56    */
57   public static AtomSpec fromChimeraAtomspec(String spec)
58   {
59     int modelSeparatorPos = spec.indexOf(":");
60     if (modelSeparatorPos == -1)
61     {
62       throw new IllegalArgumentException(spec);
63     }
64
65     int hashPos = spec.indexOf("#");
66     if (hashPos == -1 && modelSeparatorPos != 0)
67     {
68       // # is missing but something precedes : - reject
69       throw new IllegalArgumentException(spec);
70     }
71
72     String modelSubmodel = spec.substring(hashPos + 1, modelSeparatorPos);
73     int modelId = 0;
74     try
75     {
76       int subModelPos = modelSubmodel.indexOf(".");
77       modelId = Integer.valueOf(
78               subModelPos > 0 ? modelSubmodel.substring(0, subModelPos)
79                       : modelSubmodel);
80     } catch (NumberFormatException e)
81     {
82       // ignore, default to model 0
83     }
84
85     /*
86      * now process what follows the model, either
87      * Chimera:  atoms.chain
88      * ChimeraX: chain:atoms
89      */
90     String atomsAndChain = spec.substring(modelSeparatorPos + 1);
91     String[] tokens = atomsAndChain.split("\\.");
92     String atoms = tokens.length == 1 ? atomsAndChain : (tokens[0]);
93     int resNum = 0;
94     try
95     {
96       resNum = Integer.parseInt(atoms);
97     } catch (NumberFormatException e)
98     {
99       // could be a range e.g. #1:4-7.B
100       throw new IllegalArgumentException(spec);
101     }
102
103     String chainId = tokens.length == 1 ? "" : (tokens[1]);
104
105     return new AtomSpec(modelId, chainId, resNum, 0);
106   }
107
108   /**
109    * Constructor
110    * 
111    * @param pdbFile
112    * @param chain
113    * @param resNo
114    * @param atomNo
115    */
116   public AtomSpec(String pdbFile, String chain, int resNo, int atomNo)
117   {
118     this.pdbFile = pdbFile;
119     this.chain = chain;
120     this.pdbResNum = resNo;
121     this.atomIndex = atomNo;
122   }
123
124   /**
125    * Constructor
126    * 
127    * @param modelId
128    * @param chainId
129    * @param resNo
130    * @param atomNo
131    */
132   public AtomSpec(int modelId, String chainId, int resNo, int atomNo)
133   {
134     this.modelNo = modelId;
135     this.chain = chainId;
136     this.pdbResNum = resNo;
137     this.atomIndex = atomNo;
138   }
139
140   public String getPdbFile()
141   {
142     return pdbFile;
143   }
144
145   public String getChain()
146   {
147     return chain;
148   }
149
150   public int getPdbResNum()
151   {
152     return pdbResNum;
153   }
154
155   public int getAtomIndex()
156   {
157     return atomIndex;
158   }
159
160   public int getModelNumber()
161   {
162     return modelNo;
163   }
164
165   public void setPdbFile(String file)
166   {
167     pdbFile = file;
168   }
169
170   @Override
171   public String toString()
172   {
173     return "pdbFile: " + pdbFile + ", chain: " + chain + ", res: "
174             + pdbResNum + ", atom: " + atomIndex;
175   }
176
177   /**
178    * Parses a ChimeraX atomspec to construct an AtomSpec model (with
179    * null pdb file name)
180    * 
181    * <pre>
182    * ChimeraX format:
183    *    #1.2/A:12-20     model 1, submodel 2, chain A, atoms 12-20
184    * </pre>
185    * 
186    * @param spec
187    * @return
188    * @throw IllegalArgumentException if the spec cannot be parsed, or represents
189    *        more than one residue
190    * @see http://rbvi.ucsf.edu/chimerax/docs/user/commands/atomspec.html
191    */
192   public static AtomSpec fromChimeraXAtomspec(String spec)
193   {
194     int modelSeparatorPos = spec.indexOf("/");
195     if (modelSeparatorPos == -1)
196     {
197       throw new IllegalArgumentException(spec);
198     }
199
200     int hashPos = spec.indexOf("#");
201     if (hashPos == -1 && modelSeparatorPos != 0)
202     {
203       // # is missing but something precedes : - reject
204       throw new IllegalArgumentException(spec);
205     }
206
207     String modelSubmodel = spec.substring(hashPos + 1, modelSeparatorPos);
208     int modelId = 0;
209     try
210     {
211       int subModelPos = modelSubmodel.indexOf(".");
212       modelId = Integer.valueOf(
213               subModelPos > 0 ? modelSubmodel.substring(0, subModelPos)
214                       : modelSubmodel);
215     } catch (NumberFormatException e)
216     {
217       // ignore, default to model 0
218     }
219
220     /*
221      * now process what follows the model, either
222      * Chimera:  atoms.chain
223      * ChimeraX: chain:atoms
224      */
225     String atomsAndChain = spec.substring(modelSeparatorPos + 1);
226     String[] tokens = atomsAndChain.split("\\:");
227     String atoms = tokens.length == 1 ? atomsAndChain : (tokens[1]);
228     int resNum = 0;
229     try
230     {
231       resNum = Integer.parseInt(atoms);
232     } catch (NumberFormatException e)
233     {
234       // could be a range e.g. #1:4-7.B
235       throw new IllegalArgumentException(spec);
236     }
237
238     String chainId = tokens.length == 1 ? "" : (tokens[0]);
239
240     return new AtomSpec(modelId, chainId, resNum, 0);
241   }
242 }