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:
- A interface
Plugin— o contrato que todo plugin deve cumprir. - O
PluginLoader— a classe do pitiupi que varre a pastaplugins/e carrega os.jar. - O arquivo de registro do
ServiceLoader— um arquivo de texto dentro do.jarque diz "esta classe aqui implementaPlugin".
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:
- Olha dentro da pasta
plugins/e lista todos os arquivos.jar. - Cria um
URLClassLoader— um "carregador de classes" isolado, que sabe ler código de dentro desses.jar. - Usa
ServiceLoader.load(Plugin.class, pluginClassLoader)para perguntar: "dentre essas classes, quais implementamPlugin?" - 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
- O
Plugindiz "eu sei fazer o trabalho". - O arquivo em
META-INF/servicesdiz "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.chatpor 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.jarcomo 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.