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-2021.
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.potential.parsers;
39  
40  import static ffx.utilities.StringUtils.parseAtomRange;
41  import static java.lang.String.format;
42  
43  import ffx.potential.MolecularAssembly;
44  import ffx.potential.Utilities.FileType;
45  import ffx.potential.bonded.Atom;
46  import ffx.potential.bonded.Bond;
47  import ffx.potential.bonded.Polymer;
48  import ffx.potential.nonbonded.CoordRestraint;
49  import ffx.potential.parameters.ForceField;
50  import java.io.File;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.HashSet;
54  import java.util.List;
55  import java.util.OptionalDouble;
56  import java.util.Set;
57  import java.util.Vector;
58  import java.util.logging.Level;
59  import java.util.logging.Logger;
60  import java.util.regex.Pattern;
61  import java.util.stream.Collectors;
62  import java.util.stream.IntStream;
63  import java.util.stream.Stream;
64  import org.apache.commons.configuration2.CompositeConfiguration;
65  
66  /**
67   * The SystemFilter class is the base class for most Force Field X file parsers.
68   *
69   * @author Michael J. Schnieders
70   * @since 1.0
71   */
72  public abstract class SystemFilter {
73  
74    protected static final Pattern lambdaPattern = Pattern.compile("Lambda: +([01]\\.\\d+)");
75    private static final Logger logger = Logger.getLogger(SystemFilter.class.getName());
76    private static Versioning vers = Versioning.TINKER;
77    private static int absoluteCounter = 0;
78    /** Constant <code>dieOnMissingAtom=</code> */
79    protected final boolean dieOnMissingAtom;
80    /** Standardize atom names to PDB standard by default. */
81    protected final boolean standardizeAtomNames;
82    /**
83     * True if atoms are to be printed to their van der Waals centers instead of nuclear centers
84     * (applies primarily to hydrogens).
85     */
86    protected final boolean vdwH;
87    /** The atomList is filled by filters that extend SystemFilter. */
88    protected List<Atom> atomList = null;
89    /** The bondList may be filled by the filters that extend SystemFilter. */
90    protected List<Bond> bondList = null;
91    /**
92     * All MolecularAssembly instances defined. More than one MolecularAssembly should be defined for
93     * PDB entries with alternate locations.
94     */
95    protected List<MolecularAssembly> systems = new Vector<>();
96    /** Append multiple files into one MolecularAssembly. */
97    protected List<File> files;
98    /** The file format being handled. */
99    protected FileType fileType = FileType.UNK;
100   /** Properties associated with this file. */
101   protected CompositeConfiguration properties;
102   /** The molecular mechanics force field being used. */
103   protected ForceField forceField;
104   /** True after the file has been read successfully. */
105   protected boolean fileRead = false;
106   /**
107    * The current MolecularAssembly being populated. Note that more than one MolecularAssembly should
108    * be defined for PDB files with alternate locations.
109    */
110   MolecularAssembly activeMolecularAssembly;
111   /** File currently being read. */
112   File currentFile = null;
113   /** A set of coordinate restraints obtained from the properties. */
114   private List<CoordRestraint> coordRestraints;
115 
116   /**
117    * Initializations common to the all the constructors.
118    *
119    * @param forceField The force field.
120    * @param properties The CompositeConfiguration properties.
121    */
122   private SystemFilter(ForceField forceField, CompositeConfiguration properties) {
123     this.forceField = forceField;
124     this.properties = properties;
125     if (properties != null) {
126       vdwH = properties.getBoolean("vdwHydrogens", false);
127       dieOnMissingAtom = properties.getBoolean("trajectory-dieOnMissing", false);
128       standardizeAtomNames = properties.getBoolean("standardizeAtomNames", true);
129     } else {
130       vdwH = false;
131       dieOnMissingAtom = false;
132       standardizeAtomNames = true;
133     }
134   }
135 
136   /**
137    * Constructor for SystemFilter.
138    *
139    * @param files a {@link java.util.List} object.
140    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
141    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
142    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
143    *     object.
144    */
145   public SystemFilter(
146       List<File> files,
147       MolecularAssembly molecularAssembly,
148       ForceField forceField,
149       CompositeConfiguration properties) {
150     this(forceField, properties);
151     this.files = files;
152     if (files != null) {
153       this.currentFile = files.get(0);
154     }
155     this.activeMolecularAssembly = molecularAssembly;
156   }
157 
158   /**
159    * Constructor for SystemFilter.
160    *
161    * @param file a {@link java.io.File} object.
162    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
163    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
164    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
165    *     object.
166    */
167   public SystemFilter(
168       File file,
169       MolecularAssembly molecularAssembly,
170       ForceField forceField,
171       CompositeConfiguration properties) {
172     this(forceField, properties);
173     files = new ArrayList<>();
174     if (file != null) {
175       files.add(file);
176     }
177     this.currentFile = file;
178     this.activeMolecularAssembly = molecularAssembly;
179   }
180 
181   /**
182    * Constructor for SystemFilter.
183    *
184    * @param file a {@link java.io.File} object.
185    * @param molecularAssemblies a {@link java.util.List} object.
186    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
187    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
188    *     object.
189    */
190   public SystemFilter(
191       File file,
192       List<MolecularAssembly> molecularAssemblies,
193       ForceField forceField,
194       CompositeConfiguration properties) {
195     this(forceField, properties);
196     files = new ArrayList<>();
197     if (file != null) {
198       files.add(file);
199     }
200     this.currentFile = file;
201     this.systems = new ArrayList<>(molecularAssemblies);
202     this.activeMolecularAssembly = systems.get(0);
203   }
204 
205   /**
206    * previousVersion
207    *
208    * @param file a {@link java.io.File} object.
209    * @return a {@link java.io.File} object.
210    */
211   public static File previousVersion(File file) {
212     if (file == null) {
213       return null;
214     }
215     String fileName = file.getAbsolutePath();
216     int dot = file.getAbsolutePath().lastIndexOf(".");
217     int under = file.getAbsolutePath().lastIndexOf("_");
218     File newFile = file;
219     if (under > dot) {
220       String name = fileName.substring(0, under);
221       newFile = new File(name);
222     }
223     File baseFile = newFile;
224     File previousFile = null;
225     int i = 1;
226     while (newFile.exists()) {
227       i = i + 1;
228       previousFile = newFile;
229       newFile = baseFile;
230       int thousand = i / 1000;
231       int hundred = (i - 1000 * thousand) / 100;
232       int tens = (i - 1000 * thousand - 100 * hundred) / 10;
233       int ones = i - 1000 * thousand - 100 * hundred - 10 * tens;
234       StringBuilder newFileString = new StringBuilder(baseFile.getAbsolutePath());
235       if (thousand != 0) {
236         newFileString.append('_').append(thousand).append(hundred).append(tens).append(ones);
237       } else if (hundred != 0) {
238         newFileString.append('_').append(hundred).append(tens).append(ones);
239       } else if (tens != 0) {
240         newFileString.append('_').append(tens).append(ones);
241       } else {
242         newFileString.append('_').append(ones);
243       }
244       newFile = new File(newFileString.toString());
245     }
246     return previousFile;
247   }
248 
249   /**
250    * Negative: prefix a version number; Positive: postfix; Zero: TINKER-style.
251    *
252    * @param vers a {@link ffx.potential.parsers.SystemFilter.Versioning} object.
253    */
254   public static void setVersioning(Versioning vers) {
255     SystemFilter.vers = vers;
256   }
257 
258   /**
259    * Use setVersioning() to choose between prefix/postfix.
260    *
261    * @param file a {@link java.io.File} object.
262    * @return a {@link java.io.File} object.
263    */
264   public static File version(File file) {
265     if (vers == Versioning.TINKER) {
266       return versionTinker(file);
267     } else {
268       if (vers == Versioning.PREFIX_ABSOLUTE || vers == Versioning.POSTFIX_ABSOLUTE) {
269         return versionAbsolute(file, (vers == Versioning.PREFIX_ABSOLUTE));
270       } else {
271         return version(file, (vers == Versioning.PREFIX));
272       }
273     }
274   }
275 
276   private static Set<Atom> parseAtomicRanges(MolecularAssembly mola, String[] toks, int tokOffset) {
277     return parseAtomicRanges(mola, Arrays.copyOfRange(toks, tokOffset, toks.length));
278   }
279 
280   private static Set<Atom> parseAtomicRanges(MolecularAssembly mola, String[] toks) {
281     Atom[] atoms = mola.getAtomArray();
282     return Arrays.stream(toks)
283         .parallel()
284         .flatMap(
285             (String tok) -> {
286               if (tok.equalsIgnoreCase("HEAVY")) {
287                 return Arrays.stream(mola.getChains())
288                     .flatMap((Polymer poly) -> poly.getAtomList().stream().filter(Atom::isHeavy));
289               } else if (tok.matches("^[0-9]+$")) {
290                 return Stream.of(atoms[Integer.parseInt(tok) - 1]);
291               } else if (tok.matches("^[0-9]+-[0-9]+")) {
292                 String[] subtoks = tok.split("-");
293                 int first = Integer.parseInt(subtoks[0]) - 1;
294                 int last = Integer.parseInt(subtoks[1]) - 1;
295                 return IntStream.rangeClosed(first, last).mapToObj((int i) -> atoms[i]);
296               } else {
297                 return Arrays.stream(atoms).filter((Atom a) -> a.getName().equals(tok));
298               }
299             })
300         .collect(Collectors.toSet());
301   }
302 
303   private static File version(File file, boolean prefix) {
304     if (file == null || !(file.exists())) {
305       return file;
306     }
307     String fn = file.getAbsolutePath();
308     int dot = fn.lastIndexOf(".");
309     int under = fn.lastIndexOf("_");
310     if (dot < 0) {
311       fn += ".unk";
312       dot = fn.lastIndexOf(".");
313     }
314     if (under < 0) {
315       under = dot;
316     }
317     String name = (prefix) ? fn.substring(0, under) : fn.substring(0, dot);
318     String extension = (prefix) ? fn.substring(dot + 1) : fn.substring(dot + 1, under);
319     int number = 0;
320     String newFn =
321         (prefix)
322             ? format("%s_%d.%s", name, number, extension)
323             : format("%s.%s_%d", name, extension, number);
324     if (prefix && under < dot) {
325       try {
326         number = Integer.parseInt(fn.substring(under + 1, dot));
327       } catch (NumberFormatException ex) {
328         // Then we have something like "AKA_dyn.pdb"
329         name = fn.substring(0, dot);
330         number++;
331         newFn = format("%s_%d.%s", name, number, extension);
332       }
333     } else if (!prefix && under > dot) {
334       try {
335         number = Integer.parseInt(fn.substring(under + 1));
336         number++;
337       } catch (NumberFormatException ex) {
338         //
339       }
340     }
341     File newFile = new File(newFn);
342     while (newFile.exists()) {
343       ++number;
344       newFn =
345           (prefix)
346               ? format("%s_%d.%s", name, number, extension)
347               : format("%s.%s_%d", name, extension, number);
348       newFile = new File(newFn);
349     }
350     return newFile;
351   }
352 
353   private static synchronized File versionAbsolute(File file, boolean prefix) {
354     if (file == null || !(file.exists())) {
355       return file;
356     }
357     String fn = file.getAbsolutePath();
358     int dot = fn.lastIndexOf(".");
359     int under = fn.lastIndexOf("_");
360     if (dot < 0) {
361       fn += ".unk";
362       dot = fn.lastIndexOf(".");
363     }
364     if (under < 0) {
365       under = dot;
366     }
367     String name = (prefix) ? fn.substring(0, under) : fn.substring(0, dot);
368     String extension = (prefix) ? fn.substring(dot + 1) : fn.substring(dot + 1, under);
369     String newFn =
370         (prefix)
371             ? format("%s_%d.%s", name, absoluteCounter, extension)
372             : format("%s.%s_%d", name, extension, absoluteCounter);
373     File newFile = new File(newFn);
374     while (newFile.exists()) {
375       absoluteCounter++;
376       newFn =
377           (prefix)
378               ? format("%s_%d.%s", name, absoluteCounter, extension)
379               : format("%s.%s_%d", name, extension, absoluteCounter);
380       newFile = new File(newFn);
381     }
382     return newFile;
383   }
384 
385   /**
386    * This follows the TINKER file versioning scheme.
387    *
388    * @param file File to find a version for.
389    * @return File Versioned File.
390    */
391   private static File versionTinker(File file) {
392     if (file == null) {
393       return null;
394     }
395     if (!file.exists()) {
396       return file;
397     }
398     String fileName = file.getAbsolutePath();
399     int dot = file.getAbsolutePath().lastIndexOf(".");
400     int under = file.getAbsolutePath().lastIndexOf("_");
401     File newFile = file;
402     if (under > dot) {
403       String name = fileName.substring(0, under);
404       newFile = new File(name);
405     }
406     File oldFile = newFile;
407     int i = 1;
408     while (newFile.exists()) {
409       i = i + 1;
410       String newFileString = format("%s_%d", oldFile.getAbsolutePath(), i);
411       newFile = new File(newFileString);
412     }
413     return newFile;
414   }
415 
416   /**
417    * Automatically sets atom-specific flags, particularly nouse and inactive, and apply harmonic
418    * restraints. Intended to be called at the end of readFile() implementations.
419    *
420    * <p>Supported syntax: "(\\d+)-(\\d+)"
421    */
422   public void applyAtomProperties() {
423     /*
424       What may be a more elegant implementation is to make readFile() a
425       public concrete, but skeletal method, and then have readFile() call a
426       protected abstract readFile method for each implementation.
427     */
428     Atom[] molaAtoms = activeMolecularAssembly.getAtomArray();
429     int nmolaAtoms = molaAtoms.length;
430     String[] nouseKeys = properties.getStringArray("nouse");
431     for (String nouseKey : nouseKeys) {
432       String[] toks = nouseKey.split("\\s+");
433       for (String tok : toks) {
434         try {
435           List<Integer> nouseRange = parseAtomRange("nouse", tok, nmolaAtoms);
436           for (int j : nouseRange) {
437             molaAtoms[j].setUse(false);
438           }
439         } catch (IllegalArgumentException ex) {
440           boolean atomFound = false;
441           for (Atom atom : molaAtoms) {
442             if (atom.getName().equalsIgnoreCase(tok)) {
443               atomFound = true;
444               atom.setUse(false);
445             }
446           }
447           if (atomFound) {
448             logger.info(format(" Setting atoms with name %s to not be used", tok));
449           } else {
450             logger.log(Level.INFO, ex.getLocalizedMessage());
451           }
452         }
453       }
454     }
455 
456     if (properties.containsKey("active")) {
457       for (Atom atom : molaAtoms) {
458         atom.setActive(false);
459       }
460       String[] activeKeys = properties.getStringArray("active");
461       for (String inactiveKey : activeKeys) {
462         try {
463           List<Integer> inactiveRange = parseAtomRange("inactive", inactiveKey, nmolaAtoms);
464           for (int i : inactiveRange) {
465             molaAtoms[i].setActive(false);
466           }
467         } catch (IllegalArgumentException ex) {
468           logger.log(Level.INFO, ex.getLocalizedMessage());
469         }
470       }
471     } else if (properties.containsKey("inactive")) {
472       for (Atom atom : molaAtoms) {
473         atom.setActive(true);
474       }
475       String[] inactiveKeys = properties.getStringArray("inactive");
476       for (String inactiveKey : inactiveKeys) {
477         try {
478           List<Integer> inactiveRange = parseAtomRange("inactive", inactiveKey, nmolaAtoms);
479           for (int i : inactiveRange) {
480             molaAtoms[i].setActive(false);
481           }
482         } catch (IllegalArgumentException ex) {
483           logger.log(Level.INFO, ex.getLocalizedMessage());
484         }
485       }
486     }
487 
488     coordRestraints = new ArrayList<>();
489     String[] cRestraintStrings = properties.getStringArray("restraint");
490     for (String coordRestraint : cRestraintStrings) {
491       String[] toks = coordRestraint.split("\\s+");
492       double forceconst;
493       try {
494         forceconst = Double.parseDouble(toks[0]);
495       } catch (NumberFormatException ex) {
496         logger.log(
497             Level.INFO,
498             " First argument to coordinate restraint must be a positive force constant; discarding coordinate restraint.");
499         continue;
500       }
501       if (forceconst < 0) {
502         logger.log(
503             Level.INFO, " Force constants must be positive. Discarding coordinate restraint.");
504         continue;
505       }
506       logger.info(
507           format(
508               " Adding lambda-disabled coordinate restraint "
509                   + "with force constant %10.4f kcal/mol/A",
510               forceconst));
511       Set<Atom> restraintAtoms = parseAtomicRanges(activeMolecularAssembly, toks, 1);
512       if (!(restraintAtoms == null || restraintAtoms.isEmpty())) {
513         Atom[] ats = restraintAtoms.toArray(new Atom[0]);
514         coordRestraints.add(new CoordRestraint(ats, forceField, false, forceconst));
515       } else {
516         logger.warning(format(" Empty or unparseable restraint argument %s", coordRestraint));
517       }
518     }
519 
520     String[] lamRestraintStrings = properties.getStringArray("lamrestraint");
521     for (String coordRestraint : lamRestraintStrings) {
522       String[] toks = coordRestraint.split("\\s+");
523       double forceconst = Double.parseDouble(toks[0]);
524       logger.info(
525           format(
526               " Adding lambda-enabled coordinate restraint "
527                   + "with force constant %10.4f kcal/mol/A",
528               forceconst));
529       Set<Atom> restraintAtoms = new HashSet<>();
530 
531       for (int i = 1; i < toks.length; i++) {
532         try {
533           List<Integer> restrRange = parseAtomRange("restraint", toks[i], nmolaAtoms);
534           for (int j : restrRange) {
535             restraintAtoms.add(molaAtoms[j]);
536           }
537         } catch (IllegalArgumentException ex) {
538           boolean atomFound = false;
539           for (Atom atom : molaAtoms) {
540             if (atom.getName().equalsIgnoreCase(toks[i])) {
541               atomFound = true;
542               restraintAtoms.add(atom);
543             }
544           }
545           if (atomFound) {
546             logger.info(format(" Added atoms with name %s to restraint", toks[i]));
547           } else {
548             logger.log(
549                 Level.INFO,
550                 format(
551                     " Restraint input %s "
552                         + "could not be parsed as a numerical range or "
553                         + "an atom type present in assembly",
554                     toks[i]));
555           }
556         }
557       }
558       if (!restraintAtoms.isEmpty()) {
559         Atom[] ats = restraintAtoms.toArray(new Atom[0]);
560         coordRestraints.add(new CoordRestraint(ats, forceField, true, forceconst));
561       } else {
562         logger.warning(format(" Empty or unparseable restraint argument %s", coordRestraint));
563       }
564     }
565 
566     String[] xyzRestStrings = properties.getStringArray("xyzRestraint");
567 
568     // Variables to let sequential and otherwise identical xyzRestraint strings to be globbed into
569     // one restraint.
570     List<Atom> restraintAts = new ArrayList<>();
571     List<double[]> coords = new ArrayList<>();
572     double lastForceConst = 0;
573     boolean lastUseLam = false;
574 
575     for (String xR : xyzRestStrings) {
576       String[] toks = xR.split("\\s+");
577       int nToks = toks.length;
578       if (nToks != 6) {
579         logger.info(
580             " XYZ restraint rejected: must have force constant, lambda boolean (true/false), 3 coordinates, and an atom number");
581         logger.info(
582             " For a coordinate restraint centered on original coordinates, use restraint or lamrestraint keys.");
583         logger.info(format(" Rejected restraint string: %s", xR));
584       } else {
585         try {
586           double forceConst = Double.parseDouble(toks[0]);
587           boolean useLambda = Boolean.parseBoolean(toks[1]);
588 
589           if (forceConst != lastForceConst || useLambda != lastUseLam) {
590             int nAts = restraintAts.size();
591             if (nAts != 0) {
592               double[][] restXYZ = new double[3][nAts];
593               Atom[] atArr = restraintAts.toArray(new Atom[nAts]);
594               for (int i = 0; i < 3; i++) {
595                 for (int j = 0; j < nAts; j++) {
596                   restXYZ[i][j] = coords.get(j)[i];
597                 }
598               }
599               CoordRestraint thePin =
600                   new CoordRestraint(atArr, forceField, lastUseLam, lastForceConst);
601               thePin.setCoordinatePin(restXYZ);
602               thePin.setIgnoreHydrogen(false);
603               coordRestraints.add(thePin);
604             }
605             restraintAts = new ArrayList<>();
606             coords = new ArrayList<>();
607             lastForceConst = forceConst;
608             lastUseLam = useLambda;
609           }
610 
611           double[] atXYZ = new double[3];
612           for (int i = 0; i < 3; i++) {
613             atXYZ[i] = Double.parseDouble(toks[i + 2]);
614           }
615           int atNum = Integer.parseInt(toks[5]) - 1;
616           restraintAts.add(molaAtoms[atNum]);
617           coords.add(atXYZ);
618         } catch (Exception ex) {
619           logger.info(format(" Exception parsing xyzRestraint %s: %s", xR, ex.toString()));
620         }
621       }
622     }
623 
624     int nAts = restraintAts.size();
625     if (nAts != 0) {
626       double[][] restXYZ = new double[3][nAts];
627       Atom[] atArr = restraintAts.toArray(new Atom[nAts]);
628       for (int i = 0; i < 3; i++) {
629         for (int j = 0; j < nAts; j++) {
630           restXYZ[i][j] = coords.get(j)[i];
631         }
632       }
633       CoordRestraint thePin = new CoordRestraint(atArr, forceField, lastUseLam, lastForceConst);
634       thePin.setCoordinatePin(restXYZ);
635       thePin.setIgnoreHydrogen(false);
636       coordRestraints.add(thePin);
637     }
638 
639     String[] noElStrings = properties.getStringArray("noElectro");
640     for (String noE : noElStrings) {
641       String[] toks = noE.split("\\s+");
642       for (String tok : toks) {
643         try {
644           List<Integer> noERange = parseAtomRange("noElectro", tok, nmolaAtoms);
645           for (int i : noERange) {
646             molaAtoms[i].setElectrostatics(false);
647           }
648         } catch (IllegalArgumentException ex) {
649           boolean atomFound = false;
650           for (Atom atom : molaAtoms) {
651             if (atom.getName().equalsIgnoreCase(tok)) {
652               atomFound = true;
653               atom.setElectrostatics(false);
654             }
655           }
656           if (atomFound) {
657             logger.info(format(" Disabled electrostatics for atoms with name %s", tok));
658           } else {
659             logger.log(
660                 Level.INFO,
661                 format(
662                     " No electrostatics input %s could not be parsed as a numerical "
663                         + "range or atom type present in assembly",
664                     tok));
665           }
666         }
667       }
668     }
669   }
670 
671   /**
672    * Attempts to close any open resources associated with the underlying file; primarily to be used
673    * when finished reading a trajectory.
674    */
675   public abstract void closeReader();
676 
677   public int countNumModels() {
678     return -1;
679   }
680 
681   /**
682    * Returns true if the read was successful
683    *
684    * @return a boolean.
685    */
686   public boolean fileRead() {
687     return fileRead;
688   }
689 
690   /**
691    * Return the MolecularSystem that has been read in
692    *
693    * @return a {@link ffx.potential.MolecularAssembly} object.
694    */
695   public MolecularAssembly getActiveMolecularSystem() {
696     return activeMolecularAssembly;
697   }
698 
699   /**
700    * Getter for the field <code>atomList</code>.
701    *
702    * @return a {@link java.util.List} object.
703    */
704   public List<Atom> getAtomList() {
705     return atomList;
706   }
707 
708   /**
709    * getBondCount
710    *
711    * @return a int.
712    */
713   public int getBondCount() {
714     if (bondList == null) {
715       return 0;
716     }
717     return bondList.size();
718   }
719 
720   /**
721    * Gets the coordinate restraints parsed by this Filter.
722    *
723    * @return Coordinate restraints.
724    */
725   public List<CoordRestraint> getCoordRestraints() {
726     if (!coordRestraints.isEmpty()) {
727       return new ArrayList<>(coordRestraints);
728     } else {
729       return null;
730     }
731   }
732 
733   /**
734    * getFile
735    *
736    * @return a {@link java.io.File} object.
737    */
738   public File getFile() {
739     return currentFile;
740   }
741 
742   /**
743    * setFile
744    *
745    * @param file a {@link java.io.File} object.
746    */
747   public void setFile(File file) {
748     this.currentFile = file;
749     files = new ArrayList<>();
750     if (file != null) {
751       files.add(file);
752     }
753   }
754 
755   /**
756    * Getter for the field <code>files</code>.
757    *
758    * @return a {@link java.util.List} object.
759    */
760   public List<File> getFiles() {
761     return files;
762   }
763 
764   /**
765    * Setter for the field <code>files</code>.
766    *
767    * @param files a {@link java.util.List} object.
768    */
769   public void setFiles(List<File> files) {
770     this.files = files;
771     if (files != null && !files.isEmpty()) {
772       this.currentFile = files.get(0);
773     } else {
774       this.currentFile = null;
775     }
776   }
777 
778   /**
779    * Gets the last read lambda value read by the filter, if any.
780    *
781    * @return Last lambda value read by this filter.
782    */
783   public OptionalDouble getLastReadLambda() {
784     return OptionalDouble.empty();
785   }
786 
787   /**
788    * getMolecularAssemblys
789    *
790    * @return an array of {@link ffx.potential.MolecularAssembly} objects.
791    */
792   public MolecularAssembly[] getMolecularAssemblys() {
793     if (systems.size() > 0) {
794       return systems.toArray(new MolecularAssembly[0]);
795     } else {
796       return new MolecularAssembly[] {activeMolecularAssembly};
797     }
798   }
799 
800   /**
801    * Gets all remark lines read by the last readFile or readNext call.
802    *
803    * @return Array of Strings representing remark lines, if any.
804    */
805   public String[] getRemarkLines() {
806     return new String[0];
807   }
808 
809   /**
810    * Return snapshot number.
811    *
812    * @return The snapshot number.
813    */
814   public int getSnapshot() {
815     return -1;
816   }
817 
818   /**
819    * getType
820    *
821    * @return a {@link ffx.potential.Utilities.FileType} object.
822    */
823   public FileType getType() {
824     return fileType;
825   }
826 
827   /**
828    * setType
829    *
830    * @param fileType a {@link ffx.potential.Utilities.FileType} object.
831    */
832   public void setType(FileType fileType) {
833     this.fileType = fileType;
834   }
835 
836   /**
837    * This method is different for each subclass and must be overidden
838    *
839    * @return a boolean.
840    */
841   public abstract boolean readFile();
842 
843   /**
844    * Reads the next model if applicable (currently, ARC and PDB files only).
845    *
846    * @return If next model read.
847    */
848   public abstract boolean readNext();
849 
850   /**
851    * Reads the next model if applicable (currently, ARC files only).
852    *
853    * @param resetPosition Resets to first frame.
854    * @return If next model read.
855    */
856   public abstract boolean readNext(boolean resetPosition);
857 
858   /**
859    * Reads the next model if applicable (currently, ARC files only).
860    *
861    * @param resetPosition Resets to first frame.
862    * @param print Flag to print.
863    * @return If next model read.
864    */
865   public abstract boolean readNext(boolean resetPosition, boolean print);
866 
867   /**
868    * Reads the next model if applicable (currently, ARC files only).
869    *
870    * @param resetPosition Resets to first frame.
871    * @param print Flag to print.
872    * @param parse Parse data in file. May want to skip structures for parallel jobs.
873    * @return If next model read.
874    */
875   public abstract boolean readNext(boolean resetPosition, boolean print, boolean parse);
876 
877   /**
878    * Setter for the field <code>forceField</code>.
879    *
880    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
881    */
882   public void setForceField(ForceField forceField) {
883     this.forceField = forceField;
884   }
885 
886   /**
887    * Setter for the field <code>properties</code>.
888    *
889    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
890    *     object.
891    */
892   public void setProperties(CompositeConfiguration properties) {
893     this.properties = properties;
894   }
895 
896   /**
897    * This method is different for each subclass and must be overidden.
898    *
899    * <p>If the append flag is true, "saveFile" will be appended to. Otherwise the default versioning
900    * scheme will be applied.
901    *
902    * @param saveFile a {@link java.io.File} object.
903    * @param append a boolean.
904    * @return a boolean.
905    */
906   public boolean writeFile(File saveFile, boolean append) {
907     return writeFile(saveFile, append, null);
908   }
909 
910   /**
911    * This method is different for each subclass and must be overidden.
912    *
913    * <p>If the append flag is true, "saveFile" will be appended to. Otherwise the default versioning
914    * scheme will be applied.
915    *
916    * @param saveFile a {@link java.io.File} object.
917    * @param append a boolean.
918    * @param extraLines Additional lines to append to a comments section, or null.
919    * @return a boolean.
920    */
921   public abstract boolean writeFile(File saveFile, boolean append, String[] extraLines);
922 
923   /**
924    * Setter for the field <code>fileRead</code>.
925    *
926    * @param fileRead a boolean.
927    */
928   protected void setFileRead(boolean fileRead) {
929     this.fileRead = fileRead;
930   }
931 
932   /**
933    * setMolecularSystem
934    *
935    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
936    */
937   void setMolecularSystem(MolecularAssembly molecularAssembly) {
938     activeMolecularAssembly = molecularAssembly;
939   }
940 
941   public enum Versioning {
942     TINKER,
943     PREFIX,
944     POSTFIX,
945     PREFIX_ABSOLUTE,
946     POSTFIX_ABSOLUTE,
947     NONE;
948   }
949 }