class TimeTablePanel<XObject, YObject> extends JPanel implements Scrollable {

    private final TimeTableLayout layout = new TimeTableLayout();
    private final Map<Object, Integer> xMap = new HashMap<>();
    private final Map<Object, Integer> yMap = new HashMap<>();

    public TimeTablePanel() {
        setLayout(layout);
    }

    public void reset() {
        removeAll();
        layout.reset();
        xMap.clear();
        yMap.clear();
    }

    // ************************************************************************
    // Define methods
    // ************************************************************************

    public void defineColumnHeaderByKey(HeaderColumnKey xObject) {
        int x = layout.addColumn();
        xMap.put(xObject, x);
    }

    public void defineColumnHeader(XObject xObject) {
        int x = layout.addColumn();
        xMap.put(xObject, x);
    }

    public void defineColumnHeader(XObject xObject, int width) {
        int x = layout.addColumn(width);
        xMap.put(xObject, x);
    }

    public void defineRowHeaderByKey(HeaderRowKey yObject) {
        int y = layout.addRow();
        yMap.put(yObject, y);
    }

    public void defineRowHeader(YObject yObject) {
        int y = layout.addRow();
        yMap.put(yObject, y);
    }

    public void defineRowHeader(YObject yObject, int height) {
        int y = layout.addRow(height);
        yMap.put(yObject, y);
    }

    // ************************************************************************
    // Add methods
    // ************************************************************************

    public void addCornerHeader(HeaderColumnKey xObject, HeaderRowKey yObject, JComponent component) {
        int x = xMap.get(xObject);
        int y = yMap.get(yObject);
        add(component, new TimeTableLayoutConstraints(x, y, true));
    }

    public void addColumnHeader(XObject xObject, HeaderRowKey yObject, JComponent component) {
        int x = xMap.get(xObject);
        int y = yMap.get(yObject);
        add(component, new TimeTableLayoutConstraints(x, y, true));
    }

    public void addColumnHeader(XObject xObject1, HeaderRowKey yObject1, XObject xObject2, HeaderRowKey yObject2,
            JComponent component) {
        int x1 = xMap.get(xObject1);
        int y1 = yMap.get(yObject1);
        int x2 = xMap.get(xObject2);
        int y2 = yMap.get(yObject2);
        add(component, new TimeTableLayoutConstraints(x1, y1, x2 - x1 + 1, y2 - y1 + 1, true));
    }

    public void addRowHeader(HeaderColumnKey xObject, YObject yObject, JComponent component) {
        int x = xMap.get(xObject);
        int y = yMap.get(yObject);
        add(component, new TimeTableLayoutConstraints(x, y, true));
    }

    public void addRowHeader(HeaderColumnKey xObject1, YObject yObject1, HeaderColumnKey xObject2, YObject yObject2,
            JComponent component) {
        int x1 = xMap.get(xObject1);
        int y1 = yMap.get(yObject1);
        int x2 = xMap.get(xObject2);
        int y2 = yMap.get(yObject2);
        add(component, new TimeTableLayoutConstraints(x1, y1, x2 - x1 + 1, y2 - y1 + 1, true));
    }

    public void addCell(XObject xObject, YObject yObject, JComponent component) {
        int x = xMap.get(xObject);
        int y = yMap.get(yObject);
        add(component, new TimeTableLayoutConstraints(x, y));
    }

    public void addCell(XObject xObject1, YObject yObject1, XObject xObject2, YObject yObject2, JComponent component) {<FILL_FUNCTION_BODY>}

    // ************************************************************************
    // Scrollable methods
    // ************************************************************************

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return SolutionPanel.PREFERRED_SCROLLABLE_VIEWPORT_SIZE;
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return 20;
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return 20;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        if (getParent() instanceof JViewport) {
            return (getParent().getWidth() > getPreferredSize().width);
        }
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        if (getParent() instanceof JViewport) {
            return (getParent().getHeight() > getPreferredSize().height);
        }
        return false;
    }

    public enum HeaderColumnKey {
        HEADER_COLUMN_GROUP2,
        HEADER_COLUMN_GROUP1,
        HEADER_COLUMN,
        HEADER_COLUMN_EXTRA_PROPERTY_1,
        HEADER_COLUMN_EXTRA_PROPERTY_2,
        HEADER_COLUMN_EXTRA_PROPERTY_3,
        HEADER_COLUMN_EXTRA_PROPERTY_4,
        HEADER_COLUMN_EXTRA_PROPERTY_5,
        TRAILING_HEADER_COLUMN;
    }

    public enum HeaderRowKey {
        HEADER_ROW_GROUP2,
        HEADER_ROW_GROUP1,
        HEADER_ROW,
        TRAILING_HEADER_ROW;
    }

}

        int x1 = xMap.get(xObject1);
        int y1 = yMap.get(yObject1);
        int x2 = xMap.get(xObject2);
        int y2 = yMap.get(yObject2);
        add(component, new TimeTableLayoutConstraints(x1, y1, x2 - x1 + 1, y2 - y1 + 1));
    
<methods>public void <init>() ,public void <init>(java.awt.LayoutManager) ,public void <init>(boolean) ,public void <init>(java.awt.LayoutManager, boolean) ,public javax.accessibility.AccessibleContext getAccessibleContext() ,public javax.swing.plaf.PanelUI getUI() ,public java.lang.String getUIClassID() ,public void setUI(javax.swing.plaf.PanelUI) ,public void updateUI() <variables>private static final java.lang.String uiClassID