Merge release-5-0 into master
[alexxy/gromacs.git] / src / gromacs / options / filenameoption.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements classes in filenameoption.h and filenameoptionstorage.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_options
41  */
42 #include "filenameoption.h"
43 #include "filenameoptionstorage.h"
44
45 #include <string>
46 #include <vector>
47
48 #include "gromacs/fileio/filenm.h"
49 #include "gromacs/options/filenameoptionmanager.h"
50 #include "gromacs/utility/arrayref.h"
51 #include "gromacs/utility/file.h"
52 #include "gromacs/utility/gmxassert.h"
53 #include "gromacs/utility/stringutil.h"
54
55 namespace gmx
56 {
57
58 namespace
59 {
60
61 class FileTypeRegistry;
62
63 //! \addtogroup module_options
64 //! \{
65
66 //! Shorthand for a list of file extensions.
67 typedef std::vector<const char *> ExtensionList;
68
69 /********************************************************************
70  * FileTypeHandler
71  */
72
73 /*! \internal \brief
74  * Handles a single file type known to FileNameOptionStorage.
75  */
76 class FileTypeHandler
77 {
78     public:
79         //! Returns the list of extensions for this file type.
80         const ExtensionList &extensions() const { return extensions_; }
81
82         //! Returns whether \p filename has a valid extension for this type.
83         bool hasKnownExtension(const std::string &filename) const;
84         //! Adds a default extension for this type to \p filename.
85         std::string addExtension(const std::string &filename) const;
86         /*! \brief
87          * Adds an extension to \p filename if it results in an existing file.
88          *
89          * Tries to add each extension for this file type to \p filename and
90          * checks whether this results in an existing file.
91          * The first match is returned.
92          * Returns an empty string if no existing file is found.
93          */
94         std::string findFileWithExtension(const std::string &filename) const;
95
96     private:
97         //! Possible extensions for this file type.
98         ExtensionList extensions_;
99
100         /*! \brief
101          * Needed for initialization; all initialization is handled by
102          * FileTypeRegistry.
103          */
104         friend class FileTypeRegistry;
105 };
106
107 bool
108 FileTypeHandler::hasKnownExtension(const std::string &filename) const
109 {
110     for (size_t i = 0; i < extensions_.size(); ++i)
111     {
112         if (endsWith(filename, extensions_[i]))
113         {
114             return true;
115         }
116     }
117     return false;
118 }
119
120 std::string
121 FileTypeHandler::addExtension(const std::string &filename) const
122 {
123     if (extensions_.empty())
124     {
125         return filename;
126     }
127     return filename + extensions_[0];
128 }
129
130 std::string
131 FileTypeHandler::findFileWithExtension(const std::string &filename) const
132 {
133     for (size_t i = 0; i < extensions_.size(); ++i)
134     {
135         std::string testFilename(filename + extensions_[i]);
136         if (File::exists(testFilename))
137         {
138             return testFilename;
139         }
140     }
141     return std::string();
142 }
143
144 /********************************************************************
145  * FileTypeRegistry
146  */
147
148 /*! \internal \brief
149  * Singleton for managing static file type info for FileNameOptionStorage.
150  */
151 class FileTypeRegistry
152 {
153     public:
154         //! Returns a singleton instance of this class.
155         static const FileTypeRegistry &instance();
156         //! Returns a handler for a single file type.
157         const FileTypeHandler &
158         handlerForType(OptionFileType type, int legacyType) const;
159
160     private:
161         //! Initializes the file type registry.
162         FileTypeRegistry();
163
164         //! Registers a file type that corresponds to a ftp in filenm.h.
165         void registerType(int type, int ftp);
166         //! Registers a file type with a single extension.
167         void registerType(int type, const char *extension);
168
169         std::vector<FileTypeHandler> filetypes_;
170 };
171
172 // static
173 const FileTypeRegistry &
174 FileTypeRegistry::instance()
175 {
176     static FileTypeRegistry singleton;
177     return singleton;
178 }
179
180 const FileTypeHandler &
181 FileTypeRegistry::handlerForType(OptionFileType type, int legacyType) const
182 {
183     int index = type;
184     if (type == eftUnknown && legacyType >= 0)
185     {
186         index = eftOptionFileType_NR + legacyType;
187     }
188     GMX_RELEASE_ASSERT(index >= 0 && static_cast<size_t>(index) < filetypes_.size(),
189                        "Invalid file type");
190     return filetypes_[index];
191 }
192
193 FileTypeRegistry::FileTypeRegistry()
194 {
195     filetypes_.resize(eftOptionFileType_NR + efNR);
196     registerType(eftTopology,    efTPS);
197     registerType(eftTrajectory,  efTRX);
198     registerType(eftPDB,         efPDB);
199     registerType(eftIndex,       efNDX);
200     registerType(eftPlot,        efXVG);
201     registerType(eftGenericData, efDAT);
202     for (int i = 0; i < efNR; ++i)
203     {
204         registerType(eftOptionFileType_NR + i, i);
205     }
206 }
207
208 void FileTypeRegistry::registerType(int type, int ftp)
209 {
210     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
211                        "Invalid file type");
212     const int genericTypeCount = ftp2generic_count(ftp);
213     if (genericTypeCount > 0)
214     {
215         const int *const genericTypes = ftp2generic_list(ftp);
216         filetypes_[type].extensions_.clear();
217         filetypes_[type].extensions_.reserve(genericTypeCount);
218         for (int i = 0; i < genericTypeCount; ++i)
219         {
220             filetypes_[type].extensions_.push_back(ftp2ext_with_dot(genericTypes[i]));
221         }
222     }
223     else
224     {
225         registerType(type, ftp2ext_with_dot(ftp));
226     }
227 }
228
229 void FileTypeRegistry::registerType(int type, const char *extension)
230 {
231     GMX_RELEASE_ASSERT(type >= 0 && static_cast<size_t>(type) < filetypes_.size(),
232                        "Invalid file type");
233     filetypes_[type].extensions_.assign(1, extension);
234 }
235
236 /*! \brief
237  * Helper method to complete a file name provided to a file name option.
238  *
239  * \param[in] value      Value provided to the file name option.
240  * \param[in] filetype   File type for the option.
241  * \param[in] legacyType If \p filetype is eftUnknown, this gives the type as
242  *     an enum value from filenm.h.
243  * \param[in] bCompleteToExisting
244  *     Whether to check existing files when completing the extension.
245  * \returns   \p value with possible extension added.
246  */
247 std::string completeFileName(const std::string &value, OptionFileType filetype,
248                              int legacyType, bool bCompleteToExisting)
249 {
250     if (bCompleteToExisting && File::exists(value))
251     {
252         // TODO: This may not work as expected if the value is passed to a
253         // function that uses fn2ftp() to determine the file type and the input
254         // file has an unrecognized extension.
255         return value;
256     }
257     const FileTypeRegistry &registry    = FileTypeRegistry::instance();
258     const FileTypeHandler  &typeHandler = registry.handlerForType(filetype, legacyType);
259     if (typeHandler.hasKnownExtension(value))
260     {
261         return value;
262     }
263     if (bCompleteToExisting)
264     {
265         std::string newValue = typeHandler.findFileWithExtension(value);
266         if (!newValue.empty())
267         {
268             return newValue;
269         }
270     }
271     return typeHandler.addExtension(value);
272 }
273
274 //! \}
275
276 }   // namespace
277
278 /********************************************************************
279  * FileNameOptionStorage
280  */
281
282 FileNameOptionStorage::FileNameOptionStorage(const FileNameOption &settings)
283     : MyBase(settings), info_(this), manager_(NULL),
284       filetype_(settings.filetype_), legacyType_(settings.legacyType_),
285       bRead_(settings.bRead_), bWrite_(settings.bWrite_),
286       bLibrary_(settings.bLibrary_)
287 {
288     GMX_RELEASE_ASSERT(!hasFlag(efOption_MultipleTimes),
289                        "allowMultiple() is not supported for file name options");
290     if (settings.defaultBasename_ != NULL)
291     {
292         std::string defaultValue =
293             completeFileName(settings.defaultBasename_, filetype_,
294                              legacyType_, false);
295         setDefaultValueIfSet(defaultValue);
296         if (isRequired())
297         {
298             setDefaultValue(defaultValue);
299         }
300     }
301 }
302
303 void FileNameOptionStorage::setManager(FileNameOptionManager *manager)
304 {
305     GMX_RELEASE_ASSERT(manager_ == NULL || manager_ == manager,
306                        "Manager cannot be changed once set");
307     if (manager_ == NULL)
308     {
309         manager_ = manager;
310     }
311 }
312
313 std::string FileNameOptionStorage::typeString() const
314 {
315     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
316     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_, legacyType_);
317     const ExtensionList          &extensions  = typeHandler.extensions();
318     std::string                   result;
319     ExtensionList::const_iterator i;
320     int                           count = 0;
321     for (i = extensions.begin(); count < 2 && i != extensions.end(); ++i, ++count)
322     {
323         if (i != extensions.begin())
324         {
325             result.append("/");
326         }
327         result.append(*i);
328     }
329     if (i != extensions.end())
330     {
331         result.append("/...");
332     }
333     if (result.empty())
334     {
335         if (legacyType_ == efRND)
336         {
337             result = "dir";
338         }
339         else
340         {
341             result = "file";
342         }
343     }
344     return result;
345 }
346
347 std::string FileNameOptionStorage::formatExtraDescription() const
348 {
349     const FileTypeRegistry       &registry    = FileTypeRegistry::instance();
350     const FileTypeHandler        &typeHandler = registry.handlerForType(filetype_, legacyType_);
351     const ExtensionList          &extensions  = typeHandler.extensions();
352     std::string                   result;
353     if (extensions.size() > 2)
354     {
355         result.append(":");
356         ExtensionList::const_iterator i;
357         for (i = extensions.begin(); i != extensions.end(); ++i)
358         {
359             result.append(" ");
360             result.append((*i) + 1);
361         }
362     }
363     return result;
364 }
365
366 std::string FileNameOptionStorage::formatSingleValue(const std::string &value) const
367 {
368     return value;
369 }
370
371 void FileNameOptionStorage::convertValue(const std::string &value)
372 {
373     bool bInput = isInputFile() || isInputOutputFile();
374     addValue(completeFileName(value, filetype_, legacyType_, bInput));
375 }
376
377 void FileNameOptionStorage::processAll()
378 {
379     if (hasFlag(efOption_HasDefaultValue))
380     {
381         const bool              bInput      = isInputFile() || isInputOutputFile();
382         const FileTypeRegistry &registry    = FileTypeRegistry::instance();
383         const FileTypeHandler  &typeHandler = registry.handlerForType(filetype_, legacyType_);
384         const ExtensionList    &extensions  = typeHandler.extensions();
385         ValueList              &valueList   = values();
386         GMX_RELEASE_ASSERT(valueList.size() == 1,
387                            "There should be only one default value");
388         const bool              bGlobalDefault =
389             (manager_ != NULL && !manager_->defaultFileName().empty());
390         if (!valueList[0].empty() && (extensions.size() > 1 || bGlobalDefault))
391         {
392             std::string oldValue = valueList[0];
393             std::string newValue = stripSuffixIfPresent(oldValue, extensions[0]);
394             if (bGlobalDefault)
395             {
396                 newValue = manager_->defaultFileName();
397             }
398             newValue = completeFileName(newValue, filetype_, legacyType_, bInput);
399             if (newValue != oldValue)
400             {
401                 valueList[0] = newValue;
402                 refreshValues();
403             }
404         }
405     }
406 }
407
408 bool FileNameOptionStorage::isDirectoryOption() const
409 {
410     return legacyType_ == efRND;
411 }
412
413 ConstArrayRef<const char *> FileNameOptionStorage::extensions() const
414 {
415     const FileTypeRegistry &registry    = FileTypeRegistry::instance();
416     const FileTypeHandler  &typeHandler = registry.handlerForType(filetype_, legacyType_);
417     const ExtensionList    &extensions  = typeHandler.extensions();
418     return ConstArrayRef<const char *>(extensions.begin(), extensions.end());
419 }
420
421 /********************************************************************
422  * FileNameOptionInfo
423  */
424
425 FileNameOptionInfo::FileNameOptionInfo(FileNameOptionStorage *option)
426     : OptionInfo(option)
427 {
428 }
429
430 FileNameOptionStorage &FileNameOptionInfo::option()
431 {
432     return static_cast<FileNameOptionStorage &>(OptionInfo::option());
433 }
434
435 const FileNameOptionStorage &FileNameOptionInfo::option() const
436 {
437     return static_cast<const FileNameOptionStorage &>(OptionInfo::option());
438 }
439
440 void FileNameOptionInfo::setManager(FileNameOptionManager *manager)
441 {
442     option().setManager(manager);
443 }
444
445 bool FileNameOptionInfo::isInputFile() const
446 {
447     return option().isInputFile();
448 }
449
450 bool FileNameOptionInfo::isOutputFile() const
451 {
452     return option().isOutputFile();
453 }
454
455 bool FileNameOptionInfo::isInputOutputFile() const
456 {
457     return option().isInputOutputFile();
458 }
459
460 bool FileNameOptionInfo::isLibraryFile() const
461 {
462     return option().isLibraryFile();
463 }
464
465 bool FileNameOptionInfo::isDirectoryOption() const
466 {
467     return option().isDirectoryOption();
468 }
469
470 FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
471 {
472     return option().extensions();
473 }
474
475 /********************************************************************
476  * FileNameOption
477  */
478
479 AbstractOptionStoragePointer FileNameOption::createStorage() const
480 {
481     return AbstractOptionStoragePointer(new FileNameOptionStorage(*this));
482 }
483
484 } // namespace gmx