summaryrefslogtreecommitdiffstats
path: root/src/ClashAvoider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ClashAvoider.cpp')
-rw-r--r--src/ClashAvoider.cpp185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/ClashAvoider.cpp b/src/ClashAvoider.cpp
new file mode 100644
index 00000000..5ae1f004
--- /dev/null
+++ b/src/ClashAvoider.cpp
@@ -0,0 +1,185 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2018 David Robillard <http://drobilla.net/>
+
+ Ingen is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or any later version.
+
+ Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Ingen. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/ClashAvoider.hpp"
+#include "ingen/Store.hpp"
+#include "ingen/URI.hpp"
+#include "ingen/paths.hpp"
+#include "raul/Path.hpp"
+#include "raul/Symbol.hpp"
+
+#include <boost/optional/optional.hpp>
+
+#include <cassert>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+#include <utility>
+
+namespace ingen {
+
+ClashAvoider::ClashAvoider(const Store& store)
+ : _store(store)
+{}
+
+const URI
+ClashAvoider::map_uri(const URI& in)
+{
+ if (uri_is_path(in)) {
+ return path_to_uri(map_path(uri_to_path(in)));
+ } else {
+ return in;
+ }
+}
+
+const Raul::Path
+ClashAvoider::map_path(const Raul::Path& in)
+{
+ unsigned offset = 0;
+ bool has_offset = false;
+ const size_t pos = in.find_last_of('_');
+ if (pos != std::string::npos && pos != (in.length()-1)) {
+ const std::string trailing = in.substr(pos + 1);
+ has_offset = (sscanf(trailing.c_str(), "%u", &offset) > 0);
+ }
+
+ // Path without _n suffix
+ std::string base_path_str = in;
+ if (has_offset) {
+ base_path_str = base_path_str.substr(0, base_path_str.find_last_of('_'));
+ }
+
+ Raul::Path base_path(base_path_str);
+
+ auto m = _symbol_map.find(in);
+ if (m != _symbol_map.end()) {
+ return m->second;
+ } else {
+ typedef std::pair<SymbolMap::iterator, bool> InsertRecord;
+
+ // See if parent is mapped
+ Raul::Path parent = in.parent();
+ do {
+ auto p = _symbol_map.find(parent);
+ if (p != _symbol_map.end()) {
+ const Raul::Path mapped = Raul::Path(
+ p->second.base() + in.substr(parent.base().length()));
+ InsertRecord i = _symbol_map.emplace(in, mapped);
+ return i.first->second;
+ }
+ parent = parent.parent();
+ } while (!parent.is_root());
+
+ if (!exists(in) && _symbol_map.find(in) == _symbol_map.end()) {
+ // No clash, use symbol unmodified
+ InsertRecord i = _symbol_map.emplace(in, in);
+ assert(i.second);
+ return i.first->second;
+
+ } else {
+ // Append _2 _3 etc until an unused symbol is found
+ while (true) {
+ auto o = _offsets.find(base_path);
+ if (o != _offsets.end()) {
+ offset = ++o->second;
+ } else {
+ std::string parent_str = in.parent().base();
+ parent_str = parent_str.substr(0, parent_str.find_last_of("/"));
+ if (parent_str.empty()) {
+ parent_str = "/";
+ }
+ }
+
+ if (offset == 0) {
+ offset = 2;
+ }
+
+ std::stringstream ss;
+ ss << base_path << "_" << offset;
+ if (!exists(Raul::Path(ss.str()))) {
+ std::string name = base_path.symbol();
+ if (name == "") {
+ name = "_";
+ }
+ Raul::Symbol sym(name);
+ std::string str = ss.str();
+ InsertRecord i = _symbol_map.emplace(in, Raul::Path(str));
+ offset = _store.child_name_offset(in.parent(), sym, false);
+ _offsets.emplace(base_path, offset);
+ return i.first->second;
+ } else {
+ if (o != _offsets.end()) {
+ offset = ++o->second;
+ } else {
+ ++offset;
+ }
+ }
+ }
+ }
+ }
+}
+
+bool
+ClashAvoider::exists(const Raul::Path& path) const
+{
+ return _store.find(path) != _store.end();
+}
+
+static boost::optional<size_t>
+numeric_suffix_start(const std::string& str)
+{
+ if (!isdigit(str[str.length() - 1])) {
+ return {};
+ }
+
+ size_t i = str.length() - 1;
+ while (i > 0 && isdigit(str[i - 1])) {
+ --i;
+ }
+
+ return i;
+}
+
+std::string
+ClashAvoider::adjust_name(const Raul::Path& old_path,
+ const Raul::Path& new_path,
+ std::string name)
+{
+ const auto name_suffix_start = numeric_suffix_start(name);
+ if (!name_suffix_start) {
+ return name; // No numeric suffix, just re-use old label
+ }
+
+ const auto name_suffix = atoi(name.c_str() + *name_suffix_start);
+ const auto old_suffix_start = numeric_suffix_start(old_path);
+ const auto new_suffix_start = numeric_suffix_start(new_path);
+ if (old_suffix_start && new_suffix_start) {
+ // Add the offset applied to the symbol to the label suffix
+ const auto old_suffix = atoi(old_path.c_str() + *old_suffix_start);
+ const auto new_suffix = atoi(new_path.c_str() + *new_suffix_start);
+ const auto offset = new_suffix - old_suffix;
+ return (name.substr(0, *name_suffix_start) +
+ std::to_string(name_suffix + offset));
+ } else {
+ // Add 1 to previous label suffix
+ return (name.substr(0, *name_suffix_start) +
+ std::to_string(name_suffix + 1));
+ }
+}
+
+} // namespace ingen