258 lines
8.5 KiB
Markdown
258 lines
8.5 KiB
Markdown
# 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<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
|
|
```
|
|
----
|
|
`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. |