Sistema de ServiçosJavaFX

Classe Java Configurações

Classe FabricaDeConexao

A classe FabricaDeConexao é responsável pela criação e gerenciamento de um pool de conexões com o banco de dados, usando a biblioteca HikariCP. Ela também fornece métodos para obter conexões do pool e para fechá-lo adequadamente quando não for mais necessário. Aqui está uma explicação detalhada do código:

1. Dependências e Configuração Inicial

A classe importa bibliotecas relevantes, incluindo HikariCP, que é usada para gerenciamento de conexões, e SLF4J para o log.

  • HikariConfig: Classe que permite configurar o pool de conexões.
  • HikariDataSource: Gerencia o pool de conexões com base nas configurações definidas.
  • Logger e LoggerFactory: Usados para registrar informações no log.
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.SQLException;

2. Definindo o Pool de Conexões

A variável estática dataSource é usada para armazenar uma instância de HikariDataSource, que será o pool de conexões.

O bloco static é responsável por inicializar as configurações do pool de conexões assim que a classe é carregada na memória. Isso evita que a configuração precise ser feita repetidamente toda vez que uma conexão for solicitada.

  • setJdbcUrl: Especifica a URL do banco de dados. No exemplo, a conexão é feita com um banco MySQL local chamado sistema_servico.
  • setUsername e setPassword: Credenciais usadas para conectar ao banco.
  • setMaximumPoolSize: Define o número máximo de conexões simultâneas no pool.
  • setMinimumIdle: Define o número mínimo de conexões que devem estar ociosas, mas prontas para uso imediato.
  • setIdleTimeout: Define o tempo em milissegundos antes de uma conexão ociosa ser fechada.
  • setMaxLifetime: Define o tempo máximo de vida de uma conexão antes que ela seja renovada.
static {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/sistema_servico");
    config.setUsername("root");
    config.setPassword("");
    config.setMaximumPoolSize(10);  // Máximo de conexões simultâneas no pool
    config.setMinimumIdle(2);       // Mínimo de conexões ociosas que o pool deve manter
    config.setIdleTimeout(30000);   // Tempo ocioso antes de fechar uma conexão
    config.setMaxLifetime(1800000); // Tempo máximo de vida de uma conexão no pool

    dataSource = new HikariDataSource(config); // Instancia o pool com base na configuração
}

3. Obtendo Conexões do Pool

O método obterConexao retorna uma conexão ativa do pool. Ele utiliza o log para indicar que a conexão está sendo obtida.

Este método é chamado sempre que uma operação no banco de dados precisa ser realizada. Ele garante que as conexões são gerenciadas pelo pool, o que melhora o desempenho e evita que conexões sejam abertas e fechadas desnecessariamente.

public static Connection obterConexao() throws SQLException {
    logger.info("Obtendo conexão do pool...");
    return dataSource.getConnection();
}

4. Fechando o Pool de Conexões

O método fecharDataSource é responsável por encerrar o pool de conexões. Esse método deve ser chamado quando a aplicação estiver sendo finalizada, para liberar todos os recursos.

Aqui, o pool é fechado e todas as conexões ativas são encerradas de forma limpa.

 

public static void fecharDataSource() {
    if (dataSource != null) {
        logger.info("Fechando o pool de conexões...");
        dataSource.close();
    }
}

Código Completo

package br.com.professorclaytonandrade.sistemaservicojavafx.config.conexao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;

public class FabricaDeConexao {

    private static HikariDataSource dataSource;
    private static final Logger logger = LoggerFactory.getLogger(FabricaDeConexao.class);

    // Configuração do pool de conexões
    static {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/sistema_servico");
        config.setUsername("root");
        config.setPassword("");
        config.setMaximumPoolSize(10); // Máximo de conexões simultâneas
        config.setMinimumIdle(2); // Número mínimo de conexões ociosas
        config.setIdleTimeout(30000); // Tempo ocioso antes de fechar uma conexão
        config.setMaxLifetime(1800000); // Tempo máximo de vida para cada conexão

        dataSource = new HikariDataSource(config);
    }

    // Método para obter uma conexão
    public static Connection obterConexao() throws SQLException {
        logger.info("Obtendo conexão do pool...");
        return dataSource.getConnection();
    }

    // Método para fechar o pool de conexões
    public static void fecharDataSource() {
        if (dataSource != null) {
            logger.info("Fechando o pool de conexões...");
            dataSource.close();
        }
    }

    public static void main(String[] args) {
        try (Connection conexao = FabricaDeConexao.obterConexao()) {
            // Lógica para manipulação do banco de dados usando 'conexao'
            logger.info("Conexão obtida com sucesso!");
        } catch (SQLException e) {
            logger.error("Erro ao obter a conexão: {}", e.getMessage());
        }
    }
}

6. Dependências no pom.xml

Adicione as seguintes dependências ao seu arquivo pom.xml para incluir HikariCP (pool de conexões), SLF4J (para logging), e o driver JDBC para MySQL (ou outro banco de dados que você esteja utilizando):

<dependencies>
    <!-- HikariCP (pool de conexões) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>

    <!-- SLF4J (logging) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.9</version>
    </dependency>

    <!-- Driver JDBC do MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.34</version>
    </dependency>

    <!-- Adicione outras dependências aqui, se necessário -->
</dependencies>

7. Configuração do Módulo

Se você estiver utilizando um projeto modular (com module-info.java), você precisará declarar os módulos necessários. Aqui está um exemplo de como o module-info.java poderia ser configurado para incluir HikariCP, SLF4J, e o driver JDBC para MySQL:

Detalhamento:

  • HikariCP: É o pool de conexões responsável por gerenciar a criação e reutilização de conexões com o banco de dados.
  • SLF4J: É a API para log utilizada no código para registrar mensagens de log.
  • MySQL Connector/J: Driver JDBC para conectar ao banco de dados MySQL.

Agora, ao adicionar essas dependências e configurações de módulo, você terá tudo configurado para gerenciar suas conexões com o banco de dados de forma eficiente usando HikariCP e SLF4J para logs. Se você estiver utilizando outro banco de dados, substitua a dependência do driver JDBC apropriado.

module br.com.professorclaytonandrade.sistemaservicojavafx {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.sql;
    requires com.zaxxer.hikari;    // HikariCP para gerenciamento de conexões
    requires org.slf4j;            // SLF4J para logging

    // Abre o pacote para o JavaFX FXML para permitir injeção de dependências
    opens br.com.professorclaytonandrade.sistemaservicojavafx to javafx.fxml;
    
    // Exposição dos pacotes que precisam ser utilizados por outros módulos ou partes da aplicação
    exports br.com.professorclaytonandrade.sistemaservicojavafx;
    exports br.com.professorclaytonandrade.sistemaservicojavafx.controller;
    
    // Abre o pacote controller para JavaFX FXML
    opens br.com.professorclaytonandrade.sistemaservicojavafx.controller to javafx.fxml;
    
    // Permite que o JavaFX use a serialização para manipular objetos DTO
    opens br.com.professorclaytonandrade.sistemaservicojavafx.dto to javafx.base;
}

Código Java Controle de Versão Banco de Dados (Migration)

A classe InicializadorBancoDados é responsável por gerenciar e aplicar migrações de banco de dados, assegurando que a estrutura do banco de dados esteja atualizada de acordo com scripts SQL definidos em arquivos de migração. Essa abordagem é comum em projetos que necessitam versionar mudanças no banco de dados, como a criação ou alteração de tabelas.

Descrição passo a passo:

  1. Atributos da classe:

    • conexao: Este atributo armazena uma instância de Connection que é obtida pela classe FabricaDeConexao. Ele representa a conexão ativa com o banco de dados.
    • CAMINHO_MIGRACOES: Define o caminho onde os arquivos SQL de migração estão armazenados. Neste exemplo, a pasta é sql/.
  2. Construtor:

    • O construtor da classe invoca o método FabricaDeConexao.obterConexao() para estabelecer uma conexão com o banco de dados no momento em que uma instância de InicializadorBancoDados é criada.
  3. Método inicializar:

    • Função: Este método gerencia todo o processo de inicialização e migração do banco de dados.
      • Ele cria a tabela de controle de versões, caso ela ainda não exista, usando o método criarSchemaVersaoTabela().
      • Lista todos os arquivos de migração presentes na pasta definida (sql/), garantindo que eles sejam aplicados em ordem.
      • Para cada arquivo de migração, ele verifica se a migração já foi aplicada (consultando a tabela de controle de versões). Caso não tenha sido, ele lê o conteúdo do arquivo SQL e aplica a migração.
  4. Método listarArquivosDeMigracao:

    • Função: Lista todos os arquivos de migração SQL na pasta sql/ e os retorna em uma lista. Os arquivos são ordenados para garantir que as migrações sejam aplicadas na sequência correta.
  5. Método lerArquivoSql:

    • Função: Lê o conteúdo de um arquivo SQL e o converte em uma string que será executada no banco de dados. Verifica se o arquivo não está vazio e lança uma exceção se o SQL estiver vazio.
  6. Método aplicarMigracao:

    • Função: Executa o SQL de migração no banco de dados e registra a migração aplicada na tabela de controle de versões usando o método registrarMigracao().
  7. Método registrarMigracao:

    • Função: Insere um registro na tabela schema_version, indicando que a migração foi aplicada, com o número da versão e uma descrição da migração.
  8. Método isMigracaoAplicada:

    • Função: Verifica se uma migração já foi aplicada, consultando a tabela schema_version. Ele retorna true se a migração já foi aplicada (ou seja, o número da versão já existe na tabela).
  9. Método criarSchemaVersaoTabela:

    • Função: Cria a tabela schema_version caso ela ainda não exista. Essa tabela é usada para controlar quais versões de migração foram aplicadas no banco de dados.
  10. Método extrairVersaoDoArquivo:

    • Função: Extrai o número da versão a partir do nome do arquivo de migração. Por convenção, os arquivos de migração têm nomes como V1__descricao.sql, onde 1 é o número da versão.

Fluxo de execução:

Quando o método inicializar é chamado, ele:

  1. Garante que a tabela de controle de versão existe.
  2. Lista todos os arquivos de migração SQL na pasta sql/.
  3. Para cada arquivo:
    • Extrai o número da versão.
    • Verifica se a versão já foi aplicada.
    • Se não foi aplicada, lê o arquivo SQL e aplica a migração no banco de dados.
  4. Registra a migração na tabela schema_version, garantindo que ela não será reaplicada no futuro.

Código Completo:

package br.com.professorclaytonandrade.sistemaservicojavafx.config.versoesbasedados;

import br.com.professorclaytonandrade.sistemaservicojavafx.config.conexao.FabricaDeConexao;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

public class InicializadorBancoDados {
    private final Connection conexao; // Conexão com o banco de dados
    // Caminho onde os arquivos de migração estão localizados
    private static final String CAMINHO_MIGRACOES = "sql/"; // Define o caminho das migrações

    public InicializadorBancoDados() throws SQLException {
        // Construtor que obtém a conexão com o banco de dados ao criar a instância
        this.conexao = FabricaDeConexao.obterConexao();
    }

    public void inicializar() { // Método que inicia o processo de inicialização
        try {
            // Garante que a tabela de controle de versão existe
            criarSchemaVersaoTabela();

            // Lista e aplica os arquivos de migração
            List arquivosMigracao = listarArquivosDeMigracao(); // Obtém a lista de arquivos de migração

            // Percorre cada arquivo de migração
            for (Path caminho : arquivosMigracao) {
                // Extrai o número da versão a partir do nome do arquivo
                int idVersao = extrairVersaoDoArquivo(caminho.getFileName().toString());

                // Verifica se a migração já foi aplicada
                if (!isMigracaoAplicada(idVersao)) {
                    // Lê o conteúdo do arquivo SQL
                    String sql = lerArquivoSql(caminho);
                    // Aplica a migração no banco de dados
                    aplicarMigracao(idVersao, caminho.getFileName().toString(), sql);
                }
            }
        } catch (SQLException | IOException e) {
            e.printStackTrace(); // Tratar exceções conforme necessário
        }
    }

    private List listarArquivosDeMigracao() throws IOException {
        try {
            // Obtém a URL da pasta de migrações
            URI uriMigracoes = getClass().getClassLoader().getResource(CAMINHO_MIGRACOES).toURI();
            // Converte a URL para um objeto Path
            Path diretorioMigracoes = Paths.get(uriMigracoes);

            // Lista os arquivos no diretório de migrações
            return Files.list(diretorioMigracoes)
                    .filter(Files::isRegularFile) // Filtra para obter apenas arquivos regulares
                    .sorted() // Ordena para garantir que as migrações sejam aplicadas na ordem correta
                    .collect(Collectors.toList()); // Coleta os resultados em uma lista
        } catch (URISyntaxException e) {
            throw new IOException("Erro ao acessar a pasta de migrações", e);
        } catch (NullPointerException e) {
            throw new IOException("Pasta de migrações não encontrada: " + CAMINHO_MIGRACOES); 
            // Mensagem de erro caso a pasta não exista
        }
    }

    private String lerArquivoSql(Path caminhoArquivo) throws IOException, SQLException {
        // Cria um StringBuilder para armazenar o conteúdo do arquivo SQL
        StringBuilder sqlBuilder = new StringBuilder();
        // Lê o conteúdo do arquivo SQL linha por linha
        try (BufferedReader leitor = Files.newBufferedReader(caminhoArquivo)) {
            String linha;
            while ((linha = leitor.readLine()) != null) { // Continua lendo enquanto houver linhas
                sqlBuilder.append(linha).append("\n"); // Adiciona a linha ao StringBuilder
            }
        }
        String sql = sqlBuilder.toString().trim(); // Remove espaços em branco do início e fim

        // Verifica se a string SQL não está vazia
        if (sql.isEmpty()) {
            throw new SQLException("A string SQL não pode estar vazia: " + caminhoArquivo.toString()); 
            // Mensagem de erro se estiver vazia
        }
        return sql; // Retorna o conteúdo SQL lido
    }

    private void aplicarMigracao(int idVersao, String descricao, String sql) throws SQLException {
        // Prepara a declaração SQL para a execução da migração
        try (PreparedStatement declaracao = conexao.prepareStatement(sql)) {
            declaracao.executeUpdate(); // Executa a atualização no banco de dados
            // Registra a migração aplicada no controle de versão
            registrarMigracao(idVersao, descricao);
        }
    }

    private void registrarMigracao(int idVersao, String descricao) throws SQLException {
        // SQL para registrar a migração na tabela de controle de versão
        String sql = "INSERT INTO schema_version (version_id, description) VALUES (?, ?)";
        try (PreparedStatement declaracao = conexao.prepareStatement(sql)) {
            declaracao.setInt(1, idVersao); // Define o ID da versão
            declaracao.setString(2, descricao); // Define a descrição da migração
            declaracao.executeUpdate(); // Executa a inserção na tabela
        }
    }

    private boolean isMigracaoAplicada(int idVersao) throws SQLException {
        // SQL para verificar se a migração foi aplicada
        String sql = "SELECT COUNT(*) FROM schema_version WHERE version_id = ?";
        try (PreparedStatement declaracao = conexao.prepareStatement(sql)) {
            declaracao.setInt(1, idVersao); // Define o ID da versão
            var resultSet = declaracao.executeQuery(); // Executa a consulta
            resultSet.next(); // Move para o próximo registro
            // Retorna true se a migração foi aplicada (contagem maior que 0)
            return resultSet.getInt(1) > 0;
        }
    }

    private void criarSchemaVersaoTabela() throws SQLException {
        // SQL para criar a tabela de controle de versão, se não existir
        String sql = "CREATE TABLE IF NOT EXISTS schema_version (" +
                "version_id INT PRIMARY KEY, " +
                "description VARCHAR(255), " +
                "applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)";
        try (PreparedStatement declaracao = conexao.prepareStatement(sql)) {
            declaracao.executeUpdate(); // Executa a criação da tabela
        }
    }

    private int extrairVersaoDoArquivo(String arquivo) {
        // Extrai o número da versão a partir do nome do arquivo (ex: V1__descricao.sql -> 1)
        String parteVersao = arquivo.split("__")[0].substring(1); // Remove o 'V' e pega o número
        return Integer.parseInt(parteVersao); // Retorna o número da versão como um inteiro
    }
}

Código Java Mensagens

A classe Mensagens serve como uma coleção de constantes de texto utilizadas em todo o sistema, facilitando a manutenção e a centralização das mensagens exibidas para o usuário em operações de salvamento, validação e erros. Cada constante representa um texto padrão que pode ser usado em diferentes partes da aplicação para padronizar mensagens.

Descrição das constantes:

  1. TITULO_CONFIRMACAO_SALVAMENTO: "Confirmação de Salvamento"

    • Usada como título para caixas de diálogo ou notificações que pedem confirmação ao usuário antes de salvar alguma informação.
  2. TITULO_ERRO_VALIDACAO: "Erro de Validação"

    • Usada como título para alertas de erro que aparecem quando há problemas de validação, ajudando a informar o usuário que alguma entrada de dados é inválida.
  3. TITULO_ERRO_AO_SALVAR: "Erro ao salvar:"

    • Utilizada como título em mensagens de erro específicas quando ocorrem problemas durante o processo de salvamento de dados.
  4. MSG_INF_SALVAS_COM_SUCESSO: "As alterações foram salvas com sucesso."

    • Mensagem exibida ao usuário indicando que o salvamento foi bem-sucedido e que as alterações foram aplicadas corretamente.
  5. MSG_ERRO_AO_SALVAR_INFORMACOES: "Ocorreu um erro ao salvar as informações:"

    • Mensagem usada para notificar o usuário sobre uma falha no processo de salvamento, muitas vezes seguida de detalhes específicos do erro.

Benefícios do uso da classe Mensagens:

  • Centralização: Todas as mensagens estão em um único local, facilitando a manutenção e alteração futura.
  • Padronização: Evita mensagens inconsistentes na interface, criando uma experiência mais uniforme para o usuário.
  • Facilidade de Internacionalização: A classe permite adaptação para múltiplos idiomas, caso necessário, bastando trocar as mensagens em um só lugar.

Código Completo:

package br.com.professorclaytonandrade.sistemaservicojavafx.util;

public class Mensagens {
    public static String TITULO_CONFIRMACAO_SALVAMENTO = "Confirmação de Salvamento ";
    public static String TITULO_ERRO_VALIDACAO = "Erro de Validação ";
    public static String TITULO_ERRO_AO_SALVAR = "Erro ao salvar: ";
    public static String MSG_INF_SALVAS_COM_SUCESSO = "As alterações foram salvas com sucesso. ";
    public static String MSG_ERRO_AO_SALVAR_INFORMACOES = "Ocorreu um erro ao salvar as informações: ";
}

Código Java Util

A classe Util fornece métodos utilitários para lidar com a exibição de janelas modais e alertas no JavaFX, centralizando a lógica de manipulação de interfaces e facilitando a reutilização em toda a aplicação. Ela conta com métodos para carregar arquivos FXML, configurar janelas modais e exibir alertas personalizados.

Descrição dos métodos:

  1. janelaModal:

    • Abre uma nova janela modal sem dados adicionais.
    • Parâmetros:
      • anchorPane: Componente pai (para definir a posição e bloqueio da janela).
      • telaFXML: Caminho do arquivo FXML da nova janela.
      • tituloJanela: Título da nova janela.
    • Usa carregarFXML para carregar o arquivo FXML e criarPopupStage para configurar a nova janela modal.
  2. janelaModalComDados:

    • Abre uma janela modal, enviando dados a um controlador específico.
    • Parâmetros:
      • anchorPane: Componente pai.
      • telaFXML: Caminho do FXML.
      • tituloJanela: Título da janela.
      • objeto: Dados a serem enviados.
      • controller: Controlador esperado.
    • Atribui o objeto ao controlador usando reflexão para chamar o método setDado, permitindo que a janela exiba ou utilize os dados recebidos.
  3. carregarFXML:

    • Carrega e retorna um objeto Parent a partir de um arquivo FXML especificado.
    • Facilita a reutilização no código ao lidar com carregamento de arquivos FXML.
  4. criarPopupStage:

    • Cria e retorna um objeto Stage (janela) configurado com título, cena e relacionamento com o componente pai.
    • Parâmetros:
      • tituloJanela: Define o título da nova janela.
      • parent: Layout da cena.
      • anchorPane: Define o componente que será o “dono” da nova janela.
  5. configurarJanela:

    • Configura a janela como modal (usando Modality.APPLICATION_MODAL), centraliza na tela e define como não redimensionável.
  6. exibirJanelaModal:

    • Exibe a janela criada como um modal, esperando que o usuário interaja antes de continuar a execução.
  7. exibirAlerta:

    • Exibe um alerta do tipo especificado e retorna true se o usuário clicar em “OK”.
    • Parâmetros:
      • tipo: Tipo de alerta (como INFORMATION ou ERROR).
      • titulo: Título do alerta.
      • cabecalho: Cabeçalho para contexto do alerta.
      • conteudo: Mensagem do alerta.

Código Completo:

package br.com.professorclaytonandrade.sistemaservicojavafx.util;

import br.com.professorclaytonandrade.sistemaservicojavafx.StartApplication;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Util {

    public static void janelaModal(AnchorPane anchorPane, String telaFXML, 
                    String tituloJanela) throws IOException {
        Parent parent = carregarFXML(telaFXML);
        Stage popupStage = criarPopupStage(tituloJanela, parent, anchorPane);
        configurarJanela(popupStage);
        exibirJanelaModal(popupStage);
    }

    public static  boolean janelaModalComDados(AnchorPane anchorPane, String telaFXML, 
                    String tituloJanela, T objeto, Object controller) throws IOException {
        // Carregar o arquivo FXML
        FXMLLoader loader = new FXMLLoader(StartApplication.class.getResource(telaFXML));
        Parent parent = loader.load();
        // Obter o controlador do loader
        Object loadedController = loader.getController();
        // Verificar se o controlador passado é do tipo correto
        if (controller != null && loadedController != null && 
                controller.getClass() == loadedController.getClass()) {
            // Supondo que o controlador tem um método setDado
            try {
                // Aqui usamos a classe do objeto para encontrar o método setDado
                Method setDadoMethod = loadedController.getClass()
                    .getMethod("setDado", objeto.getClass());
                setDadoMethod.invoke(loadedController, objeto);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
                throw new RuntimeException("Erro ao invocar o método setDado no controlador.");
            }
        } else {
            throw new IllegalArgumentException("O controlador não corresponde 
                    ao esperado ou não pôde ser carregado.");
        }
        // Criar e exibir a janela modal
        Stage popupStage = criarPopupStage(tituloJanela, parent, anchorPane);
        configurarJanela(popupStage);
        exibirJanelaModal(popupStage);
        return true;
    }

    // Carrega o arquivo FXML fornecido
    private static Parent carregarFXML(String telaFXML) throws IOException {
        return FXMLLoader.load(StartApplication.class.getResource(telaFXML));
    }

    // Cria o Stage (janela) para o popup
    private static Stage criarPopupStage(String tituloJanela, Parent parent, AnchorPane anchorPane) {
        Stage popupStage = new Stage();
        popupStage.setTitle(tituloJanela);
        Scene scene = new Scene(parent);
        popupStage.setScene(scene);
        popupStage.initOwner(anchorPane.getScene().getWindow());
        return popupStage;
    }

    // Configura a janela, incluindo modal, centralização e redimensionamento
    private static void configurarJanela(Stage popupStage) {
        popupStage.initModality(Modality.APPLICATION_MODAL); // Mudança aqui para APPLICATION_MODAL
        popupStage.centerOnScreen();
        popupStage.setResizable(false);
    }

    // Exibe o Stage como um modal
    private static void exibirJanelaModal(Stage popupStage) {
        popupStage.showAndWait(); // Já está correto
    }

    public static boolean exibirAlerta(Alert.AlertType tipo, String titulo, 
            String cabecalho, String conteudo) {
        Alert alert = new Alert(tipo);
        alert.setTitle(titulo);
        alert.setHeaderText(cabecalho);
        alert.setContentText(conteudo);
        // Mostrar o alerta e esperar a resposta do usuário
        return alert.showAndWait().filter(response -> response == ButtonType.OK).isPresent();
    }
}