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