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 }