LibreOffice Module unoidl (master) 1
sourcetreeprovider.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10#include <sal/config.h>
11#include <sal/log.hxx>
12
13#include <map>
14#include <utility>
15#include <vector>
16
17#include <osl/file.h>
18#include <osl/file.hxx>
19#include <rtl/character.hxx>
20#include <rtl/ref.hxx>
21#include <rtl/ustrbuf.hxx>
22#include <rtl/ustring.hxx>
23#include <unoidl/unoidl.hxx>
24
27
28#if defined MACOSX
29#include <dirent.h>
30#include <osl/thread.h>
31#endif
32
33namespace unoidl::detail {
34
35namespace {
36
37//TODO: Bad hack to work around osl::FileStatus::getFileName not determining the
38// original spelling of a file name (not even with
39// osl_FileStatus_Mask_Validate):
40OUString getFileName(OUString const & uri, osl::FileStatus const & status) {
41#if defined MACOSX
42 sal_Int32 i = uri.lastIndexOf('/') + 1;
43 OUString path;
44 if (osl::FileBase::getSystemPathFromFileURL(uri.copy(0, i), path)
45 != osl::FileBase::E_None)
46 {
48 "unoidl",
49 "cannot getSystemPathFromFileURL(" << uri.copy(0, i) << ")");
50 return status.getFileName();
51 }
52 OString dir(OUStringToOString(path, osl_getThreadTextEncoding()));
53 OString name(OUStringToOString(uri.subView(i), osl_getThreadTextEncoding()));
54 DIR * d = opendir(dir.getStr());
55 if (d == nullptr) {
56 SAL_WARN("unoidl", "cannot opendir(" << dir << ")");
57 return status.getFileName();
58 }
59 for (;;) {
60 dirent ent;
61 dirent * p;
62 int e = readdir_r(d, &ent, &p);
63 if (e != 0) {
64 SAL_WARN("unoidl", "cannot readdir_r");
65 closedir(d);
66 return status.getFileName();
67 }
68 if (p == nullptr) {
70 "unoidl", "cannot find " << name << " via readdir of " << dir);
71 closedir(d);
72 return status.getFileName();
73 }
74 if (name.equalsIgnoreAsciiCase(p->d_name)) {
75 closedir(d);
76 return OUString(
77 p->d_name, std::strlen(p->d_name), osl_getThreadTextEncoding());
78 }
79 }
80#else
81 (void) uri;
82 return status.getFileName();
83#endif
84}
85
86bool exists(OUString const & uri, bool directory) {
87 osl::DirectoryItem item;
88 osl::FileStatus status(
89 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName);
90 return osl::DirectoryItem::get(uri, item) == osl::FileBase::E_None
91 && item.getFileStatus(status) == osl::FileBase::E_None
92 && (status.getFileType() == osl::FileStatus::Directory) == directory
93 && getFileName(uri, status) == uri.subView(uri.lastIndexOf('/') + 1);
94}
95
96class Cursor: public MapCursor {
97public:
98 Cursor(Manager& manager, OUString const & uri): manager_(manager), directory_(uri) {
99 auto const rc = directory_.open();
101 rc != osl::FileBase::E_None, "unoidl", "open(" << uri << ") failed with " << +rc);
102 }
103
104private:
105 virtual ~Cursor() noexcept override {}
106
107 virtual rtl::Reference<Entity> getNext(OUString *) override;
108
109 Manager& manager_;
110 osl::Directory directory_;
111};
112
113class SourceModuleEntity: public ModuleEntity {
114public:
115 SourceModuleEntity(Manager& manager, OUString uri): manager_(manager), uri_(std::move(uri)) {}
116
117private:
118 virtual ~SourceModuleEntity() noexcept override {}
119
120 virtual std::vector<OUString> getMemberNames() const override
121 { return std::vector<OUString>(); } //TODO
122
123 virtual rtl::Reference< MapCursor > createCursor() const override
124 { return new Cursor(manager_, uri_); }
125
126 Manager& manager_;
127 OUString uri_;
128};
129
130bool isValidFileName(std::u16string_view name, bool directory) {
131 for (size_t i = 0;; ++i) {
132 if (i == name.size()) {
133 if (i == 0) {
134 return false;
135 }
136 return directory;
137 }
138 auto const c = name[i];
139 if (c == '.') {
140 if (i == 0 || name[i - 1] == '_') {
141 return false;
142 }
143 return !directory && name.substr(i + 1) == u"idl";
144 } else if (c == '_') {
145 //TODO: Ignore case of name[0] only for case-insensitive file systems:
146 if (i == 0 || name[i - 1] == '_') {
147 return false;
148 }
149 } else if (rtl::isAsciiDigit(c)) {
150 if (i == 0) {
151 return false;
152 }
153 } else if (!rtl::isAsciiAlpha(c)) {
154 return false;
155 }
156 }
157}
158
159}
160
161rtl::Reference<Entity> Cursor::getNext(OUString * name) {
162 assert(name != nullptr);
163 for (;;) {
164 osl::DirectoryItem i;
165 auto rc = directory_.getNextItem(i);
166 switch (rc) {
167 case osl::FileBase::E_None:
168 {
169 osl::FileStatus stat(
170 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
171 osl_FileStatus_Mask_FileURL);
172 rc = i.getFileStatus(stat);
173 if (rc != osl::FileBase::E_None) {
174 SAL_WARN(
175 "unoidl",
176 "getFileSatus in <" << directory_.getURL() << "> failed with " << +rc);
177 continue;
178 }
179 auto const dir = stat.getFileType() == osl::FileStatus::Directory;
180 if (!isValidFileName(stat.getFileName(), dir)) {
181 continue;
182 }
183 if (dir) {
184 //TODO: Using osl::FileStatus::getFileName can likely cause issues on case-
185 // insensitive/preserving file systems, see the free getFileName function above
186 // (which likely goes unnoticed if module identifiers follow the convention of
187 // being all-lowercase):
188 *name = stat.getFileName();
189 return new SourceModuleEntity(manager_, stat.getFileURL());
190 } else {
191 SourceProviderScannerData data(&manager_);
192 if (!parse(stat.getFileURL(), &data)) {
193 SAL_WARN("unoidl", "cannot parse <" << stat.getFileURL() << ">");
194 continue;
195 }
196 auto ent = data.entities.end();
197 for (auto j = data.entities.begin(); j != data.entities.end(); ++j) {
198 if (j->second.kind == SourceProviderEntity::KIND_EXTERNAL
199 || j->second.kind == SourceProviderEntity::KIND_MODULE)
200 {
201 continue;
202 }
203 if (ent != data.entities.end()) {
204 throw FileFormatException(
205 stat.getFileURL(), "source file defines more than one entity");
206 }
207 ent = j;
208 }
209 if (ent == data.entities.end()) {
210 throw FileFormatException(
211 stat.getFileURL(), "source file defines no entity");
212 }
213 //TODO: Check that the entity's name matches the suffix of stat.getFileURL():
214 *name = ent->first.copy(ent->first.lastIndexOf('.') + 1);
215 return ent->second.entity;
216 }
217 }
218 default:
219 SAL_WARN( "unoidl", "getNext from <" << directory_.getURL() << "> failed with " << +rc);
220 [[fallthrough]];
221 case osl::FileBase::E_NOENT:
222 return {};
223 }
224 }
225}
226
227SourceTreeProvider::SourceTreeProvider(Manager & manager, OUString const & uri):
228 manager_(manager), uri_(uri.endsWith("/") ? uri : uri + "/")
229{}
230
232 return new Cursor(manager_, uri_);
233}
234
236 const
237{
238 std::map< OUString, rtl::Reference<Entity> >::iterator ci(
239 cache_.find(name));
240 if (ci != cache_.end()) {
241 return ci->second;
242 }
243 // Match name against
244 // name ::= identifier ("." identifier)*
245 // identifier ::= upper-blocks | lower-block
246 // upper-blocks ::= upper ("_"? alnum)*
247 // lower-block :== lower alnum*
248 // alnum ::= digit | upper | lower
249 // digit ::= "0"--"9"
250 // upper ::= "A"--"Z"
251 // lower ::= "a"--"z"
252 OUStringBuffer buf(name);
253 sal_Int32 start = 0;
254 sal_Int32 i = 0;
255 for (; i != name.getLength(); ++i) {
256 sal_Unicode c = name[i];
257 if (c == '.') {
258 assert(i == start || i != 0);
259 if (i == start || name[i - 1] == '_') {
260 throw FileFormatException( //TODO
261 "", "Illegal UNOIDL identifier \"" + name + "\"");
262 }
263 buf[i] = '/';
264 start = i + 1;
265 } else if (c == '_') {
266 assert(i == start || i != 0);
267 if (i == start || name[i - 1] == '_'
268 || !rtl::isAsciiUpperCase(name[start]))
269 {
270 throw FileFormatException( //TODO
271 "", "Illegal UNOIDL identifier \"" + name + "\"");
272 }
273 } else if (rtl::isAsciiDigit(c)) {
274 if (i == start) {
275 throw FileFormatException( //TODO
276 "", "Illegal UNOIDL identifier \"" + name + "\"");
277 }
278 } else if (!rtl::isAsciiAlpha(c)) {
279 throw FileFormatException( //TODO
280 "", "Illegal UNOIDL identifier \"" + name + "\"");
281 }
282 }
283 if (i == start) {
284 throw FileFormatException( //TODO
285 "", "Illegal UNOIDL identifier \"" + name + "\"");
286 }
287 OUString uri(uri_ + buf);
289 // Prevent conflicts between foo/ and Foo.idl on case-preserving file
290 // systems:
291 if (exists(uri, true) && !exists(uri + ".idl", false)) {
292 ent = new SourceModuleEntity(manager_, uri);
293 } else {
294 uri += ".idl";
296 if (parse(uri, &data)) {
297 std::map<OUString, SourceProviderEntity>::const_iterator j(
298 data.entities.find(name));
299 if (j != data.entities.end()) {
300 ent = j->second.entity;
301 }
303 !ent.is(), "unoidl",
304 "<" << uri << "> does not define entity " << name);
305 }
306 }
307 cache_.emplace(name, ent);
308 return ent;
309}
310
312
313}
314
315/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
double d
virtual rtl::Reference< Entity > findEntity(OUString const &name) const override
virtual rtl::Reference< MapCursor > createRootCursor() const override
virtual ~SourceTreeProvider() noexcept override
std::map< OUString, rtl::Reference< Entity > > cache_
SourceTreeProvider(Manager &manager, OUString const &uri)
rtl::Reference< ParseManager > manager
float u
const char * name
void * p
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
int i
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
bool parse(OUString const &uri, SourceProviderScannerData *data)
osl::Directory directory_
Manager & manager_
OUString uri_
std::map< OUString, SourceProviderEntity > entities
sal_uInt16 sal_Unicode