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 java.lang.String.format;
41  
42  import java.io.File;
43  import java.util.ArrayList;
44  import java.util.List;
45  import java.util.logging.Logger;
46  import java.util.regex.Matcher;
47  import java.util.regex.Pattern;
48  
49  /**
50   * A collection of Utility methods for compatibility with Tinker.
51   *
52   * @author Michael J. Schnieders
53   * @since 1.0
54   */
55  public class TinkerUtils {
56  
57    private static final Logger logger = Logger.getLogger(TinkerUtils.class.getName());
58  
59    /**
60     * Tinker ranges begin with a negative number.
61     *
62     * <p>Here is the description from the Tinker Guide: "Several keywords take a list of integer
63     * values (atom numbers, for example) as modifiers. For these keywords the integers can simply be
64     * listed explicitly and separated by spaces, commas or tabs. If a range of numbers is desired, it
65     * can be specified by listing the negative of the first number of the range, followed by a
66     * separator and the last number of the range. For example, the keyword line ACTIVE 4 -9 17 23
67     * could be used to add atoms 4, 9 through 17, and 23 to the set of active atoms during a TINKER
68     * calculation."
69     */
70    private static final Pattern atomSelectionStartPattern = Pattern.compile("-(\\d+)");
71  
72    /** Tinker ranges end with a positive number. Positive numbers may also be a Singleton. */
73    private static final Pattern atomSingletonPattern = Pattern.compile("(\\d+)");
74  
75    /**
76     * Private constructor to prevent instantiation.
77     */
78    private TinkerUtils() {
79      // Empty constructor.
80    }
81  
82    /**
83     * Parse a Tinker selection list. No checking is done to make sure range i
84     *
85     * @param tokens A list of tokens to parse.
86     * @param offset A constant offset that is added to each selected integer (i.e. an offset of -1
87     *     will index atoms from 0 to n-1).
88     * @param maxEntries The maximum number of entries to parse (e.g. "4 -9 17 23" is 3 entries). A
89     *     value less than 1 includes all entries.
90     * @return A list of selected integers.
91     */
92    public static List<Integer> parseTinkerAtomList(List<String> tokens, int offset, int maxEntries) {
93      // Add selected atom to this list.
94      List<Integer> list = new ArrayList<>();
95  
96      // No entries parsed to start.
97      int nParsed = 0;
98  
99      // Loop over tokens.
100     int n = tokens.size();
101     for (int i = 0; i < n; i++) {
102       // Check for Tinker range that begins with a negative number.
103       Matcher m = atomSelectionStartPattern.matcher(tokens.get(i));
104       if (m.matches()) {
105         int start = Integer.parseInt(m.group(1));
106         ++i;
107         if (i == n) {
108           logger.info(format(
109                   " Attempted to parse -%d as a Tinker-style range, but it was ignored because it was the last token provided.",
110                   start));
111           continue;
112         }
113         int end = Integer.parseInt(tokens.get(i));
114         if (end < start) {
115           logger.info(
116               format(
117                   " Attempted to parse -%d to %d as a Tinker-style range, which is ignored due to being invalid.",
118                   start, end));
119           continue;
120         }
121         for (int j = start; j <= end; j++) {
122           list.add(j + offset);
123         }
124         nParsed++;
125       } else {
126         // Check for a singleton entry.
127         m = atomSingletonPattern.matcher(tokens.get(i));
128         if (m.matches()) {
129           int start = Integer.parseInt(m.group(1));
130           list.add(start + offset);
131           nParsed++;
132         } else {
133           logger.info(
134               format(
135                   " Attempted to parse %s as a Tinker-style range, but it was not recognized and ignored.",
136                   tokens.get(i)));
137         }
138       }
139 
140       // Enforce the maximum number of entries to parse.
141       if (maxEntries > 0 && nParsed >= maxEntries) {
142         break;
143       }
144     }
145     return list;
146   }
147 
148   /**
149    * Get the previous file based on the TINKER scheme.
150    *
151    * @param file Root file.
152    * @return New File created previously to the passed file.
153    */
154   public static File previousVersion(File file) {
155     if (file == null) {
156       return null;
157     }
158     String fileName = file.getAbsolutePath();
159     int dot = file.getAbsolutePath().lastIndexOf(".");
160     int under = file.getAbsolutePath().lastIndexOf("_");
161     File newFile = file;
162     if (under > dot) {
163       String name = fileName.substring(0, under);
164       newFile = new File(name);
165     }
166     File baseFile = newFile;
167     File previousFile = null;
168     int i = 1;
169     while (newFile.exists()) {
170       i = i + 1;
171       previousFile = newFile;
172       newFile = tinkerFile(baseFile, i);
173     }
174     return previousFile;
175   }
176 
177   /**
178    * This follows the TINKER file versioning scheme.
179    *
180    * @param file File to find a version for.
181    * @return File Versioned File.
182    */
183   public static File version(File file) {
184     if (file == null) {
185       return null;
186     }
187     if (!file.exists()) {
188       return file;
189     }
190     String fileName = file.getAbsolutePath();
191     int dot = file.getAbsolutePath().lastIndexOf(".");
192     int under = file.getAbsolutePath().lastIndexOf("_");
193     File newFile = file;
194     if (under > dot) {
195       String name = fileName.substring(0, under);
196       newFile = new File(name);
197     }
198     File oldFile = newFile;
199     int i = 1;
200     while (newFile.exists()) {
201       i = i + 1;
202       newFile = tinkerFile(oldFile, i);
203     }
204     return newFile;
205   }
206 
207   /**
208    * Append the integer i to filename of File.
209    *
210    * @param file Root file.
211    * @param i Integer to append.
212    * @return New File with integer appended.
213    */
214   private static File tinkerFile(File file, int i) {
215     return new File(file.getAbsolutePath() + '_' + i);
216   }
217 }