001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.gui;
021
022import java.io.Serial;
023
024import javax.swing.ListSelectionModel;
025import javax.swing.tree.DefaultTreeSelectionModel;
026import javax.swing.tree.TreePath;
027
028/**
029 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
030 * to listen for changes in the ListSelectionModel it maintains. Once
031 * a change in the ListSelectionModel happens, the paths are updated
032 * in the DefaultTreeSelectionModel.
033 *
034 */
035final class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
036
037    /** A unique serial version identifier. */
038    @Serial
039    private static final long serialVersionUID = 2267930983939339510L;
040    /** TreeTable to perform updates on. */
041    private final TreeTable treeTable;
042    /** Set to true when we are updating the ListSelectionModel. */
043    private boolean updatingListSelectionModel;
044
045    /**
046     * Constructor to initialise treeTable.
047     *
048     * @param jTreeTable TreeTable to perform updates on.
049     */
050    /* package */ ListToTreeSelectionModelWrapper(TreeTable jTreeTable) {
051        treeTable = jTreeTable;
052        getListSelectionModel().addListSelectionListener(event -> {
053            updateSelectedPathsFromSelectedRows();
054        });
055    }
056
057    /**
058     * Returns the list selection model. ListToTreeSelectionModelWrapper
059     * listens for changes to this model and updates the selected paths
060     * accordingly.
061     *
062     * @return the list selection model
063     */
064    public ListSelectionModel getListSelectionModel() {
065        return listSelectionModel;
066    }
067
068    /**
069     * This is overridden to set {@code updatingListSelectionModel}
070     * and message super. This is the only place DefaultTreeSelectionModel
071     * alters the ListSelectionModel.
072     */
073    @Override
074    public void resetRowSelection() {
075        if (!updatingListSelectionModel) {
076            updatingListSelectionModel = true;
077            try {
078                super.resetRowSelection();
079            }
080            finally {
081                updatingListSelectionModel = false;
082            }
083        }
084        // Notice how we don't message super if
085        // updatingListSelectionModel is true. If
086        // updatingListSelectionModel is true, it implies the
087        // ListSelectionModel has already been updated and the
088        // paths are the only thing that needs to be updated.
089    }
090
091    /**
092     * If {@code updatingListSelectionModel} is false, this will
093     * reset the selected paths from the selected rows in the list
094     * selection model.
095     */
096    private void updateSelectedPathsFromSelectedRows() {
097        if (!updatingListSelectionModel) {
098            updatingListSelectionModel = true;
099            try {
100                // This is way expensive, ListSelectionModel needs an
101                // enumerator for iterating.
102                final int min = listSelectionModel.getMinSelectionIndex();
103                final int max = listSelectionModel.getMaxSelectionIndex();
104
105                clearSelection();
106                if (min != -1 && max != -1) {
107                    for (int counter = min; counter <= max; counter++) {
108                        updateSelectedPathIfRowIsSelected(counter);
109                    }
110                }
111            }
112            finally {
113                updatingListSelectionModel = false;
114            }
115        }
116    }
117
118    /**
119     * If the row at given index is selected, selected paths are updated.
120     *
121     * @param counter number of row.
122     */
123    private void updateSelectedPathIfRowIsSelected(int counter) {
124        if (listSelectionModel.isSelectedIndex(counter)) {
125            final TreePath selPath = treeTable.getTree().getPathForRow(counter);
126
127            if (selPath != null) {
128                addSelectionPath(selPath);
129            }
130        }
131    }
132
133}