diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 1b0c205b2..499046cc7 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -79,6 +79,7 @@ SOURCES = \ dbPolygonNeighborhood.cc \ dbPolygonTools.cc \ dbPolygonGenerators.cc \ + dbPropertiesFilter.cc \ dbPropertiesRepository.cc \ dbReader.cc \ dbRecursiveInstanceIterator.cc \ @@ -315,6 +316,7 @@ HEADERS = \ dbPolygonNeighborhood.h \ dbPolygonTools.h \ dbPolygonGenerators.h \ + dbPropertiesFilter.h \ dbPropertiesRepository.h \ dbPropertyConstraint.h \ dbReader.h \ diff --git a/src/db/db/dbCompoundOperation.h b/src/db/db/dbCompoundOperation.h index ca42cf77e..4f109553a 100644 --- a/src/db/db/dbCompoundOperation.h +++ b/src/db/db/dbCompoundOperation.h @@ -1064,7 +1064,7 @@ private: child (0)->compute_local (cache, layout, cell, interactions, one, proc); if (m_sum_of) { - if (mp_filter->selected (one.front ())) { + if (mp_filter->selected_set (one.front ())) { results.front ().insert (one.front ().begin (), one.front ().end ()); } } else { diff --git a/src/db/db/dbEdgesDelegate.h b/src/db/db/dbEdgesDelegate.h index f3e8cab77..7424bfedf 100644 --- a/src/db/db/dbEdgesDelegate.h +++ b/src/db/db/dbEdgesDelegate.h @@ -62,7 +62,7 @@ public: * @brief Filters the edge set * If this method returns true, the edges are kept. Otherwise they are discarded. */ - virtual bool selected (const std::unordered_set &edge) const = 0; + virtual bool selected_set (const std::unordered_set &edge) const = 0; /** * @brief Returns the transformation reducer for building cell variants diff --git a/src/db/db/dbEdgesUtils.h b/src/db/db/dbEdgesUtils.h index 8daa404e8..d0082806d 100644 --- a/src/db/db/dbEdgesUtils.h +++ b/src/db/db/dbEdgesUtils.h @@ -79,7 +79,7 @@ struct DB_PUBLIC EdgeLengthFilter /** * @brief Returns true if the total edge length matches the criterion */ - bool selected (const std::unordered_set &edges) const + bool selected_set (const std::unordered_set &edges) const { length_type l = 0; for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { @@ -209,7 +209,7 @@ struct DB_PUBLIC EdgeOrientationFilter /** * @brief Returns true if all edge orientations match the criterion */ - virtual bool selected (const std::unordered_set &edges) const + virtual bool selected_set (const std::unordered_set &edges) const { for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { if (! selected (*e, e->properties_id ())) { @@ -279,7 +279,7 @@ struct DB_PUBLIC SpecialEdgeOrientationFilter /** * @brief Returns true if all edge orientations match the criterion */ - virtual bool selected (const std::unordered_set &edges) const + virtual bool selected_set (const std::unordered_set &edges) const { for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { if (! selected (*e, e->properties_id ())) { @@ -319,6 +319,29 @@ private: db::OrthogonalTransformationReducer m_vars; }; +/** + * @brief A filter implementation which implements the set filters through "all must match" + */ + +struct DB_PUBLIC AllEdgesMustMatchFilter + : public EdgeFilterBase +{ + /** + * @brief Constructor + */ + AllEdgesMustMatchFilter () { } + + virtual bool selected_set (const std::unordered_set &edges) const + { + for (std::unordered_set::const_iterator p = edges.begin (); p != edges.end (); ++p) { + if (! selected (*p, p->properties_id ())) { + return false; + } + } + return true; + } +}; + /** * @brief A predicate defining edge a interacts with b */ diff --git a/src/db/db/dbPropertiesFilter.cc b/src/db/db/dbPropertiesFilter.cc new file mode 100644 index 000000000..b2e065c5b --- /dev/null +++ b/src/db/db/dbPropertiesFilter.cc @@ -0,0 +1,82 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "dbPropertiesFilter.h" + +namespace db +{ + +PropertiesFilter::PropertiesFilter (const tl::Variant &name, const tl::Variant &value, bool inverse) + : m_name_id (db::property_names_id (name)), m_value_from (value), m_exact (true), m_glob (false), m_inverse (inverse) +{ + // .. nothing yet .. +} + +PropertiesFilter::PropertiesFilter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) + : m_name_id (db::property_names_id (name)), m_value_from (from), m_value_to (to), m_exact (false), m_glob (false), m_inverse (inverse) +{ + // .. nothing yet .. +} + +PropertiesFilter::PropertiesFilter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse) + : m_name_id (db::property_names_id (name)), m_pattern (pattern), m_exact (true), m_glob (true), m_inverse (inverse) +{ + // .. nothing yet .. +} + +bool +PropertiesFilter::prop_selected (db::properties_id_type prop_id) const +{ + tl::MutexLocker locker (&m_lock); + + auto c = m_cache.find (prop_id); + if (c != m_cache.end ()) { + return c->second; + } + + bool res = prop_selected_impl (prop_id); + m_cache.insert (std::make_pair (prop_id, res)); + return res; +} + +bool +PropertiesFilter::prop_selected_impl (db::properties_id_type prop_id) const +{ + const db::PropertiesSet &ps = db::properties (prop_id); + if (ps.has_value (m_name_id)) { + + const tl::Variant &value = ps.value (m_name_id); + + if (m_glob) { + return m_pattern.match (value.to_string ()) != m_inverse; + } else if (m_exact) { + return (value == m_value_from) != m_inverse; + } else { + return ((m_value_from.is_nil () || ! (value < m_value_from)) && (m_value_to.is_nil () || value < m_value_to)) != m_inverse; + } + + } else { + return m_inverse; + } +} + +} diff --git a/src/db/db/dbPropertiesFilter.h b/src/db/db/dbPropertiesFilter.h new file mode 100644 index 000000000..4b3d71ea0 --- /dev/null +++ b/src/db/db/dbPropertiesFilter.h @@ -0,0 +1,132 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_dbPropertiesFilter +#define HDR_dbPropertiesFilter + +#include "dbCommon.h" +#include "dbPropertiesRepository.h" +#include "dbPolygon.h" +#include "tlVariant.h" +#include "tlGlobPattern.h" +#include "tlThreads.h" + +#include + +namespace db +{ + +/** + * @brief A properties filter + * + * This is a base class for PolygonFilters, EdgeFilters etc. for selecting + * Polygons from Regions by property. + */ +class DB_PUBLIC PropertiesFilter +{ +public: + PropertiesFilter (const tl::Variant &name, const tl::Variant &value, bool inverse); + PropertiesFilter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse); + PropertiesFilter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse); + + bool prop_selected (db::properties_id_type prop_id) const; + +private: + bool prop_selected_impl (db::properties_id_type prop_id) const; + + mutable std::map m_cache; + db::property_names_id_type m_name_id; + tl::Variant m_value_from, m_value_to; + tl::GlobPattern m_pattern; + bool m_exact; + bool m_glob; + bool m_inverse; + mutable tl::Mutex m_lock; +}; + +template +class polygon_properties_filter + : public PolygonFilter, public PropertiesFilter +{ +public: + polygon_properties_filter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse) + : PropertiesFilter (name, pattern, inverse) + { + // .. nothing yet .. + } + + polygon_properties_filter (const tl::Variant &name, const tl::Variant &value, bool inverse) + : PropertiesFilter (name, value, inverse) + { + // .. nothing yet .. + } + + polygon_properties_filter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) + : PropertiesFilter (name, from, to, inverse) + { + // .. nothing yet .. + } + + bool selected (const db::Polygon &, db::properties_id_type prop_id) const + { + return PropertiesFilter::prop_selected (prop_id); + } + + bool selected (const db::PolygonRef &, db::properties_id_type prop_id) const + { + return PropertiesFilter::prop_selected (prop_id); + } +}; + +template +class generic_properties_filter + : public BasicFilter, public PropertiesFilter +{ +public: + generic_properties_filter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse) + : PropertiesFilter (name, pattern, inverse) + { + // .. nothing yet .. + } + + generic_properties_filter (const tl::Variant &name, const tl::Variant &value, bool inverse) + : PropertiesFilter (name, value, inverse) + { + // .. nothing yet .. + } + + generic_properties_filter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) + : PropertiesFilter (name, from, to, inverse) + { + // .. nothing yet .. + } + + bool selected (const ShapeType &, db::properties_id_type prop_id) const + { + return PropertiesFilter::prop_selected (prop_id); + } +}; + +} + +#endif diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 8339805b4..ec03fd2c1 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -31,6 +31,7 @@ #include "dbRegion.h" #include "dbOriginalLayerRegion.h" #include "dbLayoutUtils.h" +#include "dbPropertiesFilter.h" #include "gsiDeclDbContainerHelpers.h" @@ -40,8 +41,10 @@ namespace gsi // --------------------------------------------------------------------------------- // EdgeFilter binding +typedef shape_filter_impl EdgeFilterBase; + class EdgeFilterImpl - : public shape_filter_impl + : public gsi::EdgeFilterBase { public: EdgeFilterImpl () { } @@ -60,25 +63,6 @@ public: } } - // Returns true if all edges match the criterion - virtual bool selected (const std::unordered_set &edges) const - { - if (f_selected.can_issue ()) { - for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { - if (! f_selected.issue (&EdgeFilterImpl::issue_selected, *e)) { - return false; - } - } - } else { - for (std::unordered_set::const_iterator e = edges.begin (); e != edges.end (); ++e) { - if (! issue_selected (*e)) { - return false; - } - } - } - return true; - } - gsi::Callback f_selected; private: @@ -87,8 +71,70 @@ private: EdgeFilterImpl (const EdgeFilterImpl &); }; -Class decl_EdgeFilterImpl ("db", "EdgeFilter", - EdgeFilterImpl::method_decls (true) + +typedef db::generic_properties_filter EdgePropertiesFilter; + +static gsi::EdgeFilterBase *make_ppf1 (const tl::Variant &name, const tl::Variant &value, bool inverse) +{ + return new EdgePropertiesFilter (name, value, inverse); +} + +static gsi::EdgeFilterBase *make_ppf2 (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) +{ + return new EdgePropertiesFilter (name, from, to, inverse); +} + +static gsi::EdgeFilterBase *make_pg (const tl::Variant &name, const std::string &glob, bool inverse, bool case_sensitive) +{ + tl::GlobPattern pattern (glob); + pattern.set_case_sensitive (case_sensitive); + return new EdgePropertiesFilter (name, pattern, inverse); +} + +Class decl_EdgeFilterBase ("db", "EdgeFilterBase", + gsi::EdgeFilterBase::method_decls (true) + + gsi::constructor ("property_glob", &make_pg, gsi::arg ("name"), gsi::arg ("pattern"), gsi::arg ("inverse", false), gsi::arg ("case_sensitive", true), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The glob pattern to match the property value against.\n" + "@param inverse If true, inverts the selection - i.e. all edges without a matching property are selected.\n" + "@param case_sensitive If true, the match is case sensitive (the default), if false, the match is not case sensitive.\n" + "\n" + "Apply this filter with \\Edges#filtered:\n" + "\n" + "@code\n" + "# edges is a Edges object\n" + "# filtered_edges contains all edges where the 'net' property starts with 'C':\n" + "filtered_edges = edges.filtered(RBA::EdgeFilterBase::property_glob('net', 'C*'))\n" + "@/code\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter", &make_ppf1, gsi::arg ("name"), gsi::arg ("value"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param value The value against which the property is checked (exact match).\n" + "@param inverse If true, inverts the selection - i.e. all edges without a property with the given name and value are selected.\n" + "\n" + "Apply this filter with \\Edges#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ) + + gsi::constructor ("property_filter_bounded", &make_ppf2, gsi::arg ("name"), gsi::arg ("from"), gsi::arg ("to"), gsi::arg ("inverse", false), + "@brief Creates a single-valued property filter\n" + "@param name The name of the property to use.\n" + "@param from The lower value against which the property is checked or 'nil' if no lower bound shall be used.\n" + "@param to The upper value against which the property is checked or 'nil' if no upper bound shall be used.\n" + "@param inverse If true, inverts the selection - i.e. all edges without a property with the given name and value range are selected.\n" + "\n" + "This version does a bounded match. The value of the propery needs to be larger or equal to 'from' and less than 'to'.\n" + "Apply this filter with \\Edges#filtered. See \\property_glob for an example.\n" + "\n" + "This feature has been introduced in version 0.30." + ), + "@hide" +); + +Class decl_EdgeFilterImpl (decl_EdgeFilterBase, "db", "EdgeFilter", callback ("selected", &EdgeFilterImpl::issue_selected, &EdgeFilterImpl::f_selected, gsi::arg ("edge"), "@brief Selects an edge\n" "This method is the actual payload. It needs to be reimplemented in a derived class.\n" @@ -455,12 +501,12 @@ static db::Edges moved_xy (const db::Edges *r, db::Coord x, db::Coord y) return r->transformed (db::Disp (db::Vector (x, y))); } -static db::Edges filtered (const db::Edges *r, const EdgeFilterImpl *f) +static db::Edges filtered (const db::Edges *r, const gsi::EdgeFilterBase *f) { return r->filtered (*f); } -static void filter (db::Edges *r, const EdgeFilterImpl *f) +static void filter (db::Edges *r, const gsi::EdgeFilterBase *f) { r->filter (*f); } diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index b01ffd261..86eb77c6d 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -37,6 +37,7 @@ #include "dbCompoundOperation.h" #include "dbLayoutToNetlist.h" #include "dbPropertiesRepository.h" +#include "dbPropertiesFilter.h" #include "tlGlobPattern.h" #include "gsiDeclDbContainerHelpers.h" @@ -51,12 +52,7 @@ namespace gsi // --------------------------------------------------------------------------------- // PolygonFilter binding -class PolygonFilterBase - : public shape_filter_impl -{ -public: - PolygonFilterBase () { } -}; +typedef shape_filter_impl PolygonFilterBase; class PolygonFilterImpl : public PolygonFilterBase @@ -93,128 +89,41 @@ private: PolygonFilterImpl (const PolygonFilterImpl &); }; -/** - * @brief A properties filter - */ -class PropertiesFilter -{ -public: - PropertiesFilter (const tl::Variant &name, const tl::Variant &value, bool inverse) - : m_name_id (db::property_names_id (name)), m_value_from (value), m_exact (true), m_glob (false), m_inverse (inverse) - { - // .. nothing yet .. - } +typedef db::polygon_properties_filter PolygonPropertiesFilter; - PropertiesFilter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) - : m_name_id (db::property_names_id (name)), m_value_from (from), m_value_to (to), m_exact (false), m_glob (false), m_inverse (inverse) - { - // .. nothing yet .. - } - - PropertiesFilter (const tl::Variant &name, const std::string &pattern, bool inverse) - : m_name_id (db::property_names_id (name)), m_pattern (pattern), m_exact (true), m_glob (true), m_inverse (inverse) - { - // .. nothing yet .. - } - - bool prop_selected (db::properties_id_type prop_id) const - { - auto c = m_cache.find (prop_id); - if (c != m_cache.end ()) { - return c->second; - } - - bool res = prop_selected_impl (prop_id); - m_cache.insert (std::make_pair (prop_id, res)); - return res; - } - -private: - bool prop_selected_impl (db::properties_id_type prop_id) const - { - const db::PropertiesSet &ps = db::properties (prop_id); - if (ps.has_value (m_name_id)) { - - const tl::Variant &value = ps.value (m_name_id); - - if (m_glob) { - return m_pattern.match (value.to_string ()) != m_inverse; - } else if (m_exact) { - return (value == m_value_from) != m_inverse; - } else { - return ((m_value_from.is_nil () || ! (value < m_value_from)) && (m_value_to.is_nil () || value < m_value_to)) != m_inverse; - } - - } else { - return m_inverse; - } - } - - mutable std::map m_cache; - db::property_names_id_type m_name_id; - tl::Variant m_value_from, m_value_to; - tl::GlobPattern m_pattern; - bool m_exact; - bool m_glob; - bool m_inverse; -}; - -class PolygonPropertiesFilter - : public PolygonFilterBase, public PropertiesFilter -{ -public: - PolygonPropertiesFilter (const tl::Variant &name, const std::string &pattern, bool inverse) - : PropertiesFilter (name, pattern, inverse) - { - // .. nothing yet .. - } - - PolygonPropertiesFilter (const tl::Variant &name, const tl::Variant &value, bool inverse) - : PropertiesFilter (name, value, inverse) - { - // .. nothing yet .. - } - - PolygonPropertiesFilter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) - : PropertiesFilter (name, from, to, inverse) - { - // .. nothing yet .. - } - - bool selected (const db::Polygon &, db::properties_id_type prop_id) const - { - return PropertiesFilter::prop_selected (prop_id); - } - - bool selected (const db::PolygonRef &, db::properties_id_type prop_id) const - { - return PropertiesFilter::prop_selected (prop_id); - } -}; - -static PolygonFilterBase *make_ppf1 (const tl::Variant &name, const tl::Variant &value, bool inverse) +static gsi::PolygonFilterBase *make_ppf1 (const tl::Variant &name, const tl::Variant &value, bool inverse) { return new PolygonPropertiesFilter (name, value, inverse); } -static PolygonFilterBase *make_ppf2 (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) +static gsi::PolygonFilterBase *make_ppf2 (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse) { return new PolygonPropertiesFilter (name, from, to, inverse); } -static PolygonFilterBase *make_pg (const tl::Variant &name, const std::string &glob, bool inverse) +static gsi::PolygonFilterBase *make_pg (const tl::Variant &name, const std::string &glob, bool inverse, bool case_sensitive) { - return new PolygonPropertiesFilter (name, glob, inverse); + tl::GlobPattern pattern (glob); + pattern.set_case_sensitive (case_sensitive); + return new PolygonPropertiesFilter (name, pattern, inverse); } Class decl_PolygonFilterBase ("db", "PolygonFilterBase", - gsi::constructor ("property_glob", &make_pg, gsi::arg ("name"), gsi::arg ("pattern"), gsi::arg ("inverse", false), + gsi::PolygonFilterBase::method_decls (true) + + gsi::constructor ("property_glob", &make_pg, gsi::arg ("name"), gsi::arg ("pattern"), gsi::arg ("inverse", false), gsi::arg ("case_sensitive", true), "@brief Creates a single-valued property filter\n" "@param name The name of the property to use.\n" "@param value The glob pattern to match the property value against.\n" "@param inverse If true, inverts the selection - i.e. all polygons without a matching property are selected.\n" + "@param case_sensitive If true, the match is case sensitive (the default), if false, the match is not case sensitive.\n" "\n" - "Apply this filter with \\Region#filtered.\n" + "Apply this filter with \\Region#filtered:\n" + "\n" + "@code\n" + "# region is a Region object\n" + "# filtered_region contains all polygons where the 'net' property starts with 'C':\n" + "filtered_region = region.filtered(RBA::PolygonFilterBase::property_glob('net', 'C*'))\n" + "@/code\n" "\n" "This feature has been introduced in version 0.30." ) + @@ -224,7 +133,7 @@ Class decl_PolygonFilterBase ("db", "PolygonFilterBase", "@param value The value against which the property is checked (exact match).\n" "@param inverse If true, inverts the selection - i.e. all polygons without a property with the given name and value are selected.\n" "\n" - "Apply this filter with \\Region#filtered.\n" + "Apply this filter with \\Region#filtered. See \\property_glob for an example.\n" "\n" "This feature has been introduced in version 0.30." ) + @@ -236,7 +145,7 @@ Class decl_PolygonFilterBase ("db", "PolygonFilterBase", "@param inverse If true, inverts the selection - i.e. all polygons without a property with the given name and value range are selected.\n" "\n" "This version does a bounded match. The value of the propery needs to be larger or equal to 'from' and less than 'to'.\n" - "Apply this filter with \\Region#filtered.\n" + "Apply this filter with \\Region#filtered. See \\property_glob for an example.\n" "\n" "This feature has been introduced in version 0.30." ), @@ -244,7 +153,6 @@ Class decl_PolygonFilterBase ("db", "PolygonFilterBase", ); Class decl_PolygonFilterImpl (decl_PolygonFilterBase, "db", "PolygonFilter", - PolygonFilterImpl::method_decls (true) + callback ("selected", &PolygonFilterImpl::issue_selected, &PolygonFilterImpl::f_selected, gsi::arg ("polygon"), "@brief Selects a polygon\n" "This method is the actual payload. It needs to be reimplemented in a derived class.\n" diff --git a/src/db/unit_tests/dbPropertiesFilterTests.cc b/src/db/unit_tests/dbPropertiesFilterTests.cc new file mode 100644 index 000000000..208c2894f --- /dev/null +++ b/src/db/unit_tests/dbPropertiesFilterTests.cc @@ -0,0 +1,103 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "dbPropertiesFilter.h" +#include "tlUnitTest.h" + +namespace { + +/** + * @brief Installs a temporary repository instance for testing + * + * By using a temp instance, we do not disturb other tests. + */ +class TempPropertiesRepository +{ +public: + TempPropertiesRepository () + { + db::PropertiesRepository::replace_instance_temporarily (&m_temp); + } + + ~TempPropertiesRepository () + { + db::PropertiesRepository::replace_instance_temporarily (0); + } + +private: + db::PropertiesRepository m_temp; +}; + +} + +TEST(1) +{ + TempPropertiesRepository temp_pr; + + db::PropertiesSet ps; + ps.insert ("net", 17); + auto net17 = properties_id (ps); + + ps.clear (); + ps.insert ("net", 1); + auto net1 = properties_id (ps); + + ps.clear (); + ps.insert ("net", 42); + ps.insert ("not", "never"); + auto net42 = properties_id (ps); + + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), false).prop_selected (net17), false); + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), true).prop_selected (net17), true); + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), false).prop_selected (net42), true); + EXPECT_EQ (db::PropertiesFilter ("not", tl::Variant ("never"), true).prop_selected (net42), false); + EXPECT_EQ (db::PropertiesFilter ("doesnotexist", tl::Variant ("never"), false).prop_selected (net42), false); + EXPECT_EQ (db::PropertiesFilter ("doesnotexist", tl::Variant ("never"), true).prop_selected (net42), true); + EXPECT_EQ (db::PropertiesFilter ("net", 17, false).prop_selected (net17), true); + EXPECT_EQ (db::PropertiesFilter ("net", 17, true).prop_selected (net17), false); + EXPECT_EQ (db::PropertiesFilter ("net", 17, false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", 17, true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::Variant (), 17, false).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::Variant (), 17, true).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", tl::Variant (), 1, false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", tl::Variant (), 1, true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", 0, 2, false).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", 0, 2, true).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", 0, 1, false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", 0, 1, true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", 1, 2, false).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", 1, 2, true).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", -1, tl::Variant (), false).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", -1, tl::Variant (), true).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", 2, tl::Variant (), false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("net", 2, tl::Variant (), true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), false).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), true).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("not", tl::GlobPattern ("1*"), false).prop_selected (net1), false); + EXPECT_EQ (db::PropertiesFilter ("not", tl::GlobPattern ("1*"), true).prop_selected (net1), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), false).prop_selected (net17), true); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), true).prop_selected (net17), false); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), false).prop_selected (net42), false); + EXPECT_EQ (db::PropertiesFilter ("net", tl::GlobPattern ("1*"), true).prop_selected (net42), true); +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index 5183520f5..a24390237 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -13,6 +13,7 @@ SOURCES = \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ dbPolygonNeighborhoodTests.cc \ + dbPropertiesFilterTests.cc \ dbRecursiveInstanceIteratorTests.cc \ dbRegionCheckUtilsTests.cc \ dbTriangleTests.cc \ diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 590c247a9..5f7db1bb7 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -26,8 +26,8 @@ load("test_prologue.rb") # normalizes a specification string for region, edges etc. # such that the order of the objects becomes irrelevant def csort(s) - # splits at ");(" without consuming the brackets - s.split(/(?<=\));(?=\()/).sort.join(";") + # splits at ");(" or "};(" without consuming the brackets + s.split(/(?<=[\)\}]);(?=\()/).sort.join(";") end class ParallelFilter < RBA::EdgeFilter @@ -1037,6 +1037,56 @@ class DBEdges_TestClass < TestBase end + # properties + def test_prop_filters + + r = RBA::Edges::new + r.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(0, 0, 100, 200), { "one" => -1 })) + r.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(1, 1, 101, 201), { "one" => 17 })) + r.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(2, 2, 102, 202), { "one" => 42 })) + + assert_equal(r.filtered(RBA::EdgeFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::EdgeFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter("one", 17)).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter("one", 17, true)).to_s), csort("(0,0;100,200){one=>-1};(2,2;102,202){one=>42}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(2,2;102,202){one=>42};(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(2,2;102,202){one=>42};(0,0;100,200){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(1,1;101,201){one=>17};(0,0;100,200){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_glob("one", "1*")).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_glob("one", "1*", true)).to_s), csort("(2,2;102,202){one=>42};(0,0;100,200){one=>-1}")) + + ly = RBA::Layout::new + top = ly.create_cell("TOP") + l1 = ly.layer(1, 0) + + s = top.shapes(l1) + s.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(0, 0, 100, 200), { "one" => -1 })) + s.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(1, 1, 101, 201), { "one" => 17 })) + s.insert(RBA::EdgeWithProperties::new(RBA::Edge::new(2, 2, 102, 202), { "one" => 42 })) + + dss = RBA::DeepShapeStore::new + iter = top.begin_shapes_rec(l1) + iter.enable_properties() + r = RBA::Edges::new(iter, dss) + + assert_equal(r.filtered(RBA::EdgeFilter::property_filter("one", 11)).to_s, "") + assert_equal(r.filtered(RBA::EdgeFilter::property_filter("two", 17)).to_s, "") + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter("one", 17)).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter("one", 17, true)).to_s), csort("(0,0;100,200){one=>-1};(2,2;102,202){one=>42}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(2,2;102,202){one=>42};(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(2,2;102,202){one=>42};(0,0;100,200){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(1,1;101,201){one=>17};(0,0;100,200){one=>-1}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_glob("one", "1*")).to_s), csort("(1,1;101,201){one=>17}")) + assert_equal(csort(r.filtered(RBA::EdgeFilter::property_glob("one", "1*", true)).to_s), csort("(2,2;102,202){one=>42};(0,0;100,200){one=>-1}")) + + rr = r.dup + rr.filter(RBA::EdgeFilter::property_filter("one", 17)) + assert_equal(csort(rr.to_s), csort("(1,1;101,201){one=>17}")) + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index e60250ee8..663232c33 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -26,8 +26,8 @@ load("test_prologue.rb") # normalizes a specification string for region, edges etc. # such that the order of the objects becomes irrelevant def csort(s) - # splits at ");(" without consuming the brackets - s.split(/(?<=\));(?=\()/).sort.join(";") + # splits at ");(" or "};(" without consuming the brackets + s.split(/(?<=[\)\}]);(?=\()/).sort.join(";") end class TriangleFilter < RBA::PolygonFilter @@ -1593,14 +1593,14 @@ class DBRegion_TestClass < TestBase assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 11)).to_s, "") assert_equal(r.filtered(RBA::PolygonFilter::property_filter("two", 17)).to_s, "") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 17)).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 17, true)).to_s, "(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, nil)).to_s, "(1,1;1,201;101,201;101,1){one=>17};(2,2;2,202;102,202;102,2){one=>42}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18)).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18, true)).to_s, "(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", nil, 18)).to_s, "(1,1;1,201;101,201;101,1){one=>17};(0,0;0,200;100,200;100,0){one=>-1}") - assert_equal(r.filtered(RBA::PolygonFilter::property_glob("one", "1*")).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_glob("one", "1*", true)).to_s, "(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}") + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter("one", 17)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter("one", 17, true)).to_s), csort("(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17};(2,2;2,202;102,202;102,2){one=>42}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17};(0,0;0,200;100,200;100,0){one=>-1}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_glob("one", "1*")).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_glob("one", "1*", true)).to_s), csort("(2,2;2,202;102,202;102,2){one=>42};(0,0;0,200;100,200;100,0){one=>-1}")) ly = RBA::Layout::new top = ly.create_cell("TOP") @@ -1618,14 +1618,18 @@ class DBRegion_TestClass < TestBase assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 11)).to_s, "") assert_equal(r.filtered(RBA::PolygonFilter::property_filter("two", 17)).to_s, "") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 17)).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter("one", 17, true)).to_s, "(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, nil)).to_s, "(1,1;1,201;101,201;101,1){one=>17};(2,2;2,202;102,202;102,2){one=>42}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18)).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18, true)).to_s, "(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}") - assert_equal(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", nil, 18)).to_s, "(0,0;0,200;100,200;100,0){one=>-1};(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_glob("one", "1*")).to_s, "(1,1;1,201;101,201;101,1){one=>17}") - assert_equal(r.filtered(RBA::PolygonFilter::property_glob("one", "1*", true)).to_s, "(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}") + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter("one", 17)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter("one", 17, true)).to_s), csort("(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, nil)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17};(2,2;2,202;102,202;102,2){one=>42}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18)).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", 17, 18, true)).to_s), csort("(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_filter_bounded("one", nil, 18)).to_s), csort("(0,0;0,200;100,200;100,0){one=>-1};(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_glob("one", "1*")).to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) + assert_equal(csort(r.filtered(RBA::PolygonFilter::property_glob("one", "1*", true)).to_s), csort("(0,0;0,200;100,200;100,0){one=>-1};(2,2;2,202;102,202;102,2){one=>42}")) + + rr = r.dup + rr.filter(RBA::PolygonFilter::property_filter("one", 17)) + assert_equal(csort(rr.to_s), csort("(1,1;1,201;101,201;101,1){one=>17}")) end