Search code examples
javaswingsortingstackjlist

How to sort Stack by certain value and show it in DefaultListModel in java?


I have next assignment: Implement Java Swing Application which allows to add new rectangle (described by X, Y coordinates and a and b side) to stack. Application should allow also to delete rectangle from stack using LIFO model. All rectangles should be presented in JList. Like this:

enter image description here

I implemented on this way:

public class Stack extends JFrame {

private JPanel contentPane;
private Deque<String> stack = new ArrayDeque<String>();
private DefaultListModel<String> dlm = new DefaultListModel<String>();



JList lstRectangle = new JList();
lstRectangle.setModel(dlm);     
scrollPane.setViewportView(lstRectangle);


JButton btnAdd = new JButton("Add rectangle");
        btnAdd.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                DlgRectangle dlgRectangle = new DlgRectangle();
                dlgRectangle.setVisible(true);
                if(dlgRectangle.isOk) {
                    dlm.add(0, "Upper left point: (" + dlgRectangle.txtX.getText() + "," + dlgRectangle.txtY.getText() + ") " + "width: " + dlgRectangle.txtWidth.getText() + " " + "height: " + dlgRectangle.txtHeight.getText());
                
                String rectangle = new String();
                rectangle = "Upper left point: (" + dlgRectangle.txtX.getText() + "," + dlgRectangle.txtY.getText() + ") " + "width: " + dlgRectangle.txtWidth.getText() + " " + "height: " + dlgRectangle.txtHeight.getText();
                stack.addFirst(rectangle);
                System.out.println(stack);
            }           
        }
    });


JButton btnDelete = new JButton("Delete rectangle");
        btnDelete.addActionListener(new ActionListener() {          
            @Override
            public void actionPerformed(ActionEvent e) {
                if(dlm != null && !dlm.isEmpty()) {
                    dlm.remove(0);
                    stack.pop();
                    System.out.println(stack);
                } else {
                    System.out.println("Stack is empty");
                }
            }
        });

At first I would like to ask is this good practice and good approach of solving this task. It's working, but I think it could be done better then seppeated pushing objects to stack and DefaultListModel. Some how to do it at same time and showing stack objects directly in DLM.

Next and main question (for similar task) is how to sort stack according to the area of ​​the rectangle?

I tried to improve code above, but I just calculated area and than I stucked...

int sideA = Integer.parseInt(dlgRectangle.txtWidth.getText());
                    int sideB = Integer.parseInt(dlgRectangle.txtHeight.getText());
                    int surfaceArea = sideA*sideB;
                    
                    System.out.println(surfaceArea);
                    
                    dlm.add(0, "Upper left point: (" + dlgRectangle.txtX.getText() + "," + dlgRectangle.txtY.getText() + ") " + "width: " + dlgRectangle.txtWidth.getText() + " " + "height: " + dlgRectangle.txtHeight.getText() + " " + "Surface area: " + String.valueOf(surfaceArea));                  
                    
                    String rectangle = new String();
                    rectangle = "Upper left point: (" + dlgRectangle.txtX.getText() + "," + dlgRectangle.txtY.getText() + ") " + "width: " + dlgRectangle.txtWidth.getText() + " " + "height: " + dlgRectangle.txtHeight.getText()+ " " + "Surface area: " + String.valueOf(surfaceArea);
                    stack.addFirst(rectangle);
                    stack.toArray();
                    
                    System.out.println(stack);

Solution

  • At first I would like to ask is this good practice and good approach of solving this task

    Generally, no. You're running a API which supports the Model/View/Controller concept.

    The point of the model is to model the data. The point of the view is present the data in some meaningful way to the user.

    It makes no sense to apply a String to the model. Instead, you should be managing a representation of a "rectangle" in the model and the configure the JList to present this value in some meaningful way.

    See How to Use Lists and Writing a Custom Cell Renderer

    For example

    Simple model/view

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.EventQueue;
    import java.awt.Rectangle;
    import javax.swing.DefaultListCellRenderer;
    import javax.swing.DefaultListModel;
    import javax.swing.JFrame;
    import javax.swing.JList;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    
    public class Stack {
    
        public static void main(String[] args) {
            new Stack();
        }
    
        public Stack() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private JList<Rectangle> list;
            private DefaultListModel<Rectangle> model;
    
            public TestPane() {
                setLayout(new BorderLayout());
                list = new JList();
                model = new DefaultListModel<Rectangle>();
    
                list.setCellRenderer(new RectangeListCell());
                list.setModel(model);
    
                model.addElement(new Rectangle(100, 100, 10, 10));
    
                add(new JScrollPane(list));
            }
    
        }
    
        public class RectangeListCell extends DefaultListCellRenderer {
    
            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (value instanceof Rectangle) {
                    Rectangle rect = (Rectangle) value;
                    value = "Rectangle " + rect.x + "x" + rect.y + " by " + rect.width + "x" + rect.height;
                }
                return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            }
    
        }
    }
    

    Next and main question (for similar task) is how to sort stack according to the area of ​​the rectangle?

    That's a more difficult question. I'd recommend starting by creating your own POJO representation, which can store the properties of the rectangle and calculate it's own area.

    The next problem comes down to desire.

    You could calculate the position of new element and insert into the model manually. This is a little clumsy.

    You could create a "sorted list model". This places the requirement on actually sorting the data onto the model, for example...

    public class SortedListModel<T> extends AbstractListModel<T> {
    
        private SortedSet<T> model;
    
        public SortedListModel(Comparator<T> comparator) {
            model = new TreeSet<>(comparator);
        }
    
        public void add(T value) {
            if (!model.contains(value)) {
                model.add(value);
                int insertIndex = model.headSet(value).size();
    
                fireIntervalAdded(value, insertIndex, insertIndex);
            }
        }
    
        @Override
        public int getSize() {
            return model.size();
        }
    
        @Override
        public T getElementAt(int index) {
            Object value = model.toArray()[index];
            return (T)value;
        }
    }
    

    This is a little limited, as it's relying on a Set (and I've not implemented remove, but it's not hard)

    Another option might be to utilities a much "older" concept we use to use, before JTable was sortable, making a "wrapper" model which would maintain a list of indices mapped in such away as to make the data appear sorted, for example.

    This, of course, could hint at just using a single column JTable.

    Finally, you could maintain a List of the data and just manually sort it each time a new object is added and then apply it the model. This is some what heavy handed though.

    Clarifications

    This is some what heavy handed though

    This refers to the amount of work which would need to be "re-invented" each time you wanted to implement the solution each time you wanted to use it.

    A simple solution might use a seperate "source model" of the data, which, when new data is added to, needs to be sorted and then that "source model" needs to be applied to the ListModel, which adds another overhead as the JList will revalidate and repaint itself.

    A "better" solution is one which is:

    1. Easily re-usable
    2. Self contained

    The initial solution is based on a very old piece of code, which isn't exactly elegant or performant.

    A little bit of searching and researching, we can can calculate the insertion point using the Collections API instead, which allows us to make use of a simple ArrayList instead, for example:

    public class SortedListModel<T> extends AbstractListModel<T> {
    
        private List<T> model;
        private Comparator<T> comparator;
    
        public SortedListModel(Comparator<T> comparator) {
            model = new ArrayList<>(32);
            this.comparator = comparator;
        }
    
        public Comparator<T> getComparator() {
            return comparator;
        }
    
        public void add(T value) {
            int index = Collections.binarySearch(model, value, getComparator());
            if (index < 0) {
                index = -index - 1;
            }
            model.add(index, value);
            fireIntervalAdded(value, index, index);
        }
    
        @Override
        public int getSize() {
            return model.size();
        }
    
        @Override
        public T getElementAt(int index) {
            return model.get(index);
        }
    }