Salome HOME
set->list in Node.getOutNodes
[modules/yacs.git] / src / engine / Node.cxx
1 // Copyright (C) 2006-2015  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "Node.hxx"
21 #include "InputPort.hxx"
22 #include "OutputPort.hxx"
23 #include "InPropertyPort.hxx"
24 #include "ComposedNode.hxx"
25 #include "Dispatcher.hxx"
26 #include "InputDataStreamPort.hxx"
27 #include "OutputDataStreamPort.hxx"
28 #include <iostream>
29
30 //#define _DEVDEBUG_
31 #include "YacsTrace.hxx"
32
33 using namespace YACS::ENGINE;
34 using namespace std;
35
36 /*! \class YACS::ENGINE::Node
37  *  \brief Base class for all nodes
38  *
39  * \ingroup Nodes
40  */
41
42 const char Node::SEP_CHAR_IN_PORT[]=".";
43
44 int Node::_total = 0;
45 std::map<int,Node*> Node::idMap;
46
47 NodeStateNameMap::NodeStateNameMap()
48 {
49   insert(make_pair(YACS::READY, "READY"));
50   insert(make_pair(YACS::TOLOAD, "TOLOAD"));
51   insert(make_pair(YACS::LOADED, "LOADED"));
52   insert(make_pair(YACS::TOACTIVATE, "TOACTIVATE"));
53   insert(make_pair(YACS::ACTIVATED, "ACTIVATED"));
54   insert(make_pair(YACS::DESACTIVATED, "DESACTIVATED"));
55   insert(make_pair(YACS::DONE, "DONE"));
56   insert(make_pair(YACS::SUSPENDED, "SUSPENDED"));
57   insert(make_pair(YACS::LOADFAILED, "LOADFAILED"));
58   insert(make_pair(YACS::EXECFAILED, "EXECFAILED"));
59   insert(make_pair(YACS::PAUSE, "PAUSE"));
60   insert(make_pair(YACS::INTERNALERR, "INTERNALERR"));
61   insert(make_pair(YACS::DISABLED, "DISABLED"));
62   insert(make_pair(YACS::FAILED, "FAILED"));
63   insert(make_pair(YACS::ERROR, "ERROR"));
64 }
65
66
67 Node::Node(const std::string& name):_name(name),_inGate(this),_outGate(this),_father(0),_state(YACS::READY),
68                                     _implementation(Runtime::RUNTIME_ENGINE_INTERACTION_IMPL_NAME),_modified(1)
69 {
70   // Should be protected by lock ??
71   _numId = _total++;
72   idMap[_numId]=this;
73
74   // Every node has an InPropertyPort
75   _inPropertyPort = new InPropertyPort("__InPropertyPort__Node__YACS_", this, Runtime::_tc_propvec);
76 }
77
78 Node::Node(const Node& other, ComposedNode *father):_inGate(this),_outGate(this),_name(other._name),_father(father),
79                                                    _state(YACS::READY),_implementation(other._implementation),
80                                                     _propertyMap(other._propertyMap),_modified(1)
81 {
82   _numId = _total++;
83   idMap[_numId]=this;
84
85   // Every node has an InPropertyPort
86   _inPropertyPort = new InPropertyPort("__InPropertyPort__Node__YACS_", this, Runtime::_tc_propvec);
87 }
88
89 Node::~Node()
90 {
91   delete _inPropertyPort;
92 }
93
94 /**
95  *  initialisation of all input and output ports and gates, for execution
96  */
97
98 void Node::init(bool start)
99 {
100   _inGate.exReset();
101   _outGate.exReset();
102   if(_state == YACS::DISABLED)
103     {
104       exDisabledState(); // to refresh propagation of DISABLED state 
105       return;
106     }
107   setState(YACS::READY);
108 }
109
110 /*!
111  * This method clones \a this by :
112  *
113  * - deep copying nodes, links, ports, types
114  * - containers are either deep copied or shallow copied depending on _isAttachedOnCloning attribute.
115  * - component are either deep copied or shallow copied depending on _isAttachedOnCloning attribute.
116  *
117  * So \b this \b method \b clone \b is \b dedicated \b for \b DynParaLoop \b class \b or \b subclasses.
118  * It \b should \b not \b be \b used \b elsewhere, because
119  * _isAttachedOnCloning attribute is an attribute in the engine not for GUI/TUI aspects.
120  * For GUI/TUI manipulation cloneWithoutCompAndContDeepCpy method should be used preferably.
121  *
122  * \param [in] father - The new father of the returned clone.
123  * \param [in] editionOnly ignored
124  *
125  * \sa cloneWithoutCompAndContDeepCpy
126  */
127 Node *Node::clone(ComposedNode *father, bool editionOnly) const
128 {
129   Node *ret(simpleClone(father,editionOnly));
130   ret->performDuplicationOfPlacement(*this);
131   return ret;
132 }
133
134 /*!
135  * This method clones \a this by :
136  * - deep copying nodes, links, ports, types
137  * - shallow copy containers
138  * - shallow copy components
139  *
140  * So this method simply ignores isAttachedOnCloning attribute for both containers and components.
141  * So this method is dedicated for the GUI/TUI users.
142  *
143  * \param [in] father - The new father of the returned clone.
144  * \param [in] editionOnly ignored
145  */
146 Node *Node::cloneWithoutCompAndContDeepCpy(ComposedNode *father, bool editionOnly) const
147 {
148   Node *ret(simpleClone(father,editionOnly));
149   ret->performShallowDuplicationOfPlacement(*this);
150   return ret;
151 }
152
153 //! Change the name of the node
154 /*!
155  *  raise an exception if the name is already used in the scope of its father 
156  *  \param name : the new name
157  */
158 void Node::setName(const std::string& name)
159 {
160   if(_father)
161     {
162       if(_father->isNameAlreadyUsed(name))
163         {
164           if ( _father->getChildByName(name) != this )
165             {
166               std::string what("Name "); 
167               what+=name;
168               what+=" already exists in the scope of "; what+=_father->getName();
169               throw Exception(what);
170             }
171         }
172     }
173   _name=name;
174 }
175
176 /**
177  *  get the set of all nodes connected to the outGate
178  */
179
180 list<Node *> Node::getOutNodes() const
181 {
182   list<Node *> ret;
183   list<InGate *> inGates=_outGate.edSetInGate();
184   for(list<InGate *>::iterator iter=inGates.begin();iter!=inGates.end();iter++)
185     ret.push_back((*iter)->getNode());
186   return ret;
187 }
188
189 bool Node::exIsControlReady() const
190 {
191   return _inGate.exIsReady();
192 }
193
194 //! Update the node state
195 /*!
196  * \note : Update the '_state' attribute.
197  *          Typically called by 'this->_inGate' when 'this->_inGate' is ready.
198  *
199  *          Called by InGate::exNotifyFromPrecursor 
200  */
201 void Node::exUpdateState()
202 {
203   if(_state==YACS::DISABLED)return;
204   if(_inGate.exIsReady())
205     setState(YACS::TOACTIVATE);
206 }
207
208 //! Notify this node that its execution has failed
209 /*!
210  * The node goes in FAILED state and 
211  * propagate the notification through the outGate port
212  *
213  */
214 void Node::exFailedState()
215 {
216   DEBTRACE( "Node::exFailedState: " << getName() );
217   setState(YACS::FAILED);
218   _outGate.exNotifyFailed();
219 }
220
221 //! Notify this node that it has been disabled
222 /*!
223  * The node goes in DISABLED state and
224  * propagate the notification through the outGate port
225  *
226  */
227 void Node::exDisabledState()
228 {
229   DEBTRACE( "Node::exDisabledState: " << getName() );
230   setState(YACS::DISABLED);
231   _outGate.exNotifyDisabled();
232 }
233
234 InPort *Node::getInPort(const std::string& name) const throw(YACS::Exception)
235 {
236   InPort *ret;
237   try
238     {
239       ret=getInputPort(name);
240     }
241   catch(Exception& e)
242     {
243       ret=getInputDataStreamPort(name);
244     }
245   return ret;
246 }
247
248 InPropertyPort *
249 Node::getInPropertyPort() const throw(YACS::Exception)
250 {
251   return _inPropertyPort;
252 }
253
254 InputPort *
255 Node::getInputPort(const std::string& name) const throw(YACS::Exception)
256 {
257   if (name == "__InPropertyPort__Node__YACS_")
258     return _inPropertyPort;
259   else
260   {
261     std::string what("Node::getInputPort : the port with name "); what+=name; what+=" does not exist on the current level";
262     throw Exception(what);
263   }
264 }
265
266 /*!
267  * \note: Contrary to getOutputPort method, this method returns the output port at highest level, possible.
268  *        That is to say in some ComposedNode, like ForEachLoop or Switch, an outport inside 'this' is seen differently than the true outport.
269  */
270 OutPort *Node::getOutPort(const std::string& name) const throw(YACS::Exception)
271 {
272   OutPort *ret;
273   try
274     {
275       ret=getOutputPort(name);
276     }
277   catch(Exception& e)
278     {
279       ret=getOutputDataStreamPort(name);
280     }
281   return ret;
282 }
283
284 std::list<InPort *> Node::getSetOfInPort() const
285 {
286   list<InPort *> ret;
287   list<InputPort *> data=getSetOfInputPort();
288   ret.insert(ret.end(),data.begin(),data.end());
289   list<InputDataStreamPort *> ds=getSetOfInputDataStreamPort();
290   ret.insert(ret.end(),ds.begin(),ds.end());
291   return ret;
292 }
293
294 std::list<OutPort *> Node::getSetOfOutPort() const
295 {
296   list<OutPort *> ret;
297   list<OutputPort *> data=getSetOfOutputPort();
298   ret.insert(ret.end(),data.begin(),data.end());
299   list<OutputDataStreamPort *> ds=getSetOfOutputDataStreamPort();
300   ret.insert(ret.end(),ds.begin(),ds.end());
301   return ret;
302 }
303
304 /**
305  * gets a set of the composed nodes that constitute the ascendancy of this node, starting from root
306  * or from a particular ancestor
307  * \b WARNING : returned set is not sorted !
308  * @param  levelToStop   composed node which is the oldest ancestor required
309  * @return               ascendancy, direct father first in set.
310  */
311
312 std::list<ComposedNode *> Node::getAllAscendanceOf(ComposedNode *levelToStop) const
313 {
314   list<ComposedNode *> ret;
315   if(this==levelToStop)
316     return ret;
317   for(ComposedNode *iter=_father;iter!=levelToStop && iter!=0; iter=iter->_father)
318       ret.push_back(iter);
319   return ret;
320 }
321
322 bool Node::operator>(const Node& other) const
323 {
324   const ComposedNode *iter=other._father;
325   while(iter!=0 && iter!=this)
326     iter=iter->_father;
327   return iter==this;
328 }
329
330 bool Node::operator<(const Node& other) const
331 {
332   const ComposedNode *iter=_father;
333   while(iter!=0 && iter!=(&other))
334     iter=iter->_father;
335   return iter==(&other);
336 }
337
338 /**
339  *  @return Implementation of node: C++, Python, CORBA...
340  *  _implementation is set by a derived class in a Runtime
341  *  it normally applies only to elementaryNodes and it is used by Ports to select Data Converters.
342  *  Potential problem with Ports attached to composed Nodes...
343  */
344
345 string Node::getImplementation() const
346 {
347   return _implementation;
348 }
349
350 //! Becomes deprecated soon. Replaced by ComposedNode::CheckConsistency.
351 set<InputPort *> Node::edGetSetOfUnitializedInputPort() const
352 {
353   set<InputPort *> setOfUnitializedInputPort;
354   list<InputPort *> allOfInputPorts=getSetOfInputPort();
355   for(list<InputPort *>::const_iterator iter=allOfInputPorts.begin();iter!=allOfInputPorts.end();iter++)
356     {
357       if ( ! (*iter)->edIsInitialized() )
358         setOfUnitializedInputPort.insert(*iter);
359     }
360   return setOfUnitializedInputPort;
361 }
362
363 //! Becomes deprecated soon. Replaced by ComposedNode::CheckConsistency.
364 bool Node::edAreAllInputPortInitialized() const
365 {
366   set<InputPort *> setOfUnitializedInputPort = edGetSetOfUnitializedInputPort();
367   return ( setOfUnitializedInputPort.size() == 0);
368 }
369
370 /*!
371  * Called typically by Bloc to notify failure on potentially next nodes on the same scope of 'this'
372  */
373 void Node::exForwardFailed()
374 {
375   _outGate.exNotifyFailed();
376 }
377
378 /*!
379  * Called typically by Bloc to activate potentially next nodes on the same scope of 'this'
380  */
381 void Node::exForwardFinished()
382 {
383   DEBTRACE("Node::exForwardFinished");
384   _outGate.exNotifyDone();
385 }
386
387 /*!
388  * Called typically by ComposedNode to correctly update DF/CF/DS links
389  */
390 void Node::edDisconnectAllLinksWithMe()
391 {
392   _inGate.edDisconnectAllLinksToMe();
393   _outGate.edDisconnectAllLinksFromMe();
394 }
395
396 Proc *Node::getProc()
397 {
398   if(!_father)
399     return 0;
400   return _father->getProc();
401 }
402
403 const Proc * Node::getProc() const
404 {
405   if(!_father)
406     return 0;
407   return _father->getProc();
408 }
409
410 ComposedNode *Node::getRootNode() const throw(YACS::Exception)
411 {
412   if(!_father)
413     throw Exception("No root node");
414   ComposedNode *iter=_father;
415   while(iter->_father)
416     iter=iter->_father;
417   return (ComposedNode *)iter;
418 }
419
420 /**
421  * checks validity of ports name, that must not contain a particular character '?'
422  * USAGE NOT CLEAR, not used so far, when are those characters set ?
423  */
424
425 void Node::checkValidityOfPortName(const std::string& name) throw(YACS::Exception)
426 {
427   if(name.find(SEP_CHAR_IN_PORT, 0 )!=string::npos)
428     {
429       string what("Port name "); what+=name; what+="not valid because it contains character "; what+=SEP_CHAR_IN_PORT;
430       throw Exception(what);
431     }
432 }
433
434 /**
435  * @note : Check that 'node1' and 'node2' have exactly the same father
436  * @exception : If 'node1' and 'node2' have NOT exactly the same father
437  */
438 ComposedNode *Node::checkHavingCommonFather(Node *node1, Node *node2) throw(YACS::Exception)
439 {
440   if(node1!=0 && node2!=0)
441     {
442       if(node1->_father==node2->_father)
443         return node1->_father;
444     }
445   throw Exception("check failed : nodes have not the same father");
446 }
447
448 const std::string Node::getId() const
449 {
450     std::string id=getRootNode()->getName();
451     if(getRootNode() != this)
452       id= id+'.'+ getRootNode()->getChildName(this);
453     string::size_type debut =id.find_first_of('.');
454     while(debut != std::string::npos){
455         id[debut]='_';
456         debut=id.find_first_of('.',debut);
457     }
458     return id;
459 }
460
461 void Node::setProperty(const std::string& name, const std::string& value)
462 {
463     DEBTRACE("Node::setProperty " << name << " " << value);
464     _propertyMap[name]=value;
465 }
466
467 std::string Node::getProperty(const std::string& name)
468 {
469   std::map<std::string,std::string>::iterator it=_propertyMap.find(name);
470
471   if(it != _propertyMap.end())
472     return it->second;
473   else if(_father)
474     return _father->getProperty(name);
475   else
476     return "";
477 }
478
479 std::map<std::string,std::string> Node::getProperties()
480 {
481   std::map<std::string,std::string> amap=_propertyMap;
482   if(_father)
483     {
484       std::map<std::string,std::string> fatherMap=_father->getProperties();
485       amap.insert(fatherMap.begin(),fatherMap.end());
486     }
487
488   return amap;
489 }
490
491 void Node::setProperties(std::map<std::string,std::string> properties)
492 {
493   _propertyMap.clear();
494   _propertyMap=properties;
495 }
496
497 //! Return the node state in the context of its father
498 /*!
499  * \return the effective node state
500  *
501  * The node state is stored in a private attribute _state.
502  * This state is relative to its father state : a node with a
503  * TOACTIVATE state with a father node in a READY state is not
504  * to activate. Its effective state is only READY.
505  * This method returns the effective state of the node taking
506  * into account that of its father.
507  */
508 YACS::StatesForNode Node::getEffectiveState() const
509 {
510   if(!_father)   //the root node
511     return _state;
512   if(_state==YACS::DISABLED)
513     return YACS::DISABLED;
514   return _father->getEffectiveState(this);
515 }
516
517 //! Return the effective state of a node in the context of this one (its father)
518 /*!
519  * \param node: the node which effective state is queried
520  * \return the effective node state
521  */
522 YACS::StatesForNode Node::getEffectiveState(const Node* node) const
523 {
524   if(node->getState()==YACS::DISABLED)
525     return YACS::DISABLED;
526
527   YACS::StatesForNode effectiveState=getEffectiveState();
528   switch(effectiveState)
529     {
530     case YACS::READY:
531       return YACS::READY;
532     case YACS::TOACTIVATE:
533       return YACS::READY;
534     case YACS::DISABLED:
535       return YACS::DISABLED;
536     case YACS::ERROR:
537       return YACS::FAILED;
538     default:
539       return node->getState();
540     }
541 }
542
543 //! Return the color associated to a state
544 /*!
545  * \param state : the node state
546  * \return the associated color
547  */
548 std::string Node::getColorState(YACS::StatesForNode state) const
549 {
550   switch(state)
551     {
552     case YACS::READY:
553       return "pink";
554     case YACS::TOLOAD:
555       return "magenta";
556     case YACS::LOADED:
557       return "magenta";
558     case YACS::TOACTIVATE:
559       return "purple";
560     case YACS::ACTIVATED:
561       return "blue";
562     case YACS::DONE:
563       return "green";
564     case YACS::ERROR:
565       return "red";
566     case YACS::FAILED:
567       return "orange";
568     case YACS::DISABLED:
569       return "grey";
570     case YACS::PAUSE:
571       return "white";
572     default:
573       return "white";
574     }
575 }
576
577 //! Dump to the input stream a dot representation of the node
578 /*!
579  *  \param os : the input stream
580  */
581 void Node::writeDot(std::ostream &os) const
582 {
583   os << getId() << "[fillcolor=\"" ;
584   YACS::StatesForNode state=getEffectiveState();
585   os << getColorState(state);
586   os << "\" label=\"" << getImplementation() << "Node:" ;
587   os << getQualifiedName() <<"\"];\n";
588 }
589
590 //! same as Node::getName() in most cases, but differs for children of switch
591 /*!
592  *  used by writeDot to distinguish children of switch, by adding a prefix to the name.
593  *  prefix is built on case id.
594  */
595
596 std::string Node::getQualifiedName() const
597 {
598   if(_father)
599     return _father->getMyQualifiedName(this);
600   return getName();
601 }
602
603 //! return node instance identifiant, unique for each node instance 
604 /*!
605  * node instance identifiant is used to check if to nodes pointers refers to the same instance
606  */ 
607 int Node::getNumId()
608 {
609   return _numId;
610 }
611
612 //! Sets the given state for node.
613 /*! It is strongly recommended to use this function if you want to
614  *  change the state of the node, instead of direct access to _state field (_state = ...).
615  */
616 void Node::setState(YACS::StatesForNode theState)
617 {
618   DEBTRACE("Node::setState: " << getName() << " " << theState);
619   _state = theState;
620   // emit notification to all observers registered with the dispatcher on any change of the node's state
621   sendEvent("status");
622 }
623
624 //! emit notification to all observers registered with  the dispatcher 
625 /*!
626  * The dispatcher is unique and can be obtained by getDispatcher()
627  */
628 void Node::sendEvent(const std::string& event)
629 {
630   DEBTRACE("Node::sendEvent " << event);
631   Dispatcher* disp=Dispatcher::getDispatcher();
632   disp->dispatch(this,event);
633 }
634
635 /*!
636  *  For use only when loading a previously saved execution
637  */
638
639 void YACS::ENGINE::StateLoader(Node* node, YACS::StatesForNode state)
640 {
641   node->setState(state);
642 }
643
644 //! indicates if the node is valid (returns 1) or not (returns 0)
645 /*!
646  * This method is useful when editing a schema. It has no meaning in execution.
647  * When a node is edited, its modified method must be called so when isValid is called, its state
648  * is updated (call to edUpdateState) before returning the validity check
649  */
650 int Node::isValid()
651 {
652   if(_modified)
653     edUpdateState();
654   if(_state > YACS::INVALID)
655     return 1;
656   else
657     return 0;
658 }
659
660 //! update the status of the node
661 /*!
662  * Only useful when editing a schema
663  * Do nothing in base Node : to implement in derived classes
664  */
665 void Node::edUpdateState()
666 {
667   DEBTRACE("Node::edUpdateState(): " << _modified);
668   _modified=0;
669 }
670
671 //! returns a string that contains an error report if the node is in error
672 /*!
673  * 
674  */
675 std::string Node::getErrorReport()
676 {
677   if(getState()==YACS::DISABLED)
678     return "<error node= "+getName()+ "state= DISABLED/>\n";
679
680   YACS::StatesForNode effectiveState=getEffectiveState();
681
682   DEBTRACE("Node::getErrorReport: " << getName() << " " << effectiveState << " " << _errorDetails);
683   if(effectiveState != YACS::INVALID &&  effectiveState != YACS::ERROR && 
684      effectiveState != YACS::FAILED && effectiveState != YACS::INTERNALERR)
685     return "";
686
687   std::string report="<error node= " ;
688   report=report + getName() ;
689   switch(effectiveState)
690     {
691     case YACS::INVALID:
692       report=report+" state= INVALID";
693       break;
694     case YACS::ERROR:
695       report=report+" state= ERROR";
696       break;
697     case YACS::FAILED:
698       report=report+" state= FAILED";
699       break;
700     case YACS::INTERNALERR:
701       report=report+" state= INTERNALERR";
702       break;
703     default:
704       break;
705     }
706   report=report + ">\n" ;
707   report=report+_errorDetails;
708   report=report+"\n</error>";
709   return report;
710 }
711
712 //! returns a string that contains the name of the container log file if it exists
713 /*!
714  * Do nothing here. To subclass
715  */
716 std::string Node::getContainerLog()
717 {
718   return "";
719 }
720
721 //! Sets Node in modified state and its father if it exists
722 /*!
723  * 
724  */
725 void Node::modified()
726 {
727   DEBTRACE("Node::modified() " << getName());
728   _modified=1;
729   if(_father)
730     _father->modified();
731 }
732
733 //! Put this node into TOLOAD state when possible
734 /*!
735  * 
736  */
737 void Node::ensureLoading()
738 {
739   if(_state == YACS::READY)
740     setState(YACS::TOLOAD);
741 }
742
743 //! Return the name of a state
744 /*!
745  * 
746  */
747 std::string Node::getStateName(YACS::StatesForNode state)
748 {
749   static NodeStateNameMap nodeStateNameMap;
750   return nodeStateNameMap[state];
751 }
752
753 //! Stop all pending activities of the node
754 /*!
755  * This method should be called when a Proc is finished and must be deleted from the YACS server
756  */
757 void Node::shutdown(int level)
758 {
759   if(level==0)return;
760 }
761
762 //! Clean the node in case of not clean exit
763 /*!
764  * This method should be called on a control-C or sigterm
765  */
766 void Node::cleanNodes()
767 {
768 }
769
770 //! Reset the node state depending on the parameter level
771 void Node::resetState(int level)
772 {
773   DEBTRACE("Node::resetState " << getName() << "," << level << "," << _state);
774   if(_state==YACS::ERROR || _state==YACS::FAILED)
775     {
776       setState(YACS::READY);
777       InGate* inGate = getInGate();
778       std::list<OutGate*> backlinks = inGate->getBackLinks();
779       for (std::list<OutGate*>::iterator io = backlinks.begin(); io != backlinks.end(); io++)
780         {
781           Node* fromNode = (*io)->getNode();
782           if(fromNode->getState() == YACS::DONE)
783             {
784               inGate->setPrecursorDone(*io);
785             }
786         }
787     }
788 }