View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.utilities;
39  
40  import static org.apache.commons.lang3.StringUtils.repeat;
41  import static org.apache.commons.math3.util.FastMath.max;
42  
43  /**
44   * Java port of the hy36encode() and hy36decode() functions in the hybrid_36.py Python
45   * prototype/reference implementation.
46   *
47   * @author Michael J. Schnieders
48   *     <p>Derived from code by: Ralf W. Grosse-Kunstleve, Vincent B. Chen, Jeff J. Headd, Sep 2007.
49   * @see <a href="http://cci.lbl.gov/hybrid_36" target="_blank">LBL Hybrid36 Reference</a>
50   * @since 1.0
51   */
52  public class Hybrid36 {
53  
54    private static final String digitsBase10 = "0123456789";
55    private static final String digitsUpper = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
56    private static final String digitsLower = "0123456789abcdefghijklmnopqrstuvwxyz";
57    private static final String valueOutOfRange = "value out of range.";
58    private static final String invalidNumberLiteral = "invalid number literal.";
59    private static final String unsupportedWidth = "unsupported width.";
60    private static final int[] digitsValuesUpper = new int[128];
61    private static final int[] digitsValuesLower = new int[128];
62    private static boolean firstCall = true;
63  
64    /**
65     * Private constructor to prevent instantiation.
66     */
67    private Hybrid36() {
68      // Empty constructor.
69    }
70  
71    /**
72     * Hybrid-36 decoder: converts string s to integer result.
73     *
74     * @param width must be 4 (e.g. for residue sequence numbers) or 5 (e.g. for atom serial
75     *     numbers)
76     * @param s the {@link java.lang.String} to be converted.
77     * @return Returns the conversion result.
78     */
79    public static int decode(int width, String s) {
80      String outOfRange = "Internal error Hybrid-36.decode: integer value out of range.";
81      if (firstCall) {
82        firstCall = false;
83        for (int i = 0; i < 128; i++) {
84          digitsValuesUpper[i] = -1;
85        }
86        for (int i = 0; i < 128; i++) {
87          digitsValuesLower[i] = -1;
88        }
89        for (int i = 0; i < 36; i++) {
90          int di = digitsUpper.charAt(i);
91          if (di > 127) {
92            throw new Error(outOfRange);
93          }
94          digitsValuesUpper[di] = i;
95        }
96        for (int i = 0; i < 36; i++) {
97          int di = digitsLower.charAt(i);
98          if (di > 127) {
99            throw new Error(outOfRange);
100         }
101         digitsValuesLower[di] = i;
102       }
103     }
104     if (s.length() == width) {
105       int di = s.charAt(0);
106       if (di <= 127) {
107         if (digitsValuesUpper[di] >= 10) {
108           int result = decodePure(digitsValuesUpper, 36, s);
109           if (width == 4) {
110             // 10*36**3 + 10**4 = 456560
111             result -= 456560;
112           } else if (width == 5) {
113             // 10*36**4 + 10**5 = 16696160
114             result -= 16696160;
115           } else {
116             throw new Error(unsupportedWidth);
117           }
118           return result;
119         } else if (digitsValuesLower[di] >= 10) {
120           int result = decodePure(digitsValuesLower, 36, s);
121 
122           if (width == 4) {
123             // 16*36**3 + 10**4 = 756496
124             result += 756496;
125           } else if (width == 5) {
126             // 16*36**4 + 10**5 = 26973856
127             result += 26973856;
128           } else {
129             throw new Error(unsupportedWidth);
130           }
131           return result;
132         } else {
133           int result = decodePure(digitsValuesUpper, 10, s);
134           if (!(width == 4 || width == 5)) {
135             throw new Error(unsupportedWidth);
136           }
137           return result;
138         }
139       }
140     }
141     throw new Error(invalidNumberLiteral);
142   }
143 
144   /**
145    * Hybrid-36 encoder: converts integer value to string result.
146    *
147    * @param width must be 4 (e.g. for residue sequence numbers) or 5 (e.g. for atom serial
148    *     numbers).
149    * @param value the integer value to be converted.
150    * @return a {@link java.lang.String} String of size width.
151    */
152   public static String encode(int width, int value) {
153     int i = value;
154     if (width == 4) {
155       if (i >= -999) {
156         if (i < 10000) {
157           return encodePure(digitsBase10, 4, i);
158         }
159         i -= 10000;
160         // 26*36**3 = 1213056
161         if (i < 1213056) {
162           // 10*36**3 = 466560
163           i += 466560;
164           return encodePure(digitsUpper, 0, i);
165         }
166         i -= 1213056;
167         if (i < 1213056) {
168           i += 466560;
169           return encodePure(digitsLower, 0, i);
170         }
171       }
172     } else if (width == 5) {
173       if (i >= -9999) {
174         if (i < 100000) {
175           return encodePure(digitsBase10, 5, i);
176         }
177         i -= 100000;
178         // 26*36**4 = 43670016
179         if (i < 43670016) {
180           // 10*36**4 = 16796160
181           i += 16796160;
182           return encodePure(digitsUpper, 0, i);
183         }
184         i -= 43670016;
185         if (i < 43670016) {
186           i += 16796160;
187           return encodePure(digitsLower, 0, i);
188         }
189       }
190     } else {
191       throw new Error(unsupportedWidth);
192     }
193     throw new Error(valueOutOfRange);
194   }
195 
196   private static int decodePure(int[] digitsValues, int digitsSize, String s) {
197     boolean haveMinus = false;
198     boolean haveNonBlank = false;
199     int value = 0;
200     for (int i = 0; i < s.length(); i++) {
201       char si = s.charAt(i);
202       if (si > 127) {
203         throw new Error(invalidNumberLiteral);
204       }
205       if (si == ' ') {
206         if (!haveNonBlank) {
207           continue;
208         }
209         value *= digitsSize;
210       } else if (si == '-') {
211         if (haveNonBlank) {
212           throw new Error(invalidNumberLiteral);
213         }
214         haveNonBlank = true;
215         haveMinus = true;
216       } else {
217         haveNonBlank = true;
218         int dv = digitsValues[si];
219         if (dv < 0 || dv >= digitsSize) {
220           throw new Error(invalidNumberLiteral);
221         }
222         value *= digitsSize;
223         value += dv;
224       }
225     }
226     if (haveMinus) {
227       value = -value;
228     }
229     return value;
230   }
231 
232   private static String encodePure(String digits, int width, int value) {
233     boolean neg = false;
234     if (value < 0) {
235       neg = true;
236       value = -value;
237     }
238     StringBuilder buf = new StringBuilder();
239     while (true) {
240       int rest = value / digits.length();
241       buf.append(digits.charAt(value - rest * digits.length()));
242       if (rest == 0) {
243         break;
244       }
245       value = rest;
246     }
247     if (neg) {
248       buf.append('-');
249     }
250     StringBuilder result = new StringBuilder();
251     result.append(repeat(" ", max(0, width - buf.length())));
252     for (int i = buf.length() - 1; i >= 0; i--) {
253       result.append(buf.charAt(i));
254     }
255     return result.toString();
256   }
257 }