Java tutorial
/* * Copyright (C) 2013, DECOIT GmbH * * This file is part of VISA Topology-Editor. * * VISA Topology-Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your option) * any later version. * * VISA Topology-Editor is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * VISA Topology-Editor. If not, see <http://www.gnu.org/licenses/>. */ package de.decoit.visa.topology; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.TreeSet; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.log4j.Logger; import org.json.JSONException; import org.json.JSONObject; import de.decoit.visa.Dimension2D; import de.decoit.visa.Position2D; import de.decoit.visa.TEBackend; import de.decoit.visa.enums.IPVersion; import de.decoit.visa.enums.PortOrientation; import de.decoit.visa.gridlayout.GridLayout; import de.decoit.visa.interfaces.IJSON; import de.decoit.visa.interfaces.INetworkInterface; import de.decoit.visa.interfaces.IRDFObject; import de.decoit.visa.net.IPNetwork; import de.decoit.visa.rdf.VISA; import de.decoit.visa.topology.NCSwitch.GroupSwitch; import de.decoit.visa.topology.TopologyStorage.ComponentGroup.GroupInterface; /** * This class stores an object structure of the current topology. It uses the * classes defined in the de.decoit.visa.topology package. Objects are stored * using ComponentGroups which can be accessed by their group name. Components * that are not assigned to a specific group are located in the global group * "0.0.0.0". Besides the ComponentGroups this class contains separate lists of * VLANs, cables and used local names for RDF objects.<br> * <br> * The class is designed as a singleton to make sure only one topology storage * is available in the backend. This is necessary to prevent multiple identical * ID numbers or local names. * * @author Thomas Rix */ public class TopologyStorage { private static TopologyStorage instance = null; private static Logger log = Logger.getLogger(TopologyStorage.class.getName()); private static int nextGroupID = 0; private HashMap<String, String> groupNameIDMap; private HashMap<String, ComponentGroup> storage; private HashMap<String, IRDFObject> localNames; private HashMap<String, VLAN> vlans; private HashMap<String, NetworkCable> cables; private HashMap<String, IPNetwork> networks; private TreeSet<Integer> usedIDs; private String topologyID; private int lastVLANID; /** * Return the singleton instance of the topology storage. If no instance * exists a new will be created. * * @return The singleton instance of the topology storage */ public static TopologyStorage getInstance() { if (instance == null) { instance = new TopologyStorage(); } return instance; } /** * Generate a unique ID for a new topology. It is built by appending the * current UNIX timestamp to the string "custom_topology_". * * @return A new unique topology ID */ private static String genNewTopologyID() { Date currDate = new Date(); StringBuilder sb = new StringBuilder("custom_topology_"); sb.append(currDate.getTime()); return sb.toString(); } /** * Construct a new TopologyStorage */ private TopologyStorage() { groupNameIDMap = new HashMap<>(); storage = new HashMap<>(); ComponentGroup rootcg = new ComponentGroup("0.0.0.0"); storage.put(rootcg.getIdentifier(), rootcg); groupNameIDMap.put(rootcg.getName(), rootcg.getIdentifier()); localNames = new HashMap<>(); vlans = new HashMap<>(); cables = new HashMap<>(); networks = new HashMap<>(); usedIDs = new TreeSet<>(); topologyID = genNewTopologyID(); lastVLANID = 0; if (log.isTraceEnabled()) { log.trace("TopologyStorage created"); } } /** * Set a new topology ID for the current topology * * @param pID The new ID */ public void setTopologyID(String pID) { if (!pID.isEmpty()) { topologyID = pID; } else { throw new IllegalArgumentException("Empty string for topology ID provided"); } } /** * Set a new human readable name for the current topology * * @param pName The new topology name */ public void setTopologyName(String pName) { TEBackend.RDF_MANAGER.setRootNodeName(pName); } /** * Create a new host from data extracted from a RDF model. Parameters for * component dimensions and grid location are optional. If the dimensions * parameter is set to null, a default size of 5x5 cells will be used. * Setting the grid location parameter to null will force automatic * placement of the component using the graphviz software collection. * * The new component will automatically be added to the topology storage. * The local name of the RDF node will be used as identifier. * * @param pPortInfo A list containing the interface information extracted * from the RDF data stored in a <String, String> map. * @param pName Name of the new component, cannot be null or empty * @param pCompDimensions Size of the new component, can be set to null for * default size (5x5 cells) * @param pGridLoc Grid location of the component's dragbox * @param pLocName The local name of the RDF node representing this * component * @return The new host object */ public NCHost createHost(ArrayList<HashMap<String, String>> pPortInfo, String pName, Dimension2D pCompDimensions, Position2D pGridLoc, String pLocName) { if (!localNames.containsKey(pLocName)) { int newID = getNextComponentID(NCHost.TYPE); if (pCompDimensions == null) { pCompDimensions = new Dimension2D(5, 5); } NCHost rv = new NCHost(newID, pPortInfo, pName, pCompDimensions, pGridLoc, pLocName); addComponent(rv, false); return rv; } else { throw new IllegalArgumentException("Local name is already in use"); } } /** * Create a new switch from data received from the frontend. Parameters for * component dimensions and grid location are optional. If the dimensions * parameter is set to null, a default size of 5x5 cells will be used. * Setting the grid location parameter to null will force automatic * placement of the component using the graphviz software collection. * * The new component will automatically be added to the topology storage and * RDF model. A unique local name for the RDF resource is generated during * creation. * * @param pPortInfo A list containing the interface orientations for the new * interfaces, cannot be null or empty * @param pName Name of the new component, cannot be null or empty * @param pCompDimensions Size of the new component, can be set to null for * default size (5x5 cells) * @param pGridLoc Grid location of the component's dragbox * @return The new switch object */ public NCSwitch createSwitch(ArrayList<String> pPortInfo, String pName, Dimension2D pCompDimensions, Position2D pGridLoc) { int newID = getNextComponentID(NCSwitch.TYPE); if (pCompDimensions == null) { pCompDimensions = new Dimension2D(5, 5); } NCSwitch rv = new NCSwitch(newID, pPortInfo, pName, pCompDimensions, pGridLoc); addComponent(rv, true); return rv; } /** * Create a new switch from data extracted from a RDF model. Parameters for * component dimensions and grid location are optional. If the dimensions * parameter is set to null, a default size of 5x5 cells will be used. * Setting the grid location parameter to null will force automatic * placement of the component using the graphviz software collection. * * The new component will automatically be added to the topology storage. * The local name of the RDF node will be used as identifier. * * @param pPortInfo A list containing the interface information extracted * from the RDF data stored in a <String, String> map. * @param pName Name of the new component, cannot be null or empty * @param pCompDimensions Size of the new component, can be set to null for * default size (5x5 cells) * @param pGridLoc Grid location of the component's dragbox * @param pLocName The local name of the RDF node representing this * component * @return The new switch object */ public NCSwitch createSwitch(ArrayList<HashMap<String, String>> pPortInfo, String pName, Dimension2D pCompDimensions, Position2D pGridLoc, String pLocName) { if (!localNames.containsKey(pLocName)) { int newID = getNextComponentID(NCSwitch.TYPE); if (pCompDimensions == null) { pCompDimensions = new Dimension2D(5, 5); } NCSwitch rv = new NCSwitch(newID, pPortInfo, pName, pCompDimensions, pGridLoc, pLocName); addComponent(rv, false); return rv; } else { throw new IllegalArgumentException("Local name is already in use"); } } /** * Create a new VM from data received from the frontend. Parameters for * component dimensions and grid location are optional. If the dimensions * parameter is set to null, a default size of 5x5 cells will be used. * Setting the grid location parameter to null will force automatic * placement of the component using the graphviz software collection. * * The new component will automatically be added to the topology storage and * RDF model. A unique local name for the RDF resource is generated during * creation. * * @param pPortInfo A list containing the interface orientations for the new * interfaces, cannot be null or empty * @param pName Name of the new component, cannot be null or empty * @param pCompDimensions Size of the new component, can be set to null for * default size (5x5 cells) * @param pGridLoc Grid location of the component's dragbox * @return The new VM object */ public NCVM createVM(ArrayList<String> pPortInfo, String pName, Dimension2D pCompDimensions, Position2D pGridLoc) { int newID = getNextComponentID(NCVM.TYPE); if (pCompDimensions == null) { pCompDimensions = new Dimension2D(5, 5); } NCVM rv = new NCVM(newID, pPortInfo, pName, pCompDimensions, pGridLoc); addComponent(rv, true); return rv; } /** * Create a new VM from data extracted from a RDF model. Parameters for * component dimensions and grid location are optional. If the dimensions * parameter is set to null, a default size of 5x5 cells will be used. * Setting the grid location parameter to null will force automatic * placement of the component using the graphviz software collection. * * The new component will automatically be added to the topology storage. * The local name of the RDF node will be used as identifier. * * @param pPortInfo A list containing the interface information extracted * from the RDF data stored in a <String, String> map. * @param pName Name of the new component, cannot be null or empty * @param pCompDimensions Size of the new component, can be set to null for * default size (5x5 cells) * @param pGridLoc Grid location of the component's dragbox * @param pLocName The local name of the RDF node representing this * component * @return The new VM object */ public NCVM createVM(ArrayList<HashMap<String, String>> pPortInfo, String pName, Dimension2D pCompDimensions, Position2D pGridLoc, String pLocName) { if (!localNames.containsKey(pLocName)) { int newID = getNextComponentID(NCVM.TYPE); if (pCompDimensions == null) { pCompDimensions = new Dimension2D(5, 5); } NCVM rv = new NCVM(newID, pPortInfo, pName, pCompDimensions, pGridLoc, pLocName); addComponent(rv, false); return rv; } else { throw new IllegalArgumentException("Local name is already in use"); } } /** * Get an existing component object from the topology storage identified by * the provided local name. If no object with that local name was found the * method will return null. If the identified object is no instance of * NetworkComponent an IllegalArgumentException will be thrown. * * @param pLocName Local name identifying the component object, must * identify a NetworkComponent object * @return The component object identified by the local name, null if no * object was found */ public NetworkComponent getComponent(String pLocName) { IRDFObject rdfo = localNames.get(pLocName); if (rdfo instanceof NetworkComponent) { return (NetworkComponent) rdfo; } else if (rdfo == null) { return null; } else { throw new IllegalArgumentException( "Object identified by the provided local name was no NetworkComponent instance"); } } /** * Remove an existing component, identified by the provided local name, from * the topology storage and RDF model. * * @param pLocName Local name to identify the component, must identify an * existing NetworkComponent instance */ public void removeComponent(String pLocName) { IRDFObject rdfo = localNames.get(pLocName); if (rdfo instanceof NetworkComponent) { NetworkComponent nc = (NetworkComponent) rdfo; if (nc instanceof NCSwitch) { ((NCSwitch) nc).removeFromTopology(); } else if (nc instanceof NCHost) { ((NCHost) nc).removeFromTopology(); } else if (nc instanceof NCVM) { ((NCVM) nc).removeFromTopology(); } String group = nc.getConfig().getComponentGroup(); getComponentGroupByName(group).removeComponent(nc); localNames.remove(pLocName); } else if (rdfo == null) { throw new IllegalArgumentException("No object with this local name found"); } else { throw new IllegalArgumentException( "Object identified by the provided local name was no NetworkComponent instance"); } } /** * Create a new interface and assign it to the specified component. This * method is called for interfaces created when new components are added by * the frontend or new interfaces are added to existing components. * * @param pOrientation Orientation of the new interface * @param pComp Component which the interface will be assigned to * @return The new created interface */ NetworkComponent.Interface createInterface(PortOrientation pOrientation, NetworkComponent pComp) { int newID = getNextComponentID(NetworkComponent.Interface.TYPE); NetworkComponent.Interface rv = new NetworkComponent.Interface(newID, pOrientation, pComp); TEBackend.RDF_MANAGER.addObject(rv); localNames.put(rv.getIdentifier(), rv); return rv; } /** * Create a new interface and assign it to the specified component. This * method is called for interfaces created from RDF information. * * @param pOrientation Orientation of the new interface * @param pComp Component which the interface will be assigned to * @param pLocName RDF local name of the interface * @return The new created interface */ NetworkComponent.Interface createInterface(PortOrientation pOrientation, NetworkComponent pComp, String pLocName) { if (!localNames.containsKey(pLocName)) { NetworkComponent.Interface rv = new NetworkComponent.Interface(pOrientation, pComp, pLocName); localNames.put(rv.getIdentifier(), rv); return rv; } else { throw new IllegalArgumentException("Local name is already in use"); } } /** * Get the interface with the specified RDF local name from the storage. * * @param pLocName RDF local name of the interface * @return The requested interface, null if no interface with that local * name was found */ public NetworkComponent.Interface getInterface(String pLocName) { IRDFObject rdfo = localNames.get(pLocName); if (rdfo instanceof NetworkComponent.Interface) { return (NetworkComponent.Interface) rdfo; } else if (rdfo == null) { return null; } else { throw new IllegalArgumentException( "Object identified by the provided local name was no NetworkComponent.Interface instance"); } } /** * Remove the interface with the specified RDF local name from the topology. * It will be removed from the component it is assigned to, the storage * structures and the RDF model. * * @param pLocName RDF local name of the interface */ void removeInterface(String pLocName) { IRDFObject rdfo = localNames.get(pLocName); if (rdfo instanceof NetworkComponent.Interface) { NetworkComponent.Interface i = (NetworkComponent.Interface) rdfo; localNames.remove(pLocName); i.removeFromTopology(); } else if (rdfo == null) { throw new IllegalArgumentException("No object with this local name found"); } else { throw new IllegalArgumentException( "Object identified by the provided local name was no NetworkComponent.Interface instance"); } } /** * Create a new connection between two interfaces using a * {@link NetworkCable} object. An optional {@link GroupInterface * GroupInterface}, which will be used as a gateway into a * {@link ComponentGroup ComponentGroup}, can be specified. * * @param pLeft Interface at the left end of the cable * @param pRight Interface at the right end of the cable * @param pGroupGW Optional {@link GroupInterface GroupInterface} used to * connect interfaces through the border of a group. Can be set * to null if not required * @return The new created cable object */ public NetworkCable createCable(NetworkComponent.Interface pLeft, NetworkComponent.Interface pRight, GroupInterface pGroupGW) { int newID = getNextCableID(); NetworkCable rv = new NetworkCable(newID, pLeft, pRight, pGroupGW); cables.put(rv.getIdentifier(), rv); // Add the cable to its component group getComponentGroupByName(rv.getGroupName()).addCable(rv); return rv; } /** * Remove the cable with the specified identifier string from the topology. * It will be disconnected from the interfaces and removed from the storage. * Also the 'connected' properties connecting the two interfaces in the RDF * model are removed. * * @param pIdent Identifier string of the cable */ public void removeCable(String pIdent) { NetworkCable nc = cables.get(pIdent); if (nc != null) { nc.removeFromTopology(); cables.remove(pIdent); } else { throw new IllegalArgumentException("No object with this local name found"); } } /** * Create a new VLAN object using the name and color received from the * frontend. * * @param pName Human readable name of the VLAN * @param pColor Color string of the VLAN, must be specified as a sharp * followed by a 6-digit hex code (#RRGGBB) * @return The new created VLAN */ public VLAN createVLAN(String pName, String pColor) { int newID = getNextVLANID(); VLAN rv = new VLAN(newID, null); rv.setName(pName); rv.setColor(pColor); vlans.put(rv.getRDFLocalName(), rv); return rv; } /** * Create a new VLAN object using information extracted from the RDF model. * A color will be selected using the * {@link de.decoit.visa.topology.VLAN.ColorChooser ColorChooser} class. The * ID number may be overridden by an automatically generated ID * to prevent collisions with existing VLANs. Such changes will update the * RDF model to represent the new situation. * * @param pLocName RDF local name of the VLAN * @param pID ID number of the VLAN * @return The new created VLAN */ public VLAN createVLAN(String pLocName, int pID) { int id; boolean updateRDF; if (pID <= lastVLANID) { id = getNextVLANID(); updateRDF = true; } else { lastVLANID = pID; id = pID; updateRDF = false; } VLAN rv = new VLAN(id, pLocName); vlans.put(pLocName, rv); if (updateRDF) { TEBackend.RDF_MANAGER.updateProperty(rv, VISA.ID); } return rv; } /** * Get a VLAN specified by the given RDF local name. If no object with that * local name was found, a new VLAN object will be created using the local * name and an automatically generated ID number. A color will be selected * using the {@link de.decoit.visa.topology.VLAN.ColorChooser ColorChooser} * class. * * @param pLocName RDF local name of the VLAN * @return The requested VLAN */ public VLAN getVLAN(String pLocName) { if (vlans.containsKey(pLocName)) { return vlans.get(pLocName); } else { int newID = getNextVLANID(); VLAN rv = new VLAN(newID, pLocName); vlans.put(pLocName, rv); return rv; } } /** * Remove the VLAN with the specified RDF local name from the topology. It * will be removed from the storage and the RDF model. This action can only * be executed if the VLAN is <strong>not</strong> assigned to any * interfaces. * * @param pLocName RDF local name of the VLAN */ public void removeVLAN(String pLocName) { VLAN vlan = vlans.get(pLocName); if (vlan != null) { vlan.removeFromTopology(); vlans.remove(pLocName); } else { throw new IllegalArgumentException("No object with this local name found"); } } /** * Create a new network object. If a network with this address and subnet * mask length already exists it will be returned instead. If an existing * network with the provided address but different subent mask length is * found, an exception will be thrown- * * @param pNetworkAddress String notation of the network IP address, must be * valid for the provided IP version * @param pSubnetMaskLength Bit length of the subnet mask used for this * network * @param pVersion Version of the Internet Protocol which will be used for * this network * @return The newly created or existing network */ public IPNetwork createNetwork(String pNetworkAddress, int pSubnetMaskLength, IPVersion pVersion) { IPNetwork newNet = networks.get(pNetworkAddress); if (newNet == null) { newNet = new IPNetwork(pNetworkAddress, pSubnetMaskLength, pVersion); networks.put(newNet.getNetworkAddress().getAddressString(), newNet); } else if (newNet.getSubnetMaskLength() != pSubnetMaskLength) { throw new IllegalStateException( "A network with this address but different subnet mask length already exists"); } return newNet; } /** * Return the network with the provided network address. If no such network * exists, the method will return null. * * @param pNetworkAddress String notation of the network IP address * @return The stored network, null if no network was found */ public IPNetwork getNetwork(String pNetworkAddress) { return networks.get(pNetworkAddress); } /** * Get the {@link ComponentGroup ComponentGroup} with the specified * identifier. If no such group exists the method will return null. * * @param pID Identifier of the group * @return The requested group object, null if no matching group was found */ public ComponentGroup getComponentGroupByID(String pID) { return storage.get(pID); } /** * Get the {@link ComponentGroup ComponentGroup} with the specified group * name. If no group with that name exists, a new one will be created and * added to the storage. * * @param pName Name of the group * @return The requested group object */ public ComponentGroup getComponentGroupByName(String pName) { if (!pName.isEmpty()) { String cgID = groupNameIDMap.get(pName); ComponentGroup rv = storage.get(cgID); if (rv == null) { rv = new ComponentGroup(pName); storage.put(rv.getIdentifier(), rv); groupNameIDMap.put(rv.getName(), rv.getIdentifier()); } return rv; } else { throw new IllegalArgumentException("Group name cannot be empty"); } } /** * Query if the storage contains a {@link ComponentGroup ComponentGroup} * with the specified group ID. This method wraps around * HashMap.containsKey(). * * @param pID Identifier of the group * @return true if a group with that name was found, false otherwise */ public boolean hasComponentGroup(String pID) { return storage.containsKey(pID); } /** * Get the next usable group ID number * * @return ID number for the next group */ public int getNextComponentGroupID() { int rv = nextGroupID; nextGroupID++; return rv; } /** * Get the object with the specified RDF local name. The returned object can * be of any type that is stored in the RDF model. This includes, but is not * limited to, {@link NetworkComponent}, * {@link de.decoit.visa.topology.NetworkComponent.Interface Interface}, * {@link NetworkCable} and {@link VLAN}. * * @param pLocName RDF local name of the requested object * @return The requested object, null if no object was found */ public IRDFObject getRDFObject(String pLocName) { return localNames.get(pLocName); } /** * Remove all objects stored in the topology storage. The storage, vlans, * cables local names maps and set of used IDs will be cleared. */ public void clear() { groupNameIDMap.clear(); storage.clear(); vlans.clear(); cables.clear(); usedIDs.clear(); localNames.clear(); networks.clear(); nextGroupID = 0; lastVLANID = 0; topologyID = genNewTopologyID(); ComponentGroup rootcg = new ComponentGroup("0.0.0.0"); storage.put(rootcg.getIdentifier(), rootcg); groupNameIDMap.put(rootcg.getName(), rootcg.getIdentifier()); } /** * Generate a JSON encoded JavaScript object with information about all * objects that are currently present in the topology. * * @return JSONObject containing a structure with information about all * stored objects * @throws JSONException if adding values to the JSONObject fails */ public JSONObject genTopologyJSON() throws JSONException { JSONObject json = new JSONObject(); json.put("identifier", topologyID); json.put("name", StringEscapeUtils.escapeHtml4(TEBackend.RDF_MANAGER.getRootNodeName())); JSONObject groupJSON = new JSONObject(); JSONObject ifJSON = new JSONObject(); // Iterate through all stored types for (Map.Entry<String, ComponentGroup> groupEntry : storage.entrySet()) { groupJSON.put(groupEntry.getValue().identifier, groupEntry.getValue().toJSON()); // Iterate through all stored objects and add their interfaces to // the interface list for (NetworkComponent nc : groupEntry.getValue().componentList) { for (Map.Entry<String, NetworkComponent.Interface> ifEntry : nc.getConfig().getPorts().entrySet()) { ifJSON.put(ifEntry.getKey(), ifEntry.getValue().toJSON()); } } } json.put("groups", groupJSON); json.put("interfaces", ifJSON); JSONObject cableJSON = new JSONObject(); for (Map.Entry<String, NetworkCable> cableEntry : cables.entrySet()) { cableJSON.put(cableEntry.getKey(), cableEntry.getValue().toJSON()); } json.put("cables", cableJSON); JSONObject vlanJSON = new JSONObject(); for (Map.Entry<String, VLAN> vlanEntry : vlans.entrySet()) { vlanJSON.put(vlanEntry.getKey(), vlanEntry.getValue().toJSON()); } json.put("vlans", vlanJSON); JSONObject networkJSON = new JSONObject(); for (Map.Entry<String, IPNetwork> networkEntry : networks.entrySet()) { networkJSON.put(networkEntry.getKey(), networkEntry.getValue().toJSON()); } json.put("networks", networkJSON); json.put("importHistory", TEBackend.RDF_MANAGER.historyToJSON()); return json; } /** * Layout the topology by using the de.decoit.visa.gridlayout.GridLayout * class. By default, the 'neato' executable will be used for layouting. If * any nodes with fixed positions are detected, the 'fdp' executable will be * used to get better results. All existing switches and VMs will be used as * nodes, all cables as edges. Already positioned nodes will not be moved. */ public void layoutTopology() { try { // Layout the subgrids of all component groups HashSet<ComponentGroup> processedGroups = new HashSet<>(); for (Map.Entry<String, ComponentGroup> groupEntry : storage.entrySet()) { // Process all groups except the global group 0.0.0.0 if (!groupEntry.getValue().isGlobalGroup()) { // neato is the default layouter String command = "neato"; // Create a new layouter GridLayout layout = new GridLayout(groupEntry.getValue().subGridDimensions); // Add all components of this group to the layouter for (NetworkComponent nc : groupEntry.getValue().componentList) { // Use fdp layouter if there are nodes with fixed // positions if (!layout.addComponent(nc)) { command = "fdp"; } } for (NetworkCable nc : groupEntry.getValue().cables) { layout.addCable(nc); } for (Map.Entry<String, GroupSwitch> gsEntry : groupEntry.getValue().groupSwitches.entrySet()) { // Use fdp layouter if there are nodes with fixed // positions if (!layout.addGroupSwitch(gsEntry.getValue())) { command = "fdp"; } } // Run the layouter layout.run(command); // Add the current group to the processed groups set processedGroups.add(groupEntry.getValue()); } } // Layout the base layer group 0.0.0.0 // neato is the default layouter String command = "neato"; // Create a new layouter GridLayout layout = new GridLayout(TEBackend.getGridDimensions()); // Add all components to the layouter for (NetworkComponent nc : getComponentGroupByName("0.0.0.0").componentList) { // Use fdp layouter if there are nodes with fixed positions if (!layout.addComponent(nc)) { command = "fdp"; } } for (NetworkCable nc : getComponentGroupByName("0.0.0.0").cables) { if (nc.getLeft().getComponentGroup().equals(nc.getRight().getComponentGroup())) { layout.addCable(nc); } } // Add all group objects to the layouter for (ComponentGroup cg : processedGroups) { // Use fdp layouter if there are nodes with fixed positions if (!layout.addComponentGroup(cg)) { command = "fdp"; } } // Run the layouter layout.run(command); } catch (IOException ex) { StringBuilder sb = new StringBuilder("Caught: ["); sb.append(ex.getClass().getSimpleName()); sb.append("] "); sb.append(ex.getMessage()); log.error(sb.toString()); if (log.isDebugEnabled()) { for (StackTraceElement ste : ex.getStackTrace()) { log.debug(ste.toString()); } } } } /** * Update the orientation on all connected interfaces of the specified * components. The method will try to make the interfaces connected by a * cable facing each other to prevent unneccessary cable crossings. The * components are specified by their RDF local names. Local names that do * not identify a component will be ignored. * * @param pLocalNames Set of local names of the components which will be * processed */ public void updateInterfaceOrientations(HashSet<String> pLocalNames) { for (String locName : pLocalNames) { IRDFObject rdfo = localNames.get(locName); if (rdfo instanceof NetworkComponent && !(rdfo instanceof NCSwitch)) { NetworkComponent nc = (NetworkComponent) rdfo; HashMap<String, NetworkComponent.Interface> ifmap = nc.getConfig().getPorts(); int freeIfSpotsLeft = nc.getConfig().getComponentDimensions().getY(); int freeIfSpotsRight = nc.getConfig().getComponentDimensions().getY(); int freeIfSpotsTop = nc.getConfig().getComponentDimensions().getX(); int freeIfSpotsBottom = nc.getConfig().getComponentDimensions().getX(); for (Map.Entry<String, NetworkComponent.Interface> entry : ifmap.entrySet()) { if (entry.getValue().isConnected()) { NetworkComponent.Interface thisIf = entry.getValue(); NetworkComponent.Interface otherIf = null; Position2D thisPos; Position2D otherPos; if (thisIf.getCable().getGroupGateway() != null) { ComponentGroup cg = thisIf.getCable().getGroupGateway().getOuterType(); thisPos = thisIf.getComponent().getConfig().getGridLocation(); otherPos = cg.getGridLocation(); } else { switch (thisIf.getCableEnd()) { case LEFT: otherIf = thisIf.getCable().getRight(); break; case RIGHT: otherIf = thisIf.getCable().getLeft(); break; } thisPos = thisIf.getComponent().getConfig().getGridLocation(); otherPos = otherIf.getComponent().getConfig().getGridLocation(); } if (Math.abs(thisPos.getX() - otherPos.getX()) >= Math .abs(thisPos.getY() - otherPos.getY())) { if (thisPos.getX() - otherPos.getX() < 0) { // If there is space on the right side, place it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsRight > 0) { thisIf.setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } else if (freeIfSpotsLeft > 0) { thisIf.setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsTop > 0) { thisIf.setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { thisIf.setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } } else { // If there is space on the right side, place it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsLeft > 0) { thisIf.setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { thisIf.setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } else if (freeIfSpotsTop > 0) { thisIf.setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { thisIf.setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } } } else { if (thisPos.getY() - otherPos.getY() < 0) { // If there is space on the top side, place it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsBottom > 0) { thisIf.setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } else if (freeIfSpotsTop > 0) { thisIf.setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsLeft > 0) { thisIf.setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { thisIf.setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } } else { // If there is space on the top side, place it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsTop > 0) { thisIf.setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { thisIf.setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } else if (freeIfSpotsLeft > 0) { thisIf.setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { thisIf.setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } } } } else { switch (entry.getValue().getOrientation()) { case LEFT: // If there is space on the left side, leave it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsLeft > 0) { freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { entry.getValue().setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } else if (freeIfSpotsTop > 0) { entry.getValue().setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { entry.getValue().setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } break; case RIGHT: // If there is space on the right side, leave it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsRight > 0) { freeIfSpotsRight--; } else if (freeIfSpotsLeft > 0) { entry.getValue().setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsTop > 0) { entry.getValue().setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { entry.getValue().setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } break; case TOP: // If there is space on the top side, leave it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsTop > 0) { freeIfSpotsTop--; } else if (freeIfSpotsBottom > 0) { entry.getValue().setOrientation(PortOrientation.BOTTOM); freeIfSpotsBottom--; } else if (freeIfSpotsLeft > 0) { entry.getValue().setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { entry.getValue().setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } break; case BOTTOM: // If there is space on the bottom side, leave // it // there. Otherwise try positioning it on the // other sides if (freeIfSpotsBottom > 0) { freeIfSpotsBottom--; } else if (freeIfSpotsTop > 0) { entry.getValue().setOrientation(PortOrientation.TOP); freeIfSpotsTop--; } else if (freeIfSpotsLeft > 0) { entry.getValue().setOrientation(PortOrientation.LEFT); freeIfSpotsLeft--; } else if (freeIfSpotsRight > 0) { entry.getValue().setOrientation(PortOrientation.RIGHT); freeIfSpotsRight--; } } } } } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((localNames == null) ? 0 : localNames.hashCode()); result = prime * result + ((storage == null) ? 0 : storage.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TopologyStorage other = (TopologyStorage) obj; if (localNames == null) { if (other.localNames != null) return false; } else if (!localNames.equals(other.localNames)) return false; if (storage == null) { if (other.storage != null) return false; } else if (!storage.equals(other.storage)) return false; return true; } /** * Find the first unused component ID. The method is counting up from 1 * until an unused ID is found. So if the used IDs are 1, 2, 4 it will * return the 3, on the next call it will return 5. If an ID is not marked * as used but a local name following the scheme pType_newID already exists, * the ID will be marked as used and the search will continue. * * @param pType Value of the TYPE parameter of a class, cannot be null * @return The next free component ID */ private int getNextComponentID(String pType) { int newID = 1; while (usedIDs.contains(newID)) { newID++; StringBuilder sb = new StringBuilder(pType); sb.append("_"); sb.append(newID); if (localNames.containsKey(sb.toString())) { usedIDs.add(newID); } } usedIDs.add(newID); return newID; } /** * Find the first unused cable ID. The method is counting up from 1 * until an unused ID is found. So if the used IDs are 1, 2, 4 it will * return the 3, on the next call it will return 5. If an ID is not marked * as used but a local name following the scheme ncable_newID already * exists, * the ID will be marked as used and the search will continue. * * @return The next free cable ID */ private int getNextCableID() { int newID = 1; StringBuilder sb = new StringBuilder(NetworkCable.TYPE); sb.append("_"); sb.append(newID); while (cables.containsKey(sb.toString())) { newID++; sb = new StringBuilder(NetworkCable.TYPE); sb.append("_"); sb.append(newID); } return newID; } /** * Return the next unused VLAN ID. This is achieved by incrementing the last * known ID by 1. The last known ID is updated when VLANs are created using * RDF information, so there may be gabs in the ID list. * * @return The next VLAN ID */ private int getNextVLANID() { int newID = lastVLANID + 1; lastVLANID = newID; return newID; } /** * Add the specified component to the storage structure and the RDF model if * requested. * * @param pComponent Component which will be added * @param pUpdateRDF true: Update the RDF model with the information stored * in the component object. false: Do not touch the RDF model. */ private void addComponent(NetworkComponent pComponent, final boolean pUpdateRDF) { if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Adding component to storage: "); sb.append(pComponent.getIdentifier()); sb.append(", local name: "); sb.append(pComponent.getRDFLocalName()); log.debug(sb.toString()); } if (!localNames.containsKey(pComponent.getRDFLocalName())) { if (pUpdateRDF) { if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Adding object to RDF model: "); sb.append(pComponent.getRDFLocalName()); log.debug(sb.toString()); } // Add the object to the RDF model TEBackend.RDF_MANAGER.addObject(pComponent); } localNames.put(pComponent.getRDFLocalName(), pComponent); ComponentGroup group = getComponentGroupByName(pComponent.getConfig().getComponentGroup()); group.addComponent(pComponent); } else { throw new IllegalStateException("The RDF local name of this object is already in use"); } } /** * This class represents a group of components that share a specific * property. They can belong to the same subnet or the same VSA. The group * name identifies the group and should be unique, using an already taken * group name will overwrite the existing group. * * @author Thomas Rix */ public class ComponentGroup implements IJSON { private HashSet<NetworkComponent> componentList; private String groupName; private Position2D gridLocation; private Dimension2D groupDimensions; private Dimension2D dragboxDimensions; private Position2D componentInterfaceOffset; private Dimension2D subGridDimensions; private HashSet<GroupInterface> groupInterfaces; private HashMap<String, GroupSwitch> groupSwitches; private HashSet<NetworkCable> cables; private int nextGroupInterfaceID; private String identifier; /** * Construct a new group using the specified name as identifier. The * group element on the editor grid will have the dimensions 10x10 grid * cells and the subgrid will have the dimensions 80x40 grid cells. * * @param pName Group name, cannot be empty or null */ private ComponentGroup(String pName) { nextGroupInterfaceID = 0; if (!pName.isEmpty()) { groupName = pName; } else { throw new IllegalArgumentException("Empty group name provided"); } StringBuilder sbID = new StringBuilder("cgroup"); sbID.append(nextGroupID); nextGroupID++; identifier = sbID.toString(); gridLocation = null; groupDimensions = new Dimension2D(10, 10); subGridDimensions = new Dimension2D(175, 75); componentList = new HashSet<>(); groupInterfaces = new HashSet<>(); groupSwitches = new HashMap<>(); cables = new HashSet<>(); calcDragboxDimensions(); } /** * Return the identifier of this component group * * @return Identifier string */ public String getIdentifier() { return identifier; } /** * Return the name of this group * * @return The name of this group */ public String getName() { return groupName; } /** * Add a component to this group. * * @param pNC Component which will be added, cannot be null * @return true if the component was added successfully, false otherwise */ boolean addComponent(NetworkComponent pNC) { if (pNC != null) { boolean rv = componentList.add(pNC); return rv; } else { throw new NullPointerException("NULL pointer for network component"); } } /** * Remove a component from this group. * * @param pNC Component which will be removed, cannot be null * @return true if the component was removed successfully, false * otherwise */ boolean removeComponent(NetworkComponent pNC) { if (pNC != null) { boolean rv = componentList.remove(pNC); if (componentList.isEmpty()) { for (Map.Entry<String, GroupSwitch> gsEntry : groupSwitches.entrySet()) { gsEntry.getValue().removeFromGroup(); } for (GroupInterface gIf : groupInterfaces) { gIf.removeFromGroup(); } storage.remove(groupName); } return rv; } else { throw new NullPointerException("NULL pointer for network component"); } } /** * Set the grid location of this group to the specified coordinates * * @param pX Horizontal coordinate * @param pY Vertical coordinate */ public void setGridLocation(int pX, int pY) { gridLocation = new Position2D(pX, pY, TEBackend.getGridDimensions()); if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("ComponentGroup relocated to x="); sb.append(gridLocation.getX()); sb.append(", y="); sb.append(gridLocation.getY()); log.debug(sb.toString()); } } /** * Return the grid location of this group * * @return The grid location of this group */ public Position2D getGridLocation() { return gridLocation; } /** * Set the size of the subgrid to the provided dimensions * * @param pX Horizontal size in grid cells * @param pY Vertical size in grid cells */ public void setSubgridDimensions(int pX, int pY) { if (subGridDimensions == null) { subGridDimensions = new Dimension2D(pX, pY); } else { subGridDimensions.set(pX, pY); } } /** * Return the dimensions of the subgrid * * @return The dimensions of the subgrid */ public Dimension2D getSubgridDimensions() { return subGridDimensions; } /** * Return the dimensions of the dragbox containing this group * * @return The dimensions of the dragbox */ public Dimension2D getDragboxDimensions() { return dragboxDimensions; } /** * Add a virtual group switch to this group. * * @param pGS Reference to the group switch */ void addGroupSwitch(GroupSwitch pGS) { if (pGS != null) { groupSwitches.put(pGS.getSwitchRDFLocalName(), pGS); } else { throw new NullPointerException("NULL pointer for group switch"); } } /** * Remove a virtual group switch from this group. * * @param pGS Reference to the group switch */ void removeGroupSwitch(GroupSwitch pGS) { if (pGS != null) { groupInterfaces.remove(pGS.getVirtualInterface().getConnectedTo()); groupSwitches.remove(pGS.getSwitchRDFLocalName()); } else { throw new NullPointerException("NULL pointer for group switch"); } } /** * Get the group switch with the specified RDF local name from this * group. * * @param pSwitchLocName RDF local name of the group switch * @return The requested object, null if no object was found */ public GroupSwitch getGroupSwitch(String pSwitchLocName) { return groupSwitches.get(pSwitchLocName); } /** * Get a set containing all {@link GroupInterface GroupInterfaces} of * this group. * * @return A set containing all {@link GroupInterface GroupInterfaces} * of this group */ public HashSet<GroupInterface> getGroupInterfaces() { return groupInterfaces; } /** * Create a new connection between an interface inside the group and an * interface outside the group. This will add a new GroupInterface to * this group which will act as gateway through the group border to * connect the inner and outer interfaces. * * @param pInner Interface of the component inside the group * @param pOuter Interface of the component outside the group * @return true if the group interface was added successfully, false * otherwise */ public GroupInterface createOuterConnection(INetworkInterface pInner, INetworkInterface pOuter) { GroupInterface newGI = new GroupInterface(pInner, pOuter); groupInterfaces.add(newGI); calcDragboxDimensions(); return newGI; } /** * Add a cable to this group * * @param pNC The cable to be added * @return true if adding the cable was successful, false otherwise */ boolean addCable(NetworkCable pNC) { if (pNC != null) { return cables.add(pNC); } else { throw new NullPointerException("NULL pointer for cable provided"); } } /** * Remove a cable from this group * * @param pNC The cable to be removed * @return true if removing the cable was successful, false otherwise */ boolean removeCable(NetworkCable pNC) { if (pNC != null) { return cables.remove(pNC); } else { throw new NullPointerException("NULL pointer for cable provided"); } } /** * Check if this is the global group 0.0.0.0 * * @return true if the group name equals "0.0.0.0", false otherwise */ public boolean isGlobalGroup() { return groupName.equals("0.0.0.0"); } @Override public JSONObject toJSON() throws JSONException { JSONObject rv = new JSONObject(); rv.put("name", StringEscapeUtils.escapeHtml4(groupName)); rv.put("identifier", identifier); rv.put("isGroup", true); JSONObject compsJSON = new JSONObject(); for (NetworkComponent nc : componentList) { JSONObject ncJSON = null; if (nc instanceof NCSwitch) { ncJSON = ((NCSwitch) nc).toJSON(); } else if (nc instanceof NCHost) { ncJSON = ((NCHost) nc).toJSON(); } else if (nc instanceof NCVM) { ncJSON = ((NCVM) nc).toJSON(); } if (ncJSON != null) { compsJSON.put(nc.getIdentifier(), ncJSON); } } rv.put("components", compsJSON); JSONObject cableJSON = new JSONObject(); for (NetworkCable nc : cables) { cableJSON.put(nc.getIdentifier(), nc.toJSON()); } rv.put("cables", cableJSON); JSONObject gsJSON = new JSONObject(); for (Map.Entry<String, GroupSwitch> gsEntry : groupSwitches.entrySet()) { gsJSON.put(gsEntry.getValue().getRDFLocalName(), gsEntry.getValue().toJSON()); } rv.put("groupSwitches", gsJSON); JSONObject confJSON = new JSONObject(); if (gridLocation != null) { confJSON.put("gloc", gridLocation.toJSON()); } confJSON.put("compOffset", componentInterfaceOffset.toJSON()); confJSON.put("compDim", groupDimensions.toJSON()); confJSON.put("dragDim", dragboxDimensions.toJSON()); confJSON.put("gridDim", subGridDimensions.toJSON()); JSONObject ifJSON = new JSONObject(); for (GroupInterface gIf : groupInterfaces) { ifJSON.put(gIf.localName, gIf.toJSON()); } confJSON.put("interfaces", ifJSON); rv.put("config", confJSON); return rv; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((groupName == null) ? 0 : groupName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof ComponentGroup) { ComponentGroup ng = (ComponentGroup) obj; return ng.groupName.equals(groupName); } else { return false; } } /** * Calculate the dimensions of the dragbox containing this group object */ private void calcDragboxDimensions() { if (groupDimensions != null) { int x = groupDimensions.getX() + TEBackend.getComponentMargin() * 2; int y = groupDimensions.getY() + TEBackend.getComponentMargin() * 2 + groupInterfaces.size(); int ifOffsetX = TEBackend.getComponentMargin(); int ifOffsetY = TEBackend.getComponentMargin(); dragboxDimensions = new Dimension2D(x, y); componentInterfaceOffset = new Position2D(ifOffsetX, ifOffsetY); if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Dragbox resized to x="); sb.append(dragboxDimensions.getX()); sb.append(", y="); sb.append(dragboxDimensions.getY()); log.debug(sb.toString()); } } else { throw new IllegalStateException("No component dimensions were set"); } } /** * An interface connecting two interfaces through the border of a * ComponentGroup. * * @author Thomas Rix */ public class GroupInterface implements IRDFObject, IJSON { private INetworkInterface innerConnection; private INetworkInterface outerConnection; private PortOrientation orientation; private String localName; /** * Construct a new group interface connecting the two specified * interfaces. * * @param pInner Interface that is located inside the group, cannot * be null and must be part of a component inside the * group * @param pOuter Interface that is located outside the group, cannot * be null and must be part of a component that belongs * to the global group 0.0.0.0 */ private GroupInterface(INetworkInterface pInner, INetworkInterface pOuter) { if (pInner != null) { if (pInner.getComponentGroup().equals(getOuterType().groupName)) { innerConnection = pInner; } else { StringBuilder sb = new StringBuilder("Inner interface group mismatch: "); sb.append(pInner.getComponentGroup()); sb.append(" -> "); sb.append(getOuterType().groupName); log.error(sb.toString()); throw new IllegalArgumentException( "Specified inner interface is not located in this group"); } } else { throw new NullPointerException("NULL pointer for inner virtual interface"); } if (pOuter != null) { if (pOuter.getComponentGroup().equals("0.0.0.0")) { outerConnection = pOuter; } else { StringBuilder sb = new StringBuilder("Outer interface group mismatch: "); sb.append(pOuter.getComponentGroup()); sb.append(" -> "); sb.append("0.0.0.0"); log.error(sb.toString()); throw new IllegalArgumentException( "Specified outer interface is not located in the global group 0.0.0.0"); } } else { throw new NullPointerException("NULL pointer for outer virtual interface"); } StringBuilder sbLocName = new StringBuilder("GroupInterface_"); sbLocName.append(getOuterType().getIdentifier()); sbLocName.append("_"); sbLocName.append(getOuterType().nextGroupInterfaceID); localName = sbLocName.toString(); getOuterType().nextGroupInterfaceID++; orientation = PortOrientation.BOTTOM; } /** * Return the interface of the inner component * * @return The interface of the inner component */ public INetworkInterface getInnerConnection() { return innerConnection; } /** * Return the interface of the outer component * * @return The interface of the outer component */ public INetworkInterface getOuterConnection() { return outerConnection; } /** * Remove this group interface from the containing group */ void removeFromGroup() { groupInterfaces.remove(this); } /** * Set the orientation of this interface. * * @param pOri The new orientation, cannot be null */ public void setOrientation(PortOrientation pOri) { if (pOri != null) { if (!pOri.toString().equals(orientation.toString())) { orientation = pOri; ComponentGroup.this.calcDragboxDimensions(); if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Interface orientation of '"); sb.append(localName); sb.append("' updated to: "); sb.append(orientation.toString()); log.debug(sb.toString()); } } else { if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Interface orientation of '"); sb.append(localName); sb.append("' did not change. Old and new orientation: "); sb.append(orientation.toString()); log.debug(sb.toString()); } } } else { throw new NullPointerException("NULL pointer for interface orientation provided"); } } /** * Return the orientation of this port * * @return The orientation of this port */ public PortOrientation getOrientation() { return orientation; } @Override public String getRDFLocalName() { return localName; } @Override public JSONObject toJSON() throws JSONException { JSONObject rv = new JSONObject(); rv.put("identifier", localName); rv.put("orientation", orientation.toString()); rv.put("innerConn", innerConnection.toJSON()); rv.put("outerConn", outerConnection.toJSON()); return rv; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((localName == null) ? 0 : localName.hashCode()); result = prime * result + ((innerConnection == null) ? 0 : innerConnection.hashCode()); result = prime * result + ((outerConnection == null) ? 0 : outerConnection.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof GroupInterface) { GroupInterface ng = (GroupInterface) obj; return (ng.getOuterType().equals(getOuterType()) && ng.localName.equals(localName) && ng.innerConnection.equals(innerConnection) && ng.outerConnection.equals(outerConnection)); } else { return false; } } /** * Return the enclosing ComponentGroup object * * @return The enclosing ComponentGroup object */ private ComponentGroup getOuterType() { return ComponentGroup.this; } } } }