diff options
Diffstat (limited to 'unoidl/source/sourcetreeprovider.cxx')
-rw-r--r-- | unoidl/source/sourcetreeprovider.cxx | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/unoidl/source/sourcetreeprovider.cxx b/unoidl/source/sourcetreeprovider.cxx new file mode 100644 index 0000000000..629e50fbf8 --- /dev/null +++ b/unoidl/source/sourcetreeprovider.cxx @@ -0,0 +1,315 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <map> +#include <utility> +#include <vector> + +#include <osl/file.h> +#include <osl/file.hxx> +#include <rtl/character.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.hxx> +#include <unoidl/unoidl.hxx> + +#include "sourceprovider-scanner.hxx" +#include "sourcetreeprovider.hxx" + +#if defined MACOSX +#include <dirent.h> +#include <osl/thread.h> +#endif + +namespace unoidl::detail { + +namespace { + +//TODO: Bad hack to work around osl::FileStatus::getFileName not determining the +// original spelling of a file name (not even with +// osl_FileStatus_Mask_Validate): +OUString getFileName(OUString const & uri, osl::FileStatus const & status) { +#if defined MACOSX + sal_Int32 i = uri.lastIndexOf('/') + 1; + OUString path; + if (osl::FileBase::getSystemPathFromFileURL(uri.copy(0, i), path) + != osl::FileBase::E_None) + { + SAL_WARN( + "unoidl", + "cannot getSystemPathFromFileURL(" << uri.copy(0, i) << ")"); + return status.getFileName(); + } + OString dir(OUStringToOString(path, osl_getThreadTextEncoding())); + OString name(OUStringToOString(uri.subView(i), osl_getThreadTextEncoding())); + DIR * d = opendir(dir.getStr()); + if (d == nullptr) { + SAL_WARN("unoidl", "cannot opendir(" << dir << ")"); + return status.getFileName(); + } + for (;;) { + dirent ent; + dirent * p; + int e = readdir_r(d, &ent, &p); + if (e != 0) { + SAL_WARN("unoidl", "cannot readdir_r"); + closedir(d); + return status.getFileName(); + } + if (p == nullptr) { + SAL_WARN( + "unoidl", "cannot find " << name << " via readdir of " << dir); + closedir(d); + return status.getFileName(); + } + if (name.equalsIgnoreAsciiCase(p->d_name)) { + closedir(d); + return OUString( + p->d_name, std::strlen(p->d_name), osl_getThreadTextEncoding()); + } + } +#else + (void) uri; + return status.getFileName(); +#endif +} + +bool exists(OUString const & uri, bool directory) { + osl::DirectoryItem item; + osl::FileStatus status( + osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName); + return osl::DirectoryItem::get(uri, item) == osl::FileBase::E_None + && item.getFileStatus(status) == osl::FileBase::E_None + && (status.getFileType() == osl::FileStatus::Directory) == directory + && getFileName(uri, status) == uri.subView(uri.lastIndexOf('/') + 1); +} + +class Cursor: public MapCursor { +public: + Cursor(Manager& manager, OUString const & uri): manager_(manager), directory_(uri) { + auto const rc = directory_.open(); + SAL_WARN_IF( + rc != osl::FileBase::E_None, "unoidl", "open(" << uri << ") failed with " << +rc); + } + +private: + virtual ~Cursor() noexcept override {} + + virtual rtl::Reference<Entity> getNext(OUString *) override; + + Manager& manager_; + osl::Directory directory_; +}; + +class SourceModuleEntity: public ModuleEntity { +public: + SourceModuleEntity(Manager& manager, OUString uri): manager_(manager), uri_(std::move(uri)) {} + +private: + virtual ~SourceModuleEntity() noexcept override {} + + virtual std::vector<OUString> getMemberNames() const override + { return std::vector<OUString>(); } //TODO + + virtual rtl::Reference< MapCursor > createCursor() const override + { return new Cursor(manager_, uri_); } + + Manager& manager_; + OUString uri_; +}; + +bool isValidFileName(std::u16string_view name, bool directory) { + for (size_t i = 0;; ++i) { + if (i == name.size()) { + if (i == 0) { + return false; + } + return directory; + } + auto const c = name[i]; + if (c == '.') { + if (i == 0 || name[i - 1] == '_') { + return false; + } + return !directory && name.substr(i + 1) == u"idl"; + } else if (c == '_') { + //TODO: Ignore case of name[0] only for case-insensitive file systems: + if (i == 0 || name[i - 1] == '_') { + return false; + } + } else if (rtl::isAsciiDigit(c)) { + if (i == 0) { + return false; + } + } else if (!rtl::isAsciiAlpha(c)) { + return false; + } + } +} + +} + +rtl::Reference<Entity> Cursor::getNext(OUString * name) { + assert(name != nullptr); + for (;;) { + osl::DirectoryItem i; + auto rc = directory_.getNextItem(i); + switch (rc) { + case osl::FileBase::E_None: + { + osl::FileStatus stat( + osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName | + osl_FileStatus_Mask_FileURL); + rc = i.getFileStatus(stat); + if (rc != osl::FileBase::E_None) { + SAL_WARN( + "unoidl", + "getFileSatus in <" << directory_.getURL() << "> failed with " << +rc); + continue; + } + auto const dir = stat.getFileType() == osl::FileStatus::Directory; + if (!isValidFileName(stat.getFileName(), dir)) { + continue; + } + if (dir) { + //TODO: Using osl::FileStatus::getFileName can likely cause issues on case- + // insensitive/preserving file systems, see the free getFileName function above + // (which likely goes unnoticed if module identifiers follow the convention of + // being all-lowercase): + *name = stat.getFileName(); + return new SourceModuleEntity(manager_, stat.getFileURL()); + } else { + SourceProviderScannerData data(&manager_); + if (!parse(stat.getFileURL(), &data)) { + SAL_WARN("unoidl", "cannot parse <" << stat.getFileURL() << ">"); + continue; + } + auto ent = data.entities.end(); + for (auto j = data.entities.begin(); j != data.entities.end(); ++j) { + if (j->second.kind == SourceProviderEntity::KIND_EXTERNAL + || j->second.kind == SourceProviderEntity::KIND_MODULE) + { + continue; + } + if (ent != data.entities.end()) { + throw FileFormatException( + stat.getFileURL(), "source file defines more than one entity"); + } + ent = j; + } + if (ent == data.entities.end()) { + throw FileFormatException( + stat.getFileURL(), "source file defines no entity"); + } + //TODO: Check that the entity's name matches the suffix of stat.getFileURL(): + *name = ent->first.copy(ent->first.lastIndexOf('.') + 1); + return ent->second.entity; + } + } + default: + SAL_WARN( "unoidl", "getNext from <" << directory_.getURL() << "> failed with " << +rc); + [[fallthrough]]; + case osl::FileBase::E_NOENT: + return {}; + } + } +} + +SourceTreeProvider::SourceTreeProvider(Manager & manager, OUString const & uri): + manager_(manager), uri_(uri.endsWith("/") ? uri : uri + "/") +{} + +rtl::Reference<MapCursor> SourceTreeProvider::createRootCursor() const { + return new Cursor(manager_, uri_); +} + +rtl::Reference<Entity> SourceTreeProvider::findEntity(OUString const & name) + const +{ + std::map< OUString, rtl::Reference<Entity> >::iterator ci( + cache_.find(name)); + if (ci != cache_.end()) { + return ci->second; + } + // Match name against + // name ::= identifier ("." identifier)* + // identifier ::= upper-blocks | lower-block + // upper-blocks ::= upper ("_"? alnum)* + // lower-block :== lower alnum* + // alnum ::= digit | upper | lower + // digit ::= "0"--"9" + // upper ::= "A"--"Z" + // lower ::= "a"--"z" + OUStringBuffer buf(name); + sal_Int32 start = 0; + sal_Int32 i = 0; + for (; i != name.getLength(); ++i) { + sal_Unicode c = name[i]; + if (c == '.') { + assert(i == start || i != 0); + if (i == start || name[i - 1] == '_') { + throw FileFormatException( //TODO + "", "Illegal UNOIDL identifier \"" + name + "\""); + } + buf[i] = '/'; + start = i + 1; + } else if (c == '_') { + assert(i == start || i != 0); + if (i == start || name[i - 1] == '_' + || !rtl::isAsciiUpperCase(name[start])) + { + throw FileFormatException( //TODO + "", "Illegal UNOIDL identifier \"" + name + "\""); + } + } else if (rtl::isAsciiDigit(c)) { + if (i == start) { + throw FileFormatException( //TODO + "", "Illegal UNOIDL identifier \"" + name + "\""); + } + } else if (!rtl::isAsciiAlpha(c)) { + throw FileFormatException( //TODO + "", "Illegal UNOIDL identifier \"" + name + "\""); + } + } + if (i == start) { + throw FileFormatException( //TODO + "", "Illegal UNOIDL identifier \"" + name + "\""); + } + OUString uri(uri_ + buf); + rtl::Reference<Entity> ent; + // Prevent conflicts between foo/ and Foo.idl on case-preserving file + // systems: + if (exists(uri, true) && !exists(uri + ".idl", false)) { + ent = new SourceModuleEntity(manager_, uri); + } else { + uri += ".idl"; + SourceProviderScannerData data(&manager_); + if (parse(uri, &data)) { + std::map<OUString, SourceProviderEntity>::const_iterator j( + data.entities.find(name)); + if (j != data.entities.end()) { + ent = j->second.entity; + } + SAL_WARN_IF( + !ent.is(), "unoidl", + "<" << uri << "> does not define entity " << name); + } + } + cache_.emplace(name, ent); + return ent; +} + +SourceTreeProvider::~SourceTreeProvider() noexcept {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |