0

I am trying to create a calendar out of JPanels but am getting an array index out of bounds for some reason and I don't know why. Can someone please help? It is not showing as an error in my code but I don't know where it could be miscounting the number of rows.

public class Calendar {

    JTable calendar;
    GregorianCalendar cal;
    JPanel[][] dates;
    public Calendar()
    {
        cal = new GregorianCalendar();
        cal.set(2024, 3, 1);
        String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
        List<JPanel> mon = new ArrayList<JPanel>();
        List<JPanel> tue = new ArrayList<JPanel>();
        List<JPanel> wed = new ArrayList<JPanel>();
        List<JPanel> thur = new ArrayList<JPanel>();
        List<JPanel> fri = new ArrayList<JPanel>();
        List<JPanel> sat = new ArrayList<JPanel>();
        List<JPanel> sun = new ArrayList<JPanel>();
        System.out.println(cal.get(GregorianCalendar.DAY_OF_WEEK));
        while(cal.get(GregorianCalendar.MONTH) == 3)
        {
            int day = cal.get(GregorianCalendar.DAY_OF_WEEK);
            switch(day)
            {
            case 2:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 2)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    mon.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    mon.add(new JPanel());
                }
                break;
            case 3:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 3)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    tue.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    tue.add(new JPanel());
                }
                break;
            case 4:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 4)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    wed.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    wed.add(new JPanel());
                }
                break;
            case 5:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 5)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    thur.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    thur.add(new JPanel());
                }
                break;
            case 6:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 6)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    fri.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    fri.add(new JPanel());
                }
                break;
            case 7:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 7)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    sat.add(yub);
                }
                else if(cal.get(GregorianCalendar.DATE) == 1)
                {
                    sat.add(new JPanel());
                }
                break;
            case 1:
                if(cal.get(GregorianCalendar.DAY_OF_WEEK) == 1)
                {
                    JPanel yub = new JPanel();
                    JLabel meow = new JLabel(String.valueOf(cal.get(GregorianCalendar.DATE)));
                    yub.add(meow);
                    sun.add(yub);
                }
                break;
            }
            cal.roll(GregorianCalendar.DATE, true);
            if(cal.get(GregorianCalendar.DATE) == 1)
            {
                break;
            }
        }
        if(tue.size() != mon.size())
        {
            tue.add(new JPanel());
        }
        if(wed.size() != tue.size())
        {
            wed.add(new JPanel());
        }
        if(thur.size() != wed.size())
        {
            thur.add(new JPanel());
        }
        if(fri.size() != thur.size())
        {
            fri.add(new JPanel());
        }
        if(sat.size() != fri.size())
        {
            sat.add(new JPanel());
        }
        if(sun.size() != sat.size())
        {
            sun.add(new JPanel());
        }
        System.out.println(mon.size() + " " + tue.size() + " " + wed.size() + " " + thur.size() + " " + fri.size() +
                " " + sat.size() + " " + sun.size());
        tue.stream().forEach(x -> System.out.println(x.getComponentCount()));
        JPanel[][] dates = {mon.toArray(new JPanel[mon.size()]), tue.toArray(new JPanel[tue.size()]),
                wed.toArray(new JPanel[wed.size()]), thur.toArray(new JPanel[thur.size()]),
                fri.toArray(new JPanel[fri.size()]), sat.toArray(new JPanel[sat.size()]),
                sun.toArray(new JPanel[sun.size()])};
        this.dates = dates;
        calendar = new JTable(dates, days);
    }
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
    at java.desktop/javax.swing.JTable$1.getValueAt(JTable.java:704)
    at java.desktop/javax.swing.JTable.getValueAt(JTable.java:2706)
    at java.desktop/javax.swing.JTable.prepareRenderer(JTable.java:5724)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2210)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2112)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1908)
    at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
    at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
    at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5271)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
    at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1060)
    at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
    at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
    at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
    at java.desktop/java.awt.Container.paint(Container.java:2003)
    at java.desktop/java.awt.Window.paint(Window.java:3949)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:876)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
    at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
    at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
    at java.desktop/javax.swing.JTable$1.getValueAt(JTable.java:704)
    at java.desktop/javax.swing.JTable.getValueAt(JTable.java:2706)
    at java.desktop/javax.swing.JTable.prepareRenderer(JTable.java:5724)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2210)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2112)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1908)
    at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
    at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1083)
    at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
    at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:907)
    at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5271)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
    at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1060)
    at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
    at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
    at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
    at java.desktop/java.awt.Container.paint(Container.java:2003)
    at java.desktop/java.awt.Window.paint(Window.java:3949)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:876)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
    at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
    at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

I tried comparing the size of each List before converting to arrays and made sure that they were all the same but it seems to think that there is an extra row somewhere. It should create a table that is a calendar for the month.

3
  • 2
    Don't put components in your model, that's not how tables work - instead, you fill your table model with data and use renderers to customise the output. See How to Use Tables for more details Commented Apr 15 at 2:08
  • 3
    I would also, strongly, discourage you from making use of of the out-of-date Calendar API and start making use of the java.time.* APIs instead - see the date/time trail for more details Commented Apr 15 at 2:15
  • 1
    Your table model is been built the wrong way. The dates array is actually [row][column] ordering, so you're placing each on in each row, as apposed to interlacing the days on each row Commented Apr 15 at 2:24

1 Answer 1

1

Don't put components in your model, that's not how tables work - instead, you fill your table model with data and use renderers to customise the output. See How to Use Tables for more details.

Don't use the out-of-date java.util.Calendar/Date APIs, instead start making use of the java.time.* APIs instead - see the date/time trail for more details.

Your table model is been built the wrong way. The dates array is actually [row][column] ordering. You're placing each day in each row, as apposed to interlacing the days on each row.

In your current setup, you should be using something like...

JPanel[][] dates = new JPanel[5][days.length];
for (int x = 0; x < dates.length; x++) {
    dates[x][0] = mon.get(x);
    dates[x][1] = tue.get(x);
    dates[x][2] = wed.get(x);
    dates[x][3] = thur.get(x);
    dates[x][4] = fri.get(x);
    dates[x][5] = sat.get(x);
    dates[x][6] = sun.get(x);
}

This is still error prone, as you don't ensure that the individual days are filled correctly (to always have 5 elements).

Now, if I was doing this, I might approach it something like...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    protected class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            CalendarModel calendarModel = new CalendarModel(2024, Month.MARCH);
            JTable table;
            table = new JTable(new CalendarTableModel(calendarModel)) {
                @Override
                public TableCellRenderer getCellRenderer(int row, int column) {
                    TableCellRenderer cellRenderer = super.getCellRenderer(row, column);
//                    System.out.println(cellRenderer);
                    return cellRenderer;
                }
            };

            table.setDefaultRenderer(LocalDate.class, new CalendarTableCellRenderer(calendarModel));

            add(new JScrollPane(table));
        }
    }

    // You could setup this up to be navigatable (ie change the month)
    public class CalendarModel {        
        // The bounds of the calendar based on the desired month
        private LocalDate startDate;
        private LocalDate endDate;
        // The actual first date the calendar represents (ie, the first cell)
        private LocalDate anchorDate;

        public CalendarModel(int year, Month month) {
            startDate = LocalDate.of(year, month, 1);
            endDate = startDate.withDayOfMonth(startDate.getMonth().length(startDate.isLeapYear()));
            int startOfWeekOffset = startDate.get(ChronoField.DAY_OF_WEEK) - 1;
            anchorDate = startDate.minusDays(startOfWeekOffset);
        }

        public LocalDate getDateForDayOffset(int offset) {
            return anchorDate.plusDays(offset);
        }

        public boolean isWithinBounds(LocalDate target) {
            return (target.equals(startDate) || target.isAfter(startDate)) &&
                    (target.equals(endDate) || target.isBefore(endDate));
        }
    }

    public class CalendarTableCellRenderer extends DefaultTableCellRenderer {

        public static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd");

        private CalendarModel model;

        public CalendarTableCellRenderer(CalendarModel model) {
            this.model = model;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            String text = "---";
            boolean isWithinBounds = false;
            if (value instanceof LocalDate) {
                text = FORMATTER.format((LocalDate)value);
                isWithinBounds = model.isWithinBounds((LocalDate)value);
            }
            Component component = super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
            if (!isWithinBounds) {
                component.setForeground(Color.LIGHT_GRAY);
            } else {
                component.setForeground(Color.BLACK);
            }
            return component;
        }
    }

    public class CalendarTableModel extends AbstractTableModel {
        private String[] days = {
            "Mon",
            "Tue",
            "Wed",
            "Thu",
            "Fri",
            "Sat",
            "Sun"
        };

        private CalendarModel model;

        public CalendarTableModel(CalendarModel model) {
            this.model = model;
        }

        @Override
        public String getColumnName(int column) {
            return days[column];
        }

        @Override
        public int getRowCount() {
            return 5;
        }

        @Override
        public int getColumnCount() {
            return 7;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return LocalDate.class;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            // The number of days we want based on the row/column
            int offset = (rowIndex * 7) + columnIndex;
            return model.getDateForDayOffset(offset);
        }
    }
}
5
  • Using a cell renderer to alter the displayed text breaks clipboard copying and breaks accessibility. I usually create a wrapper class with a customized toString method instead, and I put instances of that into the model.
    – VGR
    Commented Apr 15 at 14:48
  • @VGR 🤔 I wouldn't consider that a reason not to support custom rendering. Also, the renderer can take up the accessibility support itself and as for copy, it'd avoid using toString as the cell isn't always rendering "plain text" and you might want to copy "data" instead - so that would also fall into the realm of the renderer ... although it's been a very long time since I've done it Commented Apr 15 at 22:16
  • @vgr for example; example - but, again, these are all beyond the scope of the original question Commented Apr 15 at 22:18
  • @MadProgrammer Is there a particular reason that endDate = startDate.withDayOfMonth(startDate.getMonth().length(startDate.isLeapYear())); is used instead of endDate = startDate.withDayOfMonth(startDate.lengthOfMonth());? Commented Apr 16 at 23:47
  • @NoahGaston I googled for "get first and last day of month" and that's what it gave me, so I used it. Commented Apr 16 at 23:59

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