5
\$\begingroup\$

This Java Swing code consists of 2 JLists and a delete button. When I select an item from locationList, it will also delete items from branchList. This is achieved by using the equals method to compare object between the two lists.

Is there any better solution/data structure that I can implement where when I delete an object in List A, it will also delete object in List B which is linked to it?

Branch class:

public class Branch {
private String loc;
private String branch;

public Branch() {

}

public Branch(String loc, String branch) {
    this.loc = loc;
    this.branch = branch;

}

public String toString() {
    return "(" + loc + ") - " + branch;
}
public String getLoc() {
    return loc;
}

Location class:

public class Location {
private Long locCount;
private String loc;

public Location(String loc, Long locCount) {
    this.locCount = locCount;
    this.loc = loc;
}

public String toString() {
    return loc + " (" + locCount + ")";
}

public String getLocation() {
    return loc;
}

Main class:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;

public class Test {

private JFrame frame;
private List<String> locationCount;
private Long total;
private Map<Location, Double> showLocation;

/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                Test window = new Test();
                window.frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

/**
 * Create the application.
 */
public Test() {
    initialize();
}

/**
 * Initialize the contents of the frame.
 */
private void initialize() {
    frame = new JFrame();
    frame.setBounds(100, 100, 450, 300);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(null);

    DefaultListModel<Location> location = new DefaultListModel<Location>();
    JList<Location> locationList = new JList<Location>(location);
    locationList.setBounds(20, 24, 143, 212);
    frame.getContentPane().add(locationList);
    JScrollPane locationScroll = new JScrollPane();
    frame.getContentPane().add(locationScroll);

    DefaultListModel<Branch> branch = new DefaultListModel<Branch>();
    JList<Branch> branchList = new JList<Branch>(branch);
    branchList.setBounds(204, 24, 183, 212);
    frame.getContentPane().add(branchList);
    JScrollPane branchScroll = new JScrollPane();
    frame.getContentPane().add(branchScroll);

    locationCount = new ArrayList<String>();

    branch.addElement(new Branch("London", "Branch 1"));
    branch.addElement(new Branch("London", "Branch 2"));
    branch.addElement(new Branch("Manchester", "Branch 3"));
    branch.addElement(new Branch("London", "Branch 4"));
    branch.addElement(new Branch("Manchester", "Branch 5"));

    for (int i=0;i<branch.size();i++) {
        locationCount.add(branch.get(i).getLoc());
    }

    // Group similar strings
    Map<String, Long> counts = locationCount.stream().collect(Collectors.groupingBy(e -> e.toString().toLowerCase(), Collectors.counting()));

    total = 0L;

    for (Long a : counts.values()) {
        total += a;
    }

    showLocation = new HashMap<Location, Double>();


    for ( Map.Entry<String, Long> entry : counts.entrySet()) {
        String key = entry.getKey();
        Long locCount = entry.getValue();
        Double percent = (locCount/Double.valueOf(total))*100;
        showLocation.put(new Location(key,locCount), percent);
    }

    JButton btnDelete = new JButton("Delete");
    btnDelete.setBounds(70, 248, 200, 20);
    frame.getContentPane().add(btnDelete);
    btnDelete.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            if (!locationList.isSelectionEmpty()) {
                String selectedLocation = locationList.getSelectedValue().getLocation();
                location.removeElementAt(locationList.getSelectedIndex());

                for(int k=branch.size()-1;k>=0;k--) {
                    if(branch.get(k).getLoc().toLowerCase().equals(selectedLocation)){

                        System.out.println(branch.get(k));
                        branch.removeElement(branch.get(k));
                    }
                }

            } else if (!branchList.isSelectionEmpty()) {
                branch.removeElementAt(branchList.getSelectedIndex());

            } else {
                JOptionPane.showMessageDialog(frame, "Select one of the list first");
            }
        }
    });

    for (Location a : showLocation.keySet()) {
        location.addElement(a);
    }

}
}
\$\endgroup\$
3
  • \$\begingroup\$ I think that what you need actually is some kind of MVVM framework for java swing. \$\endgroup\$ – Łukasz Zwierko Jul 21 '15 at 12:36
  • \$\begingroup\$ @ŁukaszZwierko how would suggesting a design pattern be helpful to his problem at hand? To me it looks like the OP has 2 classes, and made a makeshift JFrame to show what he is wanting to do so that you wouldn't be bothered with details of how the frame looks/ interacts with the entire program \$\endgroup\$ – Robert Snyder Jul 21 '15 at 16:27
  • \$\begingroup\$ stackoverflow.com/questions/4939563/… pls take a look here seems that swing does actually support basic data binding \$\endgroup\$ – Łukasz Zwierko Jul 21 '15 at 19:51
2
\$\begingroup\$

In my comment I mentioned that this frame looks like a demo of what you are trying to achieve. After diving into the code though I feel compelled to mention a few things that will make what you are trying to do hopefully easier. First thing is to use methods to help make clear what you are trying to do from a higher level. Here is a small idea that I've been playing with

private void initialize() {
    JPanel listsPanel = new JPanel(new BorderLayout());

    DefaultListModel<Branch> branches = initializeBranchesPanel(listsPanel);
    DefaultListModel<Location> location = initializeLocationPanel(listsPanel);
    JButton btnDelete = initialzeDeleteButtonPanel();

    add(listsPanel, BorderLayout.NORTH);
    add(btnDelete, BorderLayout.SOUTH);

    putDataIntoBranches(branches);
    //...
}

Lukasz Zwierko mentioned a design pattern in his comment to you (MVVM to be specific) This is a very helpful aspect to making the code easier to write, and understand. It separates concern of certain bits of code into different classes that describe their intention. This will make it easier to come back to later and adjust the appropriate class for changed functionality/bug fixes. I would highly suggest looking into MVP (but other popular patterns are the MVVM, and the older MVC). It would take too long for me to give an example of how to do this.

Next I would also like to mention unit tests. Your problem at hand uses too much code to give you the desired results. I know you can feel it because you asked your question here. Unit tests are great because they are small bits of code that can help mold how your code works. There is a art to it, and it needs practice to get good at it. That said there are TONS of resources out there and how to write unit tests. The following two tests are what I wrote to show that I only use one source of data and update the other two.

public class BranchDataTest {
    private BranchData data;
    @BeforeMethod
    public void setup(){
        data = new BranchData();
    }

    @Test
    public void testWhenNewBranchIsAddedNewLocationIsAdded(){
        Branch branch = new Branch("Location", "Branch");
        data.addBranch(branch);

        assertEquals(data.getLocations().size(), 1);
        Location location = data.getLocations().get(0);

        assertEquals(location.toString(), "Location (1)");
    }

    @Test
    public void testWhenMultipleBranchesExistLocationCountIsIncreased(){
        data.addBranch(new Branch("Location", "Branch1"));
        data.addBranch(new Branch("Location", "Branch2"));

        assertEquals(data.getLocations().size(), 1);
        Location location = data.getLocations().get(0);

        assertEquals(location.toString(), "Location (2)");
    }

}

There are probably more and better ways to write the test, but this just shows where I started. FYI in case you are curious what my new BranchData class looks like (it isn't complete it is just enough to make those 2 tests pass)

public class BranchData implements ListDataListener {
    private DefaultListModel<Branch> branches = new DefaultListModel<>();
    private DefaultListModel<Location> locations = new DefaultListModel<>();

    public BranchData() {
        branches.addListDataListener(this);
    }

    public void addBranch(Branch branch) {
        branches.addElement(branch);
    }

    public DefaultListModel<Location> getLocations() {
        return locations;
    }

    @Override
    public void intervalAdded(ListDataEvent e) {
        //debating about just using branches, or using the getSource(),
        //for now I'll keep this modular.
        Branch branchAdded = ((DefaultListModel<Branch>) e.getSource())
                .elementAt(e.getIndex0());

        if (updatedExistingLocationCount(branchAdded)) return;

        locations.addElement(new Location(branchAdded.getLocation(), 1));
    }

    private boolean updatedExistingLocationCount(Branch branchAdded) {
        for (int i = 0; i < locations.size(); i++) {
            Location location = locations.get(i);
            if(location.getLocation()==branchAdded.getLocation()){
                location.increaseCount();
                return true;
            }
        }
        return false;
    }

    @Override
    public void intervalRemoved(ListDataEvent e) {
    }

    @Override
    public void contentsChanged(ListDataEvent e) {
    }
}

Those two tests pass in 23ms, and once I get that class fully built I can know for certain that whatever I add (and delete) that my list will update as needed. Hope this helps guide you to a better solution.

\$\endgroup\$
1
  • \$\begingroup\$ +1 Thanks for sharing this. I'll take a look at the unit testing. I've never tried it before. \$\endgroup\$ – sw2 Aug 3 '15 at 2:31

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.