001package com.hfg.bio;
002
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009import java.util.stream.Collectors;
010
011import com.hfg.chem.Molecule;
012import com.hfg.util.StringUtil;
013
014//------------------------------------------------------------------------------
015/**
016 * Nucleotide.
017 * @author J. Alex Taylor, hairyfatguy.com
018 */
019//------------------------------------------------------------------------------
020// com.hfg XML/HTML Coding Library
021//
022// This library is free software; you can redistribute it and/or
023// modify it under the terms of the GNU Lesser General Public
024// License as published by the Free Software Foundation; either
025// version 2.1 of the License, or (at your option) any later version.
026//
027// This library is distributed in the hope that it will be useful,
028// but WITHOUT ANY WARRANTY; without even the implied warranty of
029// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
030// Lesser General Public License for more details.
031//
032// You should have received a copy of the GNU Lesser General Public
033// License along with this library; if not, write to the Free Software
034// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
035//
036// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
037// jataylor@hairyfatguy.com
038//------------------------------------------------------------------------------
039
040public class Nucleotide extends Molecule
041{
042   //##########################################################################
043   // PRIVATE FIELDS
044   //##########################################################################
045
046   private Character  m1LetterCode;
047   private BaseType   mBaseType;
048   private List<Nucleotide> mDegeneracy;
049   private Nucleotide mComplement;
050
051   // This declaration has to come before the public constants below.
052   private static Set<Nucleotide> sValues = new HashSet<>();
053
054   //##########################################################################
055   // PUBLIC FIELDS
056   //##########################################################################
057
058   public enum BaseType { PURINE, PYRIMIDINE };
059
060   public static final Nucleotide ADENINE   = new Nucleotide("Adenine", 'A', BaseType.PURINE)
061         .lock()
062         .register();
063
064   public static final Nucleotide GUANINE   = new Nucleotide("Guanine", 'G', BaseType.PURINE)
065         .lock()
066         .register();
067
068   public static final Nucleotide CYTOSINE  = new Nucleotide("Cytosine",'C', BaseType.PYRIMIDINE)
069         .lock()
070         .register();
071
072   // DNA only
073   public static final Nucleotide THYMINE   = new Nucleotide("Thymine", 'T', BaseType.PYRIMIDINE)
074         .lock()
075         .register();
076
077   // RNA only
078   public static final Nucleotide URACIL    = new Nucleotide("Uracil",  'U', BaseType.PYRIMIDINE)
079         .lock()
080         .register();
081
082   // TODO Add elemental composition. How to distinguish RNA & DNA?
083
084   // Degenerate (ambiguous) nucleotides
085   public static final Nucleotide W    = new Nucleotide("W",  'W').addDegeneracy(ADENINE).addDegeneracy(THYMINE)
086         .lock()
087         .register();
088
089   public static final Nucleotide S    = new Nucleotide("S",  'S').addDegeneracy(CYTOSINE).addDegeneracy(GUANINE)
090         .lock()
091         .register();
092
093   public static final Nucleotide M    = new Nucleotide("M",  'M').addDegeneracy(ADENINE).addDegeneracy(CYTOSINE)
094         .lock()
095         .register();
096
097   public static final Nucleotide K    = new Nucleotide("K",  'K').addDegeneracy(GUANINE).addDegeneracy(THYMINE)
098         .lock()
099         .register();
100
101   public static final Nucleotide R    = new Nucleotide("R",  'R').addDegeneracy(ADENINE).addDegeneracy(GUANINE)
102         .lock()
103         .register();
104
105   public static final Nucleotide Y    = new Nucleotide("Y",  'Y').addDegeneracy(CYTOSINE).addDegeneracy(THYMINE)
106         .lock()
107         .register();
108
109   public static final Nucleotide B    = new Nucleotide("B",  'B').addDegeneracy(CYTOSINE).addDegeneracy(GUANINE).addDegeneracy(THYMINE)
110         .lock()
111         .register();
112
113   public static final Nucleotide D    = new Nucleotide("D",  'D').addDegeneracy(ADENINE).addDegeneracy(GUANINE).addDegeneracy(THYMINE)
114         .lock()
115         .register();
116
117   public static final Nucleotide H    = new Nucleotide("H",  'H').addDegeneracy(ADENINE).addDegeneracy(CYTOSINE).addDegeneracy(THYMINE)
118         .lock()
119         .register();
120
121   public static final Nucleotide V    = new Nucleotide("V",  'V').addDegeneracy(ADENINE).addDegeneracy(CYTOSINE).addDegeneracy(GUANINE)
122         .lock()
123         .register();
124
125   public static final Nucleotide N    = new Nucleotide("N",  'N').addDegeneracy(ADENINE).addDegeneracy(CYTOSINE).addDegeneracy(GUANINE).addDegeneracy(THYMINE)
126         .lock()
127         .register();
128
129
130   static
131   {
132      // Set the complement values
133      ADENINE.setComplement(THYMINE);
134      THYMINE.setComplement(ADENINE);
135      CYTOSINE.setComplement(GUANINE);
136      GUANINE.setComplement(CYTOSINE);
137
138      R.setComplement(Y);
139      Y.setComplement(R);
140      S.setComplement(S);
141      W.setComplement(W);
142      K.setComplement(M);
143      M.setComplement(K);
144      B.setComplement(V);
145      V.setComplement(B);
146      D.setComplement(H);
147      H.setComplement(D);
148      N.setComplement(N);
149   }
150
151
152   //##########################################################################
153   // CONSTRUCTORS
154   //##########################################################################
155
156   //--------------------------------------------------------------------------
157   public Nucleotide(String inName)
158   {
159      super();
160      setName(inName);
161   }
162
163   //--------------------------------------------------------------------------
164   public Nucleotide(String inName, char in1LetterCode)
165   {
166      this(inName);
167      m1LetterCode = in1LetterCode;
168   }
169
170   //--------------------------------------------------------------------------
171   public Nucleotide(String inName, char in1LetterCode, BaseType inBaseType)
172   {
173      this(inName, in1LetterCode);
174      mBaseType = inBaseType;
175   }
176
177
178   //##########################################################################
179   // PUBLIC METHODS
180   //##########################################################################
181
182
183   //--------------------------------------------------------------------------
184   @Override
185   public Nucleotide lock()
186   {
187      return (Nucleotide) super.lock();
188   }
189
190   //--------------------------------------------------------------------------
191   /**
192    Puts the Nucleotide into the Set of unique Nucleotides returned by Nucleotide.values().
193    */
194   public Nucleotide register()
195   {
196      if (! isLocked())
197      {
198         throw new RuntimeException("Only locked Nucleotides can be added to the values list!");
199      }
200
201      sValues.add(this);
202
203      return this;
204   }
205
206   //--------------------------------------------------------------------------
207   /**
208    Returns the Nucleotide whose name or 1-letter code matches the specified String.
209    @param inString the name or 1-letter code for the Nucleotide to retrieve
210    @return the Nucleotide whose name or 1-letter code matches the specified String
211    */
212   public static Nucleotide valueOf(String inString)
213   {
214      Nucleotide value = null;
215
216      if (StringUtil.isSet(inString))
217      {
218         for (Nucleotide nuc : sValues)
219         {
220            if (nuc.name().equalsIgnoreCase(inString)
221                  || inString.charAt(0) == nuc.getOneLetterCode())
222            {
223               value = nuc;
224               break;
225            }
226         }
227      }
228
229      return value;
230   }
231
232   //--------------------------------------------------------------------------
233   /**
234    Returns the Nucleotide whose 1-letter code matches the specified String.
235    @param inChar the 1-letter code for the Nucleotide to retrieve
236    @return the Nucleotide whose 1-letter code matches the specified String
237    */
238   public static Nucleotide valueOf(char inChar)
239   {
240      return valueOf(inChar + "");
241   }
242
243   //--------------------------------------------------------------------------
244   public static Nucleotide[] values()
245   {
246      return sValues.toArray(new Nucleotide[sValues.size()]);
247   }
248
249   //--------------------------------------------------------------------------
250   public static Nucleotide[] degenerateValues()
251   {
252      Set<Nucleotide> values = new HashSet<>(11);
253      for (Nucleotide nucleotide : sValues)
254      {
255         if (nucleotide.isAmbiguous())
256         {
257            values.add(nucleotide);
258         }
259      }
260
261      return values.toArray(new Nucleotide[values.size()]);
262   }
263
264   //--------------------------------------------------------------------------
265   /**
266    Returns an unlocked copy of the Nucleotide.
267    */
268   @Override
269   public Nucleotide clone()
270   {
271      return (Nucleotide) super.clone();
272   }
273
274   //--------------------------------------------------------------------------
275   public BaseType getBaseType()
276   {
277      return mBaseType;
278   }
279
280   //--------------------------------------------------------------------------
281   public Character getOneLetterCode()
282   {
283      return m1LetterCode;
284   }
285
286
287   //--------------------------------------------------------------------------
288   private Nucleotide setComplement(Nucleotide inValue)
289   {
290      mComplement = inValue;
291      return this;
292   }
293
294   //--------------------------------------------------------------------------
295   public Nucleotide getComplement()
296   {
297      return mComplement;
298   }
299
300
301   //--------------------------------------------------------------------------
302   public Nucleotide addDegeneracy(Nucleotide inValue)
303   {
304      if (null == mDegeneracy)
305      {
306         mDegeneracy = new ArrayList<>(4);
307      }
308
309      mDegeneracy.add(inValue);
310      return this;
311   }
312
313   //--------------------------------------------------------------------------
314   public Collection<Nucleotide> getDegeneracy()
315   {
316      return mDegeneracy;
317   }
318
319   //--------------------------------------------------------------------------
320   public String getDegeneracyAsString()
321   {
322      return StringUtil.join(mDegeneracy.stream().map(Nucleotide::getOneLetterCode).collect(Collectors.toCollection(ArrayList::new)), "");
323   }
324
325   //--------------------------------------------------------------------------
326   public boolean isAmbiguous()
327   {
328      return mDegeneracy != null;
329   }
330}