quinta-feira, 25 de fevereiro de 2016

De um DAO genérico a uma API de persistência

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

O tema que iremos discutir hoje, foi construído em parte em um post passado aqui do blog. Nele, conseguimos projetar uma classe DAO totalmente genérica e independente. O resultado foi: muitas sugestões e elogios. Isso é ótimo !! É disso que precisamos !!

Se você chegou aqui agora e não tem idéia do que eu to falando. Antes de tudo, seja bem-vindo. Agora, sugiro que você dê uma lidinha no post abaixo.

Um DAO totalmente genérico e independente

Hoje, discutiremos a evolução do post acima. Hoje, sairemos de um DAO genérico e iremos para uma API de persistência.

Achou ousado ??? Se não sonharmos alto, não atingimos novos níveis.

Análise da solução

Para iniciarmos nosso debate, é necessário que você tenha um conhecimento médio/avançado em Java, OO, UML e do padrão de projeto DAO. Nosso projeto será construído em Java 7, devido aos recursos que essa versão oferece e será dividido em dois módulos.
  • Módulo PersistenceAPI - Representa o projeto da API, de fato.
  • Módulo PersistenceAPIClient - Representa um projeto de software que, usa a nossa API para persistir e recuperar seus dados.
A figura 1 representa o diagrama de classes completo (PersistenceAPI + PersistenceAPIClient). Vamos analisar a estrutura de pacotes.
Figura 1 - Diagrama de classes completo (PersistenceAPI + PersistenceAPIClient)
  • client - Pacote de representação para as classes clientes, ou seja, as classes que acessam a nossa API.
    • dao - Pacote das classes DAO.
    • entidade - Pacote das classes de domínio.
    • exception - Pacote para exceptions.
  • br.com.persistenceapi.core - Pacote que encapsula todas as classes da API.
    • dao - Pacote da classe DAO genérica.
    • datasource - Pacote da classe que gerencia o datasource da API.
    • exception - Pacote para exceptions
    • pool - Pacote das classes que gerenciam o pool.
A classe DAO cliente deve estender (estabelecer uma relação de herança) a classe DAO genérica da API. A partir do momento que isso é feito, a subclasse tem acesso aos métodos públicos da superclasse a qual, tem uma relação de composição com a classe DataSource e um relacionamento de uso com a interface RowMapping, a qual é genérica.

A interface tem um único método chamado mapping. Este método é responsável pelo mapeamento do objeto ResultSet em uma classe de domínio. Através da composição, a superclasse tem acesso a métodos para obter e fechar conexões do pool.

O DataSource é composto pelo pool visto que, representa uma camada intermediária entre a classe GenericDAO e o pool. Sua responsabilidade é prover acesso a obtenção de conexões e encapsular a inteligência para o encerramento.

Por último mas, não menos importante, temos a classe JDBCConnectionPool. Ela é responsável por ler o arquivo de propriedades de configuração e validar seus dados, inicializar o pool com as configurações passadas, iniciar a thread de timeout (caso configurada) e encerrar todas as conexões. A thread de timeout tem um papel chave no funcionamento do pool. Ela é iniciada no construtor e controla o tempo de timeout definido no arquivo de configuração.

Iremos abordar com mais detalhes o funcionamento de cada classe em seções posteriores.

Banco de dados

Precisamos antes de tudo, construir nossa base de dados. Não irei por restrições sobre sua preferência de banco de dados. Eu estou utilizando o MySQL mas, se você quiser usar o Postgres, Oracle ou DB2, sem problemas. Lembre-se somente que você deve ter o JAR do drive desse banco porque senão, nem poderemos começar nosso projeto de API.
  CREATE TABLE `funcionario` (   
  `ID` int(11) NOT NULL AUTO_INCREMENT,   
  `NM_FUNCIONARIO` varchar(45) DEFAULT NULL,   
  `EM_FUNCIONARIO` varchar(45) DEFAULT NULL,   
  `DT_NASCIMENTO_FUNCIONARIO` date DEFAULT NULL,   
  `MAT_FUNCIONARIO` varchar(45) DEFAULT NULL,   
  `NM_LOGRADOURO` varchar(45) DEFAULT NULL,   
  `NUM_LOGRADOURO` int(11) DEFAULT NULL,   
  `NM_BAIRRO` varchar(45) DEFAULT NULL,   
  PRIMARY KEY (`ID`)   
  ) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8$$   

OBSERVAÇÃO: Se você já tem a tabela do post anterior, não precisa executar o script SQL pois, é a mesma tabela.

PersistenceAPI

A nossa API será um projeto Java comum, independente da IDE que você usar. Já sabemos quais pacotes precisamos criar, vamos analisar agora as classes de cada pacote e entender os conceitos enraizados em cada uma delas.

Antes de começarmos, é importante citar que as classes do pacote de exceção dispensam comentários porque suas implementações não fogem da normalidade. Elas foram projetadas para terem nomes auto explicativos facilitando assim, a legibilidade do código. Exceções são usadas como sinalizadoras de erros ou inconformidades.

Para que possamos entender como a API funciona, precisamos antes, entender como um pool funciona. O conceito de pool não está preso somente a conexões de banco de dados. É possível criar um pool com conexões a arquivos, por exemplo. Um pool é repositório de recursos os quais, são gerenciados visando maior performance.

Vamos facilitar !!

Na abordagem tradicional Java + DAO, geralmente uma conexão é aberta, no início de um bloco try, e fechada, dentro de um bloco finally, para todos os métodos de uma classe DAO. Em geral, isso é péssimo para o desempenho do sistema pois, causa lentidão. Abrir e fechar uma conexão é algo que pode demorar visto que, diversas tarefas precisam ser realizadas, como por exemplo: sockets (se você não sabe o que um socket, dá uma olhada nas referências) devem ser abertos e conectados, questões de concorrência, a porta pode estar sendo ocupada por outro processo, entre outras coisas. Então, qual é a solução para este grande problema visto que, precisamos abrir uma conexão ? Será que deixar uma conexão aberta por todo o tempo de vida de uma aplicação, é uma abordagem melhor ? A resposta para essas perguntas é: o pool de conexões.

A lógica de um pool de conexões é fácil. Deve ser criada uma lista de tamanho determinado e todas as posições são preenchidas com conexões abertas. A partir do momento que um método precisa executar operações no banco, ele solicita uma conexão. O pool analisa essa solicitação e verifica a disponibilidade de suas conexões. Caso alguma esteja disponível, ela é retornada para o método. Caso contrário, uma exceção pode ser propagada. Para o código cliente, a impressão passada é que realmente uma nova conexão está sendo aberta. Quando o método termina de executar suas operações, ele "fecha" a conexão. O processo de encerramento de conexões consiste em retornar a instância outrora usada, para a lista interna do pool. A codificação cliente não tem sequer idéia de todo o controle realizado por trás.

Vendo por esse aspecto, você deve estar pensando que criar um pool próprio é mole. Contudo, existem outros fatores ainda não debatidos.

Uma vez inicializado e configurado, o pool deve permanecer quanto tempo em memória ? Se você está pensando que ele deve ficar ativo enquanto a aplicação estiver funcionando, você sabe que isso não é viável para uma aplicação desktop GUI (Swing) ou web. Não seria correto deixar o pool ativo, cheio de conexões, não tendo nenhuma requisição de conexão ao banco. Contudo, como é possível identificar o momento em que devemos "desligar" o pool, sem desligar a aplicação ?

Para que essa pergunta seja respondida, é interessante que conheçamos o conceito de timeout. O timeout é o tempo que algum recurso permanece ativo a partir do momento que está ocioso. Logo, é possível concluir que nosso pool precisa de um timeout. Porém, a rotina do timeout deve ser iniciada logo após, o pool ter sido inicializado. Então, precisamos executar essas tarefas de forma paralela.

A estrutura computacional oferecida para as linguagens de programação para executar tarefa com tal característica, é uma thread. Se você não está familiarizado, dê uma clicada aqui e fique por dentro.

Depois que o timeout expira, as conexões do pool devem ser encerradas. Porém, como o pool deve se comportar, se logo após que o timeout expirou, ele pode receber uma nova solicitação de obtenção de conexão ? Lembre-se que o fato do pool ter expirado, não significa que o programa principal chegou ao seu fim. Para cada solicitação feita ao pool, este deve verificar se suas conexões ainda estão ativas. Caso não estejam, são reativadas.

E a questão da concorrência ? Como o pool deve se comportar caso, dois método sejam executados na mesma hora e tentem obter conexões ? Essa questão responderemos mais a frente quando analisarmos o código-fonte do nosso pool.

Acho que já abordamos bem a parte teórica, vamos para a parte prática !!!

Classe GenericDAO e interface RowMapping

 package br.com.persistenceapi.core.dao;  
   
 import br.com.persistenceapi.core.datasource.DataSource;  
 import br.com.persistenceapi.core.exception.EmptyPoolException;  
 import br.com.persistenceapi.core.exception.EmptyResultSetException;  
 import br.com.persistenceapi.core.exception.MoreThanOneResultException;  
 import java.sql.Connection;  
 import java.sql.PreparedStatement;  
 import java.sql.ResultSet;  
 import java.sql.SQLException;  
 import java.util.ArrayList;  
 import java.util.List;  
   
 /**  
  * Classe genérica que representa o DAO Genérico.  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  * @param <T> Notação genérica da classe  
  */  
 public class GenericDAO<T>{  
   
   private final DataSource dataSource;  
   
   /**  
    * Construtor da classe. Inicializa o data source.  
    */  
   public GenericDAO(){  
     dataSource = new DataSource();  
   }  
   
   /**  
    * Implementação de método que é responsável por realizar as operações de escrita (insert, update, delete) no banco de dados.  
    * @param sql Representa a string sql.  
    * @param parametros Representa a lista de parâmetros da query.  
    * @throws br.com.persistenceapi.core.exception.EmptyPoolException Representa o momento em que o pool não possui conexões disponíveis.  
    * @throws java.sql.SQLException Representa algum erro de SQL ou de conexão.  
    */  
   public void insertUpdateDelete(String sql, List<Object> parametros) throws EmptyPoolException, SQLException{  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     try {  
       connection = dataSource.getConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       preparedStatement.execute();  
       connection.commit();  
     } catch (SQLException ex) {  
       if(connection != null){  
         connection.rollback();  
       }  
       throw ex;  
     } catch (EmptyPoolException ex) {  
       throw ex;  
     } finally {  
       dataSource.closeConnection(connection, preparedStatement);  
     }  
   }  
     
   /**  
    * Implementação de método que é responsável por receber os parâmetros, avaliar se algum deles é nulo e configurá-los no statement.  
    * @param preparedStatement Representa o statement oriundo da query.  
    * @param parametros Representa a lista de parâmetros da query.  
    * @throws SQLException Representa algum erro da atribuição dos parâmetros da query ao statement.  
    */  
   private void receiveParameters(PreparedStatement preparedStatement, List<Object> parametros) throws SQLException{  
     int paramPos = 1;  
     for(Object parametro : parametros){  
       if(parametro == null){  
         preparedStatement.setNull(paramPos, 1);  
       } else {  
         preparedStatement.setObject(paramPos, parametro);  
       }  
       paramPos++;  
     }  
   }  
     
   /**  
    * Implementação de método responsável por realizar operações de leitura no banco de dados (select) as quais, podem retornar vários registros.  
    * @param sql Representa a query a ser executada.  
    * @param parametros Representa a lista de parâmetros da query.  
    * @param rowMapping Representa o mapeamento do resultado da query com os objetos de entidade.  
    * @return Retorna uma lista genérica de objetos oriundos da consulta SQL.  
    * @throws java.sql.SQLException Representa algum erro de SQL ou de conexão.  
    * @throws br.com.persistenceapi.core.exception.EmptyPoolException Representa o momento em que o pool não possui conexões disponíveis.  
    * @throws br.com.persistenceapi.core.exception.EmptyResultSetException Representa que a consulta realizada não retornou nenhum dado.  
    */  
   public List<T> findAll(String sql, List<Object> parametros, RowMapping rowMapping) throws SQLException, EmptyPoolException, EmptyResultSetException{  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     ResultSet resultSet = null;  
     List<T> rows = new ArrayList();  
     try {  
       connection = dataSource.getConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       resultSet = preparedStatement.executeQuery();  
       if(!resultSet.isBeforeFirst()){  
         throw new EmptyResultSetException("A consulta SQL realizada não possui resultados.");  
       }  
       while(resultSet.next()){  
         rows.add((T) rowMapping.mapping(resultSet));  
       }        
       connection.commit();  
     } catch (SQLException | EmptyPoolException ex) {  
       throw ex;  
     } finally {  
       dataSource.closeConnection(connection, preparedStatement, resultSet);  
     }  
     return rows;  
   }  
   
   /**  
    * Implementação de método responsável por realizar operações de leitura no banco de dados (select) as quais, somente retornam um registro.  
    * @param sql Representa a query a ser executada.  
    * @param parametros Representa a lista de parâmetros da query.  
    * @param rowMapping Representa o mapeamento do resultado da query com os objetos de entidade.  
    * @return Retorna o objeto genérico oriundo da consulta SQL.  
    * @throws br.com.persistenceapi.core.exception.EmptyResultSetException Representa que a consulta realizada não retornou nenhum dado.  
    * @throws br.com.persistenceapi.core.exception.MoreThanOneResultException Representa que a consulta realizada retornou mais de um registro.  
    * @throws br.com.persistenceapi.core.exception.EmptyPoolException Representa o momento em que o pool não possui conexões disponíveis.  
    * @throws java.sql.SQLException Representa algum erro de SQL ou de conexão.  
    */  
   public T findById(String sql, List<Object> parametros, RowMapping rowMapping) throws EmptyResultSetException, MoreThanOneResultException, EmptyPoolException, SQLException{  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     ResultSet resultSet = null;  
     T row = null;  
     try {  
       connection = dataSource.getConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       resultSet = preparedStatement.executeQuery();  
       if(!resultSet.isBeforeFirst()){  
         throw new EmptyResultSetException("A consulta SQL realizada não possui resultados.");  
       }  
       resultSet.last();  
       if(resultSet.getRow() > 1){  
         throw new MoreThanOneResultException("A consulta SQL retorna mais de um resultado.");  
       }  
       resultSet.first();  
       row = (T) rowMapping.mapping(resultSet);  
       connection.commit();  
     } catch (SQLException | EmptyPoolException ex) {  
       throw ex;  
     } finally {  
       dataSource.closeConnection(connection, preparedStatement, resultSet);  
     }  
     return row;  
   }  
 }  
   


 package br.com.persistenceapi.core.dao;  
   
 import java.sql.ResultSet;  
 import java.sql.SQLException;  
   
 /**  
  * Inteface genérica do mapeamento resultado do banco(result set) - objeto de domínio  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  * @param <T> Notação genérica da interface.  
  */  
 public interface RowMapping<T> {  
     
   /**  
    * Método que realiza o mapeamento do result set em um objeto de domínio.  
    * @param resultSet Objeto do tipo ResultSet. Contém o retorno da query.  
    * @return T Retorna um objeto especificado pela generic.  
    * @throws SQLException Caso algum erro aconteça quando for acessar os resultados da query.  
    */  
   T mapping(ResultSet resultSet) throws SQLException;  
 }  

Estas são a classe DAO genérica e a interface de método único, citados anteriormente. Em ambas, é possível identificar o forte uso de generics (recurso Java 5+) visto que, o objetivo principal é a reutilização máxima de código para as operações de banco. Logo, em vários pontos é possível notar que o tipo do retorno de métodos é inferido em tempo de execução. A propagação de exceções é feita com o fim de, sinalizar inconformidades durante a execução.

O método receiveParameters é responsável por receber os parâmetros de uma query e configurá-los no objeto PreparedStatement. Na sua implementação, foi usado o método setObject visto que, segundo a documentação, ele define em runtime qual o tipo do dado passado e atribui corretamente seu valor à query. Eliminando assim, uma extensa verificação.

Classe DataSource


 package br.com.persistenceapi.core.datasource;  
   
 import br.com.persistenceapi.core.exception.EmptyPoolException;  
 import br.com.persistenceapi.core.exception.PoolCreationException;  
 import br.com.persistenceapi.core.pool.JDBCConnectionPool;  
 import java.sql.Connection;  
 import java.sql.PreparedStatement;  
 import java.sql.ResultSet;  
 import java.sql.SQLException;  
   
 /**  
  * Classe criada com a finalidade de representar um data source. Esta classe constitui uma camada intermediária entre o pool de conexões e o dao genérico.  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  */  
 public class DataSource {  
   
   /*instância do pool*/  
   private JDBCConnectionPool pool;  
   
   /**  
    * Construtor da classe DataSource. Inicializa o pool de conexões.     
    */  
   public DataSource() {  
     try{  
       this.pool = new JDBCConnectionPool();  
     } catch (PoolCreationException pce){  
       pce.printStackTrace();  
     }  
   }  
   
   /**  
    * Obtém uma conexão disponível do pool.  
    * @return Representa a conexão.  
    * @throws SQLException Representa um erro de conexão a base de dados.  
    * @throws br.com.persistenceapi.core.exception.EmptyPoolException Representa o momento em que o pool não possui conexões disponíveis.  
    */  
   public Connection getConnection() throws SQLException, EmptyPoolException {  
     return pool.getConnection();  
   }  
   
   /**  
    * Retorna a conexão ao pool.  
    * @param connection Representa a conexão.  
    */  
   public void closeConnection(Connection connection) {  
     pool.returnConnection(connection);  
   }  
     
   /**  
    * Realiza o encerramento do statement atrelado à conexão e devolve a conexão ao pool.  
    * @param connection Representa a conexão.  
    * @param preparedStatement Representa o statement.  
    */  
   public void closeConnection(Connection connection, PreparedStatement preparedStatement) {  
     if(preparedStatement != null){  
       try {  
         preparedStatement.close();  
       } catch (SQLException ex) {  
         ex.printStackTrace();  
       }  
     }  
     this.closeConnection(connection);  
   }  
     
   /**  
    * Realiza o encerramento do result set, statement e devolve a conexão ao pool.  
    * @param connection Representa a conexão.  
    * @param preparedStatement Representa o statement.  
    * @param resultSet Representa o result set.  
    */  
   public void closeConnection(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){  
     if(resultSet != null){  
       try {  
         resultSet.close();  
       } catch (SQLException ex) {  
         ex.printStackTrace();  
       }  
     }  
     this.closeConnection(connection, preparedStatement);  
   }  
 }  
   

Essa classe é responsável pela comunicação da classe GenericDAO com o pool. Ela atua como uma cada camada intermediária, inicializando o pool, encaminhando as solicitações de conexão e provendo uma abstração para o encerramento de conexões. Essa abstração omite o fato de que o encerramento da conexão, aqui,  não existe. O que realmente existe é o retorno da conexão para o pool e encerramento dos objetos PreparedStatement e ResultSet. O verdadeiro encerramento é feito em outro ponto, o qual já vimos.

Classes JDBCConnectionPool e TerminatePoolThread


 package br.com.persistenceapi.core.pool;  
   
 import br.com.persistenceapi.core.exception.EmptyPoolException;  
 import br.com.persistenceapi.core.exception.PoolCreationException;  
 import br.com.persistenceapi.core.exception.PropertiesConfigurationException;  
 import java.io.File;  
 import java.io.FileInputStream;  
 import java.io.IOException;  
 import java.sql.Connection;  
 import java.sql.DriverManager;  
 import java.sql.SQLException;  
 import java.util.ArrayList;  
 import java.util.List;  
 import java.util.Properties;  
   
 /**  
  * Classe que representa o pool de conexões.  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  */  
 public class JDBCConnectionPool {  
   
   /*pool*/  
   private static List<Connection> connectionPool;  
     
   /*dados do arquivo de propriedades*/  
   private static Integer poolSize;  
   private static String driver;  
   private static String user;  
   private static String pass;  
   private static String host;  
   private static String databaseName;  
   private static Integer timeout;  
   private static boolean isNeverTimeout;  
   
   /*flag para controle da leitura do arquivo de propriedades*/  
   private static boolean isPoolAlreadyConfigured;  
   
   private final String MSG_PROPERTY_CONFIGURATION_EXCEPTION = "Erro ao realizar a configuração do pool.";  
   
   /**  
    * Construtor da classe.  
    * @throws br.com.persistenceapi.core.exception.PoolCreationException Representa um erro de criação do pool.  
    */  
   public JDBCConnectionPool() throws PoolCreationException {    
     try {  
       if(!isPoolAlreadyConfigured){  
         this.initializeConfiguration();  
         this.connectionPool = new ArrayList<>(this.poolSize);  
         this.initializeConnectionPool();  
         if(!isNeverTimeout){  
           new TerminatePoolThread(this).start();  
         }  
       }  
     } catch (PropertiesConfigurationException | SQLException ex) {  
       throw new PoolCreationException("Erro na criação do pool.", ex);  
     }  
   }  
   
   /**  
    * Implementação de método responsável por ler todos os dados do arquivo de propriedades.  
    * @throws PropertiesConfigurationException Representa um erro na leitura do arquivo de propriedades. Este erro pode ser ocasionado pela ausência do arquivo, nome de arquivo incorreto ou algum erro de I/O no fechamento do arquivo.  
    */  
   private void initializeConfiguration() throws PropertiesConfigurationException {  
     Properties poolProperties = new Properties();  
     FileInputStream fis = null;  
     try {  
       fis = new FileInputStream(new File("..//database.properties"));  
       poolProperties.load(fis);  
       this.validateConfigurations(poolProperties);  
     } catch (IOException ioe) {  
       throw new PropertiesConfigurationException("Erro ao ler o arquivo de configuração. Arquivo inexistente ou o nome de arquivo de configuração incorreto. O nome deve ser 'database.properties'.", ioe);  
     } finally {  
       if (fis != null) {  
         try {  
           fis.close();  
         } catch (IOException ioe) {  
           throw new PropertiesConfigurationException("Erro ao fechar o arquivo de configuração.", ioe);  
         }  
       }  
     }  
   }  
   
   /**  
    * Realiza a validação dos dados oriundos do arquivo de propriedades.  
    * @param poolProperties Representa o arquivo de propriedades.  
    * @throws PropertiesConfigurationException Representa um erro na configuração, exemplos: ausência de dados obrigatórios,   
    * dados em formatos incorretos, valores abaixo ou acima do permitido.  
    */  
   private void validateConfigurations(Properties poolProperties) throws PropertiesConfigurationException{      
     this.isPoolAlreadyConfigured = true;  
   
     String poolSize = poolProperties.getProperty("poolSize");  
     if("".equals(poolSize)){  
       //valor default  
       this.poolSize = 10;  
     } else {  
       try {  
         this.poolSize = Integer.parseInt(poolSize);  
       } catch (NumberFormatException nfe) {  
         throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " O valor para o tamanho do pool deve ser inteiro.");  
       }  
   
       if(this.poolSize > 30 || this.poolSize < 10){  
         throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION +   
           " Tamanho do pool acima ou abaixo do permitido. O tamanho do pool deve estar entre 10 e 30, inclusive.");  
       }  
     }  
   
     this.user = poolProperties.getProperty("user");  
     if("".equals(this.user)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " Usuário do banco não especificado. Este campo é obrigatório.");  
     }  
   
     this.pass = poolProperties.getProperty("pass");  
     if("".equals(this.pass)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " Senha do banco não especificada. Este campo é obrigatório.");  
     }  
   
     this.driver = poolProperties.getProperty("driver");  
     if("".equals(this.driver)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " Driver do banco não especificado. Este campo é obrigatório.");  
     }  
   
     this.host = poolProperties.getProperty("host");  
     if("".equals(this.driver)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " Host do banco não especificado. Este campo é obrigatório.");  
     }  
       
     this.databaseName = poolProperties.getProperty("databaseName");  
     if("".equals(this.driver)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " Nome do banco não especificado. Este campo é obrigatório.");  
     }  
       
     String timeout = poolProperties.getProperty("timeout");  
     if("".equals(timeout)){  
       //valor default  
       this.timeout = 30;  
     } else {  
       try {  
         this.timeout = Integer.parseInt(timeout);  
       } catch (NumberFormatException nfe) {  
         throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " O valor para o timeout deve ser inteiro.");  
       }  
     }  
           
     String neverTimeout = poolProperties.getProperty("neverTimeout");  
     if(!"true".equalsIgnoreCase(neverTimeout) && !"false".equalsIgnoreCase(neverTimeout)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION + " O valor para a flag deve ser 'true' ou 'false'.");  
     }  
     this.isNeverTimeout = Boolean.valueOf(neverTimeout);  
   
     if(!this.isNeverTimeout && (this.timeout > 60 || this.timeout < 10)){  
       throw new PropertiesConfigurationException(MSG_PROPERTY_CONFIGURATION_EXCEPTION +   
         " O tempo de timeout acima ou abaixo do permitido. O intervalo de tempo deve estar entre 10 e 60 segundos, inclusive.");  
     }  
   }  
   
   /**  
    * Implementação de método que inicializa as conexões no pool.  
    * @throws SQLException Representa um erro da criação da conexão.  
    * @throws PropertiesConfigurationException Representa um erro na leitura dos dados do arquivo.  
    */  
   private void initializeConnectionPool() throws SQLException, PropertiesConfigurationException {  
     while (!checkIfConnectionPoolIsFull()) {  
       connectionPool.add(createNewConnection());  
     }  
   }  
   
   /**  
    * Implementação de método que verifica se pool de conexões está cheio ou não.  
    * @return Retorna true caso o pool esteja cheio. Caso contrário, retorna false.  
    */  
   protected boolean checkIfConnectionPoolIsFull() {  
     return connectionPool.size() == this.poolSize;  
   }  
   
   /**  
    * Implementação de método responsável por criar uma nova conexão com a base de dados.  
    * @return Retorna a conexão criada com a base de dados.  
    * @throws SQLException Representa um erro na criação de uma conexão.  
    * @throws PropertiesConfigurationException Representa um erro no driver de banco especificado.  
    */  
   private Connection createNewConnection() throws SQLException, PropertiesConfigurationException {  
     try {  
       Class.forName(this.driver);  
     } catch (ClassNotFoundException cnfe) {  
       throw new PropertiesConfigurationException("Erro na leitura do arquivo de configuração. Verifique o valor referente ao driver do banco de dados.");  
     }  
     return DriverManager.getConnection(this.host + this.databaseName, this.user, this.pass);  
   }  
   
   /**  
    * Implementação de método sincronizado para a obtenção de conexão.  
    * @return Representa a conexão retornada do pool.  
    * @throws EmptyPoolException Representa o momento em que o pool não possui conexões disponíveis.  
    */  
   public synchronized Connection getConnection() throws EmptyPoolException {  
     if (connectionPool.size() > 0) {  
       Connection connection = connectionPool.get(0);  
       connectionPool.remove(0);  
       return connection;  
     } else {  
       throw new EmptyPoolException("O pool não possui conexões disponíveis no momento.");  
     }      
   }  
   
   /**  
    * Implementação de método que retorna a conexão para o pool.  
    * @param connection Representa a conexão.  
    */  
   public synchronized void returnConnection(Connection connection) {  
     connectionPool.add(connection);  
   }  
   
   /**  
    * Método sobreescrito de Object. Este método encerra todas as conexões caso o objeto do pool seja destruído.  
    * @throws Throwable Representa qualquer exceção.  
    */  
   @Override  
   protected void finalize() throws Throwable {  
     try {  
       this.terminateAllConnections();  
     } finally {  
       super.finalize();  
     }  
   }  
   
   /**  
    * Implementação de método que encerra todas as conexões.  
    */  
   protected void terminateAllConnections() {  
     for (Connection connection : connectionPool) {  
       try {  
         connection.close();  
       } catch (SQLException ex) {  
         ex.printStackTrace();  
       }  
     }  
     connectionPool.clear();  
     isPoolAlreadyConfigured = false;  
   }  
   
   /**  
    * @return the timeout  
    */  
   public Integer getTimeout() {  
     return timeout;  
   }  
 }  
   

A classe acima representa a implementação do pool. Acredito que se debatêssemos todos os métodos, a leitura desse texto ficaria extremamente extenso. Então, comentarei sobre os pontos cruciais para o funcionamento.

No construtor da classe é onde, o pool é configurado e inicializado. Sua configuração é feita através de um arquivo de propriedades, representado pela figura 2. Todas as entradas do arquivo são lidas, validadas e então armazenados na classe. Após isso, o pool é inicializado, ou seja, as conexões são criadas. A quantidade de conexões criadas é definida pelo atributo poolSize do arquivo de configuração.

Figura 2 - O arquivo de propriedades de configuração
Outro atributo do arquivo de configuração que, desempenha papel importante na configuração, é o neverTimeout. Este só pode receber dois valores: true e false. Caso seu valor seja falso, a lógica de timeout deve ser aplicada e então, a thread de timeout é iniciada. Caso contrário, o pool não possui timeout.

Os atributos da classe referente a configuração do pool, a lista de conexões, e o booleano isPoolAlreadyConfigured precisam ser estáticos porque, só é necessário que o pool seja configurado e iniciado uma única vez, na sua criação. O responsável por realizar todo esse controle é o último atributo citado. Caso as conexões do pool sejam destruídas pela rotina de timeout, o flag de controle de configuração volta a ser falso e o pool é reconfigurado e reinicializado.

É necessário que tanto o método getConnection quanto o returnConnection sejam synchronized para que não haja problema de concorrência a recursos, no acesso simultâneo de várias classes clientes.

 package br.com.persistenceapi.core.pool;  
   
 /**  
  * Thread usada para a liberação das conexões do pool. É necessário utilizar uma thread para a realizar a liberação pois,   
  * essa tarefa deve ser feito de forma paralela juntamente com as tarefas desempenhadas pelo pool.  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  */  
 public class TerminatePoolThread extends Thread {  
   
   /*instância do pool*/  
   private final JDBCConnectionPool jdbcConnectionPool;  
   
   /**  
    * Construtor da Thread.  
    * @param jdbcConnectionPool Representa o pool de conexões. É necessário receber a instância do pool para que, possa  
    * realizar acesso a propriedade timeout.  
    */  
   public TerminatePoolThread(JDBCConnectionPool jdbcConnectionPool) {  
     this.jdbcConnectionPool = jdbcConnectionPool;  
   }  
     
   /**  
    * Implementação de método que contém o comportamento executado pela thread. A thread verificará com o delay de meio segundo  
    * se todas as conexões não estão sendo utilizadas. Se não estiverem, o contador do timeout começa sua contagem.  
    * Caso contrário, o contador é zerado e o código cliente pode usar a conexão.  
    */  
   @Override  
   public void run(){  
     super.run();  
     System.out.println("Thread de timeout iniciada");  
     int timeoutCounter = 0;  
     while (true) {  
       if (jdbcConnectionPool.checkIfConnectionPoolIsFull()) {  
         timeoutCounter++;  
         if (jdbcConnectionPool.getTimeout() == timeoutCounter) {  
           jdbcConnectionPool.terminateAllConnections();  
           break;  
         }  
       } else {  
         timeoutCounter = 0;  
       }  
       try {  
         Thread.sleep(500);  
       } catch (InterruptedException ex) {          
         ex.printStackTrace();  
       }  
     }  
     System.out.println("Thread de timeout terminada");  
   }  
 }  
   

A thread de timeout necessita de uma representação interna do pool pois, precisa acessar seus dados. A contagem só pode ser iniciada quando nenhuma conexão estiver sendo utilizada. O sleep é necessária pois, loops infinitos consomem muita CPU, podendo causar lentidão ou até, paralisação do programa.

Caso a contagem chegue ao fim, ou seja, o contador de timeout é igual ao valor definido no arquivo de configuração, o comando para encerramento real das conexões é dado. As conexões são encerradas, a lista é limpa e o flag de pool já configurado, citado anteriormente, volta ao valor default.

PersistenceAPIClient

Nosso projeto cliente também é um projeto Java comum e muito simples. Para que ele possa usar a nossa API recém criada, é necessário que tenha uma classe DAO que estenda a classe GenericDAO, da API e, que o driver do banco esteja em seu classpath.

 package dao;  
   
 import br.com.persistenceapi.core.dao.RowMapping;  
 import br.com.persistenceapi.core.dao.GenericDAO;  
 import br.com.persistenceapi.core.exception.EmptyPoolException;  
 import br.com.persistenceapi.core.exception.EmptyResultSetException;  
 import br.com.persistenceapi.core.exception.MoreThanOneResultException;  
 import entidade.Funcionario;  
 import exception.BusinessException;  
 import exception.IntegrationException;  
 import java.sql.ResultSet;  
 import java.sql.SQLException;  
 import java.util.ArrayList;  
 import java.util.List;  
   
 /**  
  * Classe cliente DAO da entidade Funcionario.  
  * @author Preciso Estudar Sempre - precisoestudarsempre@gmail.com  
  */  
 public class FuncionarioDAO extends GenericDAO<Funcionario>{  
   
   public void insert(Funcionario funcionario) throws IntegrationException, BusinessException{  
     String sql = "INSERT INTO funcionario ("  
         + "NM_FUNCIONARIO, "  
         + "EM_FUNCIONARIO,"  
         + "DT_NASCIMENTO_FUNCIONARIO,"  
         + "MAT_FUNCIONARIO,"  
         + "NM_LOGRADOURO,"  
         + "NUM_LOGRADOURO,"  
         + "NM_BAIRRO"  
         + ") VALUES (?,?,?,?,?,?,?)";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(funcionario.getNome());  
     parametros.add(funcionario.getEmail());  
     parametros.add(funcionario.getDataNascimento());  
     parametros.add(funcionario.getMatricula());  
     parametros.add(funcionario.getLogradouro());  
     parametros.add(funcionario.getNumero());  
     parametros.add(funcionario.getBairro());  
     try {  
       super.insertUpdateDelete(sql, parametros);  
     } catch (EmptyPoolException ex) {  
       throw new IntegrationException("Erro de integração com a base de dados.", ex);  
     } catch (SQLException ex) {  
       throw new BusinessException("Verifique a operação SQL.", ex);  
     }  
   }  
     
   public void update(Funcionario funcionario) throws IntegrationException, BusinessException{  
     String sql = "UPDATE FUNCIONARIO SET "  
         + "NM_FUNCIONARIO = ?, "  
         + "EM_FUNCIONARIO = ?,"  
         + "DT_NASCIMENTO_FUNCIONARIO = ?,"  
         + "MAT_FUNCIONARIO = ?,"  
         + "NM_LOGRADOURO = ?,"  
         + "NUM_LOGRADOURO = ?,"  
         + "NM_BAIRRO = ? "  
       + "WHERE ID = ?";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(funcionario.getNome());  
     parametros.add(funcionario.getEmail());  
     parametros.add(funcionario.getDataNascimento());  
     parametros.add(funcionario.getMatricula());  
     parametros.add(funcionario.getLogradouro());  
     parametros.add(funcionario.getNumero());  
     parametros.add(funcionario.getBairro());  
     parametros.add(funcionario.getId());  
     try {  
       super.insertUpdateDelete(sql, parametros);  
     } catch (EmptyPoolException ex) {  
       throw new IntegrationException("Erro de integração com a base de dados.", ex);  
     } catch (SQLException ex) {  
       throw new BusinessException("Verifique a operação SQL.", ex);  
     }  
   }  
     
   public void delete(Long id) throws IntegrationException, BusinessException{  
     String sql = "DELETE FROM FUNCIONARIO WHERE ID = ?";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(id);  
     try {  
       super.insertUpdateDelete(sql, parametros);  
     } catch (EmptyPoolException ex) {  
       throw new IntegrationException("Erro de integração com a base de dados.", ex);   
     } catch (SQLException ex) {  
       throw new BusinessException("Verifique a operação SQL.", ex);  
     }  
   }  
     
   public List<Funcionario> findAll() throws IntegrationException, BusinessException{  
     String sql = "SELECT * FROM FUNCIONARIO";  
     List<Object> parametros = new ArrayList<>();  
     try{  
       return super.findAll(sql, parametros, new RowMapping<Funcionario>() {  
         @Override  
         public Funcionario mapping(ResultSet resultSet) throws SQLException{  
           Funcionario funcionario = new Funcionario();  
           if(resultSet != null){  
             funcionario.setId(resultSet.getLong("ID"));  
             funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));  
             funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));  
             funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));  
             funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));  
             funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));  
             funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));  
             funcionario.setBairro(resultSet.getString("NM_BAIRRO"));  
           }  
           return funcionario;  
         }  
       });  
     } catch (EmptyPoolException | SQLException ex) {  
       throw new IntegrationException("Erro de integração com a base de dados.", ex);  
     } catch (EmptyResultSetException ex) {  
       throw new BusinessException(ex);  
     }  
   }  
     
   public Funcionario findById(Long id) throws IntegrationException, BusinessException{  
     String sql = "SELECT * FROM FUNCIONARIO WHERE ID = ?";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(id);  
     try {  
       return super.findById(sql, parametros, new RowMapping<Funcionario>() {  
         @Override  
         public Funcionario mapping(ResultSet resultSet) throws SQLException{  
           Funcionario funcionario = new Funcionario();  
           funcionario.setId(resultSet.getLong("ID"));  
           funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));  
           funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));  
           funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));  
           funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));  
           funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));  
           funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));  
           funcionario.setBairro(resultSet.getString("NM_BAIRRO"));  
           return funcionario;  
         }  
       });  
     } catch (EmptyResultSetException | MoreThanOneResultException ex) {  
       throw new BusinessException("A consulta realizada possui uma não conformidade de negócio.", ex);  
     } catch (EmptyPoolException | SQLException ex) {  
       throw new IntegrationException("Erro de integração com a base de dados.", ex);  
     }  
   }  
 }  
   

É possível notar que desenvolver uma camada de persistência usando nossa API, se tornou algo extremamente fácil e flexível. As únicas preocupações que sobraram para o desenvolvedor são:
  • Construir a query.
  • Por os parâmetros da query em uma lista.
  • Chamar o método da API.
  • Caso a query tenha retorno, implementar o método mapping.
  • Tratar possíveis exceções
As exceptions BusinessException IntegrationException foram criadas para representar, respectivamente, um erro de negócio e um erro de integração.  a finalidade de sinalizar alguma inconformidade. Elas não são necessárias em seu projeto, você trata as exceções que vem da API, da forma que você quiser.

Pronto !!!! Conseguimos acabar !! Uhullllll !! Até que enfim !!!

Acho que esse é o maior post do blog até o momento e foi mais de um mês de trabalho nele. Mas enfim, para trazer um conteúdo de qualidade para vocês, vale a pena. ;)

Agradecimentos:
Ao imenso amigo Luís Marcelo Bruckner. Sem você este projeto não seria possível - http://github.com/marcelobruckner
Ao colega Itacir Pompeu, do grupo do facebook DevCast - http://github.com/Pompeu
Ao colega Carlos Alexandre Becker, do grupo do facebook DevCast - http://github.com/caarlos0

Link no dropbox: https://www.dropbox.com/s/95w82mmhjp9gv58/PersistenceAPI.rar?dl=0
Link no github: https://github.com/PrecisoEstudarSempre/PersistenceAPI

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, me mande um e-mail ou, na nossa página do facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/

Referências:
Interface PreparedStatement - https://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html#setObject(int,%20java.lang.Object)
Need Code to create Connection Pool in java - http://stackoverflow.com/questions/2826212/need-code-to-create-connection-pool-in-java
Thread (ciência da computação) - https://pt.wikipedia.org/wiki/Thread_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)
Vamos fazer um chat ? - http://precisoestudarsempre.blogspot.com.br/2015/06/vamos-fazer-um-chat.html
Leia Mais ››

quinta-feira, 14 de janeiro de 2016

Gerando cores aleatórias com Java

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Como você faria para gerar cores aleatoriamente em Java as quais, seguem as características descritas abaixo ?
  1. Devem ser aleatórias (não podem ser fixas/hard-coded)
  2. Devem estar no formato hexadecimal para HTML
  3. Não podem ser muito claras
  4. Não podem ser muito escuras
  5. Não podem ser preto nem branco
  6. Não podem ser parecidas
  7. Não podem haver cores repetidas
Se você tá com a famosa pressinha (mi mi mi) e não pode esperar, clique aqui ou aqui, baixe o projeto e depois volte aqui para ler tudo. :)

Tá .... vamos começar pelo começo !!

Para que você tenha um total entendimento do assunto abordado aqui, é necessário conhecimento em Java e OO. Caso você não tenha, recomendo algumas aulinhas antes.

Nossa primeira exigência é que as cores devem ser aleatórias. Contudo, como gerar cores através de Java ? Bem, sabemos que o padrão de cores utilizado por componentes eletrônicos é o RGB. O modelo de cores RGB é baseado na teoria de visão colorida tricromática, de Young-Helmholtz, e no triângulo de cores de Hardwell. O uso do modelo RGB como padrão para apresentação de cores na Internet tem suas raízes nos padrões de cores de televisões RCA de 1953 e no uso do padrão RGB nas câmeras Land/Polaroid, pós Edwin Land [Wikipedia]. Este padrão é dividido em três cores, sendo elas:
  • Red = vermelho
  • Green = verde
  • Blue = azul
Uma cor no modelo de cores RGB pode ser descrita pela indicação da quantidade de vermelho, verde e azul que contém. Cada uma pode variar entre o mínimo (completamente escuro - 0) e máximo (completamente intenso - 255). Quando todas as cores estão no mínimo, o resultado é preto. Se todas estão no máximo, o resultado é branco [Wikipedia].

Uma das representações mais usuais para as cores é a utilização da escala de 0 à 255, bastante encontrada na computação pela conveniência de se guardar cada valor de cor em 1 byte (8 bits) [Wikipedia].

Clique aqui e se diverta fazendo suas combinações de cores. Algumas sugestões para você:
  • Branco - RGB (255,255,255);
  • Azul - RGB (0,0,255);
  • Vermelho - RGB (255,0,0);
  • Verde - RGB (0,255,0);
  • Amarelo - RGB (255,255,0);
  • Magenta - RGB (255,0,255);
  • Ciano - RGB (0,255,255);
  • Preto - RGB (0,0,0).

Mãos a massa !!

Método gerarCorAleatoriamente()

1:  /**  
2:   * Método responsável pela geração de cores em hexadecimal  
3:   * @return A cor  
4:   */  
5:  private Color gerarCorAleatoriamente(){  
6:       Random randColor = new Random();   
7:       int r = randColor.nextInt(256);  
8:       int g = randColor.nextInt(256);  
9:       int b = randColor.nextInt(256);  
10:       return new Color(r, g, b);  
11:  }  

Esse método é responsável pela geração de cores aleatórias. Para realizar tal, utiliza o método nextInt(int) o qual, recebe um argumento inteiro que representa o limite do número randômico. O número que será gerado estará no limite 0 - 255. Através da geração aleatória dos números, são definidos as quantidades de R, G e B da cor. A classe Color é utilizada somente para fins de representação.

Método gerarCorHexadecimal(color) e tratarHexString(string)

1:  /**  
2:   * Método que exporta a cor para o formato hexadecimal.   
3:   * @param color Representa a cor.  
4:   * @return Retorna a cor no formato hexadecimal.  
5:   */  
6:  private String gerarCorHexadecimal(Color color){  
7:       return '#'+  
8:            this.tratarHexString(Integer.toHexString(color.getRed()))+  
9:            this.tratarHexString(Integer.toHexString(color.getGreen()))+  
10:            this.tratarHexString(Integer.toHexString(color.getBlue()));  
11:  }  

Este método trabalha em conjunto com o método tratarHexString(string).

1:  /**  
2:   * Método responsável por tratar a string hexadecimal. Caso a string possua tamanho 1, o método adiciona o número 0 na frente da string.  
3:   * @param hexString A string hexadecimal  
4:   * @return A string hexadecimal  
5:   */  
6:  private String tratarHexString(String hexString){  
7:       String hex = null;  
8:       if(hexString.length() == 1){  
9:            hex = '0'+hexString;  
10:       }else{  
11:            hex = hexString;  
12:       }  
13:       return hex;  
14:  }  

Os métodos acima são fácies de entender. O primeiro obtém as quantidades de Rda cor, gerada aleatoriamente na seção acima, e transforma-as em hexadecimal. Contudo, um tratamento precisa ser feito o qual, é realizado no segundo método.

Quando uma quantidade tem seu correspondente hexadecimal gerado somente com um único caractere, torna problemática a exibição de cores pois, o formato de cores em hexadecimal espera três pares de RB. Logo, isso #001a0f é diferente disso #001af. Então, é adicionado o caractere '0' à esquerda da string (linha 9) e o problema é solucionado.

Método isBrilhoCorreto(float) e isSaturacaoCorreta(float)

1:  /**  
2:   * Método que trata o brilho de uma cor.   
3:   * Para que uma cor seja escolhida para o gráfico seu nível de brilho deve estar acima ou igual de 0.5 e abaixo ou igual de 0.9.  
4:   * @param brightness Representa o brilho.  
5:   * @return Retorna true caso a cor esteja de acordo com o ponto necessário (brilho), false caso contrário.  
6:   */  
7:  private boolean isBrilhoCorreto(float brightness){  
8:       if (brightness >= 0.5 && brightness <= 0.9) {  
9:            return true;  
10:       }  
11:       return false;  
12:  }  
13:    
14:  /**  
15:   * Método que trata a saturação de uma cor.   
16:   * Para que uma cor seja escolhida para o gráfico seu nível de saturação deve estar acima ou igual de 0.7.  
17:   * @param saturation Representa a saturação  
18:   * @return Retorna true caso a cor esteja de acordo com o ponto necessário (saturação), false caso contrário.  
19:   */  
20:  private boolean isSaturacaoCorreta(float saturation){  
21:       if (saturation >= 0.7){  
22:            return true;  
23:       }  
24:       return false;  
25:  }  

Para que possamos gerar cores que não sejam muito claras nem muito escuras precisamos controlar seus níveis de brilho (brightness) e saturação (saturation). Como parâmetros para o controle, adotaremos que nossas cores ideais tenham o nível de saturação maior ou igual a 0.7 e que possuam brilho entre 0.5, inclusive, e 0.9, inclusive.

A saturação é um parâmetro que especifica a qualidade de um matiz de cor pelo grau de mesclagem do matiz com a cor branca (padrão HSB) ou, com a cor cinza média (padrão HSL). Então, quanto menos cinza ou branco na cor, mais saturada ela é [Wikipedia]. Entenda por matiz, a cor pura.

Método calcularDistanciaDeCores(color, color), isDistanciaAceitavel(color, color) e isCorParecidaComCorPermitida(list, color)

1:  /**  
2:   * Método que calcula a distância entre cores.  
3:   * @param cor1 Representa uma cor.  
4:   * @param cor2 Representa uma cor.  
5:   * @return Retorna a distância.  
6:   */  
7:  private double calcularDistanciaDeCores(Color cor1, Color cor2){  
8:       long meanRed = (cor1.getRed() + cor2.getRed())/2;  
9:       long deltaRed = cor1.getRed() - cor2.getRed();  
10:       long deltaGreen = cor1.getGreen() - cor2.getGreen();  
11:       long deltaBlue = cor1.getBlue() - cor2.getBlue();  
12:       return Math.sqrt((2+meanRed/256)*Math.pow(deltaRed, 2)+4*Math.pow(deltaGreen, 2)+(2+(255-meanRed)/256)*Math.pow(deltaBlue, 2));  
13:  }  
14:    
15:  /**  
16:   * Método que avalia se a distância entre duas é aceitável. Uma distância é dita aceita se é menor que 200.   
17:   * @param cor1 Representa uma cor.  
18:   * @param cor2 Representa uma cor.   
19:   * @return boolean Retorna true caso a distância seja menor que 200, false caso contrário.  
20:   */  
21:  private boolean isDistanciaAceitavel(Color cor1, Color cor2){  
22:       if (this.calcularDistanciaDeCores(cor1, cor2) < 200) {  
23:            return false;  
24:       }  
25:       return true;  
26:  }  
27:    
28:  /**  
29:   * Método que valida se uma cor gerada aleatoriamente possui uma distância aceitável com as cores previamente permitidas.   
30:   * @param coresPermitidas Representa as cores permitidas.  
31:   * @param corAleatoria Representa a cor gerada aleatoriamente.  
32:   * @return Retorna true caso a cor seja parecida, retorna false caso contrário.  
33:   */  
34:  private boolean isCorParecidaComCorPermitida(List<Color> coresPermitidas, Color corAleatoria){  
35:       boolean isCorParecida = false;  
36:       for (Color corPermitida : coresPermitidas) {  
37:            if(!this.isDistanciaAceitavel(corPermitida, corAleatoria)){  
38:                 isCorParecida = true;  
39:                 break;  
40:            }  
41:       }  
42:       return isCorParecida;  
43:  }  

Gerar cores aleatoriamente tem um sério problema, a geração de cores parecidas. A cor azul e azul' não são iguais, mas absurdamente parecidas. Então, como saber se um cor é parecida com a outra? Comparar os níveis de Rda cor não é boa idéia pois, mesmo que as cores sejam muito parecidas, seus níveis são extremamente diferentes. Logo, não temos como estabelecer um parâmetro e voltamos a pergunta. Como identificar uma cor parecida ?

Através de pesquisa, encontrei uma forma para resolver esse problema. Precisamos calcular a distância euclidiana entre as cores, ou seja, a distância entre dois pontos. O cálculo é realizado através do método calcularDistanciaDeCores(color, color) o qual, recebe duas cores como parâmetros. O valor retornado é a distância.

Agora, precisamos estabelecer o que é uma distância aceitável. Através de testes, notei que cores com a distância maior que 200 mantinham uma boa diferença em sua percepção. Então, criei o método isDistanciaAceitavel(color, color). Esse método retorna true, caso as cores estejam em uma distância aceitável ( > 200), caso contrário retorna false. Logo, o problema citado anteriormente foi resolvido. Não precisamos mais nos preocupar com azul e azul'.

A terceira etapa para encerrar esse problema é comparar a cor gerada com as cores já previamente escolhidas. Logo, o método isCorParecidaComCorPermitida(list, color) foi criado. Este método realiza essa comparação utilizando o método citado no parágrafo acima. A lista recebida como parâmetro contém as cores previamente escolhidas e o segundo parâmetro representa a cor gerada recentemente. Caso a cor não seja "parecida" com nenhuma das outras cores já escolhidas, o método retorna true, caso contrário, false.

Método carregarCoresProibidasDefault(list)

1:  /**  
2:   * Método que realiza a carga inicial de cores proibidas.  
3:   * @param coresProibidas Representa a lista de cores proibidas.  
4:   */  
5:  private void carregarCoresProibidasDefault(List<Color> coresProibidas){  
6:       coresProibidas.add(new Color(0,0,0)); //preto  
7:       coresProibidas.add(new Color(255,255,255)); //branco  
8:  }  

Este método carrega cores proibidas as quais são default (padrão). No nosso escopo, são cores proibidas por padrão, a cor preta e branca. Contudo, caso no seu escopo, a cor marrom ou azul-petróleo forem proibidas por padrão, você pode alterar o método e verificação de cores proibidas levará em conta essas, previamente adicionadas.

É possível notar que o método recebe um parâmetro do tipo List. Esse parâmetro é passado para que a lista de cores seja alimentada com as configurações iniciais e posteriormente com as cores proibidas geradas aleatoriamente.

Método gerarCores(int)

1:  /**  
2:   * Método responsável por gerar cores aleatoriamente no formato RGB seguindo os padrões de qualidade especificados.  
3:   * @param qtdDeCores Representa a quantidade de cores que deseja-se gerar.  
4:   * @return List<Color> Representa a lista de cores permitidas em formato RGB.  
5:   */  
6:  public List<Color> gerarCores(int qtdDeCores){  
7:       List<Color> coresPermitidas = new ArrayList<Color>();            
8:       List<Color> coresProibidas = new ArrayList<Color>();  
9:       this.carregarCoresProibidasDefault(coresProibidas);  
10:         
11:       Color corAleatoria = null;  
12:       boolean isCorProibida = false;  
13:       for(int i=0; i<qtdDeCores; i++){  
14:            while(true){  
15:                 corAleatoria = this.gerarCorAleatoriamente();  
16:                 float[] hsb = Color.RGBtoHSB(corAleatoria.getRed(), corAleatoria.getGreen(), corAleatoria.getBlue(), null);  
17:                 float saturation = hsb[1];  
18:                 float brightness = hsb[2];  
19:                 for(Color corProibida : coresProibidas){  
20:                      isCorProibida = corProibida.equals(corAleatoria)   
21:                                          || !this.isBrilhoCorreto(brightness)   
22:                                          || !this.isSaturacaoCorreta(saturation)  
23:                                          || !this.isDistanciaAceitavel(corAleatoria, corProibida)   
24:                                          || this.isCorParecidaComCorPermitida(coresPermitidas, corAleatoria);  
25:                      if(isCorProibida){  
26:                           coresProibidas.add(corProibida);  
27:                           break;  
28:                      }  
29:                 }  
30:                 if(isCorProibida){  
31:                      isCorProibida = false;  
32:                      continue;  
33:                 }  
34:                 break;  
35:            }  
36:            coresProibidas.clear();  
37:            coresPermitidas.add(corAleatoria);  
38:       }  
39:                   
40:       return coresPermitidas;  
41:  }  

Esse método é o grande controlador de toda a inteligência da geração de cores. É ele quem aplica todas as regras que especificamos acima mas, existem alguns pontos os quais, merecem uma atenção maior. Na linha 16, é possível notar uma transformação de RGB para HSB (HueSaturationBrightness). Essa transformação é necessária para que, possamos acessar essas propriedades das cores.

O loop infinito é necessário para que seja gerada uma cor aleatoriamente até que ela seja aceita segundo nossas regras de qualidade. Como não sabemos quando essa suposta cor será gerada, então é necessário um loop infinito acompanhada de uma condicional com break.

A limpeza da lista de cores proibidas é necessária pois seu tamanho cresce absurdamente. Caso esse tratamento não fosse realizado uma OutOfMemoryError aconteceria e aplicação seria encerrada.

Pronto !! Terminamos !!



Abaixo você pode encontrar as referências usadas e um link para um site muito legal sobre cores, vale a pena dar uma conferida.

Caso você faça o download do projeto, encontrará a classe que desenvolvemos aqui e um Jar. O arquivo Jar contém nossa classe compilada para que, você possa adicioná-la ao seu projeto e gerar suas cores.

Baixe o projeto
Dropbox: https://www.dropbox.com/s/dc4b021zse7akfp/GeradorCoresAleatorias.rar?dl=0
GitHub: https://github.com/PrecisoEstudarSempre/GeradorCoresAleatorias.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, na nossa página do facebook ou mande um email.

Facebook: https://www.facebook.com/precisoestudarsempre/
E-mail: precisoestudarsempre@gmail.com

Referências:
Colour metric - http://www.compuphase.com/cmetric.htm
RGB - https://pt.wikipedia.org/wiki/RGB
HTML Color Picker - http://www.w3schools.com/tags/ref_colorpicker.asp
Saturação - https://pt.wikipedia.org/wiki/Satura%C3%A7%C3%A3o
Matiz (cor) - https://pt.wikipedia.org/wiki/Matiz_(cor)
Color Hex Color Codes - http://www.color-hex.com/
Leia Mais ››

segunda-feira, 7 de dezembro de 2015

Um DAO totalmente genérico e independente

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Hoje iremos ousar, hoje iremos ostentar no camarote. Se você lembrou da imagem abaixo, sim, você está certo.
Figura 1 - O rei do camarote
Seu sobrenome será "ostentação" mas, estamos aqui falando da ostentação do código-fonte. Eu afirmo com toda certeza que somente com Java e uma IDE, conseguimos criar uma camada DAO com um alto índice de código reutilizável. Contudo, a pergunta é: como ?

Se você é novo por aqui, bote seu email na caixa de texto para se inscrever no blog (clique aqui para ver como) ou curta a página do facebook, o link está no fim do post.

Voltando.....

Primeiro, precisamos entender como funciona um método que está na camada DAO. Se você não sabe o que é DAO, clique aqui ou dê uma olhada nas referências.
Figura 2 - O funcionamento de um método da camada DAO
É possível notar que algumas etapas sempre se repetem para todos os métodos DAO. Essas etapas são:
  • Abertura de conexão.
  • Configuração do commit automático.
  • Criação do statement.
  • Passagem de parâmetros para statement.
  • Execução do statement.
  • Commit da transação.
  • Rollback da transação (em caso de erro).
  • Encerramento da conexão.

E o que não se repete ? O que é diferente de um método para o outro ?
  • Query SQL
  • Parâmetros
Se pararmos para refletir sobre isso, realmente o que muda de um método para o outro é a query SQL realizada e os parâmetros que são passados. Então, temos muito código repetido o qual, podemos eliminar. Caso contrário, teremos problemas muito maiores no futuro. Mas, como eliminar ?

A resposta é simples. Temos que ter um bom projeto orientado a objetos onde, nossa meta é a reusabilidade de código.

----------------------------------------------------------------------------------------------------------

AVISO: É necessário o conhecimento dos seguintes assuntos para que, tenhamos um total aproveitamento do assunto:
  • Java
  • Orientação a objetos
  • Java + JDBC
  • SQL
  • UML
----------------------------------------------------------------------------------------------------------

Vamos modelar nossa solução.

Figura 3 - Diagrama de classes
GenericDAO

Essa superclasse possui os métodos que realizam as operações de banco (insert, update, delete, select) de forma genérica, configura os parâmetros da query, abrem conexão e encerram conexão. Como existem muitos métodos nessa classe, farei comentários pontuais sobre alguns.
  • insertUpdateDelete - Método responsável pelas operações de escrita (insert, update e delete). Consegue desempenhar a função genérica porque recebe como parâmetros, a query e os parâmetros da query e porque utiliza o método receiveParameters o qual, realiza a configuração dos parâmetros no objeto Statement. O índice dos parâmetros é conservado pela sua posição na lista.
  • receiveParameters - Método responsável pela configuração dos parâmetros da query recebidos no objeto Statement. Essa configuração é feita através de uma estrutura condicional que verifica o tipo da instância que está na lista de parâmetros. Uma vez que é determinado o tipo, o método correto do objeto Statement pode ser invocado. O índice dos parâmetros é conservado pela sua posição na lista.
  • findAll - Método responsável por listar registros de acordo com a query passada. O mapeamento dos dados do objeto ResultSet em objetos do tipo Funcionario acontece dentro da iteração do primeiro objeto e é feito através da implementação do método mapping.
  • findById - Método responsável por listar somente 1 registro de acordo com a query passada. Assim como no método acima, o mapeamento também é feito através do método mapping. Contudo, é necessário retornar sempre o primeiro registro portanto, uma verificação de cursor é feita em cima do retorno da query. Caso a query não tenha retornado nada, o objeto ResultSet se torna nulo e o método retorna um objeto zerado. Caso contrário, o cursor é movido em 1 posição e os dados são obtidos.
 public void insertUpdateDelete(String sql, List<Object> parametros){  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     try {  
       connection = this.openConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       preparedStatement.execute();  
       connection.commit();  
     } catch (SQLException ex) {  
       ex.printStackTrace();  
       if(connection != null){  
         try {  
           connection.rollback();  
         } catch (SQLException sqle) {  
           sqle.printStackTrace();  
         }  
       }  
     } finally {  
       this.closeConnection(connection, preparedStatement);  
     }  
   }  

   private void receiveParameters(PreparedStatement preparedStatement, List<Object> parametros) throws SQLException{  
     int paramPos = 1;  
     for(Object parametro : parametros){  
       if(parametro == null){  
         preparedStatement.setNull(paramPos, 1);  
       } else if(parametro instanceof Integer){  
         preparedStatement.setInt(paramPos, (Integer) parametro);  
       } else if(parametro instanceof Long){  
         preparedStatement.setLong(paramPos, (Long) parametro);  
       } else if(parametro instanceof Float){  
         preparedStatement.setFloat(paramPos, (Float) parametro);  
       } else if(parametro instanceof Double){  
         preparedStatement.setDouble(paramPos, (Double) parametro);  
       } else if(parametro instanceof Date){  
         preparedStatement.setDate(paramPos, new java.sql.Date(((Date) parametro).getTime()));  
       } else if(parametro instanceof String){  
         preparedStatement.setString(paramPos, parametro.toString());  
       }  
       paramPos++;  
     }  
   }  

   public List findAll(String sql, List<Object> parametros, RowMapping mapping){  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     ResultSet resultSet = null;  
     List rows = new ArrayList();  
     try {  
       connection = this.openConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       resultSet = preparedStatement.executeQuery();  
       while(resultSet.next()){  
         rows.add(mapping.mapping(resultSet));  
       }        
       connection.commit();  
     } catch (SQLException ex) {  
       ex.printStackTrace();  
       if(connection != null){  
         try {  
           connection.rollback();  
         } catch (SQLException sqle) {  
           sqle.printStackTrace();  
         }  
       }  
     } finally {  
       this.closeConnection(connection, preparedStatement, resultSet);  
     }  
     return rows;  
   }  

   public Object findById(String sql, List<Object> parametros, RowMapping mapping){  
     Connection connection = null;  
     PreparedStatement preparedStatement = null;  
     ResultSet resultSet = null;  
     Object row = null;  
     try {  
       connection = this.openConnection();  
       connection.setAutoCommit(false);  
       preparedStatement = connection.prepareStatement(sql);  
       this.receiveParameters(preparedStatement, parametros);  
       resultSet = preparedStatement.executeQuery();  
       resultSet = resultSet.isBeforeFirst() ? resultSet : null;  
       if(resultSet != null){  
         resultSet.next();  
       }  
       row = mapping.mapping(resultSet);  
       connection.commit();  
     } catch (SQLException ex) {  
       ex.printStackTrace();  
       if(connection != null){  
         try {  
           connection.rollback();  
         } catch (SQLException sqle) {  
           sqle.printStackTrace();  
         }  
       }  
     } finally {  
       this.closeConnection(connection, preparedStatement, resultSet);  
     }  
     return row;  
   }  
 }  

FuncionarioDAO

Esta é uma subclasse de GenericDAO. Utiliza os métodos da classe pai para persistir ou recuperar dados do banco de dados relacional. Os métodos de escrita são bem simples, englobam somente a criação da query e a adição dos parâmetros na lista. Os métodos que recuperam dados utilizam um recurso muito prático do Java, a implementação de interfaces em métodos. Dessa forma, eu não preciso criar uma classe nova a qual, implementa RowMapping. Eu posso passar diretamente a implementação da interface como um parâmetro do método. O método definido na interface RowMapping é o responsável pelo mapeamento ResultSet - Funcionario.

   public void insert(Funcionario funcionario){  
     String sql = "INSERT INTO funcionario ("  
         + "NM_FUNCIONARIO, "  
         + "EM_FUNCIONARIO,"  
         + "DT_NASCIMENTO_FUNCIONARIO,"  
         + "MAT_FUNCIONARIO,"  
         + "NM_LOGRADOURO,"  
         + "NUM_LOGRADOURO,"  
         + "NM_BAIRRO"  
         + ") VALUES (?,?,?,?,?,?,?)";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(funcionario.getNome());  
     parametros.add(funcionario.getEmail());  
     parametros.add(funcionario.getDataNascimento());  
     parametros.add(funcionario.getMatricula());  
     parametros.add(funcionario.getLogradouro());  
     parametros.add(funcionario.getNumero());  
     parametros.add(funcionario.getBairro());  
     super.insertUpdateDelete(sql, parametros);  
   }  
   
   public List<Funcionario> findAll(){  
     String sql = "SELECT * FROM FUNCIONARIO";  
     List<Object> parametros = new ArrayList<>();  
     return super.findAll(sql, parametros, new RowMapping<Funcionario>() {  
       @Override  
       public Funcionario mapping(ResultSet resultSet) throws SQLException{  
         Funcionario funcionario = new Funcionario();  
         if(resultSet != null){  
           funcionario.setId(resultSet.getLong("ID"));  
           funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));  
           funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));  
           funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));  
           funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));  
           funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));  
           funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));  
           funcionario.setBairro(resultSet.getString("NM_BAIRRO"));  
         }  
         return funcionario;  
       }  
     });  
   }  
     
   public Funcionario findById(Long id){  
     String sql = "SELECT * FROM FUNCIONARIO WHERE ID = ?";  
     List<Object> parametros = new ArrayList<>();  
     parametros.add(id);  
     return (Funcionario) super.findById(sql, parametros, new RowMapping<Funcionario>() {  
       @Override  
       public Funcionario mapping(ResultSet resultSet) throws SQLException{  
         Funcionario funcionario = new Funcionario();  
         if(resultSet != null){  
           funcionario.setId(resultSet.getLong("ID"));  
           funcionario.setNome(resultSet.getString("NM_FUNCIONARIO"));  
           funcionario.setEmail(resultSet.getString("EM_FUNCIONARIO"));  
           funcionario.setDataNascimento(resultSet.getDate("DT_NASCIMENTO_FUNCIONARIO"));  
           funcionario.setMatricula(resultSet.getString("MAT_FUNCIONARIO"));  
           funcionario.setLogradouro(resultSet.getString("NM_LOGRADOURO"));  
           funcionario.setNumero(resultSet.getInt("NUM_LOGRADOURO"));  
           funcionario.setBairro(resultSet.getString("NM_BAIRRO"));  
         }  
         return funcionario;  
       }  
     });  
   }  
 }  

RowMapping

Interface que possui único método, mapping. Este método é responsável pelo mapeamento do objeto ResultSet em uma entidade do sistema. Pelo fato de sempre retornar uma nova instância sendo essa, qualquer objeto do sistema, a interface é marcada como genérica . Portanto, o tipo de retorno também é genérico, ou seja, T.

Como citado anteriormente, a implementação dessa interface acontece na passagem de parâmetros dos métodos os quais, esperam por uma implementação da mesma.

 public interface RowMapping<T> {  
     
   /**  
    * Método que realiza o mapeamento do result set em um objeto.  
    * @param resultSet Objeto do tipo ResultSet. Contém o retorno da query.  
    * @return T Retorna um objeto especificado pela generic.  
    * @throws SQLException Erro de acesso ao banco ou SQL.  
    */  
   T mapping(ResultSet resultSet) throws SQLException;  
 }  

Funcionario

Um POJO comum.

Para criar a tabela do banco de dados, utilize o script abaixo:

 CREATE TABLE `funcionario` (  
  `ID` int(11) NOT NULL AUTO_INCREMENT,  
  `NM_FUNCIONARIO` varchar(45) DEFAULT NULL,  
  `EM_FUNCIONARIO` varchar(45) DEFAULT NULL,  
  `DT_NASCIMENTO_FUNCIONARIO` date DEFAULT NULL,  
  `MAT_FUNCIONARIO` varchar(45) DEFAULT NULL,  
  `NM_LOGRADOURO` varchar(45) DEFAULT NULL,  
  `NUM_LOGRADOURO` int(11) DEFAULT NULL,  
  `NM_BAIRRO` varchar(45) DEFAULT NULL,  
  PRIMARY KEY (`ID`)  
 ) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8$$  

Terminamos nosso DAO reutilizável e genérico. Sinta-se a vontade para criticar, comentar ou propor melhorias. Até porque é assim que conseguimos evoluir.

Baixe o projeto
Dropbox: https://www.dropbox.com/s/epg48k36xfp1brv/GenericDAO.rar?dl=0
GitHub: https://github.com/PrecisoEstudarSempre/GenericDAO.git

Agradecimentos a Bruno Souza.

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, na nossa página do facebook ou mande um email.

E-mail: precisoestudarsempre@gmail.com

Referências:
Objeto de acesso a dados - https://pt.wikipedia.org/wiki/DAO
Leia Mais ››

domingo, 29 de novembro de 2015

Dividindo e conquistando - O algoritmo Merge Sort

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar. Hoje abordaremos mais um tópico da série: Vamos por ordem nessa bagunça ? - Algoritmos de ordenação

Clique para dar uma olhada.

Neste post aprenderemos o algoritmo de ordenação Merge Sort.

AVISO: Esse algoritmo não é para iniciantes logo, você vai precisar de algum tempo para entendê-lo. Procure a supervisão de um adulto. =)

A nossa primeira pergunta é: como ele funciona ?

Criado por John von Neumann em 1945, este algoritmo utiliza a técnica de dividir para conquistar, diferentemente do seu primo, o algoritmo Bubble Sort. Possui complexidade de tempo Θ(n log 2n), no pior caso e no melhor caso, Θ(n log n).
Figura 1 - Criador do algoritmo Merge Sort
O algoritmo Merge Sort é de fácil implementação. É conceitualmente mais fácil do que a ordenação quicksort e a ordenação Shell mas, possui uma desvantagem. Requer um vetor adicional na memória, igual em tamanho àquele sendo ordenado. Se seu vetor original mal couber na memória, a ordenação não funcionará. Porém, se você possuir bastante espaço, isso não será um problema.

Sua estratégia consiste em criar uma sequência ordenada a partir de outras duas já ordenadas. Para tal, divide-se a sequência original em pares de dados, e ordena-se. Depois, agrupa-se em sequências de quatro elementos, e assim por diante até a sequência original estar separada em apenas duas partes. Este processo se repete até o array estar totalmente unido e ordenado.

A imagem abaixo ilustra bem o explicado.

Figura 2 - Funcionamento do algoritmo Merge Sort
Interprete cada faixa da imagem como uma etapa, ou seja, na primeira etapa é quando o array está todo unido, e assim por diante. É possível notar claramente como esse algoritmo funciona. Na primeira etapa temos o array todo unido, na segunda já dividimos ele ao meio (a divisão não é exata porque o tamanho do array não é par), ou seja, já estamos utilizando da técnica dividir para conquistar e esse processo de divisão se repete até a etapa 4.

Na etapa 4 dividimos o array até o máximo, ou seja, quando sobra somente 1 elemento, esse é o nosso caso base. A partir daí, podemos começar a ordená-lo. Na etapa 5 realizamos a primeira ordenação, ou seja, a ordenação dos pares. A partir da ordenação dos pares, podemos unir eles e ordená-los novamente, Esse processo se repete até o ponto em que obtemos o array todo unido novamente só que, agora ordenado.

O gif abaixo mostra todo o processo de ordenação de forma animada.
Figura 3 - Animação de funcionamento do algoritmo Merge Sort
Mas como transformar toda essa inteligência em um programa Java ?

1:  public class MergeSort {  
2:       private int[] numbers;  
3:       private int[] helper;  
4:    
5:       private int number;  
6:    
7:       public void sort(int[] values) {  
8:            this.numbers = values;  
9:            number = values.length;  
10:            this.helper = new int[number];  
11:            mergeSort(0, number - 1);  
12:       }  
13:    
14:       private void mergeSort(int begin, int end) {  
15:            // check if begin is smaller then end, if not then the array is sorted  
16:            if (begin < end) {  
17:                 // Get the index of the element which is in the middle  
18:                 int middle = begin + (end - begin) / 2;  
19:                 // Sort the left side of the array  
20:                 mergeSort(begin, middle);  
21:                 // Sort the right side of the array  
22:                 mergeSort(middle + 1, end);  
23:                 // Combine them both  
24:                 merge(begin, middle, end);  
25:            }  
26:       }  
27:    
28:       private void merge(int begin, int middle, int end) {  
29:    
30:            // Copy both parts into the helper array  
31:            for (int i = begin; i <= end; i++) {  
32:                 helper[i] = numbers[i];  
33:            }  
34:    
35:            int i = begin;  
36:            int j = middle + 1;  
37:            int k = begin;  
38:            // Copy the smallest values from either the left or the right side back  
39:            // to the original array  
40:            while (i <= middle && j <= end) {  
41:                 if (helper[i] <= helper[j]) {  
42:                      numbers[k] = helper[i];  
43:                      i++;  
44:                 } else {  
45:                      numbers[k] = helper[j];  
46:                      j++;  
47:                 }  
48:                 k++;  
49:            }  
50:            // Copy the rest of the left side of the array into the target array  
51:            while (i <= middle) {  
52:                 numbers[k] = helper[i];  
53:                 k++;  
54:                 i++;  
55:            }  
56:       }  
57:  }  

Pronto, conseguimos !! Vamos entender agora como o programa Java implementa o algoritmo.

Classe MergeSort

Essa classe possui os métodos de ordenação e os atributos numbers, helper e number os quais representam respectivamente, o array de valores, o array auxiliar e a variável usada para armazenar o tamanho do array de valores.

Esta implementação já possui uma otimização implementada para o problema de memória citado acima. Da forma que foi projetado, o array auxiliar só é carregado uma vez em memória visando aliviar o consumo.

Método sort

Esse método recebe o array original e realiza as seguintes operações:

  • Linha 8: passa os valores do array, recebido como parâmetro, para o array atributo, numbers.
  • Linha 9: Define o valor do atributo number onde, esse valor é o tamanho do array original.
  • Linha 10: Define a dimensão do array auxiliar onde, essa dimensão é a mesma do array original.
  • Linha 11: Realiza a chamada ao método mergeSort, passando como parâmetros o número 0 e o resultado da operação number - 1 onde, zero representa o início de qualquer array e number - 1 representa a última posição do array.


Método mergeSort

Esse método realiza a divisão já citada acima. Recebe dois parâmetros: a posição inicial e final do array.

  • Linha 16: Verifico se o parâmetro begin é menor que o parâmetro end. Essa verificação é necessária pois, ela realiza o controle da divisão do array. Caso a condição não seja verdadeira é porque o array está vazio ou, já está ordenado.
  • Linha 18: Calculo o meio do array visando a divisão do mesmo.
  • Linha 20: Realizo chamada recursiva passando como parâmetros begin e middle, ou seja, estou analisando a primeira metade do array dividido. Como esta é uma chamada recursiva, as linhas anteriores (16 e 18) serão executadas novamente, até o momento em que reste somente uma posição no array dividido n-vezes.
  • Linha 22: Realizo chamada recursiva passando como parâmetros middle +1 end, ou seja, estou analisando a segunda metade do array dividido. O processo de divisão segue de forma igual ao citado acima.
  • Linha 24: Realizo chamada ao método merge passando os parâmetros: begin, end e middle. Dependendo do nível de recursividade da execução do algoritmo, os valores dessas variáveis não estarão iguais as que estavam originalmente.


Método merge

Esse método realiza a ordenação. Recebe três parâmetros: a posição inicial, final e média do array.

  • Linha 31 até 33: Copia os valores do array original para o array auxiliar. Os valores copiados são limitados pelo intervalo definido pelas variáveis begin e end.
  • Linha 35 até 37: Declaro as variáveis i, j e k.
  • Linha 40 até 49: É aqui aonde a mágica acontece. A condição definido no loop while da linha 40 define que a iteração será feita do início da fatia esquerda até seu fim e depois do início da parte direita até seu fim. Essa condição é definida dessa forma porque sempre comparamos o grupo da esquerda com o da direita. Na imagem ou no gif animado é possível notar isso. Nas linhas 41 e 44, é avaliado quem é menor valor. Caso o valor da esquerda seja o menor, ele é copiado para o array original na posição certa (k) e a variável i é incrementada, caso contrário, o valor da direita é copiado e a variável j é incrementada. A variável k é incrementada fora do bloco condicional if pois, ele representa a posição final do valor ordenado logo, ele não precisa estar sendo incrementado onde é avaliado o menor valor e sim, depois que isso foi feito.
  • Linha 51 até 55: Nesse bloco são copiados os valores restantes da ordenação. O bloco acima não deixa o array original totalmente ordenado, ele só passa os menores valores para frente (ordenação crescente). Então, precisamos de uma outra iteração para recuperar os outros valores do array auxiliar e posicioná-los nas posições corretas. Como sabemos quais as posições corretas ? A variável k não é zerada logo, ele ainda guarda referência do último valor ordenado posicionado. As variáveis i e k devem ser incrementadas para realização do posicionamento.

Cumprimos mais uma etapa do nosso estudo. Entendemos a implementação do nosso programa. Vamos realizar alguns testes ?

Nossos testes serão feitos da seguinte forma: iremos montar um array um valores aleatórios e com as dimensões da primeira coluna. Depois de montado, iremos realizar a operação de ordenação 1 milhão de vezes e medir o tempo total. O tempo individual de cada processamento é calculado pela divisão do tempo total por 1 milhão.

Figura 4 - Testes feitos para medir a execução do algoritmo
Gráfico gerado a partir da planilha.

Figura 5 - Gráfico de quantidade de elementos por tempo
Através das evidências acima podemos notar que, conforme multiplicamos por 10 a quantidade de números, o resultado final também cresce da mesma forma. Esse algoritmo se mostrou eficaz para uma grande quantidade de números visto que, seu primo, Bubble Sort, demorou muito mais tempo.

Para fazer o download do algoritmo, clique aqui.

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários ou na nossa página do facebook.

Facebook: https://www.facebook.com/precisoestudarsempre/

Referências:
Merge sort - https://pt.wikipedia.org/wiki/Merge_sort
John von Neumann - https://pt.wikipedia.org/wiki/John_von_Neumann
Gif Merge Sort - https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif
Program: Implement merge sort in java. - http://java2novice.com/java-sorting-algorithms/merge-sort/
Mergesort in Java - Tutorial - http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html
Estruturas de dados e algoritmos em Java, Robert Lafore, 2004

Leia Mais ››