# 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`): ```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. ```java ServiceLoader 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 ``` - O`Plugin` 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: ```java 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`: ```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 ```bash cd pitiupi.seuplugin ant jar ``` Isso gera o `.jar` em `pitiupi.seuplugin/dist/`. ### Passo 6 : Linkar o `.jar` na pasta de plugins ```bash 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.