1
\$\begingroup\$

I have to render a menu in tree structure in our UI. I am building the tree in the backend. The Tree can be built by taking many combinations of input parameters. So I created a hierarchy of Input parameters classes and a single TreeBuilder class to build the tree. Some of the input hierarchy classes are shown below. I have ignored the setters and getters for simplicity.

             public abstract class TreeInput {

                    private final String modelNativeName;
                    private final String modelCommonName;
                    private final String modelOrCardName;
                    private final String vendorName;
                    private final String parentCommonSlotName;

 private final CNGDataProvider cngDataProvider;

    public TreeInput(CNGDataProvider cngDataProvider, String modelNativeName) {
        this.cngDataProvider = cngDataProvider;
        this.vendorName = cngDataProvider.getVendorNameForModelName(modelNativeName);
        this.modelCommonName= cngDataProvider.getCommonNameForModel(modelNativeName, vendorName);
        this.modelNativeName = modelNativeName;
        this.modelOrCardName = modelCommonName;
        this.parentCommonSlotName = "";
        this.sfpCards = cngDataProvider.getSFPCards();
    }

    public TreeInput(CNGDataProvider cngDataProvider, String[] vendorAndModelName) {
        this.cngDataProvider = cngDataProvider;
        this.vendorName = vendorAndModelName[0];
        this.modelNativeName = vendorAndModelName[1];
        this.modelCommonName= cngDataProvider.getCommonNameForModel(modelNativeName, vendorName);
        this.modelOrCardName = modelCommonName;
        this.parentCommonSlotName = "";
    }

                    abstract public Map<String, NativeSlotDisplayLabel> getChildSubTypeMap(Tree<EquipmentDetails> tree);

                }

            public class NeNameBasedTreeInput extends TreeInput {

                private final String neName;

                private final String neLabel;

                private final Map<String, String> discoveryData;
                private final Map<String, String> otherPlanEquipmentData;

                public NeNameBasedTreeInput(CNGDataProvider cngDataProvider, String neName, String neLabel) {
                    super(cngDataProvider, cngDataProvider.getDiscoveredEquipFromCygnet(neName).getVendorAndmodelname());
                    this.neName = neName;
                    this.neLabel = neLabel;
                    this.discoveryData = cngDataProvider.getDiscoveredEquipFromCygnet(neName).getHolderToEquip();
                    this.otherPlanEquipmentData = cngDataProvider.getEquipDetailFromOtherPlan(neLabel);
                }
        @Override
        public Map<String, NativeSlotDisplayLabel> getChildSubTypeMap(Tree<EquipmentDetails> tree) {

            EquipmentDetails equipmentDetails = tree.getUserObject();

            String holderCommonName = equipmentDetails.getCommonName();

            String modelOrCardName = equipmentDetails.getModelEquipType();

            CommonNativeSlotNamesWrapper slotCommonNative =
                    getCngDataProvider().getChildSlotNameMapForCard(holderCommonName, getNeName(), modelOrCardName, getVendorName(), getModelCommonName());

            if (slotCommonNative == null)
                return null;

            Map<String, NativeSlotDisplayLabel> result = new TreeMap<>(Comparator.comparingInt(this::extractInt));

            Map<String, NativeSlotDisplayLabel> childSubTypeMap = slotCommonNative.getMap();

            for (String childHolderCommonName : childSubTypeMap.keySet()) {

                if (childHolderCommonName.contains("port"))
                    continue;

                result.put(childHolderCommonName, childSubTypeMap.get(childHolderCommonName));
            }

            return result;
        }


        }

            public class ModelAndCardBasedTreeInput extends TreeInput {

                private final Map<String, String> thisPlanEquipmentData;
                private final Map<String, String> otherEquipmentPlanData;

                private final String modelLabel;

                public ModelAndCardBasedTreeInput(CNGDataProvider cngDataProvider, String modelName, Map<String, String> thisPlanEquipmentData, String modelLabel) {
                    super(cngDataProvider,modelName);
                    this.thisPlanEquipmentData = thisPlanEquipmentData;
                    this.otherEquipmentPlanData = cngDataProvider.getEquipDetailFromOtherPlan(modelLabel);
                    this.modelLabel = modelLabel;
                }

    @Override
        public Map<String, NativeSlotDisplayLabel> getChildSubTypeMap(Tree<EquipmentDetails> tree) {

            EquipmentDetails equipmentDetails = tree.getUserObject();

            String holderCommonName = equipmentDetails.getCommonName();

            String modelOrCardName = equipmentDetails.getModelEquipType();

            CommonNativeSlotNamesWrapper slotCommonNative = getCngDataProvider().getChildSlotNameMapForCard(holderCommonName, null, modelOrCardName, null, null);

            if (slotCommonNative == null)
                return null;

            Map<String, NativeSlotDisplayLabel> result = new TreeMap<>(Comparator.comparingInt(this::extractInt));

            Map<String, NativeSlotDisplayLabel> childSubTypeMap = slotCommonNative.getMap();

            for (String childHolderCommonName : childSubTypeMap.keySet()) {

                if (childHolderCommonName.contains("port"))
                    continue;

                result.put(childHolderCommonName, childSubTypeMap.get(childHolderCommonName));
            }

            return result;
        }

        }

and the TreeBuilder class which takes a TreeInput and return a Tree :

public class TreeBuilder {

    private TreeInput treeInput;

    public TreeBuilder(TreeInput treeInput) {
        this.treeInput = treeInput;
    }

    public Tree<EquipmentDetails> buildTree() {

        Tree<EquipmentDetails> tree = new Tree<>(treeInput.getEquipmentDetails(treeInput.getModelCommonName()));

        return buildTree(tree);
    }

    private Tree<EquipmentDetails> buildTree(Tree<EquipmentDetails> parentTree) {

        Map<String, NativeSlotDisplayLabel> childSubTypeMap = treeInput.getChildSubTypeMap(parentTree);

        if (CommonUtils.nullOrEmpty(childSubTypeMap))
            return parentTree;

        for (String holderCommonName : childSubTypeMap.keySet()) {

            NativeSlotDisplayLabel nativeSlotDisplayLabel = childSubTypeMap.get(holderCommonName);

            EquipmentDetails equipmentDetails = treeInput.constructEqptDetails(holderCommonName, nativeSlotDisplayLabel, parentTree);

            Tree<EquipmentDetails> childTree = new Tree<>(equipmentDetails);

            parentTree.addChild(childTree);

            String equipmentCommonName = equipmentDetails.getModelEquipType();

            if (equipmentCommonName == null)
                continue;

            buildTree(childTree);
        }

        return parentTree;
    }
}

When a new requirement shows up I was simply extending the basic TreeInput class added the specific parameters in the subclass and voila the Tree was successfully built. So the TreeInput is extendible and closed for modifications. (Feels good for me).

But as can be seen from the TreeInput subclasses I was ignoring the "port" components in the result of getChildSlotNameMapForCard() call.

Now the new requirment is I need to return a Set of those "port" specifically. Here a completely new return type needed even though most of the steps to achieve this are already implmented for Tree building. I think the TreeInput is tightly coupled with TreeBuilder. Now not knowing to implement a better design I extended one of the TreeInput Subclass and a dirty trick.

public class NeNamePortStatusTreeInput extends NeNameBasedTreeInput {

    private final PTPInfo ptpInfo;
    private final Set<PortNameAndLabel> portNameAndLabels = new HashSet<>();

    public NeNamePortStatusTreeInput(CNGDataProvider cngDataProvider, String neName, String neLabel) throws Exception {
        super(cngDataProvider, neName, neLabel);
        this.ptpInfo = cngDataProvider.getPTPInfo(neName);
    }

    private void addInPortNameAndLabels(PortNameAndLabel portNameAndLabel) {
        portNameAndLabels.add(portNameAndLabel);
    }

    @Override
    public Map<String, NativeSlotDisplayLabel> getChildSubTypeMap(Tree<EquipmentDetails> tree) {

        EquipmentDetails equipmentDetails = tree.getUserObject();

        String parentCommonSlotName = equipmentDetails.getCommonName();

        String modelOrCardName = equipmentDetails.getModelEquipType();

        CommonNativeSlotNamesWrapper slotCommonNative = getCngDataProvider().getChildSlotNameMapForCard(parentCommonSlotName, getNeName(), modelOrCardName, getVendorName(), getModelCommonName());

        if (slotCommonNative == null)
            return null;

        Map<String, NativeSlotDisplayLabel> result = new TreeMap<>(this::compare);

        Map<String, NativeSlotDisplayLabel> slotCommonNativeMap = slotCommonNative.getMap();

        for (String commonSlotOrPortName : slotCommonNativeMap.keySet()) {

            NativeSlotDisplayLabel nativeSlotDisplayLabel = slotCommonNativeMap.get(commonSlotOrPortName);

            result.put(commonSlotOrPortName, nativeSlotDisplayLabel);

            if (!commonSlotOrPortName.contains("port") || isPortAlreadyPlanned(getNeLabel(), commonSlotOrPortName))
                continue;

            Map<String, LabelAndStatus> portNameToPortLabelAndStatus = ptpInfo.getPortNameToPortLabelAndStatus();

            LabelAndStatus labelAndStatus = portNameToPortLabelAndStatus.get(nativeSlotDisplayLabel.getNativeSlot());

            if (labelAndStatus != null) {

                if (labelAndStatus.isPortUsed())
                    continue;

                addInPortNameAndLabels(new PortNameAndLabel(commonSlotOrPortName, labelAndStatus.getPortLabel().trim()));
            } else {

                String simplePortName = nativeSlotDisplayLabel.getSimpleSlotCommonName().replace("port","p");

                String portPrefix = equipmentDetails.getPortPrefix();

                String label;

                if (CommonUtils.nullOrEmpty(portPrefix))
                    label = simplePortName;
                else {
                    if (equipmentDetails.isSlotSFPCompatible()) {
                        String slotNumber = equipmentDetails.getSlotNumber();
                        label = portPrefix + ":SFP:p=" + slotNumber;
                    } else
                        label = portPrefix + ":" + simplePortName;
                }
                addInPortNameAndLabels(new PortNameAndLabel(commonSlotOrPortName, label));
            }
        }

        return result;
    }

    public Set<PortNameAndLabel> getPortNameAndLabels() {
        return portNameAndLabels;
    }
}

Once the Tree is built i call the getPortNameAndLabels() method and return the set of Ports. I am just processing the previously ignored ports and save it a collection and in the end instead of returning a tree I return the collection of ports.

Though this works (for now), I dont think this is correct use of Inheritance. I am struck for almost a week in search of a better approach (what noob I must be!!). I hope I am clear enough. Please comment if any clarifications needed. Any suggestions are welcome.

\$\endgroup\$
  • \$\begingroup\$ Does the code in TreeInput really look like that? \$\endgroup\$ – Vogel612 Oct 4 '17 at 15:52
  • \$\begingroup\$ @Vogel612 i had ignored the contructor for simplicity. Added it now. There is a lot of setters and getters which I have ignored this time as well. \$\endgroup\$ – Tiny Rick Oct 5 '17 at 5:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.