Convert forcefield search to C++
[alexxy/gromacs.git] / src / gromacs / utility / datafilefinder.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015, 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 gmx::DataFileFinder.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "datafilefinder.h"
45
46 #include <cstdlib>
47
48 #include <string>
49 #include <vector>
50
51 #include "gromacs/utility/directoryenumerator.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/file.h"
54 #include "gromacs/utility/path.h"
55 #include "gromacs/utility/programcontext.h"
56 #include "gromacs/utility/stringutil.h"
57
58 namespace gmx
59 {
60
61 /********************************************************************
62  * DataFileFinder::Impl
63  */
64
65 class DataFileFinder::Impl
66 {
67     public:
68         Impl() : envName_(NULL), bEnvIsSet_(false) {}
69
70         const char               *envName_;
71         bool                      bEnvIsSet_;
72         std::vector<std::string>  searchPath_;
73 };
74
75 /********************************************************************
76  * DataFileFinder
77  */
78
79 DataFileFinder::DataFileFinder()
80     : impl_(NULL)
81 {
82 }
83
84 DataFileFinder::~DataFileFinder()
85 {
86 }
87
88 void DataFileFinder::setSearchPathFromEnv(const char *envVarName)
89 {
90     if (!impl_.get())
91     {
92         impl_.reset(new Impl());
93     }
94     impl_->envName_ = envVarName;
95     const char *const lib = getenv(envVarName);
96     if (lib != NULL)
97     {
98         impl_->bEnvIsSet_ = true;
99         Path::splitPathEnvironment(lib, &impl_->searchPath_);
100     }
101 }
102
103 FILE *DataFileFinder::openFile(const DataFileOptions &options) const
104 {
105     // TODO: There is a small race here, since there is some time between
106     // the exists() calls and actually opening the file.  It would be better
107     // to leave the file open after a successful exists() if the desire is to
108     // actually open the file.
109     std::string filename = findFile(options);
110     if (filename.empty())
111     {
112         return NULL;
113     }
114 #if 0
115     if (debug)
116     {
117         fprintf(debug, "Opening library file %s\n", fn);
118     }
119 #endif
120     return File::openRawHandle(filename, "r");
121 }
122
123 std::string DataFileFinder::findFile(const DataFileOptions &options) const
124 {
125     if (options.bCurrentDir_ && Path::exists(options.filename_))
126     {
127         return options.filename_;
128     }
129     if (impl_.get())
130     {
131         std::vector<std::string>::const_iterator i;
132         for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
133         {
134             // TODO: Deal with an empty search path entry more reasonably.
135             std::string testPath = Path::join(*i, options.filename_);
136             // TODO: Consider skipping directories.
137             if (Path::exists(testPath))
138             {
139                 return testPath;
140             }
141         }
142     }
143     const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
144     if (defaultPath != NULL && defaultPath[0] != '\0')
145     {
146         std::string testPath = Path::join(defaultPath, options.filename_);
147         if (Path::exists(testPath))
148         {
149             return testPath;
150         }
151     }
152     if (options.bThrow_)
153     {
154         const char *const envName   = (impl_.get() ? impl_->envName_ : NULL);
155         const bool        bEnvIsSet = (impl_.get() ? impl_->bEnvIsSet_ : false);
156         std::string       message(
157                 formatString("Library file '%s' not found", options.filename_));
158         if (options.bCurrentDir_)
159         {
160             message.append(" in current dir nor");
161         }
162         if (bEnvIsSet)
163         {
164             message.append(formatString(" in your %s path nor", envName));
165         }
166         message.append(" in the default directories.\nThe following paths were searched:");
167         if (options.bCurrentDir_)
168         {
169             message.append("\n  ");
170             message.append(Path::getWorkingDirectory());
171             message.append(" (current dir)");
172         }
173         if (impl_.get())
174         {
175             std::vector<std::string>::const_iterator i;
176             for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
177             {
178                 message.append("\n  ");
179                 message.append(*i);
180             }
181         }
182         if (defaultPath != NULL && defaultPath[0] != '\0')
183         {
184             message.append("\n  ");
185             message.append(defaultPath);
186             message.append(" (default)");
187         }
188         if (!bEnvIsSet && envName != NULL)
189         {
190             message.append(
191                     formatString("\nYou can set additional directories to search "
192                                  "with the %s path variable.", envName));
193         }
194         GMX_THROW(FileIOError(message));
195     }
196     return std::string();
197 }
198
199 std::vector<DataFileInfo>
200 DataFileFinder::enumerateFiles(const DataFileOptions &options) const
201 {
202     // TODO: Consider if not being able to list one of the directories should
203     // really be a fatal error. Or alternatively, check somewhere else that
204     // paths in GMXLIB are valid.
205     std::vector<DataFileInfo>                result;
206     std::vector<std::string>::const_iterator i;
207     if (options.bCurrentDir_)
208     {
209         std::vector<std::string> files
210             = DirectoryEnumerator::enumerateFilesWithExtension(
211                         ".", options.filename_, false);
212         for (i = files.begin(); i != files.end(); ++i)
213         {
214             result.push_back(DataFileInfo(".", *i, false));
215         }
216     }
217     if (impl_.get())
218     {
219         std::vector<std::string>::const_iterator j;
220         for (j = impl_->searchPath_.begin(); j != impl_->searchPath_.end(); ++j)
221         {
222             std::vector<std::string> files
223                 = DirectoryEnumerator::enumerateFilesWithExtension(
224                             j->c_str(), options.filename_, false);
225             for (i = files.begin(); i != files.end(); ++i)
226             {
227                 result.push_back(DataFileInfo(*j, *i, false));
228             }
229         }
230     }
231     const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
232     if (defaultPath != NULL && defaultPath[0] != '\0')
233     {
234         std::vector<std::string> files
235             = DirectoryEnumerator::enumerateFilesWithExtension(
236                         defaultPath, options.filename_, false);
237         for (i = files.begin(); i != files.end(); ++i)
238         {
239             result.push_back(DataFileInfo(defaultPath, *i, true));
240         }
241     }
242     if (result.empty() && options.bThrow_)
243     {
244         // TODO: Print the search path as is done in findFile().
245         std::string message(
246                 formatString("Could not find any files ending on '%s' in the "
247                              "current directory or the GROMACS library search path",
248                              options.filename_));
249         GMX_THROW(FileIOError(message));
250     }
251     return result;
252 }
253
254 } // namespace gmx