Bressam

ServidorPersistencia - Servidor

Jun 28th, 2018
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 22.46 KB | None | 0 0
  1. /*
  2.  * To change this license header, choose License Headers in Project Properties.
  3.  * To change this template file, choose Tools | Templates
  4.  * and open the template in the editor.
  5.  */
  6. package servidorpersistencia;
  7.  
  8. import java.io.BufferedReader;
  9. import java.io.BufferedWriter;
  10. import java.io.File;
  11. import java.io.FileNotFoundException;
  12. import java.io.FileReader;
  13. import java.io.FileWriter;
  14. import java.io.IOException;
  15. import java.net.DatagramPacket;
  16. import java.net.InetAddress;
  17. import java.net.MulticastSocket;
  18. import java.net.SocketTimeoutException;
  19. import java.nio.charset.Charset;
  20. import java.nio.file.Files;
  21. import java.nio.file.Paths;
  22. import java.nio.file.StandardOpenOption;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.List;
  26. import java.util.Random;
  27. import java.util.Scanner;
  28. import java.util.logging.Level;
  29. import java.util.logging.Logger;
  30. import sun.nio.cs.StandardCharsets;
  31.  
  32. /**
  33.  *
  34.  * @author giovanne.gaspareto
  35.  */
  36. public class ServidorPersistencia {
  37.  
  38.     static InetAddress              grupo;
  39.     static MulticastSocket          s;
  40.     static DatagramPacket           msg;
  41.     static DatagramPacket           recv;
  42.     private static Scanner          keyboard;
  43.    
  44.     static final String         GROUP_IP = "224.0.0.1";
  45.     static final int            MULTICAST_PORT = 2000;
  46.     static int                  IDProcesso = 0;
  47.     static String               mensagem = "";
  48.     static int                  numeroProcessosGrupo = 0;
  49.     static int                  tempoRandom = 0;
  50.     static int                  idProcessoNaRegiaoCritica = 0;
  51.     static int                  idRecebido = 0;
  52.     static int                  proximo =   0;
  53.    
  54.    
  55.    
  56.     //VARIAVEIS Para leitura de arquivo
  57.     static File             baseDados;
  58.     static int              contadorInsercoes;
  59.     static final String     DIVISOR_STRING_BD = "//";
  60.     static String[]         partesBD;
  61.     static char             indiceBD;
  62.     static String           mensagemBD;
  63.     static String           prefixoBD;
  64.     static boolean          idEncontradoBD;
  65.     static int              idCliente;
  66.     static String           mensagemCliente;
  67.     static  String          tipoRequisicao = "";
  68.     static int              idDestino;
  69.     static int              k = 0;
  70.     static boolean          escolhaBusca = false;
  71.     static int              idBuscaCliente = 0;
  72.     //Constantes leitura de arquivo
  73.     static final String     ERRO_ID_NAO_EXISTE = "Erro, ID nao registrado na base de dados";
  74.  
  75.    
  76.     //Variaveis para controle da area critica
  77.     static List<Integer>            listaEsperaRegiaoCritica = new ArrayList<Integer>();  //lista com IDs dos processos aguardando pela regiao critica
  78.     static boolean                  executarProcesso = true;
  79.     static boolean                  aguardaPermissaoRegiaoCritica = false;
  80.     static boolean                  regiaoCritica = false;
  81.     static boolean                  aguardarResposta = false;
  82.    
  83.     // Constantes para comunicacao de processos
  84.     static final String         OK_STRING = "ok";
  85.     static final String         DESCONECTAR_STRING = "q";
  86.     static final String         DIVISOR_STRING = ";";
  87.     static final String         GRUPO_COMPLETO_STRING = "gc";
  88.     static final String         EXISTE_PROCESSO_REGIAO_CRITICA = "regiao critica";
  89.     static final String         REQUISICAO_REG_CRITICA_STRING  =   "rc";
  90.     static final String         SAINDO_REGIAO_CRITICA_STRING  = "sc";
  91.     static final String         ENTROU_REGIAO_CRITICA_STRING = "ec";
  92.     static final String         CLIENTE_M= "M";
  93.     static final String         BUSCA = "b";
  94.     static final String         INSERCAO = "i";
  95.     static final String         VERIFICA_MEMBROS_VIVOS = "ver";
  96.     static final String         RESPOSTA = "resposta";
  97.  
  98.    
  99.     static final int            NOVO_MEMBRO = 1;
  100.     static final int            GRUPO_COMPLETO = 2;
  101.     static final int            REQUISICAO_REG_CRITICA = 3;
  102.     static final int            SAINDO_REGIAO_CRITICA = 4;
  103.     static final int            ENTROU_REGIAO_CRITICA = 5;
  104.     static final int            REQUISICAO_CLIENTE = 6;
  105.     static final int            REQUISICAO_INSERCAO_CLIENTE = 7;
  106.     static final int            REQUISICAO_BUSCA_CLIENTE = 8;
  107.     static final int            VERIFICACAO_MEMBROS = 9;
  108.     static final int            TIMEOUT = 2;
  109.  
  110.    
  111.    
  112.     public static void lerArquivo(){
  113.         String linhaAtual;
  114.         FileReader fr;
  115.         try {
  116.             fr = new FileReader(baseDados);
  117.             BufferedReader reader = new BufferedReader(fr);
  118.             partesBD = new String[2];
  119.             while((linhaAtual = reader.readLine())!= null){
  120.                 partesBD = linhaAtual.split(DIVISOR_STRING_BD);
  121.                 prefixoBD = partesBD[0];
  122.                 indiceBD = prefixoBD.charAt(1);
  123.                 mensagemBD = partesBD[1];
  124.                 System.out.println("Prefixo lido: " + prefixoBD + " com indice: " + indiceBD + " Mensagem: " + mensagemBD);
  125.             }
  126.             reader.close();
  127.         }   catch (IOException ex) {
  128.                 Logger.getLogger(ServidorPersistencia.class.getName()).log(Level.SEVERE, null, ex);
  129.             }
  130.     }
  131.        
  132.     public static void escreveArquivo(String texto){    
  133.         contadorInsercoes++;
  134.         String aux = "M"+contadorInsercoes+ DIVISOR_STRING_BD + texto;
  135.         texto = aux;
  136.         BufferedWriter writer = null;
  137.             try {
  138.                 //Criando arquivo no diretorio do projeto
  139.                 // Imprime o diretorio do arquivo criado
  140.                 System.out.println(baseDados.getCanonicalPath());
  141.                 //Escreve no arquivo
  142.                writer = new BufferedWriter(new FileWriter(baseDados,true)); //true=adiciona ao fim do arquivo
  143.                writer.write(texto);
  144.                writer.newLine();
  145.             } catch (IOException e) {
  146.             } finally {
  147.                 try {
  148.                     // Close the writer regardless of what happens...
  149.                     writer.close();
  150.                 } catch (IOException e) {
  151.                 }
  152.             }
  153.     }  
  154.    
  155.     public static void inserir(String texto, int k){
  156.         //contadorInsercoes++;
  157.         String aux = k + DIVISOR_STRING + "M"+ idCliente + DIVISOR_STRING_BD + texto;
  158.         texto = aux;
  159.         BufferedWriter writer = null;
  160.             try {
  161.                 //Criando arquivo no diretorio do projeto
  162.                 // Imprime o diretorio do arquivo criado
  163.               //  System.out.println(baseDados.getCanonicalPath());
  164.                 //Escreve no arquivo
  165.                writer = new BufferedWriter(new FileWriter(baseDados,true)); //true=adiciona ao fim do arquivo
  166.                writer.write(texto);
  167.                writer.newLine();
  168.             } catch (IOException e) {
  169.             } finally {
  170.                 try {
  171.                     // Close the writer regardless of what happens...
  172.                     writer.close();
  173.                 } catch (IOException e) {
  174.                 }
  175.             }
  176.     }
  177.    
  178.     public static String buscaBD(int indiceBusca){
  179.         String linhaAtual;
  180.         FileReader fr;
  181.         try {
  182.             idEncontradoBD = false;
  183.             fr = new FileReader(baseDados);
  184.             BufferedReader reader = new BufferedReader(fr);
  185.             partesBD = new String[2];
  186.             String[] linhaInteira = new String[2];
  187.             while((linhaAtual = reader.readLine())!= null && !idEncontradoBD ){
  188.                 linhaInteira = linhaAtual.split(DIVISOR_STRING);
  189.                 indiceBD = linhaInteira[0].charAt(0);
  190.                 partesBD = linhaInteira[1].split(DIVISOR_STRING_BD);
  191.                 prefixoBD = partesBD[0];
  192.                 //indiceBD = prefixoBD.charAt(1);
  193.                 if(indiceBusca == Character.getNumericValue(indiceBD))
  194.                 { //encontrou id fornecido, salva mensagem
  195.                     mensagemBD = partesBD[1];
  196.                     idEncontradoBD = true;
  197.                 }
  198.                 else{
  199.                     mensagemBD = ERRO_ID_NAO_EXISTE;
  200.                 }
  201.                // mensagemBD = partesBD[1];
  202.                 //System.out.println("Prefixo lido: " + prefixoBD + " com indice: " + indiceBD + " Mensagem: " + mensagemBD);
  203.             }
  204.             reader.close();
  205.         }   catch (IOException ex) {
  206.                 Logger.getLogger(ServidorPersistencia.class.getName()).log(Level.SEVERE, null, ex);
  207.             }
  208.         return mensagemBD;
  209.     }
  210.    
  211.     public static String receiveMessageGroup(){
  212.         byte[] buf=new byte[1000];
  213.         recv=new DatagramPacket(buf, buf.length);
  214.         try {
  215.             s.setSoTimeout(10);
  216.             s.setLoopbackMode(false);
  217.            // System.out.println("timeout no recevei sem nada: " + s.getSoTimeout());
  218.             s.receive(recv);
  219.             byte[] dest=new byte[recv.getLength()];
  220.             System.arraycopy(recv.getData(), 0, dest, 0, dest.length);
  221.             String mensagemRecebida = new String(recv.getData(),recv.getOffset(),recv.getLength());
  222.             System.err.println("Mensagen recebida: "+ mensagemRecebida);
  223.             return mensagemRecebida;
  224.         } catch (SocketTimeoutException ex) {
  225.           // Logger.getLogger(ProcessoSender.class.getName()).log(Level.SEVERE, null, ex);
  226.             return "x;0;0";
  227.         } catch (IOException e) {
  228.             // TODO Auto-generated catch block
  229.             e.printStackTrace();
  230.             return "x;0;0";
  231.         }
  232.     }
  233.    
  234.     public static void sendMessageGroup(String msgStr) {
  235.         msg=new DatagramPacket(msgStr.getBytes(), msgStr.length(),grupo, 2000);
  236.        // System.out.println("Enviando mensagem para o grupo ...");
  237.         try {
  238.             s.send(msg);
  239.         } catch (IOException e) {
  240.             // TODO Auto-generated catch block
  241.             e.printStackTrace();
  242.         }
  243.     }
  244.    
  245.     public static int geraRandomEntre(int min, int max){
  246.         Random rn = new Random();
  247.         return rn.nextInt(max-min+1)+min;
  248.     }
  249.    
  250.     public static void defineIDProcesso(){
  251.         keyboard = new Scanner(System.in);
  252.         System.out.println("Informe o ID deste processo: ");
  253.         IDProcesso = keyboard.nextInt();
  254.     }
  255.    
  256.     public static void setUp(){
  257.        baseDados = new File(".//","baseDados.txt");
  258.        try {
  259.             //System.setProperty("java.net.preferIPv4Stack", "true"); // somente pra mac no wifi
  260.             grupo=InetAddress.getByName(GROUP_IP);
  261.             s=new MulticastSocket(MULTICAST_PORT);
  262.             System.err.println("Entrando no grupo ...");
  263.             s.joinGroup(grupo);
  264.             System.err.println("Ok.");
  265.         } catch (IOException e) {
  266.             // TODO Auto-generated catch block
  267.             e.printStackTrace();
  268.         }
  269.     }
  270.    
  271.     public static int trataMensagem(String mensagemRecebida, DatagramPacket PacketLider){
  272.         String[] partes = mensagemRecebida.split(DIVISOR_STRING);
  273.         String operacao = partes[0];
  274.         String mensagem = partes[1];
  275.         idRecebido = Integer.parseInt(mensagem);
  276.        
  277.         if(operacao.equals(VERIFICA_MEMBROS_VIVOS)){
  278.             idRecebido = Integer.parseInt(mensagem);
  279.             return  VERIFICACAO_MEMBROS;
  280.         }else if(operacao.equals(CLIENTE_M)){
  281.                 idCliente = Integer.parseInt(mensagem);
  282.                 idRecebido = Integer.parseInt(mensagem);
  283.                 tipoRequisicao = partes[2];
  284.                 if(tipoRequisicao.equals(BUSCA) && (idRecebido == IDProcesso)){
  285.                     escolhaBusca = true;
  286.                     idBuscaCliente = Integer.parseInt(partes[3]);
  287.                     return REQUISICAO_BUSCA_CLIENTE;
  288.                 }
  289.                 else if (tipoRequisicao.equals(INSERCAO)&& (idRecebido == IDProcesso)){
  290.                     escolhaBusca = false;
  291.                     mensagemCliente = partes[3];
  292.                     return REQUISICAO_INSERCAO_CLIENTE;
  293.                 }
  294.         }else if(operacao.equals(OK_STRING)){
  295.             idRecebido = Integer.parseInt(mensagem);
  296.             idDestino = Integer.parseInt(partes[2]);
  297.             if(idRecebido != IDProcesso && ((idDestino == IDProcesso) || (idDestino == 0))){
  298.               return  NOVO_MEMBRO;
  299.             }
  300.         }
  301.         else if(operacao.equals(GRUPO_COMPLETO_STRING)){
  302.             return GRUPO_COMPLETO;
  303.         }else if(operacao.equals(REQUISICAO_REG_CRITICA_STRING)){
  304.             return REQUISICAO_REG_CRITICA;
  305.         }else if(operacao.equals(SAINDO_REGIAO_CRITICA_STRING)){
  306.             idProcessoNaRegiaoCritica = 0;
  307.             return SAINDO_REGIAO_CRITICA;
  308.         }else if(operacao.equals(ENTROU_REGIAO_CRITICA_STRING)){
  309.             idProcessoNaRegiaoCritica = Integer.parseInt(mensagem);
  310.             return ENTROU_REGIAO_CRITICA;
  311.         }
  312.         return -1;
  313.     }
  314.    
  315.     public static void lerGrupoTrataMensagem(){
  316.         switch(trataMensagem(receiveMessageGroup(),recv)){//só cria timer apos estar de fato atuando na regiao critica, so acontecera quando receber um ok que o processo anterior liberou ela
  317.             //ler mensagens ate receber ok que a regiao critica esta livre
  318.             case REQUISICAO_REG_CRITICA:
  319.                 //Recebeu uma requisicao de regiao critica, todos atualizam a lista de espera
  320.                 listaEsperaRegiaoCritica.add(idRecebido);
  321.                 proximo = listaEsperaRegiaoCritica.get(0);
  322.                 if(proximo == IDProcesso && idProcessoNaRegiaoCritica == 0){ //é o primeiro da fila e nao ha nenhum processo na regiao critica
  323.                    
  324.                     aguardaPermissaoRegiaoCritica = false;
  325.                     regiaoCritica = true;
  326.                 }
  327.                 break;
  328.  
  329.             case SAINDO_REGIAO_CRITICA:
  330.                 //verificar se seu proprio ID é o proximo a entrar na regiao critica, se for, envia que entrou e inicia timer, senao salva o id de quem entrou
  331.                 proximo = listaEsperaRegiaoCritica.get(0);
  332.                 System.out.println("[Processo "+ IDProcesso + "] Proximo da lista: " + proximo);
  333.                 if(proximo == IDProcesso){
  334.                      aguardaPermissaoRegiaoCritica = false; //este processo é o proximo da fila, sai do loop de espera espera
  335.                     //sendMessageGroup(ENTROU_REGIAO_CRITICA_STRING + DIVISOR_STRING + IDProcesso); //avisa outros processos que esta entrando
  336.                     regiaoCritica = true;
  337.                 }
  338.                 break;
  339.  
  340.             case ENTROU_REGIAO_CRITICA:
  341.                 //se idProcesso = idProcessoNaRegiaoCritica, significa que pode entrar. aguardaPermissaoRegiaoCritica = false, regiaoCritica = true
  342.                 // remove ID do processo que entrou da lista de espera
  343.                 listaEsperaRegiaoCritica.remove(0);
  344.                 break;  
  345.             case VERIFICACAO_MEMBROS:
  346.                 mensagem = OK_STRING+DIVISOR_STRING+IDProcesso+DIVISOR_STRING + idRecebido;
  347.                 sendMessageGroup(mensagem);
  348.                 break;
  349.         }
  350.     }
  351.    
  352.    public static void lerGrupoRegiaoNaoCritica(){
  353.                     switch(trataMensagem(receiveMessageGroup(),recv)){//só cria timer apos estar de fato atuando na regiao critica, so acontecera quando receber um ok que o processo anterior liberou ela
  354.                     //ler mensagens ate receber ok que a regiao critica esta livre
  355.                     case REQUISICAO_REG_CRITICA:
  356.                         //Recebeu uma requisicao de regiao critica, todos atualizam a lista de espera
  357.                         listaEsperaRegiaoCritica.add(idRecebido);
  358.                         break;
  359.                     case ENTROU_REGIAO_CRITICA:
  360.                         //se idProcesso = idProcessoNaRegiaoCritica, significa que pode entrar. aguardaPermissaoRegiaoCritica = false, regiaoCritica = true
  361.                         // remove ID do processo que entrou da lista de espera
  362.                        // if(IDProcesso != idRecebido){ //somente se for outro processo lendo que alguem entrou. Se for o proprio processo, ele ja se removeu da lista ao avaliar que era o proximo
  363.                          listaEsperaRegiaoCritica.remove(0);
  364.                          break;
  365.                     case REQUISICAO_INSERCAO_CLIENTE:
  366.                         aguardaPermissaoRegiaoCritica = true;
  367.                         break;
  368.                     case REQUISICAO_BUSCA_CLIENTE:
  369.                         aguardaPermissaoRegiaoCritica = true;
  370.                         break;  
  371.                     case VERIFICACAO_MEMBROS:
  372.                         mensagem = OK_STRING+DIVISOR_STRING+IDProcesso+DIVISOR_STRING + idRecebido;
  373.                         sendMessageGroup(mensagem);
  374.                         break;
  375.                     default:
  376.                        
  377.                         break;  
  378.                 }
  379.     }
  380.    
  381.    public static void aguardaMembrosDoGrupo(){
  382.     numeroProcessosGrupo = 1;
  383.     while(numeroProcessosGrupo<3){
  384.             switch(trataMensagem(receiveMessageGroup(),recv)){
  385.                 case NOVO_MEMBRO:
  386.                     numeroProcessosGrupo ++;
  387.                     System.out.println("Novo processo Conectado ao Grupo!");
  388.                     break;
  389.                
  390.                 case GRUPO_COMPLETO:
  391.                     numeroProcessosGrupo = 3;
  392.                     break;
  393.                 case VERIFICACAO_MEMBROS:
  394.                     mensagem = OK_STRING+DIVISOR_STRING+IDProcesso+DIVISOR_STRING + idRecebido;
  395.                     sendMessageGroup(mensagem);
  396.                     break;
  397.                 default:
  398.                   //  System.out.println("Case Default da espera grupo");
  399.                     break;
  400.             }
  401.         }
  402.         sendMessageGroup(GRUPO_COMPLETO_STRING+DIVISOR_STRING+IDProcesso);
  403.    }
  404.    
  405.    public static void testaBuscaID(){
  406.         for(int i=0;i<4;i++){
  407.            escreveArquivo("texto qualquer " + i);
  408.        }
  409.        System.out.println(buscaBD(1));
  410.    }
  411.    
  412.    
  413.    public static boolean todosProcessosVivos(){
  414.        aguardarResposta = true;
  415.        TimerProcessos timeoutResposta = new TimerProcessos(TIMEOUT);
  416.        sendMessageGroup(VERIFICA_MEMBROS_VIVOS+DIVISOR_STRING+IDProcesso);
  417.        numeroProcessosGrupo = 1;
  418.        while(aguardarResposta && !timeoutResposta.shouldCreate){
  419.             switch(trataMensagem(receiveMessageGroup(),recv)){
  420.                 case NOVO_MEMBRO:
  421.                     numeroProcessosGrupo ++;
  422.                     System.out.println(numeroProcessosGrupo);
  423.                     if(numeroProcessosGrupo>=2){
  424.                         aguardarResposta = false;
  425.                         System.out.println("Todos vivos!");
  426.                     }
  427.                     break;
  428.                 case GRUPO_COMPLETO:
  429.                     numeroProcessosGrupo = 3;
  430.                     break;
  431.                 default:
  432.                   //  System.out.println("Case Default da espera grupo");
  433.                     break;
  434.             }
  435.         }
  436.        timeoutResposta.cancelTimer();
  437.        return !aguardarResposta;
  438.    }
  439.     /**
  440.      * @param args the command line arguments
  441.      */
  442.     public static void main(String[] args) {
  443.         setUp();
  444.         defineIDProcesso();
  445.         sendMessageGroup(OK_STRING+DIVISOR_STRING+IDProcesso + DIVISOR_STRING + 0);
  446.         aguardaMembrosDoGrupo();
  447.        
  448.         System.out.println("Grupo completo! Iniciar execucao.");
  449.         aguardaPermissaoRegiaoCritica = false;
  450.         while(executarProcesso){      
  451.            
  452.             //Se receber requisicao de busca ou insercao -> aguardaPermissaoRegiaoCritica =  true;
  453.             if(aguardaPermissaoRegiaoCritica){
  454.                 //fazer coisas da regiao critica
  455.                 System.out.println("[Processo " + IDProcesso + "] Aguardando entrada na regiao critica!");
  456.                
  457.                 //Notifica a todos que deseja estar na regiao critica
  458.                 sendMessageGroup(REQUISICAO_REG_CRITICA_STRING + DIVISOR_STRING + IDProcesso);
  459.  
  460.                 while(aguardaPermissaoRegiaoCritica){
  461.                     lerGrupoTrataMensagem();
  462.                 }
  463.                 sendMessageGroup(ENTROU_REGIAO_CRITICA_STRING + DIVISOR_STRING + IDProcesso); //avisa outros processos que esta entrando na regiao critica
  464.                 //Iniciar timer da regiao critica
  465.                
  466.                 regiaoCritica = true;
  467.                 while(regiaoCritica){
  468.                     //verifica timer, muda regiaoCritica para false apos fim do timer
  469.                     aguardarResposta = true;
  470.                     TimerProcessos timeoutResposta = new TimerProcessos(TIMEOUT);
  471.                      while(aguardarResposta && !timeoutResposta.shouldCreate)
  472.                      {  if(escolhaBusca)  
  473.                         {
  474.                          mensagem = buscaBD(idBuscaCliente);
  475.                         }
  476.                         else{
  477.                            k++;
  478.                            inserir(mensagemCliente,k);
  479.                            mensagem = "Sucesso";
  480.                         }
  481.                      sendMessageGroup(RESPOSTA+DIVISOR_STRING+idCliente+DIVISOR_STRING+mensagem);
  482.                      aguardarResposta = false;
  483.                      regiaoCritica = false;
  484.                         //lerGrupoTrataMensagem();
  485.                      }
  486.                     //Ainda ler grupo, para atualizar lista de espera
  487.                 }
  488.                 System.err.println("[Processo " + IDProcesso + "]Fim da regiao critica!");
  489.                 sendMessageGroup(SAINDO_REGIAO_CRITICA_STRING + DIVISOR_STRING + IDProcesso); //notifica que saiu da regiao critica
  490.                 System.out.println("[Processo " + IDProcesso + "] Executando na regiao nao critica");
  491.             }else{
  492.                 //fazer coisas da regiao nao critica
  493.                 //Ler grupo para atualizar controle de quem esta na regiao critica e atualizar a lista de espera
  494.                 lerGrupoRegiaoNaoCritica();
  495.                 if(aguardaPermissaoRegiaoCritica){
  496.                     //se veio requisicao de adicionar texto, precisa verificar se os 3 processos estao vivos
  497.                     if(todosProcessosVivos()){
  498.                         aguardaPermissaoRegiaoCritica  =true;
  499.                         System.out.println("Todos processos vivos! Pode requisitar entrar na regiao critica");
  500.                     }else{
  501.                         aguardaPermissaoRegiaoCritica = false; // envia mensagem de erro
  502.                         System.out.println("Algum processo nao esta presente!");
  503.                     }
  504.                 }
  505.             }
  506.            
  507.         }
  508.        
  509.        
  510.     }
  511.    
  512. }
Add Comment
Please, Sign In to add comment