1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
10 #include <sal/config.h>
11 #include <sal/log.hxx>
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>
25 #include "sourceprovider-scanner.hxx"
26 #include "sourcetreeprovider.hxx"
30 #include <osl/thread.h>
33 namespace unoidl::detail
{
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):
40 OUString
getFileName(OUString
const & uri
, osl::FileStatus
const & status
) {
42 sal_Int32 i
= uri
.lastIndexOf('/') + 1;
44 if (osl::FileBase::getSystemPathFromFileURL(uri
.copy(0, i
), path
)
45 != osl::FileBase::E_None
)
49 "cannot getSystemPathFromFileURL(" << uri
.copy(0, i
) << ")");
50 return status
.getFileName();
52 OString
dir(OUStringToOString(path
, osl_getThreadTextEncoding()));
53 OString
name(OUStringToOString(uri
.subView(i
), osl_getThreadTextEncoding()));
54 DIR * d
= opendir(dir
.getStr());
56 SAL_WARN("unoidl", "cannot opendir(" << dir
<< ")");
57 return status
.getFileName();
62 int e
= readdir_r(d
, &ent
, &p
);
64 SAL_WARN("unoidl", "cannot readdir_r");
66 return status
.getFileName();
70 "unoidl", "cannot find " << name
<< " via readdir of " << dir
);
72 return status
.getFileName();
74 if (name
.equalsIgnoreAsciiCase(p
->d_name
)) {
77 p
->d_name
, std::strlen(p
->d_name
), osl_getThreadTextEncoding());
82 return status
.getFileName();
86 bool 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);
96 class Cursor
: public MapCursor
{
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
);
105 virtual ~Cursor() noexcept override
{}
107 virtual rtl::Reference
<Entity
> getNext(OUString
*) override
;
110 osl::Directory directory_
;
113 class SourceModuleEntity
: public ModuleEntity
{
115 SourceModuleEntity(Manager
& manager
, OUString uri
): manager_(manager
), uri_(std::move(uri
)) {}
118 virtual ~SourceModuleEntity() noexcept override
{}
120 virtual std::vector
<OUString
> getMemberNames() const override
121 { return std::vector
<OUString
>(); } //TODO
123 virtual rtl::Reference
< MapCursor
> createCursor() const override
124 { return new Cursor(manager_
, uri_
); }
130 bool isValidFileName(std::u16string_view name
, bool directory
) {
131 for (size_t i
= 0;; ++i
) {
132 if (i
== name
.size()) {
138 auto const c
= name
[i
];
140 if (i
== 0 || name
[i
- 1] == '_') {
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] == '_') {
149 } else if (rtl::isAsciiDigit(c
)) {
153 } else if (!rtl::isAsciiAlpha(c
)) {
161 rtl::Reference
<Entity
> Cursor::getNext(OUString
* name
) {
162 assert(name
!= nullptr);
164 osl::DirectoryItem i
;
165 auto rc
= directory_
.getNextItem(i
);
167 case osl::FileBase::E_None
:
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
) {
176 "getFileSatus in <" << directory_
.getURL() << "> failed with " << +rc
);
179 auto const dir
= stat
.getFileType() == osl::FileStatus::Directory
;
180 if (!isValidFileName(stat
.getFileName(), 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());
191 SourceProviderScannerData
data(&manager_
);
192 if (!parse(stat
.getFileURL(), &data
)) {
193 SAL_WARN("unoidl", "cannot parse <" << stat
.getFileURL() << ">");
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
)
203 if (ent
!= data
.entities
.end()) {
204 throw FileFormatException(
205 stat
.getFileURL(), "source file defines more than one entity");
209 if (ent
== data
.entities
.end()) {
210 throw FileFormatException(
211 stat
.getFileURL(), "source file defines no entity");
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
;
219 SAL_WARN( "unoidl", "getNext from <" << directory_
.getURL() << "> failed with " << +rc
);
221 case osl::FileBase::E_NOENT
:
227 SourceTreeProvider::SourceTreeProvider(Manager
& manager
, OUString
const & uri
):
228 manager_(manager
), uri_(uri
.endsWith("/") ? uri
: uri
+ "/")
231 rtl::Reference
<MapCursor
> SourceTreeProvider::createRootCursor() const {
232 return new Cursor(manager_
, uri_
);
235 rtl::Reference
<Entity
> SourceTreeProvider::findEntity(OUString
const & name
)
238 std::map
< OUString
, rtl::Reference
<Entity
> >::iterator
ci(
240 if (ci
!= cache_
.end()) {
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
);
255 for (; i
!= name
.getLength(); ++i
) {
256 sal_Unicode c
= name
[i
];
258 assert(i
== start
|| i
!= 0);
259 if (i
== start
|| name
[i
- 1] == '_') {
260 throw FileFormatException( //TODO
261 "", "Illegal UNOIDL identifier \"" + name
+ "\"");
265 } else if (c
== '_') {
266 assert(i
== start
|| i
!= 0);
267 if (i
== start
|| name
[i
- 1] == '_'
268 || !rtl::isAsciiUpperCase(name
[start
]))
270 throw FileFormatException( //TODO
271 "", "Illegal UNOIDL identifier \"" + name
+ "\"");
273 } else if (rtl::isAsciiDigit(c
)) {
275 throw FileFormatException( //TODO
276 "", "Illegal UNOIDL identifier \"" + name
+ "\"");
278 } else if (!rtl::isAsciiAlpha(c
)) {
279 throw FileFormatException( //TODO
280 "", "Illegal UNOIDL identifier \"" + name
+ "\"");
284 throw FileFormatException( //TODO
285 "", "Illegal UNOIDL identifier \"" + name
+ "\"");
287 OUString
uri(uri_
+ buf
);
288 rtl::Reference
<Entity
> ent
;
289 // Prevent conflicts between foo/ and Foo.idl on case-preserving file
291 if (exists(uri
, true) && !exists(uri
+ ".idl", false)) {
292 ent
= new SourceModuleEntity(manager_
, uri
);
295 SourceProviderScannerData
data(&manager_
);
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
;
304 "<" << uri
<< "> does not define entity " << name
);
307 cache_
.emplace(name
, ent
);
311 SourceTreeProvider::~SourceTreeProvider() noexcept
{}
315 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */