/// <summary>
 /// Coloca un mensaje en la cola
 /// </summary>
 /// <param name="m">El mensaje a colocar en la cola</param>
 public void put(Message m)
 {
     lock (thisLock)
     {
         queue.Enqueue(m);
     }
 }
 /// <summary>
 /// Atiende una solicitud de envío de parte
 /// </summary>
 public override void attendMessage(Message message)
 {
     if (message.Type == SubProtocol.FileTransfer.Types.FILECOMPLETEMESSAGE)
     {
         State = FileMessageHandlerState.COMPLETED;
         close();
     }
 }
 /// <summary>
 /// Coloca un mensaje en la cola
 /// </summary>
 /// <param name="m">el mensaje a colocar en la cola</param>
 public void put(Message m)
 {
     monitor.Enter();
     queue.Enqueue(m);
     itemCount++;
     monitor.Pulse();
     monitor.Exit();
 }
 /// <summary>
 /// Coloca un mensaje en la cola
 /// </summary>
 /// <param name="m">el mensaje a colocar en la cola</param>
 public void put(Message m)
 {
     lock (this)
     {
         queue.Enqueue(m);
         itemCount++;
         Monitor.Pulse(this);
     }
 }
 /// <summary>
 /// Agrega un mensaje a la lista
 /// </summary>
 /// <param name="message">el mensaje a agregar</param>
 public void add(Message message)
 {
     lock (thisLock)
     {
         //Si no tengo el ip, entonces agrego al usuario como alguien nuevo
         if (!messageCollection.Contains(message.Id))
         {
             messageCollection.Add(message.Id, message);
         }
         //Si ya la tengo, actualizo el objeto usuario
         else
         {
             messageCollection.Remove(message.Id);
             messageCollection.Add(message.Id, message);
         }
     }
 }
 /// <summary>
 /// Procesa un mensaje que no se ha podido enviar
 /// </summary>
 /// <param name="message">El mensaje que no se ha podido enviar</param>
 public void errorMessage(Message message)
 {
     switch (message.Type)
     {
         case SubProtocol.Chat.Types.CHATMESSAGE:
             {
                 ChatMessage textMessage = (ChatMessage)message;
                 controlChatHandler.chatWarninglInformation("No se ha podido entregar el mensage: " + textMessage.Text + ". Al usuario: " + textMessage.TargetNetUser.Name);
                 break;
             }
         case SubProtocol.Chat.Types.GROUPCHATMESSAGE:
             {
                 GroupChatMessage textMessage = (GroupChatMessage)message;
                 controlChatHandler.chatWarninglInformation("No se ha podido enviar el mensage: " + textMessage.Text);
                 break;
             }
     }
 }
 /// <summary>
 /// retorna un array con la lista de mensajes
 /// </summary>
 /// <returns>un array de los mensajes listados</returns>
 public Message[] messageListToArray()
 {
     lock (thisLock)
     {
         Message[] us = new Message[messageCollection.Count];
         IDictionaryEnumerator en = messageCollection.GetEnumerator();
         int i = 0;
         while (en.MoveNext())
         {
             us[i] = (Message)en.Value;
             i++;
         }
         return us;
     }
 }
 /// <summary>
 /// Procesa un mensaje que no se ha podido enviar
 /// </summary>
 /// <param name="message">El mensaje que no se ha podido enviar</param>
 public void errorMessage(Message message)
 {
 }
 /// <summary>
 /// Procesa un mensaje recibido de la red
 /// </summary>
 /// <param name="message">El mensaje recibido</param>
 public void proccessMessage(Message message)
 {
     switch (message.Type)
     {
         case SubProtocol.Ping.Types.PINGMESSAGE:
             {
                 PingMessage pingMessage = (PingMessage)message;
                 PingResponseMessage pingResponseMessage = new PingResponseMessage(pingMessage.Timestamp, message.SenderNetUser);
                 sendMessageEvent(pingResponseMessage);
                 break;
             }
         case SubProtocol.Ping.Types.PINGRESPONSEMESSAGE:
             {
                 PingResponseMessage pingResponseMessage = (PingResponseMessage)message;
                 controlPingHandler.pingResponseMessageReceived(pingResponseMessage.SenderNetUser, (new TimeSpan(DateTime.Now.Ticks - pingResponseMessage.Timestamp)).TotalMilliseconds);
                 break;
             }
     }
 }
 /// <summary>
 /// Ejecuta un paso en la iteración del transmisor
 /// </summary>
 public abstract void attendMessage(Message message);
 /// <summary>
 /// Envía un mensaje de petición de partes del archivo al usuario remoto y marca el estado del manejador como esperando
 /// </summary>
 public override void attendMessage(Message message)
 {
     if (message.Type == SubProtocol.FileTransfer.Types.FILEPARTMESSAGE)
     {
         FilePartMessage filePartMessage = (FilePartMessage)message;
         receivePartMessage(filePartMessage);
     }
 }
 /// <summary>
 /// Se gatilla cuando se recibe un mensaje
 /// </summary>
 /// <param name="message">El mensaje recibido</param>
 public void proccessMessage(Message message)
 {
     switch (message.Type)
     {
         case SubProtocol.FileTransfer.Types.FILEREQUESTMESSAGE:
             {
                 FileRequestMessage fileRequestMessage = (FileRequestMessage)message;
                 FileInformation fileInformation = fileData.FileList.getFileInformation(fileRequestMessage.FileId);
                 if (fileInformation != null)
                 {
                     FileMessageSender fileMessageSender = new FileMessageSender(fileRequestMessage.SenderNetUser, fileRequestMessage.FileHandlerId, sendMessageDelegate, fileInformation, fileData);
                     lock (fileMessageHandlerLock)
                     {
                         if (!activeUploads.Contains(fileMessageSender.Id))
                         {
                             if (fileMessageUploadQueue.put(fileMessageSender))
                             {
                                 controlFileHandler.uploadFileQueued(fileRequestMessage.SenderNetUser, fileMessageSender.Id.ToString(), fileInformation.Name);
                             }
                         }
                     }
                 }
                 else
                 {
                     FileErrorMessage fileErrorMessage = new FileErrorMessage(fileRequestMessage.SenderNetUser, fileRequestMessage.FileHandlerId);
                     sendMessageEvent(fileErrorMessage);
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEERRORMESSAGES:
             {
                 FileErrorMessage fileErrorMessage = (FileErrorMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeUploads.Contains(fileErrorMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeUploads[fileErrorMessage.FileHandlerId];
                         controlFileHandler.uploadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeUploads.Remove(fileMessageHandler.Id);
                     }
                     else if (activeDownloads.Contains(fileErrorMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeDownloads[fileErrorMessage.FileHandlerId];
                         controlFileHandler.downloadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeDownloads.Remove(fileMessageHandler.Id);
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEPARTMESSAGE:
             {
                 FilePartMessage filePartMessage = (FilePartMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeDownloads.Contains(filePartMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeDownloads[filePartMessage.FileHandlerId];
                         fileMessageHandler.attendMessage(filePartMessage);
                         fileMessageHandler.waitUp(fileData.FileRiseUp);
                     }
                     else
                     {
                         FileErrorMessage fileErrorMessage = new FileErrorMessage(filePartMessage.SenderNetUser, filePartMessage.FileHandlerId);
                         sendMessageEvent(fileErrorMessage);
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEWAITMESSAGE:
             {
                 FileWaitMessage fileWaitMessage = (FileWaitMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeDownloads.Contains(fileWaitMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeDownloads[fileWaitMessage.FileHandlerId];
                         fileMessageHandler.waitUp(fileData.FileRiseUp);
                     }
                     else if (activeUploads.Contains(fileWaitMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeUploads[fileWaitMessage.FileHandlerId];
                         fileMessageHandler.waitUp(fileData.FileRiseUp);
                     }
                     else
                     {
                         if (!fileMessageDownloadQueue.contains(fileWaitMessage.FileHandlerId) && !fileMessageUploadQueue.contains(fileWaitMessage.FileHandlerId))
                         {
                             FileErrorMessage fileErrorMessage = new FileErrorMessage(fileWaitMessage.SenderNetUser, fileWaitMessage.FileHandlerId);
                             sendMessageEvent(fileErrorMessage);
                         }
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILECOMPLETEMESSAGE:
             {
                 FileCompleteMessage fileCompleteMessage = (FileCompleteMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeUploads.Contains(fileCompleteMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeUploads[fileCompleteMessage.FileHandlerId];
                         fileMessageHandler.State = FileMessageHandlerState.COMPLETED;
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILELISTREQUESTMESSAGE:
             {
                 FileListRequestMessage fileListRequestMessage = (FileListRequestMessage)message;
                 FileListMessage fileListMessage = new FileListMessage(fileListRequestMessage.SenderNetUser, fileData.FileList);
                 sendMessageEvent(fileListMessage);
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILELISTMESSAGE:
             {
                 FileListMessage fileListMessage = (FileListMessage)message;
                 controlFileListHandler.addFileList(fileListMessage.SenderNetUser, fileListMessage.FileList);
                 break;
             }
     }
 }
Ejemplo n.º 13
0
 /// <summary>
 /// Encola un mensaje a la lista de mensajes para enviar
 /// </summary>
 /// <param name="message">El mensaje a enviar</param>
 public void queueMessageToSend(Message message)
 {
     if (message.MetaType == MessageMetaType.MULTICAST)
     {
         messageIdCollection.add(message.Id);
         nMessagesCounted++;
         send(message);
     }
     else if (message.MetaType == MessageMetaType.SAFEMULTICAST)
     {
         messageIdCollection.add(message.Id);
         nMessagesCounted++;
         notSentMessageQueue.put(message);
     }
     else if (message.MetaType == MessageMetaType.FASTUNICAST)
     {
         send(message);
     }
     else
     {
         notSentMessageQueue.put(message);
     }
 }
 /// <summary>
 /// Procesa un mensaje recibido de la red
 /// </summary>
 /// <param name="message">El mensaje recibido</param>
 public void proccessMessage(Message message)
 {
     switch (message.Type)
     {
         case SubProtocol.Chat.Types.CHATMESSAGE:
             {
                 ChatMessage textMessage = (ChatMessage)message;
                 controlChatHandler.chatMessageReceived(textMessage.SenderNetUser, textMessage.Text);
                 break;
             }
         case SubProtocol.Chat.Types.GROUPCHATMESSAGE:
             {
                 GroupChatMessage textMessage = (GroupChatMessage)message;
                 controlChatHandler.groupChatMessageReceived(textMessage.SenderNetUser, textMessage.Text);
                 break;
             }
     }
 }
 /// <summary>
 /// Procesa un mensaje que no ha podido ser envíado correctamente
 /// </summary>
 /// <param name="message">El mensaje</param>
 internal void routerMessageErrorHandler(Message message)
 {
     //ImAliveMessage
     if (message.Type == MessageType.IMALIVE)
     {
         ImAliveMessage imAliveMessage = (ImAliveMessage)message;
         produceEvent(CommunicationEvent.NETINFORMATION, "ROUTER: message delivery fail " + imAliveMessage.ToString());
     }
     //AckMessage
     else if (message.Type == MessageType.ACK)
     {
         AckMessage ackMessage = (AckMessage)message;
         produceEvent(CommunicationEvent.NETINFORMATION, "ROUTER: message delivery fail " + ackMessage.ToString());
     }
     //Resto de los mensajes
     else
     {
         SubProtocolI subProtocol = (SubProtocolI)subProtocols[message.ProtocolType];
         if (subProtocol != null)
         {
             eventQueuePC.put(new Event(new MessageEvent(subProtocol.errorMessage), message));
         }
         else
         {
             produceEvent(CommunicationEvent.ERRORMESSAGE, message);
         }
         produceEvent(CommunicationEvent.NETINFORMATION, "ROUTER: message delivery fail " + message.ToString());
     }
 }
 /// <summary>
 /// Envía un mensaje de forma interna aplicando filtros
 /// </summary>
 /// <param name="message">el mensaje a enviar</param>
 internal void internalSendMessage(Message message)
 {
     try
     {
         if (configuration.NetUser.Ip != null)
         {
             message.SenderNetUser = configuration.NetUser;
             router.queueMessageToSend(message);
             if (message.Type != MessageType.IMALIVE && message.Type != MessageType.ACK)
             {
                 produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION: queued " + message.ToString());
             }
         }
     }
     catch (ThreadAbortException e)
     {
         throw e;
     }
     catch (Exception e)
     {
         produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION: impossible to send this message " + e.Message);
     }
 }
 /// <summary>
 /// Envía un mensaje a la MANET
 /// </summary>
 /// <param name="message">el mensaje a enviar</param>
 public void send(Message message)
 {
     //ImAliveMessage
     if (message.Type == MessageType.IMALIVE)
     {
         ImAliveMessage imAliveMessage = (ImAliveMessage)message;
         produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION WARNING: reserved kind of message " + imAliveMessage.ToString());
     }
     //AckMessage
     else if (message.Type == MessageType.ACK)
     {
         AckMessage ackMessage = (AckMessage)message;
         produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION WARNING: reserved kind of message " + ackMessage.ToString());
     }
     //Resto de los mensajes
     else
     {
         internalSendMessage(message);
     }
 }
 /// <summary>
 /// Se gatilla cuando se quiere envíar un mensaje
 /// Este parametro es para asignar a los handlers
 /// </summary>
 /// <param name="message">El mensaje a enviar</param>
 internal void sendMessageDelegate(Message message)
 {
     sendMessageEvent(message);
 }
Ejemplo n.º 19
0
 /// <summary>
 /// Identifica el tipo de mensaje a enviar y lo envia mediante un netHandler
 /// </summary>
 /// <param name="message">El mensaje e enviar</param>
 private void send(Message message)
 {
     if (message.MetaType == MessageMetaType.MULTICAST)
     {
         MulticastMessage multicastMessage = (MulticastMessage)message;
         multicastMessage.send(netHandler);
     }
     else if (message.MetaType == MessageMetaType.SAFEMULTICAST)
     {
         SafeMulticastMessage safeMulticastMessage = (SafeMulticastMessage)message;
         safeMulticastMessage.send(netHandler);
     }
     else if (message.MetaType == MessageMetaType.UNICAST)
     {
         UnicastMessage unicastMessage = (UnicastMessage)message;
         IPAddress ip = pathNextIp(unicastMessage.TargetNetUser);
         NetUser listedNetUSer = netUserList.getUser(unicastMessage.TargetNetUser.Ip);
         if (ip != null)
         {
             if (!unicastMessage.send(netHandler, ip))
             {
                 unicastMessage.FailReason = MessageFailReason.TCPFAIL;
                 failedMessageQueue.put(unicastMessage);
             }
         }
         else if (listedNetUSer != null && listedNetUSer.Id.Equals(unicastMessage.TargetNetUser.Id))
         {
             unicastMessage.FailReason = MessageFailReason.NOTROUTEBUTHOSTONNET;
             failedMessageQueue.put(unicastMessage);
         }
         else
         {
             unicastMessage.FailReason = MessageFailReason.NOTROUTETOHOST;
             failedMessageQueue.put(unicastMessage);
         }
     }
     else if (message.MetaType == MessageMetaType.SAFEUNICAST)
     {
         SafeUnicastMessage safeMessage = (SafeUnicastMessage)message;
         IPAddress ip = pathNextIp(safeMessage.TargetNetUser);
         NetUser listedNetUser = netUserList.getUser(safeMessage.TargetNetUser.Ip);
         if (ip != null)
         {
             if (safeMessage.send(netHandler, ip))
             {
                 //statics
                 nMessagesSent++;
                 //si el mensaje enviado fue de este usuario se espera confirmación
                 if (safeMessage.SenderNetUser.Id.Equals(netUser.Id))
                 {
                     safeMessage.WaitTimeOut = waitForAck + 2 * safeMessage.TargetNetUser.JumpsAway;
                     notConfirmedMessageList.add(safeMessage);
                 }
             }
             else
             {
                 //statics
                 nMessagesFailed++;
                 safeMessage.FailReason = MessageFailReason.TCPFAIL;
                 failedMessageQueue.put(safeMessage);
             }
         }
         else if (listedNetUser != null && listedNetUser.Id.Equals(safeMessage.TargetNetUser.Id))
         {
             safeMessage.FailReason = MessageFailReason.NOTROUTEBUTHOSTONNET;
             failedMessageQueue.put(safeMessage);
         }
         else
         {
             safeMessage.FailReason = MessageFailReason.DESTROY;
             failedMessageQueue.put(safeMessage);
         }
     }
     else if (message.MetaType == MessageMetaType.FASTUNICAST)
     {
         FastUnicastMessage fastUnicastMessage = (FastUnicastMessage)message;
         IPAddress ip = pathNextIp(fastUnicastMessage.TargetNetUser);
         NetUser listedNetUSer = netUserList.getUser(fastUnicastMessage.TargetNetUser.Ip);
         if (ip != null)
         {
             fastUnicastMessage.send(netHandler, ip);
         }
     }
 }
        /// <summary>
        /// Procesa un mensaje recibido
        /// </summary>
        /// <param name="message">El mensaje recibido</param>
        private void proccessMessage(Message message)
        {
            if (message != null)
            {
                //registra la ip del mensaje recibido en el manejador de Ips, previo chequeo de que el mensaje no es de
                //este mismo usuario
                if (!message.SenderNetUser.Id.Equals(configuration.NetUser.Id))
                {
                    netHandler.registerIp(message.SenderNetUser.Ip);
                }

                //verifica que se recibe el mensaje de un usuario conocido
                NetUser listedNetUser = netUserList.getUser(message.SenderNetUser.Ip);

                //ImAliveMessage
                if (message.Type == MessageType.IMALIVE)
                {
                    if (listedNetUser == null)
                    {
                        newNetUser(message.SenderNetUser);
                    }
                    else if (!listedNetUser.Id.Equals(message.SenderNetUser.Id))
                    {
                        disconnectNetUser(listedNetUser);
                    }
                    else
                    {
                        listedNetUser.qualityUp(configuration.NetData);
                        listedNetUser.NeighborhoodIds = message.SenderNetUser.NeighborhoodIds;
                        listedNetUser.JumpsAway = message.Jumps;
                        listedNetUser.State = message.SenderNetUser.State;
                        listedNetUser.UpLayerData = message.SenderNetUser.UpLayerData;
                    }
                    //produceEvent(CommunicationEvent.NETINFORMATION, "recibido: " + subProtocol.ToString());
                }
                //AckMessage
                else if (message.Type == MessageType.ACK)
                {
                    AckMessage ackMessage = (AckMessage)message;
                    router.proccessAckMessage(ackMessage);
                    //produceEvent(CommunicationEvent.NETINFORMATION, "recibidos: " + subProtocol.ToString());
                }
                //Resto de los mensajes
                else
                {
                    if (listedNetUser != null && listedNetUser.Id.Equals(message.SenderNetUser.Id))
                    {
                        message.SenderNetUser = listedNetUser;
                        SubProtocolI subProtocol = (SubProtocolI)subProtocols[message.ProtocolType];
                        if (subProtocol != null)
                        {
                            eventQueuePC.put(new Event(new MessageEvent(subProtocol.proccessMessage), message));
                        }
                        else
                        {
                            produceEvent(CommunicationEvent.PROCESSMESSAGE, message);
                        }
                        produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION: received " + message.ToString());
                    }
                    else
                    {
                        message.SenderNetUser.Name = "Unknown (" + message.SenderNetUser.Ip.ToString() + ")";
                        SubProtocolI subProtocol = (SubProtocolI)subProtocols[message.ProtocolType];
                        if (subProtocol != null)
                        {
                            eventQueuePC.put(new Event(new MessageEvent(subProtocol.proccessMessage), message));
                        }
                        else
                        {
                            produceEvent(CommunicationEvent.PROCESSMESSAGE, message);
                        }
                        produceEvent(CommunicationEvent.NETINFORMATION, "COMMUNICATION: unknown message  " + message.ToString());
                    }
                }
            }
        }
 /// <summary>
 /// Se gatilla cuando no es posible entregar un mensaje
 /// </summary>
 /// <param name="message">El mensaje no entregado</param>
 public void errorMessage(Message message)
 {
     switch (message.Type)
     {
         case SubProtocol.FileTransfer.Types.FILEREQUESTMESSAGE:
             {
                 FileRequestMessage fileRequestMessage = (FileRequestMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeDownloads.Contains(fileRequestMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeDownloads[fileRequestMessage.FileHandlerId];
                         controlFileHandler.downloadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeDownloads.Remove(fileMessageHandler.Id);
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEERRORMESSAGES:
             {
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEPARTMESSAGE:
             {
                 FilePartMessage filePartMessage = (FilePartMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeUploads.Contains(filePartMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeUploads[filePartMessage.FileHandlerId];
                         controlFileHandler.uploadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeUploads.Remove(fileMessageHandler.Id);
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILEWAITMESSAGE:
             {
                 FileWaitMessage fileWaitMessage = (FileWaitMessage)message;
                 lock (fileMessageHandlerLock)
                 {
                     if (activeDownloads.Contains(fileWaitMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeDownloads[fileWaitMessage.FileHandlerId];
                         controlFileHandler.downloadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeDownloads.Remove(fileMessageHandler.Id);
                     }
                     else if (activeUploads.Contains(fileWaitMessage.FileHandlerId))
                     {
                         FileMessageHandler fileMessageHandler = (FileMessageHandler)activeUploads[fileWaitMessage.FileHandlerId];
                         controlFileHandler.uploadFileFailed(fileMessageHandler.Id.ToString());
                         fileMessageHandler.close();
                         activeUploads.Remove(fileMessageHandler.Id);
                     }
                 }
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILECOMPLETEMESSAGE:
             {
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILELISTREQUESTMESSAGE:
             {
                 break;
             }
         case SubProtocol.FileTransfer.Types.FILELISTMESSAGE:
             {
                 break;
             }
     }
 }