1

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);
Paleolog
  • 31
  • 4
  • Second question this week about sorting stacks. You can sort it but it ceases to be a stack the moment you do so. Confused and confusiing instructor here. – user207421 Sep 19 '21 at 07:16
  • @user207421 So I should use some Array or do you have some other suggestion? – Paleolog Sep 19 '21 at 07:43
  • I am unable to understand how you got from what I wrote to what you are now asking me. My point is that the *question* is ill-formed. And I already said 'you can do it'. – user207421 Sep 19 '21 at 09:52

1 Answers1

4

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);
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • An extended `java.awt.Rectangle` can be an easy alternative to a custom POJO (with some additional benefits). – c0der Sep 19 '21 at 17:31
  • @c0der It all depends on needs - just beed the ability to more easily calculate the area – MadProgrammer Sep 19 '21 at 21:55
  • 1
    Calling sorting on insertion “heavy handed” is strange, in view of `model.toArray()[index]` done *on every `getElementAt` call*. The query calls will happen far more often in practice. And you don’t need a full sorting on insertion anyway, just binary search followed by inserting at the identified position. Yes, inserting into an `ArrayList` is O(n), but on the other hand, you’ll have the correct index for the event already, whereas `model.headSet(value).size()` is O(n) too. – Holger Sep 22 '21 at 15:06
  • @Holger *"Calling sorting on insertion “heavy handed” is strange, in view of model.toArray()[index] done on every getElementAt call"* - the `toArray` is what I'm talking about – MadProgrammer Sep 22 '21 at 21:50
  • 1
    So “this” does not refer to the previous sentence? – Holger Sep 23 '21 at 07:09
  • @Holger In the long term, a self contained unit of work would be more preferable then trying to maintain two models of the data (and running the risk of either duplicating work or missing updates), as updating the whole `ListModel` is probably more intensive then adding/removing row. A "better" long term solution might be to produce a `ListModel` which was backed by a `RowSorter` of some kind, which could better map the indices between the "facing" model and the internal model – MadProgrammer Sep 23 '21 at 07:19
  • 1
    As long as a model backed by an `ArrayList` is suitable for the use case, a rowsorter wouldn’t improve much; the maintenance of the rowsorter’s indices isn’t simpler than the maintenance of the references. – Holger Sep 23 '21 at 07:41
  • 1
    @Holger The `RowSorter` simply provides an establish workflow, which, technically could be reused across different domains. Managing two containers would be "heavy handed" and could be better managed through a properly designed, self contained unit of work - which is the "intent" of the sorted model, although I'd argue the implementation could be better done - but it is an attempt to demonstrate the concept, not necessarily the solution. As a "personal" preference I don't like modifying the "user" source data, which is where something like `RowSorter` can be used – MadProgrammer Sep 23 '21 at 09:50
  • 1
    I think, I understood what you’re aiming at. Unfortunately, it’s difficult to tell beginners, how the initially more complicated approach will serve better long term. And it’s a shame that `JList` (still) has no direct support for `RowSorter`… – Holger Sep 23 '21 at 10:36