Files
pitiupi/criar-plugin.md
T
2026-09-25 10:46:16 -03:00

8.5 KiB

Como criar um plugin para o pitiupi ?

O objetivo aqui, além de criar plugins para esse sistema, é entender por que cada parte existe. Isso é importante porque a ideia de carregar código externo em tempo de execução aparece em várias ferramentas nas áreas da computação.


1. Visão rápida

O repositório contém projetos Java independentes:

Projeto O que é
pitiupi A ferramenta principal — P2P, janela, rede, e o "motor" que carrega plugins
pitiupi.chat Um plugin que adiciona um chat
pitiupi.screenCast Outro plugin que adiciona compartilhamento de tela

Cada plugin é compilado separadamente com ant e o .jar gerado é colocado (ou linkado) dentro da pasta pitiupi/plugins.

Quando o pitiupi principal inicia, ele varre essa pasta e carrega dinamicamente cada plugin encontrado, sem que o código principal precise conhecer o plugin em tempo de compilação.

Isso é possível graças a três partes que trabalham juntas:

  1. A interface Plugin — o contrato que todo plugin deve cumprir.
  2. O PluginLoader — a classe do pitiupi que varre a pasta plugins/ e carrega os .jar.
  3. O arquivo de registro do ServiceLoader — um arquivo de texto dentro do .jar que diz "esta classe aqui implementa Plugin".

2. O contrato: a interface Plugin

Todo plugin precisa implementar esta interface (pitiupi/src/pitiupi/plugin/Plugin.java):

public interface Plugin {
    public String getName();
    public String getAuthor();
    public String getVersion();
    public void createPlugin(MainWindow window);
    public Message getMessage();
    public void receiveMessage(byte[] message);
}

O que cada método faz:

  • getName() / getAuthor() / getVersion() — indentificação de dados básicos.
  • createPlugin(MainWindow window) — É aqui que o plugin cria sua interface gráfica, adiciona itens de menu na janela principal, etc.
  • getMessage() — Serve como modelo para a camada de rede saber que tipo de mensagem esse plugin envia.
  • receiveMessage(byte[] message) — recebe as mensagens que chegam pela rede e permite que o plugin interprete esses dados.

3. Como o pitiupi encontra e carrega os plugins

A classe PluginLoader (pitiupi/src/pitiupi/control/PluginLoader.java) faz o seguinte:

  1. Olha dentro da pasta plugins/ e lista todos os arquivos .jar.
  2. Cria um URLClassLoader — um "carregador de classes" isolado, que sabe ler código de dentro desses .jar.
  3. Usa ServiceLoader.load(Plugin.class, pluginClassLoader) para perguntar: "dentre essas classes, quais implementam Plugin?"
  4. Para cada plugin encontrado, chama createPlugin(mainWindow) e guarda o plugin numa lista.
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, pluginClassLoader);
 
for (Plugin plugin : serviceLoader) {
    plugin.createPlugin(this.mainWindow);
    this.mainWindow.getPlugins().add(plugin);
}

Obs: o ServiceLoader do Java não escaneia o bytecode procurando quem implementa a interface. Ele lê um arquivo de texto simples dentro do .jar. Se esse arquivo não existir ou estiver errado, o plugin é ignorado — mesmo que o código esteja perfeito.


4.META-INF/services

Dentro do projeto pitiupi.chat por exemplo, existe este arquivo:

pitiupi.chat/src/META-INF/services/pitiupi.plugin.Plugin

Repare em duas coisas:

  • A pasta se chama META-INF/services.
  • O nome do arquivo é o nome completo da interface: pitiupi.plugin.Plugin.
  • O conteúdo do arquivo é uma linha com o nome completo da classe que implementa essa interface:
pitiupi.chat.plugin.ChatPlugin

  • OPlugin diz "eu sei fazer o trabalho".
  • O arquivo em META-INF/services diz "aqui está quem sabe fazer o trabalho".
  • Sem o segundo, o pitiupi nunca fica sabendo que a classe existe.

5. Exemplo real: o ChatPlugin

Veja como o plugin de chat implementa o contrato — use isso como referência ao escrever o seu:

public class ChatPlugin implements Plugin {
 
    private ChatWindow chatWindow;
    private MainWindow mainWindow;
 
    @Override
    public void createPlugin(MainWindow mainWindow) {
        this.mainWindow = mainWindow;
        this.chatWindow = new ChatWindow(mainWindow);
        this.mainWindow.addMenu(new ChatMenu(chatWindow));
    }
 
    @Override
    public void receiveMessage(byte[] message) {
        // desserializa os bytes recebidos de volta em um objeto ChatMessage
        ByteArrayInputStream bis = new ByteArrayInputStream(message);
        try {
            ObjectInputStream ois = new ObjectInputStream(bis);
            if (ois.readObject() instanceof ChatMessage msg) {
                this.chatWindow.receiveChatMessage(msg);
            }
        } catch (IOException | ClassNotFoundException ex) {
            System.out.println(ex.toString());
        }
    }
 
    @Override
    public String getName() { return "Chat"; }
 
    @Override
    public String getAuthor() { return "Flávio, Tony e Gustavo "; }
 
    @Override
    public String getVersion() { return "0"; }
 
    @Override
    public Message getMessage() { return new ChatMessage(); }
}

Em createPlugin é onde o bum de "aparecer na tela" acontece: ele cria a janela do plugin e adiciona um menu na janela principal do pitiupi.


6. Passo a passo: criando um plugin novo do zero

Vamos chamar o plugin de exemplo de SeuPlugin.

Passo 1 — Criar a estrutura do projeto

Se você quiser um jeito mais facil sem se aprofundar, você pode criar o projeto de duas formas:

  • Mais simples: copie um projeto Ant existente, como pitiupi.chat por exemplo, e remova o código específico do Chat.
  • Outra opção: crie um novo projeto no NetBeans usando "Java Class Library", mantendo a mesma estrutura de pastas.
pitiupi.seuplugin/
├── build.xml
├── nbproject/
└── src/
    ├── META-INF/services/
    └── pitiupi/seuplugin/plugin/

Passo 2 — Adicionar o pitiupi.jar como dependência

O plugin precisa do arquivo pitiupi/dist/pitiupi.jar para acessar Plugin e MainWindow.

  • Se criou do zero: configure o pitiupi.jar como dependência.
  • Se copiou um projeto existente: essa configuração já está pronta.
  • Se criou pelo NetBeans: Properties → Libraries → Add JAR/Folder → selecione pitiupi/dist/pitiupi.jar.

Passo 3 — Implementar a interface Plugin

Crie src/pitiupi/seuplugin/plugin/SeuPlugin.java:

package pitiupi.seuplugin.plugin;
 
import pitiupi.GUI.MainWindow;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin;
 
public class SeuPlugin implements Plugin {
 
    @Override
    public void createPlugin(MainWindow window) {
        System.out.println("SeuPlugin foi carregado!");
        // aqui você criaria sua janela/menu, como o ChatPlugin faz
    }
 
    @Override
    public void receiveMessage(byte[] message) {
        // trate as mensagens recebidas da rede aqui
    }
 
    @Override
    public String getName() { return "MeuPlugin"; }
 
    @Override
    public String getAuthor() { return "Seu Nome"; }
 
    @Override
    public String getVersion() { return "0.1"; }
 
    @Override
    public Message getMessage() { return null; } // troque por uma Message real, se tiver
}

Passo 4 — Registrar o plugin no ServiceLoader

Crie o arquivo (sem extensão):

src/META-INF/services/pitiupi.plugin.Plugin

Com uma única linha dentro, o nome completo da sua classe:

pitiupi.seuplugin.plugin.MeuPlugin

Passo 5 — Compilar com Ant

cd pitiupi.seuplugin
ant jar

Isso gera o .jar em pitiupi.seuplugin/dist/.

Passo 6 — Linkar o .jar na pasta de plugins

cd pitiupi/plugins
ln -s ../../pitiupi.meuplugin/dist/*.jar .

Usar link simbólico (ln -s) é melhor durante o desenvolvimento: toda vez que você recompilar o plugin, não precisa copiar de novo.

7. Geral

Plugin.java  →  "o que todo plugin precisa saber fazer" (o contrato)
MeuPlugin.java  →  "como EU faço esse trabalho" (a implementação)
META-INF/services/pitiupi.plugin.Plugin  →  "aqui está quem faz o trabalho" (o registro)
PluginLoader.java  →  "eu procuro registros e chamo quem se registrou" (o carregador, já pronto no pitiupi)

Entendendo essas quatro peças, criar qualquer plugin novo — chat, screencast, ou algo totalmente diferente (arquivos, jogos, sensores) — é sempre o mesmo padrão: implementar a interface, registrar no META-INF/services, compilar e linkar.