1 //******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025.
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.fine(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.fine(
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.fine(
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 }