package de.tu_darmstadt.tk.SmartHomeNetworkSim.view.popups; import java.awt.Color; import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.Controller; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.NetworkController; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Connection; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.ConnectionPrecision; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Model; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.PrecisionLink; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Protocol; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.protocols.MQTT_protocol; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.simpleImplementation.SimpleLink; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.JComboBox; import javax.swing.JButton; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @SuppressWarnings("serial") public class ConnectionCreationPanel extends JScrollPane { /** * Panel which contains the content of this creation panel */ private JPanel content; /** * Textfield which stores the Name of the connection */ private JTextField tfName; /** * Parent window which contains this frame - for the close operation */ private Window frame; /** * Connection which is edited */ private Connection connection; /** * Index of the disconnected option in the cmbPortRoles Array */ private int disconnectedIndex; /** * Ports which could participate in the connection */ private Port[] ports; /** * ComboBox for link selection */ private JComboBox cmbSelectedLink; /** * ComboBox for connection selection */ private JComboBox cmbConnection; /** * ComboBox for the protocol selection */ private JComboBox cmbProtocolType; /** * ComboBoxes to allow role changes of the different Ports */ private JComboBox[] cmbPortRoles; /** * ComboBox for the status */ private JComboBox cmbStatus; /** * True if an existing connection is being edited, or false if a new one is * being created */ private boolean edit; /** * True if a new Link was created for this connection */ private boolean newLink = false; /** * Last index which was selected in the protocolType ComboBox, to allow * restore the selected Index in case of invalid protocols */ private int lastProtocolIndex = 0; /** * Last index which was selected in the selectedLink ComboBox, to allow * restore the selected Index in case of invalid links */ private int lastLinkIndex = -1; /** * Last index which was selected in the selectedConnection ComboBox, to * allow restore the selected Index in case of invalid Connection */ private int lastConnectionIndex = -1; /** * Mutex for the protocol change, so the different Devices don't change * their role, while their comboBoxes are updated. True if the protocol * change is in progress and the cmbPortRoles should not update their role. */ private boolean protocolChange = false; /** * Controller to manipulate the model */ private Controller controller; /** * Network Controller */ private NetworkController network; /** * List of available protocols: should be moved to the Model and accessed * via controller */ private LinkedList> availableProtocols; /** * List of available connections */ private LinkedList> availableConnection; /** * Textfield for the packet loss */ private JTextField tfPacketLossRate; /** * Should not update if mutex is true */ private boolean mutex = true; private JTextField tfLabel; /** * Creates a new ConnectionCreation panel * @param connection connection which should be edited * @param controller controller for manipulation * @param frame parent frame * @wbp.parser.constructor */// @wbp.parser.constructor for Eclipse:WindowBuilder public ConnectionCreationPanel(Connection connection, Controller controller, Window frame) { this.controller = controller; this.connection = connection; this.network = controller.getNetworkController(); this.frame = frame; ports = new Port[connection.getParticipants().size()]; int i = 0; for (Port d : connection.getParticipants()) { // controller.addLinkToDevice(connection, d); this.ports[i++] = d; } edit = true; initializePanel(); } public ConnectionCreationPanel(Collection ports, Link l, Controller controller, Window frame) { this.controller = controller; this.network = controller.getNetworkController(); this.frame = frame; /** * if link null -> create new link */ if(l == null){ l = new PrecisionLink("New Link"); for(Port device: ports){ controller.getNetworkController().addLinkToDevice(l, device.getOwner()); } newLink = true; } connection = new ConnectionPrecision(l, new MQTT_protocol()); this.ports = new Port[ports.size()]; int i = 0; for (Port d : ports) { // controller.addLinkToDevice(connection, d); this.ports[i++] = d; } edit = false; initializePanel(); } @SuppressWarnings("unchecked") private void initializePanel() { // Init variables disconnectedIndex = connection.getProtocol().getNumberOfRoles(); availableProtocols = controller.getImportController().getProtocols(); availableConnection = controller.getImportController().getConnections(); // Sets up window setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); this.setPreferredSize(new Dimension(600, 250 + ports.length * 20)); content = new JPanel(); content.setPreferredSize(new Dimension(600, 240 + ports.length * 20)); this.setViewportView(content); content.setLayout(null); JLabel lblDescription = new JLabel("Connection Creation"); lblDescription.setBounds(12, 0, 144, 16); content.add(lblDescription); JLabel lblName = new JLabel("Name:"); lblName.setHorizontalAlignment(SwingConstants.RIGHT); lblName.setBounds(10, 10, 140, 20); content.add(lblName); tfName = new JTextField(); tfName.setBounds(155, 10, 290, 20); content.add(tfName); tfName.setColumns(10); tfName.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (mutex) { return; } connection.setName(tfName.getText()); } }); JLabel lblSelectedLink = new JLabel("Selected link:"); lblSelectedLink.setHorizontalAlignment(SwingConstants.RIGHT); lblSelectedLink.setBounds(10, 40, 140, 20); content.add(lblSelectedLink); // Check if model contains link if (connection.getLink() != null && !network.getLinks().contains(connection.getLink())) network.addLink(connection.getLink()); cmbSelectedLink = new JComboBox(); cmbSelectedLink.setBounds(155, 40, 125, 20); content.add(cmbSelectedLink); cmbSelectedLink.addActionListener(a -> { if (mutex) { return; } int cmbSelectedLinkIndex = cmbSelectedLink.getSelectedIndex(); if (cmbSelectedLinkIndex == -1) return; Link newLink = null; Iterator linkIterator = network.getLinks().iterator(); for (int i = 0; i < cmbSelectedLinkIndex && linkIterator.hasNext(); i++) { linkIterator.next(); } if (linkIterator.hasNext()) newLink = linkIterator.next(); if (!network.changeLinkOfConnection(connection, newLink)) { // if LinkChange failed: Restore cmbSelectedLink.setSelectedIndex(lastLinkIndex); } else { lastLinkIndex = cmbSelectedLinkIndex; } }); JButton btnCreateLink = new JButton("Create Link"); btnCreateLink.setBounds(290, 40, 155, 20); content.add(btnCreateLink); btnCreateLink.addActionListener(a -> { new LinkCreationDialog( connection.getParticipants().stream().map(lElem -> lElem.getOwner()).collect(Collectors.toList()), controller, content); // TODO: Refresh }); JLabel lblConnectionType = new JLabel("Connection type:"); lblConnectionType.setHorizontalAlignment(SwingConstants.RIGHT); lblConnectionType.setBounds(10, 70, 140, 20); content.add(lblConnectionType); cmbConnection = new JComboBox(); cmbConnection.setBounds(155, 70, 125, 20); content.add(cmbConnection); cmbConnection.addActionListener(a -> { if (mutex) { return; } cmbConnection.getSelectedIndex(); int selectedIndex = cmbConnection.getSelectedIndex(); if (lastConnectionIndex != selectedIndex && selectedIndex != -1) { Connection newConnection = network.changeConnectionType(connection, availableConnection.get(selectedIndex)); if (newConnection == null) { System.out.println("Warning invalidConnection changed"); cmbConnection.setSelectedIndex(lastConnectionIndex); } else { connection = newConnection; lastConnectionIndex = selectedIndex; } } else { cmbConnection.setSelectedIndex(lastConnectionIndex); } }); JButton btnImportConnection = new JButton("Import Connection"); btnImportConnection.setBounds(290, 70, 155, 20); content.add(btnImportConnection); btnImportConnection.addActionListener(a -> { ImportPopUp popUp = new ImportPopUp(this, Connection.class); try { Class imported = popUp.showPopUp(); if (imported == null) return; if (controller.getImportController().addConnection(imported)) { refreshGUI(); } else { JOptionPane.showMessageDialog(frame, "Import failed: Invalid Connection"); } } catch (Exception e1) { JOptionPane.showMessageDialog(frame, "Import failed: " + e1.getMessage()); } }); JLabel lblProtocolType = new JLabel("Protocol Type:"); lblProtocolType.setHorizontalAlignment(SwingConstants.RIGHT); lblProtocolType.setBounds(10, 100, 140, 20); content.add(lblProtocolType); cmbProtocolType = new JComboBox(); cmbProtocolType.setBounds(155, 100, 125, 20); content.add(cmbProtocolType); // Add Functionality for changing protocol cmbProtocolType.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (mutex) return; /** * Old Protocol */ Protocol oldProtocol = connection.getProtocol(); // Set Mutex to true, to disallow changes of roles during the // editing protocolChange = true; /** * New Protocol which should be created and configured */ Protocol newProtocol = null; try { // Create new Instance of the protocol newProtocol = controller.getImportController().getProtocols() .get(cmbProtocolType.getSelectedIndex()).newInstance(); } catch (InstantiationException | IllegalAccessException e1) { System.out.println("WARNING: Protocol could not be initialized"); } if (newProtocol == null) { cmbProtocolType.setSelectedIndex(lastProtocolIndex); System.out.println("WARNING: Invalid Protocol Selected - restore last index"); } else if (connection.setProtocol(newProtocol)) { /** * New Roles as Strings */ String[] newRoles = newProtocol.getRoles(); /** * The old disconnected status */ int oldDisconnected = oldProtocol.getNumberOfRoles(); // Update to new disconnected Index disconnectedIndex = newProtocol.getNumberOfRoles(); // Update Protocol // Update lastProtocolIndex to new Index lastProtocolIndex = cmbProtocolType.getSelectedIndex(); // Update Boxes for (int i = 0; i < cmbPortRoles.length; i++) { /** * The previous selected Index */ int oldSelected = cmbPortRoles[i].getSelectedIndex(); // Update Labels of the current Box cmbPortRoles[i].removeAllItems(); for (int j = 0; j < newRoles.length; j++) cmbPortRoles[i].addItem(newRoles[j]); cmbPortRoles[i].addItem("Disconnected"); // Try to update the Roles if (oldSelected < 0 || oldSelected >= disconnectedIndex || oldSelected == oldDisconnected) { // Invalid Index -> Disconnected network.removeDeviceFromConnectionAndProtocol(ports[i], connection); cmbPortRoles[i].setSelectedIndex(disconnectedIndex); } else { if (network.addDeviceToConnectionAndProtocol(ports[i], connection, oldSelected)) { // Set to Box to display role cmbPortRoles[i].setSelectedIndex(oldSelected); } else { // Could not be added -> Disconnected network.removeDeviceFromConnectionAndProtocol(ports[i], connection); cmbPortRoles[i].setSelectedIndex(disconnectedIndex); } } } } else { cmbProtocolType.setSelectedIndex(lastProtocolIndex); } // Set Mutex back to false - allow changes protocolChange = false; } }); JButton btnImportProtocol = new JButton("Import Protocol"); btnImportProtocol.setBounds(290, 100, 155, 20); content.add(btnImportProtocol); btnImportProtocol.addActionListener(a -> { ImportPopUp popUp = new ImportPopUp(this, Protocol.class); Class imported = null; try { imported = popUp.showPopUp(); } catch (Exception e1) { JOptionPane.showMessageDialog(frame, "Import failed: " + e1.getMessage()); return; } if (imported == null) { // Import cancelled return; } // Add Protocol if (controller.getImportController().addProtocol(imported)) { // Update GUI refreshGUI(); } else { JOptionPane.showMessageDialog(frame, "Import failed: Invalid Protocol"); return; } }); JLabel lblPacketlossrate = new JLabel("PacketLossRate:"); lblPacketlossrate.setHorizontalAlignment(SwingConstants.RIGHT); lblPacketlossrate.setBounds(10, 130, 140, 20); lblPacketlossrate.setToolTipText("Probability of Packet loss, should be a Double in [0.0,1.0]"); content.add(lblPacketlossrate); tfPacketLossRate = new JTextField(); tfPacketLossRate.setBounds(155, 130, 90, 20); content.add(tfPacketLossRate); tfPacketLossRate.setColumns(10); tfPacketLossRate.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { updateConnectionAndField(); } @Override public void insertUpdate(DocumentEvent e) { updateConnectionAndField(); } @Override public void changedUpdate(DocumentEvent e) { updateConnectionAndField(); } private void updateConnectionAndField() { if(mutex) return; try { double newRate = Double.parseDouble(tfPacketLossRate.getText()); if (newRate >= 0.0 && newRate <= 1.0) { connection.setPacketLossProbability(newRate); tfPacketLossRate.setBackground(Color.WHITE); } else { tfPacketLossRate.setBackground(Color.RED); } } catch (Exception exception) { tfPacketLossRate.setBackground(Color.RED); } } }); JLabel lblLabel = new JLabel("Label:"); lblLabel.setHorizontalAlignment(SwingConstants.RIGHT); lblLabel.setBounds(10, 160, 140, 20); content.add(lblLabel); tfLabel = new JTextField(); tfLabel.setColumns(10); tfLabel.setBounds(155, 160, 90, 20); content.add(tfLabel); tfLabel.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { updateConnectionAndField(); } @Override public void insertUpdate(DocumentEvent e) { updateConnectionAndField(); } @Override public void changedUpdate(DocumentEvent e) { updateConnectionAndField(); } private void updateConnectionAndField() { if(mutex) return; try { short newLabel = Short.parseShort(tfLabel.getText()); connection.setLabel(newLabel); tfLabel.setBackground(Color.WHITE); } catch (Exception exception) { tfLabel.setBackground(Color.RED); } } }); JLabel lblStatus = new JLabel("Status:"); lblStatus.setHorizontalAlignment(SwingConstants.RIGHT); lblStatus.setBounds(250, 130, 60, 20); content.add(lblStatus); cmbStatus = new JComboBox(); for (int i = 0; i < 5; i++) cmbStatus.addItem(Connection.getStatusName((byte) i)); cmbStatus.setBounds(320, 130, 125, 20); content.add(cmbStatus); cmbStatus.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (mutex) { cmbStatus.setSelectedIndex(connection.getStatus()); } if (cmbStatus.getSelectedIndex() < 0 || cmbStatus.getSelectedIndex() > 4) cmbStatus.setSelectedIndex(connection.getStatus()); else connection.setStatus((byte) cmbStatus.getSelectedIndex()); } }); JButton btnCreate = new JButton("Verify and Create"); btnCreate.setBounds(127, 186, 206, 25); content.add(btnCreate); btnCreate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { /** * Validate Packet loss rate */ try{ double d = Double.parseDouble(tfPacketLossRate.getText()); if(d < 0.0 && 1.0 < d) throw new Exception("Packet loss rate out of bounds"); connection.setPacketLossProbability(d); }catch(Exception e1){ JOptionPane.showMessageDialog(frame, "Invalid PacketsLossProbability, should be double between 0.0 and 1.0", "Invalid Packetloss Rate", JOptionPane.WARNING_MESSAGE); return; } /** * Validate Status */ if(cmbStatus.getSelectedIndex()<0){ JOptionPane.showMessageDialog(frame, "Status not set", "Status invalid", JOptionPane.WARNING_MESSAGE); return; } connection.setStatus((byte)cmbStatus.getSelectedIndex()); /** * Link should be set and contain connection */ if (connection.getLink() == null) { JOptionPane.showMessageDialog(frame, "Link should not be null.", "Invalid Link Selected", JOptionPane.WARNING_MESSAGE); return; } else if (!connection.getLink().getConnections().contains(connection)){ network.addConnectionToLink(connection, connection.getLink()); } if(tfName.getText()!=null) connection.setName(tfName.getText()); /** * Check each Port/Device */ for (int i = 0; i < ports.length; i++) { //Add Device to Link if (connection.getProtocol().getDevices().contains(ports[i])) { /** * If Link part of protocol & connection */ if (!ports[i].getOwner().getLinks().contains(connection.getLink())) network.addLinkToDevice(connection.getLink(), ports[i].getOwner()); if (!connection.getParticipants().contains(ports[i])) connection.addSmartDevice(ports[i]); if (ports[i].getConnection() != connection) ports[i].setConnection(connection); if (!ports[i].getOwner().getPorts().contains(ports[i])) ports[i].getOwner().addPort(ports[i]); } else { /** * Else remove from COnnection */ network.removeDeviceFromConnectionAndProtocol(ports[i], connection); if (ports[i].getOwner().getPorts().contains(ports[i])) ports[i].getOwner().removePort(ports[i]); } } /** * Connection should be in the model */ if(!network.getConnections().contains(connection)) network.addConnection(connection); /** * Link should be in the model */ if(!network.getLinks().contains(connection.getLink())){ network.addLink(connection.getLink()); } edit = true; /** * Close Window */ controller.notifyObservers(); content.setVisible(false); setVisible(false); if (frame != null) { frame.setVisible(false); frame.dispose(); } } }); /** * Small Label to describe the roles */ JLabel lblRole = new JLabel("Roles:"); lblRole.setHorizontalAlignment(SwingConstants.CENTER); lblRole.setBounds(345, 189, 50, 18); lblRole.setToolTipText("The roles each device plays in the connection, can be defined below. It defines how the device acts."); content.add(lblRole); /** * Height of the current Box which is being created */ int currentHeight = 220; cmbPortRoles = new JComboBox[ports.length]; for (int i = 0; i < ports.length; i++) { // Effectively final variables for the action listener int pos = i; Port currentPort = ports[i]; SmartDevice currentDevice = currentPort.getOwner(); JLabel lblDevice = new JLabel(currentDevice.getName() + ":" + currentPort.getPortNumber() + ":"); lblDevice.setHorizontalAlignment(SwingConstants.RIGHT); lblDevice.setBounds(10, currentHeight, 230, 18); content.add(lblDevice); cmbPortRoles[pos] = new JComboBox(); for (String role : connection.getProtocol().getRoles()) cmbPortRoles[pos].addItem(role); cmbPortRoles[pos].addItem("Disconnected"); cmbPortRoles[pos].setBounds(250, currentHeight, 240, 18); content.add(cmbPortRoles[pos]); if (edit) { int roleOfDevice = connection.getProtocol().getRoleOfDevice(currentPort); if (roleOfDevice != -1) cmbPortRoles[pos].setSelectedIndex(roleOfDevice); else cmbPortRoles[pos].setSelectedIndex(disconnectedIndex); } else { /** * Number of tries to add the Port */ int n = 5; for (int j = 0; j <= n; j++) { if (j == 0) {// Try to add Router first if (network.addDeviceToConnectionAndProtocol(currentPort, connection, 0)) { cmbPortRoles[pos].setSelectedIndex(0); break; } } else if (j == n) {// If it could not be added cmbPortRoles[pos].setSelectedIndex(disconnectedIndex); network.removeDeviceFromConnectionAndProtocol(currentPort, connection); break; } else { int randomRole = ThreadLocalRandom.current().nextInt(0, disconnectedIndex); if (network.addDeviceToConnectionAndProtocol(currentPort, connection, randomRole)) { cmbPortRoles[pos].setSelectedIndex(randomRole); break; } } } } cmbPortRoles[pos].addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (protocolChange) { return; } /** * Selected Index of the ComboBox, which represents the new * role (or disconnected) */ int selected = cmbPortRoles[pos].getSelectedIndex(); /** * True if role was successfully changed */ boolean successfullChange = network.changeRoleOfDevice(connection.getProtocol(), connection, ports[pos], selected); /** * Set to Disconnected, if role could not be changed */ if (!successfullChange) { cmbPortRoles[pos].setSelectedIndex(connection.getProtocol().getNumberOfRoles()); } } }); currentHeight += 20; } frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { if(!edit){ if(newLink){ network.deleteLink(connection.getLink()); } network.deleteConnection(connection); } }; }); refreshGUI(); } /** * Refreshes the GUI */ private void refreshGUI() { // Set Mutex mutex = true; /* * Update Variables */ disconnectedIndex = connection.getProtocol().getNumberOfRoles(); availableProtocols = controller.getImportController().getProtocols(); availableConnection = controller.getImportController().getConnections(); /** * Update Boxes, Textfield e.g. */ // Update Name Textfield tfName.setText(connection.getName()); // Update packet loss field tfPacketLossRate.setText("" + connection.getPacketLossProbability()); // Update Label tfLabel.setText("" + connection.getLabel()); // Update Status cmbStatus.setSelectedIndex(connection.getStatus()); // Update selected Connection cmbConnection.removeAllItems(); for (Class c : availableConnection) cmbConnection.addItem(c.getSimpleName()); // Set Index to selected Connection for (int i = 0; i < availableConnection.size(); i++) if (connection.getClass().equals(availableConnection.get(i))) { // Select the right protocol and save last index lastConnectionIndex = i; } cmbConnection.setSelectedIndex(lastConnectionIndex); // Update selected Link int linkIndex = 0; for (Link l : network.getLinks()) { cmbSelectedLink.addItem(l.getName()); if (l.equals(connection.getLink())) { lastLinkIndex = linkIndex; } linkIndex++; } cmbSelectedLink.setSelectedIndex(lastLinkIndex); // Update protocol cmbProtocolType.removeAllItems(); for (int i = 0; i < availableProtocols.size(); i++) try { cmbProtocolType.addItem(availableProtocols.get(i).newInstance().getName()); } catch (InstantiationException | IllegalAccessException e1) { System.out.println("Protocol " + i + " is invalid"); cmbProtocolType.addItem("unknown"); } // Set Index to selected Protocol lastProtocolIndex = -1; for (int i = 0; i < availableProtocols.size(); i++) { if (connection.getProtocol().getClass().equals(availableProtocols.get(i))) { // Select the right protocol and save last index lastProtocolIndex = i; } } cmbProtocolType.setSelectedIndex(lastProtocolIndex); // Release mutex mutex = false; } /** * Test the Panel */ private static void testGUI() { JFrame frame = new JFrame("Connection Creation Panel"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ConnectionCreationPanel panel; Link testNet = new SimpleLink("Test Network"); LinkedList devicesOfLink = new LinkedList(); LinkedList ports = new LinkedList(); for (int i = 0; i < 5; i++) { SmartDevice d = new SmartDevice("Device" + i); Port p = new Port(d, (short) i); d.addPort(p); devicesOfLink.add(d); ports.add(p); } Model m = new Model(); m.addConnectionNetwork(testNet); m.addConnectionNetwork(new SimpleLink("Wifi")); m.addConnectionNetwork(new SimpleLink("Ethernet")); panel = new ConnectionCreationPanel(ports, testNet, new Controller(m),frame); frame.setContentPane(panel); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { testGUI(); } }); } }