From 1b36eebe9b5185828843f9d5a1eb3543abf737db Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Feb 2025 12:30:15 +0000 Subject: [PATCH 01/63] toString method created for the abstract class OctetArray.java This class will be used as a parent class for IPAddress.java and SubnetMask.java Signed-off-by: Lukas Bauza --- src/OctetArray.java | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/OctetArray.java b/src/OctetArray.java index 0ed9c38..e5fb617 100644 --- a/src/OctetArray.java +++ b/src/OctetArray.java @@ -1,21 +1,39 @@ public abstract class OctetArray { private byte[] octetArray; - private int octetArraySize; + private String separator; + private int size; - public OctetArray(int octetArraySize) { - this.octetArraySize = octetArraySize; + public byte[] getOctetArray() { + return octetArray; } - public byte[] getOctetArray() { return octetArray; } - public String toString() { - String[] byteString = new String[octetArraySize]; - for (int i = 0; i < octetArraySize; i++) { - byteString[i] = Integer.toString(Byte.toUnsignedInt(octetArray[i])); + String bytesString = ""; + for (int i = 0; i < size; i++) { + if (!(i == size - 1)) { + bytesString = bytesString + Integer.toString(Byte.toUnsignedInt(octetArray[i])) + separator; + } else { + // Octet at the very right doesn't need the separator. + bytesString = bytesString + Integer.toString(Byte.toUnsignedInt(octetArray[i])); + } } + return bytesString; + } + + // TODO: Start here next + public void setOctetArray(String octetArray) { + + } - System.out.println(String.join(".", byteString)); + public void setSpecificByte(int byteLocation, byte byteVal) { + + } + + public boolean equals(Object object) { + return true; + } - return String.join(".", byteString); + public byte[] octetArrayStringToByteArray(String octetArray) { + return null; } } From c4bcf434b5ea2c3fe70fe6c52eb119ee92b64805 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Thu, 13 Feb 2025 12:22:44 +0000 Subject: [PATCH 02/63] OctetArray.java created for the generalisation of the IPAddress.java class and SubnetMask.java classes. Signed-off-by: Lukas Bauza --- network_protocol_simulation.iml | 28 ++++++++++++++++++++++++++++ src/IPAddress.java | 24 ++++++++++++++---------- src/OctetArray.java | 10 +++++++--- src/OctetArrayTest.java | 5 ----- test/IPAddressTest.java | 12 ++++++++++++ 5 files changed, 61 insertions(+), 18 deletions(-) delete mode 100644 src/OctetArrayTest.java create mode 100644 test/IPAddressTest.java diff --git a/network_protocol_simulation.iml b/network_protocol_simulation.iml index c3dc060..1a35fa4 100644 --- a/network_protocol_simulation.iml +++ b/network_protocol_simulation.iml @@ -4,6 +4,8 @@ + + @@ -17,5 +19,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/IPAddress.java b/src/IPAddress.java index fc7a280..db42d2a 100644 --- a/src/IPAddress.java +++ b/src/IPAddress.java @@ -1,8 +1,10 @@ -public class IPAddress { +public class IPAddress extends OctetArray { //private String ipAddress; private byte[] ipAddress = new byte[4]; public IPAddress(String ipAddress) { + // Set up the OctetArray parent class, for its constructor. + super(".", 4); // constructor used for checking that each segment is the right type and in the right range. // if a wrong type is entered, it will throw out an error into main, which // can be caught and used for error handling. @@ -14,6 +16,8 @@ public IPAddress(String ipAddress) { } public IPAddress() { + // Set up the OctetArray parent class, for its constructor. + super(".", 4); this.ipAddress = new byte[4]; } @@ -21,15 +25,15 @@ public byte[] getIpAddress() { return ipAddress; } - public String toString() { - // Needs to be converted to an unsigned int, to not have minus values. As bytes are represented in negatives. - String byte0 = Integer.toString(Byte.toUnsignedInt(ipAddress[0])); - String byte1 = Integer.toString(Byte.toUnsignedInt(ipAddress[1])); - String byte2 = Integer.toString(Byte.toUnsignedInt(ipAddress[2])); - String byte3 = Integer.toString(Byte.toUnsignedInt(ipAddress[3])); - - return byte0 + "." + byte1 + "." + byte2 + "." + byte3; - } +// public String toString() { +// // Needs to be converted to an unsigned int, to not have minus values. As bytes are represented in negatives. +// String byte0 = Integer.toString(Byte.toUnsignedInt(ipAddress[0])); +// String byte1 = Integer.toString(Byte.toUnsignedInt(ipAddress[1])); +// String byte2 = Integer.toString(Byte.toUnsignedInt(ipAddress[2])); +// String byte3 = Integer.toString(Byte.toUnsignedInt(ipAddress[3])); +// +// return byte0 + "." + byte1 + "." + byte2 + "." + byte3; +// } public void setIpAddress(String ipAddress) { //this.ipAddress = ipAddress; diff --git a/src/OctetArray.java b/src/OctetArray.java index e5fb617..d35de48 100644 --- a/src/OctetArray.java +++ b/src/OctetArray.java @@ -1,7 +1,11 @@ public abstract class OctetArray { private byte[] octetArray; private String separator; - private int size; + + public OctetArray(String separator, int size) { + this.octetArray = new byte[size]; + this.separator = separator; + } public byte[] getOctetArray() { return octetArray; @@ -9,8 +13,8 @@ public byte[] getOctetArray() { public String toString() { String bytesString = ""; - for (int i = 0; i < size; i++) { - if (!(i == size - 1)) { + for (int i = 0; i < octetArray.length; i++) { + if (!(i == octetArray.length - 1)) { bytesString = bytesString + Integer.toString(Byte.toUnsignedInt(octetArray[i])) + separator; } else { // Octet at the very right doesn't need the separator. diff --git a/src/OctetArrayTest.java b/src/OctetArrayTest.java deleted file mode 100644 index 4817534..0000000 --- a/src/OctetArrayTest.java +++ /dev/null @@ -1,5 +0,0 @@ -import static org.junit.jupiter.api.Assertions.*; - -class OctetArrayTest { - -} \ No newline at end of file diff --git a/test/IPAddressTest.java b/test/IPAddressTest.java new file mode 100644 index 0000000..2fc2b9d --- /dev/null +++ b/test/IPAddressTest.java @@ -0,0 +1,12 @@ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class IPAddressTest { + @Test + void convertFromIPAddressToString() { + IPAddress ipAddress = new IPAddress("192.168.1.1"); + // Check if the toString method still creates the correct formatting. + assertEquals("192.168.1.1", ipAddress.toString()); + } +} \ No newline at end of file From 81ff314acc7749deaaff74d29d5839d8ee6e26f3 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sat, 22 Feb 2025 16:50:31 +0000 Subject: [PATCH 03/63] Removed the gui/ package, as no longer need to use other directories for new features. Signed-off-by: Lukas Bauza --- src/gui/Main.java | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/gui/Main.java diff --git a/src/gui/Main.java b/src/gui/Main.java deleted file mode 100644 index c2acf29..0000000 --- a/src/gui/Main.java +++ /dev/null @@ -1,4 +0,0 @@ -package gui; - -public class Main { -} From a935204a96338747eb1073274e0857ebf6f8521b Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sat, 22 Feb 2025 17:15:16 +0000 Subject: [PATCH 04/63] Re-added the gui/ package, as it will make it easier to organise and for the sake of naming conflicts Signed-off-by: Lukas Bauza --- src/gui/Frame.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/gui/Frame.java diff --git a/src/gui/Frame.java b/src/gui/Frame.java new file mode 100644 index 0000000..99eda39 --- /dev/null +++ b/src/gui/Frame.java @@ -0,0 +1,14 @@ +package gui; + +import javax.swing.*; + +// Inherit the JFrame class, to create a frame for the window. +public class Frame extends JFrame { + Frame() { + this.setTitle("OSPF Simulation"); + this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + this.setResizable(false); + this.setSize(800, 600); + this.setVisible(true); // Make frame visible + } +} From a40fe27c752cc9a833b71ad83452614a57f03c25 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sat, 22 Feb 2025 17:15:45 +0000 Subject: [PATCH 05/63] Basic JFrame. Signed-off-by: Lukas Bauza --- src/Main.java | 304 +------------------------------------------------- 1 file changed, 4 insertions(+), 300 deletions(-) diff --git a/src/Main.java b/src/Main.java index 099e1c6..f1bcebf 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,309 +1,13 @@ +import gui.Frame; + import java.util.ArrayList; import java.util.Scanner; // delete this comment public class Main { public static void main(String[] args) { - - // All the created PCs within the simulation. - ArrayList pcList = new ArrayList<>(); - // All the created Routers within the simulation. - ArrayList routerList = new ArrayList<>(); - // Keeps track of all the default gateways that have been assigned to a PC. - ArrayList assignedDefaultGatewayList = new ArrayList<>(); - - System.out.print("########################################\n"); - System.out.print("Welcome to the PING protocol simulation.\n"); - System.out.print("########################################\n\n"); - - // Variables used for menu navigation and options. - String menu = "main"; // Menu (main, pc, or router) - int exit = 0; // When to exit the program - int option = -1; // User selected menu option - int pcSelection = -1; // pc index for pcList selection - int routerSelection = -1; // router index for routerList selection - Scanner scanner = new Scanner(System.in); // Scanner for user input - PingProtocol pingProtocol = new PingProtocol(); // Instance used for simulating ping between devices. - - // Loop until the user decides to exit. - do { - if (menu.equals("main")) { - // Display the main menu. - displayMainMenu(pcList, routerList); - - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - option = scanner.nextInt(); // Read the user selection - - // Handle user selection for the main menu. - switch (option) { - case 1: - // Create PC and add it to the list of PC's - pcList.add(createPC()); - break; - - case 2: - // Delete PC - // This will need to show a list of PC's where the user can select what PC to delete. - System.out.println("Select a PC to delete"); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - // -1 because it is based on the index of the ArrayList of the PC. - pcSelection = scanner.nextInt() - 1; - - // Attempt to remove a PC from the pcList, and handle the error if there is one. - try { - pcList.remove(pcSelection); - } catch (IndexOutOfBoundsException e) { - System.out.println("PC does not exist"); - } - - break; - - case 3: - // Select PC - System.out.println("Select a PC from the list"); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - pcSelection = scanner.nextInt() - 1; // convert input to 0 based index. - - try { - // Check if the PC exists within the pcList. - pcList.get(pcSelection); - menu = "pc"; // Switch to PC menu - } catch (IndexOutOfBoundsException e) { - System.out.println("PC does not exist"); - menu = "main"; // Switch back to main menu if error - } - - // Reset the option for the PC menu. - option = -1; - break; - - case 4: - // Create a Router - routerList.add(createRouter()); - break; - - case 5: - // Delete a Router - System.out.println("Select a Router to delete"); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - routerSelection = scanner.nextInt() - 1; - - // Attempt remove the selected Router. - try { - routerList.remove(routerSelection); - } catch (IndexOutOfBoundsException e) { - System.out.println("Router does not exist"); - } - - break; - - case 6: - // Select a Router - System.out.println("Select a Router from the list"); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - routerSelection = scanner.nextInt() - 1; - - try { - // Check if the Router exists within the routerList. - routerList.get(routerSelection); - menu = "router"; - } catch (IndexOutOfBoundsException e) { - System.out.println("Router does not exist"); - menu = "main"; - } - - // Reset the option for the PC menu. - option = -1; - break; - - case 7: - // Exit the program. - System.out.println("Bye"); - exit = 1; - break; - - default: - System.out.println("Invalid option"); - break; - } - - } else if (menu.equals("pc")) { - displayPCMenu(pcList, pcSelection); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - option = scanner.nextInt(); - // Removes any new line characters that nextInt() didn't use. If not then any nextLine() function used - // on the scanner, will automatically have \n as its input. - scanner.nextLine(); - PC pc = pcList.get(pcSelection); - - switch (option) { - // Change name - case 1: - System.out.println("Enter the new name for the PC"); - String name = scanner.nextLine(); - pc.setName(name); - break; - - // Change IP address - case 2: - System.out.println("Enter the new IP for the PC"); - String ipAddressString = scanner.nextLine(); - // Create a new instance IPAddress with the inputted ip address String - IPAddress ipAddress = createIP(ipAddressString); - - System.out.println("Enter the subnet mask for the PC"); - String subnetMaskString = scanner.nextLine(); - // Create a new instance SubnetMask with the inputted subnet mask String - SubnetMask subnetMask = createSubnetMask(subnetMaskString); - - // Set the port with the ip address and the subnet mask for the pc. - pc.setPortFA00(ipAddress, subnetMask); - break; - - // Set default gateway for the PC - case 3: - System.out.println("Enter the IP for the default gateway"); - String defaultGatewayString = scanner.nextLine(); - // Create a default gateway ip address for the pc. - IPAddress defaultGatewayIP = createIP(defaultGatewayString); - - System.out.println("Enter the subnet mask for the default gateway"); - // Create a default gateway subnet mask for the pc. - String defaultGatewaySubnetMaskString = scanner.nextLine(); - SubnetMask defaultGatewaySubnet = createSubnetMask(defaultGatewaySubnetMaskString); - - if(!checkDefaultGatewayExists(routerList, defaultGatewayIP, defaultGatewaySubnet)) { - // Checks to see whether the default gateway exists within the network. - System.out.println("The provided default gateway does not exist within the network."); - } else if (checkDefaultGatewayAssigned(assignedDefaultGatewayList, defaultGatewayIP, defaultGatewaySubnet)) { - // Checks if the default gateway is not assigned, to another PC. - System.out.println("The provided default gateway is already assigned to another device."); - } else { - // Sets the default gateway for the PC. - pc.setDefaultGateway(defaultGatewayIP, defaultGatewaySubnet); - // Add the PC to the list of assigned default gateways, only if the PC is not already in the - // list - if(!assignedDefaultGatewayList.contains(pc)) { - assignedDefaultGatewayList.add(pc); - } - } - - break; - - case 4: - // Start the ping process with another device. - System.out.println("Enter the IP address you want to ping to"); - // Creates the destination IP address from the user input. - IPAddress destinationIP = new IPAddress(scanner.nextLine()); - // retreive the index based on the destination Ip address from the pcList - int destinationPCIndex = getIndexFromPCListWithIP(destinationIP, pcList); - // If ther destinationPCIndex is correctly returned, it means it exists. - if(destinationPCIndex != -1) { - pingProtocol.ping(pc, pcList.get(destinationPCIndex), pcList, routerList); - } else { - System.out.println("Destination PC does not exist"); - } - break; - - case 5: - // Return to the main menu. - menu = "main"; - break; - - default: - System.out.println("Invalid option"); - break; - } - } else if (menu.equals("router")) { - displayRouterMenu(routerList, routerSelection); - // Checks whether the scanner had received an int or not, if it doesn't it prnts the below. - while (!scanner.hasNextInt()) { - System.out.println("Invalid input. Please enter a valid number."); - scanner.next(); // Consume the invalid input - } - option = scanner.nextInt(); - // Removes any new line characters that nextInt() didn't use. If not then any nextLine() function used - // on the scanner, will automatically have \n as its input. - scanner.nextLine(); - Router router = routerList.get(routerSelection); - - switch (option) { - case 1: - System.out.println("Enter the new name for the router"); - String name = scanner.nextLine(); - router.setName(name); - break; - - // cases 2 to case 4 are used for creating the IP address for the ports of the router, as well - // as the subnet mask. - case 2: - System.out.println("Enter IP address for GigabitEthernet 0/0"); - String ipAddress00String = scanner.nextLine(); - IPAddress ipAddress00 = createIP(ipAddress00String); - - System.out.println("Enter the subnet mask for GigabitEthernet 0/0"); - String subnetMask00String = scanner.nextLine(); - SubnetMask subnetMask00 = createSubnetMask(subnetMask00String); - - router.setPortGig00(ipAddress00, subnetMask00); - break; - - case 3: - System.out.println("Enter IP address for GigabitEthernet 0/1"); - String ipAddress01String = scanner.nextLine(); - IPAddress ipAddress01 = createIP(ipAddress01String); - - System.out.println("Enter the subnet mask for GigabitEthernet 0/1"); - String subnetMask01String = scanner.nextLine(); - SubnetMask subnetMask01 = createSubnetMask(subnetMask01String); - - router.setPortGig01(ipAddress01, subnetMask01); - break; - - case 4: - System.out.println("Enter IP address for GigabitEthernet 0/2"); - String ipAddress02String = scanner.nextLine(); - IPAddress ipAddress02 = createIP(ipAddress02String); - - System.out.println("Enter the subnet mask for GigabitEthernet 0/2"); - String subnetMask02String = scanner.nextLine(); - SubnetMask subnetMask02 = createSubnetMask(subnetMask02String); - - router.setPortGig02(ipAddress02, subnetMask02); - break; - - case 5: - // Go back to the main menu - menu = "main"; - break; - } - } - // Check if user wants to exit program. - } while (exit == 0); + // Set the window. + Frame frame = new Frame(); } public static IPAddress createIP(String ipAddressString) { From 4a10fda5f1409a4a0028d6ba6390515d4ded9058 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 23 Feb 2025 09:15:24 +0000 Subject: [PATCH 06/63] Icons for PC and Router. Signed-off-by: Lukas Bauza --- images/pc_icon.png | Bin 0 -> 157298 bytes images/router_icon.png | Bin 0 -> 168990 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/pc_icon.png create mode 100644 images/router_icon.png diff --git a/images/pc_icon.png b/images/pc_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..293925cd3ed40054e2a8407ebfb5e6a3c60aca2b GIT binary patch literal 157298 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 literal 0 HcmV?d00001 diff --git a/images/router_icon.png b/images/router_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1eda2bae70dc6eaa0a3cba5ddbfc717ac0535ad GIT binary patch literal 168990 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 Date: Sun, 23 Feb 2025 17:43:21 +0000 Subject: [PATCH 07/63] Removal of gui package, and Frame class. Too complicated to deal with Signed-off-by: Lukas Bauza --- src/gui/Frame.java | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 src/gui/Frame.java diff --git a/src/gui/Frame.java b/src/gui/Frame.java deleted file mode 100644 index 99eda39..0000000 --- a/src/gui/Frame.java +++ /dev/null @@ -1,14 +0,0 @@ -package gui; - -import javax.swing.*; - -// Inherit the JFrame class, to create a frame for the window. -public class Frame extends JFrame { - Frame() { - this.setTitle("OSPF Simulation"); - this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X - this.setResizable(false); - this.setSize(800, 600); - this.setVisible(true); // Make frame visible - } -} From db8e119f7d2980fcd660f007aee9cb622e9cb414 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 23 Feb 2025 17:43:51 +0000 Subject: [PATCH 08/63] Simple buttons added, with two panels. Signed-off-by: Lukas Bauza --- src/Main.java | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Main.java b/src/Main.java index f1bcebf..ccbcc96 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,13 +1,55 @@ -import gui.Frame; +// imports the gui package and the Frame class into this class. +import javax.swing.*; +import java.awt.*; import java.util.ArrayList; import java.util.Scanner; // delete this comment public class Main { public static void main(String[] args) { - // Set the window. - Frame frame = new Frame(); + JFrame frame = new JFrame(); + + frame.setTitle("OSPF Simulation"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + frame.setResizable(false); + frame.setSize(1000, 800); + frame.setLayout(new GridLayout()); + + JPanel itemPanel = getJPanel(); + // rows: 0 is used for allowing as many rows as possible. This fills widgets vertically. + itemPanel.setLayout(new GridLayout(0, 1)); + + JPanel playgroundPanel = new JPanel(); + + frame.add(itemPanel); + frame.add(playgroundPanel); + frame.setVisible(true); // Make frame visible + } + + private static JPanel getJPanel() { + JButton routerButton = new JButton("Router"); + // Function for creating allowing the button to listen for a click, and thus performing the + // following function (which is a lambda function). + routerButton.addActionListener(e -> { + System.out.println("Router button clicked"); + }); + + JButton pcButton = new JButton("PC"); + pcButton.addActionListener(e -> { + System.out.println("PC button clicked"); + }); + + JButton connectButton = new JButton("Connect"); + connectButton.addActionListener(e -> { + System.out.println("Connect button clicked"); + }); + + JPanel itemPanel = new JPanel(); + itemPanel.add(routerButton); + itemPanel.add(pcButton); + itemPanel.add(connectButton); + return itemPanel; } public static IPAddress createIP(String ipAddressString) { From 3ea88008ce3a7a5dd26b256a4fe9039d3826ebd7 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Mar 2025 14:22:42 +0000 Subject: [PATCH 09/63] Branch created for setting up the new main menu. Signed-off-by: Lukas Bauza --- src/IPAddress.java | 2 +- src/Main.java | 93 ++++++++++++++++++++++++++++++++++++--------- src/Router.java | 17 +++++++-- src/SubnetMask.java | 2 +- 4 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/IPAddress.java b/src/IPAddress.java index db42d2a..5d1b382 100644 --- a/src/IPAddress.java +++ b/src/IPAddress.java @@ -1,6 +1,6 @@ public class IPAddress extends OctetArray { //private String ipAddress; - private byte[] ipAddress = new byte[4]; + private byte[] ipAddress; public IPAddress(String ipAddress) { // Set up the OctetArray parent class, for its constructor. diff --git a/src/Main.java b/src/Main.java index ccbcc96..7233ee2 100644 --- a/src/Main.java +++ b/src/Main.java @@ -4,6 +4,7 @@ import java.awt.*; import java.util.ArrayList; import java.util.Scanner; +import java.util.concurrent.atomic.AtomicReference; // delete this comment public class Main { @@ -13,43 +14,99 @@ public static void main(String[] args) { frame.setTitle("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X frame.setResizable(false); - frame.setSize(1000, 800); + frame.setSize(1000, 300); frame.setLayout(new GridLayout()); - JPanel itemPanel = getJPanel(); - // rows: 0 is used for allowing as many rows as possible. This fills widgets vertically. - itemPanel.setLayout(new GridLayout(0, 1)); + Router r1 = new Router("R1"); + Router r2 = new Router("R2"); + Router r3 = new Router("R3"); - JPanel playgroundPanel = new JPanel(); + // Atomic variable is needed for the sake of syncronising across the difference UI elements, as they won't be + // updated. + AtomicReference selectedRouter = new AtomicReference<>(r1); - frame.add(itemPanel); - frame.add(playgroundPanel); - frame.setVisible(true); // Make frame visible - } - - private static JPanel getJPanel() { - JButton routerButton = new JButton("Router"); + JButton routerButton = new JButton(r1.getName()); // Function for creating allowing the button to listen for a click, and thus performing the // following function (which is a lambda function). routerButton.addActionListener(e -> { - System.out.println("Router button clicked"); + System.out.println("Router " + r1.getName() + " clicked"); + selectedRouter.set(r1); }); - JButton pcButton = new JButton("PC"); + JButton pcButton = new JButton(r2.getName()); pcButton.addActionListener(e -> { - System.out.println("PC button clicked"); + System.out.println("Router " + r2.getName() + " clicked"); + selectedRouter.set(r2); }); - JButton connectButton = new JButton("Connect"); + JButton connectButton = new JButton(r3.getName()); connectButton.addActionListener(e -> { - System.out.println("Connect button clicked"); + System.out.println("Router " + r3.getName() + " clicked"); + selectedRouter.set(r3); }); JPanel itemPanel = new JPanel(); itemPanel.add(routerButton); itemPanel.add(pcButton); itemPanel.add(connectButton); - return itemPanel; + // rows: 0 is used for allowing as many rows as possible. This fills widgets vertically. + itemPanel.setLayout(new GridLayout(0, 1)); + + JPanel descriptionPanel = new JPanel(); + + descriptionPanel.setLayout(new GridLayout(8, 2)); + + descriptionPanel.add(new JLabel("Name:")); + JTextField nameField = new JTextField(selectedRouter.get().getName()); + descriptionPanel.add(nameField); + + descriptionPanel.add(new JLabel("Gig0/0 IP Address:")); + JTextField gig00IP = new JTextField(); + descriptionPanel.add(gig00IP); + + descriptionPanel.add(new JLabel("Gig0/0 Subnet Mask:")); + JTextField gig00Mask = new JTextField(); + descriptionPanel.add(gig00Mask); + + descriptionPanel.add(new JLabel("Gig0/1 IP Address:")); + JTextField gig01IP = new JTextField(); + descriptionPanel.add(gig01IP); + + descriptionPanel.add(new JLabel("Gig0/1 Subnet Mask:")); + JTextField gig01Mask = new JTextField(); + descriptionPanel.add(gig01Mask); + + descriptionPanel.add(new JLabel("Gig0/2 IP Address:")); + JTextField gig02IP = new JTextField(); + descriptionPanel.add(gig02IP); + + descriptionPanel.add(new JLabel("Gig0/2 Subnet Mask:")); + JTextField gig02Mask = new JTextField(); + descriptionPanel.add(gig02Mask); + + JButton submitButton = new JButton("Submit"); + submitButton.addActionListener(e -> { + Router router = selectedRouter.get(); + router.setPortGig00IPAddress(new IPAddress(gig00IP.getText())); + System.out.println(new IPAddress(gig00IP.getText())); + router.setPortGig00SubnetMask(new SubnetMask(gig00Mask.getText())); + router.setPortGig01IPAddress(new IPAddress(gig01IP.getText())); + router.setPortGig01SubnetMask(new SubnetMask(gig01Mask.getText())); + router.setPortGig02IPAddress(new IPAddress(gig02IP.getText())); + router.setPortGig02SubnetMask(new SubnetMask(gig02Mask.getText())); + + System.out.println("Updated Router: " + router.getName()); + System.out.println("Gig0/0: " + router.getPortGig00().getIpAddress() + " / " + router.getPortGig00().getSubnetMask()); + System.out.println("Gig0/1: " + router.getPortGig01().getIpAddress() + " / " + router.getPortGig01().getSubnetMask()); + System.out.println("Gig0/2: " + router.getPortGig02().getIpAddress() + " / " + router.getPortGig02().getSubnetMask()); + }); + descriptionPanel.add(submitButton, BorderLayout.SOUTH); + + frame.add(descriptionPanel, BorderLayout.CENTER); + + frame.add(itemPanel); + frame.add(descriptionPanel); + frame.setVisible(true); // Make frame visible } public static IPAddress createIP(String ipAddressString) { diff --git a/src/Router.java b/src/Router.java index 7cc4b57..18eb60b 100644 --- a/src/Router.java +++ b/src/Router.java @@ -19,20 +19,31 @@ public Router(String name) { public NIC getPortGig00() { return super.getNICList().get(0); } - public void setPortGig00(IPAddress ipAddress, SubnetMask subnetMask) { + public void setPortGig00IPAddress(IPAddress ipAddress) { super.getNICList().get(0).setIpAddress(ipAddress); + } + + public void setPortGig00SubnetMask(SubnetMask subnetMask) { super.getNICList().get(0).setSubnetMask(subnetMask); } public NIC getPortGig01() { return super.getNICList().get(1); } - public void setPortGig01(IPAddress ipAddress, SubnetMask subnetMask) { + + public void setPortGig01IPAddress(IPAddress ipAddress) { super.getNICList().get(1).setIpAddress(ipAddress); + } + + public void setPortGig01SubnetMask(SubnetMask subnetMask) { super.getNICList().get(1).setSubnetMask(subnetMask); } public NIC getPortGig02() { return super.getNICList().get(2); } - public void setPortGig02(IPAddress ipAddress, SubnetMask subnetMask) { + + public void setPortGig02IPAddress(IPAddress ipAddress) { super.getNICList().get(2).setIpAddress(ipAddress); + } + + public void setPortGig02SubnetMask(SubnetMask subnetMask) { super.getNICList().get(2).setSubnetMask(subnetMask); } } diff --git a/src/SubnetMask.java b/src/SubnetMask.java index 3e496a7..ad027a9 100644 --- a/src/SubnetMask.java +++ b/src/SubnetMask.java @@ -1,5 +1,5 @@ public class SubnetMask { - private byte[] subnetMask = new byte[4]; + private byte[] subnetMask; public SubnetMask(String subnetMask) { // constructor used for checking that each segment is the right type and in the right range. From b80c0934d66404fd6979690ca48b0a21914c2664 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Mar 2025 14:28:51 +0000 Subject: [PATCH 10/63] Custom/preconfigured network button options template. Signed-off-by: Lukas Bauza --- src/Main.java | 93 +++++---------------------------------------------- 1 file changed, 8 insertions(+), 85 deletions(-) diff --git a/src/Main.java b/src/Main.java index 7233ee2..4ad60db 100644 --- a/src/Main.java +++ b/src/Main.java @@ -17,95 +17,18 @@ public static void main(String[] args) { frame.setSize(1000, 300); frame.setLayout(new GridLayout()); - Router r1 = new Router("R1"); - Router r2 = new Router("R2"); - Router r3 = new Router("R3"); - - // Atomic variable is needed for the sake of syncronising across the difference UI elements, as they won't be - // updated. - AtomicReference selectedRouter = new AtomicReference<>(r1); - - JButton routerButton = new JButton(r1.getName()); - // Function for creating allowing the button to listen for a click, and thus performing the - // following function (which is a lambda function). - routerButton.addActionListener(e -> { - System.out.println("Router " + r1.getName() + " clicked"); - selectedRouter.set(r1); + JButton customNetworkButton = new JButton("Custom Network"); + customNetworkButton.addActionListener(e -> { + System.out.println("Custom network button pressed."); }); - JButton pcButton = new JButton(r2.getName()); - pcButton.addActionListener(e -> { - System.out.println("Router " + r2.getName() + " clicked"); - selectedRouter.set(r2); + JButton preconfiguredNetworkButton = new JButton("Preconfigured Network"); + preconfiguredNetworkButton.addActionListener(e -> { + System.out.println("Preconfigured network button pressed."); }); - JButton connectButton = new JButton(r3.getName()); - connectButton.addActionListener(e -> { - System.out.println("Router " + r3.getName() + " clicked"); - selectedRouter.set(r3); - }); - - JPanel itemPanel = new JPanel(); - itemPanel.add(routerButton); - itemPanel.add(pcButton); - itemPanel.add(connectButton); - // rows: 0 is used for allowing as many rows as possible. This fills widgets vertically. - itemPanel.setLayout(new GridLayout(0, 1)); - - JPanel descriptionPanel = new JPanel(); - - descriptionPanel.setLayout(new GridLayout(8, 2)); - - descriptionPanel.add(new JLabel("Name:")); - JTextField nameField = new JTextField(selectedRouter.get().getName()); - descriptionPanel.add(nameField); - - descriptionPanel.add(new JLabel("Gig0/0 IP Address:")); - JTextField gig00IP = new JTextField(); - descriptionPanel.add(gig00IP); - - descriptionPanel.add(new JLabel("Gig0/0 Subnet Mask:")); - JTextField gig00Mask = new JTextField(); - descriptionPanel.add(gig00Mask); - - descriptionPanel.add(new JLabel("Gig0/1 IP Address:")); - JTextField gig01IP = new JTextField(); - descriptionPanel.add(gig01IP); - - descriptionPanel.add(new JLabel("Gig0/1 Subnet Mask:")); - JTextField gig01Mask = new JTextField(); - descriptionPanel.add(gig01Mask); - - descriptionPanel.add(new JLabel("Gig0/2 IP Address:")); - JTextField gig02IP = new JTextField(); - descriptionPanel.add(gig02IP); - - descriptionPanel.add(new JLabel("Gig0/2 Subnet Mask:")); - JTextField gig02Mask = new JTextField(); - descriptionPanel.add(gig02Mask); - - JButton submitButton = new JButton("Submit"); - submitButton.addActionListener(e -> { - Router router = selectedRouter.get(); - router.setPortGig00IPAddress(new IPAddress(gig00IP.getText())); - System.out.println(new IPAddress(gig00IP.getText())); - router.setPortGig00SubnetMask(new SubnetMask(gig00Mask.getText())); - router.setPortGig01IPAddress(new IPAddress(gig01IP.getText())); - router.setPortGig01SubnetMask(new SubnetMask(gig01Mask.getText())); - router.setPortGig02IPAddress(new IPAddress(gig02IP.getText())); - router.setPortGig02SubnetMask(new SubnetMask(gig02Mask.getText())); - - System.out.println("Updated Router: " + router.getName()); - System.out.println("Gig0/0: " + router.getPortGig00().getIpAddress() + " / " + router.getPortGig00().getSubnetMask()); - System.out.println("Gig0/1: " + router.getPortGig01().getIpAddress() + " / " + router.getPortGig01().getSubnetMask()); - System.out.println("Gig0/2: " + router.getPortGig02().getIpAddress() + " / " + router.getPortGig02().getSubnetMask()); - }); - descriptionPanel.add(submitButton, BorderLayout.SOUTH); - - frame.add(descriptionPanel, BorderLayout.CENTER); - - frame.add(itemPanel); - frame.add(descriptionPanel); + frame.add(customNetworkButton); + frame.add(preconfiguredNetworkButton); frame.setVisible(true); // Make frame visible } From 33aeceb46a12c315927569d4b8de5649c223c773 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Mar 2025 15:01:51 +0000 Subject: [PATCH 11/63] Added label for the user, and changed the font settings. Signed-off-by: Lukas Bauza --- src/Main.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Main.java b/src/Main.java index 4ad60db..ca71298 100644 --- a/src/Main.java +++ b/src/Main.java @@ -14,19 +14,32 @@ public static void main(String[] args) { frame.setTitle("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X frame.setResizable(false); - frame.setSize(1000, 300); - frame.setLayout(new GridLayout()); + frame.setSize(600, 300); + frame.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. + + JLabel welcomeLabel = new JLabel(""" + + Welcome to the OSPF simulation protocol!
+ Please select whether you want to create a custom network or a preconfigured network. + """, SwingConstants.CENTER); // html is used to add line break + welcomeLabel.setBackground(Color.lightGray); + welcomeLabel.setOpaque(true); + JButton customNetworkButton = new JButton("Custom Network"); + Font networkButtonFont = new Font(customNetworkButton.getFont().getName(), Font.BOLD, 20); + customNetworkButton.setFont(networkButtonFont); customNetworkButton.addActionListener(e -> { System.out.println("Custom network button pressed."); }); JButton preconfiguredNetworkButton = new JButton("Preconfigured Network"); + preconfiguredNetworkButton.setFont(networkButtonFont); preconfiguredNetworkButton.addActionListener(e -> { System.out.println("Preconfigured network button pressed."); }); + frame.add(welcomeLabel); frame.add(customNetworkButton); frame.add(preconfiguredNetworkButton); frame.setVisible(true); // Make frame visible From bfdf0949fa69ef3f7158dcb31e6ada2616886fb7 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Mar 2025 18:51:05 +0000 Subject: [PATCH 12/63] Removed necessary methods from master branch. Signed-off-by: Lukas Bauza --- src/Main.java | 341 ++------------------------------------------------ 1 file changed, 12 insertions(+), 329 deletions(-) diff --git a/src/Main.java b/src/Main.java index ca71298..dab4fda 100644 --- a/src/Main.java +++ b/src/Main.java @@ -6,16 +6,17 @@ import java.util.Scanner; import java.util.concurrent.atomic.AtomicReference; -// delete this comment public class Main { public static void main(String[] args) { - JFrame frame = new JFrame(); - frame.setTitle("OSPF Simulation"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X - frame.setResizable(false); - frame.setSize(600, 300); - frame.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. + // **** Start Menu **** + JFrame start_menu = new JFrame(); + + start_menu.setTitle("OSPF Simulation"); + start_menu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + start_menu.setResizable(false); + start_menu.setSize(600, 300); + start_menu.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. JLabel welcomeLabel = new JLabel(""" @@ -39,327 +40,9 @@ public static void main(String[] args) { System.out.println("Preconfigured network button pressed."); }); - frame.add(welcomeLabel); - frame.add(customNetworkButton); - frame.add(preconfiguredNetworkButton); - frame.setVisible(true); // Make frame visible - } - - public static IPAddress createIP(String ipAddressString) { - // tries to create an IPAddress, if it doesn't work then it will catch - // the error from within the class and prints it out here. - boolean valid; - - do { - try { - new IPAddress(ipAddressString); - valid = true; - } catch (IllegalArgumentException e) { - Scanner scanner = new Scanner(System.in); - System.out.print("Please enter a valid IP address: "); - ipAddressString = scanner.nextLine(); - valid = false; - } - } while (!valid); - - return new IPAddress(ipAddressString); - } - - public static SubnetMask createSubnetMask(String subnetString) { - // tries to create an IPAddress, if it doesn't work then it will catch - // the error from within the class and prints it out here. - boolean valid; - - do { - try { - new SubnetMask(subnetString); - valid = true; - } catch (IllegalArgumentException e) { - Scanner scanner = new Scanner(System.in); - System.out.print("Please enter a valid subnet mask: "); - subnetString = scanner.nextLine(); - valid = false; - } - } while (!valid); - - return new SubnetMask(subnetString); - } - - public static PC createPC() { - // this just prompts the user to enter the needed information for creating a PC. It will ask for the name, IP, - // and subnet mask and create the object. - - Scanner scanner = new Scanner(System.in); - - System.out.print("Please enter the name for the PC: "); - String name = scanner.nextLine(); - - System.out.print("Please enter the IP address for " + name + ": "); - String ipInput = scanner.nextLine(); - IPAddress ip = createIP(ipInput); - - System.out.println("Enter the subnet mask for the PC"); - String subnetMaskInput = scanner.nextLine(); - SubnetMask subnetMask = createSubnetMask(subnetMaskInput); - - return new PC(name, ip, subnetMask); - } - - public static Router createRouter() { - // this prompts the user to create the router with the input of its name only. The user can change the other - // details of the router through different options. - Scanner scanner = new Scanner(System.in); - - System.out.print("Please enter the name for the Router: "); - String name = scanner.nextLine(); - - return new Router(name); - } - - public static void displayMainMenu(ArrayList pcList, ArrayList routerList) { - // Options displayed for the user to choose on what actions they want to take. - String[] menuOptions = { - "Create PC ", - "Delete PC ", - "Select PC ", - "Create Router", - "Delete Router", - "Select Router", - "Exit Program " - }; - - System.out.print("------------------------------------------------------------------------------------\n"); - System.out.println("Options \t\t\t| List of Devices (PC on Left, Router on Right)"); - System.out.print("------------------------------------------------------------------------------------\n"); - - // Picks the largest array/list to iterate through and prints out all the options from the menu as well as - // the devices to the right of the options. - int max1 = Math.max(pcList.size(), menuOptions.length); - int max2 = Math.max(max1, routerList.size()); - // Iterate through the largest array and print out the menu. - for(int i = 0; i < max2; i++) { - int lineNum = i + 1; - - if (i < menuOptions.length) { - // This is for printing out the menu and the devices. - System.out.print(lineNum + "." + " " + menuOptions[i] + "\t\t" + "|"); - } - if (i < pcList.size()) { - // This is for printing out the pc's only, once all the options are printed. - PC pc = pcList.get(i); - System.out.print(" " + lineNum + "." + " " + pc.getName()); - } - if (i < routerList.size()) { - // This is for printing out the router's only. - Router router = routerList.get(i); - System.out.print(" " + lineNum + "." + " " + router.getName()); - } - // Print a new line after every line. - System.out.println(); - } - - // Some space for user input and the menu. - System.out.println(); - } - - public static void displayPCMenu(ArrayList pcList, Integer pcIndex) { - // Options displayed for the user to choose on what actions they want to take. - String[] menuOptions = { - "Change PC name ", - "Change FastEthernet 0/0 IP address ", - "Change default gateway ", - "Ping another device ", - "Return to main menu " - }; - - String[] pcAttributes = { - "Name: " + pcList.get(pcIndex).getName(), - "FastEthernet 0/0 IP address: " + pcList.get(pcIndex).getPortFA00().getIpAddress(), - "FastEthernet 0/0 subnet mask: " + pcList.get(pcIndex).getPortFA00().getSubnetMask(), - "FastEthernet 0/0 MAC address: " + pcList.get(pcIndex).getPortFA00().getMacAddress(), - "Default gateway IP address: " + pcList.get(pcIndex).getDefaultGatewayIPAddress(), - "Default gateway subnet mask: " + pcList.get(pcIndex).getDefaultGatewaySubnetMask() - }; - - System.out.print("------------------------------------------------------------------------------------\n"); - System.out.println("Options \t\t\t\t\t\t\t\t| PC Information"); - System.out.print("------------------------------------------------------------------------------------\n"); - - // Picks the largest array/list to iterate through and prints out all the options from the menu as well as - // the devices to the right of the options. - for(int i = 0; i < Math.max(pcAttributes.length, menuOptions.length); i++) { - int lineNum = i + 1; - - if (i < menuOptions.length) { - // This is for printing out the menu and the devices. - System.out.print(lineNum + "." + " " + menuOptions[i] + "\t\t" + "|"); - } else { - // Prints out the tabs and barrier for the menu within the router information - System.out.print("\t\t\t\t\t\t\t\t\t\t\t" + "|"); - } - if (i < pcAttributes.length) { - // Prints out the PC details - System.out.print(" " + pcAttributes[i]); - } - // Print a new line after every line. - System.out.println(); - } - - // Some space for user input and the menu. - System.out.println(); - } - - public static void displayRouterMenu(ArrayList routerList, Integer routerIndex) { - // Options displayed for the user to choose on what actions they want to take. - String[] menuOptions = { - "Change Router name ", - "Change GigabitEthernet 0/0 IP address ", - "Change GigabitEthernet 0/1 IP address ", - "Change GigabitEthernet 0/2 IP address ", - "Return to main menu " - }; - - String[] routerAttributes = { - "Name: " + routerList.get(routerIndex).getName(), - "GigabitEthernet 0/0 IP address: " + routerList.get(routerIndex).getPortGig00().getIpAddress(), - "GigabitEthernet 0/0 subnet mask: " + routerList.get(routerIndex).getPortGig00().getSubnetMask(), - "GigabitEthernet 0/0 MAC address: " + routerList.get(routerIndex).getPortGig00().getMacAddress(), - "GigabitEthernet 0/1 IP address: " + routerList.get(routerIndex).getPortGig01().getIpAddress(), - "GigabitEthernet 0/1 subnet mask: " + routerList.get(routerIndex).getPortGig01().getSubnetMask(), - "GigabitEthernet 0/1 MAC address: " + routerList.get(routerIndex).getPortGig01().getMacAddress(), - "GigabitEthernet 0/2 IP address: " + routerList.get(routerIndex).getPortGig02().getIpAddress(), - "GigabitEthernet 0/2 subnet mask: " + routerList.get(routerIndex).getPortGig02().getSubnetMask(), - "GigabitEthernet 0/2 MAC address: " + routerList.get(routerIndex).getPortGig02().getMacAddress(), - }; - - System.out.print("------------------------------------------------------------------------------------\n"); - System.out.println("Options \t\t\t\t\t\t\t\t\t\t| Router Information"); - System.out.print("------------------------------------------------------------------------------------\n"); - - // Picks the largest array/list to iterate through and prints out all the options from the menu as well as - // the devices to the right of the options. - for(int i = 0; i < Math.max(routerAttributes.length, menuOptions.length); i++) { - int lineNum = i + 1; - - if (i < menuOptions.length) { - // This is for printing out the menu and the devices. - System.out.print(lineNum + "." + " " + menuOptions[i] + "\t\t" + "|"); - } else { - // Prints out the tabs and barrier for the menu within the router information - System.out.print("\t\t\t\t\t\t\t\t\t\t\t\t" + "|"); - } - if (i < routerAttributes.length) { - // Prints out the Router details - System.out.print(" " + routerAttributes[i]); - } - // Print a new line after every line. - System.out.println(); - } - - // Some space for user input and the menu. - System.out.println(); - } - - public static boolean gig00Match(ArrayList routerList, IPAddress defaultGatewayIPAddress, SubnetMask subnetMask) { - // Checks if the router has any assigned subnet mask or IP address to its ports. - for(Router router : routerList) { - if((router.getPortGig00().getSubnetMask() == null) || (router.getPortGig00().getIpAddress() == null)) { - // Move to the next router. - continue; - } - // Checks if the subnet mask or the IP address equal to the required subnet mask and IP for the default - // gateway. - if((router.getPortGig00().getSubnetMask().equals(subnetMask)) && (router.getPortGig00().getIpAddress().equals(defaultGatewayIPAddress))) { - System.out.println("Found match!"); - System.out.println("Router name:" + router.getName()); - System.out.println("Port name: " + router.getPortGig00().getName()); - System.out.println("Port IP address:" + router.getPortGig00().getIpAddress()); - System.out.println("Port Subnet Mask: " + router.getPortGig00().getSubnetMask()); - return true; - } - - System.out.println(router.getPortGig00().getIpAddress() + " " + router.getPortGig00().getSubnetMask()); - System.out.println(defaultGatewayIPAddress + " " + subnetMask); - } - - return false; - } - - public static boolean gig01Match(ArrayList routerList, IPAddress defaultGatewayIPAddress, SubnetMask subnetMask) { - // Checks if the router has any assigned subnet mask or IP address to its ports. - for(Router router : routerList) { - if((router.getPortGig01().getSubnetMask() == null) || (router.getPortGig01().getIpAddress() == null)) { - // Move to the next router. - continue; - } - // Checks if the subnet mask or the IP address equal to the required subnet mask and IP for the default - // gateway. - if((router.getPortGig01().getSubnetMask().equals(subnetMask)) && (router.getPortGig01().getIpAddress().equals(defaultGatewayIPAddress))) { - System.out.println("Found match!"); - System.out.println("Router name: " + router.getName()); - System.out.println("Port name: " + router.getPortGig01().getName()); - System.out.println("Port IP address: " + router.getPortGig01().getIpAddress()); - System.out.println("Port Subnet Mask: " + router.getPortGig01().getSubnetMask()); - return true; - } - - System.out.println(router.getPortGig01().getIpAddress() + " " + router.getPortGig01().getSubnetMask()); - } - - return false; - } - - public static boolean gig02Match(ArrayList routerList, IPAddress defaultGatewayIPAddress, SubnetMask subnetMask) { - // Checks if the router has any assigned subnet mask or IP address to its ports. - for(Router router : routerList) { - if((router.getPortGig02().getSubnetMask() == null) || (router.getPortGig02().getIpAddress() == null)) { - // Move to the next router. - continue; - } - // Checks if the subnet mask or the IP address equal to the required subnet mask and IP for the default - // gateway. - if((router.getPortGig02().getSubnetMask().equals(subnetMask)) && (router.getPortGig02().getIpAddress().equals(defaultGatewayIPAddress))) { - System.out.println("Found match!"); - System.out.println("Router name:" + router.getName()); - System.out.println("Port name: " + router.getPortGig02().getName()); - System.out.println("Port IP address:" + router.getPortGig02().getIpAddress()); - System.out.println("Port Subnet Mask: " + router.getPortGig02().getSubnetMask()); - return true; - } - - System.out.println(router.getPortGig02().getIpAddress() + " " + router.getPortGig02().getSubnetMask()); - } - - return false; - } - - public static boolean checkDefaultGatewayExists(ArrayList routerList, IPAddress defaultGatewayIPAddress, SubnetMask subnetMask) { - // This is just a holder for the previous gigXXMatch functions, it checks if any of the interfaces matched with the required - // subnet mask and ip address. - return gig00Match(routerList, defaultGatewayIPAddress, subnetMask) || (gig01Match(routerList, defaultGatewayIPAddress, subnetMask)) || - gig02Match(routerList, defaultGatewayIPAddress, subnetMask); - } - - public static boolean checkDefaultGatewayAssigned(ArrayList assignedDefaultGatewayList, IPAddress ipAddress, SubnetMask subnetMask) { - // This checks if a default gateway is already assigned to another PC for a given router interface. - for(PC pc : assignedDefaultGatewayList) { - if((pc.getDefaultGatewaySubnetMask().equals(subnetMask)) && (pc.getDefaultGatewayIPAddress().equals(ipAddress))) { - return true; - } - } - - return false; - } - - public static int getIndexFromPCListWithIP(IPAddress ip, ArrayList pcList) { - // Search through all the PCs in the network and see if there is an available PC. - for(int i = 0; i < pcList.size(); i++) { - if(pcList.get(i).getNICList().get(0).getIpAddress().equals(ip)) { - // return the index from the ArrayList - return i; - } - } - - return -1; + start_menu.add(welcomeLabel); + start_menu.add(customNetworkButton); + start_menu.add(preconfiguredNetworkButton); + start_menu.setVisible(true); // Make start_menu visible } } \ No newline at end of file From ddd60d4e5ed3d3f4245b37cddc1e725fd542c9fe Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 10 Mar 2025 19:32:29 +0000 Subject: [PATCH 13/63] Window dynamically changes between custom network display, and prebuilt network display. Signed-off-by: Lukas Bauza --- src/Main.java | 58 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Main.java b/src/Main.java index dab4fda..ae5c4ad 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,14 +9,12 @@ public class Main { public static void main(String[] args) { - // **** Start Menu **** - JFrame start_menu = new JFrame(); - - start_menu.setTitle("OSPF Simulation"); - start_menu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X - start_menu.setResizable(false); - start_menu.setSize(600, 300); - start_menu.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. + JFrame frame = new MainMenuFrame(); + frame.setTitle("OSPF Simulation"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + frame.setResizable(false); + frame.setSize(600, 300); + frame.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. JLabel welcomeLabel = new JLabel(""" @@ -30,19 +28,49 @@ public static void main(String[] args) { JButton customNetworkButton = new JButton("Custom Network"); Font networkButtonFont = new Font(customNetworkButton.getFont().getName(), Font.BOLD, 20); customNetworkButton.setFont(networkButtonFont); - customNetworkButton.addActionListener(e -> { - System.out.println("Custom network button pressed."); - }); JButton preconfiguredNetworkButton = new JButton("Preconfigured Network"); preconfiguredNetworkButton.setFont(networkButtonFont); + + frame.add(welcomeLabel); + frame.add(customNetworkButton); + frame.add(preconfiguredNetworkButton); + frame.setVisible(true); // Make start_menu_frame visible + + JLabel prebuiltNetworkLabel = new JLabel("Prebuilt Network"); + + JLabel customNetworkLabel = new JLabel("Custom Network"); + preconfiguredNetworkButton.addActionListener(e -> { System.out.println("Preconfigured network button pressed."); + + frame.remove(welcomeLabel); + frame.remove(customNetworkButton); + frame.remove(preconfiguredNetworkButton); + + frame.setTitle("OSPF Simulation: Prebuilt Network"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + frame.setResizable(false); + frame.setSize(1000, 600); + frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. + + frame.add(prebuiltNetworkLabel); }); - start_menu.add(welcomeLabel); - start_menu.add(customNetworkButton); - start_menu.add(preconfiguredNetworkButton); - start_menu.setVisible(true); // Make start_menu visible + customNetworkButton.addActionListener(e -> { + System.out.println("Custom network button pressed."); + + frame.remove(welcomeLabel); + frame.remove(customNetworkButton); + frame.remove(preconfiguredNetworkButton); + + frame.setTitle("OSPF Simulation: Custom Network"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + frame.setResizable(false); + frame.setSize(1000, 600); + frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. + + frame.add(customNetworkLabel); + }); } } \ No newline at end of file From cd02a5bea79a9489c251f72ffaa1ed06eb54ebf1 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Tue, 11 Mar 2025 07:41:04 +0000 Subject: [PATCH 14/63] Created PC and Router objects for buttons. Signed-off-by: Lukas Bauza --- src/Main.java | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Main.java b/src/Main.java index ae5c4ad..4a801b9 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,8 +9,7 @@ public class Main { public static void main(String[] args) { - JFrame frame = new MainMenuFrame(); - frame.setTitle("OSPF Simulation"); + JFrame frame = new JFrame("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X frame.setResizable(false); frame.setSize(600, 300); @@ -39,6 +38,9 @@ public static void main(String[] args) { JLabel prebuiltNetworkLabel = new JLabel("Prebuilt Network"); + JButton[] routerButtons = getRouterJButtons(); + JButton[] pcButtons = getPCButtons(); + JLabel customNetworkLabel = new JLabel("Custom Network"); preconfiguredNetworkButton.addActionListener(e -> { @@ -55,6 +57,14 @@ public static void main(String[] args) { frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. frame.add(prebuiltNetworkLabel); + + for (JButton button : routerButtons) { + frame.add(button); + } + + for (JButton button : pcButtons) { + frame.add(button); + } }); customNetworkButton.addActionListener(e -> { @@ -73,4 +83,36 @@ public static void main(String[] args) { frame.add(customNetworkLabel); }); } + + private static JButton[] getRouterJButtons() { + Router[] routers = { + new Router("R0"), + new Router("R1"), + new Router("R2"), + new Router("R3"), + new Router("R4") + }; + return new JButton[]{ + new JButton(routers[0].getName()), + new JButton(routers[1].getName()), + new JButton(routers[2].getName()), + new JButton(routers[3].getName()), + new JButton(routers[4].getName()) + }; + } + + private static JButton[] getPCButtons() { + PC[] pcs = { + new PC("PC0", new IPAddress("192.168.1.1"), new SubnetMask("255.255.255.0")), + new PC("PC1", new IPAddress("192.168.2.1"), new SubnetMask("255.255.255.0")), + new PC("PC2", new IPAddress("192.168.3.1."), new SubnetMask("255.255.255.0")), + new PC("PC3", new IPAddress("192.168.4.1"), new SubnetMask("255.255.255.0")), + }; + return new JButton[]{ + new JButton("PC0"), + new JButton("PC1"), + new JButton("PC2"), + new JButton("PC3") + }; + }; } \ No newline at end of file From 2d1af777bad8c23977a33a1f3d5ceebdead72177 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sat, 15 Mar 2025 17:40:56 +0000 Subject: [PATCH 15/63] Typo Signed-off-by: Lukas Bauza --- src/Device.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Device.java b/src/Device.java index 5dfe7c0..9327224 100644 --- a/src/Device.java +++ b/src/Device.java @@ -1,7 +1,7 @@ import java.util.ArrayList; abstract public class Device { - // Abstract class for holding data memebers and methods that are common within the Router and PC child classes. + // Abstract class for holding data members and methods that are common within the Router and PC child classes. private String name; // Name of the device private ARPTable arpTable = new ARPTable(); // ARP table for the device. From 5eaa55b32d0ad81008217aa7aacdc203ca29eec5 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 12:59:29 +0000 Subject: [PATCH 16/63] Typo Signed-off-by: Lukas Bauza --- src/IPAddress.java | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/IPAddress.java b/src/IPAddress.java index 5d1b382..7189c67 100644 --- a/src/IPAddress.java +++ b/src/IPAddress.java @@ -1,10 +1,8 @@ -public class IPAddress extends OctetArray { +public class IPAddress { //private String ipAddress; - private byte[] ipAddress; + private byte[] ipAddress = new byte[4]; public IPAddress(String ipAddress) { - // Set up the OctetArray parent class, for its constructor. - super(".", 4); // constructor used for checking that each segment is the right type and in the right range. // if a wrong type is entered, it will throw out an error into main, which // can be caught and used for error handling. @@ -16,8 +14,6 @@ public IPAddress(String ipAddress) { } public IPAddress() { - // Set up the OctetArray parent class, for its constructor. - super(".", 4); this.ipAddress = new byte[4]; } @@ -25,15 +21,15 @@ public byte[] getIpAddress() { return ipAddress; } -// public String toString() { -// // Needs to be converted to an unsigned int, to not have minus values. As bytes are represented in negatives. -// String byte0 = Integer.toString(Byte.toUnsignedInt(ipAddress[0])); -// String byte1 = Integer.toString(Byte.toUnsignedInt(ipAddress[1])); -// String byte2 = Integer.toString(Byte.toUnsignedInt(ipAddress[2])); -// String byte3 = Integer.toString(Byte.toUnsignedInt(ipAddress[3])); -// -// return byte0 + "." + byte1 + "." + byte2 + "." + byte3; -// } + public String toString() { + // Needs to be converted to an unsigned int, to not have minus values. As bytes are represented in negatives. + String byte0 = Integer.toString(Byte.toUnsignedInt(ipAddress[0])); + String byte1 = Integer.toString(Byte.toUnsignedInt(ipAddress[1])); + String byte2 = Integer.toString(Byte.toUnsignedInt(ipAddress[2])); + String byte3 = Integer.toString(Byte.toUnsignedInt(ipAddress[3])); + + return byte0 + "." + byte1 + "." + byte2 + "." + byte3; + } public void setIpAddress(String ipAddress) { //this.ipAddress = ipAddress; @@ -113,7 +109,7 @@ public boolean equals(Object object) { if(object == null || this.getClass() != object.getClass()) return false; // Cast the object to the SubnetMask object class. IPAddress ipAddress = (IPAddress) object; - // Check if all of the subnet mask values match, if not then return false. + // Check if all the subnet mask values match, if not then return false. if(this.ipAddress[0] != ipAddress.getIpAddress()[0]) return false; if(this.ipAddress[1] != ipAddress.getIpAddress()[1]) return false; if(this.ipAddress[2] != ipAddress.getIpAddress()[2]) return false; @@ -121,4 +117,4 @@ public boolean equals(Object object) { // Once all conditions are met, then the objects equal. return true; } -} \ No newline at end of file +} From ffec7caeb693d069b47964c75b893abe4a6e881d Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 14:05:50 +0000 Subject: [PATCH 17/63] Able to keep track of the NIC connections more easily, and whether an IP + SubnetMask, and MACAddress is already being used within the application. Signed-off-by: Lukas Bauza --- src/Device.java | 1 + src/NIC.java | 23 +++++++++++++++++++++-- src/NICManager.java | 42 ++++++++++++++++++++++++++++++++++++++++++ src/PC.java | 4 ++-- src/Router.java | 10 +++++----- 5 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 src/NICManager.java diff --git a/src/Device.java b/src/Device.java index 9327224..b50b6bd 100644 --- a/src/Device.java +++ b/src/Device.java @@ -3,6 +3,7 @@ abstract public class Device { // Abstract class for holding data members and methods that are common within the Router and PC child classes. + // TODO: Need a device manager to keep track of device? Or maybe a device ID? private String name; // Name of the device private ARPTable arpTable = new ARPTable(); // ARP table for the device. private ArrayList nicList; // Interfaces for the device diff --git a/src/NIC.java b/src/NIC.java index 15d2507..f3687b7 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -4,9 +4,12 @@ public class NIC { private IPAddress ipAddress; private SubnetMask subNetMask; private final MACAddress macAddress = new MACAddress(); + private NIC connection; - public NIC(String name) { + public NIC(String name, NICManager nicManager) { this.name = name; + // Add the NIC to the NICManager to keep track of NICs automatically. + nicManager.addNIC(this); } public String getName() { @@ -36,7 +39,7 @@ public void setSubnetMask(SubnetMask subnetMask) { public IPAddress getNetwork() { IPAddress network = new IPAddress(); - // AND the IP address bits with the subnet mask bits and it should return the network bits for each byte. + // AND the IP address bits with the subnet mask bits, and it should return the network bits for each byte. byte byte3 = (byte) (this.ipAddress.getIpAddress()[3] & this.subNetMask.getSubnetMask()[3]); byte byte2 = (byte) (this.ipAddress.getIpAddress()[2] & this.subNetMask.getSubnetMask()[2]); byte byte1 = (byte) (this.ipAddress.getIpAddress()[1] & this.subNetMask.getSubnetMask()[1]); @@ -50,4 +53,20 @@ public IPAddress getNetwork() { return network; } + + /** + * Sets up a connection to another NIC. + * @param otherNIC This is the other NIC that this instance will connect to. + */ + public void setConnection(NIC otherNIC) { + this.connection = otherNIC; + } + + /** + * Checks whether this NIC is connected to another NIC. + * @return True if there is a connection, false if not. + */ + public boolean isConnected() { + return this.connection != null; + } } \ No newline at end of file diff --git a/src/NICManager.java b/src/NICManager.java new file mode 100644 index 0000000..d455128 --- /dev/null +++ b/src/NICManager.java @@ -0,0 +1,42 @@ +import java.util.ArrayList; + +public class NICManager { + ArrayList createdNICs; + + /** + * Inserts a NIC within the ArrayList + * @param nic The NIC to be inserted. + */ + void addNIC(NIC nic) { + createdNICs.add(nic); + } + + /** + * Checks if a MACAddress has already been created for another NIC. + * @param macAddress MACAddress that will be searched for. + * @return If a MACAddress exists returns true, else false. + */ + boolean macExists(MACAddress macAddress) { + for (NIC nic : createdNICs) { + if (nic.getMacAddress().equals(macAddress)) { + return true; + } + } + return false; + } + + /** + * Checks if a IPAddress and SubnetMask combination are being used by another NIC. + * @param ipAddress Used for checking for a matching IPAddress. + * @param subnetMask Used for checking a matching SubnetMask. + * @return If both the ipAddress and subnetMask match, then return true, else false. + */ + boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { + for (NIC nic : createdNICs) { + if (nic.getIpAddress().equals(ipAddress) && nic.getSubnetMask().equals(subnetMask)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/PC.java b/src/PC.java index 701858e..3caf9b0 100644 --- a/src/PC.java +++ b/src/PC.java @@ -6,12 +6,12 @@ public class PC extends Device { private IPAddress defaultGatewayIPAddress; private SubnetMask defaultGatewaySubnetMask; - PC(String name, IPAddress ipaddress, SubnetMask subnetMask) { + PC(String name, IPAddress ipaddress, SubnetMask subnetMask, NICManager nicManager) { // Use the parent class constructor to add the name variable within the Device class. super(name); // Set up the NIC for the PC, which only has the FastEthernet 0/0 - NIC fa00 = new NIC("FastEthernet 0/0"); + NIC fa00 = new NIC("FastEthernet 0/0", nicManager); fa00.setIpAddress(ipaddress); fa00.setSubnetMask(subnetMask); // Add the fa00 NIC to the ArrayList of nicList. diff --git a/src/Router.java b/src/Router.java index 18eb60b..e3885a8 100644 --- a/src/Router.java +++ b/src/Router.java @@ -3,15 +3,15 @@ public class Router extends Device { - public Router(String name) { + public Router(String name, NICManager nicManager) { // Use the parent class constructor to add the name variable within the Device class. super(name); // Set up the NIC for the Router, which is Gig 0/0, Gig 0/1, Gig 0/2. There is only a name for them set, as the // user should be able to set the IP and subnet mask at a later time. - NIC gig00 = new NIC("GigabitEthernet 0/0"); - NIC gig01 = new NIC("GigabitEthernet 0/1"); - NIC gig02 = new NIC("GigabitEthernet 0/2"); + NIC gig00 = new NIC("GigabitEthernet 0/0", nicManager); + NIC gig01 = new NIC("GigabitEthernet 0/1", nicManager); + NIC gig02 = new NIC("GigabitEthernet 0/2", nicManager); // Add the NICs to the ArrayList of the nic list within the parent class. super.setNICList(new ArrayList<>(List.of(gig00, gig01, gig02))); @@ -46,4 +46,4 @@ public void setPortGig02IPAddress(IPAddress ipAddress) { public void setPortGig02SubnetMask(SubnetMask subnetMask) { super.getNICList().get(2).setSubnetMask(subnetMask); } -} +} \ No newline at end of file From 2a8c72e72df9d5e79c0e2496675b4e810f278d0a Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 14:23:52 +0000 Subject: [PATCH 18/63] Made sure that only one instance of the NICManager is available within the applicaiton. Signed-off-by: Lukas Bauza --- src/NICManager.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/NICManager.java b/src/NICManager.java index d455128..0935126 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -1,13 +1,23 @@ import java.util.ArrayList; public class NICManager { - ArrayList createdNICs; + private ArrayList createdNICs; + // This will hold the instance of the class, it is used to ensure that this will be the only instance that can be used. + // static variable to indicate the value is associated with the class, and not the object, this ensures that the same + // instance is held throughout the class. new NICManager() calls the private constructor, which can only be accessed + // from within itself. + private static NICManager instance = new NICManager(); + + // private constructor, so no other class can call the constructor, thus not allowing any more instances of the class. + private NICManager() { + + } /** * Inserts a NIC within the ArrayList * @param nic The NIC to be inserted. */ - void addNIC(NIC nic) { + public void addNIC(NIC nic) { createdNICs.add(nic); } @@ -16,7 +26,7 @@ void addNIC(NIC nic) { * @param macAddress MACAddress that will be searched for. * @return If a MACAddress exists returns true, else false. */ - boolean macExists(MACAddress macAddress) { + public boolean macExists(MACAddress macAddress) { for (NIC nic : createdNICs) { if (nic.getMacAddress().equals(macAddress)) { return true; @@ -31,7 +41,7 @@ boolean macExists(MACAddress macAddress) { * @param subnetMask Used for checking a matching SubnetMask. * @return If both the ipAddress and subnetMask match, then return true, else false. */ - boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { + public boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { for (NIC nic : createdNICs) { if (nic.getIpAddress().equals(ipAddress) && nic.getSubnetMask().equals(subnetMask)) { return true; From 328b08495872d8b0f02f17dff601c7467333a270 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 14:32:56 +0000 Subject: [PATCH 19/63] Basic layout of devices for the prebuilt layout. Setup the NICManager to get its instance. Signed-off-by: Lukas Bauza --- src/Main.java | 95 ++++++++++++++++++++++++++------------------- src/NICManager.java | 13 ++++++- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/Main.java b/src/Main.java index 4a801b9..12c34a0 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,6 +9,9 @@ public class Main { public static void main(String[] args) { + // Retrieve the only instance of the NICManager. + NICManager nicManager = NICManager.getInstance(); + JFrame frame = new JFrame("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X frame.setResizable(false); @@ -38,8 +41,8 @@ public static void main(String[] args) { JLabel prebuiltNetworkLabel = new JLabel("Prebuilt Network"); - JButton[] routerButtons = getRouterJButtons(); - JButton[] pcButtons = getPCButtons(); + JButton[] routerButtons = getJButtonArray(7, "R"); + JButton[] pcButtons = getJButtonArray(3, "PC"); JLabel customNetworkLabel = new JLabel("Custom Network"); @@ -51,20 +54,50 @@ public static void main(String[] args) { frame.remove(preconfiguredNetworkButton); frame.setTitle("OSPF Simulation: Prebuilt Network"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X - frame.setResizable(false); - frame.setSize(1000, 600); + frame.setSize(1200, 1000); frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. - frame.add(prebuiltNetworkLabel); + JPanel panel = new JPanel(); + panel.setLayout(null); // No layout, for placing items with x and y coordinates. + + panel.add(pcButtons[0]); + pcButtons[0].setBounds(new Rectangle(50, 50, 60, 60)); + + panel.add(routerButtons[0]); + routerButtons[0].setBounds(new Rectangle(180, 180, 60, 60)); + + panel.add(routerButtons[1]); + routerButtons[1].setBounds(new Rectangle(310, 310, 60, 60)); + + panel.add(routerButtons[2]); + routerButtons[2].setBounds(new Rectangle(440, 440, 60, 60)); + + panel.add(routerButtons[3]); + routerButtons[3].setBounds(new Rectangle(570, 570, 60, 60)); + + panel.add(pcButtons[1]); + pcButtons[1].setBounds(new Rectangle(700, 700, 60, 60)); + + panel.add(routerButtons[4]); + routerButtons[4].setBounds(new Rectangle(545, 310, 60, 60)); + + panel.add(routerButtons[5]); + routerButtons[5].setBounds(new Rectangle(700, 180, 60, 60)); + + panel.add(routerButtons[6]); + routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); + + //frame.add(prebuiltNetworkLabel); for (JButton button : routerButtons) { - frame.add(button); + panel.add(button); } for (JButton button : pcButtons) { - frame.add(button); + panel.add(button); } + + frame.add(panel); }); customNetworkButton.addActionListener(e -> { @@ -75,8 +108,6 @@ public static void main(String[] args) { frame.remove(preconfiguredNetworkButton); frame.setTitle("OSPF Simulation: Custom Network"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X - frame.setResizable(false); frame.setSize(1000, 600); frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. @@ -84,35 +115,19 @@ public static void main(String[] args) { }); } - private static JButton[] getRouterJButtons() { - Router[] routers = { - new Router("R0"), - new Router("R1"), - new Router("R2"), - new Router("R3"), - new Router("R4") - }; - return new JButton[]{ - new JButton(routers[0].getName()), - new JButton(routers[1].getName()), - new JButton(routers[2].getName()), - new JButton(routers[3].getName()), - new JButton(routers[4].getName()) - }; + /** + * Method for creating an array of JButton objects, with a name for each object. + * @param count + * @param name + * @return Array of JButton objects, with size count. With the text as: name + (count - 1), for each button. + */ + private static JButton[] getJButtonArray(int count, String name) { + JButton[] buttons = new JButton[count]; + + while (count != 0) { + buttons[count - 1] = new JButton(name + (count - 1)); + count--; + } + return buttons; } - - private static JButton[] getPCButtons() { - PC[] pcs = { - new PC("PC0", new IPAddress("192.168.1.1"), new SubnetMask("255.255.255.0")), - new PC("PC1", new IPAddress("192.168.2.1"), new SubnetMask("255.255.255.0")), - new PC("PC2", new IPAddress("192.168.3.1."), new SubnetMask("255.255.255.0")), - new PC("PC3", new IPAddress("192.168.4.1"), new SubnetMask("255.255.255.0")), - }; - return new JButton[]{ - new JButton("PC0"), - new JButton("PC1"), - new JButton("PC2"), - new JButton("PC3") - }; - }; } \ No newline at end of file diff --git a/src/NICManager.java b/src/NICManager.java index 0935126..1186a89 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -1,6 +1,7 @@ import java.util.ArrayList; public class NICManager { + // Holds the all the NICs that have been created. private ArrayList createdNICs; // This will hold the instance of the class, it is used to ensure that this will be the only instance that can be used. // static variable to indicate the value is associated with the class, and not the object, this ensures that the same @@ -8,11 +9,21 @@ public class NICManager { // from within itself. private static NICManager instance = new NICManager(); - // private constructor, so no other class can call the constructor, thus not allowing any more instances of the class. + /** + * Private constructor, so no other class can call the constructor, thus not allowing any more instances of the class. + */ private NICManager() { } + /** + * Method for returning the instance of the object, since only one instance can exist. + * @return NICManager instance + */ + public static NICManager getInstance() { + return instance; + } + /** * Inserts a NIC within the ArrayList * @param nic The NIC to be inserted. From 0670163e95ee75b15def506fc745cac0df6d8e36 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 14:40:12 +0000 Subject: [PATCH 20/63] createdNICs was null, causing an error when trying to add a NIC to it. Signed-off-by: Lukas Bauza --- src/NICManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NICManager.java b/src/NICManager.java index 1186a89..e2d0375 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -2,7 +2,7 @@ public class NICManager { // Holds the all the NICs that have been created. - private ArrayList createdNICs; + private ArrayList createdNICs = new ArrayList<>(); // This will hold the instance of the class, it is used to ensure that this will be the only instance that can be used. // static variable to indicate the value is associated with the class, and not the object, this ensures that the same // instance is held throughout the class. new NICManager() calls the private constructor, which can only be accessed From 327b450f967997a39cf91702fa8d9cfa095e9852 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 15:36:43 +0000 Subject: [PATCH 21/63] Added comments. Check if the IP Address or Subnet Mask of the NIC is equal to null. Signed-off-by: Lukas Bauza --- src/NICManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/NICManager.java b/src/NICManager.java index e2d0375..ba72027 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -54,10 +54,16 @@ public boolean macExists(MACAddress macAddress) { */ public boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { for (NIC nic : createdNICs) { + // Check if the IP Address or the Subnet Mask is not equal to null, if so go to the next iteration + if (nic.getIpAddress() == null || nic.getSubnetMask() == null) { + continue; + } + // If the ipAddres and the subnetMask match with the subnetMask and ipAddress of the NIC, then return true. if (nic.getIpAddress().equals(ipAddress) && nic.getSubnetMask().equals(subnetMask)) { return true; } } + // When no matches are mad then return false. return false; } } \ No newline at end of file From 92f08d668e80c84ea7a3aa1a9c5741e6bbc1bde9 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 15:40:09 +0000 Subject: [PATCH 22/63] Added comments. Handling of when there is the same subnet mask and ip address already used. Signed-off-by: Lukas Bauza --- src/NIC.java | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/NIC.java b/src/NIC.java index f3687b7..29561bc 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -5,8 +5,9 @@ public class NIC { private SubnetMask subNetMask; private final MACAddress macAddress = new MACAddress(); private NIC connection; + private NICManager nicManager = NICManager.getInstance(); - public NIC(String name, NICManager nicManager) { + public NIC(String name) { this.name = name; // Add the NIC to the NICManager to keep track of NICs automatically. nicManager.addNIC(this); @@ -21,7 +22,14 @@ public IPAddress getIpAddress() { } public void setIpAddress(IPAddress ipAddress) { - this.ipAddress = ipAddress; + // Check if the combination of ip address and subnet mask is already set up for another NIC + if (subNetMask != null && nicManager.ipAndSubnetExists(ipAddress, subNetMask)) { + System.err.println("This combination of IP Address and Subnet Mask already exists"); + // Reset the subnet mask also + this.subNetMask = null; + } else { + this.ipAddress = ipAddress; + } } public MACAddress getMacAddress() { @@ -33,7 +41,13 @@ public SubnetMask getSubnetMask() { } public void setSubnetMask(SubnetMask subnetMask) { - this.subNetMask = subnetMask; + if (ipAddress != null && nicManager.ipAndSubnetExists(ipAddress, subnetMask)) { + System.err.println("This combination of IP Address and Subnet Mask already exists"); + // Reset the ip address also. + this.ipAddress = null; + } else { + this.subNetMask = subnetMask; + } } public IPAddress getNetwork() { @@ -69,4 +83,6 @@ public void setConnection(NIC otherNIC) { public boolean isConnected() { return this.connection != null; } + + } \ No newline at end of file From 6cbb061f108353107cbf8618efdb7ca3bcc1fd26 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 16:41:53 +0000 Subject: [PATCH 23/63] Removed an old TODO Signed-off-by: Lukas Bauza --- src/MACAddress.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MACAddress.java b/src/MACAddress.java index b01b8d2..3a840ae 100644 --- a/src/MACAddress.java +++ b/src/MACAddress.java @@ -1,5 +1,3 @@ -// TODO: Need to add subnet for PC. - import java.util.Random; public class MACAddress { From e905e6c33025e914ce34f792ce3bcd9098d76bb4 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 16:43:40 +0000 Subject: [PATCH 24/63] Generates a random MACAddress for the NIC, and make sure that there isn't one already generated. Signed-off-by: Lukas Bauza --- src/NIC.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/NIC.java b/src/NIC.java index 29561bc..ad9d58d 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -3,7 +3,7 @@ public class NIC { private final String name; private IPAddress ipAddress; private SubnetMask subNetMask; - private final MACAddress macAddress = new MACAddress(); + private MACAddress macAddress; private NIC connection; private NICManager nicManager = NICManager.getInstance(); @@ -11,6 +11,17 @@ public NIC(String name) { this.name = name; // Add the NIC to the NICManager to keep track of NICs automatically. nicManager.addNIC(this); + setMacAddress(); + } + + private void setMacAddress() { + int count = 10; + // create a MACAddress for the NIC, if it already exists, then keep creating one until it is unique, or if looped + // 10 times. 10 times to make sure it won't infinitely loop, the likelihood of generated 10 of the same MACAddresses + // is almost zero. + do { + macAddress = new MACAddress(); + } while (nicManager.macExists(macAddress) && count-- > 0); } public String getName() { From 34963f4e8fd1452c5a30b45b880db2b53df618ab Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 16:44:12 +0000 Subject: [PATCH 25/63] Updated use of NIC constructor. Signed-off-by: Lukas Bauza --- src/PC.java | 2 +- src/Router.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PC.java b/src/PC.java index 3caf9b0..a5fa59f 100644 --- a/src/PC.java +++ b/src/PC.java @@ -11,7 +11,7 @@ public class PC extends Device { super(name); // Set up the NIC for the PC, which only has the FastEthernet 0/0 - NIC fa00 = new NIC("FastEthernet 0/0", nicManager); + NIC fa00 = new NIC("FastEthernet 0/0"); fa00.setIpAddress(ipaddress); fa00.setSubnetMask(subnetMask); // Add the fa00 NIC to the ArrayList of nicList. diff --git a/src/Router.java b/src/Router.java index e3885a8..6726cdf 100644 --- a/src/Router.java +++ b/src/Router.java @@ -9,9 +9,9 @@ public Router(String name, NICManager nicManager) { // Set up the NIC for the Router, which is Gig 0/0, Gig 0/1, Gig 0/2. There is only a name for them set, as the // user should be able to set the IP and subnet mask at a later time. - NIC gig00 = new NIC("GigabitEthernet 0/0", nicManager); - NIC gig01 = new NIC("GigabitEthernet 0/1", nicManager); - NIC gig02 = new NIC("GigabitEthernet 0/2", nicManager); + NIC gig00 = new NIC("GigabitEthernet 0/0"); + NIC gig01 = new NIC("GigabitEthernet 0/1"); + NIC gig02 = new NIC("GigabitEthernet 0/2"); // Add the NICs to the ArrayList of the nic list within the parent class. super.setNICList(new ArrayList<>(List.of(gig00, gig01, gig02))); From bd2e354fcd98d51b0e50a91e9482b62ff3748502 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 19:49:22 +0000 Subject: [PATCH 26/63] Object for creating lines on the GUI, to be used for showing connections between devices. Signed-off-by: Lukas Bauza --- src/Line.java | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Line.java diff --git a/src/Line.java b/src/Line.java new file mode 100644 index 0000000..bc69186 --- /dev/null +++ b/src/Line.java @@ -0,0 +1,23 @@ +import javax.swing.*; +import java.awt.*; + +public class Line extends JPanel { + private int x1, y1, x2, y2; + private Color color; + + public Line(int x1, int y1, int x2, int y2, Color color) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.color = color; + } + + // Method to draw the line + public void draw(Graphics g) { + Graphics2D g2D = (Graphics2D) g; + g2D.setColor(color); + g2D.setStroke(new BasicStroke(5)); // Set line thickness + g2D.drawLine(x1, y1, x2, y2); + } +} From c8a91889e508ce2d42ecbefdf5865bde92cb7373 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 16 Mar 2025 19:49:50 +0000 Subject: [PATCH 27/63] Created the visual connections between the devices. Signed-off-by: Lukas Bauza --- src/Main.java | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Main.java b/src/Main.java index 12c34a0..ff5d15b 100644 --- a/src/Main.java +++ b/src/Main.java @@ -57,7 +57,29 @@ public static void main(String[] args) { frame.setSize(1200, 1000); frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. - JPanel panel = new JPanel(); + Line[] wires = { + new Line(60, 60, 180, 180, Color.BLACK), // PC0 to R0 + new Line(180, 180, 310, 310, Color.BLACK), // R0 to R1 + new Line(310, 310, 440, 440, Color.BLACK), // R1 to R2 + new Line(440, 440, 570, 570, Color.BLACK), // R2 to R3 + new Line(570, 570, 700, 700, Color.BLACK), // R3 to PC1 + new Line(240, 210, 730, 210, Color.BLACK), // R0 to R5 + new Line(320, 340, 545, 340, Color.BLACK), // R1 to R4 + new Line(545, 340, 850, 340, Color.BLACK), // R4 to R6 + new Line(760, 240, 850, 310, Color.BLACK), // R5 to R6 + new Line(480, 470, 570, 340, Color.BLACK), // R2 to R4 + }; + + JPanel panel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + for (Line line : wires) { + line.draw(g); // Draw stored lines + } + } + }; + panel.setLayout(null); // No layout, for placing items with x and y coordinates. panel.add(pcButtons[0]); @@ -87,8 +109,6 @@ public static void main(String[] args) { panel.add(routerButtons[6]); routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); - //frame.add(prebuiltNetworkLabel); - for (JButton button : routerButtons) { panel.add(button); } From e0ee9ba59315775f669d075e64b37f24b0c0d044 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 10:13:21 +0000 Subject: [PATCH 28/63] Removed old NICManager parameter from constructor. Signed-off-by: Lukas Bauza --- src/PC.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PC.java b/src/PC.java index a5fa59f..701858e 100644 --- a/src/PC.java +++ b/src/PC.java @@ -6,7 +6,7 @@ public class PC extends Device { private IPAddress defaultGatewayIPAddress; private SubnetMask defaultGatewaySubnetMask; - PC(String name, IPAddress ipaddress, SubnetMask subnetMask, NICManager nicManager) { + PC(String name, IPAddress ipaddress, SubnetMask subnetMask) { // Use the parent class constructor to add the name variable within the Device class. super(name); From fe371a4e31804f3d8432bbc4ce234e5a1366a275 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 10:14:44 +0000 Subject: [PATCH 29/63] Overloaded constructor for PC, with only the name required. Signed-off-by: Lukas Bauza --- src/PC.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/PC.java b/src/PC.java index 701858e..1098182 100644 --- a/src/PC.java +++ b/src/PC.java @@ -19,6 +19,14 @@ public class PC extends Device { super.setNICList(new ArrayList<>(List.of(fa00))); } + /** + * Overloaded constructor with only the name as a parameter. + * @param name The name for the PC. + */ + PC(String name) { + super(name); + } + public NIC getPortFA00() { return super.getNICList().get(0); } // You shouldn't be able to change the name of the interface, as they are always predefined. Only the IP can be From 76acd2da4cac8bdfb721d50efa52063e08c7b893 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 10:25:15 +0000 Subject: [PATCH 30/63] Removed NICManager from the Router constructor. Signed-off-by: Lukas Bauza --- src/Router.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Router.java b/src/Router.java index 6726cdf..7d2f943 100644 --- a/src/Router.java +++ b/src/Router.java @@ -3,7 +3,7 @@ public class Router extends Device { - public Router(String name, NICManager nicManager) { + public Router(String name) { // Use the parent class constructor to add the name variable within the Device class. super(name); From 2ae0ddd5891cbcb27035df5cd2ac04a621cc7118 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 10:48:40 +0000 Subject: [PATCH 31/63] RouterButton for coupling the Router and JButton together. Signed-off-by: Lukas Bauza --- src/RouterButton.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/RouterButton.java diff --git a/src/RouterButton.java b/src/RouterButton.java new file mode 100644 index 0000000..b005ee1 --- /dev/null +++ b/src/RouterButton.java @@ -0,0 +1,14 @@ +import javax.swing.*; + +public class RouterButton extends JButton { + Router router; + + public RouterButton(String name) { + super(name); + this.router = new Router(name); + } + + public Router getRouter() { + return router; + } +} From 9827844ff100be09b5e751a48c62138c868ca589 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 11:28:28 +0000 Subject: [PATCH 32/63] PCButton for coupling the PC and JButton together. Signed-off-by: Lukas Bauza --- src/PCButton.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/PCButton.java diff --git a/src/PCButton.java b/src/PCButton.java new file mode 100644 index 0000000..6c2bdd9 --- /dev/null +++ b/src/PCButton.java @@ -0,0 +1,14 @@ +import javax.swing.*; + +public class PCButton extends JButton { + PC pc; + + public PCButton(String name) { + super(name); + this.pc = new PC(name); + } + + public PC getPC() { + return pc; + } +} From 6366d7833a8852cfbf2d51d8e0d008e4c7e1f9a7 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 11:54:10 +0000 Subject: [PATCH 33/63] PCButton and RouterButton implemented, providing cohesion with the button object and data from the specific device. Signed-off-by: Lukas Bauza --- src/Main.java | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Main.java b/src/Main.java index ff5d15b..d712ebf 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,8 +9,8 @@ public class Main { public static void main(String[] args) { - // Retrieve the only instance of the NICManager. - NICManager nicManager = NICManager.getInstance(); + PCButton[] pcButtons = getPCButtonArray(3, "PC"); + RouterButton[] routerButtons = getRouterButtonArray(7, "R"); JFrame frame = new JFrame("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X @@ -41,9 +41,6 @@ public static void main(String[] args) { JLabel prebuiltNetworkLabel = new JLabel("Prebuilt Network"); - JButton[] routerButtons = getJButtonArray(7, "R"); - JButton[] pcButtons = getJButtonArray(3, "PC"); - JLabel customNetworkLabel = new JLabel("Custom Network"); preconfiguredNetworkButton.addActionListener(e -> { @@ -68,6 +65,7 @@ public static void main(String[] args) { new Line(545, 340, 850, 340, Color.BLACK), // R4 to R6 new Line(760, 240, 850, 310, Color.BLACK), // R5 to R6 new Line(480, 470, 570, 340, Color.BLACK), // R2 to R4 + new Line(880, 340, 1030, 210, Color.BLACK), // R6 to PC3 }; JPanel panel = new JPanel() { @@ -109,6 +107,9 @@ protected void paintComponent(Graphics g) { panel.add(routerButtons[6]); routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); + panel.add(pcButtons[2]); + pcButtons[2].setBounds(new Rectangle(1000, 180, 60, 60)); + for (JButton button : routerButtons) { panel.add(button); } @@ -137,17 +138,37 @@ protected void paintComponent(Graphics g) { /** * Method for creating an array of JButton objects, with a name for each object. - * @param count - * @param name + * @param count The amount of Button objects in the array. + * @param name The text for the buttons + i, where is the current button being created. * @return Array of JButton objects, with size count. With the text as: name + (count - 1), for each button. */ private static JButton[] getJButtonArray(int count, String name) { JButton[] buttons = new JButton[count]; - while (count != 0) { - buttons[count - 1] = new JButton(name + (count - 1)); - count--; + // count-- is post decrement, meaning the current count variable is used, then it is decremented. + while (count-- > 0) { + buttons[count] = new JButton(name + (count)); } return buttons; } + + private static RouterButton[] getRouterButtonArray(int count, String name) { + RouterButton[] routers = new RouterButton[count]; + + // count-- is post decrement, meaning the current count variable is used, then it is decremented. + while (count-- > 0) { + routers[count] = new RouterButton(name + (count)); + } + return routers; + } + + private static PCButton[] getPCButtonArray(int count, String name) { + PCButton[] pcButtons = new PCButton[count]; + + // count-- is post decrement, meaning the current count variable is used, then it is decremented. + while (count-- > 0) { + pcButtons[count] = new PCButton(name + (count)); + } + return pcButtons; + } } \ No newline at end of file From 33d5d636757a7e1ffa8b8115c3baa54c2a58c78e Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 14:07:54 +0000 Subject: [PATCH 34/63] Created windows for the devices on the prebuilt network. Signed-off-by: Lukas Bauza --- src/PCButton.java | 10 ++++++++++ src/RouterButton.java | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/PCButton.java b/src/PCButton.java index 6c2bdd9..12aa234 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -11,4 +11,14 @@ public PCButton(String name) { public PC getPC() { return pc; } + + public JFrame getInfoFrame() { + JFrame frame = new JFrame(); + + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + // DISPOSE_ON_CLOSE will ensure that the windows won't all close. + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + return frame; + } } diff --git a/src/RouterButton.java b/src/RouterButton.java index b005ee1..150f0c6 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -11,4 +11,14 @@ public RouterButton(String name) { public Router getRouter() { return router; } + + public JFrame getInfoFrame() { + JFrame frame = new JFrame(); + + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + // DISPOSE_ON_CLOSE will ensure that the windows won't all close. + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + return frame; + } } From 8c81d105a967263cb651d231b36deda797d629e8 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 14:07:59 +0000 Subject: [PATCH 35/63] Created windows for the devices on the prebuilt network. Signed-off-by: Lukas Bauza --- src/Main.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Main.java b/src/Main.java index d712ebf..b31f45b 100644 --- a/src/Main.java +++ b/src/Main.java @@ -13,7 +13,7 @@ public static void main(String[] args) { RouterButton[] routerButtons = getRouterButtonArray(7, "R"); JFrame frame = new JFrame("OSPF Simulation"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X (all frames close) frame.setResizable(false); frame.setSize(600, 300); frame.setLayout(new GridLayout(0, 1)); // rows=0, cols=1. Makes it vertical. @@ -118,6 +118,20 @@ protected void paintComponent(Graphics g) { panel.add(button); } + for (PCButton pcButton : pcButtons) { + pcButton.addActionListener(ePC -> { + JFrame pcFrame = pcButton.getInfoFrame(); + pcFrame.setVisible(true); + }); + } + + for (RouterButton routerButton : routerButtons) { + routerButton.addActionListener(eR -> { + JFrame routerFrame = routerButton.getInfoFrame(); + routerFrame.setVisible(true); + }); + } + frame.add(panel); }); From 8f10fe375017671c66a589b7725f4d2a70f21ab2 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 18:59:13 +0000 Subject: [PATCH 36/63] Change display size for PC info. Title of window matches name of PC. Signed-off-by: Lukas Bauza --- src/PCButton.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PCButton.java b/src/PCButton.java index 12aa234..d894e80 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -15,8 +15,9 @@ public PC getPC() { public JFrame getInfoFrame() { JFrame frame = new JFrame(); - frame.setSize(800, 600); + frame.setSize( 600, 400); frame.setLocationRelativeTo(null); + frame.setTitle(pc.getName()); // DISPOSE_ON_CLOSE will ensure that the windows won't all close. frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); return frame; From db793eae6f11be607badb135038a637a985f9e61 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 19:21:09 +0000 Subject: [PATCH 37/63] Able to return the whole ArrayList of NICs from the Router. Signed-off-by: Lukas Bauza --- src/Router.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Router.java b/src/Router.java index 7d2f943..6673aac 100644 --- a/src/Router.java +++ b/src/Router.java @@ -17,6 +17,8 @@ public Router(String name) { super.setNICList(new ArrayList<>(List.of(gig00, gig01, gig02))); } + public ArrayList getNICList() { return super.getNICList(); } + public NIC getPortGig00() { return super.getNICList().get(0); } public void setPortGig00IPAddress(IPAddress ipAddress) { From c121404446b567b5e4a71587cb76722db24e2ced Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 17 Mar 2025 19:22:04 +0000 Subject: [PATCH 38/63] Router's have a window with their information displayed, although changes to the text fields are not accounted for. Signed-off-by: Lukas Bauza --- src/RouterButton.java | 64 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/RouterButton.java b/src/RouterButton.java index 150f0c6..6d2e520 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -1,4 +1,5 @@ import javax.swing.*; +import java.awt.*; public class RouterButton extends JButton { Router router; @@ -15,10 +16,69 @@ public Router getRouter() { public JFrame getInfoFrame() { JFrame frame = new JFrame(); - frame.setSize(800, 600); + frame.setSize(600, 400); + frame.setTitle(router.getName()); + // Make sure it is in the middle of the screen. frame.setLocationRelativeTo(null); // DISPOSE_ON_CLOSE will ensure that the windows won't all close. frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setLayout(new CardLayout()); + + JTabbedPane tabs = new JTabbedPane(); + + JPanel routerInfoPanel = new JPanel(); + routerInfoPanel.setLayout(new GridLayout(0, 2)); + + JLabel[] routerLeftInfoLabels = { + new JLabel("Name"), + new JLabel("Gig 0/0 IP Address"), + new JLabel("Gig 0/0 Subnet Mask"), + new JLabel("Gig 0/0 MAC Address"), + new JLabel("Gig 0/1 IP Address"), + new JLabel("Gig 0/1 Subnet Mask"), + new JLabel("Gig 0/1 MAC Address"), + new JLabel("Gig 0/2 IP Address"), + new JLabel("Gig 0/2 Subnet Mask"), + new JLabel("Gig 0/2 MAC Address"), + }; + + JPanel routerLeftInfoPanel = new JPanel(new GridLayout(0, 1)); + for (JLabel label : routerLeftInfoLabels) { + routerLeftInfoPanel.add(label); + } + routerInfoPanel.add(routerLeftInfoPanel); + + JTextField[] routerLeftTextFields = { + new JTextField(router.getName()), + router.getPortGig00().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig00().getIpAddress().toString()), + router.getPortGig00().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig00().getSubnetMask().toString()), + router.getPortGig00().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig00().getMacAddress().toString()), + router.getPortGig01().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig01().getIpAddress().toString()), + router.getPortGig01().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig01().getSubnetMask().toString()), + router.getPortGig01().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig01().getMacAddress().toString()), + router.getPortGig02().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig02().getIpAddress().toString()), + router.getPortGig02().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig02().getSubnetMask().toString()), + router.getPortGig02().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig02().getMacAddress().toString()), + }; + + // Make sure the user cannot change the MAC Address. + routerLeftTextFields[3].setEditable(false); + routerLeftTextFields[6].setEditable(false); + routerLeftTextFields[9].setEditable(false); + + routerLeftTextFields[0] = new JTextField(router.getName()); + + JPanel routerRightInfoPanel = new JPanel(new GridLayout(0, 1)); + for (JTextField textField : routerLeftTextFields) { + routerRightInfoPanel.add(textField); + } + routerInfoPanel.add(routerRightInfoPanel); + + JPanel routerOSPInfoPanel = new JPanel(); + + tabs.addTab("Router Information", routerInfoPanel); + + frame.add(tabs); return frame; } -} +} \ No newline at end of file From b3397301e93d096428cfd05faa906e75d409e61a Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Tue, 18 Mar 2025 09:01:30 +0000 Subject: [PATCH 39/63] Router buttons now can now update the name only. Signed-off-by: Lukas Bauza --- src/RouterButton.java | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/RouterButton.java b/src/RouterButton.java index 6d2e520..c7297cf 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -1,5 +1,7 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; public class RouterButton extends JButton { Router router; @@ -48,7 +50,7 @@ public JFrame getInfoFrame() { } routerInfoPanel.add(routerLeftInfoPanel); - JTextField[] routerLeftTextFields = { + JTextField[] routerRightTextFields = { new JTextField(router.getName()), router.getPortGig00().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig00().getIpAddress().toString()), router.getPortGig00().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig00().getSubnetMask().toString()), @@ -62,18 +64,31 @@ public JFrame getInfoFrame() { }; // Make sure the user cannot change the MAC Address. - routerLeftTextFields[3].setEditable(false); - routerLeftTextFields[6].setEditable(false); - routerLeftTextFields[9].setEditable(false); - - routerLeftTextFields[0] = new JTextField(router.getName()); + routerRightTextFields[3].setEditable(false); + routerRightTextFields[6].setEditable(false); + routerRightTextFields[9].setEditable(false); JPanel routerRightInfoPanel = new JPanel(new GridLayout(0, 1)); - for (JTextField textField : routerLeftTextFields) { + for (JTextField textField : routerRightTextFields) { routerRightInfoPanel.add(textField); } routerInfoPanel.add(routerRightInfoPanel); + JButton saveButton = new JButton("Save Changes"); + routerInfoPanel.add(saveButton); + saveButton.addActionListener(e -> { + System.out.println("Saving changes"); + for (int i = 0; i < routerRightTextFields.length; i++) { + if (i != 3 && i != 6 && i != 9) { + routerRightTextFields[i].setText(routerRightTextFields[i].getText()); + System.out.println(routerRightTextFields[i].getText()); + } + } + router.setName(routerRightTextFields[0].getText()); + // Makes sure that the RouterButton is updated also. + this.setText(router.getName()); + }); + JPanel routerOSPInfoPanel = new JPanel(); tabs.addTab("Router Information", routerInfoPanel); From 2feca7f3851f1f0ea7eb9a06f647ec900b51a785 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 24 Mar 2025 11:50:03 +0000 Subject: [PATCH 40/63] TODO removed Signed-off-by: Lukas Bauza --- src/Device.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Device.java b/src/Device.java index b50b6bd..ce7972c 100644 --- a/src/Device.java +++ b/src/Device.java @@ -3,7 +3,6 @@ abstract public class Device { // Abstract class for holding data members and methods that are common within the Router and PC child classes. - // TODO: Need a device manager to keep track of device? Or maybe a device ID? private String name; // Name of the device private ARPTable arpTable = new ARPTable(); // ARP table for the device. private ArrayList nicList; // Interfaces for the device @@ -24,5 +23,4 @@ public Device(String name) { public void setNICList(ArrayList nicList) { this.nicList = nicList; } public ArrayList getNICList() { return this.nicList; } - } \ No newline at end of file From 9b630ad4e40be97b33d90667b65f5c89a9b6cd70 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 24 Mar 2025 11:51:49 +0000 Subject: [PATCH 41/63] Abstracted the button methods from PCButton to DeviceInfoFrame and InfoField, for better encapsulation and readability. Signed-off-by: Lukas Bauza --- src/DeviceInfoFrame.java | 93 ++++++++++++++++++++++++++++ src/InfoField.java | 48 +++++++++++++++ src/Main.java | 7 --- src/PCButton.java | 9 ++- src/RouterButton.java | 127 +++++++++++++-------------------------- 5 files changed, 191 insertions(+), 93 deletions(-) create mode 100644 src/DeviceInfoFrame.java create mode 100644 src/InfoField.java diff --git a/src/DeviceInfoFrame.java b/src/DeviceInfoFrame.java new file mode 100644 index 0000000..74d24c8 --- /dev/null +++ b/src/DeviceInfoFrame.java @@ -0,0 +1,93 @@ +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +public class DeviceInfoFrame extends JFrame { + private ArrayList infoFields = new ArrayList<>(); + private JButton saveButton = new JButton("Save"); + private Router router; + private RouterButton routerButton; + + public DeviceInfoFrame(String title, String[] labels, String[] fields, Router router, RouterButton routerButton) { + if (labels.length != fields.length) { + throw new IllegalArgumentException("Number of labels and fields do not match"); + } + + super.setTitle(title); + super.setSize(300, 300); + super.setLocationRelativeTo(null); // Make it center. + super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. + super.setResizable(false); + super.setLayout(new GridLayout(0, 1)); + + JTabbedPane tabs = new JTabbedPane(); + //setDeviceInfoTab(); + + for (int i = 0; i < labels.length; i++) { + infoFields.add(new InfoField(labels[i], fields[i])); + // Add the JLabel and JTextField to the JFrame. + super.add(infoFields.get(i)); + } + + this.saveButton.addActionListener(e -> { + System.out.println("Saving "); + router.setName(infoFields.get(0).getTextField().getText()); + routerButton.setText(infoFields.get(0).getTextField().getText()); + this.setTitle(infoFields.get(0).getTextField().getText()); + try { + IPAddress ipGig00 = new IPAddress(infoFields.get(1).getTextField().getText()); + router.setPortGig00IPAddress(ipGig00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid IP address for Gig0/0"); + } + try { + SubnetMask subnetGig00 = new SubnetMask(infoFields.get(2).getTextField().getText()); + router.setPortGig00SubnetMask(subnetGig00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid subnet mask for Gig0/0"); + } + try { + IPAddress ipGig01 = new IPAddress(infoFields.get(4).getTextField().getText()); + router.setPortGig01IPAddress(ipGig01); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid IP address for Gig0/1"); + } + try { + SubnetMask subnetGig01 = new SubnetMask(infoFields.get(5).getTextField().getText()); + router.setPortGig01SubnetMask(subnetGig01); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid subnet mask for Gig0/1"); + } + try { + IPAddress ipGig02 = new IPAddress(infoFields.get(7).getTextField().getText()); + router.setPortGig02IPAddress(ipGig02); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid IP address for Gig0/2"); + } + try { + SubnetMask subnetGig02 = new SubnetMask(infoFields.get(8).getTextField().getText()); + router.setPortGig02SubnetMask(subnetGig02); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid subnet mask for Gig0/2"); + } + }); + + super.add(saveButton); + } + + public void setEditable(String fieldLabel, boolean editable) { + for (InfoField infoField : infoFields) { + if (infoField.getLabel().getText().equals(fieldLabel)) { + infoField.setEditable(editable); + } + } + } + + private void setDeviceInfoTab() { + super.setLayout(new GridLayout(0, 1)); + + for (InfoField infoField : infoFields) { + super.add(infoField); + } + } +} \ No newline at end of file diff --git a/src/InfoField.java b/src/InfoField.java new file mode 100644 index 0000000..62e166b --- /dev/null +++ b/src/InfoField.java @@ -0,0 +1,48 @@ +import javax.swing.*; +import java.awt.*; + +public class InfoField extends JPanel { + private JLabel label; + private JTextField textField; + + InfoField(String labelText, String textFieldText) { + super.setLayout(new GridLayout(1, 2)); + super.setBorder(BorderFactory.createLineBorder(Color.lightGray)); + + this.label = new JLabel(labelText); + this.label.setHorizontalAlignment(SwingConstants.LEFT); + super.add(label); + + this.textField = new JTextField(textFieldText); + super.add(textField); + } + + public void setLabelText(String labelText) { + this.label.setText(labelText); + updateComponents(); + } + + public void setTextFieldText(String textFieldText) { + this.textField.setText(textFieldText); + updateComponents(); + } + + public void setEditable(boolean editable) { + this.textField.setEditable(editable); + } + + public JLabel getLabel() { + return label; + } + + public JTextField getTextField() { + return textField; + } + + private void updateComponents() { + super.removeAll(); + super.add(label); + super.add(textField); + } + +} diff --git a/src/Main.java b/src/Main.java index b31f45b..a188f8a 100644 --- a/src/Main.java +++ b/src/Main.java @@ -125,13 +125,6 @@ protected void paintComponent(Graphics g) { }); } - for (RouterButton routerButton : routerButtons) { - routerButton.addActionListener(eR -> { - JFrame routerFrame = routerButton.getInfoFrame(); - routerFrame.setVisible(true); - }); - } - frame.add(panel); }); diff --git a/src/PCButton.java b/src/PCButton.java index d894e80..f9d7544 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -1,4 +1,5 @@ import javax.swing.*; +import java.awt.*; public class PCButton extends JButton { PC pc; @@ -16,10 +17,16 @@ public JFrame getInfoFrame() { JFrame frame = new JFrame(); frame.setSize( 600, 400); - frame.setLocationRelativeTo(null); frame.setTitle(pc.getName()); + frame.setLocationRelativeTo(null); // DISPOSE_ON_CLOSE will ensure that the windows won't all close. frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + JTabbedPane tabs = new JTabbedPane(); + + JPanel pcInfoPanel = new JPanel(); + pcInfoPanel.setLayout(new GridLayout(0, 2)); + return frame; } } diff --git a/src/RouterButton.java b/src/RouterButton.java index c7297cf..57ec4fc 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -1,7 +1,4 @@ import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; public class RouterButton extends JButton { Router router; @@ -9,91 +6,51 @@ public class RouterButton extends JButton { public RouterButton(String name) { super(name); this.router = new Router(name); + + super.addActionListener(e -> { + String[] labels = { + "Name", + "Gig 0/0 IP Address", + "Gig 0/0 Subnet Mask", + "Gig 0/0 MAC Address", + "Gig 0/1 IP Address", + "Gig 0/1 Subnet Mask", + "Gig 0/1 MAC Address", + "Gig 0/2 IP Address", + "Gig 0/2 Subnet Mask", + "Gig 0/2 MAC Address" + }; + + String[] fields = { + router.getName(), + router.getPortGig00().getIpAddress() == null ? "" : router.getPortGig00().getIpAddress().toString(), + router.getPortGig00().getSubnetMask() == null ? "" : router.getPortGig00().getSubnetMask().toString(), + router.getPortGig00().getMacAddress() == null ? "" : router.getPortGig00().getMacAddress().toString(), + router.getPortGig01().getIpAddress() == null ? "" : router.getPortGig01().getIpAddress().toString(), + router.getPortGig01().getSubnetMask() == null ? "" : router.getPortGig01().getSubnetMask().toString(), + router.getPortGig01().getMacAddress() == null ? "" : router.getPortGig01().getMacAddress().toString(), + router.getPortGig02().getIpAddress() == null ? "" : router.getPortGig02().getIpAddress().toString(), + router.getPortGig02().getSubnetMask() == null ? "" : router.getPortGig02().getSubnetMask().toString(), + router.getPortGig02().getMacAddress() == null ? "" : router.getPortGig02().getMacAddress().toString(), + }; + + DeviceInfoFrame deviceInfoFrame = new DeviceInfoFrame( + router.getName(), + labels, + fields, + router, + this + ); + + deviceInfoFrame.setEditable("Gig 0/0 MAC Address", false); + deviceInfoFrame.setEditable("Gig 0/1 MAC Address", false); + deviceInfoFrame.setEditable("Gig 0/2 MAC Address", false); + + deviceInfoFrame.setVisible(true); + }); } public Router getRouter() { return router; } - - public JFrame getInfoFrame() { - JFrame frame = new JFrame(); - - frame.setSize(600, 400); - frame.setTitle(router.getName()); - // Make sure it is in the middle of the screen. - frame.setLocationRelativeTo(null); - // DISPOSE_ON_CLOSE will ensure that the windows won't all close. - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frame.setLayout(new CardLayout()); - - JTabbedPane tabs = new JTabbedPane(); - - JPanel routerInfoPanel = new JPanel(); - routerInfoPanel.setLayout(new GridLayout(0, 2)); - - JLabel[] routerLeftInfoLabels = { - new JLabel("Name"), - new JLabel("Gig 0/0 IP Address"), - new JLabel("Gig 0/0 Subnet Mask"), - new JLabel("Gig 0/0 MAC Address"), - new JLabel("Gig 0/1 IP Address"), - new JLabel("Gig 0/1 Subnet Mask"), - new JLabel("Gig 0/1 MAC Address"), - new JLabel("Gig 0/2 IP Address"), - new JLabel("Gig 0/2 Subnet Mask"), - new JLabel("Gig 0/2 MAC Address"), - }; - - JPanel routerLeftInfoPanel = new JPanel(new GridLayout(0, 1)); - for (JLabel label : routerLeftInfoLabels) { - routerLeftInfoPanel.add(label); - } - routerInfoPanel.add(routerLeftInfoPanel); - - JTextField[] routerRightTextFields = { - new JTextField(router.getName()), - router.getPortGig00().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig00().getIpAddress().toString()), - router.getPortGig00().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig00().getSubnetMask().toString()), - router.getPortGig00().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig00().getMacAddress().toString()), - router.getPortGig01().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig01().getIpAddress().toString()), - router.getPortGig01().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig01().getSubnetMask().toString()), - router.getPortGig01().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig01().getMacAddress().toString()), - router.getPortGig02().getIpAddress() == null ? new JTextField() : new JTextField(router.getPortGig02().getIpAddress().toString()), - router.getPortGig02().getSubnetMask() == null ? new JTextField() : new JTextField(router.getPortGig02().getSubnetMask().toString()), - router.getPortGig02().getMacAddress() == null ? new JTextField() : new JTextField(router.getPortGig02().getMacAddress().toString()), - }; - - // Make sure the user cannot change the MAC Address. - routerRightTextFields[3].setEditable(false); - routerRightTextFields[6].setEditable(false); - routerRightTextFields[9].setEditable(false); - - JPanel routerRightInfoPanel = new JPanel(new GridLayout(0, 1)); - for (JTextField textField : routerRightTextFields) { - routerRightInfoPanel.add(textField); - } - routerInfoPanel.add(routerRightInfoPanel); - - JButton saveButton = new JButton("Save Changes"); - routerInfoPanel.add(saveButton); - saveButton.addActionListener(e -> { - System.out.println("Saving changes"); - for (int i = 0; i < routerRightTextFields.length; i++) { - if (i != 3 && i != 6 && i != 9) { - routerRightTextFields[i].setText(routerRightTextFields[i].getText()); - System.out.println(routerRightTextFields[i].getText()); - } - } - router.setName(routerRightTextFields[0].getText()); - // Makes sure that the RouterButton is updated also. - this.setText(router.getName()); - }); - - JPanel routerOSPInfoPanel = new JPanel(); - - tabs.addTab("Router Information", routerInfoPanel); - - frame.add(tabs); - return frame; - } } \ No newline at end of file From 41fc13828d17d5843edfb53386742c9a7c3e962f Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 24 Mar 2025 12:07:21 +0000 Subject: [PATCH 42/63] Device information is now in tabs. Signed-off-by: Lukas Bauza --- src/DeviceInfoFrame.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/DeviceInfoFrame.java b/src/DeviceInfoFrame.java index 74d24c8..bce6ad8 100644 --- a/src/DeviceInfoFrame.java +++ b/src/DeviceInfoFrame.java @@ -18,16 +18,19 @@ public DeviceInfoFrame(String title, String[] labels, String[] fields, Router ro super.setLocationRelativeTo(null); // Make it center. super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. super.setResizable(false); - super.setLayout(new GridLayout(0, 1)); JTabbedPane tabs = new JTabbedPane(); - //setDeviceInfoTab(); + JPanel generalInformationPanel = new JPanel(); + generalInformationPanel.setLayout(new GridLayout(0, 1)); for (int i = 0; i < labels.length; i++) { infoFields.add(new InfoField(labels[i], fields[i])); // Add the JLabel and JTextField to the JFrame. - super.add(infoFields.get(i)); + generalInformationPanel.add(infoFields.get(i)); } + //setDeviceInfoTab(tabs); + tabs.addTab("General", generalInformationPanel); + super.add(tabs); this.saveButton.addActionListener(e -> { System.out.println("Saving "); @@ -72,7 +75,7 @@ public DeviceInfoFrame(String title, String[] labels, String[] fields, Router ro } }); - super.add(saveButton); + generalInformationPanel.add(saveButton); } public void setEditable(String fieldLabel, boolean editable) { @@ -83,11 +86,11 @@ public void setEditable(String fieldLabel, boolean editable) { } } - private void setDeviceInfoTab() { - super.setLayout(new GridLayout(0, 1)); + private void setDeviceInfoTab(JTabbedPane tabs) { + tabs.setLayout(new GridLayout(0, 1)); for (InfoField infoField : infoFields) { - super.add(infoField); + tabs.add(infoField); } } } \ No newline at end of file From 46a2f7040e8f4e1f76e0e66883c272bf82fe0bd0 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 24 Mar 2025 14:31:04 +0000 Subject: [PATCH 43/63] PC buttons are now abstracted with their own PC information displayed. Signed-off-by: Lukas Bauza --- src/Main.java | 7 ----- src/PC.java | 11 ++++++++ src/PCButton.java | 26 +++++++++++++++++++ src/PCInfoFrame.java | 61 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/PCInfoFrame.java diff --git a/src/Main.java b/src/Main.java index a188f8a..e8a8f22 100644 --- a/src/Main.java +++ b/src/Main.java @@ -118,13 +118,6 @@ protected void paintComponent(Graphics g) { panel.add(button); } - for (PCButton pcButton : pcButtons) { - pcButton.addActionListener(ePC -> { - JFrame pcFrame = pcButton.getInfoFrame(); - pcFrame.setVisible(true); - }); - } - frame.add(panel); }); diff --git a/src/PC.java b/src/PC.java index 1098182..215fadb 100644 --- a/src/PC.java +++ b/src/PC.java @@ -25,6 +25,9 @@ public class PC extends Device { */ PC(String name) { super(name); + + NIC fa00 = new NIC("FastEthernet 0/0"); + super.setNICList(new ArrayList<>(List.of(fa00))); } public NIC getPortFA00() { return super.getNICList().get(0); } @@ -43,4 +46,12 @@ public void setDefaultGateway(IPAddress ipaddress, SubnetMask subnetMask) { this.defaultGatewayIPAddress = ipaddress; this.defaultGatewaySubnetMask = subnetMask; } + + public void setPortFA00IPAddress(IPAddress ipAddress) { + super.getNICList().get(0).setIpAddress(ipAddress); + } + + public void setPortFA00SubnetMask(SubnetMask subnetMask) { + super.getNICList().get(0).setSubnetMask(subnetMask); + } } \ No newline at end of file diff --git a/src/PCButton.java b/src/PCButton.java index f9d7544..a62af82 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -7,6 +7,32 @@ public class PCButton extends JButton { public PCButton(String name) { super(name); this.pc = new PC(name); + + super.addActionListener(e -> { + String[] labels = { + "Name", + "Fa 0/0 IP Address", + "Fa 0/0 Subnet Mask" + }; + + String[] fields = { + pc.getName(), + pc.getPortFA00().getIpAddress() == null ? "" : pc.getPortFA00().getIpAddress().toString(), + pc.getPortFA00().getSubnetMask() == null ? "" : pc.getPortFA00().getSubnetMask().toString(), + }; + + PCInfoFrame pcInfoFrame = new PCInfoFrame( + pc.getName(), + labels, + fields, + pc, + this + ); + + pcInfoFrame.setEditable("Fa 0/0 MAC Address", false); + + pcInfoFrame.setVisible(true); + }); } public PC getPC() { diff --git a/src/PCInfoFrame.java b/src/PCInfoFrame.java new file mode 100644 index 0000000..0d54ef4 --- /dev/null +++ b/src/PCInfoFrame.java @@ -0,0 +1,61 @@ +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +public class PCInfoFrame extends JFrame{ + private ArrayList infoFields = new ArrayList<>(); + private JButton saveButton = new JButton("Save"); + + public PCInfoFrame(String title, String[] labels, String[] fields, PC pc, PCButton pcButton) { + if (labels.length != fields.length) { + throw new IllegalArgumentException("Number of labels and fields do not match"); + } + + super.setTitle(title); + super.setSize(300, 300); + super.setLocationRelativeTo(null); // Make it center. + super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. + super.setResizable(false); + + JTabbedPane tabs = new JTabbedPane(); + JPanel generalInformationPanel = new JPanel(); + generalInformationPanel.setLayout(new GridLayout(0, 1)); + + for (int i = 0; i < labels.length; i++) { + infoFields.add(new InfoField(labels[i], fields[i])); + // Add the JLabel and JTextField to the JFrame. + generalInformationPanel.add(infoFields.get(i)); + } + tabs.addTab("General", generalInformationPanel); + super.add(tabs); + + this.saveButton.addActionListener(e -> { + System.out.println("Saving"); + pc.setName(infoFields.get(0).getTextField().getText()); + pcButton.setText(infoFields.get(0).getTextField().getText()); + this.setTitle(infoFields.get(0).getTextField().getText()); + try { + IPAddress ipFa00 = new IPAddress(infoFields.get(1).getTextField().getText()); + pc.setPortFA00IPAddress(ipFa00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid IP address for Fa 0/0"); + } + try { + SubnetMask subnetFa00 = new SubnetMask(infoFields.get(2).getTextField().getText()); + pc.setPortFA00SubnetMask(subnetFa00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid Subnet mask for Fa 0/0"); + } + }); + + generalInformationPanel.add(saveButton); + } + + public void setEditable(String fieldLabel, boolean editable) { + for (InfoField infoField : infoFields) { + if (infoField.getLabel().getText().equals(fieldLabel)) { + infoField.setEditable(editable); + } + } + } +} From e8c14aec14d6696d55fed38004f492ba27324ee7 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 24 Mar 2025 14:31:33 +0000 Subject: [PATCH 44/63] DeviceInfoFrame.java and DeviceButton.java renamed. Signed-off-by: Lukas Bauza --- src/RouterButton.java | 10 +++++----- src/{DeviceInfoFrame.java => RouterInfoFrame.java} | 14 ++------------ 2 files changed, 7 insertions(+), 17 deletions(-) rename src/{DeviceInfoFrame.java => RouterInfoFrame.java} (90%) diff --git a/src/RouterButton.java b/src/RouterButton.java index 57ec4fc..2ea1316 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -34,7 +34,7 @@ public RouterButton(String name) { router.getPortGig02().getMacAddress() == null ? "" : router.getPortGig02().getMacAddress().toString(), }; - DeviceInfoFrame deviceInfoFrame = new DeviceInfoFrame( + RouterInfoFrame routerInfoFrame = new RouterInfoFrame( router.getName(), labels, fields, @@ -42,11 +42,11 @@ public RouterButton(String name) { this ); - deviceInfoFrame.setEditable("Gig 0/0 MAC Address", false); - deviceInfoFrame.setEditable("Gig 0/1 MAC Address", false); - deviceInfoFrame.setEditable("Gig 0/2 MAC Address", false); + routerInfoFrame.setEditable("Gig 0/0 MAC Address", false); + routerInfoFrame.setEditable("Gig 0/1 MAC Address", false); + routerInfoFrame.setEditable("Gig 0/2 MAC Address", false); - deviceInfoFrame.setVisible(true); + routerInfoFrame.setVisible(true); }); } diff --git a/src/DeviceInfoFrame.java b/src/RouterInfoFrame.java similarity index 90% rename from src/DeviceInfoFrame.java rename to src/RouterInfoFrame.java index bce6ad8..74e04ae 100644 --- a/src/DeviceInfoFrame.java +++ b/src/RouterInfoFrame.java @@ -2,13 +2,11 @@ import java.awt.*; import java.util.ArrayList; -public class DeviceInfoFrame extends JFrame { +public class RouterInfoFrame extends JFrame { private ArrayList infoFields = new ArrayList<>(); private JButton saveButton = new JButton("Save"); - private Router router; - private RouterButton routerButton; - public DeviceInfoFrame(String title, String[] labels, String[] fields, Router router, RouterButton routerButton) { + public RouterInfoFrame(String title, String[] labels, String[] fields, Router router, RouterButton routerButton) { if (labels.length != fields.length) { throw new IllegalArgumentException("Number of labels and fields do not match"); } @@ -85,12 +83,4 @@ public void setEditable(String fieldLabel, boolean editable) { } } } - - private void setDeviceInfoTab(JTabbedPane tabs) { - tabs.setLayout(new GridLayout(0, 1)); - - for (InfoField infoField : infoFields) { - tabs.add(infoField); - } - } } \ No newline at end of file From e5753213f3195144be7e7090291d5b233dad7a2d Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 30 Mar 2025 12:42:38 +0100 Subject: [PATCH 45/63] Created the JLabels for the ports, just need to display them. Signed-off-by: Lukas Bauza --- src/Main.java | 70 +----------- src/PreconfiguredNetworkPanel.java | 166 +++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 67 deletions(-) create mode 100644 src/PreconfiguredNetworkPanel.java diff --git a/src/Main.java b/src/Main.java index e8a8f22..cfed221 100644 --- a/src/Main.java +++ b/src/Main.java @@ -39,8 +39,6 @@ public static void main(String[] args) { frame.add(preconfiguredNetworkButton); frame.setVisible(true); // Make start_menu_frame visible - JLabel prebuiltNetworkLabel = new JLabel("Prebuilt Network"); - JLabel customNetworkLabel = new JLabel("Custom Network"); preconfiguredNetworkButton.addActionListener(e -> { @@ -54,71 +52,9 @@ public static void main(String[] args) { frame.setSize(1200, 1000); frame.setLayout(new GridLayout()); // rows=0, cols=1. Makes it vertical. - Line[] wires = { - new Line(60, 60, 180, 180, Color.BLACK), // PC0 to R0 - new Line(180, 180, 310, 310, Color.BLACK), // R0 to R1 - new Line(310, 310, 440, 440, Color.BLACK), // R1 to R2 - new Line(440, 440, 570, 570, Color.BLACK), // R2 to R3 - new Line(570, 570, 700, 700, Color.BLACK), // R3 to PC1 - new Line(240, 210, 730, 210, Color.BLACK), // R0 to R5 - new Line(320, 340, 545, 340, Color.BLACK), // R1 to R4 - new Line(545, 340, 850, 340, Color.BLACK), // R4 to R6 - new Line(760, 240, 850, 310, Color.BLACK), // R5 to R6 - new Line(480, 470, 570, 340, Color.BLACK), // R2 to R4 - new Line(880, 340, 1030, 210, Color.BLACK), // R6 to PC3 - }; - - JPanel panel = new JPanel() { - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - for (Line line : wires) { - line.draw(g); // Draw stored lines - } - } - }; - - panel.setLayout(null); // No layout, for placing items with x and y coordinates. - - panel.add(pcButtons[0]); - pcButtons[0].setBounds(new Rectangle(50, 50, 60, 60)); - - panel.add(routerButtons[0]); - routerButtons[0].setBounds(new Rectangle(180, 180, 60, 60)); - - panel.add(routerButtons[1]); - routerButtons[1].setBounds(new Rectangle(310, 310, 60, 60)); - - panel.add(routerButtons[2]); - routerButtons[2].setBounds(new Rectangle(440, 440, 60, 60)); - - panel.add(routerButtons[3]); - routerButtons[3].setBounds(new Rectangle(570, 570, 60, 60)); - - panel.add(pcButtons[1]); - pcButtons[1].setBounds(new Rectangle(700, 700, 60, 60)); - - panel.add(routerButtons[4]); - routerButtons[4].setBounds(new Rectangle(545, 310, 60, 60)); - - panel.add(routerButtons[5]); - routerButtons[5].setBounds(new Rectangle(700, 180, 60, 60)); - - panel.add(routerButtons[6]); - routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); - - panel.add(pcButtons[2]); - pcButtons[2].setBounds(new Rectangle(1000, 180, 60, 60)); - - for (JButton button : routerButtons) { - panel.add(button); - } - - for (JButton button : pcButtons) { - panel.add(button); - } - - frame.add(panel); + PreconfiguredNetworkPanel preconfiguredNetworkPanel = new PreconfiguredNetworkPanel(); + frame.add(preconfiguredNetworkPanel); + preconfiguredNetworkPanel.setVisible(true); }); customNetworkButton.addActionListener(e -> { diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java new file mode 100644 index 0000000..9c5125a --- /dev/null +++ b/src/PreconfiguredNetworkPanel.java @@ -0,0 +1,166 @@ +import javax.swing.*; +import java.awt.*; + +public class PreconfiguredNetworkPanel extends JPanel { + Line[] wires = { + new Line(60, 60, 180, 180, Color.BLACK), // PC0 to R0 + new Line(180, 180, 310, 310, Color.BLACK), // R0 to R1 + new Line(310, 310, 440, 440, Color.BLACK), // R1 to R2 + new Line(440, 440, 570, 570, Color.BLACK), // R2 to R3 + new Line(570, 570, 700, 700, Color.BLACK), // R3 to PC1 + new Line(240, 210, 730, 210, Color.BLACK), // R0 to R5 + new Line(320, 340, 545, 340, Color.BLACK), // R1 to R4 + new Line(545, 340, 850, 340, Color.BLACK), // R4 to R6 + new Line(760, 240, 850, 310, Color.BLACK), // R5 to R6 + new Line(480, 470, 570, 340, Color.BLACK), // R2 to R4 + new Line(880, 340, 1030, 210, Color.BLACK), // R6 to PC3 + }; + + PCButton[] pcButtons = getPCButtonArray(3, "PC"); + RouterButton[] routerButtons = getRouterButtonArray(7, "R"); + + JLabel[] pcFa00Lables = new JLabel[pcButtons.length]; + + JLabel[] routerGig00Lables = new JLabel[routerButtons.length]; + JLabel[] routerGig01Lables = new JLabel[routerButtons.length]; + JLabel[] routerGig02Lables = new JLabel[routerButtons.length]; + + public PreconfiguredNetworkPanel() { + this.setLayout(null); // No layout, for placing items with x and y coordinates. + addRouterAndPCButtons(); + setupPCPorts(); + setupRouterPorts(); + connectPCToRouter(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + for (Line line : wires) { + line.draw(g); // Draw stored lines + } + } + + public static void main(String[] args) { + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(1000, 800); + frame.setLocationRelativeTo(null); + frame.setLayout(new BorderLayout()); + + PreconfiguredNetworkPanel panel = new PreconfiguredNetworkPanel(); + + frame.add(panel, BorderLayout.CENTER); + frame.setVisible(true); + } + + private static RouterButton[] getRouterButtonArray(int count, String name) { + RouterButton[] routers = new RouterButton[count]; + + // count-- is post decrement, meaning the current count variable is used, then it is decremented. + while (count-- > 0) { + routers[count] = new RouterButton(name + (count)); + } + return routers; + } + + private static PCButton[] getPCButtonArray(int count, String name) { + PCButton[] pcButtons = new PCButton[count]; + + // count-- is post decrement, meaning the current count variable is used, then it is decremented. + while (count-- > 0) { + pcButtons[count] = new PCButton(name + (count)); + } + return pcButtons; + } + + private void setupPCPorts() { + pcButtons[0].getPC().setPortFA00(new IPAddress("192.168.1.254"), new SubnetMask("255.255.255.0")); + pcButtons[1].getPC().setPortFA00(new IPAddress("192.168.10.254"), new SubnetMask("255.255.255.0")); + pcButtons[2].getPC().setPortFA00(new IPAddress("192.168.19.254"), new SubnetMask("255.255.255.0")); + } + + private void setupRouterPorts() { + // Setup all the router IP Address and Subnet Masks for all of their ports. + for (int i = 0; i < routerButtons.length; i++) { + Router router = routerButtons[i].getRouter(); + router.setPortGig00(new IPAddress("192.168.%d.1".formatted((i * 3) + 1)), new SubnetMask("255.255.255.0")); + router.setPortGig01(new IPAddress("192.168.%d.1".formatted((i * 3) + 2)), new SubnetMask("255.255.255.0")); + router.setPortGig02(new IPAddress("192.168.%d.1".formatted((i * 3) + 3)), new SubnetMask("255.255.255.0")); + } + } + + private void connectPCToRouter() { + // Connect the router NIC to the PC NIC. + // PC0 connection to R0 Gig00 + routerButtons[0].getRouter().getPortGig00().setConnection(pcButtons[0].getPC().getPortFA00()); + pcButtons[0].getPC().getPortFA00().setConnection(routerButtons[0].getRouter().getPortGig00()); + // PC1 connection to R3 Gig00 + routerButtons[3].getRouter().getPortGig00().setConnection(pcButtons[1].getPC().getPortFA00()); + pcButtons[1].getPC().getPortFA00().setConnection(routerButtons[3].getRouter().getPortGig00()); + // PC2 connection to R6 Gig00 + routerButtons[6].getRouter().getPortGig00().setConnection(pcButtons[2].getPC().getPortFA00()); + pcButtons[2].getPC().getPortFA00().setConnection(routerButtons[6].getRouter().getPortGig00()); + } + + private void addRouterAndPCButtons() { + + this.add(pcButtons[0]); + pcButtons[0].setBounds(new Rectangle(50, 50, 60, 60)); + + this.add(routerButtons[0]); + routerButtons[0].setBounds(new Rectangle(180, 180, 60, 60)); + + this.add(routerButtons[1]); + routerButtons[1].setBounds(new Rectangle(310, 310, 60, 60)); + + this.add(routerButtons[2]); + routerButtons[2].setBounds(new Rectangle(440, 440, 60, 60)); + + this.add(routerButtons[3]); + routerButtons[3].setBounds(new Rectangle(570, 570, 60, 60)); + + this.add(pcButtons[1]); + pcButtons[1].setBounds(new Rectangle(700, 700, 60, 60)); + + this.add(routerButtons[4]); + routerButtons[4].setBounds(new Rectangle(545, 310, 60, 60)); + + this.add(routerButtons[5]); + routerButtons[5].setBounds(new Rectangle(700, 180, 60, 60)); + + this.add(routerButtons[6]); + routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); + + this.add(pcButtons[2]); + pcButtons[2].setBounds(new Rectangle(1000, 180, 60, 60)); + + for (JButton button : routerButtons) { + this.add(button); + } + + for (JButton button : pcButtons) { + this.add(button); + } + } + + private void createPCFa00Labels() { + // Set the name for all the PC labels + for (int i = 0; i < pcFa00Lables.length; i++) { + pcFa00Lables[i] = new JLabel("Fa 0/0"); + } + } + + private void createRouterAllLabels() { + // Set the name for all the Router labels + for (int i = 0; i < routerButtons.length; i++) { + if (i % 3 == 0) { + routerGig00Lables[i] = new JLabel("Gig 0/0"); + } else if (i % 3 == 1) { + routerGig01Lables[i] = new JLabel("Gig 0/1"); + } else { + routerGig02Lables[i] = new JLabel("Gig 0/2"); + } + } + } +} \ No newline at end of file From b2b1b05404a1a8852a385dfa11eb1350ca0ec174 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 30 Mar 2025 12:43:09 +0100 Subject: [PATCH 46/63] Can now set the IP and Subnet of a Router at the same time. Signed-off-by: Lukas Bauza --- src/Router.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Router.java b/src/Router.java index 6673aac..cb30bbc 100644 --- a/src/Router.java +++ b/src/Router.java @@ -21,6 +21,11 @@ public Router(String name) { public NIC getPortGig00() { return super.getNICList().get(0); } + public void setPortGig00(IPAddress ipAddress, SubnetMask subnetMask) { + super.getNICList().get(0).setIpAddress(ipAddress); + super.getNICList().get(0).setSubnetMask(subnetMask); + } + public void setPortGig00IPAddress(IPAddress ipAddress) { super.getNICList().get(0).setIpAddress(ipAddress); } @@ -29,6 +34,11 @@ public void setPortGig00SubnetMask(SubnetMask subnetMask) { super.getNICList().get(0).setSubnetMask(subnetMask); } + public void setPortGig01(IPAddress ipAddress, SubnetMask subnetMask) { + super.getNICList().get(1).setIpAddress(ipAddress); + super.getNICList().get(1).setSubnetMask(subnetMask); + } + public NIC getPortGig01() { return super.getNICList().get(1); } public void setPortGig01IPAddress(IPAddress ipAddress) { @@ -41,6 +51,11 @@ public void setPortGig01SubnetMask(SubnetMask subnetMask) { public NIC getPortGig02() { return super.getNICList().get(2); } + public void setPortGig02(IPAddress ipAddress, SubnetMask subnetMask) { + super.getNICList().get(2).setIpAddress(ipAddress); + super.getNICList().get(2).setSubnetMask(subnetMask); + } + public void setPortGig02IPAddress(IPAddress ipAddress) { super.getNICList().get(2).setIpAddress(ipAddress); } From 43292532bf9f9aa521a4375644f7d13dc052c721 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 31 Mar 2025 12:39:56 +0100 Subject: [PATCH 47/63] Port lables added to devices. Signed-off-by: Lukas Bauza --- src/PreconfiguredNetworkPanel.java | 97 ++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java index 9c5125a..a16f214 100644 --- a/src/PreconfiguredNetworkPanel.java +++ b/src/PreconfiguredNetworkPanel.java @@ -31,6 +31,10 @@ public PreconfiguredNetworkPanel() { setupPCPorts(); setupRouterPorts(); connectPCToRouter(); + createPCFa00Labels(); + createRouterAllLabels(); + placePCFa00Labels(); + placeRouterAllLabels(); } @Override @@ -41,19 +45,6 @@ protected void paintComponent(Graphics g) { } } - public static void main(String[] args) { - JFrame frame = new JFrame(); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(1000, 800); - frame.setLocationRelativeTo(null); - frame.setLayout(new BorderLayout()); - - PreconfiguredNetworkPanel panel = new PreconfiguredNetworkPanel(); - - frame.add(panel, BorderLayout.CENTER); - frame.setVisible(true); - } - private static RouterButton[] getRouterButtonArray(int count, String name) { RouterButton[] routers = new RouterButton[count]; @@ -105,34 +96,24 @@ private void connectPCToRouter() { private void addRouterAndPCButtons() { - this.add(pcButtons[0]); pcButtons[0].setBounds(new Rectangle(50, 50, 60, 60)); - this.add(routerButtons[0]); routerButtons[0].setBounds(new Rectangle(180, 180, 60, 60)); - this.add(routerButtons[1]); routerButtons[1].setBounds(new Rectangle(310, 310, 60, 60)); - this.add(routerButtons[2]); routerButtons[2].setBounds(new Rectangle(440, 440, 60, 60)); - this.add(routerButtons[3]); routerButtons[3].setBounds(new Rectangle(570, 570, 60, 60)); - this.add(pcButtons[1]); pcButtons[1].setBounds(new Rectangle(700, 700, 60, 60)); - this.add(routerButtons[4]); routerButtons[4].setBounds(new Rectangle(545, 310, 60, 60)); - this.add(routerButtons[5]); routerButtons[5].setBounds(new Rectangle(700, 180, 60, 60)); - this.add(routerButtons[6]); routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); - this.add(pcButtons[2]); pcButtons[2].setBounds(new Rectangle(1000, 180, 60, 60)); for (JButton button : routerButtons) { @@ -152,15 +133,69 @@ private void createPCFa00Labels() { } private void createRouterAllLabels() { - // Set the name for all the Router labels for (int i = 0; i < routerButtons.length; i++) { - if (i % 3 == 0) { - routerGig00Lables[i] = new JLabel("Gig 0/0"); - } else if (i % 3 == 1) { - routerGig01Lables[i] = new JLabel("Gig 0/1"); - } else { - routerGig02Lables[i] = new JLabel("Gig 0/2"); - } + routerGig00Lables[i] = new JLabel("Gig 0/0"); + } + + for (int i = 0; i < routerButtons.length; i++) { + routerGig01Lables[i] = new JLabel("Gig 0/1"); + } + + for (int i = 0; i < routerButtons.length; i++) { + routerGig02Lables[i] = new JLabel("Gig 0/2"); + } + } + + private void placePCFa00Labels() { + pcFa00Lables[0].setBounds(70, 90, 60, 60); + + pcFa00Lables[1].setBounds(700, 660, 60, 60); + + pcFa00Lables[2].setBounds(1000, 220, 60, 60); + + for (JLabel label : pcFa00Lables) { + this.add(label); + } + } + + private void placeRouterAllLabels() { + // R0 + routerGig00Lables[0].setBounds(130, 150, 60, 60); + routerGig01Lables[0].setBounds(200, 220, 60, 60); + routerGig02Lables[0].setBounds(240, 160, 60, 60); + // R1 + routerGig00Lables[1].setBounds(260, 280, 60, 60); + routerGig01Lables[1].setBounds(330, 350, 60, 60); + routerGig02Lables[1].setBounds(370, 290, 60, 60); + // R2 + routerGig00Lables[2].setBounds(390, 410, 60, 60); + routerGig01Lables[2].setBounds(460, 480, 60, 60); + routerGig02Lables[2].setBounds(500, 420, 60, 60); + // R3 + routerGig00Lables[3].setBounds(520, 540, 60, 60); + routerGig01Lables[3].setBounds(590, 610, 60, 60); + // R4 + routerGig00Lables[4].setBounds(500, 290, 60, 60); + routerGig01Lables[4].setBounds(550, 350, 60, 60); + routerGig02Lables[4].setBounds(610, 290, 60, 60); + // R5 + routerGig00Lables[5].setBounds(650, 160, 60, 60); + routerGig01Lables[5].setBounds(720, 220, 60, 60); + // R6 + routerGig00Lables[6].setBounds(800, 280, 60, 60); + routerGig01Lables[6].setBounds(800, 320, 60, 60); + routerGig02Lables[6].setBounds(910, 290, 60, 60); + + for (JLabel label : routerGig00Lables) { + this.add(label); + } + + for (JLabel label : routerGig01Lables) { + this.add(label); + } + + for (JLabel label : routerGig02Lables) { + this.add(label); } } } \ No newline at end of file From d907433e0755b3f8c894b733744ec2970952f28d Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 31 Mar 2025 14:42:00 +0100 Subject: [PATCH 48/63] Actual connections setup between devices, not just visual connections. Signed-off-by: Lukas Bauza --- src/PreconfiguredNetworkPanel.java | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java index a16f214..33e5beb 100644 --- a/src/PreconfiguredNetworkPanel.java +++ b/src/PreconfiguredNetworkPanel.java @@ -30,7 +30,7 @@ public PreconfiguredNetworkPanel() { addRouterAndPCButtons(); setupPCPorts(); setupRouterPorts(); - connectPCToRouter(); + setupConnections(); createPCFa00Labels(); createRouterAllLabels(); placePCFa00Labels(); @@ -81,17 +81,42 @@ private void setupRouterPorts() { } } - private void connectPCToRouter() { + private void setupConnections() { // Connect the router NIC to the PC NIC. - // PC0 connection to R0 Gig00 + // PC0 to R0 Gig00 routerButtons[0].getRouter().getPortGig00().setConnection(pcButtons[0].getPC().getPortFA00()); pcButtons[0].getPC().getPortFA00().setConnection(routerButtons[0].getRouter().getPortGig00()); - // PC1 connection to R3 Gig00 + // PC1 to R3 Gig00 routerButtons[3].getRouter().getPortGig00().setConnection(pcButtons[1].getPC().getPortFA00()); pcButtons[1].getPC().getPortFA00().setConnection(routerButtons[3].getRouter().getPortGig00()); - // PC2 connection to R6 Gig00 + // PC2 to R6 Gig00 routerButtons[6].getRouter().getPortGig00().setConnection(pcButtons[2].getPC().getPortFA00()); pcButtons[2].getPC().getPortFA00().setConnection(routerButtons[6].getRouter().getPortGig00()); + // Connect Router NIC ot the other Router NIC. + // R0 Gig01 to R1 Gig00 + routerButtons[0].getRouter().getPortGig01().setConnection(routerButtons[1].getRouter().getPortGig00()); + routerButtons[1].getRouter().getPortGig00().setConnection(routerButtons[0].getRouter().getPortGig01()); + // R0 Gig02 to R5 Gig00 + routerButtons[0].getRouter().getPortGig02().setConnection(routerButtons[5].getRouter().getPortGig00()); + routerButtons[5].getRouter().getPortGig00().setConnection(routerButtons[0].getRouter().getPortGig02()); + // R1 Gig01 to R2 Gig00 + routerButtons[1].getRouter().getPortGig01().setConnection(routerButtons[2].getRouter().getPortGig00()); + routerButtons[2].getRouter().getPortGig00().setConnection(routerButtons[1].getRouter().getPortGig01()); + // R1 Gig02 to R4 Gig00 + routerButtons[1].getRouter().getPortGig02().setConnection(routerButtons[4].getRouter().getPortGig00()); + routerButtons[4].getRouter().getPortGig00().setConnection(routerButtons[1].getRouter().getPortGig02()); + // R2 Gig01 to R4 Gig01 + routerButtons[2].getRouter().getPortGig01().setConnection(routerButtons[4].getRouter().getPortGig01()); + routerButtons[4].getRouter().getPortGig01().setConnection(routerButtons[2].getRouter().getPortGig01()); + // R2 Gig01 to R3 Gig00 + routerButtons[2].getRouter().getPortGig01().setConnection(routerButtons[3].getRouter().getPortGig00()); + routerButtons[3].getRouter().getPortGig00().setConnection(routerButtons[2].getRouter().getPortGig01()); + // R5 Gig01 to R6 Gig00 + routerButtons[5].getRouter().getPortGig01().setConnection(routerButtons[6].getRouter().getPortGig00()); + routerButtons[6].getRouter().getPortGig00().setConnection(routerButtons[5].getRouter().getPortGig01()); + // R4 Gig02 to R6 Gig01 + routerButtons[4].getRouter().getPortGig02().setConnection(routerButtons[6].getRouter().getPortGig01()); + routerButtons[6].getRouter().getPortGig01().setConnection(routerButtons[4].getRouter().getPortGig02()); } private void addRouterAndPCButtons() { From a0cab72b3dd67c6930f242709248c8f2076535b9 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:55:07 +0100 Subject: [PATCH 49/63] Added OSPF information and fixed error checking for setting the subnet mask and ip address. Signed-off-by: Lukas Bauza --- src/NIC.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/NIC.java b/src/NIC.java index ad9d58d..9ca4bb6 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -1,11 +1,25 @@ public class NIC { - // The name of an interface should not be changed once created. - private final String name; + private String name; private IPAddress ipAddress; private SubnetMask subNetMask; private MACAddress macAddress; private NIC connection; private NICManager nicManager = NICManager.getInstance(); + // Default is always 0 for OSPF + private int priority = 0; + // Assuming that OSPF is always configured. + // States: + // 1. Down + // 2. Init + // 3. Two-way + // 4. ExStart + // 5. Exchange + // 6. Loading + // 7. Full + private String state = "Full"; + // TODO: Placeholder, can change it to values between 20 and 40 seconds everytime the user + // looks at the value + private String deadTime = "00:00:31"; public NIC(String name) { this.name = name; @@ -33,12 +47,18 @@ public IPAddress getIpAddress() { } public void setIpAddress(IPAddress ipAddress) { - // Check if the combination of ip address and subnet mask is already set up for another NIC - if (subNetMask != null && nicManager.ipAndSubnetExists(ipAddress, subNetMask)) { + if (ipAddress == null) { + System.out.println("IP Address entered is empty"); + } else if (ipAddress.equals(this.ipAddress)) { + System.out.println("IP Address is the same"); + } else if (subNetMask != null && nicManager.ipAndSubnetExists(ipAddress, subNetMask)) { + System.err.println("setIpAddress() method called"); + // Check if the combination of ip address and subnet mask is already set up for another NIC System.err.println("This combination of IP Address and Subnet Mask already exists"); // Reset the subnet mask also this.subNetMask = null; } else { + System.out.println("IP Address set to: " + ipAddress); this.ipAddress = ipAddress; } } @@ -52,7 +72,12 @@ public SubnetMask getSubnetMask() { } public void setSubnetMask(SubnetMask subnetMask) { - if (ipAddress != null && nicManager.ipAndSubnetExists(ipAddress, subnetMask)) { + if (subnetMask == null) { + System.out.println("Subnet Mask entered is empty"); + } else if (subnetMask.equals(this.subNetMask)) { + System.out.println("Subnet Mask already set"); + } else if (ipAddress != null && nicManager.ipAndSubnetExists(ipAddress, subnetMask)) { + System.err.println("setSubnetMask() method called"); System.err.println("This combination of IP Address and Subnet Mask already exists"); // Reset the ip address also. this.ipAddress = null; @@ -95,5 +120,27 @@ public boolean isConnected() { return this.connection != null; } + public NIC getConnectedNIC() { + return this.connection; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + public String getDeadTime() { + return this.deadTime; + } + + public void setDeadTime(String deadTime) { + this.deadTime = deadTime; + } + + public String getState() { + return state; + } } \ No newline at end of file From 07189e40e8ae2bc0633998195d805fc6e00b19a7 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:55:19 +0100 Subject: [PATCH 50/63] Typo Signed-off-by: Lukas Bauza --- src/NICManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NICManager.java b/src/NICManager.java index ba72027..904b32e 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -58,7 +58,7 @@ public boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { if (nic.getIpAddress() == null || nic.getSubnetMask() == null) { continue; } - // If the ipAddres and the subnetMask match with the subnetMask and ipAddress of the NIC, then return true. + // If the ipAddress and the subnetMask match with the subnetMask and ipAddress of the NIC, then return true. if (nic.getIpAddress().equals(ipAddress) && nic.getSubnetMask().equals(subnetMask)) { return true; } From c6ede701e7ec7eb62eb66742983f8ff02f82bbcc Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:55:39 +0100 Subject: [PATCH 51/63] OSPF neighbours pane for routers Signed-off-by: Lukas Bauza --- src/OSPFNeighboursScrollPane.java | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/OSPFNeighboursScrollPane.java diff --git a/src/OSPFNeighboursScrollPane.java b/src/OSPFNeighboursScrollPane.java new file mode 100644 index 0000000..9628514 --- /dev/null +++ b/src/OSPFNeighboursScrollPane.java @@ -0,0 +1,58 @@ +import javax.swing.*; + +public class OSPFNeighboursScrollPane extends JScrollPane { + JTable table; + String[] columnNames = { "Neighbour ID", "Pri", "State", "Dead Time", "Address", "Interface" }; + Router router; + + public OSPFNeighboursScrollPane(Router router) { + this.router = router; + this.table = new JTable(getRows(), this.columnNames); + super.setViewportView(table); + setColumnWidth(); + } + + private int getConnectionCount() { + int count = 0; + for (NIC nic : router.getNICList()) { + if (nic.isConnected()) { + count++; + } + } + return count; + } + + private String[][] getRows() { + String[][] rows = new String[getConnectionCount()][columnNames.length]; + for (int i = 0; i < getConnectionCount(); i++) { + NIC currentNic = router.getNICList().get(i).getConnectedNIC(); + // TODO: + // Neighbour ID + rows[i][0] = router.getRid(); + // Pri + rows[i][1] = Integer.toString(currentNic.getPriority()); + // State + rows[i][2] = currentNic.getState(); + // Dead time + rows[i][3] = currentNic.getDeadTime(); + // Address + if (currentNic.getIpAddress() != null) { + rows[i][4] = currentNic.getIpAddress().toString(); + } + // Interface + rows[i][5] = currentNic.getName(); + } + return rows; + } + + private void setColumnWidth() { + // Neighbor ID + table.getColumnModel().getColumn(0).setPreferredWidth(15); + // Pri + table.getColumnModel().getColumn(1).setPreferredWidth(2); + // State + table.getColumnModel().getColumn(2).setPreferredWidth(2); + // Dead Time + table.getColumnModel().getColumn(3).setPreferredWidth(5); + } +} \ No newline at end of file From 590c18325bdf3a401b14707550aafce915b39cc4 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:55:58 +0100 Subject: [PATCH 52/63] RID for routers. Signed-off-by: Lukas Bauza --- src/RID.java | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/RID.java diff --git a/src/RID.java b/src/RID.java new file mode 100644 index 0000000..884e3e5 --- /dev/null +++ b/src/RID.java @@ -0,0 +1,120 @@ +public class RID { + //private String ipAddress; + private byte[] rid = new byte[4]; + + public RID(String rid) { + // constructor used for checking that each segment is the right type and in the right range. + // if a wrong type is entered, it will throw out an error into main, which + // can be caught and used for error handling. + if (validIpAddress(rid)) { + this.rid = ipStringToByteArray(rid); + } else { + throw new IllegalArgumentException("Each segment must be between 0 and 255."); + } + } + + public RID() { + this.rid = new byte[4]; + } + + public byte[] getRid() { + return rid; + } + + public String toString() { + // Needs to be converted to an unsigned int, to not have minus values. As bytes are represented in negatives. + String byte0 = Integer.toString(Byte.toUnsignedInt(rid[0])); + String byte1 = Integer.toString(Byte.toUnsignedInt(rid[1])); + String byte2 = Integer.toString(Byte.toUnsignedInt(rid[2])); + String byte3 = Integer.toString(Byte.toUnsignedInt(rid[3])); + + return byte0 + "." + byte1 + "." + byte2 + "." + byte3; + } + + public void setRid(String rid) { + //this.ipAddress = ipAddress; + if (validIpAddress(rid)) { + this.rid = ipStringToByteArray(rid); + } else { + throw new IllegalArgumentException("Each segment must be between 0 and 255."); + } + } + + public void setByte3(byte byte3) { + this.rid[3] = byte3; + } + + public void setByte2(byte byte2) { + this.rid[2] = byte2; + } + + public void setByte1(byte byte1) { + this.rid[1] = byte1; + } + + public void setByte0(byte byte0) { + this.rid[0] = byte0; + } + + private boolean validIpAddress(String rid) { + // used for checking if the entered IP address is in the correct + // format + + // Regex pattern that will accept 0.0.0.0 to 999.999.999.999 + String regex = "[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}"; + + // This will check if the pattern is at least correct, but won't check + // if the numbers within the pattern are below 255 + if (!rid.matches(regex)) { + return false; + } + + // This will check if the numbers within the rid are not too big + // Will split the string at hte "." and will have 4 digits + String[] arrRid = rid.split("[.]", 4); + + // Each digit within the array will be converted from a String to an in + // and will be checked if it is not over 255 + for (String digit : arrRid) { + int number = Integer.parseInt(digit); + if (number > 0x255) { + return false; + } + } + + // if all checks are complete, then the RID format is correct + return true; + } + + private byte[] ipStringToByteArray(String strRid) { + + byte[] rid = new byte[4]; + // Splits the ip address string at every ".", of a maximum of 4. + String[] strBytes = strRid.split("[.]", 4); + + for (int i = 0; i < strBytes.length; i++) { + rid[i] = (byte) Integer.parseInt(strBytes[i]); + } + + return rid; + } + + // Overrides the original equals method, which only compares memory locations matching, instead we also need to compare, + // if each instance is the same, while still being in another memory location. + public boolean equals(Object object) { + // Checks if both references (this and object) point to the same memory location, if so they are the same. + if(this == object) return true; + // Checks if the object is null or if the objects are of the same class, if so, then they are not equal, so + // return false. + if(object == null || this.getClass() != object.getClass()) return false; + // Cast the object to the SubnetMask object class. + RID ipAddress = (RID) object; + // Check if all the subnet mask values match, if not then return false. + if(this.rid[0] != ipAddress.getRid()[0]) return false; + if(this.rid[1] != ipAddress.getRid()[1]) return false; + if(this.rid[2] != ipAddress.getRid()[2]) return false; + if(this.rid[3] != ipAddress.getRid()[3]) return false; + // Once all conditions are met, then the objects equal. + return true; + } +} From a8a1dd2878d7620e0550efb9a89723d36e628a98 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:56:18 +0100 Subject: [PATCH 53/63] RID for routers. Signed-off-by: Lukas Bauza --- src/Router.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Router.java b/src/Router.java index cb30bbc..ef5b553 100644 --- a/src/Router.java +++ b/src/Router.java @@ -2,6 +2,7 @@ import java.util.List; public class Router extends Device { + RID rid; public Router(String name) { // Use the parent class constructor to add the name variable within the Device class. @@ -63,4 +64,15 @@ public void setPortGig02IPAddress(IPAddress ipAddress) { public void setPortGig02SubnetMask(SubnetMask subnetMask) { super.getNICList().get(2).setSubnetMask(subnetMask); } + + public void setRid(RID rid) { + this.rid = rid; + } + + public String getRid() { + if (this.rid == null) { + return ""; + } + return rid.toString(); + } } \ No newline at end of file From 18e5d92e6a0111a577ea40c7269efaacf88c1434 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:56:31 +0100 Subject: [PATCH 54/63] RID for routers. Signed-off-by: Lukas Bauza --- src/RouterButton.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/RouterButton.java b/src/RouterButton.java index 2ea1316..313ed2d 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -18,7 +18,8 @@ public RouterButton(String name) { "Gig 0/1 MAC Address", "Gig 0/2 IP Address", "Gig 0/2 Subnet Mask", - "Gig 0/2 MAC Address" + "Gig 0/2 MAC Address", + "RID", }; String[] fields = { @@ -32,6 +33,7 @@ public RouterButton(String name) { router.getPortGig02().getIpAddress() == null ? "" : router.getPortGig02().getIpAddress().toString(), router.getPortGig02().getSubnetMask() == null ? "" : router.getPortGig02().getSubnetMask().toString(), router.getPortGig02().getMacAddress() == null ? "" : router.getPortGig02().getMacAddress().toString(), + router.getRid() == null ? "" : router.getRid(), }; RouterInfoFrame routerInfoFrame = new RouterInfoFrame( From e02695dd126a4b18e37ea4a821753360a0bfcf6f Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Sun, 6 Apr 2025 08:57:00 +0100 Subject: [PATCH 55/63] RID for routers and OSPF neighbour pane. Signed-off-by: Lukas Bauza --- src/RouterInfoFrame.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/RouterInfoFrame.java b/src/RouterInfoFrame.java index 74e04ae..f1889af 100644 --- a/src/RouterInfoFrame.java +++ b/src/RouterInfoFrame.java @@ -12,7 +12,7 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro } super.setTitle(title); - super.setSize(300, 300); + super.setSize(500, 300); super.setLocationRelativeTo(null); // Make it center. super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. super.setResizable(false); @@ -28,6 +28,10 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro } //setDeviceInfoTab(tabs); tabs.addTab("General", generalInformationPanel); + + OSPFNeighboursScrollPane ospfNeighboursScrollPane = new OSPFNeighboursScrollPane(router); + tabs.addTab("OSPF Neighbours", ospfNeighboursScrollPane); + super.add(tabs); this.saveButton.addActionListener(e -> { @@ -71,6 +75,13 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro } catch (IllegalArgumentException exception) { System.out.println("Invalid subnet mask for Gig0/2"); } + try { + RID rid = new RID(infoFields.get(9).getTextField().getText()); + router.setRid(rid); + System.out.println(router.getRid()); + } catch (IllegalArgumentException exceptions) { + System.out.println("Invalid RID"); + } }); generalInformationPanel.add(saveButton); From d06e3563958bc498c829969b53cb41ab3ad700bc Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 28 Apr 2025 06:44:03 +0100 Subject: [PATCH 56/63] OSPF ping animation with path setup. Signed-off-by: Lukas Bauza --- .idea/vcs.xml | 1 + src/InfoField.java | 9 ++ src/Main.java | 8 +- src/NIC.java | 9 +- src/NICManager.java | 14 +++ src/PC.java | 4 +- src/PCButton.java | 11 +- src/PCInfoFrame.java | 110 +++++++++++++---- src/PacketAnimation.java | 92 ++++++++++++++ src/PrePathCalculation.java | 177 ++++++++++++++++++++++++++ src/PrePingProtocol.java | 191 +++++++++++++++++++++++++++++ src/PreconfiguredNetworkPanel.java | 110 +++++++++++++++-- src/Router.java | 6 +- 13 files changed, 689 insertions(+), 53 deletions(-) create mode 100644 src/PacketAnimation.java create mode 100644 src/PrePathCalculation.java create mode 100644 src/PrePingProtocol.java diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..e470994 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/src/InfoField.java b/src/InfoField.java index 62e166b..38731d0 100644 --- a/src/InfoField.java +++ b/src/InfoField.java @@ -17,6 +17,15 @@ public class InfoField extends JPanel { super.add(textField); } + InfoField(String labelTest) { + super.setLayout(new GridLayout(1, 2)); + super.setBorder(BorderFactory.createLineBorder(Color.lightGray)); + + this.label = new JLabel(labelTest); + this.label.setHorizontalAlignment(SwingConstants.LEFT); + super.add(label); + } + public void setLabelText(String labelText) { this.label.setText(labelText); updateComponents(); diff --git a/src/Main.java b/src/Main.java index cfed221..70f9138 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,8 +9,8 @@ public class Main { public static void main(String[] args) { - PCButton[] pcButtons = getPCButtonArray(3, "PC"); - RouterButton[] routerButtons = getRouterButtonArray(7, "R"); + //PCButton[] pcButtons = getPCButtonArray(3, "PC"); + //RouterButton[] routerButtons = getRouterButtonArray(7, "R"); JFrame frame = new JFrame("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X (all frames close) @@ -98,12 +98,12 @@ private static RouterButton[] getRouterButtonArray(int count, String name) { return routers; } - private static PCButton[] getPCButtonArray(int count, String name) { + private static PCButton[] getPCButtonArray(int count, String name, PreconfiguredNetworkPanel preconfiguredNetworkPanel) { PCButton[] pcButtons = new PCButton[count]; // count-- is post decrement, meaning the current count variable is used, then it is decremented. while (count-- > 0) { - pcButtons[count] = new PCButton(name + (count)); + pcButtons[count] = new PCButton(name + (count), preconfiguredNetworkPanel); } return pcButtons; } diff --git a/src/NIC.java b/src/NIC.java index 9ca4bb6..b3ec152 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -1,4 +1,5 @@ public class NIC { + private Device assignedDevice; private String name; private IPAddress ipAddress; private SubnetMask subNetMask; @@ -21,8 +22,9 @@ public class NIC { // looks at the value private String deadTime = "00:00:31"; - public NIC(String name) { + public NIC(String name, Device assignedDevice) { this.name = name; + this.assignedDevice = assignedDevice; // Add the NIC to the NICManager to keep track of NICs automatically. nicManager.addNIC(this); setMacAddress(); @@ -143,4 +145,9 @@ public void setDeadTime(String deadTime) { public String getState() { return state; } + + public Device getAssignedDevice() { + return assignedDevice; + } + } \ No newline at end of file diff --git a/src/NICManager.java b/src/NICManager.java index 904b32e..e1e3327 100644 --- a/src/NICManager.java +++ b/src/NICManager.java @@ -66,4 +66,18 @@ public boolean ipAndSubnetExists(IPAddress ipAddress, SubnetMask subnetMask) { // When no matches are mad then return false. return false; } + + public Device getDevice(IPAddress ipAddress, SubnetMask subnetMask) { + for (NIC nic : createdNICs) { + if (nic.getIpAddress() == null || nic.getSubnetMask() == null) { + continue; + } + if (nic.getIpAddress().equals(ipAddress) && nic.getSubnetMask().equals(subnetMask)) { + return nic.getAssignedDevice(); + } + } + + System.out.println("No device found for IP " + ipAddress + " and subnet " + subnetMask); + return null; + } } \ No newline at end of file diff --git a/src/PC.java b/src/PC.java index 215fadb..b94183a 100644 --- a/src/PC.java +++ b/src/PC.java @@ -11,7 +11,7 @@ public class PC extends Device { super(name); // Set up the NIC for the PC, which only has the FastEthernet 0/0 - NIC fa00 = new NIC("FastEthernet 0/0"); + NIC fa00 = new NIC("FastEthernet 0/0", this); fa00.setIpAddress(ipaddress); fa00.setSubnetMask(subnetMask); // Add the fa00 NIC to the ArrayList of nicList. @@ -26,7 +26,7 @@ public class PC extends Device { PC(String name) { super(name); - NIC fa00 = new NIC("FastEthernet 0/0"); + NIC fa00 = new NIC("FastEthernet 0/0", this); super.setNICList(new ArrayList<>(List.of(fa00))); } diff --git a/src/PCButton.java b/src/PCButton.java index a62af82..2e8ce8a 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -2,11 +2,13 @@ import java.awt.*; public class PCButton extends JButton { - PC pc; + private PC pc; + private PreconfiguredNetworkPanel networkPanel; - public PCButton(String name) { + public PCButton(String name, PreconfiguredNetworkPanel networkPanel) { super(name); this.pc = new PC(name); + this.networkPanel = networkPanel; super.addActionListener(e -> { String[] labels = { @@ -26,7 +28,8 @@ public PCButton(String name) { labels, fields, pc, - this + this, + networkPanel ); pcInfoFrame.setEditable("Fa 0/0 MAC Address", false); @@ -48,8 +51,6 @@ public JFrame getInfoFrame() { // DISPOSE_ON_CLOSE will ensure that the windows won't all close. frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - JTabbedPane tabs = new JTabbedPane(); - JPanel pcInfoPanel = new JPanel(); pcInfoPanel.setLayout(new GridLayout(0, 2)); diff --git a/src/PCInfoFrame.java b/src/PCInfoFrame.java index 0d54ef4..b094e29 100644 --- a/src/PCInfoFrame.java +++ b/src/PCInfoFrame.java @@ -1,20 +1,34 @@ -import javax.swing.*; import java.awt.*; import java.util.ArrayList; +import javax.swing.*; + +public class PCInfoFrame extends JFrame { -public class PCInfoFrame extends JFrame{ - private ArrayList infoFields = new ArrayList<>(); + private ArrayList infoFields = new ArrayList<>(); private JButton saveButton = new JButton("Save"); + private JButton pingButton = new JButton("Ping another PC"); + private PreconfiguredNetworkPanel networkPanel; - public PCInfoFrame(String title, String[] labels, String[] fields, PC pc, PCButton pcButton) { + public PCInfoFrame( + String title, + String[] labels, + String[] fields, + PC pc, + PCButton pcButton, + PreconfiguredNetworkPanel networkPanel + ) { if (labels.length != fields.length) { - throw new IllegalArgumentException("Number of labels and fields do not match"); + throw new IllegalArgumentException( + "Number of labels and fields do not match" + ); } + this.networkPanel = networkPanel; + super.setTitle(title); super.setSize(300, 300); - super.setLocationRelativeTo(null); // Make it center. - super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. + super.setLocationRelativeTo(null); // Make it center. + super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. super.setResizable(false); JTabbedPane tabs = new JTabbedPane(); @@ -30,25 +44,57 @@ public PCInfoFrame(String title, String[] labels, String[] fields, PC pc, PCButt super.add(tabs); this.saveButton.addActionListener(e -> { - System.out.println("Saving"); - pc.setName(infoFields.get(0).getTextField().getText()); - pcButton.setText(infoFields.get(0).getTextField().getText()); - this.setTitle(infoFields.get(0).getTextField().getText()); - try { - IPAddress ipFa00 = new IPAddress(infoFields.get(1).getTextField().getText()); - pc.setPortFA00IPAddress(ipFa00); - } catch (IllegalArgumentException exception) { - System.out.println("Invalid IP address for Fa 0/0"); - } - try { - SubnetMask subnetFa00 = new SubnetMask(infoFields.get(2).getTextField().getText()); - pc.setPortFA00SubnetMask(subnetFa00); - } catch (IllegalArgumentException exception) { - System.out.println("Invalid Subnet mask for Fa 0/0"); - } - }); - + System.out.println("Saving"); + pc.setName(infoFields.get(0).getTextField().getText()); + pcButton.setText(infoFields.get(0).getTextField().getText()); + this.setTitle(infoFields.get(0).getTextField().getText()); + try { + IPAddress ipFa00 = new IPAddress( + infoFields.get(1).getTextField().getText() + ); + pc.setPortFA00IPAddress(ipFa00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid IP address for Fa 0/0"); + } + try { + SubnetMask subnetFa00 = new SubnetMask( + infoFields.get(2).getTextField().getText() + ); + pc.setPortFA00SubnetMask(subnetFa00); + } catch (IllegalArgumentException exception) { + System.out.println("Invalid Subnet mask for Fa 0/0"); + } + }); generalInformationPanel.add(saveButton); + + this.pingButton.addActionListener(e1 -> { + System.out.println("Pinging"); + + JFrame destinationQuery = new JFrame(); + destinationQuery.setSize(400, 200); + destinationQuery.setLocationRelativeTo(null); + destinationQuery.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + destinationQuery.setResizable(false); + destinationQuery.setVisible(true); + destinationQuery.setLayout(new GridLayout(0, 1)); + + InfoField infoDestinationIPAddress = new InfoField("Destination IP Address", ""); + destinationQuery.add(infoDestinationIPAddress); + + InfoField infoDestinationSubnetMask = new InfoField("Destination Subnet Mask", ""); + destinationQuery.add(infoDestinationSubnetMask); + + JButton startPingButton = new JButton("Start ping"); + startPingButton.addActionListener(e2 -> { + String strDestinationIP = infoDestinationIPAddress.getTextField().getText(); + String strDestinationSubnetMask = infoDestinationSubnetMask.getTextField().getText(); + PrePingProtocol prePingProtocol = new PrePingProtocol(pc, strDestinationIP, strDestinationSubnetMask, networkPanel); + prePingProtocol.ping(); + }); + destinationQuery.add(startPingButton); + + }); + generalInformationPanel.add(pingButton); } public void setEditable(String fieldLabel, boolean editable) { @@ -58,4 +104,18 @@ public void setEditable(String fieldLabel, boolean editable) { } } } + + private boolean destinationExists(String destinationIPAddress, String destinationSubnetMask) { + // Convert the destination subnet mask string to a SubnetMask object + SubnetMask destSubnetMask = new SubnetMask(destinationSubnetMask); + + // Convert the destination IP address string to an IPAddress object + IPAddress destIPAddress = new IPAddress(destinationIPAddress); + + // Get the NICManager instance which keeps track of all NICs in the network + NICManager nicManager = NICManager.getInstance(); + + // Check if the IP and subnet mask combination exists in any NIC in the network + return nicManager.ipAndSubnetExists(destIPAddress, destSubnetMask); + } } diff --git a/src/PacketAnimation.java b/src/PacketAnimation.java new file mode 100644 index 0000000..7beffa3 --- /dev/null +++ b/src/PacketAnimation.java @@ -0,0 +1,92 @@ +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class PacketAnimation extends JPanel { + private List pointList; // List of Points for animation. + private int startX, startY; // Starting positions + private int endX, endY; // Ending positions + private float progress = 0f; // Progress of the animation (0.0 - 1.0) + private int currentCorner = 0; // Current corner we're animating towards + private boolean animating = false; // Flag to check if the animation is ongoing + + private static final int TIMER_DELAY = 20; // Timer delay in ms for smoother animation + private static final float PROGRESS_INCREMENT = 0.02f; // How much progress increases per frame + private static final int CIRCLE_SIZE = 10; // Size of the animated circle + + public PacketAnimation(List pointList) { + this.pointList = pointList; + + // Timer to update position every 10 ms for smooth transition + Timer timer = new Timer(TIMER_DELAY, e -> updatePosition()); + timer.start(); + } + + // Start the animation from the first point to the next + public void startAnimation() { + currentCorner = 0; // Start at the first point + animating = true; + progress = 0f; + startX = pointList.get(currentCorner).x; + startY = pointList.get(currentCorner).y; + endX = pointList.get((currentCorner + 1) % pointList.size()).x; // The next point + endY = pointList.get((currentCorner + 1) % pointList.size()).y; + } + + + private void updatePosition() { + if (animating) { + progress += PROGRESS_INCREMENT; // Increment progress by 1% every frame + if (progress >= 1f) { + progress = 1f; + // Move to the next point + currentCorner = (currentCorner + 1) % pointList.size(); + startX = endX; + startY = endY; + endX = pointList.get((currentCorner + 1) % pointList.size()).x; + endY = pointList.get((currentCorner + 1) % pointList.size()).y; + progress = 0f; // Reset progress for the next point animation + if (currentCorner == pointList.size() - 1) { + animating = false; // Stop once we've reached the last point + } + } + repaint(); + } + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + if (animating) { + // Linearly interpolate between start and end positions + int x = (int) (startX + progress * (endX - startX)); + int y = (int) (startY + progress * (endY - startY)); + + g.setColor(Color.RED); // Set the circle color + g.fillOval(x - 20, y - 20, 40, 40); // Draw the circle (diameter = 40px) + } + } + + public static void main(String[] args) { + // Example of setting up 6 corners using Point objects + List corners = List.of( + new Point(100, 100), + new Point(500, 100), + new Point(500, 500), + new Point(100, 500), + new Point(300, 300), + new Point(300, 100) + ); + + JFrame frame = new JFrame("Smooth Circle Animation"); + PacketAnimation panel = new PacketAnimation(corners); + frame.add(panel); + frame.setSize(600, 600); // Size of the window + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + + // Start the animation after the window is visible + SwingUtilities.invokeLater(panel::startAnimation); + } +} diff --git a/src/PrePathCalculation.java b/src/PrePathCalculation.java new file mode 100644 index 0000000..d0bc037 --- /dev/null +++ b/src/PrePathCalculation.java @@ -0,0 +1,177 @@ +public class PrePathCalculation { + private Router[] routers = new Router[7]; + + public PrePathCalculation(Router[] routers) { + if (routers.length != 7) { + throw new IllegalArgumentException("Router array length should be equal to 7"); + } else { + this.routers = routers; + } + } + + public String getShortestPath(String startPc, String endPc) { + // PC0, PC1 + if ((startPc.equals("PC0") && endPc.equals("PC1")) || (startPc.equals("PC1") && endPc.equals("PC0"))) { + int paths[] = { getPc0Pc1PathCost0(), getPc0Pc1PathCost1(), getPc0Pc1PathCost2(), getPc0Pc1PathCost3() }; + switch (findShortestPathIndex(paths)) { + case 0: + return "PC0--R0--R1--R2--R3--PC1"; + case 1: + return "PC0--R0--R1--R4--R2--R3--PC1"; + case 2: + return "PC0--R0--R5--R6--R4--R2--R3--PC1"; + case 3: + return "PC0--R0--R5--R6--R4--R1--R2--R3--PC1"; + default: + System.err.println("Invalid path comparison for " + startPc + " and " + endPc); + return "No path"; + } + } else if ((startPc.equals("PC0") && endPc.equals("PC2")) || (startPc.equals("PC2") && endPc.equals("PC0"))) { + int paths[] = { getPc0Pc2PathCost0(), getPc0Pc2PathCost1(), getPc0Pc2PathCost2() }; + switch (findShortestPathIndex(paths)) { + case 0: + return "PC0--R0--R5--R6--PC2"; + case 1: + return "PC0--R0--R1--R4--R6--PC2"; + case 2: + return "PC0--R0--R1--R2--R4--R6--PC2"; + default: + System.err.println("Invalid path comparison for " + startPc + " and " + endPc); + return "No path"; + } + } else if ((startPc.equals("PC1") && endPc.equals("PC2")) || (startPc.equals("PC2") && endPc.equals("PC1"))) { + int paths[] = { getPc1Pc2PathCost0(), getPc1Pc2PathCost1(), getPc1Pc2PathCost2() }; + switch (findShortestPathIndex(paths)) { + case 0: + return "PC1--R3--R2--R4--R6--PC2"; + case 1: + return "PC1--R3--R2--R1--R4--R6--PC2"; + case 2: + return "PC1--R3--R2--R1--R0--R5--R6--PC2"; + default: + System.err.println("Invalid path comparison for " + startPc + " and " + endPc); + return "No path"; + } + } + System.err.println("No path found between " + startPc + " and " + endPc); + return "No path"; + } + + private int findShortestPathIndex(int paths[]) { + int shortestIndex = 0; + int shortestCost = paths[0]; + + for (int i = 1; i < paths.length; i++) { + if (paths[i] < shortestCost) { + shortestCost = paths[i]; + shortestIndex = i; + } + } + + return shortestIndex; + } + + // PC0 PC2 path + // 1. R0--R5--R6 + private int getPc0Pc2PathCost0() { + return getR0R5Cost() + getR5R6Cost(); + } + + // 2. R0--R1--R4--R6 + private int getPc0Pc2PathCost1() { + return getR0R1Cost() + getR1R4Cost() + getR4R6Cost(); + } + + // 3. R0--R1--R2--R4--R6 + private int getPc0Pc2PathCost2() { + return getR0R1Cost() + getR1R2Cost() + getR2R4Cost() + getR4R6Cost(); + } + + // PC0 PC1 path + // 1. R0--R1--R2--R3 + private int getPc0Pc1PathCost0() { + return getR0R1Cost() + getR1R2Cost() + getR2R3Cost(); + } + + // 2. R0--R1--R4--R2--R3 + private int getPc0Pc1PathCost1() { + return getR0R1Cost() + getR1R4Cost() + getR2R4Cost() + getR2R3Cost(); + } + + // 3. R0--R5--R6--R4--R2--R3 + private int getPc0Pc1PathCost2() { + return getR0R5Cost() + getR5R6Cost() + getR4R6Cost() + getR2R4Cost() + getR2R3Cost(); + } + + // 4. R0--R5--R6--R4--R1--R2--R3 + private int getPc0Pc1PathCost3() { + return getR0R5Cost() + getR5R6Cost() + getR4R6Cost() + getR1R4Cost() + getR1R2Cost() + getR2R3Cost(); + } + + // PC1 PC2 path + // 1. R3--R2--R4--R6 + private int getPc1Pc2PathCost0() { + return getR2R3Cost() + getR2R4Cost() + getR4R6Cost(); + } + + // 2. PC1 || R3--R2--R1--R4--R6 || PC2 + private int getPc1Pc2PathCost1() { + return getR2R3Cost() + getR1R2Cost() + getR1R4Cost() + getR4R6Cost(); + } + + // 3. PC1 || R3--R2--R1--R0--R5--R6 || PC2 + private int getPc1Pc2PathCost2() { + return getR2R3Cost() + getR1R2Cost() + getR0R1Cost() + getR0R5Cost() + getR5R6Cost(); + } + + private int getPortCost(String name) { + if (name.equals("Fastethernet 0/0") || name.equals("Fastethernet 0/1") || name.equals("Fastethernet 0/2")) { + return 10; + } else { + return 1; + } + } + + private int getSectionCost(Router firstRouter, int firstRouterPort, Router secondRouter, int secondRouterPort) { + String firstRouterPortName = firstRouter.getNICList().get(firstRouterPort).getName(); + String secondRouterPortName = secondRouter.getNICList().get(secondRouterPort).getName(); + + if (getPortCost(firstRouterPortName) == 10 || getPortCost(secondRouterPortName) == 10) { + return 10; + } else { + return 1; + } + } + + private int getR0R5Cost() { + return getSectionCost(routers[0], 2, routers[5], 0); + } + + private int getR0R1Cost() { + return getSectionCost(routers[0], 1, routers[1], 0); + } + + private int getR5R6Cost() { + return getSectionCost(routers[5], 1, routers[6], 0); + } + + private int getR4R6Cost() { + return getSectionCost(routers[4], 2, routers[6], 1); + } + + private int getR1R4Cost() { + return getSectionCost(routers[1], 2, routers[4], 0); + } + + private int getR1R2Cost() { + return getSectionCost(routers[1], 1, routers[2], 0); + } + + private int getR2R4Cost() { + return getSectionCost(routers[2], 2, routers[4], 1); + } + + private int getR2R3Cost() { + return getSectionCost(routers[2], 1, routers[3], 0); + } +} \ No newline at end of file diff --git a/src/PrePingProtocol.java b/src/PrePingProtocol.java new file mode 100644 index 0000000..f98a4db --- /dev/null +++ b/src/PrePingProtocol.java @@ -0,0 +1,191 @@ +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class PrePingProtocol { + private Device sourceDevice; + private Device destinationDevice; + private Router[] routers; + private PC[] pcs; + private boolean canPing; + private PrePathCalculation prePathCalculation; + private PreconfiguredNetworkPanel networkPanel; + + // Declare coordinates as fields but don't initialize yet + private Point PC0_COORDINATES; + private Point PC1_COORDINATES; + private Point PC2_COORDINATES; + private Point R0_COORDINATES; + private Point R1_COORDINATES; + private Point R2_COORDINATES; + private Point R3_COORDINATES; + private Point R4_COORDINATES; + private Point R5_COORDINATES; + private Point R6_COORDINATES; + + public PrePingProtocol(Device sourceDevice, String strDestinationIP, String strDestinationSubnet, PreconfiguredNetworkPanel networkPanel) { + this.sourceDevice = sourceDevice; + this.networkPanel = networkPanel; + this.routers = networkPanel.getRouters(); + this.pcs = networkPanel.getPCs(); + + // Initialize coordinates after networkPanel is set + this.PC0_COORDINATES = networkPanel.getDeviceCoordinates("PC0"); + this.PC1_COORDINATES = networkPanel.getDeviceCoordinates("PC1"); + this.PC2_COORDINATES = networkPanel.getDeviceCoordinates("PC2"); + this.R0_COORDINATES = networkPanel.getDeviceCoordinates("R0"); + this.R1_COORDINATES = networkPanel.getDeviceCoordinates("R1"); + this.R2_COORDINATES = networkPanel.getDeviceCoordinates("R2"); + this.R3_COORDINATES = networkPanel.getDeviceCoordinates("R3"); + this.R4_COORDINATES = networkPanel.getDeviceCoordinates("R4"); + this.R5_COORDINATES = networkPanel.getDeviceCoordinates("R5"); + this.R6_COORDINATES = networkPanel.getDeviceCoordinates("R6"); + + if (!destinationExists(new IPAddress(strDestinationIP), new SubnetMask(strDestinationSubnet))) { + canPing = false; + System.out.println("Destination does not exist"); + // Show error popup if destination doesn't exist + javax.swing.JOptionPane.showMessageDialog( + null, + String.format(""" + IP Address: %s\n + SubnetMask: %s\n + Destination does not exist in the network""", strDestinationIP, strDestinationSubnet), + "Destination Not Found", + javax.swing.JOptionPane.ERROR_MESSAGE + ); + } else { + canPing = true; + this.destinationDevice = NICManager.getInstance().getDevice(new IPAddress(strDestinationIP), new SubnetMask(strDestinationSubnet)); + this.prePathCalculation = new PrePathCalculation(this.routers); + } + } + + public void ping() { + if (canPing) { + String sourceDeviceName = sourceDevice.getName(); + String destinationDeviceName = destinationDevice.getName(); + String path = prePathCalculation.getShortestPath(sourceDeviceName, destinationDeviceName); + System.out.println(sourceDeviceName + " -> " + destinationDeviceName); + pathAnimation(path, sourceDeviceName); + } + } + + private boolean destinationExists(IPAddress ipAddress, SubnetMask subnetMask) { + // Get the NICManager instance which keeps track of all NICs in the network + NICManager nicManager = NICManager.getInstance(); + + // Check if the IP and subnet mask combination exists in any NIC in the network + return nicManager.ipAndSubnetExists(ipAddress, subnetMask); + } + + private void pathAnimation(String path, String startingPoint) { + System.out.println(path); + PacketAnimation anim; + List pointList; + + switch (path) { + // PC0 to PC1 paths + case "PC0--R0--R1--R2--R3--PC1": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } else { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + case "PC0--R0--R1--R4--R2--R3--PC1": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } else { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + case "PC0--R0--R5--R6--R4--R2--R3--PC1": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } else { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + case "PC0--R0--R5--R6--R4--R1--R2--R3--PC1": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } else { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + // PC0 to PC2 paths + case "PC0--R0--R5--R6--PC2": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + case "PC0--R0--R1--R4--R6--PC2": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + case "PC0--R0--R1--R2--R4--R6--PC2": + if (startingPoint.equals("PC0")) { + pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + } + break; + + // PC1 to PC2 paths + case "PC1--R3--R2--R4--R6--PC2": + if (startingPoint.equals("PC1")) { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } + break; + + case "PC1--R3--R2--R1--R4--R6--PC2": + if (startingPoint.equals("PC1")) { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } + break; + + case "PC1--R3--R2--R1--R0--R5--R6--PC2": + if (startingPoint.equals("PC1")) { + pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + } else { + pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + } + break; + + default: + System.out.println("Unknown path: " + path); + return; + } + + // Create and setup the animation + anim = new PacketAnimation(pointList); + anim.setPreferredSize(networkPanel.getSize()); + anim.setSize(networkPanel.getSize()); + anim.setOpaque(false); + anim.setBounds(0, 0, networkPanel.getWidth(), networkPanel.getHeight()); + + // Add the animation panel at index 0 (bottom layer) + networkPanel.add(anim, 0); + networkPanel.revalidate(); + networkPanel.repaint(); + + // Start the animation on the EDT + SwingUtilities.invokeLater(anim::startAnimation); + } +} diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java index 33e5beb..d517e04 100644 --- a/src/PreconfiguredNetworkPanel.java +++ b/src/PreconfiguredNetworkPanel.java @@ -2,6 +2,42 @@ import java.awt.*; public class PreconfiguredNetworkPanel extends JPanel { + // Button size constants + private static final int BUTTON_WIDTH = 60; + private static final int BUTTON_HEIGHT = 60; + + // PC Button positions + private static final int PC0_X = 50; + private static final int PC0_Y = 50; + + private static final int PC1_X = 700; + private static final int PC1_Y = 700; + + private static final int PC2_X = 1000; + private static final int PC2_Y = 180; + + // Router Button positions + private static final int R0_X = 180; + private static final int R0_Y = 180; + + private static final int R1_X = 310; + private static final int R1_Y = 310; + + private static final int R2_X = 440; + private static final int R2_Y = 440; + + private static final int R3_X = 570; + private static final int R3_Y = 570; + + private static final int R4_X = 545; + private static final int R4_Y = 310; + + private static final int R5_X = 700; + private static final int R5_Y = 180; + + private static final int R6_X = 850; + private static final int R6_Y = 310; + Line[] wires = { new Line(60, 60, 180, 180, Color.BLACK), // PC0 to R0 new Line(180, 180, 310, 310, Color.BLACK), // R0 to R1 @@ -55,12 +91,12 @@ private static RouterButton[] getRouterButtonArray(int count, String name) { return routers; } - private static PCButton[] getPCButtonArray(int count, String name) { + private PCButton[] getPCButtonArray(int count, String name) { PCButton[] pcButtons = new PCButton[count]; // count-- is post decrement, meaning the current count variable is used, then it is decremented. while (count-- > 0) { - pcButtons[count] = new PCButton(name + (count)); + pcButtons[count] = new PCButton(name + (count), this); } return pcButtons; } @@ -120,26 +156,25 @@ private void setupConnections() { } private void addRouterAndPCButtons() { + pcButtons[0].setBounds(new Rectangle(PC0_X, PC0_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - pcButtons[0].setBounds(new Rectangle(50, 50, 60, 60)); - - routerButtons[0].setBounds(new Rectangle(180, 180, 60, 60)); + routerButtons[0].setBounds(new Rectangle(R0_X, R0_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[1].setBounds(new Rectangle(310, 310, 60, 60)); + routerButtons[1].setBounds(new Rectangle(R1_X, R1_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[2].setBounds(new Rectangle(440, 440, 60, 60)); + routerButtons[2].setBounds(new Rectangle(R2_X, R2_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[3].setBounds(new Rectangle(570, 570, 60, 60)); + routerButtons[3].setBounds(new Rectangle(R3_X, R3_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - pcButtons[1].setBounds(new Rectangle(700, 700, 60, 60)); + pcButtons[1].setBounds(new Rectangle(PC1_X, PC1_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[4].setBounds(new Rectangle(545, 310, 60, 60)); + routerButtons[4].setBounds(new Rectangle(R4_X, R4_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[5].setBounds(new Rectangle(700, 180, 60, 60)); + routerButtons[5].setBounds(new Rectangle(R5_X, R5_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - routerButtons[6].setBounds(new Rectangle(850, 310, 60, 60)); + routerButtons[6].setBounds(new Rectangle(R6_X, R6_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); - pcButtons[2].setBounds(new Rectangle(1000, 180, 60, 60)); + pcButtons[2].setBounds(new Rectangle(PC2_X, PC2_Y, BUTTON_WIDTH, BUTTON_HEIGHT)); for (JButton button : routerButtons) { this.add(button); @@ -223,4 +258,53 @@ private void placeRouterAllLabels() { this.add(label); } } + + public Router[] getRouters() { + Router[] routers = new Router[routerButtons.length]; + for (int i = 0; i < routerButtons.length; i++) { + routers[i] = routerButtons[i].getRouter(); + } + return routers; + } + + public PC[] getPCs() { + PC[] pcs = new PC[pcButtons.length]; + for (int i = 0; i < pcButtons.length; i++) { + pcs[i] = pcButtons[i].getPC(); + } + return pcs; + } + + // Coordinate getter methods for devices. + public Point getDeviceCoordinates(String deviceName) { + switch (deviceName) { + case "R0": + return new Point(R0_X, R0_Y); + case "R1": + return new Point(R1_X, R1_Y); + case "R2": + return new Point(R2_X, R2_Y); + case "R3": + return new Point(R3_X, R3_Y); + case "R4": + return new Point(R4_X, R4_Y); + case "R5": + return new Point(R5_X, R5_Y); + case "R6": + return new Point(R6_X, R6_Y); + case "PC0": + return new Point(PC0_X, PC0_Y); + case "PC1": + return new Point(PC1_X, PC1_Y); + case "PC2": + return new Point(PC2_X, PC2_Y); + default: + return null; + } + } + + // Get button dimensions + public Dimension getButtonDimension() { + return new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT); + } } \ No newline at end of file diff --git a/src/Router.java b/src/Router.java index ef5b553..ddbb7be 100644 --- a/src/Router.java +++ b/src/Router.java @@ -10,9 +10,9 @@ public Router(String name) { // Set up the NIC for the Router, which is Gig 0/0, Gig 0/1, Gig 0/2. There is only a name for them set, as the // user should be able to set the IP and subnet mask at a later time. - NIC gig00 = new NIC("GigabitEthernet 0/0"); - NIC gig01 = new NIC("GigabitEthernet 0/1"); - NIC gig02 = new NIC("GigabitEthernet 0/2"); + NIC gig00 = new NIC("GigabitEthernet 0/0", this); + NIC gig01 = new NIC("GigabitEthernet 0/1", this); + NIC gig02 = new NIC("GigabitEthernet 0/2", this); // Add the NICs to the ArrayList of the nic list within the parent class. super.setNICList(new ArrayList<>(List.of(gig00, gig01, gig02))); From 1f4fc5fe607c2179b42df7303dbf7406cb75ae37 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 28 Apr 2025 12:17:54 +0100 Subject: [PATCH 57/63] Showing the cost of the paths to the user. Signed-off-by: Lukas Bauza --- src/PrePathCalculation.java | 16 ++++++------ src/PreconfiguredNetworkPanel.java | 42 +++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/PrePathCalculation.java b/src/PrePathCalculation.java index d0bc037..6db66b6 100644 --- a/src/PrePathCalculation.java +++ b/src/PrePathCalculation.java @@ -143,35 +143,35 @@ private int getSectionCost(Router firstRouter, int firstRouterPort, Router secon } } - private int getR0R5Cost() { + public int getR0R5Cost() { return getSectionCost(routers[0], 2, routers[5], 0); } - private int getR0R1Cost() { + public int getR0R1Cost() { return getSectionCost(routers[0], 1, routers[1], 0); } - private int getR5R6Cost() { + public int getR5R6Cost() { return getSectionCost(routers[5], 1, routers[6], 0); } - private int getR4R6Cost() { + public int getR4R6Cost() { return getSectionCost(routers[4], 2, routers[6], 1); } - private int getR1R4Cost() { + public int getR1R4Cost() { return getSectionCost(routers[1], 2, routers[4], 0); } - private int getR1R2Cost() { + public int getR1R2Cost() { return getSectionCost(routers[1], 1, routers[2], 0); } - private int getR2R4Cost() { + public int getR2R4Cost() { return getSectionCost(routers[2], 2, routers[4], 1); } - private int getR2R3Cost() { + public int getR2R3Cost() { return getSectionCost(routers[2], 1, routers[3], 0); } } \ No newline at end of file diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java index d517e04..d1b78eb 100644 --- a/src/PreconfiguredNetworkPanel.java +++ b/src/PreconfiguredNetworkPanel.java @@ -38,6 +38,8 @@ public class PreconfiguredNetworkPanel extends JPanel { private static final int R6_X = 850; private static final int R6_Y = 310; + private PrePathCalculation prePathCalculation; + Line[] wires = { new Line(60, 60, 180, 180, Color.BLACK), // PC0 to R0 new Line(180, 180, 310, 310, Color.BLACK), // R0 to R1 @@ -51,7 +53,7 @@ public class PreconfiguredNetworkPanel extends JPanel { new Line(480, 470, 570, 340, Color.BLACK), // R2 to R4 new Line(880, 340, 1030, 210, Color.BLACK), // R6 to PC3 }; - + PCButton[] pcButtons = getPCButtonArray(3, "PC"); RouterButton[] routerButtons = getRouterButtonArray(7, "R"); @@ -71,6 +73,7 @@ public PreconfiguredNetworkPanel() { createRouterAllLabels(); placePCFa00Labels(); placeRouterAllLabels(); + placeCostLabels(); } @Override @@ -259,6 +262,43 @@ private void placeRouterAllLabels() { } } + private void placeCostLabels() { + prePathCalculation = new PrePathCalculation(getRouters()); + + int R0_R1_COST = prePathCalculation.getR0R1Cost(); + int R0_R5_COST = prePathCalculation.getR0R5Cost(); + int R1_R2_COST = prePathCalculation.getR1R2Cost(); + int R1_R4_COST = prePathCalculation.getR1R4Cost(); + int R2_R3_COST = prePathCalculation.getR2R3Cost(); + int R2_R4_COST = prePathCalculation.getR2R4Cost(); + int R4_R6_COST = prePathCalculation.getR4R6Cost(); + int R5_R6_COST = prePathCalculation.getR5R6Cost(); + + JLabel[] costLabels = { + new JLabel("R0 to R1: " + R0_R1_COST), + new JLabel("R0 to R5: " + R0_R5_COST), + new JLabel("R1 to R2: " + R1_R2_COST), + new JLabel("R1 to R4: " + R1_R4_COST), + new JLabel("R2 to R3: " + R2_R3_COST), + new JLabel("R2 to R4: " + R2_R4_COST), + new JLabel("R4 to R6: " + R4_R6_COST), + new JLabel("R5 to R6: " + R5_R6_COST) + }; + + costLabels[0].setBounds(((R0_X + R1_X) / 2) - 50, ((R0_Y + R1_Y) / 2) + 20, 120, 60); + costLabels[1].setBounds(((R0_X + R5_X) / 2), ((R0_Y + R5_Y) / 2) + 20, 120, 60); + costLabels[2].setBounds(((R1_X + R2_X) / 2) - 50, ((R1_Y + R2_Y) / 2) + 20, 120, 60); + costLabels[3].setBounds(((R1_X + R4_X) / 2), ((R1_Y + R4_Y) / 2) + 20, 120, 60); + costLabels[4].setBounds(((R2_X + R3_X) / 2) - 50, ((R2_Y + R3_Y) / 2) + 20, 120, 60); + costLabels[5].setBounds(((R2_X + R4_X) / 2) + 50, ((R2_Y + R4_Y) / 2) + 20, 120, 60); + costLabels[6].setBounds(((R4_X + R6_X) / 2), ((R4_Y + R6_Y) / 2) + 20, 120, 60); + costLabels[7].setBounds(((R5_X + R6_X) / 2) - 50, ((R5_Y + R6_Y) / 2) + 20, 120, 60); + + for (JLabel label : costLabels) { + this.add(label); + } + } + public Router[] getRouters() { Router[] routers = new Router[routerButtons.length]; for (int i = 0; i < routerButtons.length; i++) { From ebb84c91ec975230e88fc6bd6b797e9dfa1d0931 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 28 Apr 2025 20:00:48 +0100 Subject: [PATCH 58/63] Path changes now working, and changed the icons for the Routers and PC buttons. Signed-off-by: Lukas Bauza --- src/NIC.java | 18 +++++-- src/OSPFNeighboursScrollPane.java | 2 +- src/PCButton.java | 12 ++++- src/PacketAnimation.java | 10 ++-- src/PingProtocol.java | 6 +-- src/PrePathCalculation.java | 7 +-- src/PreconfiguredNetworkPanel.java | 65 +++++++++++++++---------- src/Router.java | 44 ++++++++++------- src/RouterButton.java | 60 ++++++++++++++--------- src/RouterInfoFrame.java | 77 +++++++++++++++++++++++++++--- 10 files changed, 214 insertions(+), 87 deletions(-) diff --git a/src/NIC.java b/src/NIC.java index b3ec152..a2d7d0d 100644 --- a/src/NIC.java +++ b/src/NIC.java @@ -1,6 +1,6 @@ public class NIC { private Device assignedDevice; - private String name; + private String type; private IPAddress ipAddress; private SubnetMask subNetMask; private MACAddress macAddress; @@ -22,8 +22,8 @@ public class NIC { // looks at the value private String deadTime = "00:00:31"; - public NIC(String name, Device assignedDevice) { - this.name = name; + public NIC(String type, Device assignedDevice) { + this.type = type; this.assignedDevice = assignedDevice; // Add the NIC to the NICManager to keep track of NICs automatically. nicManager.addNIC(this); @@ -40,8 +40,16 @@ private void setMacAddress() { } while (nicManager.macExists(macAddress) && count-- > 0); } - public String getName() { - return name; + public String getType() { + return type; + } + + public void setType(String type) { + if (type.equalsIgnoreCase("GigabitEthernet") || type.equalsIgnoreCase("FastEthernet")) { + this.type = type; + } else { + System.out.println("Invalid port type"); + } } public IPAddress getIpAddress() { diff --git a/src/OSPFNeighboursScrollPane.java b/src/OSPFNeighboursScrollPane.java index 9628514..1d096ca 100644 --- a/src/OSPFNeighboursScrollPane.java +++ b/src/OSPFNeighboursScrollPane.java @@ -40,7 +40,7 @@ private String[][] getRows() { rows[i][4] = currentNic.getIpAddress().toString(); } // Interface - rows[i][5] = currentNic.getName(); + rows[i][5] = currentNic.getType(); } return rows; } diff --git a/src/PCButton.java b/src/PCButton.java index 2e8ce8a..32e8bb4 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -6,10 +6,20 @@ public class PCButton extends JButton { private PreconfiguredNetworkPanel networkPanel; public PCButton(String name, PreconfiguredNetworkPanel networkPanel) { - super(name); + super(); this.pc = new PC(name); this.networkPanel = networkPanel; + // Set the pc icon + ImageIcon icon = new ImageIcon("images/icons8-pc-50.png"); + Image scaledImage = icon.getImage().getScaledInstance(50, 50, Image.SCALE_SMOOTH); + setIcon(new ImageIcon(scaledImage)); + + // Remove the border around the button. + setBorderPainted(false); + // Match the same colour as the background. + setBackground(new Color(240, 240, 240)); + super.addActionListener(e -> { String[] labels = { "Name", diff --git a/src/PacketAnimation.java b/src/PacketAnimation.java index 7beffa3..f454b7c 100644 --- a/src/PacketAnimation.java +++ b/src/PacketAnimation.java @@ -9,13 +9,17 @@ public class PacketAnimation extends JPanel { private float progress = 0f; // Progress of the animation (0.0 - 1.0) private int currentCorner = 0; // Current corner we're animating towards private boolean animating = false; // Flag to check if the animation is ongoing + private Image letterImage; // The letter icon image private static final int TIMER_DELAY = 20; // Timer delay in ms for smoother animation private static final float PROGRESS_INCREMENT = 0.02f; // How much progress increases per frame - private static final int CIRCLE_SIZE = 10; // Size of the animated circle public PacketAnimation(List pointList) { this.pointList = pointList; + + // Load the letter icon + ImageIcon icon = new ImageIcon("images/icons8-letter-50.png"); + letterImage = icon.getImage().getScaledInstance(40, 40, Image.SCALE_SMOOTH); // Timer to update position every 10 ms for smooth transition Timer timer = new Timer(TIMER_DELAY, e -> updatePosition()); @@ -63,8 +67,8 @@ protected void paintComponent(Graphics g) { int x = (int) (startX + progress * (endX - startX)); int y = (int) (startY + progress * (endY - startY)); - g.setColor(Color.RED); // Set the circle color - g.fillOval(x - 20, y - 20, 40, 40); // Draw the circle (diameter = 40px) + // Draw the letter icon + g.drawImage(letterImage, x - 20, y - 20, null); } } diff --git a/src/PingProtocol.java b/src/PingProtocol.java index 80e1da9..e0ca41e 100644 --- a/src/PingProtocol.java +++ b/src/PingProtocol.java @@ -87,7 +87,7 @@ private void delay(int seconds) { private void displayNICList(ArrayList nicList) { for (int i = 0; i < nicList.size(); i++) { - System.out.println(i + 1 + " " + nicList.get(i).getName()); + System.out.println(i + 1 + " " + nicList.get(i).getType()); } } @@ -108,7 +108,7 @@ private void arpProcessSuccessful(Device sourceDevice, Device destinationDevice) "-", // Placeholder for now, not really needed in my protocol, just in case for future. destinationNIC.getMacAddress(), "ARPA", // Placeholder for now, not really needed in my protocol, just in case for future. - destinationNIC.getName()); + destinationNIC.getType()); // Adding to the destination Device ARP table, with the necessary details. destinationDevice.getARPTable().addEntry( "Internet", // Placeholder for now, not really needed in my protocol, just in case for future. @@ -116,7 +116,7 @@ private void arpProcessSuccessful(Device sourceDevice, Device destinationDevice) "-", // Placeholder for now, not really needed in my protocol, just in case for future. sourceNIC.getMacAddress(), "ARPA", // Placeholder for now, not really needed in my protocol, just in case for future. - sourceNIC.getName()); + sourceNIC.getType()); System.out.println("--------- " + sourceDevice.getName() + " ARP Table ---------"); System.out.println(sourceDevice.getARPTable()); diff --git a/src/PrePathCalculation.java b/src/PrePathCalculation.java index 6db66b6..d4037c7 100644 --- a/src/PrePathCalculation.java +++ b/src/PrePathCalculation.java @@ -125,7 +125,8 @@ private int getPc1Pc2PathCost2() { } private int getPortCost(String name) { - if (name.equals("Fastethernet 0/0") || name.equals("Fastethernet 0/1") || name.equals("Fastethernet 0/2")) { + // TODO: Temporary, need to change so it will only be Fastethernet + if (name.equals("Fastethernet 0/0") || name.equals("Fastethernet 0/1") || name.equals("Fastethernet 0/2") || name.equals("FastEthernet")) { return 10; } else { return 1; @@ -133,8 +134,8 @@ private int getPortCost(String name) { } private int getSectionCost(Router firstRouter, int firstRouterPort, Router secondRouter, int secondRouterPort) { - String firstRouterPortName = firstRouter.getNICList().get(firstRouterPort).getName(); - String secondRouterPortName = secondRouter.getNICList().get(secondRouterPort).getName(); + String firstRouterPortName = firstRouter.getNICList().get(firstRouterPort).getType(); + String secondRouterPortName = secondRouter.getNICList().get(secondRouterPort).getType(); if (getPortCost(firstRouterPortName) == 10 || getPortCost(secondRouterPortName) == 10) { return 10; diff --git a/src/PreconfiguredNetworkPanel.java b/src/PreconfiguredNetworkPanel.java index d1b78eb..55e7ebb 100644 --- a/src/PreconfiguredNetworkPanel.java +++ b/src/PreconfiguredNetworkPanel.java @@ -114,48 +114,48 @@ private void setupRouterPorts() { // Setup all the router IP Address and Subnet Masks for all of their ports. for (int i = 0; i < routerButtons.length; i++) { Router router = routerButtons[i].getRouter(); - router.setPortGig00(new IPAddress("192.168.%d.1".formatted((i * 3) + 1)), new SubnetMask("255.255.255.0")); - router.setPortGig01(new IPAddress("192.168.%d.1".formatted((i * 3) + 2)), new SubnetMask("255.255.255.0")); - router.setPortGig02(new IPAddress("192.168.%d.1".formatted((i * 3) + 3)), new SubnetMask("255.255.255.0")); + router.setPort00(new IPAddress("192.168.%d.1".formatted((i * 3) + 1)), new SubnetMask("255.255.255.0")); + router.setPort01(new IPAddress("192.168.%d.1".formatted((i * 3) + 2)), new SubnetMask("255.255.255.0")); + router.setPort02(new IPAddress("192.168.%d.1".formatted((i * 3) + 3)), new SubnetMask("255.255.255.0")); } } private void setupConnections() { // Connect the router NIC to the PC NIC. // PC0 to R0 Gig00 - routerButtons[0].getRouter().getPortGig00().setConnection(pcButtons[0].getPC().getPortFA00()); - pcButtons[0].getPC().getPortFA00().setConnection(routerButtons[0].getRouter().getPortGig00()); + routerButtons[0].getRouter().getPort00().setConnection(pcButtons[0].getPC().getPortFA00()); + pcButtons[0].getPC().getPortFA00().setConnection(routerButtons[0].getRouter().getPort00()); // PC1 to R3 Gig00 - routerButtons[3].getRouter().getPortGig00().setConnection(pcButtons[1].getPC().getPortFA00()); - pcButtons[1].getPC().getPortFA00().setConnection(routerButtons[3].getRouter().getPortGig00()); + routerButtons[3].getRouter().getPort00().setConnection(pcButtons[1].getPC().getPortFA00()); + pcButtons[1].getPC().getPortFA00().setConnection(routerButtons[3].getRouter().getPort00()); // PC2 to R6 Gig00 - routerButtons[6].getRouter().getPortGig00().setConnection(pcButtons[2].getPC().getPortFA00()); - pcButtons[2].getPC().getPortFA00().setConnection(routerButtons[6].getRouter().getPortGig00()); + routerButtons[6].getRouter().getPort00().setConnection(pcButtons[2].getPC().getPortFA00()); + pcButtons[2].getPC().getPortFA00().setConnection(routerButtons[6].getRouter().getPort00()); // Connect Router NIC ot the other Router NIC. // R0 Gig01 to R1 Gig00 - routerButtons[0].getRouter().getPortGig01().setConnection(routerButtons[1].getRouter().getPortGig00()); - routerButtons[1].getRouter().getPortGig00().setConnection(routerButtons[0].getRouter().getPortGig01()); + routerButtons[0].getRouter().getPort01().setConnection(routerButtons[1].getRouter().getPort00()); + routerButtons[1].getRouter().getPort00().setConnection(routerButtons[0].getRouter().getPort01()); // R0 Gig02 to R5 Gig00 - routerButtons[0].getRouter().getPortGig02().setConnection(routerButtons[5].getRouter().getPortGig00()); - routerButtons[5].getRouter().getPortGig00().setConnection(routerButtons[0].getRouter().getPortGig02()); + routerButtons[0].getRouter().getPort02().setConnection(routerButtons[5].getRouter().getPort00()); + routerButtons[5].getRouter().getPort00().setConnection(routerButtons[0].getRouter().getPort02()); // R1 Gig01 to R2 Gig00 - routerButtons[1].getRouter().getPortGig01().setConnection(routerButtons[2].getRouter().getPortGig00()); - routerButtons[2].getRouter().getPortGig00().setConnection(routerButtons[1].getRouter().getPortGig01()); + routerButtons[1].getRouter().getPort01().setConnection(routerButtons[2].getRouter().getPort00()); + routerButtons[2].getRouter().getPort00().setConnection(routerButtons[1].getRouter().getPort01()); // R1 Gig02 to R4 Gig00 - routerButtons[1].getRouter().getPortGig02().setConnection(routerButtons[4].getRouter().getPortGig00()); - routerButtons[4].getRouter().getPortGig00().setConnection(routerButtons[1].getRouter().getPortGig02()); + routerButtons[1].getRouter().getPort02().setConnection(routerButtons[4].getRouter().getPort00()); + routerButtons[4].getRouter().getPort00().setConnection(routerButtons[1].getRouter().getPort02()); // R2 Gig01 to R4 Gig01 - routerButtons[2].getRouter().getPortGig01().setConnection(routerButtons[4].getRouter().getPortGig01()); - routerButtons[4].getRouter().getPortGig01().setConnection(routerButtons[2].getRouter().getPortGig01()); + routerButtons[2].getRouter().getPort01().setConnection(routerButtons[4].getRouter().getPort01()); + routerButtons[4].getRouter().getPort01().setConnection(routerButtons[2].getRouter().getPort01()); // R2 Gig01 to R3 Gig00 - routerButtons[2].getRouter().getPortGig01().setConnection(routerButtons[3].getRouter().getPortGig00()); - routerButtons[3].getRouter().getPortGig00().setConnection(routerButtons[2].getRouter().getPortGig01()); + routerButtons[2].getRouter().getPort01().setConnection(routerButtons[3].getRouter().getPort00()); + routerButtons[3].getRouter().getPort00().setConnection(routerButtons[2].getRouter().getPort01()); // R5 Gig01 to R6 Gig00 - routerButtons[5].getRouter().getPortGig01().setConnection(routerButtons[6].getRouter().getPortGig00()); - routerButtons[6].getRouter().getPortGig00().setConnection(routerButtons[5].getRouter().getPortGig01()); + routerButtons[5].getRouter().getPort01().setConnection(routerButtons[6].getRouter().getPort00()); + routerButtons[6].getRouter().getPort00().setConnection(routerButtons[5].getRouter().getPort01()); // R4 Gig02 to R6 Gig01 - routerButtons[4].getRouter().getPortGig02().setConnection(routerButtons[6].getRouter().getPortGig01()); - routerButtons[6].getRouter().getPortGig01().setConnection(routerButtons[4].getRouter().getPortGig02()); + routerButtons[4].getRouter().getPort02().setConnection(routerButtons[6].getRouter().getPort01()); + routerButtons[6].getRouter().getPort01().setConnection(routerButtons[4].getRouter().getPort02()); } private void addRouterAndPCButtons() { @@ -262,7 +262,16 @@ private void placeRouterAllLabels() { } } - private void placeCostLabels() { + public void placeCostLabels() { + // Remove existing cost labels + Component[] components = this.getComponents(); + for (Component component : components) { + if (component instanceof JLabel && ((JLabel) component).getText().contains(" to ")) { + this.remove(component); + } + } + + // Create new PrePathCalculation with current router configurations prePathCalculation = new PrePathCalculation(getRouters()); int R0_R1_COST = prePathCalculation.getR0R1Cost(); @@ -297,6 +306,10 @@ private void placeCostLabels() { for (JLabel label : costLabels) { this.add(label); } + + // Force a repaint to show the updated labels + this.revalidate(); + this.repaint(); } public Router[] getRouters() { diff --git a/src/Router.java b/src/Router.java index ddbb7be..4780e3e 100644 --- a/src/Router.java +++ b/src/Router.java @@ -10,61 +10,73 @@ public Router(String name) { // Set up the NIC for the Router, which is Gig 0/0, Gig 0/1, Gig 0/2. There is only a name for them set, as the // user should be able to set the IP and subnet mask at a later time. - NIC gig00 = new NIC("GigabitEthernet 0/0", this); - NIC gig01 = new NIC("GigabitEthernet 0/1", this); - NIC gig02 = new NIC("GigabitEthernet 0/2", this); + NIC port00 = new NIC("GigabitEthernet", this); + NIC port01 = new NIC("GigabitEthernet", this); + NIC port02 = new NIC("GigabitEthernet", this); // Add the NICs to the ArrayList of the nic list within the parent class. - super.setNICList(new ArrayList<>(List.of(gig00, gig01, gig02))); + super.setNICList(new ArrayList<>(List.of(port00, port01, port02))); } public ArrayList getNICList() { return super.getNICList(); } - public NIC getPortGig00() { return super.getNICList().get(0); } + public NIC getPort00() { return super.getNICList().get(0); } - public void setPortGig00(IPAddress ipAddress, SubnetMask subnetMask) { + public void setPort00(IPAddress ipAddress, SubnetMask subnetMask) { super.getNICList().get(0).setIpAddress(ipAddress); super.getNICList().get(0).setSubnetMask(subnetMask); } - public void setPortGig00IPAddress(IPAddress ipAddress) { + public void setPort00IPAddress(IPAddress ipAddress) { super.getNICList().get(0).setIpAddress(ipAddress); } - public void setPortGig00SubnetMask(SubnetMask subnetMask) { + public void setPort00SubnetMask(SubnetMask subnetMask) { super.getNICList().get(0).setSubnetMask(subnetMask); } - public void setPortGig01(IPAddress ipAddress, SubnetMask subnetMask) { + public void setPort00Type(String type) { + super.getNICList().get(0).setType(type); + } + + public void setPort01(IPAddress ipAddress, SubnetMask subnetMask) { super.getNICList().get(1).setIpAddress(ipAddress); super.getNICList().get(1).setSubnetMask(subnetMask); } - public NIC getPortGig01() { return super.getNICList().get(1); } + public NIC getPort01() { return super.getNICList().get(1); } - public void setPortGig01IPAddress(IPAddress ipAddress) { + public void setPort01IPAddress(IPAddress ipAddress) { super.getNICList().get(1).setIpAddress(ipAddress); } - public void setPortGig01SubnetMask(SubnetMask subnetMask) { + public void setPort01SubnetMask(SubnetMask subnetMask) { super.getNICList().get(1).setSubnetMask(subnetMask); } - public NIC getPortGig02() { return super.getNICList().get(2); } + public void setPort01Type(String type) { + super.getNICList().get(1).setType(type); + } + + public NIC getPort02() { return super.getNICList().get(2); } - public void setPortGig02(IPAddress ipAddress, SubnetMask subnetMask) { + public void setPort02(IPAddress ipAddress, SubnetMask subnetMask) { super.getNICList().get(2).setIpAddress(ipAddress); super.getNICList().get(2).setSubnetMask(subnetMask); } - public void setPortGig02IPAddress(IPAddress ipAddress) { + public void setPort02IPAddress(IPAddress ipAddress) { super.getNICList().get(2).setIpAddress(ipAddress); } - public void setPortGig02SubnetMask(SubnetMask subnetMask) { + public void setPort02SubnetMask(SubnetMask subnetMask) { super.getNICList().get(2).setSubnetMask(subnetMask); } + public void setPort02Type(String type) { + super.getNICList().get(2).setType(type); + } + public void setRid(RID rid) { this.rid = rid; } diff --git a/src/RouterButton.java b/src/RouterButton.java index 313ed2d..f2faeed 100644 --- a/src/RouterButton.java +++ b/src/RouterButton.java @@ -1,38 +1,53 @@ import javax.swing.*; +import java.awt.*; public class RouterButton extends JButton { Router router; public RouterButton(String name) { - super(name); + super(); this.router = new Router(name); + + // Set the router icon + ImageIcon icon = new ImageIcon("images/icons8-router-symbol-50.png"); + Image scaledImage = icon.getImage().getScaledInstance(50, 50, Image.SCALE_SMOOTH); + // This method comes from the AbstractButton class. + setIcon(new ImageIcon(scaledImage)); + + // Remove the border around the button. + setBorderPainted(false); + // Match the same colour as the background. + setBackground(new Color(240, 240, 240)); + + // Set tooltip to show the router name + setToolTipText(name); super.addActionListener(e -> { String[] labels = { "Name", - "Gig 0/0 IP Address", - "Gig 0/0 Subnet Mask", - "Gig 0/0 MAC Address", - "Gig 0/1 IP Address", - "Gig 0/1 Subnet Mask", - "Gig 0/1 MAC Address", - "Gig 0/2 IP Address", - "Gig 0/2 Subnet Mask", - "Gig 0/2 MAC Address", + router.getPort00().getType() + " 0/0 IP Address", + router.getPort00().getType() + " 0/0 Subnet Mask", + router.getPort00().getType() + " 0/0 MAC Address", + router.getPort01().getType() + " 0/1 IP Address", + router.getPort01().getType() + " 0/1 Subnet Mask", + router.getPort01().getType() + " 0/1 MAC Address", + router.getPort02().getType() + " 0/2 IP Address", + router.getPort02().getType() + " 0/2 Subnet Mask", + router.getPort02().getType() + " 0/2 MAC Address", "RID", }; String[] fields = { router.getName(), - router.getPortGig00().getIpAddress() == null ? "" : router.getPortGig00().getIpAddress().toString(), - router.getPortGig00().getSubnetMask() == null ? "" : router.getPortGig00().getSubnetMask().toString(), - router.getPortGig00().getMacAddress() == null ? "" : router.getPortGig00().getMacAddress().toString(), - router.getPortGig01().getIpAddress() == null ? "" : router.getPortGig01().getIpAddress().toString(), - router.getPortGig01().getSubnetMask() == null ? "" : router.getPortGig01().getSubnetMask().toString(), - router.getPortGig01().getMacAddress() == null ? "" : router.getPortGig01().getMacAddress().toString(), - router.getPortGig02().getIpAddress() == null ? "" : router.getPortGig02().getIpAddress().toString(), - router.getPortGig02().getSubnetMask() == null ? "" : router.getPortGig02().getSubnetMask().toString(), - router.getPortGig02().getMacAddress() == null ? "" : router.getPortGig02().getMacAddress().toString(), + router.getPort00().getIpAddress() == null ? "" : router.getPort00().getIpAddress().toString(), + router.getPort00().getSubnetMask() == null ? "" : router.getPort00().getSubnetMask().toString(), + router.getPort00().getMacAddress() == null ? "" : router.getPort00().getMacAddress().toString(), + router.getPort01().getIpAddress() == null ? "" : router.getPort01().getIpAddress().toString(), + router.getPort01().getSubnetMask() == null ? "" : router.getPort01().getSubnetMask().toString(), + router.getPort01().getMacAddress() == null ? "" : router.getPort01().getMacAddress().toString(), + router.getPort02().getIpAddress() == null ? "" : router.getPort02().getIpAddress().toString(), + router.getPort02().getSubnetMask() == null ? "" : router.getPort02().getSubnetMask().toString(), + router.getPort02().getMacAddress() == null ? "" : router.getPort02().getMacAddress().toString(), router.getRid() == null ? "" : router.getRid(), }; @@ -44,9 +59,10 @@ public RouterButton(String name) { this ); - routerInfoFrame.setEditable("Gig 0/0 MAC Address", false); - routerInfoFrame.setEditable("Gig 0/1 MAC Address", false); - routerInfoFrame.setEditable("Gig 0/2 MAC Address", false); + routerInfoFrame.setEditable(router.getPort00().getType() + " 0/0 MAC Address", false); + routerInfoFrame.setEditable(router.getPort01().getType() + " 0/1 MAC Address", false); + routerInfoFrame.setEditable(router.getPort02().getType() + " 0/2 MAC Address", false); + routerInfoFrame.setEditable("Port Type", false); routerInfoFrame.setVisible(true); }); diff --git a/src/RouterInfoFrame.java b/src/RouterInfoFrame.java index f1889af..6ddeb66 100644 --- a/src/RouterInfoFrame.java +++ b/src/RouterInfoFrame.java @@ -5,6 +5,7 @@ public class RouterInfoFrame extends JFrame { private ArrayList infoFields = new ArrayList<>(); private JButton saveButton = new JButton("Save"); + private JButton editPortTypeButton = new JButton("Edit PortType"); public RouterInfoFrame(String title, String[] labels, String[] fields, Router router, RouterButton routerButton) { if (labels.length != fields.length) { @@ -12,7 +13,7 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro } super.setTitle(title); - super.setSize(500, 300); + super.setSize(500, 500); super.setLocationRelativeTo(null); // Make it center. super.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // DISPOSE_ON_CLOSE Make sure the whole app doesn't shut down. super.setResizable(false); @@ -41,37 +42,37 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro this.setTitle(infoFields.get(0).getTextField().getText()); try { IPAddress ipGig00 = new IPAddress(infoFields.get(1).getTextField().getText()); - router.setPortGig00IPAddress(ipGig00); + router.setPort00IPAddress(ipGig00); } catch (IllegalArgumentException exception) { System.out.println("Invalid IP address for Gig0/0"); } try { SubnetMask subnetGig00 = new SubnetMask(infoFields.get(2).getTextField().getText()); - router.setPortGig00SubnetMask(subnetGig00); + router.setPort00SubnetMask(subnetGig00); } catch (IllegalArgumentException exception) { System.out.println("Invalid subnet mask for Gig0/0"); } try { IPAddress ipGig01 = new IPAddress(infoFields.get(4).getTextField().getText()); - router.setPortGig01IPAddress(ipGig01); + router.setPort01IPAddress(ipGig01); } catch (IllegalArgumentException exception) { System.out.println("Invalid IP address for Gig0/1"); } try { SubnetMask subnetGig01 = new SubnetMask(infoFields.get(5).getTextField().getText()); - router.setPortGig01SubnetMask(subnetGig01); + router.setPort01SubnetMask(subnetGig01); } catch (IllegalArgumentException exception) { System.out.println("Invalid subnet mask for Gig0/1"); } try { IPAddress ipGig02 = new IPAddress(infoFields.get(7).getTextField().getText()); - router.setPortGig02IPAddress(ipGig02); + router.setPort02IPAddress(ipGig02); } catch (IllegalArgumentException exception) { System.out.println("Invalid IP address for Gig0/2"); } try { SubnetMask subnetGig02 = new SubnetMask(infoFields.get(8).getTextField().getText()); - router.setPortGig02SubnetMask(subnetGig02); + router.setPort02SubnetMask(subnetGig02); } catch (IllegalArgumentException exception) { System.out.println("Invalid subnet mask for Gig0/2"); } @@ -85,6 +86,68 @@ public RouterInfoFrame(String title, String[] labels, String[] fields, Router ro }); generalInformationPanel.add(saveButton); + + this.editPortTypeButton.addActionListener(e -> { + JFrame editPortTypeFrame = new JFrame("Edit Port Type"); + editPortTypeFrame.setSize(300, 150); + editPortTypeFrame.setLocationRelativeTo(null); + editPortTypeFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + editPortTypeFrame.setResizable(false); + editPortTypeFrame.setLayout(new GridLayout(0, 1)); + + JComboBox port00TypeComboBox = new JComboBox<>(new String[]{"Port 00: GigabitEthernet", "Port 00: FastEthernet"}); + JComboBox port01TypeComboBox = new JComboBox<>(new String[]{"Port 01: GigabitEthernet", "Port 01: FastEthernet"}); + JComboBox port02TypeComboBox = new JComboBox<>(new String[]{"Port 02: GigabitEthernet", "Port 02: FastEthernet"}); + + // Set the current port types in the combo boxes + String currentPort00Type = router.getPort00().getType(); + String currentPort01Type = router.getPort01().getType(); + String currentPort02Type = router.getPort02().getType(); + + port00TypeComboBox.setSelectedItem("Port 00: " + currentPort00Type); + port01TypeComboBox.setSelectedItem("Port 01: " + currentPort01Type); + port02TypeComboBox.setSelectedItem("Port 02: " + currentPort02Type); + + editPortTypeFrame.add(port00TypeComboBox); + editPortTypeFrame.add(port01TypeComboBox); + editPortTypeFrame.add(port02TypeComboBox); + + JButton saveButton = new JButton("Save"); + saveButton.addActionListener(e1 -> { + String port00Type = port00TypeComboBox.getSelectedItem().toString().split(": ")[1]; + String port01Type = port01TypeComboBox.getSelectedItem().toString().split(": ")[1]; + String port02Type = port02TypeComboBox.getSelectedItem().toString().split(": ")[1]; + + router.setPort00Type(port00Type); + router.setPort01Type(port01Type); + router.setPort02Type(port02Type); + + // Update the labels in the info fields + infoFields.get(1).getLabel().setText(port00Type + " 0/0 IP Address"); + infoFields.get(2).getLabel().setText(port00Type + " 0/0 Subnet Mask"); + infoFields.get(3).getLabel().setText(port00Type + " 0/0 MAC Address"); + infoFields.get(4).getLabel().setText(port01Type + " 0/1 IP Address"); + infoFields.get(5).getLabel().setText(port01Type + " 0/1 Subnet Mask"); + infoFields.get(6).getLabel().setText(port01Type + " 0/1 MAC Address"); + infoFields.get(7).getLabel().setText(port02Type + " 0/2 IP Address"); + infoFields.get(8).getLabel().setText(port02Type + " 0/2 Subnet Mask"); + infoFields.get(9).getLabel().setText(port02Type + " 0/2 MAC Address"); + + // Update the cost path labels + PreconfiguredNetworkPanel networkPanel = (PreconfiguredNetworkPanel) routerButton.getParent(); + networkPanel.placeCostLabels(); + + System.out.println("Router Port 00: " + router.getPort00().getType()); + System.out.println("Router Port 01: " + router.getPort01().getType()); + System.out.println("Router Port 02: " + router.getPort02().getType()); + editPortTypeFrame.dispose(); + }); + editPortTypeFrame.add(saveButton); + + editPortTypeFrame.setVisible(true); + }); + + generalInformationPanel.add(editPortTypeButton); } public void setEditable(String fieldLabel, boolean editable) { From a7c8900c89a7c19bc994f62f27eb89814b3d45c3 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Mon, 28 Apr 2025 20:14:38 +0100 Subject: [PATCH 59/63] Add images for the Routers and PC buttons. Signed-off-by: Lukas Bauza --- images/icons8-letter-100.png | Bin 0 -> 1480 bytes images/icons8-letter-50.png | Bin 0 -> 724 bytes images/icons8-pc-50.png | Bin 0 -> 315 bytes images/icons8-router-symbol-100.png | Bin 0 -> 2317 bytes images/icons8-router-symbol-50.png | Bin 0 -> 1140 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/icons8-letter-100.png create mode 100644 images/icons8-letter-50.png create mode 100644 images/icons8-pc-50.png create mode 100644 images/icons8-router-symbol-100.png create mode 100644 images/icons8-router-symbol-50.png diff --git a/images/icons8-letter-100.png b/images/icons8-letter-100.png new file mode 100644 index 0000000000000000000000000000000000000000..a193ea33963135e3366600f0b1e10cd9de738b1e GIT binary patch literal 1480 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 literal 0 HcmV?d00001 diff --git a/images/icons8-router-symbol-50.png b/images/icons8-router-symbol-50.png new file mode 100644 index 0000000000000000000000000000000000000000..757788abe6bf3ae083511722f57e118c0cbeaef7 GIT binary patch literal 1140 zcmV-)1dIELP)3L!j_!ia=~ze8;rG*JtJZ?w>e6&R@uj5@QWL37ATzLP9&&$)N+eNNk}>6e`J zqU$oz&F5a zz}5s?j{vU&-#UA@fKgTuHd5!^2fqXM1n4#ObizL1duRXLO3coq>bQO2Z{ST}V}PC! zxDLz(lGzBn1uVGP9;PO2*K}v{I!^`a4bKz{^@IqCTsxt=gH{lJ%QnI-L+^KKLyi6okiNHdYl zHn%Fw0Ytmld}D|13B##^j^;#KiJpwGyVE%U^KQY1U4BlOcZ$Ii>Y(i{3W%V#lah_W zVYm5%me@P)oCtBTozO}o6`Oz`WXnjkMXy2y6W&cdsEdf<8QHqiX;uz84?!0a+2B0s z%k2a|mXO6x-AKeRE_>6IprNq(ZejqqT0{nv-`p$f=YD&=w4mZxD2fs;PsE=W zCnD_b0v4qga+UD_&bbk=vL%rTSErKcP=w8!(mP86`cxLmR7hTnBK8&?sG0sI-!l#= zFw3tMVn(s*wL$~9EWKNGT=;BvQ)|oY=|H{KIh1g?U~#GWGX0+)^r)VL01{G&i>hzzI)&2rae3DzkIgg)^BV(iv3?$CSlwQw^X;?{;bQl4NFFogfc84z1NMAbM35pH>QxMLzcw z#U5BI0!7%WL}yYJKy$T!xF6Nn`;SUbCnZ~&PppIgIPL?RIn-Sxi*bbj0000 Date: Mon, 28 Apr 2025 20:14:59 +0100 Subject: [PATCH 60/63] Show PC name on mouse hover. Signed-off-by: Lukas Bauza --- src/PCButton.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PCButton.java b/src/PCButton.java index 32e8bb4..900f0c3 100644 --- a/src/PCButton.java +++ b/src/PCButton.java @@ -20,6 +20,9 @@ public PCButton(String name, PreconfiguredNetworkPanel networkPanel) { // Match the same colour as the background. setBackground(new Color(240, 240, 240)); + // Set tooltip to show the pc name + setToolTipText(name); + super.addActionListener(e -> { String[] labels = { "Name", From fc5d156082688723b938d4ca24464d62cbc8de18 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Tue, 29 Apr 2025 08:38:54 +0100 Subject: [PATCH 61/63] Ping now goes back and forth 4 times, instead of just going once to the destination. Signed-off-by: Lukas Bauza --- src/PrePingProtocol.java | 74 ++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/PrePingProtocol.java b/src/PrePingProtocol.java index f98a4db..a3e551c 100644 --- a/src/PrePingProtocol.java +++ b/src/PrePingProtocol.java @@ -1,6 +1,9 @@ import javax.swing.*; import java.awt.*; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.stream.Stream; public class PrePingProtocol { private Device sourceDevice; @@ -83,88 +86,114 @@ private void pathAnimation(String path, String startingPoint) { System.out.println(path); PacketAnimation anim; List pointList; + List part1; + List part2; + // NOTE: Tried to use a for loop for these, but the animations just get canceled + // once the next one starts. switch (path) { // PC0 to PC1 paths case "PC0--R0--R1--R2--R3--PC1": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + //part2 = part1.reversed(); + part2 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } + //pointList = Stream.concat(pointList.stream(), pointList.stream()).toList(); break; case "PC0--R0--R1--R4--R2--R3--PC1": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + part2 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC0--R0--R5--R6--R4--R2--R3--PC1": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + part2 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC0--R0--R5--R6--R4--R1--R2--R3--PC1": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + part2 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; // PC0 to PC2 paths case "PC0--R0--R5--R6--PC2": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC0--R0--R1--R4--R6--PC2": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC0--R0--R1--R2--R4--R6--PC2": + part1 = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); if (startingPoint.equals("PC0")) { - pointList = List.of(PC0_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, PC0_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; // PC1 to PC2 paths case "PC1--R3--R2--R4--R6--PC2": + part1 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); if (startingPoint.equals("PC1")) { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC1--R3--R2--R1--R4--R6--PC2": + part1 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); if (startingPoint.equals("PC1")) { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R4_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R4_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; case "PC1--R3--R2--R1--R0--R5--R6--PC2": + part1 = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + part2 = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); if (startingPoint.equals("PC1")) { - pointList = List.of(PC1_COORDINATES, R3_COORDINATES, R2_COORDINATES, R1_COORDINATES, R0_COORDINATES, R5_COORDINATES, R6_COORDINATES, PC2_COORDINATES); + pointList = Stream.concat(part1.stream(), part2.stream()).toList(); } else { - pointList = List.of(PC2_COORDINATES, R6_COORDINATES, R5_COORDINATES, R0_COORDINATES, R1_COORDINATES, R2_COORDINATES, R3_COORDINATES, PC1_COORDINATES); + pointList = Stream.concat(part2.stream(), part1.stream()).toList(); } break; @@ -173,10 +202,11 @@ private void pathAnimation(String path, String startingPoint) { return; } + pointList = Stream.concat(pointList.stream(), pointList.stream()).toList(); // Create and setup the animation anim = new PacketAnimation(pointList); - anim.setPreferredSize(networkPanel.getSize()); - anim.setSize(networkPanel.getSize()); + //anim.setPreferredSize(networkPanel.getSize()); + //anim.setSize(networkPanel.getSize()); anim.setOpaque(false); anim.setBounds(0, 0, networkPanel.getWidth(), networkPanel.getHeight()); From 70b141b0d981b38e796b65de7cd4069e310e363c Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Thu, 1 May 2025 14:35:36 +0100 Subject: [PATCH 62/63] Commented out Signed-off-by: Lukas Bauza --- test/IPAddressTest.java | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/IPAddressTest.java b/test/IPAddressTest.java index 2fc2b9d..d3a5aaf 100644 --- a/test/IPAddressTest.java +++ b/test/IPAddressTest.java @@ -1,12 +1,12 @@ -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class IPAddressTest { - @Test - void convertFromIPAddressToString() { - IPAddress ipAddress = new IPAddress("192.168.1.1"); - // Check if the toString method still creates the correct formatting. - assertEquals("192.168.1.1", ipAddress.toString()); - } -} \ No newline at end of file +//import org.junit.jupiter.api.Test; +// +//import static org.junit.jupiter.api.Assertions.*; +// +//class IPAddressTest { +// @Test +// void convertFromIPAddressToString() { +// IPAddress ipAddress = new IPAddress("192.168.1.1"); +// // Check if the toString method still creates the correct formatting. +// assertEquals("192.168.1.1", ipAddress.toString()); +// } +//} \ No newline at end of file From c81e5fba7dd524c1a5321b3df9884ffde8717eb4 Mon Sep 17 00:00:00 2001 From: Lukas Bauza Date: Fri, 2 May 2025 20:04:36 +0100 Subject: [PATCH 63/63] Better animation Signed-off-by: Lukas Bauza --- src/Main.java | 3 -- src/PacketAnimation.java | 111 +++++++++++++++++++++++---------------- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/src/Main.java b/src/Main.java index 70f9138..eb09678 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,9 +9,6 @@ public class Main { public static void main(String[] args) { - //PCButton[] pcButtons = getPCButtonArray(3, "PC"); - //RouterButton[] routerButtons = getRouterButtonArray(7, "R"); - JFrame frame = new JFrame("OSPF Simulation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application, when pressing X (all frames close) frame.setResizable(false); diff --git a/src/PacketAnimation.java b/src/PacketAnimation.java index f454b7c..dcfe74c 100644 --- a/src/PacketAnimation.java +++ b/src/PacketAnimation.java @@ -3,77 +3,99 @@ import java.util.List; public class PacketAnimation extends JPanel { - private List pointList; // List of Points for animation. - private int startX, startY; // Starting positions - private int endX, endY; // Ending positions - private float progress = 0f; // Progress of the animation (0.0 - 1.0) - private int currentCorner = 0; // Current corner we're animating towards - private boolean animating = false; // Flag to check if the animation is ongoing - private Image letterImage; // The letter icon image + private List pointList; // List of ponts to do the animation + private int currentPoint = 0; // Current point in the animation + private boolean animating = false; // Used for checking if the animation is still going + private Image letterImage; // Used for the packet image - private static final int TIMER_DELAY = 20; // Timer delay in ms for smoother animation - private static final float PROGRESS_INCREMENT = 0.02f; // How much progress increases per frame + private int currentX, currentY; // Current position of the image + private int endX, endY; // Target position + + private static final int TIMER_DELAY = 20; // ms + private static final int SPEED = 4; // pixels per frame public PacketAnimation(List pointList) { this.pointList = pointList; - + // Load the letter icon ImageIcon icon = new ImageIcon("images/icons8-letter-50.png"); letterImage = icon.getImage().getScaledInstance(40, 40, Image.SCALE_SMOOTH); - // Timer to update position every 10 ms for smooth transition + // A timer is created, that has a delay set by the TIMER_DELAY, which is the amoutn of time. + // Then it calls the updatePosition() method, which then moves the packet to the next increment + // of x and y. Timer timer = new Timer(TIMER_DELAY, e -> updatePosition()); timer.start(); } - - // Start the animation from the first point to the next + public void startAnimation() { - currentCorner = 0; // Start at the first point + // If there are less than 2 points, then there cannot be an animation + if (pointList.size() < 2) return; + + currentPoint = 0; animating = true; - progress = 0f; - startX = pointList.get(currentCorner).x; - startY = pointList.get(currentCorner).y; - endX = pointList.get((currentCorner + 1) % pointList.size()).x; // The next point - endY = pointList.get((currentCorner + 1) % pointList.size()).y; + currentX = pointList.get(0).x; + currentY = pointList.get(0).y; + endX = pointList.get(1).x; + endY = pointList.get(1).y; } - private void updatePosition() { - if (animating) { - progress += PROGRESS_INCREMENT; // Increment progress by 1% every frame - if (progress >= 1f) { - progress = 1f; - // Move to the next point - currentCorner = (currentCorner + 1) % pointList.size(); - startX = endX; - startY = endY; - endX = pointList.get((currentCorner + 1) % pointList.size()).x; - endY = pointList.get((currentCorner + 1) % pointList.size()).y; - progress = 0f; // Reset progress for the next point animation - if (currentCorner == pointList.size() - 1) { - animating = false; // Stop once we've reached the last point - } + if (!animating) return; + + // Calculate the difference between the target coordinates and the current position + int dx = endX - currentX; + int dy = endY - currentY; + // Use Pythagorean theorem to get the point from the start to the end. + // This is done to get the straight point from the starting point, to the ending point. This + // is needed because of the angles that are used when making connections. + double distance = Math.sqrt(dx * dx + dy * dy); + + // If the distance of the packet is really close, then just start moving to the next point. + // This is done because sometimes it would get stuck, as it never reached the point exactly. + if (distance <= 5) { + // Move to next point + currentPoint++; + // Out of bounds check + if (currentPoint + 1 < pointList.size()) { + // Move to the next end point. + endX = pointList.get(currentPoint + 1).x; + endY = pointList.get(currentPoint + 1).y; + } else { + // If all points finished, then animation is finished. + animating = false; } - repaint(); + } else { + // Move in direction of the current end point + // The stepX and stepY is used to determine how much each axis contributes to the overall movement. + // This all depends on the angle at which it moves. It is basically checking the rate of change of y, + // and the rate of change of x. These are then added to the currentX and currentY, which moves it + // by the required rate of change. + // The SPEED is then used to also calculate how much the actual change should take place, which is used + // to just make th animation faster or slower. + double stepX = SPEED * dx / distance; + double stepY = SPEED * dy / distance; + currentX += (int) stepX; + currentY += (int) stepY; } + + repaint(); // Redraw at new position } @Override protected void paintComponent(Graphics g) { + // Clears the panel, and applies the background colour + // This is needed to repaint the packet super.paintComponent(g); if (animating) { - // Linearly interpolate between start and end positions - int x = (int) (startX + progress * (endX - startX)); - int y = (int) (startY + progress * (endY - startY)); - - // Draw the letter icon - g.drawImage(letterImage, x - 20, y - 20, null); + // If it is still animating, then it will draw the image of the packet, at the positions + g.drawImage(letterImage, currentX, currentY, null); } } + // Testing public static void main(String[] args) { - // Example of setting up 6 corners using Point objects List corners = List.of( new Point(100, 100), new Point(500, 100), @@ -83,14 +105,13 @@ public static void main(String[] args) { new Point(300, 100) ); - JFrame frame = new JFrame("Smooth Circle Animation"); + JFrame frame = new JFrame(); PacketAnimation panel = new PacketAnimation(corners); frame.add(panel); - frame.setSize(600, 600); // Size of the window + frame.setSize(600, 600); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); - // Start the animation after the window is visible SwingUtilities.invokeLater(panel::startAnimation); } }