Mover algumas classes para a camada de modelo

This commit is contained in:
GustavoHMDS committed 2026-10-01 22:21:19 -03:00
1 parent 67813e4881
commit 6538dac8af
25 files changed
+170 -183

No files matched your search

-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_26_PREVIEW" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
+2 -2
View File
@@ -468,10 +468,10 @@ MeusPlugins
|
+---services
|
+---pitiupi.plugin.Plugin
+---pitiupi.model.Plugin
```
O arquivo `pitiupi.plugin.Plugin` deve conter o nome completo das classes que
O arquivo `pitiupi.model.Plugin` deve conter o nome completo das classes que
implementam a interface Plugin:
```
+1 -1
View File
@@ -46,7 +46,7 @@ public class MainWindow extends javax.swing.JFrame {
public MainWindow(String userName, int port) {
initMenu();
pluginManager = new PluginManager(this);
pluginManager = new PluginManager();
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
guiManager = new GuiManager(this);
@@ -9,7 +9,7 @@ import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import pitiupi.control.PeerInfo;
import pitiupi.model.User;
import pitiupi.control.PeerListener;
/**
@@ -39,10 +39,10 @@ public class OnlineUsersPanel extends JPanel implements PeerListener {
* @param peers lista atualizada de usuários ativos na rede.
*/
@Override
public void onPeersChanged(List<PeerInfo> peers) {
public void onPeersChanged(List<User> peers) {
SwingUtilities.invokeLater(() -> {
listModel.clear();
for (PeerInfo peer : peers) {
for (User peer : peers) {
listModel.addElement(peer.toString());
}
});
@@ -1,8 +1,8 @@
package pitiupi.control;
import pitiupi.GUI.MainWindow;
import pitiupi.net.HeartbeatMessage;
import pitiupi.net.Message;
import pitiupi.model.Messages.HeartbeatMessage;
import pitiupi.model.Messages.Message;
import pitiupi.net.tcp.SocketTCP;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.net.udp.SocketUDP;
@@ -1,6 +1,5 @@
package pitiupi.control;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
@@ -10,10 +9,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import pitiupi.GUI.MainWindow;
import pitiupi.net.HeartbeatMessage;
import pitiupi.model.User;
import pitiupi.model.Messages.HeartbeatMessage;
import java.util.UUID;
/**
* Gerencia o mecanismo de heartbeat da aplicação.
@@ -29,7 +27,7 @@ public class HeartbeatManager {
private static final long HEARTBEAT_INTERVAL_MS = 5000;
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
private final ConnectionManager connectionManager;
private final Map<String, PeerInfo> peers = new ConcurrentHashMap<>();
private final Map<String, User> peers = new ConcurrentHashMap<>();
private final List<PeerListener> listeners = new CopyOnWriteArrayList<>();
private ScheduledExecutorService scheduler;
private final String instanceId = UUID.randomUUID().toString();
@@ -87,7 +85,7 @@ public class HeartbeatManager {
String key = msg.getInstanceId();
boolean isNew = !peers.containsKey(key);
PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp()));
User peer = peers.computeIfAbsent(key, k -> new User(msg.getUserName(), from, msg.getTimestamp()));
peer.touch(System.currentTimeMillis());
if (isNew) {
@@ -104,7 +102,7 @@ public class HeartbeatManager {
}
private void notifyListeners() {
List<PeerInfo> snapshot = new ArrayList<>(peers.values());
List<User> snapshot = new ArrayList<>(peers.values());
for (PeerListener l : listeners) {
l.onPeersChanged(snapshot);
}
@@ -137,7 +135,7 @@ public class HeartbeatManager {
*
* @return lista contendo os peers atualmente conhecidos.
*/
public List<PeerInfo> getPeers() {
public List<User> getPeers() {
return new ArrayList<>(peers.values());
}
}
@@ -1,5 +1,7 @@
package pitiupi.control;
import pitiupi.model.User;
import java.util.List;
/**
@@ -17,5 +19,5 @@ public interface PeerListener {
*
* @param peers lista atualizada de usuários ativos.
*/
void onPeersChanged(List<PeerInfo> peers);
void onPeersChanged(List<User> peers);
}
@@ -1,73 +0,0 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package pitiupi.control;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import pitiupi.GUI.MainWindow;
import pitiupi.Main;
import pitiupi.plugin.Plugin;
/**
* Responsável por localizar, carregar e registrar os plugins
* presentes no diretório {@code plugins}.
*
* <p>Os plugins são descobertos utilizando {@link java.util.ServiceLoader}
* e devem estar empacotados como arquivos JAR contendo a configuração
* {@code META-INF/services} correspondente.</p>
* @author flavio
*/
public class PluginLoader {
private final pitiupi.GUI.MainWindow mainWindow;
public PluginLoader(MainWindow main) {
this.mainWindow = main;
}
public void loadPlugins(List<Plugin> plugins, HashSet<String> loadedJars, ConnectionManager connectionManager, GuiManager guiManager) {
File pluginDir = new File("plugins");
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (jarFiles == null || jarFiles.length == 0) {
System.out.println("No plugins found in the " + pluginDir.getAbsolutePath() + " directory.");
return;
}
for (File file : jarFiles) {
if (loadedJars.contains(file.getName())) {
continue;
}
System.out.println("Discovered plugin package: " + file.getName());
try {
URLClassLoader classLoader = new URLClassLoader(
new URL[]{file.toURI().toURL()},
Main.class.getClassLoader()
);
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader);
for (Plugin plugin : serviceLoader) {
System.out.println("Loading: " + plugin.getName());
plugin.createPlugin(connectionManager, guiManager);
plugins.add(plugin);
loadedJars.add(file.getName());
}
} catch (Exception e) {
System.out.println("Failed to load plugin " + file.getName());
e.printStackTrace();
}
}
}
}
+51 -19
View File
@@ -1,29 +1,27 @@
package pitiupi.control;
import pitiupi.GUI.MainWindow;
import pitiupi.Main;
import pitiupi.net.tcp.TcpConnection;
import pitiupi.plugin.Plugin;
import pitiupi.plugin.PrivateConnectionPlugin;
import pitiupi.model.Plugin;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ServiceLoader;
public class PluginManager {
private final PluginLoader pluginLoader;
private final List<Plugin> plugins;
private final HashSet<String> loadedJars;
public PluginManager(MainWindow main) {
this.pluginLoader = new PluginLoader(main);
public PluginManager() {
this.plugins = new ArrayList<>();
this.loadedJars = new HashSet<>();
}
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
pluginLoader.loadPlugins(plugins, loadedJars, connectionManager, guiManager);
}
public void handlePublicMessage(byte[] msg) {
for (Plugin plugin : plugins) {
plugin.receiveMessage(msg);
@@ -41,28 +39,62 @@ public class PluginManager {
Plugin plugin = getPlugin(connection.getReceiver());
if (plugin == null) return;
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
privatePlugin.receiveBytes(connection, msg);
}
plugin.receiveBytes(connection, msg);
}
public boolean handleConnection(TcpConnection connection, String receiverId) {
Plugin plugin = getPlugin(receiverId);
if (plugin == null) return false;
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
privatePlugin.receivePrivateConnection(connection);
return true;
}
plugin.receivePrivateConnection(connection);
return false;
}
public void handleDisconnection(TcpConnection connection, String receiverId) {
Plugin plugin = getPlugin(receiverId);
if (plugin instanceof PrivateConnectionPlugin privatePlugin) {
privatePlugin.onSocketClosed(connection);
if (plugin == null) return;
plugin.onSocketClosed(connection);
}
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
File pluginDir = new File("plugins");
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (jarFiles == null || jarFiles.length == 0) {
System.out.println("No plugins found in the " + pluginDir.getAbsolutePath() + " directory.");
return;
}
for (File file : jarFiles) {
if (loadedJars.contains(file.getName())) {
continue;
}
System.out.println("Discovered plugin package: " + file.getName());
try {
URLClassLoader classLoader = new URLClassLoader(
new URL[]{file.toURI().toURL()},
Main.class.getClassLoader()
);
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader);
for (Plugin plugin : serviceLoader) {
System.out.println("Loading: " + plugin.getName());
plugin.createPlugin(connectionManager, guiManager);
plugins.add(plugin);
loadedJars.add(file.getName());
}
} catch (Exception e) {
System.out.println("Failed to load plugin " + file.getName());
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,6 @@
package pitiupi.model.Menus;
public abstract class Menu {
public abstract String getText();
public abstract MenuCategory getCategory();
}
@@ -0,0 +1,7 @@
package pitiupi.model.Menus;
public enum MenuCategory {
JOGOS,
SOCIAL,
STREAMING
}
@@ -0,0 +1,5 @@
package pitiupi.model.Menus;
public abstract class MulticastMenu extends Menu {
public abstract void onClick();
}
@@ -0,0 +1,7 @@
package pitiupi.model.Menus;
import pitiupi.model.User;
public abstract class UnicastMenu extends Menu {
public abstract void onClick(User user);
}
@@ -1,4 +1,6 @@
package pitiupi.net;
package pitiupi.model.Messages;
import pitiupi.model.Messages.Message;
/**
* Mensagem utilizada para sinalizar a presença de um usuário na rede.
@@ -1,6 +1,6 @@
package pitiupi.net.tcp;
package pitiupi.model.Messages;
import pitiupi.net.Message;
import pitiupi.net.tcp.TcpConnection;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -8,12 +8,11 @@ import java.io.ObjectInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SysInfoMessage extends Message {
private final SysInfoType type;
public class InfoMessage extends Message {
private final InfoType type;
private final String message;
public SysInfoMessage(String message, SysInfoType type) {
public InfoMessage(String message, InfoType type) {
this.message = message;
this.type = type;
}
@@ -22,29 +21,29 @@ public class SysInfoMessage extends Message {
return message;
}
public SysInfoType getType() {
public InfoType getType() {
return type;
}
public static void sendSystemInfo(TcpConnection connection, SysInfoType type, String description) {
public static void sendSystemInfo(TcpConnection connection, InfoType type, String description) {
try {
SysInfoMessage error = new SysInfoMessage(description, type);
InfoMessage error = new InfoMessage(description, type);
connection.send(error.toByteArray());
} catch (IOException ex) {
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
}
public static SysInfoMessage parseSystemInfo(byte[] bytes) {
public static InfoMessage parseSystemInfo(byte[] bytes) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis)) {
Object object = ois.readObject();
if (object instanceof SysInfoMessage) {
return (SysInfoMessage) object;
if (object instanceof InfoMessage) {
return (InfoMessage) object;
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(SysInfoMessage.class.getName()).log(Level.WARNING, null, ex);
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
}
return null;
}
@@ -1,6 +1,6 @@
package pitiupi.net.tcp;
package pitiupi.model.Messages;
public enum SysInfoType {
public enum InfoType {
CONNECTION_REJECTED,
RECEIVER_NOT_FOUND,
MESSAGE_REJECTED,
@@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package pitiupi.net;
package pitiupi.model.Messages;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -2,12 +2,11 @@
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
*/
package pitiupi.plugin;
package pitiupi.model;
import pitiupi.GUI.MainWindow;
import pitiupi.control.ConnectionManager;
import pitiupi.control.GuiManager;
import pitiupi.net.Message;
import pitiupi.model.Messages.Message;
import pitiupi.net.tcp.TcpConnection;
/**
@@ -18,28 +17,29 @@ import pitiupi.net.tcp.TcpConnection;
*
* @author flavio
*/
public interface Plugin {
public abstract class Plugin {
public String getName();
public String getAuthor();
public String getVersion();
public String getIdentifier();
public abstract String getName();
public abstract String getAuthor();
public abstract String getVersion();
public abstract String getIdentifier();
/**
* Inicializa o plugin.
*
* <p>Este método é chamado pela aplicação após o carregamento do plugin.</p>
*
* @param window janela principal da aplicação.
* @param connectionManager interface com o sistema de conexões da aplicação.
* @param guiManager interface com a janela principal da aplicação
*/
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager);
public abstract void createPlugin(ConnectionManager connectionManager, GuiManager guiManager);
/**
* Retorna uma mensagem utilizada pelo plugin.
*
* @return mensagem criada pelo plugin.
*/
public Message getMessage();
public abstract Message getMessage();
/**
* Recebe uma mensagem enviada pela aplicação.
@@ -49,6 +49,15 @@ public interface Plugin {
*
* @param message mensagem serializada recebida.
*/
public void receiveMessage(byte[] message);
public abstract void receiveMessage(byte[] message);
public boolean receivePrivateConnection(TcpConnection connection) {
return false;
}
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
return false;
}
public boolean onSocketClosed(TcpConnection connection) {
return false;
}
}
@@ -1,4 +1,4 @@
package pitiupi.control;
package pitiupi.model;
import java.net.InetAddress;
/**
@@ -10,13 +10,13 @@ import java.net.InetAddress;
*
* @author tony
*/
public class PeerInfo {
public class User {
private final String userName;
private final InetAddress address;
private volatile long lastSeen;
public PeerInfo(String userName, InetAddress address, long lastSeen) {
public User(String userName, InetAddress address, long lastSeen) {
this.userName = userName;
this.address = address;
this.lastSeen = lastSeen;
@@ -1,6 +1,8 @@
package pitiupi.net.tcp;
import pitiupi.net.Message;
import pitiupi.model.Messages.Message;
import pitiupi.model.Messages.InfoMessage;
import pitiupi.model.Messages.InfoType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -152,7 +154,7 @@ public class ConnectionFactory {
Object object = ois.readObject();
if (object instanceof Message message) {
SysInfoMessage sysInfo = SysInfoMessage.parseSystemInfo(bytes);
InfoMessage sysInfo = InfoMessage.parseSystemInfo(bytes);
if (sysInfo != null) return null;
@@ -161,25 +163,25 @@ public class ConnectionFactory {
}
}
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.CONNECTION_REJECTED,
InfoType.CONNECTION_REJECTED,
"The message was not from an allowed type."
);
}
catch (SocketTimeoutException ex) {
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.CONNECTION_REJECTED,
InfoType.CONNECTION_REJECTED,
"No handshake was received within the allowed time."
);
}
catch (ClassNotFoundException | IOException ex) {
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.INTERNAL_ERROR,
InfoType.INTERNAL_ERROR,
"The server could not process the handshake."
);
}
@@ -206,17 +208,17 @@ public class ConnectionFactory {
String receiver = handshake.getReceiver();
if (sender == null || sender.isBlank()) {
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.CONNECTION_REJECTED,
InfoType.CONNECTION_REJECTED,
"Invalid Sender: " + sender + "."
);
return false;
}
if (receiver == null || receiver.isBlank()) {
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.CONNECTION_REJECTED,
InfoType.CONNECTION_REJECTED,
"Invalid Receiver: " + receiver + "."
);
return false;
@@ -1,6 +1,6 @@
package pitiupi.net.tcp;
import pitiupi.net.Message;
import pitiupi.model.Messages.Message;
class HandshakeMessage extends Message {
private final boolean isPrivate;
+8 -7
View File
@@ -2,8 +2,9 @@ package pitiupi.net.tcp;
import pitiupi.control.ConnectionManager;
import pitiupi.control.PluginManager;
import pitiupi.net.Message;
import pitiupi.plugin.PrivateConnectionPlugin;
import pitiupi.model.Messages.Message;
import pitiupi.model.Messages.InfoMessage;
import pitiupi.model.Messages.InfoType;
import java.io.*;
import java.net.*;
@@ -273,9 +274,9 @@ public class SocketTCP extends Thread {
if (pluginManager.handleConnection(connection, connection.getReceiver()))
privateConnectionReceiver(connection);
else {
SysInfoMessage.sendSystemInfo(
InfoMessage.sendSystemInfo(
connection,
SysInfoType.RECEIVER_NOT_FOUND,
InfoType.RECEIVER_NOT_FOUND,
"This peer does not have the plugin: " + connection.getReceiver()
);
}
@@ -300,7 +301,7 @@ public class SocketTCP extends Thread {
try {
byte[] bytes = connection.receive();
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(bytes);
InfoMessage sysMessage = InfoMessage.parseSystemInfo(bytes);
if (sysMessage == null) {
pluginManager.handlePublicMessage(bytes);
@@ -345,7 +346,7 @@ public class SocketTCP extends Thread {
while (running) {
try {
byte[] message = connection.receive();
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
if (sysMessage == null) {
pluginManager.handlePrivateMessage(connection, message);
@@ -390,7 +391,7 @@ public class SocketTCP extends Thread {
try (ByteArrayInputStream bis = new ByteArrayInputStream(message);
ObjectInputStream ois = new ObjectInputStream(bis)) {
SysInfoMessage sysMessage = SysInfoMessage.parseSystemInfo(message);
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
if (sysMessage != null) continue;
//TODO: Ainda não existem mensagens trocadas entre o sistema.
@@ -1,7 +1,9 @@
package pitiupi.net.tcp;
import pitiupi.control.PeerInfo;
import pitiupi.model.Messages.InfoType;
import pitiupi.model.User;
import pitiupi.control.PeerListener;
import pitiupi.model.Messages.InfoMessage;
import java.io.IOException;
import java.net.InetAddress;
@@ -99,7 +101,7 @@ class TcpConnectionManager implements PeerListener {
* @param reason motivo do encerramento que será informado ao peer.
*/
public void closeConnection(TcpConnection connection, String reason) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
closeConnection(connection);
}
@@ -153,7 +155,7 @@ class TcpConnectionManager implements PeerListener {
public void closeByAdress(InetAddress address, String reason) {
List<TcpConnection> connections = privateConnections.get(address);
for (TcpConnection connection : connections) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
}
closeByAdress(address);
}
@@ -205,7 +207,7 @@ class TcpConnectionManager implements PeerListener {
public void closeAllConnections(String reason) {
for (List<TcpConnection> connections : privateConnections.values()) {
for (TcpConnection connection : connections) {
SysInfoMessage.sendSystemInfo(connection, SysInfoType.CONNECTION_CLOSED, reason);
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
}
}
closeAllConnections();
@@ -245,8 +247,8 @@ class TcpConnectionManager implements PeerListener {
*/
@Override
public void onPeersChanged(List<PeerInfo> peers) {
Set<InetAddress> activePeers = peers.stream().map(PeerInfo::getAddress).collect(Collectors.toSet());
public void onPeersChanged(List<User> peers) {
Set<InetAddress> activePeers = peers.stream().map(User::getAddress).collect(Collectors.toSet());
for (InetAddress address : publicConnections.keySet()) {
if (!activePeers.contains(address)) {
+2 -4
View File
@@ -5,7 +5,6 @@
*/
package pitiupi.net.udp;
import pitiupi.GUI.MainWindow;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInput;
@@ -16,9 +15,8 @@ import java.util.logging.Logger;
import pitiupi.control.ConnectionManager;
import pitiupi.control.PluginManager;
import pitiupi.net.HeartbeatMessage;
import pitiupi.net.Message;
import pitiupi.plugin.Plugin;
import pitiupi.model.Messages.HeartbeatMessage;
import pitiupi.model.Messages.Message;
/**
* Gerencia a comunicação UDP multicast e unicast da aplicação.
@@ -1,9 +0,0 @@
package pitiupi.plugin;
import pitiupi.net.tcp.TcpConnection;
public interface PrivateConnectionPlugin extends Plugin {
public void receivePrivateConnection(TcpConnection connection);
public void receiveBytes(TcpConnection connection, byte[] bytes);
public void onSocketClosed(TcpConnection connection);
}