1 /*
2  Copyright (c) 2008-2020, Benoit AUTHEMAN All rights reserved.
3 
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions are met:
6     * Redistributions of source code must retain the above copyright
7       notice, this list of conditions and the following disclaimer.
8     * Redistributions in binary form must reproduce the above copyright
9       notice, this list of conditions and the following disclaimer in the
10       documentation and/or other materials provided with the distribution.
11     * Neither the name of the author or Destrat.io nor the
12       names of its contributors may be used to endorse or promote products
13       derived from this software without specific prior written permission.
14 
15  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
19  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26 
27 //-----------------------------------------------------------------------------
28 // This file is a part of the GTpo software library.
29 //
30 // \file	graph.hpp
31 // \author	benoit@destrat.io
32 // \date	2016 01 22
33 //-----------------------------------------------------------------------------
34 
35 namespace gtpo { // ::gtpo
36 
37 /* Graph Node Management *///--------------------------------------------------
38 template <class config_t>
~graph()39 graph<config_t>::~graph()
40 {
41     clear();
42 }
43 
44 template <class config_t>
clear()45 void    graph<config_t>::clear() noexcept
46 {
47     // Note 20160104: First edges, then nodes (it helps maintaining topology if
48     // womething went wrong during destruction
49     for ( auto& node: _nodes )   // Do not maintain topology during node deletion
50         node->_graph = nullptr;
51     _root_nodes.clear();         // Remove weak_ptr containers first
52     _nodes_search.clear();
53     _nodes.clear();
54 
55     for ( auto& edge: _edges )   // Do not maintain topology during edge deletion
56         edge->_graph = nullptr;
57     _edges_search.clear();
58     _edges.clear();
59 
60     // Clearing groups and behaviours (Not: group->_graph is resetted with nodes)
61     _groups.clear();
62     behaviourable_base::clear();
63 }
64 //-----------------------------------------------------------------------------
65 
66 /* Graph Node Management *///--------------------------------------------------
67 template < class config_t >
create_node()68 auto graph<config_t>::create_node( ) -> weak_node_t
69 {
70     weak_node_t node;
71     try {
72         node = insert_node( std::make_shared< typename config_t::final_node_t >() );
73     } catch (...) { gtpo::assert_throw( false, "graph<>::create_node(): Error: can't insert node in graph." ); }
74     return node;
75 }
76 
77 template < class config_t >
insert_node(shared_node_t node)78 auto    graph<config_t>::insert_node( shared_node_t node ) -> weak_node_t
79 {
80     assert_throw(node != nullptr, "gtpo::graph<>::insert_node(): Error: Trying to insert a nullptr node in graph.");
81     weak_node_t weak_node;
82     try {
83         weak_node = node;
84         node->set_graph(this);
85         config_t::template container_adapter< shared_nodes_t >::insert( node, _nodes );
86         config_t::template container_adapter< weak_nodes_t_search >::insert( weak_node, _nodes_search );
87         config_t::template container_adapter< weak_nodes_t >::insert( weak_node, _root_nodes );
88         behaviourable_base::notify_node_inserted( weak_node );
89     } catch (...) { gtpo::assert_throw( false, "gtpo::graph<>::insert_node(): Error: can't insert node in graph." ); }
90     return weak_node;
91 }
92 
93 template < class config_t >
remove_node(weak_node_t weak_node)94 auto    graph<config_t>::remove_node( weak_node_t weak_node ) -> void
95 {
96     auto node = weak_node.lock();
97     if ( !node )
98         gtpo::assert_throw( false, "gtpo::graph<>::remove_node(): Error: node is expired." );
99 
100     // Eventually, ungroup node
101     auto group = node->get_group().lock();
102     if ( group )
103         ungroup_node(weak_node, group);
104 
105     behaviourable_base::notify_node_removed( weak_node );
106 
107     // Removing all orphant edges pointing to node.
108     weak_edges_t nodeInEdges; std::copy( node->get_in_edges().cbegin(),
109                                       node->get_in_edges().cend(),
110                                       std::back_inserter( nodeInEdges ) );
111     for ( auto inEdge: nodeInEdges )
112         node->remove_in_edge( inEdge );
113     for ( auto inEdge: nodeInEdges )
114         remove_edge( inEdge );
115 
116     weak_edges_t nodeOutEdges; std::copy( node->get_out_edges().cbegin(),
117                                       node->get_out_edges().cend(),
118                                       std::back_inserter( nodeOutEdges ) );
119     for ( auto outEdge: nodeOutEdges )
120         node->remove_out_edge( outEdge );
121     for ( auto outEdge: nodeOutEdges )
122         remove_edge( outEdge );
123 
124     // Remove node from main graph containers (it will generate node destruction)
125     config_t::template container_adapter<weak_nodes_t_search>::remove( weak_node, _nodes_search );
126     config_t::template container_adapter<weak_nodes_t>::remove( weak_node, _root_nodes );
127     node->set_graph( nullptr );
128     config_t::template container_adapter<shared_nodes_t>::remove( node, _nodes );
129 }
130 
131 template < class config_t >
install_root_node(weak_node_t node)132 auto    graph<config_t>::install_root_node( weak_node_t node ) -> void
133 {
134     assert_throw( !node.expired(), "gtpo::graph<>::setRootNode(): Error: node is expired." );
135     shared_node_t sharedNode = node.lock();
136     assert_throw( sharedNode->get_in_degree() == 0, "gtpo::graph<>::setRootNode(): Error: trying to set a node with non 0 in degree as a root node." );
137 
138     config_t::template container_adapter<weak_nodes_t>::insert( node, _root_nodes );
139 }
140 
141 template < class config_t >
is_root_node(weak_node_t node) const142 auto    graph<config_t>::is_root_node( weak_node_t node ) const -> bool
143 {
144     assert_throw( !node.expired(), "gtpo::graph<>::is_root_node(): Error: node is expired." );
145     shared_node_t sharedNode = node.lock();
146     if ( sharedNode->get_in_degree() != 0 )   // Fast exit when node in degree != 0, it can't be a root node
147         return false;
148     return gtpo::find_weak_ptr( _root_nodes, node );
149 }
150 
151 template < class config_t >
contains(weak_node_t node) const152 auto    graph<config_t>::contains( weak_node_t node ) const noexcept -> bool
153 {
154     if ( node.expired() )   // Fast exit.
155         return false;
156     auto nodeIter = std::find_if( _nodes_search.cbegin(), _nodes_search.cend(),
157                                             [=](const weak_node_t& n ) { return ( compare_weak_ptr<>( node, n ) ); } );
158     return nodeIter != _nodes_search.cend();
159 }
160 //-----------------------------------------------------------------------------
161 
162 /* Graph Edge Management *///--------------------------------------------------
163 template < class config_t >
164 //template < class Edge_t >
create_edge(weak_node_t source,weak_node_t destination)165 auto    graph< config_t >::create_edge( weak_node_t source, weak_node_t destination ) -> weak_edge_t
166 {
167     auto source_ptr = source.lock();
168     auto destination_ptr = destination.lock();
169     if ( !source_ptr ||
170          !destination_ptr )
171         throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(Node,Node): Insertion of edge failed, either source or destination nodes are expired." );
172 
173     auto edge = std::make_shared<typename config_t::final_edge_t>();
174     edge->set_graph( this );
175     config_t::template container_adapter< shared_edges_t >::insert( edge, _edges );
176     config_t::template container_adapter< weak_edges_search_t >::insert( edge, _edges_search );
177     edge->set_src( source );
178     edge->set_dst( destination );
179     try {
180         source_ptr->add_out_edge( edge );
181         destination_ptr->add_in_edge( edge );
182         if ( source_ptr.get() != destination_ptr.get() ) // If edge define is a trivial circuit, do not remove destination from root nodes
183             config_t::template container_adapter<weak_nodes_t>::remove( destination, _root_nodes );    // Otherwise destination is no longer a root node
184         auto weak_edge = weak_edge_t{edge};
185         behaviourable_base::notify_edge_inserted( weak_edge );
186     } catch ( ... ) {
187         throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(Node,Node): Insertion of edge failed, source or destination nodes topology can't be modified." );
188     }
189     return edge;
190 }
191 
192 template < class config_t >
insert_edge(shared_edge_t edge)193 auto    graph<config_t>::insert_edge( shared_edge_t edge ) -> weak_edge_t
194 {
195     assert_throw( edge != nullptr );
196     auto source = edge->get_src().lock();
197     if ( source == nullptr ||
198          edge->get_dst().expired() )
199         throw gtpo::bad_topology_error( "gtpo::graph<>::insert_edge(): Error: Either source and/or destination nodes are expired." );
200     edge->set_graph( this );
201     weak_edge_t weak_edge = edge;
202     config_t::template container_adapter<shared_edges_t>::insert( edge, _edges );
203     config_t::template container_adapter<weak_edges_search_t>::insert( weak_edge, _edges_search );
204     try {
205         source->add_out_edge( edge );
206         auto destination = edge->get_dst().lock();
207         if ( destination != nullptr ) {
208             destination->add_in_edge( edge );
209             if ( source.get() != destination.get() ) // If edge define is a trivial circuit, do not remove destination from root nodes
210                 config_t::template container_adapter<weak_nodes_t>::remove( destination, _root_nodes );    // Otherwise destination is no longer a root node
211         }
212         auto weak_edge = weak_edge_t(edge);
213         behaviourable_base::notify_edge_inserted( weak_edge );
214     } catch ( ... ) {
215         throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(): Insertion of edge failed, source or destination nodes topology can't be modified." );
216     }
217     return edge;
218 }
219 
220 template < class config_t >
remove_edge(weak_node_t source,weak_node_t destination)221 void    graph<config_t>::remove_edge( weak_node_t source, weak_node_t destination )
222 {
223     if ( source.expired() ||
224          destination.expired() )
225         throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Topology error." );
226 
227     // Find the edge associed with source / destination
228     if ( _edges.size() == 0 )
229         return; // Fast exit
230     auto edgeIter = std::find_if( _edges.begin(), _edges.end(),
231                                  [=](const shared_edge_t& e ){
232                                     return ( compare_weak_ptr<>( e->get_src(), source ) &&
233                                              compare_weak_ptr<>( e->get_dst(), destination ) );
234                                     }
235                                  ); // std::find_if
236     if ( edgeIter != _edges.end() )
237         remove_edge( *edgeIter );
238 }
239 
240 template < class config_t >
remove_all_edges(weak_node_t source,weak_node_t destination)241 void    graph<config_t>::remove_all_edges( weak_node_t source, weak_node_t destination )
242 {
243     if ( source.expired() ||
244          destination.expired() )
245         throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Topology error." );
246 
247     while ( get_edge_count( source, destination ) > 0 )
248         remove_edge( source, destination );
249 }
250 
251 template < class config_t >
remove_edge(weak_edge_t weak_edge)252 void    graph<config_t>::remove_edge( weak_edge_t weak_edge )
253 {
254     shared_edge_t edge = weak_edge.lock();
255     if ( !edge )
256         throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Error: Edge to be removed is already expired." );
257     auto source = edge->get_src().lock();
258     auto destination = edge->get_dst().lock();
259     if ( source == nullptr      ||           // Expecting a non null source and either a destination or an hyper destination
260          destination == nullptr )
261         throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Error: Edge source or destination are expired." );
262     behaviourable_base::notify_edge_removed( weak_edge );
263     source->remove_out_edge( weak_edge );
264     if ( destination )      // Remove edge from destination in edges
265         destination->remove_in_edge( weak_edge );
266 
267     edge->set_graph( nullptr );
268     config_t::template container_adapter<shared_edges_t>::remove( edge, _edges );
269     config_t::template container_adapter<weak_edges_search_t>::remove( weak_edge, _edges_search );
270 }
271 
272 template < class config_t >
find_edge(weak_node_t source,weak_node_t destination) const273 auto    graph<config_t>::find_edge( weak_node_t source, weak_node_t destination ) const noexcept -> weak_edge_t
274 {
275     // Find the edge associed with source / destination
276     auto edgeIter = std::find_if( _edges.begin(), _edges.end(),
277                                     [=](const shared_edge_t& e ) noexcept {
278                                         return ( compare_weak_ptr<>( e->get_src(), source ) &&
279                                                  compare_weak_ptr<>( e->get_dst(), destination ) );
280                                     } );
281     return ( edgeIter != _edges.end() ? *edgeIter : weak_edge_t{} );   // std::shared_ptr::operator* is noexcept
282 }
283 
284 template < class config_t >
has_edge(weak_node_t source,weak_node_t destination) const285 auto    graph<config_t>::has_edge( weak_node_t source, weak_node_t destination ) const noexcept -> bool
286 {
287     return ( find_edge( source, destination).use_count() != 0 );
288 }
289 
290 template < class config_t >
get_edge_count(weak_node_t source,weak_node_t destination) const291 auto    graph<config_t>::get_edge_count( weak_node_t source, weak_node_t destination ) const -> unsigned int
292 {
293     unsigned int edgeCount = 0;
294     std::for_each( _edges.begin(), _edges.end(), [&](const shared_edge_t& e ) {
295                                                         if ( compare_weak_ptr<>( e->get_src(), source ) &&
296                                                              compare_weak_ptr<>( e->get_dst(), destination ) )
297                                                             ++edgeCount;
298                                                     } );
299     return edgeCount;
300 }
301 
302 template < class config_t >
contains(weak_edge_t edge) const303 auto    graph<config_t>::contains( weak_edge_t edge ) const noexcept -> bool
304 {
305     if ( edge.expired() )   // Fast exit.
306         return false;
307     auto edgeIter = std::find_if( _edges_search.cbegin(), _edges_search.cend(),
308                                             [=](const weak_edge_t& n ) { return ( compare_weak_ptr<>( edge, n ) ); } );
309     return edgeIter != _edges_search.cend();
310 }
311 //-----------------------------------------------------------------------------
312 
313 /* Graph Group Management *///-------------------------------------------------
314 template < class config_t >
insert_group(shared_group_t group)315 auto    graph<config_t>::insert_group( shared_group_t group ) noexcept( false ) -> weak_group_t
316 {
317     assert_throw( group != nullptr, "gtpo::graph<>::insert_group(): Error: trying to insert a nullptr shared_node_t" );
318     weak_node_t group_node_ptr = insert_node(group);
319     try {
320         group->set_graph( this );
321         config_t::template container_adapter<weak_groups_t>::insert( group, _groups );
322 
323         // FIXME GROUPS
324         //this->notify_group_inserted( weakGroup );
325     } catch (...) {
326         throw gtpo::bad_topology_error( "gtpo::graph<>::insert_group(): Insertion of group failed" );
327     }
328     return group;
329 }
330 
331 template < class config_t >
remove_group(weak_group_t group_ptr)332 auto    graph<config_t>::remove_group( weak_group_t group_ptr ) noexcept( false ) -> void
333 {
334     shared_node_t group = group_ptr.lock();
335     if ( !group )
336         gtpo::assert_throw( false, "graph<>::remove_group(): Error: trying to remove and expired group." );
337 
338     // Remove group (it will be automatically deallocated)
339     // FIXME GROUPS
340     //this->notify_group_removed( group_ptr );
341     group->set_graph( nullptr );
342     config_t::template container_adapter<weak_groups_t>::remove( group_ptr, _groups );
343 
344     remove_node(group_ptr);
345 }
346 
347 template < class config_t >
has_group(const weak_group_t & group) const348 auto    graph<config_t>::has_group( const weak_group_t& group ) const -> bool
349 {
350     if ( group.expired() )
351         return false;
352     auto groupIter = std::find_if( _groups.begin(), _groups.end(),
353                                         [=](const weak_group_t& graphGroup  ){ return ( compare_weak_ptr<>( group, graphGroup ) ); } );
354     return groupIter != _groups.end();
355 }
356 
357 template < class config_t >
group_node(weak_node_t node,weak_group_t group)358 auto    graph<config_t>::group_node( weak_node_t node, weak_group_t group) noexcept(false) -> void
359 {
360     auto group_ptr = group.lock();
361     gtpo::assert_throw( group_ptr != nullptr, "gtpo::group<>::group_node(): Error: trying to insert a node into an expired group." );
362 
363     auto node_ptr = node.lock();
364     gtpo::assert_throw( node_ptr != nullptr, "gtpo::group<>::group_node(): Error: trying to insert an expired node in group." );
365 
366     node_ptr->set_group( group );
367     config_t::template container_adapter<weak_nodes_t>::insert( node, group_ptr->_nodes );
368 
369     // FIXME GROUPS
370     //group_ptr->notify_node_inserted( node );
371 }
372 
373 template < class config_t >
ungroup_node(weak_node_t weakNode,weak_node_t weakGroup)374 auto    graph<config_t>::ungroup_node( weak_node_t weakNode, weak_node_t weakGroup ) noexcept(false) -> void
375 {
376     auto group = weakGroup.lock();
377     gtpo::assert_throw( group != nullptr, "gtpo::group<>::ungroup_node(): Error: trying to ungroup from an expired group." );
378 
379     auto node = weakNode.lock();
380     gtpo::assert_throw( node != nullptr, "gtpo::group<>::ungroup_node(): Error: trying to ungroup an expired node from a group." );
381 
382     gtpo::assert_throw( node->get_group().lock() == group, "gtpo::group<>::ungroup_node(): Error: trying to ungroup a node that is not part of group." );
383 
384     config_t::template container_adapter<weak_nodes_t>::remove( weakNode, group->_nodes );
385     // FIXME GROUPS
386     //group->notify_node_removed( weakNode );
387     node->set_group( weak_group_t{} );  // Warning: group must remain valid while notify_node_removed() is called
388 }
389 //-----------------------------------------------------------------------------
390 
391 } // ::gtpo
392 
393