summaryrefslogtreecommitdiffstats
path: root/unoidl/source/sourcetreeprovider.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'unoidl/source/sourcetreeprovider.cxx')
-rw-r--r--unoidl/source/sourcetreeprovider.cxx315
1 files changed, 315 insertions, 0 deletions
diff --git a/unoidl/source/sourcetreeprovider.cxx b/unoidl/source/sourcetreeprovider.cxx
new file mode 100644
index 000000000..399329f30
--- /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.makeStringAndClear());
+ 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: */